xref: /haiku/src/add-ons/input_server/devices/tablet/TabletInputDevice.cpp (revision caed67a8cba83913b9c21ac2b06ebc6bd1cb3111)
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  *		Michael Lotz, mmlr@mlotz.ch
12  */
13 
14 
15 #include "TabletInputDevice.h"
16 
17 #include <errno.h>
18 #include <new>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 
23 #include <Autolock.h>
24 #include <Debug.h>
25 #include <Directory.h>
26 #include <Entry.h>
27 #include <NodeMonitor.h>
28 #include <Path.h>
29 #include <String.h>
30 
31 #include <kb_mouse_settings.h>
32 #include <keyboard_mouse_driver.h>
33 
34 
35 #undef TRACE
36 //#define TRACE_TABLET_DEVICE
37 #ifdef TRACE_TABLET_DEVICE
38 
39 	class FunctionTracer {
40 	public:
41 		FunctionTracer(const void* pointer, const char* className,
42 				const char* functionName,
43 				int32& depth)
44 			: fFunctionName(),
45 			  fPrepend(),
46 			  fFunctionDepth(depth),
47 			  fPointer(pointer)
48 		{
49 			fFunctionDepth++;
50 			fPrepend.Append(' ', fFunctionDepth * 2);
51 			fFunctionName << className << "::" << functionName << "()";
52 
53 			debug_printf("%p -> %s%s {\n", fPointer, fPrepend.String(),
54 				fFunctionName.String());
55 		}
56 
57 		 ~FunctionTracer()
58 		{
59 			debug_printf("%p -> %s}\n", fPointer, fPrepend.String());
60 			fFunctionDepth--;
61 		}
62 
63 	private:
64 		BString	fFunctionName;
65 		BString	fPrepend;
66 		int32&	fFunctionDepth;
67 		const void* fPointer;
68 	};
69 
70 
71 	static int32 sFunctionDepth = -1;
72 #	define TD_CALLED(x...)	FunctionTracer _ft(this, "TabletDevice", \
73 								__FUNCTION__, sFunctionDepth)
74 #	define TID_CALLED(x...)	FunctionTracer _ft(this, "TabletInputDevice", \
75 								__FUNCTION__, sFunctionDepth)
76 #	define TRACE(x...)	do { BString _to; \
77 							_to.Append(' ', (sFunctionDepth + 1) * 2); \
78 							debug_printf("%p -> %s", this, _to.String()); \
79 							debug_printf(x); } while (0)
80 #	define LOG_EVENT(text...) do {} while (0)
81 #	define LOG_ERR(text...) TRACE(text)
82 #else
83 #	define TRACE(x...) do {} while (0)
84 #	define TD_CALLED(x...) TRACE(x)
85 #	define TID_CALLED(x...) TRACE(x)
86 #	define LOG_ERR(x...) debug_printf(x)
87 #	define LOG_EVENT(x...) TRACE(x)
88 #endif
89 
90 
91 const static uint32 kTabletThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
92 const static char* kTabletDevicesDirectory = "/dev/input/tablet";
93 
94 
95 class TabletDevice {
96 public:
97 								TabletDevice(TabletInputDevice& target,
98 									const char* path);
99 								~TabletDevice();
100 
101 			status_t			Start();
102 			void				Stop();
103 
104 			status_t			UpdateSettings();
105 
106 			const char*			Path() const { return fPath.String(); }
107 			input_device_ref*	DeviceRef() { return &fDeviceRef; }
108 
109 private:
110 			char*				_BuildShortName() const;
111 
112 	static	status_t			_ControlThreadEntry(void* arg);
113 			void				_ControlThread();
114 			void				_ControlThreadCleanup();
115 			void				_UpdateSettings();
116 
117 			BMessage*			_BuildMouseMessage(uint32 what,
118 									uint64 when, uint32 buttons,
119 									float xPosition, float yPosition) const;
120 
121 private:
122 			TabletInputDevice&	fTarget;
123 			BString				fPath;
124 			int					fDevice;
125 
126 			input_device_ref	fDeviceRef;
127 			mouse_settings		fSettings;
128 
129 			thread_id			fThread;
130 	volatile bool				fActive;
131 	volatile bool				fUpdateSettings;
132 };
133 
134 
135 extern "C" BInputServerDevice*
136 instantiate_input_device()
137 {
138 	return new(std::nothrow) TabletInputDevice();
139 }
140 
141 
142 //	#pragma mark -
143 
144 
145 TabletDevice::TabletDevice(TabletInputDevice& target, const char* driverPath)
146 	:
147 	fTarget(target),
148 	fPath(driverPath),
149 	fDevice(-1),
150 	fThread(-1),
151 	fActive(false),
152 	fUpdateSettings(false)
153 {
154 	TD_CALLED();
155 
156 	fDeviceRef.name = _BuildShortName();
157 	fDeviceRef.type = B_POINTING_DEVICE;
158 	fDeviceRef.cookie = this;
159 };
160 
161 
162 TabletDevice::~TabletDevice()
163 {
164 	TD_CALLED();
165 	TRACE("delete\n");
166 
167 	if (fActive)
168 		Stop();
169 
170 	free(fDeviceRef.name);
171 }
172 
173 
174 status_t
175 TabletDevice::Start()
176 {
177 	TD_CALLED();
178 
179 	fDevice = open(fPath.String(), O_RDWR);
180 		// let the control thread handle any error on opening the device
181 
182 	char threadName[B_OS_NAME_LENGTH];
183 	snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", fDeviceRef.name);
184 
185 	fThread = spawn_thread(_ControlThreadEntry, threadName,
186 		kTabletThreadPriority, (void*)this);
187 
188 	status_t status;
189 	if (fThread < 0)
190 		status = fThread;
191 	else {
192 		fActive = true;
193 		status = resume_thread(fThread);
194 	}
195 
196 	if (status < B_OK) {
197 		LOG_ERR("%s: can't spawn/resume watching thread: %s\n",
198 			fDeviceRef.name, strerror(status));
199 		if (fDevice >= 0)
200 			close(fDevice);
201 
202 		return status;
203 	}
204 
205 	return fDevice >= 0 ? B_OK : B_ERROR;
206 }
207 
208 
209 void
210 TabletDevice::Stop()
211 {
212 	TD_CALLED();
213 
214 	fActive = false;
215 		// this will stop the thread as soon as it reads the next packet
216 
217 	close(fDevice);
218 	fDevice = -1;
219 
220 	if (fThread >= 0) {
221 		// unblock the thread, which might wait on a semaphore.
222 		suspend_thread(fThread);
223 		resume_thread(fThread);
224 
225 		status_t dummy;
226 		wait_for_thread(fThread, &dummy);
227 	}
228 }
229 
230 
231 status_t
232 TabletDevice::UpdateSettings()
233 {
234 	TD_CALLED();
235 
236 	if (fThread < 0)
237 		return B_ERROR;
238 
239 	// trigger updating the settings in the control thread
240 	fUpdateSettings = true;
241 
242 	return B_OK;
243 }
244 
245 
246 char*
247 TabletDevice::_BuildShortName() const
248 {
249 	BString string(fPath);
250 	BString name;
251 
252 	int32 slash = string.FindLast("/");
253 	string.CopyInto(name, slash + 1, string.Length() - slash);
254 	int32 index = atoi(name.String()) + 1;
255 
256 	int32 previousSlash = string.FindLast("/", slash);
257 	string.CopyInto(name, previousSlash + 1, slash - previousSlash - 1);
258 
259 	if (name.Length() < 4)
260 		name.ToUpper();
261 	else
262 		name.Capitalize();
263 
264 	name << " Tablet " << index;
265 	return strdup(name.String());
266 }
267 
268 
269 // #pragma mark - control thread
270 
271 
272 status_t
273 TabletDevice::_ControlThreadEntry(void* arg)
274 {
275 	TabletDevice* device = (TabletDevice*)arg;
276 	device->_ControlThread();
277 	return B_OK;
278 }
279 
280 
281 void
282 TabletDevice::_ControlThread()
283 {
284 	TD_CALLED();
285 
286 	if (fDevice < 0) {
287 		_ControlThreadCleanup();
288 		return;
289 	}
290 
291 	_UpdateSettings();
292 
293 	static const bigtime_t kTransferDelay = 1000000 / 125;
294 		// 125 transfers per second should be more than enough
295 	bigtime_t nextTransferTime = system_time() + kTransferDelay;
296 	uint32 lastButtons = 0;
297 	float lastXPosition = 0;
298 	float lastYPosition = 0;
299 
300 	while (fActive) {
301 		tablet_movement movements;
302 
303 		snooze_until(nextTransferTime, B_SYSTEM_TIMEBASE);
304 		nextTransferTime += kTransferDelay;
305 
306 		if (ioctl(fDevice, MS_READ, &movements, sizeof(movements)) != B_OK) {
307 			LOG_ERR("Tablet device exiting, %s\n", strerror(errno));
308 			_ControlThreadCleanup();
309 			return;
310 		}
311 
312 		// take care of updating the settings first, if necessary
313 		if (fUpdateSettings) {
314 			fUpdateSettings = false;
315 			_UpdateSettings();
316 		}
317 
318 		LOG_EVENT("%s: buttons: 0x%lx, x: %f, y: %f, clicks: %ld, contact: %c, "
319 			"pressure: %f, wheel_x: %ld, wheel_y: %ld, eraser: %c, "
320 			"tilt: %f/%f\n", fDeviceRef.name, movements.buttons, movements.xpos,
321 			movements.ypos, movements.clicks, movements.has_contact ? 'y' : 'n',
322 			movements.pressure, movements.wheel_xdelta, movements.wheel_ydelta,
323 			movements.eraser ? 'y' : 'n', movements.tilt_x, movements.tilt_y);
324 
325 		// Only send messages when pen is in range
326 
327 		if (movements.has_contact) {
328 			// Send single messages for each event
329 
330 			movements.buttons |= (movements.switches & B_TIP_SWITCH);
331 			movements.buttons |= (movements.switches & B_BARREL_SWITCH) >> 1;
332 			bool eraser = (movements.switches & B_ERASER) != 0;
333 
334 			uint32 buttons = lastButtons ^ movements.buttons;
335 			if (buttons != 0) {
336 				bool pressedButton = (buttons & movements.buttons) > 0;
337 				BMessage* message = _BuildMouseMessage(
338 					pressedButton ? B_MOUSE_DOWN : B_MOUSE_UP,
339 					movements.timestamp, movements.buttons, movements.xpos,
340 					movements.ypos);
341 				if (message != NULL) {
342 					if (pressedButton) {
343 						message->AddInt32("clicks", movements.clicks);
344 						LOG_EVENT("B_MOUSE_DOWN\n");
345 					} else
346 						LOG_EVENT("B_MOUSE_UP\n");
347 
348 					fTarget.EnqueueMessage(message);
349 					lastButtons = movements.buttons;
350 				}
351 			}
352 
353 			if (movements.xpos != lastXPosition
354 				|| movements.ypos != lastYPosition) {
355 				BMessage* message = _BuildMouseMessage(B_MOUSE_MOVED,
356 					movements.timestamp, movements.buttons, movements.xpos,
357 					movements.ypos);
358 				if (message != NULL) {
359 					message->AddFloat("be:tablet_x", movements.xpos);
360 					message->AddFloat("be:tablet_y", movements.ypos);
361 					message->AddFloat("be:tablet_pressure", movements.pressure);
362 					message->AddInt32("be:tablet_eraser", eraser);
363 
364 					if (movements.tilt_x != 0.0 || movements.tilt_y != 0.0) {
365 						message->AddFloat("be:tablet_tilt_x", movements.tilt_x);
366 						message->AddFloat("be:tablet_tilt_y", movements.tilt_y);
367 					}
368 
369 					fTarget.EnqueueMessage(message);
370 					lastXPosition = movements.xpos;
371 					lastYPosition = movements.ypos;
372 				}
373 			}
374 
375 			if (movements.wheel_ydelta != 0 || movements.wheel_xdelta != 0) {
376 				BMessage* message = new BMessage(B_MOUSE_WHEEL_CHANGED);
377 				if (message == NULL)
378 					continue;
379 
380 				if (message->AddInt64("when", movements.timestamp) == B_OK
381 					&& message->AddFloat("be:wheel_delta_x",
382 						movements.wheel_xdelta) == B_OK
383 					&& message->AddFloat("be:wheel_delta_y",
384 						movements.wheel_ydelta) == B_OK)
385 					fTarget.EnqueueMessage(message);
386 				else
387 					delete message;
388 			}
389 		}
390 	}
391 }
392 
393 
394 void
395 TabletDevice::_ControlThreadCleanup()
396 {
397 	// NOTE: Only executed when the control thread detected an error
398 	// and from within the control thread!
399 
400 	if (fActive) {
401 		fThread = -1;
402 		fTarget._RemoveDevice(fPath.String());
403 	} else {
404 		// In case active is already false, another thread
405 		// waits for this thread to quit, and may already hold
406 		// locks that _RemoveDevice() wants to acquire. In other
407 		// words, the device is already being removed, so we simply
408 		// quit here.
409 	}
410 }
411 
412 
413 void
414 TabletDevice::_UpdateSettings()
415 {
416 	TD_CALLED();
417 
418 	if (get_click_speed(fDeviceRef.name, &fSettings.click_speed) != B_OK)
419 		LOG_ERR("error when get_click_speed\n");
420 	else
421 		ioctl(fDevice, MS_SET_CLICKSPEED, &fSettings.click_speed, sizeof(bigtime_t));
422 }
423 
424 
425 BMessage*
426 TabletDevice::_BuildMouseMessage(uint32 what, uint64 when, uint32 buttons,
427 	float xPosition, float yPosition) const
428 {
429 	BMessage* message = new BMessage(what);
430 	if (message == NULL)
431 		return NULL;
432 
433 	if (message->AddInt64("when", when) < B_OK
434 		|| message->AddInt32("buttons", buttons) < B_OK
435 		|| message->AddFloat("x", xPosition) < B_OK
436 		|| message->AddFloat("y", yPosition) < B_OK) {
437 		delete message;
438 		return NULL;
439 	}
440 
441 	return message;
442 }
443 
444 
445 //	#pragma mark -
446 
447 
448 TabletInputDevice::TabletInputDevice()
449 	:
450 	fDevices(2, true),
451 	fDeviceListLock("TabletInputDevice list")
452 {
453 	TID_CALLED();
454 
455 	StartMonitoringDevice(kTabletDevicesDirectory);
456 	_RecursiveScan(kTabletDevicesDirectory);
457 }
458 
459 
460 TabletInputDevice::~TabletInputDevice()
461 {
462 	TID_CALLED();
463 
464 	StopMonitoringDevice(kTabletDevicesDirectory);
465 	fDevices.MakeEmpty();
466 }
467 
468 
469 status_t
470 TabletInputDevice::InitCheck()
471 {
472 	TID_CALLED();
473 
474 	return BInputServerDevice::InitCheck();
475 }
476 
477 
478 status_t
479 TabletInputDevice::Start(const char* name, void* cookie)
480 {
481 	TID_CALLED();
482 
483 	TabletDevice* device = (TabletDevice*)cookie;
484 
485 	return device->Start();
486 }
487 
488 
489 status_t
490 TabletInputDevice::Stop(const char* name, void* cookie)
491 {
492 	TRACE("%s(%s)\n", __PRETTY_FUNCTION__, name);
493 
494 	TabletDevice* device = (TabletDevice*)cookie;
495 	device->Stop();
496 
497 	return B_OK;
498 }
499 
500 
501 status_t
502 TabletInputDevice::Control(const char* name, void* cookie,
503 	uint32 command, BMessage* message)
504 {
505 	TRACE("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
506 
507 	TabletDevice* device = (TabletDevice*)cookie;
508 
509 	if (command == B_NODE_MONITOR)
510 		return _HandleMonitor(message);
511 
512 	if (command == B_CLICK_SPEED_CHANGED)
513 		return device->UpdateSettings();
514 
515 	return B_BAD_VALUE;
516 }
517 
518 
519 status_t
520 TabletInputDevice::_HandleMonitor(BMessage* message)
521 {
522 	TID_CALLED();
523 
524 	const char* path;
525 	int32 opcode;
526 	if (message->FindInt32("opcode", &opcode) != B_OK
527 		|| (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED)
528 		|| message->FindString("path", &path) != B_OK)
529 		return B_BAD_VALUE;
530 
531 	if (opcode == B_ENTRY_CREATED)
532 		return _AddDevice(path);
533 
534 	// Don't handle B_ENTRY_REMOVED, let the control thread take care of it.
535 	return B_OK;
536 }
537 
538 
539 void
540 TabletInputDevice::_RecursiveScan(const char* directory)
541 {
542 	TID_CALLED();
543 
544 	BEntry entry;
545 	BDirectory dir(directory);
546 	while (dir.GetNextEntry(&entry) == B_OK) {
547 		BPath path;
548 		entry.GetPath(&path);
549 
550 		if (entry.IsDirectory())
551 			_RecursiveScan(path.Path());
552 		else
553 			_AddDevice(path.Path());
554 	}
555 }
556 
557 
558 TabletDevice*
559 TabletInputDevice::_FindDevice(const char* path) const
560 {
561 	TID_CALLED();
562 
563 	for (int32 i = fDevices.CountItems() - 1; i >= 0; i--) {
564 		TabletDevice* device = fDevices.ItemAt(i);
565 		if (strcmp(device->Path(), path) == 0)
566 			return device;
567 	}
568 
569 	return NULL;
570 }
571 
572 
573 status_t
574 TabletInputDevice::_AddDevice(const char* path)
575 {
576 	TID_CALLED();
577 
578 	BAutolock _(fDeviceListLock);
579 
580 	_RemoveDevice(path);
581 
582 	TabletDevice* device = new(std::nothrow) TabletDevice(*this, path);
583 	if (device == NULL) {
584 		TRACE("No memory\n");
585 		return B_NO_MEMORY;
586 	}
587 
588 	if (!fDevices.AddItem(device)) {
589 		TRACE("No memory in list\n");
590 		delete device;
591 		return B_NO_MEMORY;
592 	}
593 
594 	input_device_ref* devices[2];
595 	devices[0] = device->DeviceRef();
596 	devices[1] = NULL;
597 
598 	TRACE("adding path: %s, name: %s\n", path, devices[0]->name);
599 
600 	return RegisterDevices(devices);
601 }
602 
603 
604 status_t
605 TabletInputDevice::_RemoveDevice(const char* path)
606 {
607 	TID_CALLED();
608 
609 	BAutolock _(fDeviceListLock);
610 
611 	TabletDevice* device = _FindDevice(path);
612 	if (device == NULL) {
613 		TRACE("%s not found\n", path);
614 		return B_ENTRY_NOT_FOUND;
615 	}
616 
617 	input_device_ref* devices[2];
618 	devices[0] = device->DeviceRef();
619 	devices[1] = NULL;
620 
621 	TRACE("removing path: %s, name: %s\n", path, devices[0]->name);
622 
623 	UnregisterDevices(devices);
624 
625 	fDevices.RemoveItem(device);
626 
627 	return B_OK;
628 }
629