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