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