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