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