xkbevd.c revision 9ff100ac
1/************************************************************
2 Copyright (c) 1995 by Silicon Graphics Computer Systems, Inc.
3
4 Permission to use, copy, modify, and distribute this
5 software and its documentation for any purpose and without
6 fee is hereby granted, provided that the above copyright
7 notice appear in all copies and that both that copyright
8 notice and this permission notice appear in supporting
9 documentation, and that the name of Silicon Graphics not be
10 used in advertising or publicity pertaining to distribution
11 of the software without specific prior written permission.
12 Silicon Graphics makes no representation about the suitability
13 of this software for any purpose. It is provided "as is"
14 without any express or implied warranty.
15
16 SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
17 SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18 AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
19 GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
20 DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21 DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
22 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
23 THE USE OR PERFORMANCE OF THIS SOFTWARE.
24
25 ********************************************************/
26
27#include <X11/Xosdefs.h>
28#include <stdlib.h>
29#include "xkbevd.h"
30
31/***====================================================================***/
32
33#ifndef DFLT_XKBEVD_CONFIG
34#define DFLT_XKBEVD_CONFIG "%s/.xkb/xkbevd.cf"
35#endif /* DFLT_XKBEVD_CONFIG */
36
37#ifndef DFLT_XKB_CONFIG_ROOT
38#define	DFLT_XKB_CONFIG_ROOT "/usr/X11R6/lib/xkb"
39#endif
40
41#ifndef DFLT_SYS_XKBEVD_CONFIG
42#define DFLT_SYS_XKBEVD_CONFIG "%s/xkbevd.cf"
43#endif /* DFLT_SYS_XKBEVD_CONFIG */
44
45#ifndef DFLT_SOUND_CMD
46#define	DFLT_SOUND_CMD "/usr/sbin/sfplay -q"
47#endif /* DFLT_SOUND_CMD */
48
49#ifndef DFLT_SOUND_DIR
50#define	DFLT_SOUND_DIR "/usr/share/data/sounds/prosonus/"
51#endif /* DFLT_SOUND_DIR */
52
53/***====================================================================***/
54
55static char *   dpyName = NULL;
56Display *       dpy = NULL;
57static const char *cfgFileName = NULL;
58int             xkbOpcode = 0;
59int             xkbEventCode = 0;
60Bool            detectableRepeat = False;
61
62static CfgEntryPtr config = NULL;
63static unsigned long eventMask = 0;
64
65static Bool     synch = False;
66static int      verbose = 0;
67static Bool     background = False;
68
69static const char *soundCmd = NULL;
70static const char *soundDir = NULL;
71
72XkbDescPtr      xkb = NULL;
73
74/***====================================================================***/
75
76static void
77Usage(int argc, char *argv[])
78{
79    fprintf(stderr, "Usage: %s [options]...\n%s", argv[0],
80            "Legal options:\n"
81            "-?, -help            Print this message\n"
82            "-cfg <file>          Specify a config file\n"
83            "-sc <cmd>            Specify the command to play sounds\n"
84            "-sd <dir>            Specify the root directory for sound files\n"
85            "-d[isplay] <dpy>     Specify the display to watch\n"
86            "-bg                  Run in background\n"
87            "-synch               Force synchronization\n"
88            "-v                   Print verbose messages\n"
89            "-version             Print program version\n"
90        );
91    return;
92}
93
94/***====================================================================***/
95
96static Bool
97parseArgs(int argc, char *argv[])
98{
99    register int i;
100
101    for (i = 1; i < argc; i++) {
102        if (strcmp(argv[i], "-bg") == 0) {
103            background = True;
104        }
105        else if (strcmp(argv[i], "-cfg") == 0) {
106            if (i >= (argc - 1)) {
107                uError("No configuration file specified on command line\n");
108                uAction("Trailing %s argument ignored\n", argv[i]);
109            }
110            else {
111                char *name = argv[++i];
112
113                if (cfgFileName != NULL) {
114                    if (uStringEqual(cfgFileName, name))
115                        uWarning("Config file \"%s\" specified twice!\n", name);
116                    else {
117                        uWarning("Multiple config files on command line\n");
118                        uAction("Using \"%s\", ignoring \"%s\"\n", name,
119                                cfgFileName);
120                    }
121                }
122                cfgFileName = name;
123            }
124        }
125        else if ((strcmp(argv[i], "-d") == 0) ||
126                 (strcmp(argv[i], "-display") == 0)) {
127            if (i >= (argc - 1)) {
128                uError("No display specified on command line\n");
129                uAction("Trailing %s argument ignored\n", argv[i]);
130            }
131            else {
132                char *name = argv[++i];
133
134                if (dpyName != NULL) {
135                    if (uStringEqual(dpyName, name))
136                        uWarning("Display \"%s\" specified twice!\n", name);
137                    else {
138                        uWarning("Multiple displays on command line\n");
139                        uAction("Using \"%s\", ignoring \"%s\"\n", name,
140                                dpyName);
141                    }
142                }
143                dpyName = name;
144            }
145        }
146        else if (strcmp(argv[i], "-sc") == 0) {
147            if (i >= (argc - 1)) {
148                uError("No sound command specified on command line\n");
149                uAction("Trailing %s argument ignored\n", argv[i]);
150            }
151            else {
152                char *name = argv[++i];
153
154                if (soundCmd != NULL) {
155                    if (uStringEqual(soundCmd, name))
156                        uWarning("Sound command \"%s\" specified twice!\n",
157                                 name);
158                    else {
159                        uWarning("Multiple sound commands on command line\n");
160                        uAction("Using \"%s\", ignoring \"%s\"\n", name,
161                                soundCmd);
162                    }
163                }
164                soundCmd = name;
165            }
166        }
167        else if (strcmp(argv[i], "-sd") == 0) {
168            if (i >= (argc - 1)) {
169                uError("No sound directory specified on command line\n");
170                uAction("Trailing %s argument ignored\n", argv[i]);
171            }
172            else {
173                char *name = argv[++i];
174
175                if (soundDir != NULL) {
176                    if (uStringEqual(soundDir, name))
177                        uWarning("Sound directory \"%s\" specified twice!\n",
178                                 name);
179                    else {
180                        uWarning("Multiple sound dirs on command line\n");
181                        uAction("Using \"%s\", ignoring \"%s\"\n", name,
182                                soundDir);
183                    }
184                }
185                soundDir = name;
186            }
187        }
188        else if ((strcmp(argv[i], "-synch") == 0) ||
189                 (strcmp(argv[i], "-s") == 0)) {
190            synch = True;
191        }
192        else if (strcmp(argv[i], "-v") == 0) {
193            verbose++;
194        }
195        else if (strcmp(argv[i], "-version") == 0) {
196            puts(PACKAGE_STRING);
197            exit(0);
198        }
199        else if ((strcmp(argv[i], "-?") == 0) ||
200                 (strcmp(argv[i], "-help") == 0)) {
201            Usage(argc, argv);
202            exit(0);
203        }
204        else {
205            uError("Unknown flag \"%s\" on command line\n", argv[i]);
206            Usage(argc, argv);
207            return False;
208        }
209    }
210    if (background == False) {
211        eventMask = XkbAllEventsMask;
212        verbose++;
213    }
214
215    return True;
216}
217
218static Display *
219GetDisplay(char *program, char *dpyName, int *opcodeRtrn, int *evBaseRtrn)
220{
221    int mjr, mnr, error;
222    Display *dpy;
223
224    mjr = XkbMajorVersion;
225    mnr = XkbMinorVersion;
226    dpy = XkbOpenDisplay(dpyName, evBaseRtrn, NULL, &mjr, &mnr, &error);
227    if (dpy == NULL) {
228        switch (error) {
229        case XkbOD_BadLibraryVersion:
230            uInformation("%s was compiled with XKB version %d.%02d\n",
231                         program, XkbMajorVersion, XkbMinorVersion);
232            uError("X library supports incompatible version %d.%02d\n",
233                   mjr, mnr);
234            break;
235        case XkbOD_ConnectionRefused:
236            uError("Cannot open display \"%s\"\n", dpyName);
237            break;
238        case XkbOD_NonXkbServer:
239            uError("XKB extension not present on %s\n", dpyName);
240            break;
241        case XkbOD_BadServerVersion:
242            uInformation("%s was compiled with XKB version %d.%02d\n",
243                         program, XkbMajorVersion, XkbMinorVersion);
244            uError("Server %s uses incompatible version %d.%02d\n",
245                   dpyName, mjr, mnr);
246            break;
247        default:
248            uInternalError("Unknown error %d from XkbOpenDisplay\n", error);
249        }
250    }
251    else if (synch)
252        XSynchronize(dpy, True);
253    if (opcodeRtrn)
254        XkbQueryExtension(dpy, opcodeRtrn, evBaseRtrn, NULL, &mjr, &mnr);
255    return dpy;
256}
257
258/***====================================================================***/
259
260void
261InterpretConfigs(CfgEntryPtr cfg)
262{
263    char *name;
264    unsigned priv = 0;
265
266    config = cfg;
267    while (cfg != NULL) {
268        name = cfg->name.str;
269        if (cfg->entry_type == VariableDef) {
270            if (uStrCaseEqual(name, "sounddirectory") ||
271                uStrCaseEqual(name, "sounddir")) {
272                if (soundDir == NULL) {
273                    soundDir = cfg->action.text;
274                    cfg->name.str = NULL;
275                    cfg->action.text = NULL;
276                }
277            }
278            else if (uStrCaseEqual(name, "soundcommand") ||
279                     uStrCaseEqual(name, "soundcmd")) {
280                if (soundCmd == NULL) {
281                    soundCmd = cfg->action.text;
282                    cfg->name.str = NULL;
283                    cfg->action.text = NULL;
284                }
285            }
286            else {
287                uWarning("Assignment to unknown variable \"%s\"\n", name);
288                uAction("Ignored\n");
289            }
290        }
291        else if (cfg->entry_type == EventDef)
292            switch (cfg->event_type) {
293            case XkbBellNotify:
294                if (name != NULL)
295                    cfg->name.atom = XInternAtom(dpy, name, False);
296                else
297                    cfg->name.atom = None;
298                if (name)
299                    free(name);
300                break;
301            case XkbAccessXNotify:
302                priv = 0;
303                if (name == NULL)
304                    priv = XkbAllNewKeyboardEventsMask;
305                else if (uStrCaseEqual(name, "skpress"))
306                    priv = XkbAXN_SKPressMask;
307                else if (uStrCaseEqual(name, "skaccept"))
308                    priv = XkbAXN_SKAcceptMask;
309                else if (uStrCaseEqual(name, "skreject"))
310                    priv = XkbAXN_SKRejectMask;
311                else if (uStrCaseEqual(name, "skrelease"))
312                    priv = XkbAXN_SKReleaseMask;
313                else if (uStrCaseEqual(name, "bkaccept"))
314                    priv = XkbAXN_BKAcceptMask;
315                else if (uStrCaseEqual(name, "bkreject"))
316                    priv = XkbAXN_BKRejectMask;
317                else if (uStrCaseEqual(name, "warning"))
318                    priv = XkbAXN_AXKWarningMask;
319                if (name)
320                    free(name);
321                cfg->name.priv = priv;
322                break;
323            case XkbActionMessage:
324                /* nothing to do */
325                break;
326            }
327        eventMask |= (1L << cfg->event_type);
328        cfg = cfg->next;
329    }
330    while ((config) && (config->entry_type != EventDef)) {
331        CfgEntryPtr next;
332
333        if (config->name.str)
334            free(config->name.str);
335        if (config->action.text)
336            free(config->action.text);
337        config->name.str = NULL;
338        config->action.text = NULL;
339        next = config->next;
340        free(config);
341        config = next;
342    }
343    cfg = config;
344    while ((cfg != NULL) && (cfg->next != NULL)) {
345        CfgEntryPtr next;
346
347        next = cfg->next;
348        if (next->entry_type != EventDef) {
349            if (next->name.str)
350                free(config->name.str);
351            if (next->action.text)
352                free(config->action.text);
353            next->name.str = NULL;
354            next->action.text = NULL;
355            cfg->next = next->next;
356            next->next = NULL;
357            free(next);
358        }
359        else
360            cfg = next;
361    }
362    return;
363}
364
365static CfgEntryPtr
366FindMatchingConfig(XkbEvent * ev)
367{
368    CfgEntryPtr cfg, dflt;
369
370    dflt = NULL;
371    for (cfg = config; (cfg != NULL); cfg = cfg->next) {
372        if ((ev->type != xkbEventCode) || (cfg->event_type != ev->any.xkb_type))
373            continue;
374        switch (ev->any.xkb_type) {
375        case XkbBellNotify:
376            if (ev->bell.name == cfg->name.atom)
377                return cfg;
378            else if ((cfg->name.atom == None) && (dflt == NULL))
379                dflt = cfg;
380            break;
381        case XkbAccessXNotify:
382            if (cfg->name.priv & (1L << ev->accessx.detail))
383                return cfg;
384            break;
385        case XkbActionMessage:
386            if (cfg->name.str == NULL)
387                dflt = cfg;
388            else if (strncmp(cfg->name.str, ev->message.message,
389                             XkbActionMessageLength) == 0)
390                return cfg;
391            break;
392        default:
393            uInternalError("Can't handle type %d XKB events yet, Sorry.\n",
394                           ev->any.xkb_type);
395            break;
396        }
397    }
398    return dflt;
399}
400
401static Bool
402ProcessMatchingConfig(XkbEvent * ev)
403{
404    CfgEntryPtr cfg;
405    char buf[1024], *cmd;
406    int ok;
407
408    cfg = FindMatchingConfig(ev);
409    if (!cfg) {
410        if (verbose)
411            PrintXkbEvent(stdout, ev);
412        return False;
413    }
414    if (cfg->action.type == UnknownAction) {
415        if (cfg->action.text == NULL)
416            cfg->action.type = NoAction;
417        else if (cfg->action.text[0] == '!') {
418            char *tmp;
419
420            cfg->action.type = ShellAction;
421            tmp = uStringDup(&cfg->action.text[1]);
422            free(cfg->action.text);
423            cfg->action.text = tmp;
424        }
425        else
426            cfg->action.type = SoundAction;
427    }
428    switch (cfg->action.type) {
429    case NoAction:
430        return True;
431    case EchoAction:
432        if (cfg->action.text != NULL) {
433            snprintf(buf, sizeof(buf), "%s", cfg->action.text);
434            cmd = SubstituteEventArgs(buf, ev);
435            printf("%s", cmd);
436        }
437        return True;
438    case PrintEvAction:
439        PrintXkbEvent(stdout, ev);
440        return True;
441    case ShellAction:
442        if (cfg->action.text == NULL) {
443            uWarning("Empty shell command!\n");
444            uAction("Ignored\n");
445            return True;
446        }
447        cmd = cfg->action.text;
448        break;
449    case SoundAction:
450        if (cfg->action.text == NULL) {
451            uWarning("Empty sound command!\n");
452            uAction("Ignored\n");
453            return True;
454        }
455        snprintf(buf, sizeof(buf), "%s %s%s", soundCmd, soundDir,
456                 cfg->action.text);
457        cmd = buf;
458        break;
459    default:
460        uInternalError("Unknown error action type %d\n", cfg->action.type);
461        return False;
462    }
463    cmd = SubstituteEventArgs(cmd, ev);
464    if (verbose)
465        uInformation("Executing shell command \"%s\"\n", cmd);
466    ok = (system(cmd) == 0);
467    return ok;
468}
469
470/***====================================================================***/
471
472int
473main(int argc, char *argv[])
474{
475    FILE *      file;
476    static char buf[1024];
477    XkbEvent    ev;
478    Bool        ok;
479
480    yyin = stdin;
481    uSetErrorFile(NullString);
482
483    if (!parseArgs(argc, argv))
484        exit(1);
485    file = NULL;
486    XkbInitAtoms(NULL);
487    if (cfgFileName == NULL) {
488        char *home = getenv("HOME");
489        snprintf(buf, sizeof(buf), DFLT_XKBEVD_CONFIG, (home ? home : ""));
490        cfgFileName = buf;
491    }
492    if (uStringEqual(cfgFileName, "-")) {
493        static const char *in = "stdin";
494
495        file = stdin;
496        cfgFileName = in;
497    }
498    else {
499        file = fopen(cfgFileName, "r");
500        if (file == NULL) {     /* no personal config, try for a system one */
501            if (cfgFileName != buf) {   /* user specified a file.  bail */
502                uError("Can't open config file \"%s\n", cfgFileName);
503                uAction("Exiting\n");
504                exit(1);
505            }
506            snprintf(buf, sizeof(buf), DFLT_SYS_XKBEVD_CONFIG,
507                     DFLT_XKB_CONFIG_ROOT);
508            file = fopen(cfgFileName, "r");
509            if (file == NULL && !eventMask) {
510                if (verbose) {
511                    uError("Couldn't find a config file anywhere\n");
512                    uAction("Exiting\n");
513                    exit(1);
514                }
515                exit(0);
516            }
517        }
518    }
519
520    if (background) {
521        if (fork() != 0) {
522            if (verbose)
523                uInformation("Running in the background\n");
524            exit(0);
525        }
526    }
527    dpy = GetDisplay(argv[0], dpyName, &xkbOpcode, &xkbEventCode);
528    if (!dpy)
529        goto BAILOUT;
530    ok = True;
531    setScanState(cfgFileName, 1);
532    CFGParseFile(file);
533    if (!config && !eventMask) {
534        uError("No configuration specified in \"%s\"\n", cfgFileName);
535        goto BAILOUT;
536    }
537    if (eventMask == 0) {
538        uError("No events to watch in \"%s\"\n", cfgFileName);
539        goto BAILOUT;
540    }
541    if (!XkbSelectEvents(dpy, XkbUseCoreKbd, eventMask, eventMask)) {
542        uError("Couldn't select desired XKB events\n");
543        goto BAILOUT;
544    }
545    xkb = XkbGetKeyboard(dpy, XkbGBN_AllComponentsMask, XkbUseCoreKbd);
546    if (eventMask & XkbBellNotifyMask) {
547        unsigned ctrls, vals;
548
549        if (verbose)
550            uInformation("Temporarily disabling the audible bell\n");
551        if (!XkbChangeEnabledControls
552            (dpy, XkbUseCoreKbd, XkbAudibleBellMask, 0)) {
553            uError("Couldn't disable audible bell\n");
554            goto BAILOUT;
555        }
556        ctrls = vals = XkbAudibleBellMask;
557        if (!XkbSetAutoResetControls(dpy, XkbAudibleBellMask, &ctrls, &vals)) {
558            uWarning("Couldn't configure audible bell to reset on exit\n");
559            uAction("Audible bell might remain off\n");
560        }
561    }
562    if (soundCmd == NULL)
563        soundCmd = DFLT_SOUND_CMD;
564    if (soundDir == NULL)
565        soundDir = DFLT_SOUND_DIR;
566    XkbStdBellEvent(dpy, None, 0, XkbBI_ImAlive);
567    while (1) {
568        XNextEvent(dpy, &ev.core);
569        if ((!ProcessMatchingConfig(&ev)) && (ev.type == xkbEventCode) &&
570            (ev.any.xkb_type == XkbBellNotify)) {
571            XkbForceDeviceBell(dpy, ev.bell.device,
572                               ev.bell.bell_class, ev.bell.bell_id,
573                               ev.bell.percent);
574        }
575    }
576
577    XCloseDisplay(dpy);
578    return (ok == 0);
579 BAILOUT:
580    uAction("Exiting\n");
581    if (dpy != NULL)
582        XCloseDisplay(dpy);
583    exit(1);
584}
585