1 /* 2 * Copyright 2004-2006, Jérôme Duval. All rights reserved. 3 * Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de. 4 * Copyright 2008-2009, Stephan Aßmus, superstippi@gmx.de. 5 * 6 * Distributed under the terms of the MIT License. 7 */ 8 9 10 #include "KeyboardInputDevice.h" 11 12 #include <errno.h> 13 #include <new> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <unistd.h> 17 18 #include <Application.h> 19 #include <Autolock.h> 20 #include <Directory.h> 21 #include <Entry.h> 22 #include <NodeMonitor.h> 23 #include <Path.h> 24 #include <String.h> 25 26 #include <keyboard_mouse_driver.h> 27 28 29 #undef TRACE 30 31 //#define TRACE_KEYBOARD_DEVICE 32 #ifdef TRACE_KEYBOARD_DEVICE 33 34 class FunctionTracer { 35 public: 36 FunctionTracer(const void* pointer, const char* className, 37 const char* functionName) 38 : 39 fFunctionName(), 40 fPrepend(), 41 fPointer(pointer) 42 { 43 sFunctionDepth++; 44 fPrepend.Append(' ', sFunctionDepth * 2); 45 fFunctionName << className << "::" << functionName << "()"; 46 47 debug_printf("%p -> %s%s {\n", fPointer, fPrepend.String(), 48 fFunctionName.String()); 49 } 50 51 ~FunctionTracer() 52 { 53 debug_printf("%p -> %s}\n", fPointer, fPrepend.String()); 54 sFunctionDepth--; 55 } 56 57 static int32 Depth() const { return sFunctionDepth; } 58 59 private: 60 BString fFunctionName; 61 BString fPrepend; 62 const void* fPointer; 63 64 static int32 sFunctionDepth = -1; 65 }; 66 67 # define KD_CALLED(x...) \ 68 FunctionTracer _ft(this, "KeyboardDevice", __FUNCTION__) 69 # define KID_CALLED(x...) \ 70 FunctionTracer _ft(this, "KeyboardInputDevice", __FUNCTION__) 71 # define TRACE(x...) \ 72 do { BString _to; \ 73 _to.Append(' ', (FunctionTracer::Depth() + 1) * 2); \ 74 debug_printf("%p -> %s", this, _to.String()); \ 75 debug_printf(x); } while (0) 76 # define LOG_EVENT(text...) do {} while (0) 77 # define LOG_ERR(text...) TRACE(text) 78 #else 79 # define TRACE(x...) do {} while (0) 80 # define KD_CALLED(x...) TRACE(x) 81 # define KID_CALLED(x...) TRACE(x) 82 # define LOG_ERR(text...) debug_printf(text) 83 # define LOG_EVENT(text...) TRACE(x) 84 #endif 85 86 87 const static uint32 kKeyboardThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4; 88 const static char* kKeyboardDevicesDirectory = "/dev/input/keyboard"; 89 90 91 extern "C" BInputServerDevice* 92 instantiate_input_device() 93 { 94 return new(std::nothrow) KeyboardInputDevice(); 95 } 96 97 98 static char* 99 get_short_name(const char* longName) 100 { 101 BString string(longName); 102 BString name; 103 104 int32 slash = string.FindLast("/"); 105 string.CopyInto(name, slash + 1, string.Length() - slash); 106 int32 index = atoi(name.String()) + 1; 107 108 int32 previousSlash = string.FindLast("/", slash); 109 string.CopyInto(name, previousSlash + 1, slash - previousSlash - 1); 110 111 // some special handling so that we get "USB" and "AT" instead of "usb"/"at" 112 if (name.Length() < 4) 113 name.ToUpper(); 114 else 115 name.Capitalize(); 116 117 name << " Keyboard " << index; 118 119 return strdup(name.String()); 120 } 121 122 123 // #pragma mark - 124 125 126 KeyboardDevice::KeyboardDevice(KeyboardInputDevice* owner, const char* path) 127 : 128 BHandler("keyboard device"), 129 fOwner(owner), 130 fFD(-1), 131 fThread(-1), 132 fActive(false), 133 fInputMethodStarted(false), 134 fUpdateSettings(false), 135 fSettingsCommand(0), 136 fKeymapLock("keymap lock") 137 { 138 KD_CALLED(); 139 140 strlcpy(fPath, path, B_PATH_NAME_LENGTH); 141 fDeviceRef.name = get_short_name(path); 142 fDeviceRef.type = B_KEYBOARD_DEVICE; 143 fDeviceRef.cookie = this; 144 145 if (be_app->Lock()) { 146 be_app->AddHandler(this); 147 be_app->Unlock(); 148 } 149 } 150 151 152 KeyboardDevice::~KeyboardDevice() 153 { 154 KD_CALLED(); 155 TRACE("delete\n"); 156 157 if (fActive) 158 Stop(); 159 160 free(fDeviceRef.name); 161 162 if (be_app->Lock()) { 163 be_app->RemoveHandler(this); 164 be_app->Unlock(); 165 } 166 } 167 168 169 void 170 KeyboardDevice::MessageReceived(BMessage* message) 171 { 172 KD_CALLED(); 173 174 if (message->what != B_INPUT_METHOD_EVENT) { 175 BHandler::MessageReceived(message); 176 return; 177 } 178 179 int32 opcode; 180 if (message->FindInt32("be:opcode", &opcode) != B_OK) 181 return; 182 183 if (opcode == B_INPUT_METHOD_STOPPED) 184 fInputMethodStarted = false; 185 } 186 187 188 status_t 189 KeyboardDevice::Start() 190 { 191 KD_CALLED(); 192 TRACE("name: %s\n", fDeviceRef.name); 193 194 fFD = open(fPath, O_RDWR); 195 // let the control thread handle any error on opening the device 196 197 char threadName[B_OS_NAME_LENGTH]; 198 snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", fDeviceRef.name); 199 200 fThread = spawn_thread(_ControlThreadEntry, threadName, 201 kKeyboardThreadPriority, this); 202 if (fThread < B_OK) 203 return fThread; 204 205 fActive = true; 206 resume_thread(fThread); 207 208 return fFD >= 0 ? B_OK : B_ERROR; 209 } 210 211 212 void 213 KeyboardDevice::Stop() 214 { 215 KD_CALLED(); 216 TRACE("name: %s\n", fDeviceRef.name); 217 218 fActive = false; 219 220 close(fFD); 221 fFD = -1; 222 223 if (fThread >= 0) { 224 suspend_thread(fThread); 225 resume_thread(fThread); 226 status_t dummy; 227 wait_for_thread(fThread, &dummy); 228 } 229 } 230 231 232 status_t 233 KeyboardDevice::UpdateSettings(uint32 opcode) 234 { 235 KD_CALLED(); 236 237 if (fThread < 0) 238 return B_ERROR; 239 240 // schedule updating the settings from within the control thread 241 fSettingsCommand = opcode; 242 fUpdateSettings = true; 243 244 return B_OK; 245 } 246 247 248 // #pragma mark - control thread 249 250 251 /*static*/ int32 252 KeyboardDevice::_ControlThreadEntry(void* arg) 253 { 254 KeyboardDevice* device = (KeyboardDevice*)arg; 255 return device->_ControlThread(); 256 } 257 258 259 int32 260 KeyboardDevice::_ControlThread() 261 { 262 KD_CALLED(); 263 TRACE("fPath: %s\n", fPath); 264 265 if (fFD < B_OK) { 266 LOG_ERR("KeyboardDevice: error when opening %s: %s\n", 267 fPath, strerror(errno)); 268 _ControlThreadCleanup(); 269 // TOAST! 270 return B_ERROR; 271 } 272 273 _UpdateSettings(0); 274 275 raw_key_info keyInfo; 276 uint8 activeDeadKey = 0; 277 uint32 lastKeyCode = 0; 278 uint32 repeatCount = 1; 279 uint8 states[16]; 280 bool ctrlAltDelPressed = false; 281 282 memset(states, 0, sizeof(states)); 283 284 while (fActive) { 285 if (ioctl(fFD, KB_READ, &keyInfo, sizeof(keyInfo)) != B_OK) { 286 _ControlThreadCleanup(); 287 // TOAST! 288 return 0; 289 } 290 291 // Update the settings from this thread if necessary 292 if (fUpdateSettings) { 293 _UpdateSettings(fSettingsCommand); 294 fUpdateSettings = false; 295 } 296 297 uint32 keycode = keyInfo.keycode; 298 bool isKeyDown = keyInfo.is_keydown; 299 300 LOG_EVENT("KB_READ: %Ld, %02x, %02lx\n", keyInfo.timestamp, isKeyDown, 301 keycode); 302 303 if (keycode == 0) 304 continue; 305 306 if (isKeyDown && keycode == 0x68) { 307 // MENU KEY for Tracker 308 bool noOtherKeyPressed = true; 309 for (int32 i = 0; i < 16; i++) { 310 if (states[i] != 0) { 311 noOtherKeyPressed = false; 312 break; 313 } 314 } 315 316 if (noOtherKeyPressed) { 317 BMessenger deskbar("application/x-vnd.Be-TSKB"); 318 if (deskbar.IsValid()) 319 deskbar.SendMessage('BeMn'); 320 } 321 } 322 323 if (keycode < 256) { 324 if (isKeyDown) 325 states[(keycode) >> 3] |= (1 << (7 - (keycode & 0x7))); 326 else 327 states[(keycode) >> 3] &= (!(1 << (7 - (keycode & 0x7)))); 328 } 329 330 if (isKeyDown && keycode == 0x34 // DELETE KEY 331 && (states[fCommandKey >> 3] & (1 << (7 - (fCommandKey & 0x7)))) 332 && (states[fControlKey >> 3] & (1 << (7 - (fControlKey & 0x7))))) { 333 LOG_EVENT("TeamMonitor called\n"); 334 335 // show the team monitor 336 if (fOwner->fTeamMonitorWindow == NULL) 337 fOwner->fTeamMonitorWindow = new(std::nothrow) TeamMonitorWindow(); 338 339 if (fOwner->fTeamMonitorWindow != NULL) 340 fOwner->fTeamMonitorWindow->Enable(); 341 342 ctrlAltDelPressed = true; 343 } 344 345 if (ctrlAltDelPressed) { 346 if (fOwner->fTeamMonitorWindow != NULL) { 347 BMessage message(kMsgCtrlAltDelPressed); 348 message.AddBool("key down", isKeyDown); 349 fOwner->fTeamMonitorWindow->PostMessage(&message); 350 } 351 352 if (!isKeyDown) 353 ctrlAltDelPressed = false; 354 } 355 356 BAutolock lock(fKeymapLock); 357 358 uint32 modifiers = fKeymap.Modifier(keycode); 359 bool isLock 360 = (modifiers & (B_CAPS_LOCK | B_NUM_LOCK | B_SCROLL_LOCK)) != 0; 361 if (modifiers != 0 && (!isLock || isKeyDown)) { 362 uint32 oldModifiers = fModifiers; 363 364 if ((isKeyDown && !isLock) 365 || (isKeyDown && !(fModifiers & modifiers))) 366 fModifiers |= modifiers; 367 else { 368 fModifiers &= ~modifiers; 369 370 // ensure that we don't clear a combined B_*_KEY when still 371 // one of the individual B_{LEFT|RIGHT}_*_KEY is pressed 372 if (fModifiers & (B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY)) 373 fModifiers |= B_SHIFT_KEY; 374 if (fModifiers & (B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY)) 375 fModifiers |= B_COMMAND_KEY; 376 if (fModifiers & (B_LEFT_CONTROL_KEY | B_RIGHT_CONTROL_KEY)) 377 fModifiers |= B_CONTROL_KEY; 378 if (fModifiers & (B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY)) 379 fModifiers |= B_OPTION_KEY; 380 } 381 382 if (fModifiers != oldModifiers) { 383 BMessage* message = new BMessage(B_MODIFIERS_CHANGED); 384 if (message == NULL) 385 continue; 386 387 message->AddInt64("when", keyInfo.timestamp); 388 message->AddInt32("be:old_modifiers", oldModifiers); 389 message->AddInt32("modifiers", fModifiers); 390 message->AddData("states", B_UINT8_TYPE, states, 16); 391 392 if (fOwner->EnqueueMessage(message) != B_OK) 393 delete message; 394 395 if (isLock) 396 _UpdateLEDs(); 397 } 398 } 399 400 uint8 newDeadKey = 0; 401 if (activeDeadKey == 0 || !isKeyDown) 402 newDeadKey = fKeymap.ActiveDeadKey(keycode, fModifiers); 403 404 char* string = NULL; 405 char* rawString = NULL; 406 int32 numBytes = 0, rawNumBytes = 0; 407 if (newDeadKey == 0) { 408 fKeymap.GetChars(keycode, fModifiers, activeDeadKey, &string, 409 &numBytes); 410 } 411 fKeymap.GetChars(keycode, 0, 0, &rawString, &rawNumBytes); 412 413 BMessage* msg = new BMessage; 414 if (msg == NULL) { 415 delete[] string; 416 delete[] rawString; 417 continue; 418 } 419 420 if (numBytes > 0) 421 msg->what = isKeyDown ? B_KEY_DOWN : B_KEY_UP; 422 else 423 msg->what = isKeyDown ? B_UNMAPPED_KEY_DOWN : B_UNMAPPED_KEY_UP; 424 425 msg->AddInt64("when", keyInfo.timestamp); 426 msg->AddInt32("key", keycode); 427 msg->AddInt32("modifiers", fModifiers); 428 msg->AddData("states", B_UINT8_TYPE, states, 16); 429 if (numBytes > 0) { 430 for (int i = 0; i < numBytes; i++) 431 msg->AddInt8("byte", (int8)string[i]); 432 msg->AddData("bytes", B_STRING_TYPE, string, numBytes + 1); 433 434 if (rawNumBytes <= 0) { 435 rawNumBytes = 1; 436 delete[] rawString; 437 rawString = string; 438 } else 439 delete[] string; 440 441 if (isKeyDown && lastKeyCode == keycode) { 442 repeatCount++; 443 msg->AddInt32("be:key_repeat", repeatCount); 444 } else 445 repeatCount = 1; 446 } else 447 delete[] string; 448 449 if (rawNumBytes > 0) 450 msg->AddInt32("raw_char", (uint32)((uint8)rawString[0] & 0x7f)); 451 452 delete[] rawString; 453 454 if (newDeadKey == 0) { 455 if (isKeyDown && !modifiers && activeDeadKey != 0) { 456 // a dead key was completed 457 activeDeadKey = 0; 458 if (fInputMethodStarted) { 459 _EnqueueInlineInputMethod(B_INPUT_METHOD_CHANGED, 460 string, true, msg); 461 _EnqueueInlineInputMethod(B_INPUT_METHOD_STOPPED); 462 fInputMethodStarted = false; 463 msg = NULL; 464 } 465 } 466 } else if (isKeyDown 467 && _EnqueueInlineInputMethod(B_INPUT_METHOD_STARTED) == B_OK) { 468 // start of a dead key 469 char* string = NULL; 470 int32 numBytes = 0; 471 fKeymap.GetChars(keycode, fModifiers, 0, &string, &numBytes); 472 473 if (_EnqueueInlineInputMethod(B_INPUT_METHOD_CHANGED, string) 474 == B_OK) 475 fInputMethodStarted = true; 476 477 activeDeadKey = newDeadKey; 478 delete[] string; 479 } 480 481 if (msg != NULL && fOwner->EnqueueMessage(msg) != B_OK) 482 delete msg; 483 484 lastKeyCode = isKeyDown ? keycode : 0; 485 } 486 487 return 0; 488 } 489 490 491 void 492 KeyboardDevice::_ControlThreadCleanup() 493 { 494 // NOTE: Only executed when the control thread detected an error 495 // and from within the control thread! 496 497 if (fActive) { 498 fThread = -1; 499 fOwner->_RemoveDevice(fPath); 500 } else { 501 // In case active is already false, another thread 502 // waits for this thread to quit, and may already hold 503 // locks that _RemoveDevice() wants to acquire. In another 504 // words, the device is already being removed, so we simply 505 // quit here. 506 } 507 } 508 509 510 void 511 KeyboardDevice::_UpdateSettings(uint32 opcode) 512 { 513 KD_CALLED(); 514 515 if (opcode == 0 || opcode == B_KEY_REPEAT_RATE_CHANGED) { 516 if (get_key_repeat_rate(&fSettings.key_repeat_rate) != B_OK) { 517 LOG_ERR("error when get_key_repeat_rate\n"); 518 } else if (ioctl(fFD, KB_SET_KEY_REPEAT_RATE, 519 &fSettings.key_repeat_rate) != B_OK) { 520 LOG_ERR("error when KB_SET_KEY_REPEAT_RATE, fd:%d\n", fFD); 521 } 522 } 523 524 if (opcode == 0 || opcode == B_KEY_REPEAT_DELAY_CHANGED) { 525 if (get_key_repeat_delay(&fSettings.key_repeat_delay) != B_OK) { 526 LOG_ERR("error when get_key_repeat_delay\n"); 527 } else if (ioctl(fFD, KB_SET_KEY_REPEAT_DELAY, 528 &fSettings.key_repeat_delay) != B_OK) { 529 LOG_ERR("error when KB_SET_KEY_REPEAT_DELAY, fd:%d\n", fFD); 530 } 531 } 532 533 if (opcode == 0 || opcode == B_KEY_MAP_CHANGED 534 || opcode == B_KEY_LOCKS_CHANGED) { 535 BAutolock lock(fKeymapLock); 536 fKeymap.RetrieveCurrent(); 537 fModifiers = fKeymap.Map().lock_settings; 538 _UpdateLEDs(); 539 fControlKey = fKeymap.KeyForModifier(B_LEFT_CONTROL_KEY); 540 fCommandKey = fKeymap.KeyForModifier(B_LEFT_COMMAND_KEY); 541 } 542 } 543 544 545 void 546 KeyboardDevice::_UpdateLEDs() 547 { 548 if (fFD < 0) 549 return; 550 551 char lockIO[3] = {0, 0, 0}; 552 553 if ((fModifiers & B_NUM_LOCK) != 0) 554 lockIO[0] = 1; 555 if ((fModifiers & B_CAPS_LOCK) != 0) 556 lockIO[1] = 1; 557 if ((fModifiers & B_SCROLL_LOCK) != 0) 558 lockIO[2] = 1; 559 560 ioctl(fFD, KB_SET_LEDS, &lockIO); 561 } 562 563 564 status_t 565 KeyboardDevice::_EnqueueInlineInputMethod(int32 opcode, 566 const char* string, bool confirmed, BMessage* keyDown) 567 { 568 BMessage* message = new BMessage(B_INPUT_METHOD_EVENT); 569 if (message == NULL) 570 return B_NO_MEMORY; 571 572 message->AddInt32("be:opcode", opcode); 573 message->AddBool("be:inline_only", true); 574 575 if (string != NULL) 576 message->AddString("be:string", string); 577 if (confirmed) 578 message->AddBool("be:confirmed", true); 579 if (keyDown) 580 message->AddMessage("be:translated", keyDown); 581 if (opcode == B_INPUT_METHOD_STARTED) 582 message->AddMessenger("be:reply_to", this); 583 584 status_t status = fOwner->EnqueueMessage(message); 585 if (status != B_OK) 586 delete message; 587 588 return status; 589 } 590 591 592 // #pragma mark - 593 594 595 KeyboardInputDevice::KeyboardInputDevice() 596 : 597 fDevices(2, true), 598 fDeviceListLock("KeyboardInputDevice list"), 599 fTeamMonitorWindow(NULL) 600 { 601 KID_CALLED(); 602 603 StartMonitoringDevice(kKeyboardDevicesDirectory); 604 _RecursiveScan(kKeyboardDevicesDirectory); 605 } 606 607 608 KeyboardInputDevice::~KeyboardInputDevice() 609 { 610 KID_CALLED(); 611 612 if (fTeamMonitorWindow) { 613 fTeamMonitorWindow->PostMessage(B_QUIT_REQUESTED); 614 fTeamMonitorWindow = NULL; 615 } 616 617 StopMonitoringDevice(kKeyboardDevicesDirectory); 618 fDevices.MakeEmpty(); 619 } 620 621 622 status_t 623 KeyboardInputDevice::SystemShuttingDown() 624 { 625 KID_CALLED(); 626 if (fTeamMonitorWindow) 627 fTeamMonitorWindow->PostMessage(SYSTEM_SHUTTING_DOWN); 628 629 return B_OK; 630 } 631 632 633 status_t 634 KeyboardInputDevice::InitCheck() 635 { 636 KID_CALLED(); 637 return BInputServerDevice::InitCheck(); 638 } 639 640 641 status_t 642 KeyboardInputDevice::Start(const char* name, void* cookie) 643 { 644 KID_CALLED(); 645 TRACE("name %s\n", name); 646 647 KeyboardDevice* device = (KeyboardDevice*)cookie; 648 649 return device->Start(); 650 } 651 652 653 status_t 654 KeyboardInputDevice::Stop(const char* name, void* cookie) 655 { 656 KID_CALLED(); 657 TRACE("name %s\n", name); 658 659 KeyboardDevice* device = (KeyboardDevice*)cookie; 660 661 device->Stop(); 662 return B_OK; 663 } 664 665 666 status_t 667 KeyboardInputDevice::Control(const char* name, void* cookie, 668 uint32 command, BMessage* message) 669 { 670 KID_CALLED(); 671 TRACE("KeyboardInputDevice::Control(%s, code: %lu)\n", name, command); 672 673 if (command == B_NODE_MONITOR) 674 _HandleMonitor(message); 675 else if (command >= B_KEY_MAP_CHANGED 676 && command <= B_KEY_REPEAT_RATE_CHANGED) { 677 KeyboardDevice* device = (KeyboardDevice*)cookie; 678 device->UpdateSettings(command); 679 } 680 return B_OK; 681 } 682 683 684 status_t 685 KeyboardInputDevice::_HandleMonitor(BMessage* message) 686 { 687 KID_CALLED(); 688 689 const char* path; 690 int32 opcode; 691 if (message->FindInt32("opcode", &opcode) != B_OK 692 || (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED) 693 || message->FindString("path", &path) != B_OK) 694 return B_BAD_VALUE; 695 696 if (opcode == B_ENTRY_CREATED) 697 return _AddDevice(path); 698 699 #if 0 700 return _RemoveDevice(path); 701 #else 702 // Don't handle B_ENTRY_REMOVED, let the control thread take care of it. 703 return B_OK; 704 #endif 705 } 706 707 708 KeyboardDevice* 709 KeyboardInputDevice::_FindDevice(const char* path) const 710 { 711 for (int i = fDevices.CountItems() - 1; i >= 0; i--) { 712 KeyboardDevice* device = fDevices.ItemAt(i); 713 if (strcmp(device->Path(), path) == 0) 714 return device; 715 } 716 717 return NULL; 718 } 719 720 721 status_t 722 KeyboardInputDevice::_AddDevice(const char* path) 723 { 724 KID_CALLED(); 725 TRACE("path: %s\n", path); 726 727 BAutolock _(fDeviceListLock); 728 729 _RemoveDevice(path); 730 731 KeyboardDevice* device = new(std::nothrow) KeyboardDevice(this, path); 732 if (device == NULL) 733 return B_NO_MEMORY; 734 735 input_device_ref* devices[2]; 736 devices[0] = device->DeviceRef(); 737 devices[1] = NULL; 738 739 fDevices.AddItem(device); 740 741 return RegisterDevices(devices); 742 } 743 744 745 status_t 746 KeyboardInputDevice::_RemoveDevice(const char* path) 747 { 748 BAutolock _(fDeviceListLock); 749 750 KeyboardDevice* device = _FindDevice(path); 751 if (device == NULL) 752 return B_ENTRY_NOT_FOUND; 753 754 KID_CALLED(); 755 TRACE("path: %s\n", path); 756 757 input_device_ref* devices[2]; 758 devices[0] = device->DeviceRef(); 759 devices[1] = NULL; 760 761 UnregisterDevices(devices); 762 763 fDevices.RemoveItem(device); 764 765 return B_OK; 766 } 767 768 769 void 770 KeyboardInputDevice::_RecursiveScan(const char* directory) 771 { 772 KID_CALLED(); 773 TRACE("directory: %s\n", directory); 774 775 BEntry entry; 776 BDirectory dir(directory); 777 while (dir.GetNextEntry(&entry) == B_OK) { 778 BPath path; 779 entry.GetPath(&path); 780 if (entry.IsDirectory()) 781 _RecursiveScan(path.Path()); 782 else 783 _AddDevice(path.Path()); 784 } 785 } 786