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