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		// 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
394void
395TabletDevice::_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
413void
414TabletDevice::_UpdateSettings()
415{
416	TD_CALLED();
417
418	if (get_click_speed(&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
425BMessage*
426TabletDevice::_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
448TabletInputDevice::TabletInputDevice()
449	:
450	fDevices(2, true),
451	fDeviceListLock("TabletInputDevice list")
452{
453	TID_CALLED();
454
455	StartMonitoringDevice(kTabletDevicesDirectory);
456	_RecursiveScan(kTabletDevicesDirectory);
457}
458
459
460TabletInputDevice::~TabletInputDevice()
461{
462	TID_CALLED();
463
464	StopMonitoringDevice(kTabletDevicesDirectory);
465	fDevices.MakeEmpty();
466}
467
468
469status_t
470TabletInputDevice::InitCheck()
471{
472	TID_CALLED();
473
474	return BInputServerDevice::InitCheck();
475}
476
477
478status_t
479TabletInputDevice::Start(const char* name, void* cookie)
480{
481	TID_CALLED();
482
483	TabletDevice* device = (TabletDevice*)cookie;
484
485	return device->Start();
486}
487
488
489status_t
490TabletInputDevice::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
501status_t
502TabletInputDevice::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
519status_t
520TabletInputDevice::_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
539void
540TabletInputDevice::_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
558TabletDevice*
559TabletInputDevice::_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
573status_t
574TabletInputDevice::_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
604status_t
605TabletInputDevice::_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