1/*
2 * Copyright 2004-2011, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
7 *		J��r��me Duval
8 *		Axel D��rfler, axeld@pinc-software.de
9 *		Clemens Zeidler, haiku@clemens-zeidler.de
10 *		Stephan A��mus, superstippi@gmx.de
11 */
12
13
14#include "MouseInputDevice.h"
15
16#include <errno.h>
17#include <new>
18#include <stdio.h>
19#include <stdlib.h>
20#include <unistd.h>
21
22#include <Autolock.h>
23#include <Debug.h>
24#include <Directory.h>
25#include <Entry.h>
26#include <File.h>
27#include <FindDirectory.h>
28#include <NodeMonitor.h>
29#include <Path.h>
30#include <String.h>
31#include <View.h>
32
33#include <kb_mouse_settings.h>
34#include <keyboard_mouse_driver.h>
35#include <touchpad_settings.h>
36
37#include "movement_maker.h"
38
39
40#undef TRACE
41//#define TRACE_MOUSE_DEVICE
42#ifdef TRACE_MOUSE_DEVICE
43
44	class FunctionTracer {
45	public:
46		FunctionTracer(const void* pointer, const char* className,
47				const char* functionName,
48				int32& depth)
49			: fFunctionName(),
50			  fPrepend(),
51			  fFunctionDepth(depth),
52			  fPointer(pointer)
53		{
54			fFunctionDepth++;
55			fPrepend.Append(' ', fFunctionDepth * 2);
56			fFunctionName << className << "::" << functionName << "()";
57
58			debug_printf("%p -> %s%s {\n", fPointer, fPrepend.String(),
59				fFunctionName.String());
60		}
61
62		 ~FunctionTracer()
63		{
64			debug_printf("%p -> %s}\n", fPointer, fPrepend.String());
65			fFunctionDepth--;
66		}
67
68	private:
69		BString	fFunctionName;
70		BString	fPrepend;
71		int32&	fFunctionDepth;
72		const void* fPointer;
73	};
74
75
76	static int32 sFunctionDepth = -1;
77#	define MD_CALLED(x...)	FunctionTracer _ft(this, "MouseDevice", \
78								__FUNCTION__, sFunctionDepth)
79#	define MID_CALLED(x...)	FunctionTracer _ft(this, "MouseInputDevice", \
80								__FUNCTION__, sFunctionDepth)
81#	define TRACE(x...)	do { BString _to; \
82							_to.Append(' ', (sFunctionDepth + 1) * 2); \
83							debug_printf("%p -> %s", this, _to.String()); \
84							debug_printf(x); } while (0)
85#	define LOG_EVENT(text...) do {} while (0)
86#	define LOG_ERR(text...) TRACE(text)
87#else
88#	define TRACE(x...) do {} while (0)
89#	define MD_CALLED(x...) TRACE(x)
90#	define MID_CALLED(x...) TRACE(x)
91#	define LOG_ERR(x...) debug_printf(x)
92#	define LOG_EVENT(x...) TRACE(x)
93#endif
94
95
96const static uint32 kMouseThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
97const static char* kMouseDevicesDirectory = "/dev/input/mouse";
98const static char* kTouchpadDevicesDirectory = "/dev/input/touchpad";
99
100
101class MouseDevice {
102public:
103								MouseDevice(MouseInputDevice& target,
104									const char* path);
105								~MouseDevice();
106
107			status_t			Start();
108			void				Stop();
109
110			status_t			UpdateSettings();
111			status_t			UpdateTouchpadSettings(const BMessage* message);
112
113			const char*			Path() const { return fPath.String(); }
114			input_device_ref*	DeviceRef() { return &fDeviceRef; }
115
116private:
117			char*				_BuildShortName() const;
118
119	static	status_t			_ControlThreadEntry(void* arg);
120			void				_ControlThread();
121			void				_ControlThreadCleanup();
122			void				_UpdateSettings();
123
124			status_t			_GetTouchpadSettingsPath(BPath& path);
125			status_t			_UpdateTouchpadSettings(BMessage* message);
126
127			BMessage*			_BuildMouseMessage(uint32 what,
128									uint64 when, uint32 buttons,
129									int32 deltaX, int32 deltaY) const;
130			void				_ComputeAcceleration(
131									const mouse_movement& movements,
132									int32& deltaX, int32& deltaY,
133									float& historyDeltaX,
134									float& historyDeltaY) const;
135			uint32				_RemapButtons(uint32 buttons) const;
136
137private:
138			MouseInputDevice&	fTarget;
139			BString				fPath;
140			int					fDevice;
141
142			input_device_ref	fDeviceRef;
143			mouse_settings		fSettings;
144			bool				fDeviceRemapsButtons;
145
146			thread_id			fThread;
147	volatile bool				fActive;
148	volatile bool				fUpdateSettings;
149
150			bool				fIsTouchpad;
151			TouchpadMovement	fTouchpadMovementMaker;
152			BMessage*			fTouchpadSettingsMessage;
153			BLocker				fTouchpadSettingsLock;
154};
155
156
157extern "C" BInputServerDevice*
158instantiate_input_device()
159{
160	return new(std::nothrow) MouseInputDevice();
161}
162
163
164//	#pragma mark -
165
166
167MouseDevice::MouseDevice(MouseInputDevice& target, const char* driverPath)
168	:
169	fTarget(target),
170	fPath(driverPath),
171	fDevice(-1),
172	fDeviceRemapsButtons(false),
173	fThread(-1),
174	fActive(false),
175	fUpdateSettings(false),
176	fIsTouchpad(false),
177	fTouchpadSettingsMessage(NULL),
178	fTouchpadSettingsLock("Touchpad settings lock")
179{
180	MD_CALLED();
181
182	fDeviceRef.name = _BuildShortName();
183	fDeviceRef.type = B_POINTING_DEVICE;
184	fDeviceRef.cookie = this;
185
186#ifdef HAIKU_TARGET_PLATFORM_HAIKU
187	for (int i = 0; i < B_MAX_MOUSE_BUTTONS; i++)
188		fSettings.map.button[i] = B_MOUSE_BUTTON(i + 1);
189#endif
190};
191
192
193MouseDevice::~MouseDevice()
194{
195	MD_CALLED();
196	TRACE("delete\n");
197
198	if (fActive)
199		Stop();
200
201	free(fDeviceRef.name);
202	delete fTouchpadSettingsMessage;
203}
204
205
206status_t
207MouseDevice::Start()
208{
209	MD_CALLED();
210
211	fDevice = open(fPath.String(), O_RDWR);
212		// let the control thread handle any error on opening the device
213
214	char threadName[B_OS_NAME_LENGTH];
215	snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", fDeviceRef.name);
216
217	fThread = spawn_thread(_ControlThreadEntry, threadName,
218		kMouseThreadPriority, (void*)this);
219
220	status_t status;
221	if (fThread < 0)
222		status = fThread;
223	else {
224		fActive = true;
225		status = resume_thread(fThread);
226	}
227
228	if (status < B_OK) {
229		LOG_ERR("%s: can't spawn/resume watching thread: %s\n",
230			fDeviceRef.name, strerror(status));
231		if (fDevice >= 0)
232			close(fDevice);
233
234		return status;
235	}
236
237	return fDevice >= 0 ? B_OK : B_ERROR;
238}
239
240
241void
242MouseDevice::Stop()
243{
244	MD_CALLED();
245
246	fActive = false;
247		// this will stop the thread as soon as it reads the next packet
248
249	close(fDevice);
250	fDevice = -1;
251
252	if (fThread >= 0) {
253		// unblock the thread, which might wait on a semaphore.
254		suspend_thread(fThread);
255		resume_thread(fThread);
256
257		status_t dummy;
258		wait_for_thread(fThread, &dummy);
259	}
260}
261
262
263status_t
264MouseDevice::UpdateSettings()
265{
266	MD_CALLED();
267
268	if (fThread < 0)
269		return B_ERROR;
270
271	// trigger updating the settings in the control thread
272	fUpdateSettings = true;
273
274	return B_OK;
275}
276
277
278status_t
279MouseDevice::UpdateTouchpadSettings(const BMessage* message)
280{
281	if (!fIsTouchpad)
282		return B_BAD_TYPE;
283	if (fThread < 0)
284		return B_ERROR;
285
286	BAutolock _(fTouchpadSettingsLock);
287
288	// trigger updating the settings in the control thread
289	fUpdateSettings = true;
290
291	delete fTouchpadSettingsMessage;
292	fTouchpadSettingsMessage = new BMessage(*message);
293	if (fTouchpadSettingsMessage == NULL)
294		return B_NO_MEMORY;
295
296	return B_OK;
297}
298
299
300char*
301MouseDevice::_BuildShortName() const
302{
303	// TODO It would be simpler and better to use B_GET_DEVICE_NAME, but...
304	// - This is currently called before the device is open
305	// - We need to implement that in our input drivers first
306	BString string(fPath);
307	BString deviceName;
308	BString name;
309
310	int32 slash = string.FindLast("/");
311	string.CopyInto(deviceName, slash + 1, string.Length() - slash);
312	// FIXME the device name may be more than just a number (for example
313	// ibm_trackpoint_0)
314	int32 index = atoi(deviceName.String()) + 1;
315
316	int32 previousSlash = string.FindLast("/", slash);
317	string.CopyInto(name, previousSlash + 1, slash - previousSlash - 1);
318
319	if (name == "ps2")
320		name = "PS/2";
321
322	if (name.Length() <= 4)
323		name.ToUpper();
324	else
325		name.Capitalize();
326
327	// Check the whole string for "touchpad" because it's a different directory
328	// FIXME use MS_IS_TOUCHPAD ioctl instead (or fIsTouchpad which caches its
329	// result) but this can only be done after the control thread is running
330	if (string.FindFirst("touchpad") >= 0) {
331		name << " Touchpad ";
332	} else if (deviceName.FindFirst("trackpoint") >= 0) {
333		// That's always PS/2, so don't keep the bus name
334		name = "Trackpoint ";
335	} else {
336		if (deviceName.FindFirst("intelli") >= 0)
337			name.Prepend("Extended ");
338
339		name << " Mouse ";
340	}
341	name << index;
342
343	return strdup(name.String());
344}
345
346
347// #pragma mark - control thread
348
349
350status_t
351MouseDevice::_ControlThreadEntry(void* arg)
352{
353	MouseDevice* device = (MouseDevice*)arg;
354	device->_ControlThread();
355	return B_OK;
356}
357
358
359void
360MouseDevice::_ControlThread()
361{
362	MD_CALLED();
363
364	if (fDevice < 0) {
365		_ControlThreadCleanup();
366		// TOAST!
367		return;
368	}
369
370	// touchpad settings
371	touchpad_specs touchpadSpecs;
372	if (ioctl(fDevice, MS_IS_TOUCHPAD, &touchpadSpecs, sizeof(touchpadSpecs)) == B_OK) {
373		TRACE("is touchpad %s\n", fPath.String());
374		fIsTouchpad = true;
375
376		touchpad_settings settings;
377		settings = kDefaultTouchpadSettings;
378
379		BPath path;
380		status_t status = _GetTouchpadSettingsPath(path);
381		BFile settingsFile(path.Path(), B_READ_ONLY);
382		if (status == B_OK && settingsFile.InitCheck() == B_OK) {
383			if (settingsFile.Read(&settings, sizeof(touchpad_settings))
384					!= sizeof(touchpad_settings)) {
385				TRACE("failed to load settings\n");
386			}
387		}
388
389		fTouchpadMovementMaker.SetSpecs(touchpadSpecs);
390		fTouchpadMovementMaker.SetSettings(settings);
391	}
392
393	_UpdateSettings();
394
395	uint32 lastButtons = 0;
396	float historyDeltaX = 0.0;
397	float historyDeltaY = 0.0;
398
399	static const bigtime_t kTransferDelay = 1000000 / 125;
400		// 125 transfers per second should be more than enough
401#define USE_REGULAR_INTERVAL 1
402#if USE_REGULAR_INTERVAL
403	bigtime_t nextTransferTime = system_time() + kTransferDelay;
404#endif
405
406	// touchpads only
407	touchpad_movement lastTouchpadMovement;
408	bigtime_t touchpadEventTimeout = B_INFINITE_TIMEOUT;
409
410	while (fActive) {
411		mouse_movement movements;
412
413#if USE_REGULAR_INTERVAL
414		snooze_until(nextTransferTime, B_SYSTEM_TIMEBASE);
415		nextTransferTime += kTransferDelay;
416#endif
417
418		if (!fIsTouchpad) {
419			if (ioctl(fDevice, MS_READ, &movements, sizeof(movements)) != B_OK) {
420				LOG_ERR("Mouse device exiting, %s\n", strerror(errno));
421				_ControlThreadCleanup();
422				// TOAST!
423				return;
424			}
425		} else {
426			touchpad_read read;
427			read.timeout = touchpadEventTimeout;
428
429			status_t status = ioctl(fDevice, MS_READ_TOUCHPAD, &read, sizeof(read));
430			if (status < 0)
431				status = errno;
432			if (status != B_OK && status != B_TIMED_OUT) {
433				LOG_ERR("Mouse (touchpad) device exiting, %s\n", strerror(errno));
434				_ControlThreadCleanup();
435				// TOAST!
436				return;
437			} else if (status == B_TIMED_OUT) {
438				read.event = MS_READ_TOUCHPAD;
439				read.u.touchpad = lastTouchpadMovement;
440			}
441
442			if (read.event == MS_READ_TOUCHPAD) {
443				lastTouchpadMovement = read.u.touchpad;
444				status = fTouchpadMovementMaker.EventToMovement(&read.u.touchpad,
445					&movements, touchpadEventTimeout);
446			} else if (read.event == MS_READ) {
447				movements = read.u.mouse;
448				touchpadEventTimeout = -1;
449			}
450
451			if (status != B_OK)
452				continue;
453		}
454
455		// take care of updating the settings first, if necessary
456		if (fUpdateSettings) {
457			fUpdateSettings = false;
458			if (fIsTouchpad) {
459				BAutolock _(fTouchpadSettingsLock);
460				if (fTouchpadSettingsMessage != NULL) {
461					_UpdateTouchpadSettings(fTouchpadSettingsMessage);
462					delete fTouchpadSettingsMessage;
463					fTouchpadSettingsMessage = NULL;
464				} else
465					_UpdateSettings();
466			} else
467				_UpdateSettings();
468		}
469
470		uint32 buttons = lastButtons ^ movements.buttons;
471
472		uint32 remappedButtons = _RemapButtons(movements.buttons);
473		int32 deltaX, deltaY;
474		_ComputeAcceleration(movements, deltaX, deltaY, historyDeltaX,
475			historyDeltaY);
476
477		LOG_EVENT("%s: buttons: 0x%lx, x: %ld, y: %ld, clicks:%ld, "
478			"wheel_x:%ld, wheel_y:%ld\n",
479			fDeviceRef.name, movements.buttons,
480			movements.xdelta, movements.ydelta, movements.clicks,
481			movements.wheel_xdelta, movements.wheel_ydelta);
482		LOG_EVENT("%s: x: %ld, y: %ld (%.4f, %.4f)\n", fDeviceRef.name,
483			deltaX, deltaY, historyDeltaX, historyDeltaY);
484
485		// Send single messages for each event
486
487		if (buttons != 0) {
488			bool pressedButton = (buttons & movements.buttons) > 0;
489			BMessage* message = _BuildMouseMessage(
490				pressedButton ? B_MOUSE_DOWN : B_MOUSE_UP,
491				movements.timestamp, remappedButtons, deltaX, deltaY);
492			if (message != NULL) {
493				if (pressedButton) {
494					message->AddInt32("clicks", movements.clicks);
495					LOG_EVENT("B_MOUSE_DOWN\n");
496				} else
497					LOG_EVENT("B_MOUSE_UP\n");
498
499				fTarget.EnqueueMessage(message);
500				lastButtons = movements.buttons;
501			}
502		}
503
504		if (movements.xdelta != 0 || movements.ydelta != 0) {
505			BMessage* message = _BuildMouseMessage(B_MOUSE_MOVED,
506				movements.timestamp, remappedButtons, deltaX, deltaY);
507			if (message != NULL)
508				fTarget.EnqueueMessage(message);
509		}
510
511		if (movements.wheel_ydelta != 0 || movements.wheel_xdelta != 0) {
512			BMessage* message = new BMessage(B_MOUSE_WHEEL_CHANGED);
513			if (message == NULL)
514				continue;
515
516			if (message->AddInt64("when", movements.timestamp) == B_OK
517				&& message->AddFloat("be:wheel_delta_x",
518					movements.wheel_xdelta) == B_OK
519				&& message->AddFloat("be:wheel_delta_y",
520					movements.wheel_ydelta) == B_OK)
521				fTarget.EnqueueMessage(message);
522			else
523				delete message;
524		}
525
526#if !USE_REGULAR_INTERVAL
527		snooze(kTransferDelay);
528#endif
529	}
530}
531
532
533void
534MouseDevice::_ControlThreadCleanup()
535{
536	// NOTE: Only executed when the control thread detected an error
537	// and from within the control thread!
538
539	if (fActive) {
540		fThread = -1;
541		fTarget._RemoveDevice(fPath.String());
542	} else {
543		// In case active is already false, another thread
544		// waits for this thread to quit, and may already hold
545		// locks that _RemoveDevice() wants to acquire. In other
546		// words, the device is already being removed, so we simply
547		// quit here.
548	}
549}
550
551
552void
553MouseDevice::_UpdateSettings()
554{
555	MD_CALLED();
556	// retrieve current values
557
558	if (get_mouse_map(&fSettings.map) != B_OK)
559		LOG_ERR("error when get_mouse_map\n");
560	else {
561		fDeviceRemapsButtons
562			= ioctl(fDevice, MS_SET_MAP, &fSettings.map) == B_OK;
563	}
564
565	if (get_click_speed(&fSettings.click_speed) == B_OK) {
566		if (fIsTouchpad)
567			fTouchpadMovementMaker.click_speed = fSettings.click_speed;
568		ioctl(fDevice, MS_SET_CLICKSPEED, &fSettings.click_speed);
569	} else
570		LOG_ERR("error when get_click_speed\n");
571
572	if (get_mouse_speed(fDeviceRef.name, &fSettings.accel.speed) != B_OK)
573		LOG_ERR("error when get_mouse_speed\n");
574	else {
575		if (get_mouse_acceleration(fDeviceRef.name, &fSettings.accel.accel_factor) != B_OK)
576			LOG_ERR("error when get_mouse_acceleration\n");
577		else {
578			mouse_accel accel;
579			ioctl(fDevice, MS_GET_ACCEL, &accel);
580			accel.speed = fSettings.accel.speed;
581			accel.accel_factor = fSettings.accel.accel_factor;
582			ioctl(fDevice, MS_SET_ACCEL, &fSettings.accel);
583		}
584	}
585
586	if (get_mouse_type(fDeviceRef.name, &fSettings.type) != B_OK)
587		LOG_ERR("error when get_mouse_type\n");
588	else
589		ioctl(fDevice, MS_SET_TYPE, &fSettings.type);
590}
591
592
593status_t
594MouseDevice::_GetTouchpadSettingsPath(BPath& path)
595{
596	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
597	if (status < B_OK)
598		return status;
599	return path.Append(TOUCHPAD_SETTINGS_FILE);
600}
601
602
603status_t
604MouseDevice::_UpdateTouchpadSettings(BMessage* message)
605{
606	touchpad_settings settings;
607	message->FindBool("scroll_twofinger", &settings.scroll_twofinger);
608	message->FindBool("scroll_twofinger_horizontal",
609		&settings.scroll_twofinger_horizontal);
610	message->FindFloat("scroll_rightrange",
611		&settings.scroll_rightrange);
612	message->FindFloat("scroll_bottomrange",
613		&settings.scroll_bottomrange);
614
615	message->FindInt16("scroll_xstepsize",
616		(int16*)&settings.scroll_xstepsize);
617	message->FindInt16("scroll_ystepsize",
618		(int16*)&settings.scroll_ystepsize);
619	message->FindInt8("scroll_acceleration",
620		(int8*)&settings.scroll_acceleration);
621	message->FindInt8("tapgesture_sensibility",
622		(int8*)&settings.tapgesture_sensibility);
623
624	if (fIsTouchpad)
625		fTouchpadMovementMaker.SetSettings(settings);
626
627	return B_OK;
628}
629
630
631BMessage*
632MouseDevice::_BuildMouseMessage(uint32 what, uint64 when, uint32 buttons,
633	int32 deltaX, int32 deltaY) const
634{
635	BMessage* message = new BMessage(what);
636	if (message == NULL)
637		return NULL;
638
639	if (message->AddInt64("when", when) < B_OK
640		|| message->AddInt32("buttons", buttons) < B_OK
641		|| message->AddInt32("x", deltaX) < B_OK
642		|| message->AddInt32("y", deltaY) < B_OK) {
643		delete message;
644		return NULL;
645	}
646
647	return message;
648}
649
650
651void
652MouseDevice::_ComputeAcceleration(const mouse_movement& movements,
653	int32& _deltaX, int32& _deltaY, float& historyDeltaX,
654	float& historyDeltaY) const
655{
656	// basic mouse speed
657	float deltaX = (float)movements.xdelta * fSettings.accel.speed / 65536.0
658		+ historyDeltaX;
659	float deltaY = (float)movements.ydelta * fSettings.accel.speed / 65536.0
660		+ historyDeltaY;
661
662	// acceleration
663	double acceleration = 1;
664	if (fSettings.accel.accel_factor) {
665		acceleration = 1 + sqrt(deltaX * deltaX + deltaY * deltaY)
666			* fSettings.accel.accel_factor / 524288.0;
667	}
668
669	deltaX *= acceleration;
670	deltaY *= acceleration;
671
672	if (deltaX >= 0)
673		_deltaX = (int32)floorf(deltaX);
674	else
675		_deltaX = (int32)ceilf(deltaX);
676
677	if (deltaY >= 0)
678		_deltaY = (int32)floorf(deltaY);
679	else
680		_deltaY = (int32)ceilf(deltaY);
681
682	historyDeltaX = deltaX - _deltaX;
683	historyDeltaY = deltaY - _deltaY;
684}
685
686
687uint32
688MouseDevice::_RemapButtons(uint32 buttons) const
689{
690	if (fDeviceRemapsButtons)
691		return buttons;
692
693	uint32 newButtons = 0;
694	for (int32 i = 0; buttons; i++) {
695		if (buttons & 0x1) {
696#if defined(HAIKU_TARGET_PLATFORM_HAIKU) || defined(HAIKU_TARGET_PLATFORM_DANO)
697			newButtons |= fSettings.map.button[i];
698#else
699			if (i == 0)
700				newButtons |= fSettings.map.left;
701			if (i == 1)
702				newButtons |= fSettings.map.right;
703			if (i == 2)
704				newButtons |= fSettings.map.middle;
705#endif
706		}
707		buttons >>= 1;
708	}
709
710	return newButtons;
711}
712
713
714//	#pragma mark -
715
716
717MouseInputDevice::MouseInputDevice()
718	:
719	fDevices(2, true),
720	fDeviceListLock("MouseInputDevice list")
721{
722	MID_CALLED();
723
724	StartMonitoringDevice(kMouseDevicesDirectory);
725	StartMonitoringDevice(kTouchpadDevicesDirectory);
726	_RecursiveScan(kMouseDevicesDirectory);
727	_RecursiveScan(kTouchpadDevicesDirectory);
728}
729
730
731MouseInputDevice::~MouseInputDevice()
732{
733	MID_CALLED();
734
735	StopMonitoringDevice(kTouchpadDevicesDirectory);
736	StopMonitoringDevice(kMouseDevicesDirectory);
737	fDevices.MakeEmpty();
738}
739
740
741status_t
742MouseInputDevice::InitCheck()
743{
744	MID_CALLED();
745
746	return BInputServerDevice::InitCheck();
747}
748
749
750status_t
751MouseInputDevice::Start(const char* name, void* cookie)
752{
753	MID_CALLED();
754
755	MouseDevice* device = (MouseDevice*)cookie;
756
757	return device->Start();
758}
759
760
761status_t
762MouseInputDevice::Stop(const char* name, void* cookie)
763{
764	TRACE("%s(%s)\n", __PRETTY_FUNCTION__, name);
765
766	MouseDevice* device = (MouseDevice*)cookie;
767	device->Stop();
768
769	return B_OK;
770}
771
772
773status_t
774MouseInputDevice::Control(const char* name, void* cookie,
775	uint32 command, BMessage* message)
776{
777	TRACE("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
778
779	MouseDevice* device = (MouseDevice*)cookie;
780
781	if (command == B_NODE_MONITOR)
782		return _HandleMonitor(message);
783
784	if (command == B_SET_TOUCHPAD_SETTINGS)
785		return device->UpdateTouchpadSettings(message);
786
787	if (command >= B_MOUSE_TYPE_CHANGED
788		&& command <= B_MOUSE_ACCELERATION_CHANGED)
789		return device->UpdateSettings();
790
791	return B_BAD_VALUE;
792}
793
794
795status_t
796MouseInputDevice::_HandleMonitor(BMessage* message)
797{
798	MID_CALLED();
799
800	const char* path;
801	int32 opcode;
802	if (message->FindInt32("opcode", &opcode) != B_OK
803		|| (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED)
804		|| message->FindString("path", &path) != B_OK)
805		return B_BAD_VALUE;
806
807	if (opcode == B_ENTRY_CREATED)
808		return _AddDevice(path);
809
810#if 0
811	return _RemoveDevice(path);
812#else
813	// Don't handle B_ENTRY_REMOVED, let the control thread take care of it.
814	return B_OK;
815#endif
816}
817
818
819void
820MouseInputDevice::_RecursiveScan(const char* directory)
821{
822	MID_CALLED();
823
824	BEntry entry;
825	BDirectory dir(directory);
826	while (dir.GetNextEntry(&entry) == B_OK) {
827		BPath path;
828		entry.GetPath(&path);
829
830		if (!strcmp(path.Leaf(), "serial")) {
831			// skip serial
832			continue;
833		}
834
835		if (entry.IsDirectory())
836			_RecursiveScan(path.Path());
837		else
838			_AddDevice(path.Path());
839	}
840}
841
842
843MouseDevice*
844MouseInputDevice::_FindDevice(const char* path) const
845{
846	MID_CALLED();
847
848	for (int32 i = fDevices.CountItems() - 1; i >= 0; i--) {
849		MouseDevice* device = fDevices.ItemAt(i);
850		if (strcmp(device->Path(), path) == 0)
851			return device;
852	}
853
854	return NULL;
855}
856
857
858status_t
859MouseInputDevice::_AddDevice(const char* path)
860{
861	MID_CALLED();
862
863	BAutolock _(fDeviceListLock);
864
865	_RemoveDevice(path);
866
867	MouseDevice* device = new(std::nothrow) MouseDevice(*this, path);
868	if (device == NULL) {
869		TRACE("No memory\n");
870		return B_NO_MEMORY;
871	}
872
873	if (!fDevices.AddItem(device)) {
874		TRACE("No memory in list\n");
875		delete device;
876		return B_NO_MEMORY;
877	}
878
879	input_device_ref* devices[2];
880	devices[0] = device->DeviceRef();
881	devices[1] = NULL;
882
883	TRACE("adding path: %s, name: %s\n", path, devices[0]->name);
884
885	return RegisterDevices(devices);
886}
887
888
889status_t
890MouseInputDevice::_RemoveDevice(const char* path)
891{
892	MID_CALLED();
893
894	BAutolock _(fDeviceListLock);
895
896	MouseDevice* device = _FindDevice(path);
897	if (device == NULL) {
898		TRACE("%s not found\n", path);
899		return B_ENTRY_NOT_FOUND;
900	}
901
902	input_device_ref* devices[2];
903	devices[0] = device->DeviceRef();
904	devices[1] = NULL;
905
906	TRACE("removing path: %s, name: %s\n", path, devices[0]->name);
907
908	UnregisterDevices(devices);
909
910	fDevices.RemoveItem(device);
911
912	return B_OK;
913}
914