syndaemon.c revision 28515619
1/* 2 * Copyright © 2003-2004 Peter Osterlund 3 * 4 * Permission to use, copy, modify, distribute, and sell this software 5 * and its documentation for any purpose is hereby granted without 6 * fee, provided that the above copyright notice appear in all copies 7 * and that both that copyright notice and this permission notice 8 * appear in supporting documentation, and that the name of Red Hat 9 * not be used in advertising or publicity pertaining to distribution 10 * of the software without specific, written prior permission. Red 11 * Hat makes no representations about the suitability of this software 12 * for any purpose. It is provided "as is" without express or implied 13 * warranty. 14 * 15 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN 17 * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR 18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 19 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 20 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 22 * 23 * Authors: 24 * Peter Osterlund (petero2@telia.com) 25 */ 26 27#ifdef HAVE_CONFIG_H 28#include "config.h" 29#endif 30 31#include <X11/Xlib.h> 32#include <X11/Xatom.h> 33#include <X11/extensions/XInput.h> 34#ifdef HAVE_X11_EXTENSIONS_RECORD_H 35#include <X11/Xproto.h> 36#include <X11/extensions/record.h> 37#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ 38 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <sys/types.h> 43#include <unistd.h> 44#include <signal.h> 45#include <sys/time.h> 46#include <sys/stat.h> 47 48#include "synaptics-properties.h" 49 50enum TouchpadState { 51 TouchpadOn = 0, 52 TouchpadOff = 1, 53 TappingOff = 2 54}; 55 56static Bool pad_disabled 57 /* internal flag, this does not correspond to device state */ ; 58static int ignore_modifier_combos; 59static int ignore_modifier_keys; 60static int background; 61static const char *pid_file; 62static Display *display; 63static XDevice *dev; 64static Atom touchpad_off_prop; 65static enum TouchpadState previous_state; 66static enum TouchpadState disable_state = TouchpadOff; 67static int verbose; 68 69#define KEYMAP_SIZE 32 70static unsigned char keyboard_mask[KEYMAP_SIZE]; 71 72static void 73usage(void) 74{ 75 fprintf(stderr, 76 "Usage: syndaemon [-i idle-time] [-m poll-delay] [-d] [-t] [-k]\n"); 77 fprintf(stderr, 78 " -i How many seconds to wait after the last key press before\n"); 79 fprintf(stderr, " enabling the touchpad. (default is 2.0s)\n"); 80 fprintf(stderr, " -m How many milli-seconds to wait until next poll.\n"); 81 fprintf(stderr, " (default is 200ms)\n"); 82 fprintf(stderr, " -d Start as a daemon, i.e. in the background.\n"); 83 fprintf(stderr, " -p Create a pid file with the specified name.\n"); 84 fprintf(stderr, 85 " -t Only disable tapping and scrolling, not mouse movements.\n"); 86 fprintf(stderr, 87 " -k Ignore modifier keys when monitoring keyboard activity.\n"); 88 fprintf(stderr, " -K Like -k but also ignore Modifier+Key combos.\n"); 89 fprintf(stderr, " -R Use the XRecord extension.\n"); 90 fprintf(stderr, " -v Print diagnostic messages.\n"); 91 fprintf(stderr, " -? Show this help message.\n"); 92 exit(1); 93} 94 95static void 96store_current_touchpad_state(void) 97{ 98 Atom real_type; 99 int real_format; 100 unsigned long nitems, bytes_after; 101 unsigned char *data; 102 103 if ((XGetDeviceProperty(display, dev, touchpad_off_prop, 0, 1, False, 104 XA_INTEGER, &real_type, &real_format, &nitems, 105 &bytes_after, &data) == Success) && 106 (real_type != None)) { 107 previous_state = data[0]; 108 } 109} 110 111/** 112 * Toggle touchpad enabled/disabled state, decided by value. 113 */ 114static void 115toggle_touchpad(Bool enable) 116{ 117 unsigned char data; 118 119 if (pad_disabled && enable) { 120 data = previous_state; 121 pad_disabled = False; 122 if (verbose) 123 printf("Enable\n"); 124 } 125 else if (!pad_disabled && !enable && 126 previous_state != disable_state && previous_state != TouchpadOff) { 127 store_current_touchpad_state(); 128 pad_disabled = True; 129 data = disable_state; 130 if (verbose) 131 printf("Disable\n"); 132 } 133 else 134 return; 135 136 /* This potentially overwrites a different client's setting, but ... */ 137 XChangeDeviceProperty(display, dev, touchpad_off_prop, XA_INTEGER, 8, 138 PropModeReplace, &data, 1); 139 XFlush(display); 140} 141 142static void 143signal_handler(int signum) 144{ 145 toggle_touchpad(True); 146 147 if (pid_file) 148 unlink(pid_file); 149 kill(getpid(), signum); 150} 151 152static void 153install_signal_handler(void) 154{ 155 static int signals[] = { 156 SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, 157 SIGBUS, SIGFPE, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, 158 SIGALRM, SIGTERM, 159#ifdef SIGPWR 160 SIGPWR 161#endif 162 }; 163 int i; 164 struct sigaction act; 165 sigset_t set; 166 167 sigemptyset(&set); 168 act.sa_handler = signal_handler; 169 act.sa_mask = set; 170#ifdef SA_ONESHOT 171 act.sa_flags = SA_ONESHOT; 172#else 173 act.sa_flags = 0; 174#endif 175 176 for (i = 0; i < sizeof(signals) / sizeof(int); i++) { 177 if (sigaction(signals[i], &act, NULL) == -1) { 178 perror("sigaction"); 179 exit(2); 180 } 181 } 182} 183 184/** 185 * Return non-zero if the keyboard state has changed since the last call. 186 */ 187static int 188keyboard_activity(Display * display) 189{ 190 static unsigned char old_key_state[KEYMAP_SIZE]; 191 unsigned char key_state[KEYMAP_SIZE]; 192 int i; 193 int ret = 0; 194 195 XQueryKeymap(display, (char *) key_state); 196 197 for (i = 0; i < KEYMAP_SIZE; i++) { 198 if ((key_state[i] & ~old_key_state[i]) & keyboard_mask[i]) { 199 ret = 1; 200 break; 201 } 202 } 203 if (ignore_modifier_combos) { 204 for (i = 0; i < KEYMAP_SIZE; i++) { 205 if (key_state[i] & ~keyboard_mask[i]) { 206 ret = 0; 207 break; 208 } 209 } 210 } 211 for (i = 0; i < KEYMAP_SIZE; i++) 212 old_key_state[i] = key_state[i]; 213 return ret; 214} 215 216static double 217get_time(void) 218{ 219 struct timeval tv; 220 221 gettimeofday(&tv, NULL); 222 return tv.tv_sec + tv.tv_usec / 1000000.0; 223} 224 225static void 226main_loop(Display * display, double idle_time, int poll_delay) 227{ 228 double last_activity = 0.0; 229 double current_time; 230 231 keyboard_activity(display); 232 233 for (;;) { 234 current_time = get_time(); 235 if (keyboard_activity(display)) 236 last_activity = current_time; 237 238 /* If system times goes backwards, touchpad can get locked. Make 239 * sure our last activity wasn't in the future and reset if it was. */ 240 if (last_activity > current_time) 241 last_activity = current_time - idle_time - 1; 242 243 if (current_time > last_activity + idle_time) { /* Enable touchpad */ 244 toggle_touchpad(True); 245 } 246 else { /* Disable touchpad */ 247 toggle_touchpad(False); 248 } 249 250 usleep(poll_delay); 251 } 252} 253 254static void 255clear_bit(unsigned char *ptr, int bit) 256{ 257 int byte_num = bit / 8; 258 int bit_num = bit % 8; 259 260 ptr[byte_num] &= ~(1 << bit_num); 261} 262 263static void 264setup_keyboard_mask(Display * display, int ignore_modifier_keys) 265{ 266 XModifierKeymap *modifiers; 267 int i; 268 269 for (i = 0; i < KEYMAP_SIZE; i++) 270 keyboard_mask[i] = 0xff; 271 272 if (ignore_modifier_keys) { 273 modifiers = XGetModifierMapping(display); 274 for (i = 0; i < 8 * modifiers->max_keypermod; i++) { 275 KeyCode kc = modifiers->modifiermap[i]; 276 277 if (kc != 0) 278 clear_bit(keyboard_mask, kc); 279 } 280 XFreeModifiermap(modifiers); 281 } 282} 283 284/* ---- the following code is for using the xrecord extension ----- */ 285#ifdef HAVE_X11_EXTENSIONS_RECORD_H 286 287#define MAX_MODIFIERS 16 288 289/* used for exchanging information with the callback function */ 290struct xrecord_callback_results { 291 XModifierKeymap *modifiers; 292 Bool key_event; 293 Bool non_modifier_event; 294 KeyCode pressed_modifiers[MAX_MODIFIERS]; 295}; 296 297/* test if the xrecord extension is found */ 298Bool 299check_xrecord(Display * display) 300{ 301 302 Bool found; 303 Status status; 304 int major_opcode, minor_opcode, first_error; 305 int version[2]; 306 307 found = XQueryExtension(display, 308 "RECORD", 309 &major_opcode, &minor_opcode, &first_error); 310 311 status = XRecordQueryVersion(display, version, version + 1); 312 if (verbose && status) { 313 printf("X RECORD extension version %d.%d\n", version[0], version[1]); 314 } 315 return found; 316} 317 318/* called by XRecordProcessReplies() */ 319void 320xrecord_callback(XPointer closure, XRecordInterceptData * recorded_data) 321{ 322 323 struct xrecord_callback_results *cbres; 324 xEvent *xev; 325 int nxev; 326 327 cbres = (struct xrecord_callback_results *) closure; 328 329 if (recorded_data->category != XRecordFromServer) { 330 XRecordFreeData(recorded_data); 331 return; 332 } 333 334 nxev = recorded_data->data_len / 8; 335 xev = (xEvent *) recorded_data->data; 336 while (nxev--) { 337 338 if ((xev->u.u.type == KeyPress) || (xev->u.u.type == KeyRelease)) { 339 int i; 340 int is_modifier = 0; 341 342 cbres->key_event = 1; /* remember, a key was pressed or released. */ 343 344 /* test if it was a modifier */ 345 for (i = 0; i < 8 * cbres->modifiers->max_keypermod; i++) { 346 KeyCode kc = cbres->modifiers->modifiermap[i]; 347 348 if (kc == xev->u.u.detail) { 349 is_modifier = 1; /* yes, it is a modifier. */ 350 break; 351 } 352 } 353 354 if (is_modifier) { 355 if (xev->u.u.type == KeyPress) { 356 for (i = 0; i < MAX_MODIFIERS; ++i) 357 if (!cbres->pressed_modifiers[i]) { 358 cbres->pressed_modifiers[i] = xev->u.u.detail; 359 break; 360 } 361 } 362 else { /* KeyRelease */ 363 for (i = 0; i < MAX_MODIFIERS; ++i) 364 if (cbres->pressed_modifiers[i] == xev->u.u.detail) 365 cbres->pressed_modifiers[i] = 0; 366 } 367 368 } 369 else { 370 /* remember, a non-modifier was pressed. */ 371 cbres->non_modifier_event = 1; 372 } 373 } 374 375 xev++; 376 } 377 378 XRecordFreeData(recorded_data); /* cleanup */ 379} 380 381static int 382is_modifier_pressed(const struct xrecord_callback_results *cbres) 383{ 384 int i; 385 386 for (i = 0; i < MAX_MODIFIERS; ++i) 387 if (cbres->pressed_modifiers[i]) 388 return 1; 389 390 return 0; 391} 392 393void 394record_main_loop(Display * display, double idle_time) 395{ 396 397 struct xrecord_callback_results cbres; 398 XRecordContext context; 399 XRecordClientSpec cspec = XRecordAllClients; 400 Display *dpy_data; 401 XRecordRange *range; 402 int i; 403 404 dpy_data = XOpenDisplay(NULL); /* we need an additional data connection. */ 405 range = XRecordAllocRange(); 406 407 range->device_events.first = KeyPress; 408 range->device_events.last = KeyRelease; 409 410 context = XRecordCreateContext(dpy_data, 0, &cspec, 1, &range, 1); 411 412 XRecordEnableContextAsync(dpy_data, context, xrecord_callback, 413 (XPointer) & cbres); 414 415 cbres.modifiers = XGetModifierMapping(display); 416 /* clear list of modifiers */ 417 for (i = 0; i < MAX_MODIFIERS; ++i) 418 cbres.pressed_modifiers[i] = 0; 419 420 while (1) { 421 422 int fd = ConnectionNumber(dpy_data); 423 fd_set read_fds; 424 int ret; 425 int disable_event = 0; 426 struct timeval timeout; 427 428 FD_ZERO(&read_fds); 429 FD_SET(fd, &read_fds); 430 431 ret = select(fd + 1 /* =(max descriptor in read_fds) + 1 */ , 432 &read_fds, NULL, NULL, 433 pad_disabled ? &timeout : NULL 434 /* timeout only required for enabling */ ); 435 436 if (FD_ISSET(fd, &read_fds)) { 437 438 cbres.key_event = 0; 439 cbres.non_modifier_event = 0; 440 441 XRecordProcessReplies(dpy_data); 442 443 /* If there are any events left over, they are in error. Drain them 444 * from the connection queue so we don't get stuck. */ 445 while (XEventsQueued(dpy_data, QueuedAlready) > 0) { 446 XEvent event; 447 448 XNextEvent(dpy_data, &event); 449 fprintf(stderr, "bad event received, major opcode %d\n", 450 event.type); 451 } 452 453 if (!ignore_modifier_keys && cbres.key_event) { 454 disable_event = 1; 455 } 456 457 if (cbres.non_modifier_event && 458 !(ignore_modifier_combos && is_modifier_pressed(&cbres))) { 459 disable_event = 1; 460 } 461 } 462 463 if (disable_event) { 464 /* adjust the enable_time */ 465 timeout.tv_sec = (int) idle_time; 466 timeout.tv_usec = (idle_time - (double) timeout.tv_sec) * 1.e6; 467 468 toggle_touchpad(False); 469 } 470 471 if (ret == 0 && pad_disabled) { /* timeout => enable event */ 472 toggle_touchpad(True); 473 } 474 475 } /* end while(1) */ 476 477 XFreeModifiermap(cbres.modifiers); 478} 479#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ 480 481static XDevice * 482dp_get_device(Display * dpy) 483{ 484 XDevice *dev = NULL; 485 XDeviceInfo *info = NULL; 486 int ndevices = 0; 487 Atom touchpad_type = 0; 488 Atom *properties = NULL; 489 int nprops = 0; 490 int error = 0; 491 492 touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); 493 touchpad_off_prop = XInternAtom(dpy, SYNAPTICS_PROP_OFF, True); 494 info = XListInputDevices(dpy, &ndevices); 495 496 while (ndevices--) { 497 if (info[ndevices].type == touchpad_type) { 498 dev = XOpenDevice(dpy, info[ndevices].id); 499 if (!dev) { 500 fprintf(stderr, "Failed to open device '%s'.\n", 501 info[ndevices].name); 502 error = 1; 503 goto unwind; 504 } 505 506 properties = XListDeviceProperties(dpy, dev, &nprops); 507 if (!properties || !nprops) { 508 fprintf(stderr, "No properties on device '%s'.\n", 509 info[ndevices].name); 510 error = 1; 511 goto unwind; 512 } 513 514 while (nprops--) { 515 if (properties[nprops] == touchpad_off_prop) 516 break; 517 } 518 if (nprops < 0) { 519 fprintf(stderr, "No synaptics properties on device '%s'.\n", 520 info[ndevices].name); 521 error = 1; 522 goto unwind; 523 } 524 525 break; /* Yay, device is suitable */ 526 } 527 } 528 529 unwind: 530 XFree(properties); 531 XFreeDeviceList(info); 532 if (!dev) 533 fprintf(stderr, "Unable to find a synaptics device.\n"); 534 else if (error && dev) { 535 XCloseDevice(dpy, dev); 536 dev = NULL; 537 } 538 return dev; 539} 540 541int 542main(int argc, char *argv[]) 543{ 544 double idle_time = 2.0; 545 int poll_delay = 200000; /* 200 ms */ 546 int c; 547 int use_xrecord = 0; 548 549 /* Parse command line parameters */ 550 while ((c = getopt(argc, argv, "i:m:dtp:kKR?v")) != EOF) { 551 switch (c) { 552 case 'i': 553 idle_time = atof(optarg); 554 break; 555 case 'm': 556 poll_delay = atoi(optarg) * 1000; 557 break; 558 case 'd': 559 background = 1; 560 break; 561 case 't': 562 disable_state = TappingOff; 563 break; 564 case 'p': 565 pid_file = optarg; 566 break; 567 case 'k': 568 ignore_modifier_keys = 1; 569 break; 570 case 'K': 571 ignore_modifier_combos = 1; 572 ignore_modifier_keys = 1; 573 break; 574 case 'R': 575 use_xrecord = 1; 576 break; 577 case 'v': 578 verbose = 1; 579 break; 580 case '?': 581 default: 582 usage(); 583 break; 584 } 585 } 586 if (idle_time <= 0.0) 587 usage(); 588 589 /* Open a connection to the X server */ 590 display = XOpenDisplay(NULL); 591 if (!display) { 592 fprintf(stderr, "Can't open display.\n"); 593 exit(2); 594 } 595 596 if (!(dev = dp_get_device(display))) 597 exit(2); 598 599 /* Install a signal handler to restore synaptics parameters on exit */ 600 install_signal_handler(); 601 602 if (background) { 603 pid_t pid; 604 605 if ((pid = fork()) < 0) { 606 perror("fork"); 607 exit(3); 608 } 609 else if (pid != 0) 610 exit(0); 611 612 /* Child (daemon) is running here */ 613 setsid(); /* Become session leader */ 614 chdir("/"); /* In case the file system gets unmounted */ 615 umask(0); /* We don't want any surprises */ 616 if (pid_file) { 617 FILE *fd = fopen(pid_file, "w"); 618 619 if (!fd) { 620 perror("Can't create pid file"); 621 exit(3); 622 } 623 fprintf(fd, "%d\n", getpid()); 624 fclose(fd); 625 } 626 } 627 628 pad_disabled = False; 629 store_current_touchpad_state(); 630 631#ifdef HAVE_X11_EXTENSIONS_RECORD_H 632 if (use_xrecord) { 633 if (check_xrecord(display)) 634 record_main_loop(display, idle_time); 635 else { 636 fprintf(stderr, "Use of XRecord requested, but failed to " 637 " initialize.\n"); 638 exit(4); 639 } 640 } 641 else 642#endif /* HAVE_X11_EXTENSIONS_RECORD_H */ 643 { 644 setup_keyboard_mask(display, ignore_modifier_keys); 645 646 /* Run the main loop */ 647 main_loop(display, idle_time, poll_delay); 648 } 649 return 0; 650} 651 652/* vim: set noexpandtab tabstop=8 shiftwidth=4: */ 653