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
91const static uint32 kTabletThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
92const static char* kTabletDevicesDirectory = "/dev/input/tablet";
93
94
95class TabletDevice {
96public:
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
109private:
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
121private:
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
135extern "C" BInputServerDevice*
136instantiate_input_device()
137{
138	return new(std::nothrow) TabletInputDevice();
139}
140
141
142//	#pragma mark -
143
144
145TabletDevice::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
162TabletDevice::~TabletDevice()
163{
164	TD_CALLED();
165	TRACE("delete\n");
166
167	if (fActive)
168		Stop();
169
170	free(fDeviceRef.name);
171}
172
173
174status_t
175TabletDevice::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
209void
210TabletDevice::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
231status_t
232TabletDevice::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
246char*
247TabletDevice::_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
272status_t
273TabletDevice::_ControlThreadEntry(void* arg)
274{
275	TabletDevice* device = (TabletDevice*)arg;
276	device->_ControlThread();
277	return B_OK;
278}
279
280
281void
282TabletDevice::_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		// Send single messages for each event
326
327		uint32 buttons = lastButtons ^ movements.buttons;
328		if (buttons != 0) {
329			bool pressedButton = (buttons & movements.buttons) > 0;
330			BMessage* message = _BuildMouseMessage(
331				pressedButton ? B_MOUSE_DOWN : B_MOUSE_UP,
332				movements.timestamp, movements.buttons, movements.xpos,
333				movements.ypos);
334			if (message != NULL) {
335				if (pressedButton) {
336					message->AddInt32("clicks", movements.clicks);
337					LOG_EVENT("B_MOUSE_DOWN\n");
338				} else
339					LOG_EVENT("B_MOUSE_UP\n");
340
341				fTarget.EnqueueMessage(message);
342				lastButtons = movements.buttons;
343			}
344		}
345
346		if (movements.xpos != lastXPosition
347			|| movements.ypos != lastYPosition) {
348			BMessage* message = _BuildMouseMessage(B_MOUSE_MOVED,
349				movements.timestamp, movements.buttons, movements.xpos,
350				movements.ypos);
351			if (message != NULL) {
352				message->AddFloat("be:tablet_x", movements.xpos);
353				message->AddFloat("be:tablet_y", movements.ypos);
354				message->AddFloat("be:tablet_pressure", movements.pressure);
355				message->AddInt32("be:tablet_eraser", movements.eraser);
356				if (movements.tilt_x != 0.0 || movements.tilt_y != 0.0) {
357					message->AddFloat("be:tablet_tilt_x", movements.tilt_x);
358					message->AddFloat("be:tablet_tilt_y", movements.tilt_y);
359				}
360
361				fTarget.EnqueueMessage(message);
362				lastXPosition = movements.xpos;
363				lastYPosition = movements.ypos;
364			}
365		}
366
367		if (movements.wheel_ydelta != 0 || movements.wheel_xdelta != 0) {
368			BMessage* message = new BMessage(B_MOUSE_WHEEL_CHANGED);
369			if (message == NULL)
370				continue;
371
372			if (message->AddInt64("when", movements.timestamp) == B_OK
373				&& message->AddFloat("be:wheel_delta_x",
374					movements.wheel_xdelta) == B_OK
375				&& message->AddFloat("be:wheel_delta_y",
376					movements.wheel_ydelta) == B_OK)
377				fTarget.EnqueueMessage(message);
378			else
379				delete message;
380		}
381	}
382}
383
384
385void
386TabletDevice::_ControlThreadCleanup()
387{
388	// NOTE: Only executed when the control thread detected an error
389	// and from within the control thread!
390
391	if (fActive) {
392		fThread = -1;
393		fTarget._RemoveDevice(fPath.String());
394	} else {
395		// In case active is already false, another thread
396		// waits for this thread to quit, and may already hold
397		// locks that _RemoveDevice() wants to acquire. In other
398		// words, the device is already being removed, so we simply
399		// quit here.
400	}
401}
402
403
404void
405TabletDevice::_UpdateSettings()
406{
407	TD_CALLED();
408
409	if (get_click_speed(&fSettings.click_speed) != B_OK)
410		LOG_ERR("error when get_click_speed\n");
411	else
412		ioctl(fDevice, MS_SET_CLICKSPEED, &fSettings.click_speed);
413}
414
415
416BMessage*
417TabletDevice::_BuildMouseMessage(uint32 what, uint64 when, uint32 buttons,
418	float xPosition, float yPosition) const
419{
420	BMessage* message = new BMessage(what);
421	if (message == NULL)
422		return NULL;
423
424	if (message->AddInt64("when", when) < B_OK
425		|| message->AddInt32("buttons", buttons) < B_OK
426		|| message->AddFloat("x", xPosition) < B_OK
427		|| message->AddFloat("y", yPosition) < B_OK) {
428		delete message;
429		return NULL;
430	}
431
432	return message;
433}
434
435
436//	#pragma mark -
437
438
439TabletInputDevice::TabletInputDevice()
440	:
441	fDevices(2, true),
442	fDeviceListLock("TabletInputDevice list")
443{
444	TID_CALLED();
445
446	StartMonitoringDevice(kTabletDevicesDirectory);
447	_RecursiveScan(kTabletDevicesDirectory);
448}
449
450
451TabletInputDevice::~TabletInputDevice()
452{
453	TID_CALLED();
454
455	StopMonitoringDevice(kTabletDevicesDirectory);
456	fDevices.MakeEmpty();
457}
458
459
460status_t
461TabletInputDevice::InitCheck()
462{
463	TID_CALLED();
464
465	return BInputServerDevice::InitCheck();
466}
467
468
469status_t
470TabletInputDevice::Start(const char* name, void* cookie)
471{
472	TID_CALLED();
473
474	TabletDevice* device = (TabletDevice*)cookie;
475
476	return device->Start();
477}
478
479
480status_t
481TabletInputDevice::Stop(const char* name, void* cookie)
482{
483	TRACE("%s(%s)\n", __PRETTY_FUNCTION__, name);
484
485	TabletDevice* device = (TabletDevice*)cookie;
486	device->Stop();
487
488	return B_OK;
489}
490
491
492status_t
493TabletInputDevice::Control(const char* name, void* cookie,
494	uint32 command, BMessage* message)
495{
496	TRACE("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
497
498	TabletDevice* device = (TabletDevice*)cookie;
499
500	if (command == B_NODE_MONITOR)
501		return _HandleMonitor(message);
502
503	if (command == B_CLICK_SPEED_CHANGED)
504		return device->UpdateSettings();
505
506	return B_BAD_VALUE;
507}
508
509
510status_t
511TabletInputDevice::_HandleMonitor(BMessage* message)
512{
513	TID_CALLED();
514
515	const char* path;
516	int32 opcode;
517	if (message->FindInt32("opcode", &opcode) != B_OK
518		|| (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED)
519		|| message->FindString("path", &path) != B_OK)
520		return B_BAD_VALUE;
521
522	if (opcode == B_ENTRY_CREATED)
523		return _AddDevice(path);
524
525	// Don't handle B_ENTRY_REMOVED, let the control thread take care of it.
526	return B_OK;
527}
528
529
530void
531TabletInputDevice::_RecursiveScan(const char* directory)
532{
533	TID_CALLED();
534
535	BEntry entry;
536	BDirectory dir(directory);
537	while (dir.GetNextEntry(&entry) == B_OK) {
538		BPath path;
539		entry.GetPath(&path);
540
541		if (entry.IsDirectory())
542			_RecursiveScan(path.Path());
543		else
544			_AddDevice(path.Path());
545	}
546}
547
548
549TabletDevice*
550TabletInputDevice::_FindDevice(const char* path) const
551{
552	TID_CALLED();
553
554	for (int32 i = fDevices.CountItems() - 1; i >= 0; i--) {
555		TabletDevice* device = fDevices.ItemAt(i);
556		if (strcmp(device->Path(), path) == 0)
557			return device;
558	}
559
560	return NULL;
561}
562
563
564status_t
565TabletInputDevice::_AddDevice(const char* path)
566{
567	TID_CALLED();
568
569	BAutolock _(fDeviceListLock);
570
571	_RemoveDevice(path);
572
573	TabletDevice* device = new(std::nothrow) TabletDevice(*this, path);
574	if (device == NULL) {
575		TRACE("No memory\n");
576		return B_NO_MEMORY;
577	}
578
579	if (!fDevices.AddItem(device)) {
580		TRACE("No memory in list\n");
581		delete device;
582		return B_NO_MEMORY;
583	}
584
585	input_device_ref* devices[2];
586	devices[0] = device->DeviceRef();
587	devices[1] = NULL;
588
589	TRACE("adding path: %s, name: %s\n", path, devices[0]->name);
590
591	return RegisterDevices(devices);
592}
593
594
595status_t
596TabletInputDevice::_RemoveDevice(const char* path)
597{
598	TID_CALLED();
599
600	BAutolock _(fDeviceListLock);
601
602	TabletDevice* device = _FindDevice(path);
603	if (device == NULL) {
604		TRACE("%s not found\n", path);
605		return B_ENTRY_NOT_FOUND;
606	}
607
608	input_device_ref* devices[2];
609	devices[0] = device->DeviceRef();
610	devices[1] = NULL;
611
612	TRACE("removing path: %s, name: %s\n", path, devices[0]->name);
613
614	UnregisterDevices(devices);
615
616	fDevices.RemoveItem(device);
617
618	return B_OK;
619}
620