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