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