2219203c38
The latter is an obsolete non-standard, Linux only synonym for the former. This caused syndaemon(1) to enter an an infinite loop whenever it receives a SIGINT signal. Patch from Luca Castagnini. Thanks.
677 lines
18 KiB
C
677 lines
18 KiB
C
/*
|
|
* 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-properties.h"
|
|
|
|
enum KeyboardActivity {
|
|
ActivityNew,
|
|
ActivityNone,
|
|
ActivityReset
|
|
};
|
|
|
|
enum TouchpadState {
|
|
TouchpadOn = 0,
|
|
TouchpadOff = 1,
|
|
TappingOff = 2
|
|
};
|
|
|
|
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 enum TouchpadState previous_state;
|
|
static enum 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");
|
|
fprintf(stderr, " -? Show this help message.\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_RESETHAND
|
|
act.sa_flags = SA_RESETHAND;
|
|
#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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static enum KeyboardActivity
|
|
keyboard_activity(Display * display)
|
|
{
|
|
static unsigned char old_key_state[KEYMAP_SIZE];
|
|
unsigned char key_state[KEYMAP_SIZE];
|
|
int i;
|
|
int ret = ActivityNone;
|
|
|
|
XQueryKeymap(display, (char *) key_state);
|
|
|
|
for (i = 0; i < KEYMAP_SIZE; i++) {
|
|
if ((key_state[i] & ~old_key_state[i]) & keyboard_mask[i]) {
|
|
ret = ActivityNew;
|
|
break;
|
|
}
|
|
}
|
|
if (ignore_modifier_combos) {
|
|
for (i = 0; i < KEYMAP_SIZE; i++) {
|
|
if (key_state[i] & ~keyboard_mask[i]) {
|
|
if (old_key_state[i] & ~keyboard_mask[i])
|
|
ret = ActivityNone;
|
|
else
|
|
ret = ActivityReset;
|
|
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();
|
|
switch (keyboard_activity(display)) {
|
|
case ActivityNew:
|
|
last_activity = current_time;
|
|
break;
|
|
case ActivityNone:
|
|
/* NOP */;
|
|
break;
|
|
case ActivityReset:
|
|
last_activity = 0.0;
|
|
break;
|
|
}
|
|
|
|
/* 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;
|
|
int modifier_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) {
|
|
if (ignore_modifier_combos && is_modifier_pressed(&cbres)) {
|
|
modifier_event = 1;
|
|
} else {
|
|
disable_event = 1;
|
|
}
|
|
} else if (ignore_modifier_keys) {
|
|
modifier_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 (modifier_event && pad_disabled) {
|
|
toggle_touchpad(True);
|
|
}
|
|
|
|
if (ret == 0 && pad_disabled) { /* timeout => enable event */
|
|
toggle_touchpad(True);
|
|
}
|
|
} /* 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;
|
|
case '?':
|
|
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(3);
|
|
}
|
|
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(4);
|
|
}
|
|
}
|
|
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: */
|