563 lines
15 KiB
C
563 lines
15 KiB
C
/************************************************************
|
|
Copyright (c) 1995 by Silicon Graphics Computer Systems, Inc.
|
|
|
|
Permission to use, copy, modify, and distribute this
|
|
software and its documentation for any purpose and without
|
|
fee is hereby granted, provided that the above copyright
|
|
notice appear in all copies and that both that copyright
|
|
notice and this permission notice appear in supporting
|
|
documentation, and that the name of Silicon Graphics not be
|
|
used in advertising or publicity pertaining to distribution
|
|
of the software without specific prior written permission.
|
|
Silicon Graphics makes no representation about the suitability
|
|
of this software for any purpose. It is provided "as is"
|
|
without any express or implied warranty.
|
|
|
|
SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
|
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
|
|
GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
|
|
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
|
|
THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
********************************************************/
|
|
|
|
#define DEBUG_VAR xkbevdDebug
|
|
#include <X11/Xosdefs.h>
|
|
#include <stdlib.h>
|
|
#include "xkbevd.h"
|
|
|
|
|
|
#define lowbit(x) ((x) & (-(x)))
|
|
|
|
/***====================================================================***/
|
|
|
|
#ifndef DFLT_XKBEVD_CONFIG
|
|
#define DFLT_XKBEVD_CONFIG "%s/.xkb/xkbevd.cf"
|
|
#endif /* DFLT_XKBEVD_CONFIG */
|
|
|
|
#ifndef DFLT_XKB_CONFIG_ROOT
|
|
#define DFLT_XKB_CONFIG_ROOT "/usr/X11R6/lib/xkb"
|
|
#endif
|
|
|
|
#ifndef DFLT_SYS_XKBEVD_CONFIG
|
|
#define DFLT_SYS_XKBEVD_CONFIG "%s/xkbevd.cf"
|
|
#endif /* DFLT_SYS_XKBEVD_CONFIG */
|
|
|
|
#ifndef DFLT_SOUND_CMD
|
|
#define DFLT_SOUND_CMD "/usr/sbin/sfplay -q"
|
|
#endif /* DFLT_SOUND_CMD */
|
|
|
|
#ifndef DFLT_SOUND_DIR
|
|
#define DFLT_SOUND_DIR "/usr/share/data/sounds/prosonus/"
|
|
#endif /* DFLT_SOUND_DIR */
|
|
|
|
/***====================================================================***/
|
|
|
|
static char * dpyName= NULL;
|
|
Display * dpy= NULL;
|
|
static const char * cfgFileName= NULL;
|
|
int xkbOpcode= 0;
|
|
int xkbEventCode= 0;
|
|
Bool detectableRepeat= False;
|
|
|
|
static
|
|
CfgEntryPtr config= NULL;
|
|
static
|
|
unsigned long eventMask= 0;
|
|
|
|
static Bool synch= False;
|
|
static int verbose= 0;
|
|
static Bool background= False;
|
|
|
|
static const char * soundCmd= NULL;
|
|
static const char * soundDir= NULL;
|
|
|
|
XkbDescPtr xkb= NULL;
|
|
|
|
/***====================================================================***/
|
|
|
|
static void
|
|
Usage(int argc, char *argv[])
|
|
{
|
|
fprintf(stderr, "Usage: %s [options]...\n%s", argv[0],
|
|
"Legal options:\n"
|
|
"-?, -help Print this message\n"
|
|
"-cfg <file> Specify a config file\n"
|
|
"-sc <cmd> Specify the command to play sounds\n"
|
|
"-sd <dir> Specify the root directory for sound files\n"
|
|
"-d[isplay] <dpy> Specify the display to watch\n"
|
|
"-bg Run in background\n"
|
|
"-synch Force synchronization\n"
|
|
"-v Print verbose messages\n");
|
|
return;
|
|
}
|
|
|
|
/***====================================================================***/
|
|
|
|
static Bool
|
|
parseArgs(int argc, char *argv[])
|
|
{
|
|
register int i;
|
|
|
|
for (i=1;i<argc;i++) {
|
|
if (strcmp(argv[i],"-bg")==0) {
|
|
background= True;
|
|
}
|
|
else if (strcmp(argv[i],"-cfg")==0) {
|
|
if (i>=(argc-1)) {
|
|
uError("No configuration file specified on command line\n");
|
|
uAction("Trailing %s argument ignored\n",argv[i]);
|
|
}
|
|
else {
|
|
char *name= argv[++i];
|
|
if (cfgFileName!=NULL) {
|
|
if (uStringEqual(cfgFileName,name))
|
|
uWarning("Config file \"%s\" specified twice!\n",
|
|
name);
|
|
else {
|
|
uWarning("Multiple config files on command line\n");
|
|
uAction("Using \"%s\", ignoring \"%s\"\n",name,
|
|
cfgFileName);
|
|
}
|
|
}
|
|
cfgFileName= name;
|
|
}
|
|
}
|
|
else if ((strcmp(argv[i],"-d")==0)||(strcmp(argv[i],"-display")==0)) {
|
|
if (i>=(argc-1)) {
|
|
uError("No display specified on command line\n");
|
|
uAction("Trailing %s argument ignored\n",argv[i]);
|
|
}
|
|
else {
|
|
char *name= argv[++i];
|
|
if (dpyName!=NULL) {
|
|
if (uStringEqual(dpyName,name))
|
|
uWarning("Display \"%s\" specified twice!\n",
|
|
name);
|
|
else {
|
|
uWarning("Multiple displays on command line\n");
|
|
uAction("Using \"%s\", ignoring \"%s\"\n",name,
|
|
dpyName);
|
|
}
|
|
}
|
|
dpyName= name;
|
|
}
|
|
}
|
|
else if (strcmp(argv[i],"-sc")==0) {
|
|
if (i>=(argc-1)) {
|
|
uError("No sound command specified on command line\n");
|
|
uAction("Trailing %s argument ignored\n",argv[i]);
|
|
}
|
|
else {
|
|
char *name= argv[++i];
|
|
if (soundCmd!=NULL) {
|
|
if (uStringEqual(soundCmd,name))
|
|
uWarning("Sound command \"%s\" specified twice!\n",
|
|
name);
|
|
else {
|
|
uWarning("Multiple sound commands on command line\n");
|
|
uAction("Using \"%s\", ignoring \"%s\"\n",name,
|
|
soundCmd);
|
|
}
|
|
}
|
|
soundCmd= name;
|
|
}
|
|
}
|
|
else if (strcmp(argv[i],"-sd")==0) {
|
|
if (i>=(argc-1)) {
|
|
uError("No sound directory specified on command line\n");
|
|
uAction("Trailing %s argument ignored\n",argv[i]);
|
|
}
|
|
else {
|
|
char *name= argv[++i];
|
|
if (soundDir!=NULL) {
|
|
if (uStringEqual(soundDir,name))
|
|
uWarning("Sound directory \"%s\" specified twice!\n",
|
|
name);
|
|
else {
|
|
uWarning("Multiple sound dirs on command line\n");
|
|
uAction("Using \"%s\", ignoring \"%s\"\n",name,
|
|
soundDir);
|
|
}
|
|
}
|
|
soundDir= name;
|
|
}
|
|
}
|
|
else if ((strcmp(argv[i],"-synch")==0)||(strcmp(argv[i],"-s")==0)) {
|
|
synch= True;
|
|
}
|
|
else if (strcmp(argv[i],"-v")==0) {
|
|
verbose++;
|
|
}
|
|
else if ((strcmp(argv[i],"-?")==0)||(strcmp(argv[i],"-help")==0)) {
|
|
Usage(argc,argv);
|
|
exit(0);
|
|
}
|
|
else {
|
|
uError("Unknown flag \"%s\" on command line\n",argv[i]);
|
|
Usage(argc,argv);
|
|
return False;
|
|
}
|
|
}
|
|
if (background == False) {
|
|
eventMask = XkbAllEventsMask;
|
|
verbose++;
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
static Display *
|
|
GetDisplay(char *program, char *dpyName, int *opcodeRtrn, int *evBaseRtrn)
|
|
{
|
|
int mjr,mnr,error;
|
|
Display *dpy;
|
|
|
|
mjr= XkbMajorVersion;
|
|
mnr= XkbMinorVersion;
|
|
dpy= XkbOpenDisplay(dpyName,evBaseRtrn,NULL,&mjr,&mnr,&error);
|
|
if (dpy==NULL) {
|
|
switch (error) {
|
|
case XkbOD_BadLibraryVersion:
|
|
uInformation("%s was compiled with XKB version %d.%02d\n",
|
|
program,XkbMajorVersion,XkbMinorVersion);
|
|
uError("X library supports incompatible version %d.%02d\n",
|
|
mjr,mnr);
|
|
break;
|
|
case XkbOD_ConnectionRefused:
|
|
uError("Cannot open display \"%s\"\n",dpyName);
|
|
break;
|
|
case XkbOD_NonXkbServer:
|
|
uError("XKB extension not present on %s\n",dpyName);
|
|
break;
|
|
case XkbOD_BadServerVersion:
|
|
uInformation("%s was compiled with XKB version %d.%02d\n",
|
|
program,XkbMajorVersion,XkbMinorVersion);
|
|
uError("Server %s uses incompatible version %d.%02d\n",
|
|
dpyName,mjr,mnr);
|
|
break;
|
|
default:
|
|
uInternalError("Unknown error %d from XkbOpenDisplay\n",error);
|
|
}
|
|
}
|
|
else if (synch)
|
|
XSynchronize(dpy,True);
|
|
if (opcodeRtrn)
|
|
XkbQueryExtension(dpy,opcodeRtrn,evBaseRtrn,NULL,&mjr,&mnr);
|
|
return dpy;
|
|
}
|
|
|
|
/***====================================================================***/
|
|
|
|
void
|
|
InterpretConfigs(CfgEntryPtr cfg)
|
|
{
|
|
char * name;
|
|
unsigned priv= 0;
|
|
|
|
config= cfg;
|
|
while (cfg!=NULL) {
|
|
name= cfg->name.str;
|
|
if (cfg->entry_type==VariableDef) {
|
|
if (uStrCaseEqual(name,"sounddirectory")||
|
|
uStrCaseEqual(name,"sounddir")) {
|
|
if (soundDir==NULL) {
|
|
soundDir= cfg->action.text;
|
|
cfg->name.str= NULL;
|
|
cfg->action.text= NULL;
|
|
}
|
|
}
|
|
else if (uStrCaseEqual(name,"soundcommand")||
|
|
uStrCaseEqual(name,"soundcmd")) {
|
|
if (soundCmd==NULL) {
|
|
soundCmd= cfg->action.text;
|
|
cfg->name.str= NULL;
|
|
cfg->action.text= NULL;
|
|
}
|
|
}
|
|
else {
|
|
uWarning("Assignment to unknown variable \"%s\"\n", name);
|
|
uAction("Ignored\n");
|
|
}
|
|
}
|
|
else if (cfg->entry_type==EventDef) switch (cfg->event_type) {
|
|
case XkbBellNotify:
|
|
if (name!=NULL) cfg->name.atom= XInternAtom(dpy,name,False);
|
|
else cfg->name.atom= None;
|
|
if (name) free(name);
|
|
break;
|
|
case XkbAccessXNotify:
|
|
priv= 0;
|
|
if (name==NULL)
|
|
priv= XkbAllNewKeyboardEventsMask;
|
|
else if (uStrCaseEqual(name,"skpress"))
|
|
priv= XkbAXN_SKPressMask;
|
|
else if (uStrCaseEqual(name,"skaccept"))
|
|
priv= XkbAXN_SKAcceptMask;
|
|
else if (uStrCaseEqual(name,"skreject"))
|
|
priv= XkbAXN_SKRejectMask;
|
|
else if (uStrCaseEqual(name,"skrelease"))
|
|
priv= XkbAXN_SKReleaseMask;
|
|
else if (uStrCaseEqual(name,"bkaccept"))
|
|
priv= XkbAXN_BKAcceptMask;
|
|
else if (uStrCaseEqual(name,"bkreject"))
|
|
priv= XkbAXN_BKRejectMask;
|
|
else if (uStrCaseEqual(name,"warning"))
|
|
priv= XkbAXN_AXKWarningMask;
|
|
if (name) free(name);
|
|
cfg->name.priv= priv;
|
|
break;
|
|
case XkbActionMessage:
|
|
/* nothing to do */
|
|
break;
|
|
}
|
|
eventMask|= (1L<<cfg->event_type);
|
|
cfg= cfg->next;
|
|
}
|
|
while ((config)&&(config->entry_type!=EventDef)) {
|
|
CfgEntryPtr next;
|
|
if (config->name.str) free(config->name.str);
|
|
if (config->action.text) free(config->action.text);
|
|
config->name.str= NULL;
|
|
config->action.text= NULL;
|
|
next= config->next;
|
|
free(config);
|
|
config= next;
|
|
}
|
|
cfg= config;
|
|
while ((cfg!=NULL)&&(cfg->next!=NULL)) {
|
|
CfgEntryPtr next;
|
|
next= cfg->next;
|
|
if (next->entry_type!=EventDef) {
|
|
if (next->name.str) free(config->name.str);
|
|
if (next->action.text) free(config->action.text);
|
|
next->name.str= NULL;
|
|
next->action.text= NULL;
|
|
cfg->next= next->next;
|
|
next->next= NULL;
|
|
free(next);
|
|
}
|
|
else cfg= next;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static CfgEntryPtr
|
|
FindMatchingConfig(XkbEvent *ev)
|
|
{
|
|
CfgEntryPtr cfg,dflt;
|
|
|
|
dflt= NULL;
|
|
for (cfg= config;(cfg!=NULL);cfg=cfg->next) {
|
|
if ((ev->type!=xkbEventCode)||(cfg->event_type!=ev->any.xkb_type))
|
|
continue;
|
|
switch (ev->any.xkb_type) {
|
|
case XkbBellNotify:
|
|
if (ev->bell.name==cfg->name.atom)
|
|
return cfg;
|
|
else if ((cfg->name.atom==None)&&(dflt==NULL))
|
|
dflt= cfg;
|
|
break;
|
|
case XkbAccessXNotify:
|
|
if (cfg->name.priv&(1L<<ev->accessx.detail))
|
|
return cfg;
|
|
break;
|
|
case XkbActionMessage:
|
|
if (cfg->name.str==NULL)
|
|
dflt= cfg;
|
|
else if (strncmp(cfg->name.str,ev->message.message,
|
|
XkbActionMessageLength)==0)
|
|
return cfg;
|
|
break;
|
|
default:
|
|
uInternalError("Can't handle type %d XKB events yet, Sorry.\n",
|
|
ev->any.xkb_type);
|
|
break;
|
|
}
|
|
}
|
|
return dflt;
|
|
}
|
|
|
|
static Bool
|
|
ProcessMatchingConfig(XkbEvent *ev)
|
|
{
|
|
CfgEntryPtr cfg;
|
|
char buf[1024],*cmd;
|
|
int ok;
|
|
|
|
cfg= FindMatchingConfig(ev);
|
|
if (!cfg) {
|
|
if (verbose)
|
|
PrintXkbEvent(stdout,ev);
|
|
return False;
|
|
}
|
|
if (cfg->action.type==UnknownAction) {
|
|
if (cfg->action.text==NULL)
|
|
cfg->action.type= NoAction;
|
|
else if (cfg->action.text[0]=='!') {
|
|
char *tmp;
|
|
cfg->action.type= ShellAction;
|
|
tmp= uStringDup(&cfg->action.text[1]);
|
|
free(cfg->action.text);
|
|
cfg->action.text= tmp;
|
|
}
|
|
else cfg->action.type= SoundAction;
|
|
}
|
|
switch (cfg->action.type) {
|
|
case NoAction:
|
|
return True;
|
|
case EchoAction:
|
|
if (cfg->action.text!=NULL) {
|
|
sprintf(buf,"%s",cfg->action.text);
|
|
cmd= SubstituteEventArgs(buf,ev);
|
|
printf("%s",cmd);
|
|
}
|
|
return True;
|
|
case PrintEvAction:
|
|
PrintXkbEvent(stdout,ev);
|
|
return True;
|
|
case ShellAction:
|
|
if (cfg->action.text==NULL) {
|
|
uWarning("Empty shell command!\n");
|
|
uAction("Ignored\n");
|
|
return True;
|
|
}
|
|
cmd= cfg->action.text;
|
|
break;
|
|
case SoundAction:
|
|
if (cfg->action.text==NULL) {
|
|
uWarning("Empty sound command!\n");
|
|
uAction("Ignored\n");
|
|
return True;
|
|
}
|
|
sprintf(buf,"%s %s%s",soundCmd,soundDir,cfg->action.text);
|
|
cmd= buf;
|
|
break;
|
|
default:
|
|
uInternalError("Unknown error action type %d\n",cfg->action.type);
|
|
return False;
|
|
}
|
|
cmd= SubstituteEventArgs(cmd,ev);
|
|
if (verbose)
|
|
uInformation("Executing shell command \"%s\"\n",cmd);
|
|
ok= (system(cmd)==0);
|
|
return ok;
|
|
}
|
|
|
|
/***====================================================================***/
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
FILE * file;
|
|
static char buf[1024];
|
|
XkbEvent ev;
|
|
Bool ok;
|
|
|
|
|
|
yyin = stdin;
|
|
uSetEntryFile(NullString);
|
|
uSetDebugFile(NullString);
|
|
uSetErrorFile(NullString);
|
|
|
|
if (!parseArgs(argc,argv))
|
|
exit(1);
|
|
file= NULL;
|
|
XkbInitAtoms(NULL);
|
|
if (cfgFileName==NULL) {
|
|
char *home;
|
|
home= (char *)getenv("HOME");
|
|
sprintf(buf,DFLT_XKBEVD_CONFIG,(home?home:""));
|
|
cfgFileName= buf;
|
|
}
|
|
if (uStringEqual(cfgFileName,"-")) {
|
|
static const char *in= "stdin";
|
|
file= stdin;
|
|
cfgFileName= in;
|
|
}
|
|
else {
|
|
file= fopen(cfgFileName,"r");
|
|
if (file==NULL) { /* no personal config, try for a system one */
|
|
if (cfgFileName!=buf) { /* user specified a file. bail */
|
|
uError("Can't open config file \"%s\n",cfgFileName);
|
|
uAction("Exiting\n");
|
|
exit(1);
|
|
}
|
|
sprintf(buf,DFLT_SYS_XKBEVD_CONFIG,DFLT_XKB_CONFIG_ROOT);
|
|
file= fopen(cfgFileName,"r");
|
|
if (file==NULL && !eventMask) {
|
|
if (verbose) {
|
|
uError("Couldn't find a config file anywhere\n");
|
|
uAction("Exiting\n");
|
|
exit(1);
|
|
}
|
|
exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (background) {
|
|
if (fork()!=0) {
|
|
if (verbose)
|
|
uInformation("Running in the background\n");
|
|
exit(0);
|
|
}
|
|
}
|
|
dpy= GetDisplay(argv[0],dpyName,&xkbOpcode,&xkbEventCode);
|
|
if (!dpy)
|
|
goto BAILOUT;
|
|
ok= True;
|
|
setScanState(cfgFileName,1);
|
|
CFGParseFile(file);
|
|
if (!config && !eventMask) {
|
|
uError("No configuration specified in \"%s\"\n",cfgFileName);
|
|
goto BAILOUT;
|
|
}
|
|
if (eventMask==0) {
|
|
uError("No events to watch in \"%s\"\n",cfgFileName);
|
|
goto BAILOUT;
|
|
}
|
|
if (!XkbSelectEvents(dpy,XkbUseCoreKbd,eventMask,eventMask)) {
|
|
uError("Couldn't select desired XKB events\n");
|
|
goto BAILOUT;
|
|
}
|
|
xkb= XkbGetKeyboard(dpy,XkbGBN_AllComponentsMask,XkbUseCoreKbd);
|
|
if (eventMask&XkbBellNotifyMask) {
|
|
unsigned ctrls,vals;
|
|
if (verbose)
|
|
uInformation("Temporarily disabling the audible bell\n");
|
|
if (!XkbChangeEnabledControls(dpy,XkbUseCoreKbd,XkbAudibleBellMask,0)) {
|
|
uError("Couldn't disable audible bell\n");
|
|
goto BAILOUT;
|
|
}
|
|
ctrls= vals= XkbAudibleBellMask;
|
|
if (!XkbSetAutoResetControls(dpy,XkbAudibleBellMask,&ctrls,&vals)) {
|
|
uWarning("Couldn't configure audible bell to reset on exit\n");
|
|
uAction("Audible bell might remain off\n");
|
|
}
|
|
}
|
|
if (soundCmd==NULL) soundCmd= DFLT_SOUND_CMD;
|
|
if (soundDir==NULL) soundDir= DFLT_SOUND_DIR;
|
|
XkbStdBellEvent(dpy,None,0,XkbBI_ImAlive);
|
|
while (1) {
|
|
XNextEvent(dpy,&ev.core);
|
|
if ((!ProcessMatchingConfig(&ev))&&(ev.type==xkbEventCode)&&
|
|
(ev.any.xkb_type==XkbBellNotify)) {
|
|
XkbForceDeviceBell(dpy,ev.bell.device,
|
|
ev.bell.bell_class,ev.bell.bell_id,
|
|
ev.bell.percent);
|
|
}
|
|
}
|
|
|
|
XCloseDisplay(dpy);
|
|
return (ok==0);
|
|
BAILOUT:
|
|
uAction("Exiting\n");
|
|
if (dpy!=NULL)
|
|
XCloseDisplay(dpy);
|
|
exit(1);
|
|
}
|