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