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