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 strcpy(fPath, path); 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 void 212 KeyboardDevice::Stop() 213 { 214 KD_CALLED(); 215 TRACE("name: %s\n", fDeviceRef.name); 216 217 fActive = false; 218 219 close(fFD); 220 fFD = -1; 221 222 if (fThread >= 0) { 223 suspend_thread(fThread); 224 resume_thread(fThread); 225 status_t dummy; 226 wait_for_thread(fThread, &dummy); 227 } 228 } 229 230 231 status_t 232 KeyboardDevice::UpdateSettings(uint32 opcode) 233 { 234 KD_CALLED(); 235 236 if (fThread < 0) 237 return B_ERROR; 238 239 // schedule updating the settings from within the control thread 240 fSettingsCommand = opcode; 241 fUpdateSettings = true; 242 243 return B_OK; 244 } 245 246 247 // #pragma mark - control thread 248 249 250 /*static*/ int32 251 KeyboardDevice::_ControlThreadEntry(void* arg) 252 { 253 KeyboardDevice* device = (KeyboardDevice*)arg; 254 return device->_ControlThread(); 255 } 256 257 258 int32 259 KeyboardDevice::_ControlThread() 260 { 261 KD_CALLED(); 262 TRACE("fPath: %s\n", fPath); 263 264 if (fFD < B_OK) { 265 LOG_ERR("KeyboardDevice: error when opening %s: %s\n", 266 fPath, strerror(errno)); 267 _ControlThreadCleanup(); 268 // TOAST! 269 return B_ERROR; 270 } 271 272 _UpdateSettings(0); 273 274 raw_key_info keyInfo; 275 uint8 activeDeadKey = 0; 276 uint32 lastKeyCode = 0; 277 uint32 repeatCount = 1; 278 uint8 states[16]; 279 bool ctrlAltDelPressed = false; 280 281 memset(states, 0, sizeof(states)); 282 283 while (fActive) { 284 if (ioctl(fFD, KB_READ, &keyInfo) != B_OK) { 285 _ControlThreadCleanup(); 286 // TOAST! 287 return 0; 288 } 289 290 // Update the settings from this thread if necessary 291 if (fUpdateSettings) { 292 _UpdateSettings(fSettingsCommand); 293 fUpdateSettings = false; 294 } 295 296 uint32 keycode = keyInfo.keycode; 297 bool isKeyDown = keyInfo.is_keydown; 298 299 LOG_EVENT("KB_READ: %Ld, %02x, %02lx\n", keyInfo.timestamp, isKeyDown, 300 keycode); 301 302 if (keycode == 0) 303 continue; 304 305 if (isKeyDown && keycode == 0x68) { 306 // MENU KEY for Tracker 307 bool noOtherKeyPressed = true; 308 for (int32 i = 0; i < 16; i++) { 309 if (states[i] != 0) { 310 noOtherKeyPressed = false; 311 break; 312 } 313 } 314 315 if (noOtherKeyPressed) { 316 BMessenger deskbar("application/x-vnd.Be-TSKB"); 317 if (deskbar.IsValid()) 318 deskbar.SendMessage('BeMn'); 319 } 320 } 321 322 if (keycode < 256) { 323 if (isKeyDown) 324 states[(keycode) >> 3] |= (1 << (7 - (keycode & 0x7))); 325 else 326 states[(keycode) >> 3] &= (!(1 << (7 - (keycode & 0x7)))); 327 } 328 329 if (isKeyDown && keycode == 0x34 // DELETE KEY 330 && (states[fCommandKey >> 3] & (1 << (7 - (fCommandKey & 0x7)))) 331 && (states[fControlKey >> 3] & (1 << (7 - (fControlKey & 0x7))))) { 332 LOG_EVENT("TeamMonitor called\n"); 333 334 // show the team monitor 335 if (fOwner->fTeamMonitorWindow == NULL) 336 fOwner->fTeamMonitorWindow = new(std::nothrow) TeamMonitorWindow(); 337 338 if (fOwner->fTeamMonitorWindow != NULL) 339 fOwner->fTeamMonitorWindow->Enable(); 340 341 ctrlAltDelPressed = true; 342 } 343 344 if (ctrlAltDelPressed) { 345 if (fOwner->fTeamMonitorWindow != NULL) { 346 BMessage message(kMsgCtrlAltDelPressed); 347 message.AddBool("key down", isKeyDown); 348 fOwner->fTeamMonitorWindow->PostMessage(&message); 349 } 350 351 if (!isKeyDown) 352 ctrlAltDelPressed = false; 353 } 354 355 BAutolock lock(fKeymapLock); 356 357 uint32 modifiers = fKeymap.Modifier(keycode); 358 bool isLock 359 = (modifiers & (B_CAPS_LOCK | B_NUM_LOCK | B_SCROLL_LOCK)) != 0; 360 if (modifiers != 0 && (!isLock || isKeyDown)) { 361 uint32 oldModifiers = fModifiers; 362 363 if ((isKeyDown && !isLock) 364 || (isKeyDown && !(fModifiers & modifiers))) 365 fModifiers |= modifiers; 366 else { 367 fModifiers &= ~modifiers; 368 369 // ensure that we don't clear a combined B_*_KEY when still 370 // one of the individual B_{LEFT|RIGHT}_*_KEY is pressed 371 if (fModifiers & (B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY)) 372 fModifiers |= B_SHIFT_KEY; 373 if (fModifiers & (B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY)) 374 fModifiers |= B_COMMAND_KEY; 375 if (fModifiers & (B_LEFT_CONTROL_KEY | B_RIGHT_CONTROL_KEY)) 376 fModifiers |= B_CONTROL_KEY; 377 if (fModifiers & (B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY)) 378 fModifiers |= B_OPTION_KEY; 379 } 380 381 if (fModifiers != oldModifiers) { 382 BMessage* message = new BMessage(B_MODIFIERS_CHANGED); 383 if (message == NULL) 384 continue; 385 386 message->AddInt64("when", keyInfo.timestamp); 387 message->AddInt32("be:old_modifiers", oldModifiers); 388 message->AddInt32("modifiers", fModifiers); 389 message->AddData("states", B_UINT8_TYPE, states, 16); 390 391 if (fOwner->EnqueueMessage(message) != B_OK) 392 delete message; 393 394 if (isLock) 395 _UpdateLEDs(); 396 } 397 } 398 399 uint8 newDeadKey = 0; 400 if (activeDeadKey == 0 || !isKeyDown) 401 newDeadKey = fKeymap.ActiveDeadKey(keycode, fModifiers); 402 403 char* string = NULL; 404 char* rawString = NULL; 405 int32 numBytes = 0, rawNumBytes = 0; 406 if (newDeadKey == 0) { 407 fKeymap.GetChars(keycode, fModifiers, activeDeadKey, &string, 408 &numBytes); 409 } 410 fKeymap.GetChars(keycode, 0, 0, &rawString, &rawNumBytes); 411 412 BMessage* msg = new BMessage; 413 if (msg == NULL) { 414 delete[] string; 415 delete[] rawString; 416 continue; 417 } 418 419 if (numBytes > 0) 420 msg->what = isKeyDown ? B_KEY_DOWN : B_KEY_UP; 421 else 422 msg->what = isKeyDown ? B_UNMAPPED_KEY_DOWN : B_UNMAPPED_KEY_UP; 423 424 msg->AddInt64("when", keyInfo.timestamp); 425 msg->AddInt32("key", keycode); 426 msg->AddInt32("modifiers", fModifiers); 427 msg->AddData("states", B_UINT8_TYPE, states, 16); 428 if (numBytes > 0) { 429 for (int i = 0; i < numBytes; i++) 430 msg->AddInt8("byte", (int8)string[i]); 431 msg->AddData("bytes", B_STRING_TYPE, string, numBytes + 1); 432 433 if (rawNumBytes <= 0) { 434 rawNumBytes = 1; 435 delete[] rawString; 436 rawString = string; 437 } else 438 delete[] string; 439 440 if (isKeyDown && lastKeyCode == keycode) { 441 repeatCount++; 442 msg->AddInt32("be:key_repeat", repeatCount); 443 } else 444 repeatCount = 1; 445 } else 446 delete[] string; 447 448 if (rawNumBytes > 0) 449 msg->AddInt32("raw_char", (uint32)((uint8)rawString[0] & 0x7f)); 450 451 delete[] rawString; 452 453 if (newDeadKey == 0) { 454 if (isKeyDown && !modifiers && activeDeadKey != 0) { 455 // a dead key was completed 456 activeDeadKey = 0; 457 if (fInputMethodStarted) { 458 _EnqueueInlineInputMethod(B_INPUT_METHOD_CHANGED, 459 string, true, msg); 460 _EnqueueInlineInputMethod(B_INPUT_METHOD_STOPPED); 461 fInputMethodStarted = false; 462 msg = NULL; 463 } 464 } 465 } else if (isKeyDown 466 && _EnqueueInlineInputMethod(B_INPUT_METHOD_STARTED) == B_OK) { 467 // start of a dead key 468 char* string = NULL; 469 int32 numBytes = 0; 470 fKeymap.GetChars(keycode, fModifiers, 0, &string, &numBytes); 471 472 if (_EnqueueInlineInputMethod(B_INPUT_METHOD_CHANGED, string) 473 == B_OK) 474 fInputMethodStarted = true; 475 476 activeDeadKey = newDeadKey; 477 } 478 479 if (msg != NULL && fOwner->EnqueueMessage(msg) != B_OK) 480 delete msg; 481 482 lastKeyCode = isKeyDown ? keycode : 0; 483 } 484 485 return 0; 486 } 487 488 489 void 490 KeyboardDevice::_ControlThreadCleanup() 491 { 492 // NOTE: Only executed when the control thread detected an error 493 // and from within the control thread! 494 495 if (fActive) { 496 fThread = -1; 497 fOwner->_RemoveDevice(fPath); 498 } else { 499 // In case active is already false, another thread 500 // waits for this thread to quit, and may already hold 501 // locks that _RemoveDevice() wants to acquire. In another 502 // words, the device is already being removed, so we simply 503 // quit here. 504 } 505 } 506 507 508 void 509 KeyboardDevice::_UpdateSettings(uint32 opcode) 510 { 511 KD_CALLED(); 512 513 if (opcode == 0 || opcode == B_KEY_REPEAT_RATE_CHANGED) { 514 if (get_key_repeat_rate(&fSettings.key_repeat_rate) != B_OK) { 515 LOG_ERR("error when get_key_repeat_rate\n"); 516 } else if (ioctl(fFD, KB_SET_KEY_REPEAT_RATE, 517 &fSettings.key_repeat_rate) != B_OK) { 518 LOG_ERR("error when KB_SET_KEY_REPEAT_RATE, fd:%d\n", fFD); 519 } 520 } 521 522 if (opcode == 0 || opcode == B_KEY_REPEAT_DELAY_CHANGED) { 523 if (get_key_repeat_delay(&fSettings.key_repeat_delay) != B_OK) { 524 LOG_ERR("error when get_key_repeat_delay\n"); 525 } else if (ioctl(fFD, KB_SET_KEY_REPEAT_DELAY, 526 &fSettings.key_repeat_delay) != B_OK) { 527 LOG_ERR("error when KB_SET_KEY_REPEAT_DELAY, fd:%d\n", fFD); 528 } 529 } 530 531 if (opcode == 0 || opcode == B_KEY_MAP_CHANGED 532 || opcode == B_KEY_LOCKS_CHANGED) { 533 BAutolock lock(fKeymapLock); 534 fKeymap.RetrieveCurrent(); 535 fModifiers = fKeymap.Map().lock_settings; 536 _UpdateLEDs(); 537 fControlKey = fKeymap.KeyForModifier(B_LEFT_CONTROL_KEY); 538 fCommandKey = fKeymap.KeyForModifier(B_LEFT_COMMAND_KEY); 539 } 540 } 541 542 543 void 544 KeyboardDevice::_UpdateLEDs() 545 { 546 if (fFD < 0) 547 return; 548 549 char lockIO[3] = {0, 0, 0}; 550 551 if ((fModifiers & B_NUM_LOCK) != 0) 552 lockIO[0] = 1; 553 if ((fModifiers & B_CAPS_LOCK) != 0) 554 lockIO[1] = 1; 555 if ((fModifiers & B_SCROLL_LOCK) != 0) 556 lockIO[2] = 1; 557 558 ioctl(fFD, KB_SET_LEDS, &lockIO); 559 } 560 561 562 status_t 563 KeyboardDevice::_EnqueueInlineInputMethod(int32 opcode, 564 const char* string, bool confirmed, BMessage* keyDown) 565 { 566 BMessage* message = new BMessage(B_INPUT_METHOD_EVENT); 567 if (message == NULL) 568 return B_NO_MEMORY; 569 570 message->AddInt32("be:opcode", opcode); 571 message->AddBool("be:inline_only", true); 572 573 if (string != NULL) 574 message->AddString("be:string", string); 575 if (confirmed) 576 message->AddBool("be:confirmed", true); 577 if (keyDown) 578 message->AddMessage("be:translated", keyDown); 579 if (opcode == B_INPUT_METHOD_STARTED) 580 message->AddMessenger("be:reply_to", this); 581 582 status_t status = fOwner->EnqueueMessage(message); 583 if (status != B_OK) 584 delete message; 585 586 return status; 587 } 588 589 590 // #pragma mark - 591 592 593 KeyboardInputDevice::KeyboardInputDevice() 594 : 595 fDevices(2, true), 596 fDeviceListLock("KeyboardInputDevice list"), 597 fTeamMonitorWindow(NULL) 598 { 599 KID_CALLED(); 600 601 StartMonitoringDevice(kKeyboardDevicesDirectory); 602 _RecursiveScan(kKeyboardDevicesDirectory); 603 } 604 605 606 KeyboardInputDevice::~KeyboardInputDevice() 607 { 608 KID_CALLED(); 609 610 if (fTeamMonitorWindow) { 611 fTeamMonitorWindow->PostMessage(B_QUIT_REQUESTED); 612 fTeamMonitorWindow = NULL; 613 } 614 615 StopMonitoringDevice(kKeyboardDevicesDirectory); 616 fDevices.MakeEmpty(); 617 } 618 619 620 status_t 621 KeyboardInputDevice::SystemShuttingDown() 622 { 623 KID_CALLED(); 624 if (fTeamMonitorWindow) 625 fTeamMonitorWindow->PostMessage(SYSTEM_SHUTTING_DOWN); 626 627 return B_OK; 628 } 629 630 631 status_t 632 KeyboardInputDevice::InitCheck() 633 { 634 KID_CALLED(); 635 return BInputServerDevice::InitCheck(); 636 } 637 638 639 status_t 640 KeyboardInputDevice::Start(const char* name, void* cookie) 641 { 642 KID_CALLED(); 643 TRACE("name %s\n", name); 644 645 KeyboardDevice* device = (KeyboardDevice*)cookie; 646 647 return device->Start(); 648 } 649 650 651 status_t 652 KeyboardInputDevice::Stop(const char* name, void* cookie) 653 { 654 KID_CALLED(); 655 TRACE("name %s\n", name); 656 657 KeyboardDevice* device = (KeyboardDevice*)cookie; 658 659 device->Stop(); 660 return B_OK; 661 } 662 663 664 status_t 665 KeyboardInputDevice::Control(const char* name, void* cookie, 666 uint32 command, BMessage* message) 667 { 668 KID_CALLED(); 669 TRACE("KeyboardInputDevice::Control(%s, code: %lu)\n", name, command); 670 671 if (command == B_NODE_MONITOR) 672 _HandleMonitor(message); 673 else if (command >= B_KEY_MAP_CHANGED 674 && command <= B_KEY_REPEAT_RATE_CHANGED) { 675 KeyboardDevice* device = (KeyboardDevice*)cookie; 676 device->UpdateSettings(command); 677 } 678 return B_OK; 679 } 680 681 682 status_t 683 KeyboardInputDevice::_HandleMonitor(BMessage* message) 684 { 685 KID_CALLED(); 686 687 const char* path; 688 int32 opcode; 689 if (message->FindInt32("opcode", &opcode) != B_OK 690 || (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED) 691 || message->FindString("path", &path) != B_OK) 692 return B_BAD_VALUE; 693 694 if (opcode == B_ENTRY_CREATED) 695 return _AddDevice(path); 696 697 #if 0 698 return _RemoveDevice(path); 699 #else 700 // Don't handle B_ENTRY_REMOVED, let the control thread take care of it. 701 return B_OK; 702 #endif 703 } 704 705 706 KeyboardDevice* 707 KeyboardInputDevice::_FindDevice(const char* path) const 708 { 709 for (int i = fDevices.CountItems() - 1; i >= 0; i--) { 710 KeyboardDevice* device = fDevices.ItemAt(i); 711 if (strcmp(device->Path(), path) == 0) 712 return device; 713 } 714 715 return NULL; 716 } 717 718 719 status_t 720 KeyboardInputDevice::_AddDevice(const char* path) 721 { 722 KID_CALLED(); 723 TRACE("path: %s\n", path); 724 725 BAutolock _(fDeviceListLock); 726 727 _RemoveDevice(path); 728 729 KeyboardDevice* device = new(std::nothrow) KeyboardDevice(this, path); 730 if (device == NULL) 731 return B_NO_MEMORY; 732 733 input_device_ref* devices[2]; 734 devices[0] = device->DeviceRef(); 735 devices[1] = NULL; 736 737 fDevices.AddItem(device); 738 739 return RegisterDevices(devices); 740 } 741 742 743 status_t 744 KeyboardInputDevice::_RemoveDevice(const char* path) 745 { 746 BAutolock _(fDeviceListLock); 747 748 KeyboardDevice* device = _FindDevice(path); 749 if (device == NULL) 750 return B_ENTRY_NOT_FOUND; 751 752 KID_CALLED(); 753 TRACE("path: %s\n", path); 754 755 input_device_ref* devices[2]; 756 devices[0] = device->DeviceRef(); 757 devices[1] = NULL; 758 759 UnregisterDevices(devices); 760 761 fDevices.RemoveItem(device); 762 763 return B_OK; 764 } 765 766 767 void 768 KeyboardInputDevice::_RecursiveScan(const char* directory) 769 { 770 KID_CALLED(); 771 TRACE("directory: %s\n", directory); 772 773 BEntry entry; 774 BDirectory dir(directory); 775 while (dir.GetNextEntry(&entry) == B_OK) { 776 BPath path; 777 entry.GetPath(&path); 778 if (entry.IsDirectory()) 779 _RecursiveScan(path.Path()); 780 else 781 _AddDevice(path.Path()); 782 } 783 } 784