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