xref: /haiku/src/add-ons/input_server/devices/mouse/MouseInputDevice.cpp (revision 55b40aa53a835472ec7952b138ae4256203d02e4)
1 /*
2  * Copyright 2004-2006, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stefano Ceccherini
7  */
8 
9 
10 #include "MouseInputDevice.h"
11 #include "kb_mouse_settings.h"
12 #include "kb_mouse_driver.h"
13 
14 #include <Debug.h>
15 #include <Directory.h>
16 #include <Entry.h>
17 #include <NodeMonitor.h>
18 #include <Path.h>
19 #include <String.h>
20 #include <View.h>
21 
22 #include <errno.h>
23 #include <new>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 
28 #if DEBUG
29 FILE *MouseInputDevice::sLogFile = NULL;
30 #	define LOG_ERR(text...) LOG(text)
31 #else
32 #	define LOG(text...)
33 #	define LOG_ERR(text...) fprintf(stderr, text)
34 #endif
35 
36 #define CALLED() LOG("%s\n", __PRETTY_FUNCTION__)
37 
38 const static uint32 kMouseThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
39 const static char *kMouseDevicesDirectory = "/dev/input/mouse";
40 
41 // "/dev/" is automatically prepended by StartMonitoringDevice()
42 const static char *kMouseDevicesDirectoryPS2 = "input/mouse/ps2";
43 const static char *kMouseDevicesDirectoryUSB = "input/mouse/usb";
44 
45 class MouseDevice {
46 	public:
47 		MouseDevice(BInputServerDevice& target, const char* path);
48 		~MouseDevice();
49 
50 		status_t Start();
51 		void Stop();
52 
53 		status_t UpdateSettings();
54 
55 		const char* Path() const { return fPath.String(); }
56 		input_device_ref* DeviceRef() { return &fDeviceRef; }
57 
58 	private:
59 		void _Run();
60 		static status_t _ThreadFunction(void *arg);
61 
62 		BMessage* _BuildMouseMessage(uint32 what, uint64 when, uint32 buttons,
63 					int32 deltaX, int32 deltaY) const;
64 		void _ComputeAcceleration(const mouse_movement& movements,
65 					int32& deltaX, int32& deltaY) const;
66 		uint32 _RemapButtons(uint32 buttons) const;
67 
68 		char* _BuildShortName() const;
69 
70 	private:
71 		BInputServerDevice& fTarget;
72 		BString	fPath;
73 		int fDevice;
74 
75 		input_device_ref fDeviceRef;
76 		mouse_settings fSettings;
77 		bool fDeviceRemapsButtons;
78 
79 		thread_id fThread;
80 		volatile bool fActive;
81 };
82 
83 
84 #if DEBUG
85 inline void
86 LOG(const char *fmt, ...)
87 {
88 	char buf[1024];
89 	va_list ap;
90 	va_start(ap, fmt);
91 	vsprintf(buf, fmt, ap); va_end(ap);
92     fputs(buf, MouseInputDevice::sLogFile); fflush(MouseInputDevice::sLogFile);
93 }
94 #endif
95 
96 
97 extern "C" BInputServerDevice *
98 instantiate_input_device()
99 {
100 	return new MouseInputDevice();
101 }
102 
103 
104 //	#pragma mark -
105 
106 
107 MouseDevice::MouseDevice(BInputServerDevice& target, const char *driverPath)
108 	:
109 	fTarget(target),
110 	fDevice(-1),
111 	fThread(-1),
112 	fActive(false)
113 {
114 	fPath = driverPath;
115 
116 	fDeviceRef.name = _BuildShortName();
117 	fDeviceRef.type = B_POINTING_DEVICE;
118 	fDeviceRef.cookie = this;
119 
120 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
121 	fSettings.map.button[0] = B_PRIMARY_MOUSE_BUTTON;
122 	fSettings.map.button[1] = B_SECONDARY_MOUSE_BUTTON;
123 	fSettings.map.button[2] = B_TERTIARY_MOUSE_BUTTON;
124 #endif
125 
126 	fDeviceRemapsButtons = false;
127 };
128 
129 
130 MouseDevice::~MouseDevice()
131 {
132 	if (fActive)
133 		Stop();
134 
135 	free(fDeviceRef.name);
136 }
137 
138 
139 status_t
140 MouseDevice::Start()
141 {
142 	fDevice = open(fPath.String(), O_RDWR);
143 	if (fDevice < 0)
144 		return errno;
145 
146 	UpdateSettings();
147 
148 	char threadName[B_OS_NAME_LENGTH];
149 	snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", fDeviceRef.name);
150 
151 	fThread = spawn_thread(_ThreadFunction, threadName,
152 		kMouseThreadPriority, (void *)this);
153 
154 	status_t status;
155 	if (fThread < B_OK)
156 		status = fThread;
157 	else {
158 		fActive = true;
159 		status = resume_thread(fThread);
160 	}
161 
162 	if (status < B_OK) {
163 		LOG_ERR("%s: can't spawn/resume watching thread: %s\n",
164 			fDeviceRef.name, strerror(status));
165 		close(fDevice);
166 		return status;
167 	}
168 
169 	return B_OK;
170 }
171 
172 
173 void
174 MouseDevice::Stop()
175 {
176 	fActive = false;
177 		// this will stop the thread as soon as it reads the next packet
178 
179 	if (fThread >= B_OK) {
180 		// unblock the thread, which might wait on a semaphore.
181 		suspend_thread(fThread);
182 		resume_thread(fThread);
183 
184 		status_t dummy;
185 		wait_for_thread(fThread, &dummy);
186 	}
187 
188 	close(fDevice);
189 }
190 
191 
192 status_t
193 MouseDevice::UpdateSettings()
194 {
195 	CALLED();
196 
197 	// retrieve current values
198 
199 	if (get_mouse_map(&fSettings.map) != B_OK)
200 		LOG_ERR("error when get_mouse_map\n");
201 	else
202 		fDeviceRemapsButtons = ioctl(fDevice, MS_SET_MAP, &fSettings.map) == B_OK;
203 
204 	if (get_click_speed(&fSettings.click_speed) != B_OK)
205 		LOG_ERR("error when get_click_speed\n");
206 	else
207 		ioctl(fDevice, MS_SET_CLICKSPEED, &fSettings.click_speed);
208 
209 	if (get_mouse_speed(&fSettings.accel.speed) != B_OK)
210 		LOG_ERR("error when get_mouse_speed\n");
211 	else {
212 		if (get_mouse_acceleration(&fSettings.accel.accel_factor) != B_OK)
213 			LOG_ERR("error when get_mouse_acceleration\n");
214 		else {
215 			mouse_accel accel;
216 			ioctl(fDevice, MS_GET_ACCEL, &accel);
217 			accel.speed = fSettings.accel.speed;
218 			accel.accel_factor = fSettings.accel.accel_factor;
219 			ioctl(fDevice, MS_SET_ACCEL, &fSettings.accel);
220 		}
221 	}
222 
223 	if (get_mouse_type(&fSettings.type) != B_OK)
224 		LOG_ERR("error when get_mouse_type\n");
225 	else
226 		ioctl(fDevice, MS_SET_TYPE, &fSettings.type);
227 
228 	return B_OK;
229 
230 }
231 
232 
233 void
234 MouseDevice::_Run()
235 {
236 	uint32 lastButtons = 0;
237 
238 	while (fActive) {
239 		mouse_movement movements;
240 		memset(&movements, 0, sizeof(movements));
241 
242 		if (ioctl(fDevice, MS_READ, &movements) != B_OK)
243 			return;
244 
245 		uint32 buttons = lastButtons ^ movements.buttons;
246 
247 		uint32 remappedButtons = _RemapButtons(movements.buttons);
248 		int32 deltaX, deltaY;
249 		_ComputeAcceleration(movements, deltaX, deltaY);
250 
251 		LOG("%s: buttons: 0x%lx, x: %ld, y: %ld, clicks:%ld, wheel_x:%ld, wheel_y:%ld\n",
252 			device->device_ref.name, movements.buttons, movements.xdelta, movements.ydelta,
253 			movements.clicks, movements.wheel_xdelta, movements.wheel_ydelta);
254 		LOG("%s: x: %ld, y: %ld\n", device->device_ref.name, deltaX, deltaY);
255 
256 		BMessage *message = NULL;
257 
258 		// Send single messages for each event
259 
260 		if (movements.xdelta != 0 || movements.ydelta != 0) {
261 			BMessage* message = _BuildMouseMessage(B_MOUSE_MOVED, movements.timestamp,
262 				remappedButtons, deltaX, deltaY);
263 			if (message != NULL)
264 				fTarget.EnqueueMessage(message);
265 		}
266 
267 		if (buttons != 0) {
268 			bool pressedButton = (buttons & movements.buttons) > 0;
269 			BMessage* message = _BuildMouseMessage(
270 				pressedButton ? B_MOUSE_DOWN : B_MOUSE_UP,
271 				movements.timestamp, remappedButtons, deltaX, deltaY);
272 			if (message != NULL) {
273 				if (pressedButton) {
274 					message->AddInt32("clicks", movements.clicks);
275 					LOG("B_MOUSE_DOWN\n");
276 				} else
277 					LOG("B_MOUSE_UP\n");
278 
279 				fTarget.EnqueueMessage(message);
280 				lastButtons = movements.buttons;
281 			}
282 		}
283 
284 		if ((movements.wheel_ydelta != 0) || (movements.wheel_xdelta != 0)) {
285 			message = new BMessage(B_MOUSE_WHEEL_CHANGED);
286 			if (message == NULL)
287 				continue;
288 
289 			if (message->AddInt64("when", movements.timestamp) == B_OK
290 				&& message->AddFloat("be:wheel_delta_x", movements.wheel_xdelta) == B_OK
291 				&& message->AddFloat("be:wheel_delta_y", movements.wheel_ydelta) == B_OK)
292 				fTarget.EnqueueMessage(message);
293 			else
294 				delete message;
295 		}
296 	}
297 }
298 
299 
300 status_t
301 MouseDevice::_ThreadFunction(void* arg)
302 {
303 	MouseDevice* device = (MouseDevice *)arg;
304 	device->_Run();
305 	return B_OK;
306 }
307 
308 
309 BMessage*
310 MouseDevice::_BuildMouseMessage(uint32 what, uint64 when, uint32 buttons,
311 	int32 deltaX, int32 deltaY) const
312 {
313 	BMessage* message = new BMessage(what);
314 	if (message == NULL)
315 		return NULL;
316 
317 	if (message->AddInt64("when", when) < B_OK
318 		|| message->AddInt32("buttons", buttons) < B_OK
319 		|| message->AddInt32("x", deltaX) < B_OK
320 		|| message->AddInt32("y", deltaY) < B_OK) {
321 		delete message;
322 		return NULL;
323 	}
324 
325 	return message;
326 }
327 
328 
329 void
330 MouseDevice::_ComputeAcceleration(const mouse_movement& movements,
331 	int32& deltaX, int32& deltaY) const
332 {
333 	// basic mouse speed
334 	deltaX = movements.xdelta * fSettings.accel.speed >> 16;
335 	deltaY = movements.ydelta * fSettings.accel.speed >> 16;
336 
337 	// acceleration
338 	double acceleration = 1;
339 	if (fSettings.accel.accel_factor) {
340 		acceleration = 1 + sqrt(deltaX * deltaX + deltaY * deltaY)
341 			* fSettings.accel.accel_factor / 524288.0;
342 	}
343 
344 	// make sure that we move at least one pixel (if there was a movement)
345 	if (deltaX > 0)
346 		deltaX = (int32)floor(deltaX * acceleration);
347 	else
348 		deltaX = (int32)ceil(deltaX * acceleration);
349 
350 	if (deltaY > 0)
351 		deltaY = (int32)floor(deltaY * acceleration);
352 	else
353 		deltaY = (int32)ceil(deltaY * acceleration);
354 }
355 
356 
357 uint32
358 MouseDevice::_RemapButtons(uint32 buttons) const
359 {
360 	if (fDeviceRemapsButtons)
361 		return buttons;
362 
363 	uint32 newButtons = 0;
364 	for (int32 i = 0; buttons; i++) {
365 		if (buttons & 0x1) {
366 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
367 			newButtons |= fSettings.map.button[i];
368 #else
369 			if (i == 0)
370 				newButtons |= fSettings.map.left;
371 			if (i == 1)
372 				newButtons |= fSettings.map.right;
373 			if (i == 2)
374 				newButtons |= fSettings.map.middle;
375 #endif
376 		}
377 		buttons >>= 1;
378 	}
379 
380 	return newButtons;
381 }
382 
383 
384 char *
385 MouseDevice::_BuildShortName() const
386 {
387 	BString string(fPath);
388 	BString name;
389 
390 	int32 slash = string.FindLast("/");
391 	string.CopyInto(name, slash + 1, string.Length() - slash);
392 	int32 index = atoi(name.String()) + 1;
393 
394 	int32 previousSlash = string.FindLast("/", slash);
395 	string.CopyInto(name, previousSlash + 1, slash - previousSlash - 1);
396 
397 	if (name == "ps2")
398 		name = "PS/2";
399 	else
400 		name.Capitalize();
401 
402 	name << " Mouse " << index;
403 
404 	return strdup(name.String());
405 }
406 
407 
408 //	#pragma mark -
409 
410 
411 MouseInputDevice::MouseInputDevice()
412 {
413 #if DEBUG
414 	sLogFile = fopen("/var/log/mouse_device_log.log", "a");
415 #endif
416 	CALLED();
417 
418 	StartMonitoringDevice(kMouseDevicesDirectoryPS2);
419 	StartMonitoringDevice(kMouseDevicesDirectoryUSB);
420 
421 	_RecursiveScan(kMouseDevicesDirectory);
422 }
423 
424 
425 MouseInputDevice::~MouseInputDevice()
426 {
427 	CALLED();
428 	StopMonitoringDevice(kMouseDevicesDirectoryUSB);
429 	StopMonitoringDevice(kMouseDevicesDirectoryPS2);
430 
431 	int32 count = fDevices.CountItems();
432 	while (count-- > 0) {
433 		delete (MouseDevice *)fDevices.RemoveItem(count);
434 	}
435 
436 #if DEBUG
437 	fclose(sLogFile);
438 #endif
439 }
440 
441 
442 status_t
443 MouseInputDevice::InitCheck()
444 {
445 	CALLED();
446 	return B_OK;
447 }
448 
449 
450 status_t
451 MouseInputDevice::Start(const char *name, void *cookie)
452 {
453 	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
454 	MouseDevice* device = (MouseDevice*)cookie;
455 
456 	return device->Start();
457 }
458 
459 
460 status_t
461 MouseInputDevice::Stop(const char *name, void *cookie)
462 {
463 	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
464 	MouseDevice* device = (MouseDevice*)cookie;
465 
466 	device->Stop();
467 	return B_OK;
468 }
469 
470 
471 status_t
472 MouseInputDevice::Control(const char* name, void* cookie,
473 	uint32 command, BMessage* message)
474 {
475 	LOG("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
476 	MouseDevice* device = (MouseDevice*)cookie;
477 
478 	if (command == B_NODE_MONITOR)
479 		return _HandleMonitor(message);
480 
481 	if (command >= B_MOUSE_TYPE_CHANGED
482 		&& command <= B_MOUSE_ACCELERATION_CHANGED)
483 		return device->UpdateSettings();
484 
485 	return B_BAD_VALUE;
486 }
487 
488 
489 // TODO: Test this. USB doesn't work on my machine
490 status_t
491 MouseInputDevice::_HandleMonitor(BMessage* message)
492 {
493 	CALLED();
494 
495 	int32 opcode;
496 	if (message->FindInt32("opcode", &opcode) < B_OK)
497 		return B_BAD_VALUE;
498 
499 	if (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED)
500 		return B_OK;
501 
502 	BEntry entry;
503 	BPath path;
504 	dev_t device;
505 	ino_t directory;
506 	const char *name;
507 
508 	if (message->FindInt32("device", &device) < B_OK
509 		|| message->FindInt64("directory", &directory) < B_OK
510 		|| message->FindString("name", &name) < B_OK)
511 		return B_BAD_VALUE;
512 
513 	entry_ref ref(device, directory, name);
514 	status_t status;
515 
516 	if ((status = entry.SetTo(&ref)) != B_OK)
517 		return status;
518 	if ((status = entry.GetPath(&path)) != B_OK)
519 		return status;
520 	if ((status = path.InitCheck()) != B_OK)
521 		return status;
522 
523 	if (opcode == B_ENTRY_CREATED)
524 		status = _AddDevice(path.Path());
525 	else
526 		status = _RemoveDevice(path.Path());
527 
528 	return status;
529 }
530 
531 
532 MouseDevice*
533 MouseInputDevice::_FindDevice(const char *path)
534 {
535 	CALLED();
536 
537 	for (int32 i = fDevices.CountItems(); i-- > 0;) {
538 		MouseDevice* device = (MouseDevice*)fDevices.ItemAt(i);
539 		if (!strcmp(device->Path(), path))
540 			return device;
541 	}
542 
543 	return NULL;
544 }
545 
546 
547 status_t
548 MouseInputDevice::_AddDevice(const char *path)
549 {
550 	CALLED();
551 
552 	MouseDevice* device = new (std::nothrow) MouseDevice(*this, path);
553 	if (!device) {
554 		LOG("No memory\n");
555 		return B_NO_MEMORY;
556 	}
557 
558 	if (!fDevices.AddItem(device)) {
559 		delete device;
560 		return B_NO_MEMORY;
561 	}
562 
563 	input_device_ref *devices[2];
564 	devices[0] = device->DeviceRef();
565 	devices[1] = NULL;
566 
567 	return RegisterDevices(devices);
568 }
569 
570 
571 status_t
572 MouseInputDevice::_RemoveDevice(const char *path)
573 {
574 	CALLED();
575 
576 	MouseDevice* device = _FindDevice(path);
577 	if (device == NULL)
578 		return B_ENTRY_NOT_FOUND;
579 
580 	fDevices.RemoveItem(device);
581 
582 	input_device_ref *devices[2];
583 	devices[0] = device->DeviceRef();
584 	devices[1] = NULL;
585 
586 	UnregisterDevices(devices);
587 
588 	delete device;
589 	return B_OK;
590 }
591 
592 
593 void
594 MouseInputDevice::_RecursiveScan(const char* directory)
595 {
596 	CALLED();
597 
598 	BEntry entry;
599 	BDirectory dir(directory);
600 	while (dir.GetNextEntry(&entry) == B_OK) {
601 		BPath path;
602 		entry.GetPath(&path);
603 
604 		if (!strcmp(path.Leaf(), "serial")) {
605 			// skip serial
606 			continue;
607 		}
608 
609 		if (entry.IsDirectory())
610 			_RecursiveScan(path.Path());
611 		else
612 			_AddDevice(path.Path());
613 	}
614 }
615 
616