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