blob: a8a0499c80608015f2ba220e88be981102c01acb [file] [log] [blame]
/*
* 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: */