| /* |
| * Copyright © 2003-2004 Peter Osterlund |
| * |
| * Permission to use, copy, modify, distribute, and sell this software |
| * and its documentation for any purpose is hereby granted without |
| * fee, provided that the above copyright notice appear in all copies |
| * and that both that copyright notice and this permission notice |
| * appear in supporting documentation, and that the name of Red Hat |
| * not be used in advertising or publicity pertaining to distribution |
| * of the software without specific, written prior permission. Red |
| * Hat makes no representations about the suitability of this software |
| * for any purpose. It is provided "as is" without express or implied |
| * warranty. |
| * |
| * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, |
| * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN |
| * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR |
| * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS |
| * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
| * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| * |
| * Authors: |
| * Peter Osterlund (petero2@telia.com) |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <X11/Xlib.h> |
| #include <X11/Xatom.h> |
| #include <X11/extensions/XInput.h> |
| #ifdef HAVE_X11_EXTENSIONS_RECORD_H |
| #include <X11/Xproto.h> |
| #include <X11/extensions/record.h> |
| #endif /* HAVE_X11_EXTENSIONS_RECORD_H */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <sys/time.h> |
| #include <sys/stat.h> |
| |
| #include "synaptics.h" |
| #include "synaptics-properties.h" |
| |
| typedef enum { |
| TouchpadOn = 0, |
| TouchpadOff = 1, |
| TappingOff = 2 |
| } TouchpadState; |
| |
| |
| static Bool pad_disabled /* internal flag, this does not correspond to device state */; |
| static int ignore_modifier_combos; |
| static int ignore_modifier_keys; |
| static int background; |
| static const char *pid_file; |
| static Display *display; |
| static XDevice *dev; |
| static Atom touchpad_off_prop; |
| static TouchpadState previous_state; |
| static TouchpadState disable_state = TouchpadOff; |
| static int verbose; |
| |
| #define KEYMAP_SIZE 32 |
| static unsigned char keyboard_mask[KEYMAP_SIZE]; |
| |
| static void |
| usage(void) |
| { |
| fprintf(stderr, "Usage: syndaemon [-i idle-time] [-m poll-delay] [-d] [-t] [-k]\n"); |
| fprintf(stderr, " -i How many seconds to wait after the last key press before\n"); |
| fprintf(stderr, " enabling the touchpad. (default is 2.0s)\n"); |
| fprintf(stderr, " -m How many milli-seconds to wait until next poll.\n"); |
| fprintf(stderr, " (default is 200ms)\n"); |
| fprintf(stderr, " -d Start as a daemon, i.e. in the background.\n"); |
| fprintf(stderr, " -p Create a pid file with the specified name.\n"); |
| fprintf(stderr, " -t Only disable tapping and scrolling, not mouse movements.\n"); |
| fprintf(stderr, " -k Ignore modifier keys when monitoring keyboard activity.\n"); |
| fprintf(stderr, " -K Like -k but also ignore Modifier+Key combos.\n"); |
| fprintf(stderr, " -R Use the XRecord extension.\n"); |
| fprintf(stderr, " -v Print diagnostic messages.\n"); |
| exit(1); |
| } |
| |
| static void |
| store_current_touchpad_state(void) |
| { |
| Atom real_type; |
| int real_format; |
| unsigned long nitems, bytes_after; |
| unsigned char *data; |
| |
| if ((XGetDeviceProperty (display, dev, touchpad_off_prop, 0, 1, False, |
| XA_INTEGER, &real_type, &real_format, &nitems, |
| &bytes_after, &data) == Success) && (real_type != None)) { |
| previous_state = data[0]; |
| } |
| } |
| |
| /** |
| * Toggle touchpad enabled/disabled state, decided by value. |
| */ |
| static void |
| toggle_touchpad(Bool enable) |
| { |
| unsigned char data; |
| if (pad_disabled && enable) { |
| data = previous_state; |
| pad_disabled = False; |
| if (verbose) |
| printf("Enable\n"); |
| } else if (!pad_disabled && !enable && |
| previous_state != disable_state && |
| previous_state != TouchpadOff) { |
| store_current_touchpad_state(); |
| pad_disabled = True; |
| data = disable_state; |
| if (verbose) |
| printf("Disable\n"); |
| } else |
| return; |
| |
| /* This potentially overwrites a different client's setting, but ...*/ |
| XChangeDeviceProperty(display, dev, touchpad_off_prop, XA_INTEGER, 8, |
| PropModeReplace, &data, 1); |
| XFlush(display); |
| } |
| |
| static void |
| signal_handler(int signum) |
| { |
| toggle_touchpad(True); |
| |
| if (pid_file) |
| unlink(pid_file); |
| kill(getpid(), signum); |
| } |
| |
| static void |
| install_signal_handler(void) |
| { |
| static int signals[] = { |
| SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, |
| SIGBUS, SIGFPE, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, |
| SIGALRM, SIGTERM, |
| #ifdef SIGPWR |
| SIGPWR |
| #endif |
| }; |
| int i; |
| struct sigaction act; |
| sigset_t set; |
| |
| sigemptyset(&set); |
| act.sa_handler = signal_handler; |
| act.sa_mask = set; |
| #ifdef SA_ONESHOT |
| act.sa_flags = SA_ONESHOT; |
| #else |
| act.sa_flags = 0; |
| #endif |
| |
| for (i = 0; i < sizeof(signals) / sizeof(int); i++) { |
| if (sigaction(signals[i], &act, NULL) == -1) { |
| perror("sigaction"); |
| exit(2); |
| } |
| } |
| } |
| |
| /** |
| * Return non-zero if the keyboard state has changed since the last call. |
| */ |
| static int |
| keyboard_activity(Display *display) |
| { |
| static unsigned char old_key_state[KEYMAP_SIZE]; |
| unsigned char key_state[KEYMAP_SIZE]; |
| int i; |
| int ret = 0; |
| |
| XQueryKeymap(display, (char*)key_state); |
| |
| for (i = 0; i < KEYMAP_SIZE; i++) { |
| if ((key_state[i] & ~old_key_state[i]) & keyboard_mask[i]) { |
| ret = 1; |
| break; |
| } |
| } |
| if (ignore_modifier_combos) { |
| for (i = 0; i < KEYMAP_SIZE; i++) { |
| if (key_state[i] & ~keyboard_mask[i]) { |
| ret = 0; |
| break; |
| } |
| } |
| } |
| for (i = 0; i < KEYMAP_SIZE; i++) |
| old_key_state[i] = key_state[i]; |
| return ret; |
| } |
| |
| static double |
| get_time(void) |
| { |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| return tv.tv_sec + tv.tv_usec / 1000000.0; |
| } |
| |
| static void |
| main_loop(Display *display, double idle_time, int poll_delay) |
| { |
| double last_activity = 0.0; |
| double current_time; |
| |
| keyboard_activity(display); |
| |
| for (;;) { |
| current_time = get_time(); |
| if (keyboard_activity(display)) |
| last_activity = current_time; |
| |
| /* If system times goes backwards, touchpad can get locked. Make |
| * sure our last activity wasn't in the future and reset if it was. */ |
| if (last_activity > current_time) |
| last_activity = current_time - idle_time - 1; |
| |
| if (current_time > last_activity + idle_time) { /* Enable touchpad */ |
| toggle_touchpad(True); |
| } else { /* Disable touchpad */ |
| toggle_touchpad(False); |
| } |
| |
| usleep(poll_delay); |
| } |
| } |
| |
| static void |
| clear_bit(unsigned char *ptr, int bit) |
| { |
| int byte_num = bit / 8; |
| int bit_num = bit % 8; |
| ptr[byte_num] &= ~(1 << bit_num); |
| } |
| |
| static void |
| setup_keyboard_mask(Display *display, int ignore_modifier_keys) |
| { |
| XModifierKeymap *modifiers; |
| int i; |
| |
| for (i = 0; i < KEYMAP_SIZE; i++) |
| keyboard_mask[i] = 0xff; |
| |
| if (ignore_modifier_keys) { |
| modifiers = XGetModifierMapping(display); |
| for (i = 0; i < 8 * modifiers->max_keypermod; i++) { |
| KeyCode kc = modifiers->modifiermap[i]; |
| if (kc != 0) |
| clear_bit(keyboard_mask, kc); |
| } |
| XFreeModifiermap(modifiers); |
| } |
| } |
| |
| /* ---- the following code is for using the xrecord extension ----- */ |
| #ifdef HAVE_X11_EXTENSIONS_RECORD_H |
| |
| #define MAX_MODIFIERS 16 |
| |
| /* used for exchanging information with the callback function */ |
| struct xrecord_callback_results { |
| XModifierKeymap *modifiers; |
| Bool key_event; |
| Bool non_modifier_event; |
| KeyCode pressed_modifiers[MAX_MODIFIERS]; |
| }; |
| |
| /* test if the xrecord extension is found */ |
| Bool check_xrecord(Display *display) { |
| |
| Bool found; |
| Status status; |
| int major_opcode, minor_opcode, first_error; |
| int version[2]; |
| |
| found = XQueryExtension(display, |
| "RECORD", |
| &major_opcode, |
| &minor_opcode, |
| &first_error); |
| |
| status = XRecordQueryVersion(display, version, version+1); |
| if (verbose && status) { |
| printf("X RECORD extension version %d.%d\n", version[0], version[1]); |
| } |
| return found; |
| } |
| |
| /* called by XRecordProcessReplies() */ |
| void xrecord_callback( XPointer closure, XRecordInterceptData* recorded_data) { |
| |
| struct xrecord_callback_results *cbres; |
| xEvent *xev; |
| int nxev; |
| |
| cbres = (struct xrecord_callback_results *)closure; |
| |
| if (recorded_data->category != XRecordFromServer) { |
| XRecordFreeData(recorded_data); |
| return; |
| } |
| |
| nxev = recorded_data->data_len / 8; |
| xev = (xEvent *)recorded_data->data; |
| while(nxev--) { |
| |
| if ( (xev->u.u.type == KeyPress) || (xev->u.u.type == KeyRelease)) { |
| int i; |
| int is_modifier = 0; |
| |
| cbres->key_event = 1; /* remember, a key was pressed or released. */ |
| |
| /* test if it was a modifier */ |
| for (i = 0; i < 8 * cbres->modifiers->max_keypermod; i++) { |
| KeyCode kc = cbres->modifiers->modifiermap[i]; |
| |
| if (kc == xev->u.u.detail) { |
| is_modifier = 1; /* yes, it is a modifier. */ |
| break; |
| } |
| } |
| |
| if (is_modifier) { |
| if (xev->u.u.type == KeyPress) { |
| for (i=0; i < MAX_MODIFIERS; ++i) |
| if (!cbres->pressed_modifiers[i]) { |
| cbres->pressed_modifiers[i] = xev->u.u.detail; |
| break; |
| } |
| } else { /* KeyRelease */ |
| for (i=0; i < MAX_MODIFIERS; ++i) |
| if (cbres->pressed_modifiers[i] == xev->u.u.detail) |
| cbres->pressed_modifiers[i] = 0; |
| } |
| |
| } else { |
| /* remember, a non-modifier was pressed. */ |
| cbres->non_modifier_event = 1; |
| } |
| } |
| |
| xev++; |
| } |
| |
| XRecordFreeData(recorded_data); /* cleanup */ |
| } |
| |
| static int is_modifier_pressed(const struct xrecord_callback_results *cbres) { |
| int i; |
| |
| for (i = 0; i < MAX_MODIFIERS; ++i) |
| if (cbres->pressed_modifiers[i]) |
| return 1; |
| |
| return 0; |
| } |
| |
| void record_main_loop(Display* display, double idle_time) { |
| |
| struct xrecord_callback_results cbres; |
| XRecordContext context; |
| XRecordClientSpec cspec = XRecordAllClients; |
| Display *dpy_data; |
| XRecordRange *range; |
| int i; |
| |
| dpy_data = XOpenDisplay(NULL); /* we need an additional data connection. */ |
| range = XRecordAllocRange(); |
| |
| range->device_events.first = KeyPress; |
| range->device_events.last = KeyRelease; |
| |
| context = XRecordCreateContext(dpy_data, 0, |
| &cspec,1, |
| &range, 1); |
| |
| XRecordEnableContextAsync(dpy_data, context, xrecord_callback, (XPointer)&cbres); |
| |
| cbres.modifiers = XGetModifierMapping(display); |
| /* clear list of modifiers */ |
| for (i = 0; i < MAX_MODIFIERS; ++i) |
| cbres.pressed_modifiers[i] = 0; |
| |
| while (1) { |
| |
| int fd = ConnectionNumber(dpy_data); |
| fd_set read_fds; |
| int ret; |
| int disable_event = 0; |
| struct timeval timeout; |
| |
| FD_ZERO(&read_fds); |
| FD_SET(fd, &read_fds); |
| |
| ret = select(fd+1 /* =(max descriptor in read_fds) + 1 */, |
| &read_fds, NULL, NULL, |
| pad_disabled ? &timeout : NULL /* timeout only required for enabling */ ); |
| |
| if (FD_ISSET(fd, &read_fds)) { |
| |
| cbres.key_event = 0; |
| cbres.non_modifier_event = 0; |
| |
| XRecordProcessReplies(dpy_data); |
| |
| /* If there are any events left over, they are in error. Drain them |
| * from the connection queue so we don't get stuck. */ |
| while (XEventsQueued(dpy_data, QueuedAlready) > 0) { |
| XEvent event; |
| XNextEvent(dpy_data, &event); |
| fprintf(stderr, "bad event received, major opcode %d\n", event.type); |
| } |
| |
| if (!ignore_modifier_keys && cbres.key_event) { |
| disable_event = 1; |
| } |
| |
| if (cbres.non_modifier_event && |
| !(ignore_modifier_combos && is_modifier_pressed(&cbres)) ) { |
| disable_event = 1; |
| } |
| } |
| |
| if (disable_event) { |
| /* adjust the enable_time */ |
| timeout.tv_sec = (int)idle_time; |
| timeout.tv_usec = (idle_time-(double)timeout.tv_sec) * 1.e6; |
| |
| toggle_touchpad(False); |
| } |
| |
| if (ret == 0 && pad_disabled) { /* timeout => enable event */ |
| toggle_touchpad(True); |
| if (verbose) printf("enable touchpad\n"); |
| } |
| |
| } /* end while(1) */ |
| |
| XFreeModifiermap(cbres.modifiers); |
| } |
| #endif /* HAVE_X11_EXTENSIONS_RECORD_H */ |
| |
| static XDevice * |
| dp_get_device(Display *dpy) |
| { |
| XDevice* dev = NULL; |
| XDeviceInfo *info = NULL; |
| int ndevices = 0; |
| Atom touchpad_type = 0; |
| Atom *properties = NULL; |
| int nprops = 0; |
| int error = 0; |
| |
| touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True); |
| touchpad_off_prop = XInternAtom(dpy, SYNAPTICS_PROP_OFF, True); |
| info = XListInputDevices(dpy, &ndevices); |
| |
| while(ndevices--) { |
| if (info[ndevices].type == touchpad_type) { |
| dev = XOpenDevice(dpy, info[ndevices].id); |
| if (!dev) { |
| fprintf(stderr, "Failed to open device '%s'.\n", |
| info[ndevices].name); |
| error = 1; |
| goto unwind; |
| } |
| |
| properties = XListDeviceProperties(dpy, dev, &nprops); |
| if (!properties || !nprops) |
| { |
| fprintf(stderr, "No properties on device '%s'.\n", |
| info[ndevices].name); |
| error = 1; |
| goto unwind; |
| } |
| |
| while(nprops--) |
| { |
| if (properties[nprops] == touchpad_off_prop) |
| break; |
| } |
| if (nprops < 0) |
| { |
| fprintf(stderr, "No synaptics properties on device '%s'.\n", |
| info[ndevices].name); |
| error = 1; |
| goto unwind; |
| } |
| |
| break; /* Yay, device is suitable */ |
| } |
| } |
| |
| unwind: |
| XFree(properties); |
| XFreeDeviceList(info); |
| if (!dev) |
| fprintf(stderr, "Unable to find a synaptics device.\n"); |
| else if (error && dev) |
| { |
| XCloseDevice(dpy, dev); |
| dev = NULL; |
| } |
| return dev; |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| double idle_time = 2.0; |
| int poll_delay = 200000; /* 200 ms */ |
| int c; |
| int use_xrecord = 0; |
| |
| /* Parse command line parameters */ |
| while ((c = getopt(argc, argv, "i:m:dtp:kKR?v")) != EOF) { |
| switch(c) { |
| case 'i': |
| idle_time = atof(optarg); |
| break; |
| case 'm': |
| poll_delay = atoi(optarg) * 1000; |
| break; |
| case 'd': |
| background = 1; |
| break; |
| case 't': |
| disable_state = TappingOff; |
| break; |
| case 'p': |
| pid_file = optarg; |
| break; |
| case 'k': |
| ignore_modifier_keys = 1; |
| break; |
| case 'K': |
| ignore_modifier_combos = 1; |
| ignore_modifier_keys = 1; |
| break; |
| case 'R': |
| use_xrecord = 1; |
| break; |
| case 'v': |
| verbose = 1; |
| break; |
| default: |
| usage(); |
| break; |
| } |
| } |
| if (idle_time <= 0.0) |
| usage(); |
| |
| /* Open a connection to the X server */ |
| display = XOpenDisplay(NULL); |
| if (!display) { |
| fprintf(stderr, "Can't open display.\n"); |
| exit(2); |
| } |
| |
| if (!(dev = dp_get_device(display))) |
| exit(2); |
| |
| /* Install a signal handler to restore synaptics parameters on exit */ |
| install_signal_handler(); |
| |
| if (background) { |
| pid_t pid; |
| if ((pid = fork()) < 0) { |
| perror("fork"); |
| exit(3); |
| } else if (pid != 0) |
| exit(0); |
| |
| /* Child (daemon) is running here */ |
| setsid(); /* Become session leader */ |
| chdir("/"); /* In case the file system gets unmounted */ |
| umask(0); /* We don't want any surprises */ |
| if (pid_file) { |
| FILE *fd = fopen(pid_file, "w"); |
| if (!fd) { |
| perror("Can't create pid file"); |
| exit(2); |
| } |
| fprintf(fd, "%d\n", getpid()); |
| fclose(fd); |
| } |
| } |
| |
| pad_disabled = False; |
| store_current_touchpad_state(); |
| |
| #ifdef HAVE_X11_EXTENSIONS_RECORD_H |
| if (use_xrecord) |
| { |
| if(check_xrecord(display)) |
| record_main_loop(display, idle_time); |
| else { |
| fprintf(stderr, "Use of XRecord requested, but failed to " |
| " initialize.\n"); |
| exit(2); |
| } |
| } else |
| #endif /* HAVE_X11_EXTENSIONS_RECORD_H */ |
| { |
| setup_keyboard_mask(display, ignore_modifier_keys); |
| |
| /* Run the main loop */ |
| main_loop(display, idle_time, poll_delay); |
| } |
| return 0; |
| } |
| |
| /* vim: set noexpandtab tabstop=8 shiftwidth=4: */ |