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 && 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 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 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* 428 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 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 464 TabletInputDevice::~TabletInputDevice() 465 { 466 TID_CALLED(); 467 468 StopMonitoringDevice(kTabletDevicesDirectory); 469 fDevices.MakeEmpty(); 470 } 471 472 473 status_t 474 TabletInputDevice::InitCheck() 475 { 476 TID_CALLED(); 477 478 return BInputServerDevice::InitCheck(); 479 } 480 481 482 status_t 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 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 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 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 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* 563 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 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 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