xkbevd.c revision a67f45c3
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/share/X11/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(const char *program, const char *displayName,
220           int *opcodeRtrn, int *evBaseRtrn)
221{
222    int mjr, mnr, error;
223    Display *display;
224
225    mjr = XkbMajorVersion;
226    mnr = XkbMinorVersion;
227    display = XkbOpenDisplay(displayName, evBaseRtrn, NULL, &mjr, &mnr, &error);
228    if (display == NULL) {
229        switch (error) {
230        case XkbOD_BadLibraryVersion:
231            uInformation("%s was compiled with XKB version %d.%02d\n",
232                         program, XkbMajorVersion, XkbMinorVersion);
233            uError("X library supports incompatible version %d.%02d\n",
234                   mjr, mnr);
235            break;
236        case XkbOD_ConnectionRefused:
237            uError("Cannot open display \"%s\"\n", displayName);
238            break;
239        case XkbOD_NonXkbServer:
240            uError("XKB extension not present on %s\n", displayName);
241            break;
242        case XkbOD_BadServerVersion:
243            uInformation("%s was compiled with XKB version %d.%02d\n",
244                         program, XkbMajorVersion, XkbMinorVersion);
245            uError("Server %s uses incompatible version %d.%02d\n",
246                   displayName, mjr, mnr);
247            break;
248        default:
249            uInternalError("Unknown error %d from XkbOpenDisplay\n", error);
250        }
251    }
252    else if (synch)
253        XSynchronize(display, True);
254    if (opcodeRtrn)
255        XkbQueryExtension(display, opcodeRtrn, evBaseRtrn, NULL, &mjr, &mnr);
256    return display;
257}
258
259/***====================================================================***/
260
261void
262InterpretConfigs(CfgEntryPtr cfg)
263{
264    config = cfg;
265    while (cfg != NULL) {
266        char *name = cfg->name.str;
267        if (cfg->entry_type == VariableDef) {
268            if (uStrCaseEqual(name, "sounddirectory") ||
269                uStrCaseEqual(name, "sounddir")) {
270                if (soundDir == NULL) {
271                    soundDir = cfg->action.text;
272                    cfg->name.str = NULL;
273                    cfg->action.text = NULL;
274                }
275            }
276            else if (uStrCaseEqual(name, "soundcommand") ||
277                     uStrCaseEqual(name, "soundcmd")) {
278                if (soundCmd == NULL) {
279                    soundCmd = cfg->action.text;
280                    cfg->name.str = NULL;
281                    cfg->action.text = NULL;
282                }
283            }
284            else {
285                uWarning("Assignment to unknown variable \"%s\"\n", name);
286                uAction("Ignored\n");
287            }
288        }
289        else if (cfg->entry_type == EventDef) {
290            unsigned int priv;
291
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        }
328        eventMask |= (1L << cfg->event_type);
329        cfg = cfg->next;
330    }
331    while ((config) && (config->entry_type != EventDef)) {
332        CfgEntryPtr next;
333
334        if (config->name.str)
335            free(config->name.str);
336        if (config->action.text)
337            free(config->action.text);
338        config->name.str = NULL;
339        config->action.text = NULL;
340        next = config->next;
341        free(config);
342        config = next;
343    }
344    cfg = config;
345    while ((cfg != NULL) && (cfg->next != NULL)) {
346        CfgEntryPtr next;
347
348        next = cfg->next;
349        if (next->entry_type != EventDef) {
350            if (next->name.str)
351                free(config->name.str);
352            if (next->action.text)
353                free(config->action.text);
354            next->name.str = NULL;
355            next->action.text = NULL;
356            cfg->next = next->next;
357            next->next = NULL;
358            free(next);
359        }
360        else
361            cfg = next;
362    }
363    return;
364}
365
366static CfgEntryPtr
367FindMatchingConfig(XkbEvent * ev)
368{
369    CfgEntryPtr cfg, dflt;
370
371    dflt = NULL;
372    for (cfg = config; (cfg != NULL); cfg = cfg->next) {
373        if ((ev->type != xkbEventCode) || (cfg->event_type != ev->any.xkb_type))
374            continue;
375        switch (ev->any.xkb_type) {
376        case XkbBellNotify:
377            if (ev->bell.name == cfg->name.atom)
378                return cfg;
379            else if ((cfg->name.atom == None) && (dflt == NULL))
380                dflt = cfg;
381            break;
382        case XkbAccessXNotify:
383            if (cfg->name.priv & (1L << ev->accessx.detail))
384                return cfg;
385            break;
386        case XkbActionMessage:
387            if (cfg->name.str == NULL)
388                dflt = cfg;
389            else if (strncmp(cfg->name.str, ev->message.message,
390                             XkbActionMessageLength) == 0)
391                return cfg;
392            break;
393        default:
394            uInternalError("Can't handle type %d XKB events yet, Sorry.\n",
395                           ev->any.xkb_type);
396            break;
397        }
398    }
399    return dflt;
400}
401
402static Bool
403ProcessMatchingConfig(XkbEvent * ev)
404{
405    CfgEntryPtr cfg;
406    char buf[1024], *cmd;
407    int ok;
408
409    cfg = FindMatchingConfig(ev);
410    if (!cfg) {
411        if (verbose)
412            PrintXkbEvent(stdout, ev);
413        return False;
414    }
415    if (cfg->action.type == UnknownAction) {
416        if (cfg->action.text == NULL)
417            cfg->action.type = NoAction;
418        else if (cfg->action.text[0] == '!') {
419            char *tmp;
420
421            cfg->action.type = ShellAction;
422            tmp = uStringDup(&cfg->action.text[1]);
423            free(cfg->action.text);
424            cfg->action.text = tmp;
425        }
426        else
427            cfg->action.type = SoundAction;
428    }
429    switch (cfg->action.type) {
430    case NoAction:
431        return True;
432    case EchoAction:
433        if (cfg->action.text != NULL) {
434            snprintf(buf, sizeof(buf), "%s", cfg->action.text);
435            cmd = SubstituteEventArgs(buf, ev);
436            printf("%s", cmd);
437        }
438        return True;
439    case PrintEvAction:
440        PrintXkbEvent(stdout, ev);
441        return True;
442    case ShellAction:
443        if (cfg->action.text == NULL) {
444            uWarning("Empty shell command!\n");
445            uAction("Ignored\n");
446            return True;
447        }
448        cmd = cfg->action.text;
449        break;
450    case SoundAction:
451        if (cfg->action.text == NULL) {
452            uWarning("Empty sound command!\n");
453            uAction("Ignored\n");
454            return True;
455        }
456        snprintf(buf, sizeof(buf), "%s %s%s", soundCmd, soundDir,
457                 cfg->action.text);
458        cmd = buf;
459        break;
460    default:
461        uInternalError("Unknown error action type %d\n", cfg->action.type);
462        return False;
463    }
464    cmd = SubstituteEventArgs(cmd, ev);
465    if (verbose)
466        uInformation("Executing shell command \"%s\"\n", cmd);
467    ok = (system(cmd) == 0);
468    return ok;
469}
470
471/***====================================================================***/
472
473int
474main(int argc, char *argv[])
475{
476    FILE *      file;
477    static char buf[1024];
478    XkbEvent    ev;
479    Bool        ok;
480
481    yyin = stdin;
482    uSetErrorFile(NullString);
483
484    if (!parseArgs(argc, argv))
485        exit(1);
486    file = NULL;
487    XkbInitAtoms(NULL);
488    if (cfgFileName == NULL) {
489        char *home = getenv("HOME");
490        snprintf(buf, sizeof(buf), DFLT_XKBEVD_CONFIG, (home ? home : ""));
491        cfgFileName = buf;
492    }
493    if (uStringEqual(cfgFileName, "-")) {
494        static const char *in = "stdin";
495
496        file = stdin;
497        cfgFileName = in;
498    }
499    else {
500        file = fopen(cfgFileName, "r");
501        if (file == NULL) {     /* no personal config, try for a system one */
502            if (cfgFileName != buf) {   /* user specified a file.  bail */
503                uError("Can't open config file \"%s\n", cfgFileName);
504                uAction("Exiting\n");
505                exit(1);
506            }
507            snprintf(buf, sizeof(buf), DFLT_SYS_XKBEVD_CONFIG,
508                     DFLT_XKB_CONFIG_ROOT);
509            file = fopen(cfgFileName, "r");
510            if (file == NULL && !eventMask) {
511                if (verbose) {
512                    uError("Couldn't find a config file anywhere\n");
513                    uAction("Exiting\n");
514                    exit(1);
515                }
516                exit(0);
517            }
518        }
519    }
520
521    if (background) {
522        if (fork() != 0) {
523            if (verbose)
524                uInformation("Running in the background\n");
525            exit(0);
526        }
527    }
528    dpy = GetDisplay(argv[0], dpyName, &xkbOpcode, &xkbEventCode);
529    if (!dpy)
530        goto BAILOUT;
531    ok = True;
532    setScanState(cfgFileName, 1);
533    CFGParseFile(file);
534    if (!config && !eventMask) {
535        uError("No configuration specified in \"%s\"\n", cfgFileName);
536        goto BAILOUT;
537    }
538    if (eventMask == 0) {
539        uError("No events to watch in \"%s\"\n", cfgFileName);
540        goto BAILOUT;
541    }
542    if (!XkbSelectEvents(dpy, XkbUseCoreKbd, eventMask, eventMask)) {
543        uError("Couldn't select desired XKB events\n");
544        goto BAILOUT;
545    }
546    xkb = XkbGetKeyboard(dpy, XkbGBN_AllComponentsMask, XkbUseCoreKbd);
547    if (eventMask & XkbBellNotifyMask) {
548        unsigned ctrls, vals;
549
550        if (verbose)
551            uInformation("Temporarily disabling the audible bell\n");
552        if (!XkbChangeEnabledControls
553            (dpy, XkbUseCoreKbd, XkbAudibleBellMask, 0)) {
554            uError("Couldn't disable audible bell\n");
555            goto BAILOUT;
556        }
557        ctrls = vals = XkbAudibleBellMask;
558        if (!XkbSetAutoResetControls(dpy, XkbAudibleBellMask, &ctrls, &vals)) {
559            uWarning("Couldn't configure audible bell to reset on exit\n");
560            uAction("Audible bell might remain off\n");
561        }
562    }
563    if (soundCmd == NULL)
564        soundCmd = DFLT_SOUND_CMD;
565    if (soundDir == NULL)
566        soundDir = DFLT_SOUND_DIR;
567    XkbStdBellEvent(dpy, None, 0, XkbBI_ImAlive);
568    while (1) {
569        XNextEvent(dpy, &ev.core);
570        if ((!ProcessMatchingConfig(&ev)) && (ev.type == xkbEventCode) &&
571            (ev.any.xkb_type == XkbBellNotify)) {
572            XkbForceDeviceBell(dpy, ev.bell.device,
573                               ev.bell.bell_class, ev.bell.bell_id,
574                               ev.bell.percent);
575        }
576    }
577
578    XCloseDisplay(dpy);
579    return (ok == 0);
580 BAILOUT:
581    uAction("Exiting\n");
582    if (dpy != NULL)
583        XCloseDisplay(dpy);
584    exit(1);
585}
586