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