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:
FunctionTracer(const void * pointer,const char * className,const char * functionName,int32 & depth)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
~FunctionTracer()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
Path() const106 const char* Path() const { return fPath.String(); }
DeviceRef()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*
instantiate_input_device()136 instantiate_input_device()
137 {
138 return new(std::nothrow) TabletInputDevice();
139 }
140
141
142 // #pragma mark -
143
144
TabletDevice(TabletInputDevice & target,const char * driverPath)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
~TabletDevice()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
Start()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
Stop()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
UpdateSettings()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*
_BuildShortName() const247 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
_ControlThreadEntry(void * arg)273 TabletDevice::_ControlThreadEntry(void* arg)
274 {
275 TabletDevice* device = (TabletDevice*)arg;
276 device->_ControlThread();
277 return B_OK;
278 }
279
280
281 void
_ControlThread()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 && message->AddInt32("be:device_subtype",
386 B_TABLET_DEVICE_SUBTYPE) == B_OK)
387 fTarget.EnqueueMessage(message);
388 else
389 delete message;
390 }
391 }
392 }
393 }
394
395
396 void
_ControlThreadCleanup()397 TabletDevice::_ControlThreadCleanup()
398 {
399 // NOTE: Only executed when the control thread detected an error
400 // and from within the control thread!
401
402 if (fActive) {
403 fThread = -1;
404 fTarget._RemoveDevice(fPath.String());
405 } else {
406 // In case active is already false, another thread
407 // waits for this thread to quit, and may already hold
408 // locks that _RemoveDevice() wants to acquire. In other
409 // words, the device is already being removed, so we simply
410 // quit here.
411 }
412 }
413
414
415 void
_UpdateSettings()416 TabletDevice::_UpdateSettings()
417 {
418 TD_CALLED();
419
420 if (get_click_speed(fDeviceRef.name, &fSettings.click_speed) != B_OK)
421 LOG_ERR("error when get_click_speed\n");
422 else
423 ioctl(fDevice, MS_SET_CLICKSPEED, &fSettings.click_speed, sizeof(bigtime_t));
424 }
425
426
427 BMessage*
_BuildMouseMessage(uint32 what,uint64 when,uint32 buttons,float xPosition,float yPosition) const428 TabletDevice::_BuildMouseMessage(uint32 what, uint64 when, uint32 buttons,
429 float xPosition, float yPosition) const
430 {
431 BMessage* message = new BMessage(what);
432 if (message == NULL)
433 return NULL;
434
435 if (message->AddInt64("when", when) < B_OK
436 || message->AddInt32("buttons", buttons) < B_OK
437 || message->AddFloat("x", xPosition) < B_OK
438 || message->AddFloat("y", yPosition) < B_OK
439 || message->AddInt32("be:device_subtype",
440 B_TABLET_DEVICE_SUBTYPE) < B_OK) {
441 delete message;
442 return NULL;
443 }
444
445 return message;
446 }
447
448
449 // #pragma mark -
450
451
TabletInputDevice()452 TabletInputDevice::TabletInputDevice()
453 :
454 fDevices(2, true),
455 fDeviceListLock("TabletInputDevice list")
456 {
457 TID_CALLED();
458
459 StartMonitoringDevice(kTabletDevicesDirectory);
460 _RecursiveScan(kTabletDevicesDirectory);
461 }
462
463
~TabletInputDevice()464 TabletInputDevice::~TabletInputDevice()
465 {
466 TID_CALLED();
467
468 StopMonitoringDevice(kTabletDevicesDirectory);
469 fDevices.MakeEmpty();
470 }
471
472
473 status_t
InitCheck()474 TabletInputDevice::InitCheck()
475 {
476 TID_CALLED();
477
478 return BInputServerDevice::InitCheck();
479 }
480
481
482 status_t
Start(const char * name,void * cookie)483 TabletInputDevice::Start(const char* name, void* cookie)
484 {
485 TID_CALLED();
486
487 TabletDevice* device = (TabletDevice*)cookie;
488
489 return device->Start();
490 }
491
492
493 status_t
Stop(const char * name,void * cookie)494 TabletInputDevice::Stop(const char* name, void* cookie)
495 {
496 TRACE("%s(%s)\n", __PRETTY_FUNCTION__, name);
497
498 TabletDevice* device = (TabletDevice*)cookie;
499 device->Stop();
500
501 return B_OK;
502 }
503
504
505 status_t
Control(const char * name,void * cookie,uint32 command,BMessage * message)506 TabletInputDevice::Control(const char* name, void* cookie,
507 uint32 command, BMessage* message)
508 {
509 TRACE("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
510
511 TabletDevice* device = (TabletDevice*)cookie;
512
513 if (command == B_NODE_MONITOR)
514 return _HandleMonitor(message);
515
516 if (command == B_CLICK_SPEED_CHANGED)
517 return device->UpdateSettings();
518
519 return B_BAD_VALUE;
520 }
521
522
523 status_t
_HandleMonitor(BMessage * message)524 TabletInputDevice::_HandleMonitor(BMessage* message)
525 {
526 TID_CALLED();
527
528 const char* path;
529 int32 opcode;
530 if (message->FindInt32("opcode", &opcode) != B_OK
531 || (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED)
532 || message->FindString("path", &path) != B_OK)
533 return B_BAD_VALUE;
534
535 if (opcode == B_ENTRY_CREATED)
536 return _AddDevice(path);
537
538 // Don't handle B_ENTRY_REMOVED, let the control thread take care of it.
539 return B_OK;
540 }
541
542
543 void
_RecursiveScan(const char * directory)544 TabletInputDevice::_RecursiveScan(const char* directory)
545 {
546 TID_CALLED();
547
548 BEntry entry;
549 BDirectory dir(directory);
550 while (dir.GetNextEntry(&entry) == B_OK) {
551 BPath path;
552 entry.GetPath(&path);
553
554 if (entry.IsDirectory())
555 _RecursiveScan(path.Path());
556 else
557 _AddDevice(path.Path());
558 }
559 }
560
561
562 TabletDevice*
_FindDevice(const char * path) const563 TabletInputDevice::_FindDevice(const char* path) const
564 {
565 TID_CALLED();
566
567 for (int32 i = fDevices.CountItems() - 1; i >= 0; i--) {
568 TabletDevice* device = fDevices.ItemAt(i);
569 if (strcmp(device->Path(), path) == 0)
570 return device;
571 }
572
573 return NULL;
574 }
575
576
577 status_t
_AddDevice(const char * path)578 TabletInputDevice::_AddDevice(const char* path)
579 {
580 TID_CALLED();
581
582 BAutolock _(fDeviceListLock);
583
584 _RemoveDevice(path);
585
586 TabletDevice* device = new(std::nothrow) TabletDevice(*this, path);
587 if (device == NULL) {
588 TRACE("No memory\n");
589 return B_NO_MEMORY;
590 }
591
592 if (!fDevices.AddItem(device)) {
593 TRACE("No memory in list\n");
594 delete device;
595 return B_NO_MEMORY;
596 }
597
598 input_device_ref* devices[2];
599 devices[0] = device->DeviceRef();
600 devices[1] = NULL;
601
602 TRACE("adding path: %s, name: %s\n", path, devices[0]->name);
603
604 return RegisterDevices(devices);
605 }
606
607
608 status_t
_RemoveDevice(const char * path)609 TabletInputDevice::_RemoveDevice(const char* path)
610 {
611 TID_CALLED();
612
613 BAutolock _(fDeviceListLock);
614
615 TabletDevice* device = _FindDevice(path);
616 if (device == NULL) {
617 TRACE("%s not found\n", path);
618 return B_ENTRY_NOT_FOUND;
619 }
620
621 input_device_ref* devices[2];
622 devices[0] = device->DeviceRef();
623 devices[1] = NULL;
624
625 TRACE("removing path: %s, name: %s\n", path, devices[0]->name);
626
627 UnregisterDevices(devices);
628
629 fDevices.RemoveItem(device);
630
631 return B_OK;
632 }
633