1/*
2 * Copyright © 2002-2005,2007 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 <stdio.h>
32#include <stdlib.h>
33#include <sys/types.h>
34#include <sys/ipc.h>
35#include <sys/time.h>
36#include <unistd.h>
37#include <string.h>
38#include <stddef.h>
39#include <math.h>
40#include <limits.h>
41
42#include <X11/Xdefs.h>
43#include <X11/Xatom.h>
44#include <X11/extensions/XI.h>
45#include <X11/extensions/XInput.h>
46#include "synaptics-properties.h"
47
48#ifndef XATOM_FLOAT
49#define XATOM_FLOAT "FLOAT"
50#endif
51
52#define SYN_MAX_BUTTONS 12
53
54union flong {                   /* Xlibs 64-bit property handling madness */
55    long l;
56    float f;
57};
58
59enum ParaType {
60    PT_INT,
61    PT_BOOL,
62    PT_DOUBLE
63};
64
65struct Parameter {
66    char *name;                 /* Name of parameter */
67    enum ParaType type;         /* Type of parameter */
68    double min_val;             /* Minimum allowed value */
69    double max_val;             /* Maximum allowed value */
70    char *prop_name;            /* Property name */
71    int prop_format;            /* Property format (0 for floats) */
72    int prop_offset;            /* Offset inside property */
73};
74
75static struct Parameter params[] = {
76    {"LeftEdge",              PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	0},
77    {"RightEdge",             PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	1},
78    {"TopEdge",               PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	2},
79    {"BottomEdge",            PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	3},
80    {"FingerLow",             PT_INT,    0, 255,   SYNAPTICS_PROP_FINGER,	32,	0},
81    {"FingerHigh",            PT_INT,    0, 255,   SYNAPTICS_PROP_FINGER,	32,	1},
82    {"MaxTapTime",            PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_TIME,	32,	0},
83    {"MaxTapMove",            PT_INT,    0, 2000,  SYNAPTICS_PROP_TAP_MOVE,	32,	0},
84    {"MaxDoubleTapTime",      PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	1},
85    {"SingleTapTimeout",      PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	0},
86    {"ClickTime",             PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	2},
87    {"FastTaps",              PT_BOOL,   0, 1,     SYNAPTICS_PROP_TAP_FAST,	8,	0},
88    {"EmulateMidButtonTime",  PT_INT,    0, 1000,  SYNAPTICS_PROP_MIDDLE_TIMEOUT,32,	0},
89    {"EmulateTwoFingerMinZ",  PT_INT,    0, 1000,  SYNAPTICS_PROP_TWOFINGER_PRESSURE,	32,	0},
90    {"EmulateTwoFingerMinW",  PT_INT,    0, 15,    SYNAPTICS_PROP_TWOFINGER_WIDTH,	32,	0},
91    {"VertScrollDelta",       PT_INT,    -1000, 1000,  SYNAPTICS_PROP_SCROLL_DISTANCE,	32,	0},
92    {"HorizScrollDelta",      PT_INT,    -1000, 1000,  SYNAPTICS_PROP_SCROLL_DISTANCE,	32,	1},
93    {"VertEdgeScroll",        PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	0},
94    {"HorizEdgeScroll",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	1},
95    {"CornerCoasting",        PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	2},
96    {"VertTwoFingerScroll",   PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_TWOFINGER,	8,	0},
97    {"HorizTwoFingerScroll",  PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_TWOFINGER,	8,	1},
98    {"MinSpeed",              PT_DOUBLE, 0, 255.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	0},
99    {"MaxSpeed",              PT_DOUBLE, 0, 255.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	1},
100    {"AccelFactor",           PT_DOUBLE, 0, 1.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	2},
101    {"TouchpadOff",           PT_INT,    0, 2,     SYNAPTICS_PROP_OFF,		8,	0},
102    {"LockedDrags",           PT_BOOL,   0, 1,     SYNAPTICS_PROP_LOCKED_DRAGS,	8,	0},
103    {"LockedDragTimeout",     PT_INT,    0, 30000, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT,	32,	0},
104    {"RTCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	0},
105    {"RBCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	1},
106    {"LTCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	2},
107    {"LBCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	3},
108    {"TapButton1",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	4},
109    {"TapButton2",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	5},
110    {"TapButton3",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	6},
111    {"ClickFinger1",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	0},
112    {"ClickFinger2",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	1},
113    {"ClickFinger3",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	2},
114    {"CircularScrolling",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_CIRCULAR_SCROLLING,	8,	0},
115    {"CircScrollDelta",       PT_DOUBLE, .01, 3,   SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST,	0 /* float */,	0},
116    {"CircScrollTrigger",     PT_INT,    0, 8,     SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER,	8,	0},
117    {"PalmDetect",            PT_BOOL,   0, 1,     SYNAPTICS_PROP_PALM_DETECT,	8,	0},
118    {"PalmMinWidth",          PT_INT,    0, 15,    SYNAPTICS_PROP_PALM_DIMENSIONS,	32,	0},
119    {"PalmMinZ",              PT_INT,    0, 255,   SYNAPTICS_PROP_PALM_DIMENSIONS,	32,	1},
120    {"CoastingSpeed",         PT_DOUBLE, 0, 255,    SYNAPTICS_PROP_COASTING_SPEED,	0 /* float*/,	0},
121    {"CoastingFriction",      PT_DOUBLE, 0, 255,   SYNAPTICS_PROP_COASTING_SPEED,	0 /* float*/,	1},
122    {"PressureMotionMinZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	0},
123    {"PressureMotionMaxZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	1},
124    {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	0},
125    {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	1},
126    {"GrabEventDevice",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_GRAB,	8,	0},
127    {"TapAndDragGesture",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_GESTURES,	8,	0},
128    {"AreaLeftEdge",          PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	0},
129    {"AreaRightEdge",         PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	1},
130    {"AreaTopEdge",           PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	2},
131    {"AreaBottomEdge",        PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	3},
132    {"HorizHysteresis",       PT_INT,    0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32,	0},
133    {"VertHysteresis",        PT_INT,    0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32,	1},
134    {"ClickPad",              PT_BOOL,   0, 1,     SYNAPTICS_PROP_CLICKPAD,	8,	0},
135    {"RightButtonAreaLeft",   PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	0},
136    {"RightButtonAreaRight",  PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	1},
137    {"RightButtonAreaTop",    PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	2},
138    {"RightButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	3},
139    {"MiddleButtonAreaLeft",  PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	4},
140    {"MiddleButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	5},
141    {"MiddleButtonAreaTop",   PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	6},
142    {"MiddleButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	7},
143    { NULL, 0, 0, 0, 0 }
144};
145
146static double
147parse_cmd(char *cmd, struct Parameter **par)
148{
149    char *eqp = strchr(cmd, '=');
150
151    *par = NULL;
152
153    if (eqp) {
154        int j;
155        int found = 0;
156
157        *eqp = 0;
158        for (j = 0; params[j].name; j++) {
159            if (strcasecmp(cmd, params[j].name) == 0) {
160                found = 1;
161                break;
162            }
163        }
164        if (found) {
165            double val = atof(&eqp[1]);
166
167            *par = &params[j];
168
169            if (val < (*par)->min_val)
170                val = (*par)->min_val;
171            if (val > (*par)->max_val)
172                val = (*par)->max_val;
173
174            return val;
175        }
176        else {
177            printf("Unknown parameter %s\n", cmd);
178        }
179    }
180    else {
181        printf("Invalid command: %s\n", cmd);
182    }
183
184    return 0;
185}
186
187/** Init display connection or NULL on error */
188static Display *
189dp_init()
190{
191    Display *dpy = NULL;
192    XExtensionVersion *v = NULL;
193    Atom touchpad_type = 0;
194    Atom synaptics_property = 0;
195    int error = 0;
196
197    dpy = XOpenDisplay(NULL);
198    if (!dpy) {
199        fprintf(stderr, "Failed to connect to X Server.\n");
200        error = 1;
201        goto unwind;
202    }
203
204    v = XGetExtensionVersion(dpy, INAME);
205    if (!v->present ||
206        (v->major_version * 1000 + v->minor_version) <
207        (XI_Add_DeviceProperties_Major * 1000 +
208         XI_Add_DeviceProperties_Minor)) {
209        fprintf(stderr, "X server supports X Input %d.%d. I need %d.%d.\n",
210                v->major_version, v->minor_version,
211                XI_Add_DeviceProperties_Major, XI_Add_DeviceProperties_Minor);
212        error = 1;
213        goto unwind;
214    }
215
216    /* We know synaptics sets XI_TOUCHPAD for all the devices. */
217    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
218    if (!touchpad_type) {
219        fprintf(stderr, "XI_TOUCHPAD not initialised.\n");
220        error = 1;
221        goto unwind;
222    }
223
224    synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True);
225    if (!synaptics_property) {
226        fprintf(stderr, "Couldn't find synaptics properties. No synaptics "
227                "driver loaded?\n");
228        error = 1;
229        goto unwind;
230    }
231
232 unwind:
233    XFree(v);
234    if (error && dpy) {
235        XCloseDisplay(dpy);
236        dpy = NULL;
237    }
238    return dpy;
239}
240
241static XDevice *
242dp_get_device(Display * dpy)
243{
244    XDevice *dev = NULL;
245    XDeviceInfo *info = NULL;
246    int ndevices = 0;
247    Atom touchpad_type = 0;
248    Atom synaptics_property = 0;
249    Atom *properties = NULL;
250    int nprops = 0;
251    int error = 0;
252
253    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
254    synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True);
255    info = XListInputDevices(dpy, &ndevices);
256
257    while (ndevices--) {
258        if (info[ndevices].type == touchpad_type) {
259            dev = XOpenDevice(dpy, info[ndevices].id);
260            if (!dev) {
261                fprintf(stderr, "Failed to open device '%s'.\n",
262                        info[ndevices].name);
263                error = 1;
264                goto unwind;
265            }
266
267            properties = XListDeviceProperties(dpy, dev, &nprops);
268            if (!properties || !nprops) {
269                fprintf(stderr, "No properties on device '%s'.\n",
270                        info[ndevices].name);
271                error = 1;
272                goto unwind;
273            }
274
275            while (nprops--) {
276                if (properties[nprops] == synaptics_property)
277                    break;
278            }
279            if (!nprops) {
280                fprintf(stderr, "No synaptics properties on device '%s'.\n",
281                        info[ndevices].name);
282                error = 1;
283                goto unwind;
284            }
285
286            break;              /* Yay, device is suitable */
287        }
288    }
289
290 unwind:
291    XFree(properties);
292    XFreeDeviceList(info);
293    if (!dev)
294        fprintf(stderr, "Unable to find a synaptics device.\n");
295    else if (error && dev) {
296        XCloseDevice(dpy, dev);
297        dev = NULL;
298    }
299    return dev;
300}
301
302static void
303dp_set_variables(Display * dpy, XDevice * dev, int argc, char *argv[],
304                 int first_cmd)
305{
306    int i;
307    double val;
308    struct Parameter *par;
309    Atom prop, type, float_type;
310    int format;
311    unsigned char *data;
312    unsigned long nitems, bytes_after;
313
314    union flong *f;
315    long *n;
316    char *b;
317
318    float_type = XInternAtom(dpy, XATOM_FLOAT, True);
319    if (!float_type)
320        fprintf(stderr, "Float properties not available.\n");
321
322    for (i = first_cmd; i < argc; i++) {
323        val = parse_cmd(argv[i], &par);
324        if (!par)
325            continue;
326
327        prop = XInternAtom(dpy, par->prop_name, True);
328        if (!prop) {
329            fprintf(stderr, "Property for '%s' not available. Skipping.\n",
330                    par->name);
331            continue;
332
333        }
334
335        XGetDeviceProperty(dpy, dev, prop, 0, 1000, False, AnyPropertyType,
336                           &type, &format, &nitems, &bytes_after, &data);
337
338        if (type == None) {
339            fprintf(stderr, "Property for '%s' not available. Skipping.\n",
340                    par->name);
341            continue;
342        }
343
344        switch (par->prop_format) {
345        case 8:
346            if (format != par->prop_format || type != XA_INTEGER) {
347                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
348                        par->name, format);
349                break;
350            }
351            b = (char *) data;
352            b[par->prop_offset] = rint(val);
353            break;
354        case 32:
355            if (format != par->prop_format ||
356                (type != XA_INTEGER && type != XA_CARDINAL)) {
357                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
358                        par->name, format);
359                break;
360            }
361            n = (long *) data;
362            n[par->prop_offset] = rint(val);
363            break;
364        case 0:                /* float */
365            if (!float_type)
366                continue;
367            if (format != 32 || type != float_type) {
368                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
369                        par->name, format);
370                break;
371            }
372            f = (union flong *) data;
373            f[par->prop_offset].f = val;
374            break;
375        }
376
377        XChangeDeviceProperty(dpy, dev, prop, type, format,
378                              PropModeReplace, data, nitems);
379        XFlush(dpy);
380    }
381}
382
383/* FIXME: horribly inefficient. */
384static void
385dp_show_settings(Display * dpy, XDevice * dev)
386{
387    int j;
388    Atom a, type, float_type;
389    int format;
390    unsigned long nitems, bytes_after;
391    unsigned char *data;
392    int len;
393
394    union flong *f;
395    long *i;
396    char *b;
397
398    float_type = XInternAtom(dpy, XATOM_FLOAT, True);
399    if (!float_type)
400        fprintf(stderr, "Float properties not available.\n");
401
402    printf("Parameter settings:\n");
403    for (j = 0; params[j].name; j++) {
404        struct Parameter *par = &params[j];
405
406        a = XInternAtom(dpy, par->prop_name, True);
407        if (!a)
408            continue;
409
410        len =
411            1 +
412            ((par->prop_offset * (par->prop_format ? par->prop_format : 32) /
413              8)) / 4;
414
415        XGetDeviceProperty(dpy, dev, a, 0, len, False,
416                           AnyPropertyType, &type, &format,
417                           &nitems, &bytes_after, &data);
418        if (type == None)
419            continue;
420
421        switch (par->prop_format) {
422        case 8:
423            if (format != par->prop_format || type != XA_INTEGER) {
424                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
425                        par->name, format);
426                break;
427            }
428
429            b = (char *) data;
430            printf("    %-23s = %d\n", par->name, b[par->prop_offset]);
431            break;
432        case 32:
433            if (format != par->prop_format ||
434                (type != XA_INTEGER && type != XA_CARDINAL)) {
435                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
436                        par->name, format);
437                break;
438            }
439
440            i = (long *) data;
441            printf("    %-23s = %ld\n", par->name, i[par->prop_offset]);
442            break;
443        case 0:                /* Float */
444            if (!float_type)
445                continue;
446            if (format != 32 || type != float_type) {
447                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
448                        par->name, format);
449                break;
450            }
451
452            f = (union flong *) data;
453            printf("    %-23s = %g\n", par->name, f[par->prop_offset].f);
454            break;
455        }
456
457        XFree(data);
458    }
459}
460
461static void
462usage(void)
463{
464    fprintf(stderr, "Usage: synclient [-h] [-l] [-V] [-?] [var1=value1 [var2=value2] ...]\n");
465    fprintf(stderr, "  -l List current user settings\n");
466    fprintf(stderr, "  -V Print synclient version string and exit\n");
467    fprintf(stderr, "  -? Show this help message\n");
468    fprintf(stderr, "  var=value  Set user parameter 'var' to 'value'.\n");
469    exit(1);
470}
471
472int
473main(int argc, char *argv[])
474{
475    int c;
476    int dump_settings = 0;
477    int first_cmd;
478
479    Display *dpy;
480    XDevice *dev;
481
482    if (argc == 1)
483        dump_settings = 1;
484
485    /* Parse command line parameters */
486    while ((c = getopt(argc, argv, "lV?")) != -1) {
487        switch (c) {
488        case 'l':
489            dump_settings = 1;
490            break;
491        case 'V':
492            printf("%s\n", VERSION);
493            exit(0);
494        case '?':
495        default:
496            usage();
497        }
498    }
499
500    first_cmd = optind;
501    if (!dump_settings && first_cmd == argc)
502        usage();
503
504    dpy = dp_init();
505    if (!dpy || !(dev = dp_get_device(dpy)))
506        return 1;
507
508    dp_set_variables(dpy, dev, argc, argv, first_cmd);
509    if (dump_settings)
510        dp_show_settings(dpy, dev);
511
512    XCloseDevice(dpy, dev);
513    XCloseDisplay(dpy);
514
515    return 0;
516}
517