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