1 /*****************************************************************************/ 2 // Tablet input server device addon 3 // Adapted by Jerome Duval and written by Stefano Ceccherini 4 // 5 // TabletInputDevice.cpp 6 // 7 // Copyright (c) 2005 Haiku Project 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining a 10 // copy of this software and associated documentation files (the "Software"), 11 // to deal in the Software without restriction, including without limitation 12 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 // and/or sell copies of the Software, and to permit persons to whom the 14 // Software is furnished to do so, subject to the following conditions: 15 // 16 // The above copyright notice and this permission notice shall be included 17 // in all copies or substantial portions of the Software. 18 // 19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 // DEALINGS IN THE SOFTWARE. 26 /*****************************************************************************/ 27 28 // TODO: Use strlcpy instead of strcpy 29 30 #include "TabletInputDevice.h" 31 #include "kb_mouse_settings.h" 32 #include "kb_mouse_driver.h" 33 34 #include <stdlib.h> 35 #include <unistd.h> 36 37 #include <Debug.h> 38 #include <Directory.h> 39 #include <Entry.h> 40 #include <NodeMonitor.h> 41 #include <Path.h> 42 #include <String.h> 43 44 #if DEBUG 45 inline void LOG(const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); \ 46 fputs(buf, TabletInputDevice::sLogFile); fflush(TabletInputDevice::sLogFile); } 47 #define LOG_ERR(text...) LOG(text) 48 FILE *TabletInputDevice::sLogFile = NULL; 49 #else 50 #define LOG(text...) 51 #define LOG_ERR(text...) fprintf(stderr, text) 52 #endif 53 54 #define CALLED() LOG("%s\n", __PRETTY_FUNCTION__) 55 56 static TabletInputDevice *sSingletonTabletDevice = NULL; 57 58 const static uint32 kTabletThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4; 59 const static char *kTabletDevicesDirectory = "/dev/input/tablet"; 60 61 // "/dev/" is automatically prepended by StartMonitoringDevice() 62 const static char *kTabletDevicesDirectoryUSB = "input/tablet/usb"; 63 64 struct tablet_device { 65 tablet_device(const char *path); 66 ~tablet_device(); 67 68 input_device_ref device_ref; 69 char path[B_PATH_NAME_LENGTH]; 70 int fd; 71 thread_id device_watcher; 72 mouse_settings settings; 73 bool active; 74 }; 75 76 77 // forward declarations 78 static char *get_short_name(const char *longName); 79 80 81 extern "C" 82 BInputServerDevice * 83 instantiate_input_device() 84 { 85 return new TabletInputDevice(); 86 } 87 88 89 TabletInputDevice::TabletInputDevice() 90 { 91 ASSERT(sSingletonTabletDevice == NULL); 92 sSingletonTabletDevice = this; 93 94 #if DEBUG 95 sLogFile = fopen("/var/log/tablet_device_log.log", "a"); 96 #endif 97 CALLED(); 98 99 StartMonitoringDevice(kTabletDevicesDirectoryUSB); 100 } 101 102 103 TabletInputDevice::~TabletInputDevice() 104 { 105 CALLED(); 106 StopMonitoringDevice(kTabletDevicesDirectoryUSB); 107 108 for (int32 i = 0; i < fDevices.CountItems(); i++) 109 delete (tablet_device *)fDevices.ItemAt(i); 110 111 #if DEBUG 112 fclose(sLogFile); 113 #endif 114 } 115 116 117 status_t 118 TabletInputDevice::InitFromSettings(void *cookie, uint32 opcode) 119 { 120 CALLED(); 121 tablet_device *device = (tablet_device *)cookie; 122 123 // retrieve current values 124 125 if (get_mouse_map(&device->settings.map) != B_OK) 126 LOG_ERR("error when get_mouse_map\n"); 127 else 128 ioctl(device->fd, MS_SET_MAP, &device->settings.map); 129 130 if (get_click_speed(&device->settings.click_speed) != B_OK) 131 LOG_ERR("error when get_click_speed\n"); 132 else 133 ioctl(device->fd, MS_SET_CLICKSPEED, &device->settings.click_speed); 134 135 if (get_mouse_speed(&device->settings.accel.speed) != B_OK) 136 LOG_ERR("error when get_mouse_speed\n"); 137 else { 138 if (get_mouse_acceleration(&device->settings.accel.accel_factor) != B_OK) 139 LOG_ERR("error when get_mouse_acceleration\n"); 140 else { 141 mouse_accel accel; 142 ioctl(device->fd, MS_GET_ACCEL, &accel); 143 accel.speed = device->settings.accel.speed; 144 accel.accel_factor = device->settings.accel.accel_factor; 145 ioctl(device->fd, MS_SET_ACCEL, &device->settings.accel); 146 } 147 } 148 149 if (get_mouse_type(&device->settings.type) != B_OK) 150 LOG_ERR("error when get_mouse_type\n"); 151 else 152 ioctl(device->fd, MS_SET_TYPE, &device->settings.type); 153 154 return B_OK; 155 156 } 157 158 159 status_t 160 TabletInputDevice::InitCheck() 161 { 162 CALLED(); 163 RecursiveScan(kTabletDevicesDirectory); 164 165 return B_OK; 166 } 167 168 169 status_t 170 TabletInputDevice::Start(const char *name, void *cookie) 171 { 172 tablet_device *device = (tablet_device *)cookie; 173 174 LOG("%s(%s)\n", __PRETTY_FUNCTION__, name); 175 176 device->fd = open(device->path, O_RDWR); 177 if (device->fd<0) 178 return B_ERROR; 179 180 InitFromSettings(device); 181 182 char threadName[B_OS_NAME_LENGTH]; 183 snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", name); 184 185 device->active = true; 186 device->device_watcher = spawn_thread(DeviceWatcher, threadName, 187 kTabletThreadPriority, device); 188 189 resume_thread(device->device_watcher); 190 191 return B_OK; 192 } 193 194 195 status_t 196 TabletInputDevice::Stop(const char *name, void *cookie) 197 { 198 tablet_device *device = (tablet_device *)cookie; 199 200 LOG("%s(%s)\n", __PRETTY_FUNCTION__, name); 201 202 close(device->fd); 203 204 device->active = false; 205 if (device->device_watcher >= 0) { 206 suspend_thread(device->device_watcher); 207 resume_thread(device->device_watcher); 208 status_t dummy; 209 wait_for_thread(device->device_watcher, &dummy); 210 } 211 212 return B_OK; 213 } 214 215 216 status_t 217 TabletInputDevice::Control(const char *name, void *cookie, 218 uint32 command, BMessage *message) 219 { 220 LOG("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command); 221 222 if (command == B_NODE_MONITOR) 223 HandleMonitor(message); 224 else if (command >= B_MOUSE_TYPE_CHANGED 225 && command <= B_MOUSE_ACCELERATION_CHANGED) { 226 InitFromSettings(cookie, command); 227 } 228 return B_OK; 229 } 230 231 232 // TODO: Test this. USB doesn't work on my machine 233 status_t 234 TabletInputDevice::HandleMonitor(BMessage *message) 235 { 236 CALLED(); 237 int32 opcode = 0; 238 status_t status; 239 if ((status = message->FindInt32("opcode", &opcode)) < B_OK) 240 return status; 241 242 if ((opcode != B_ENTRY_CREATED) 243 && (opcode != B_ENTRY_REMOVED)) 244 return B_OK; 245 246 247 BEntry entry; 248 BPath path; 249 dev_t device; 250 ino_t directory; 251 const char *name = NULL; 252 253 message->FindInt32("device", &device); 254 message->FindInt64("directory", &directory); 255 message->FindString("name", &name); 256 257 entry_ref ref(device, directory, name); 258 259 if ((status = entry.SetTo(&ref)) != B_OK) 260 return status; 261 if ((status = entry.GetPath(&path)) != B_OK) 262 return status; 263 if ((status = path.InitCheck()) != B_OK) 264 return status; 265 266 if (opcode == B_ENTRY_CREATED) 267 AddDevice(path.Path()); 268 else 269 RemoveDevice(path.Path()); 270 271 return status; 272 } 273 274 275 status_t 276 TabletInputDevice::AddDevice(const char *path) 277 { 278 CALLED(); 279 280 tablet_device *device = new tablet_device(path); 281 if (!device) { 282 LOG("No memory\n"); 283 return B_NO_MEMORY; 284 } 285 286 input_device_ref *devices[2]; 287 devices[0] = &device->device_ref; 288 devices[1] = NULL; 289 290 fDevices.AddItem(device); 291 292 return RegisterDevices(devices); 293 } 294 295 296 status_t 297 TabletInputDevice::RemoveDevice(const char *path) 298 { 299 CALLED(); 300 int32 i = 0; 301 tablet_device *device = NULL; 302 while ((device = (tablet_device *)fDevices.ItemAt(i)) != NULL) { 303 if (!strcmp(device->path, path)) { 304 fDevices.RemoveItem(device); 305 delete device; 306 return B_OK; 307 } 308 } 309 310 return B_ENTRY_NOT_FOUND; 311 } 312 313 314 int32 315 TabletInputDevice::DeviceWatcher(void *arg) 316 { 317 tablet_device *dev = (tablet_device *)arg; 318 319 tablet_movement movements; 320 tablet_movement old_movements; 321 uint32 buttons_state = 0; 322 BMessage *message; 323 while (dev->active) { 324 memset(&movements, 0, sizeof(movements)); 325 if (ioctl(dev->fd, MS_READ, &movements) != B_OK) { 326 snooze(10000); // this is a realtime thread, and something is wrong... 327 continue; 328 } 329 330 uint32 buttons = buttons_state ^ movements.buttons; 331 332 LOG("%s: buttons: 0x%lx, x: %f, y: %f, clicks:%ld, wheel_x:%ld, wheel_y:%ld\n", dev->device_ref.name, movements.buttons, 333 movements.xpos, movements.ypos, movements.clicks, movements.wheel_xdelta, movements.wheel_ydelta); 334 335 movements.xpos /= 10206.0; 336 movements.ypos /= 7422.0; 337 float x = movements.xpos; 338 float y = movements.ypos; 339 340 LOG("%s: x: %f, y: %f, \n", dev->device_ref.name, x, y); 341 342 if (buttons != 0) { 343 message = new BMessage(B_MOUSE_UP); 344 if ((buttons & movements.buttons) > 0) { 345 message->what = B_MOUSE_DOWN; 346 message->AddInt32("clicks", movements.clicks); 347 LOG("B_MOUSE_DOWN\n"); 348 } else { 349 LOG("B_MOUSE_UP\n"); 350 } 351 352 message->AddInt64("when", movements.timestamp); 353 message->AddInt32("buttons", movements.buttons); 354 message->AddFloat("x", movements.xpos); 355 message->AddFloat("y", movements.ypos); 356 sSingletonTabletDevice->EnqueueMessage(message); 357 buttons_state = movements.buttons; 358 } 359 360 if (movements.xpos != 0.0 || movements.ypos != 0.0) { 361 message = new BMessage(B_MOUSE_MOVED); 362 if (message) { 363 message->AddInt64("when", movements.timestamp); 364 message->AddInt32("buttons", movements.buttons); 365 message->AddFloat("x", x); 366 message->AddFloat("y", y); 367 message->AddFloat("be:tablet_x", movements.xpos); 368 message->AddFloat("be:tablet_y", movements.ypos); 369 message->AddFloat("be:tablet_pressure", movements.pressure); 370 message->AddInt32("be:tablet_eraser", movements.eraser); 371 if (movements.tilt_x != 0.0 || movements.tilt_y != 0.0) { 372 message->AddFloat("be:tablet_tilt_x", movements.tilt_x); 373 message->AddFloat("be:tablet_tilt_y", movements.tilt_y); 374 } 375 376 sSingletonTabletDevice->EnqueueMessage(message); 377 } 378 } 379 380 if ((movements.wheel_ydelta != 0) || (movements.wheel_xdelta != 0)) { 381 message = new BMessage(B_MOUSE_WHEEL_CHANGED); 382 if (message) { 383 message->AddInt64("when", movements.timestamp); 384 message->AddFloat("be:wheel_delta_x", movements.wheel_xdelta); 385 message->AddFloat("be:wheel_delta_y", movements.wheel_ydelta); 386 387 sSingletonTabletDevice->EnqueueMessage(message); 388 } 389 } 390 391 old_movements = movements; 392 393 } 394 395 return 0; 396 } 397 398 399 // tablet_device 400 tablet_device::tablet_device(const char *driver_path) 401 { 402 fd = -1; 403 device_watcher = -1; 404 active = false; 405 strcpy(path, driver_path); 406 device_ref.name = get_short_name(path); 407 device_ref.type = B_POINTING_DEVICE; 408 device_ref.cookie = this; 409 }; 410 411 412 tablet_device::~tablet_device() 413 { 414 free(device_ref.name); 415 } 416 417 418 void 419 TabletInputDevice::RecursiveScan(const char *directory) 420 { 421 CALLED(); 422 BEntry entry; 423 BDirectory dir(directory); 424 while (dir.GetNextEntry(&entry) == B_OK) { 425 BPath path; 426 entry.GetPath(&path); 427 428 if (entry.IsDirectory()) 429 RecursiveScan(path.Path()); 430 else 431 AddDevice(path.Path()); 432 } 433 } 434 435 436 static char * 437 get_short_name(const char *longName) 438 { 439 BString string(longName); 440 BString name; 441 442 int32 slash = string.FindLast("/"); 443 string.CopyInto(name, slash + 1, string.Length() - slash); 444 int32 index = atoi(name.String()) + 1; 445 446 int32 previousSlash = string.FindLast("/", slash); 447 string.CopyInto(name, previousSlash + 1, slash - previousSlash - 1); 448 name << " Tablet " << index; 449 450 return strdup(name.String()); 451 } 452