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