/* xrus - keyboard switcher/indicator Copyright (c) 1996 Alexander V. Lukyanov This is free software with no warranty. See COPYING for details. */ /*__________________________________________________________________________ ** ** File: xrus.c **__________________________________________________________________________ */ #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SYS_WAIT_H # include #endif #include #include #include #include #include #include #ifdef HAVE_MOTIF #include #include #include #include #endif #ifdef HAVE_LIBXAW #include #include #include #endif #define XK_MISCELLANY #include #ifndef SA_RESTART #define SA_RESTART 0 #endif #include "xalloca.h" #include "keycomb.h" #include "xrus.h" #include "props.h" #include "menu.h" #include "kbdstate.h" const char AppClass[]="Xrus"; extern const char *DefaultResources; XtActionsRec Actions[]= { {"XrusMenuPopup",XrusMenuPopup} }; struct wincache_cell { Window win; time_t time; } window_cache[256]; XrusRec AppData; char DefaultSwitchKeys[]="Shift_L+Shift_R"; char DefaultToLatKeys[]=""; char DefaultToRusKeys[]=""; char DefaultLocker[]="exec xlock -remote >/dev/null 2>&1"; char LockerData[256]; char SwitchKeysData[256]; char ToLatKeysData[256]; char ToRusKeysData[256]; XtResource resources[]= { /* { name class type size offset default_type default_addr },*/ /* { ----------- ----------- ----------- ------------ ----------------------------------- ----------- ------------------------- },*/ { "autolock", "Autolock", XtRBoolean, sizeof(Boolean), XtOffsetOf(XrusRec,autolock), XtRImmediate, (XtPointer)False }, { "locker", "Locker", XtRString, sizeof(String), XtOffsetOf(XrusRec,locker), XtRString, DefaultLocker }, { "timeout", "Timeout", XtRInt, sizeof(int), XtOffsetOf(XrusRec,timeout), XtRImmediate, (XtPointer)30 }, { "useBell", "UseBell", XtRBoolean, sizeof(int), XtOffsetOf(XrusRec,useBell), XtRImmediate, (XtPointer)True }, { "switchKeys","SwitchKeys",XtRString,sizeof(String), XtOffsetOf(XrusRec,switchKeys), XtRString, DefaultSwitchKeys }, { "toRusKeys","ToRusKeys",XtRString,sizeof(String), XtOffsetOf(XrusRec,toRusKeys), XtRString, DefaultToRusKeys }, { "toLatKeys","ToLatKeys",XtRString,sizeof(String), XtOffsetOf(XrusRec,toLatKeys), XtRString, DefaultToLatKeys }, { "xmodmap", "Xmodmap", XtRString, sizeof(String), XtOffsetOf(XrusRec,xmodmap), XtRString, "" }, { "led", "Led", XtRInt, sizeof(int), XtOffsetOf(XrusRec,led), XtRImmediate, (XtPointer)0 }, { "altMaps", "AltMaps", XtRString, sizeof(String), XtOffsetOf(XrusRec,altMaps), XtRString, "" }, { "icon", "Icon", XtRBoolean, sizeof(Boolean), XtOffsetOf(XrusRec,icon), XtRImmediate, (XtPointer)True }, { "adjustModeButtons","AdjustModeButtons",XtRBoolean,sizeof(Boolean), XtOffsetOf(XrusRec,adjustModeButtons), XtRImmediate, (XtPointer)True }, { "alwaysOnTop","AlwaysOnTop",XtRBoolean,sizeof(Boolean), XtOffsetOf(XrusRec,alwaysOnTop), XtRImmediate, (XtPointer)False }, { "alwaysMapped","AutoMap",XtRBoolean,sizeof(Boolean), XtOffsetOf(XrusRec,alwaysMapped), XtRImmediate, (XtPointer)True }, { "capsLockEmu","CapsLockEmu",XtRBoolean,sizeof(Boolean), XtOffsetOf(XrusRec,capsLockEmu), XtRImmediate, (XtPointer)False }, { "capsLockLed","CapsLockLed",XtRInt, sizeof(int), XtOffsetOf(XrusRec,capsLockLed), XtRImmediate, (XtPointer)0 }, }; XrmOptionDescRec options[]= { /* { option, specifier, argKind, value },*/ /* { ------------- -------------- ----------------- ------ },*/ { "-autolock", "*autolock", XrmoptionNoArg, "true" }, { "+autolock", "*autolock", XrmoptionNoArg, "false"}, { "-locker", "*locker", XrmoptionSepArg, NULL }, { "-timeout", "*timeout", XrmoptionSepArg, NULL }, { "-bell", "*useBell", XrmoptionNoArg, "true" }, { "+bell", "*useBell", XrmoptionNoArg, "false"}, { "-led", "*led", XrmoptionSepArg, "0" }, { "+led", "*led", XrmoptionNoArg, "0" }, { "-icon", "*icon", XrmoptionNoArg, "true" }, { "+icon", "*icon", XrmoptionNoArg, "false"}, }; Display *disp; XtAppContext app_context; #define StartArgs() count=0 #define AddArg(name,val) (XtSetArg(args[count],(name),(val)),++count) Arg args[32]; int count; char *program; struct KeyCombination SwitchKeys,ToLatKeys,ToRusKeys; #define ShouldUseLat() IsModifierPressed(ControlMapIndex) enum { MODE=1, RUS=1, LAT=0, TEMP_LAT=2, CAPSLOCK_ON=4 }; int Mode=LAT; int NewMode; Widget top_level=NULL; #if defined HAVE_MOTIF || defined HAVE_LIBXAW Widget form_w; Widget switch_button[2]; #endif Atom type_ret; int format_ret; unsigned long nitems_ret,bytes_after_ret; unsigned char *prop; Atom wm_delete_window; int xrus_check=1; void AddChildren(Window); void SetAlarm(); int (*old_error_handler)(Display *d,XErrorEvent *ev); pid_t LockerRunning=0; int MappingNotifyIgnoreCount=0; XtIntervalId MappingNotifyTimeout=-1; XtIntervalId raise_tmout=-1; void ShowSwitchButton(void); /*________________________________________________________________________ */ static int SwappableKey(KeySym *k) { return(k[0]>0 && k[1]>0 && k[2]>0 && k[3]>0 && k[0]<256 && k[1]<256 && k[2]<256 && k[3]<256); } static int CapsLockableKey(KeySym *k) { KeySym k0=k[0],k1=k[1]; return((k0>=128 && k1>=128 && k0<256 && k1<256) || (((k0>='a' && k0<='z') || (k0>='A' && k0<='Z')) && ((k1>='a' && k1<='z') || (k1>='A' && k1<='Z')))); } void FixNewMode() { NewMode&=~(TEMP_LAT); if(ShouldUseLat()) NewMode|=TEMP_LAT; } void SwitchKeyboard(int to) { KeySym tmp; KeySym *keymap; int i; XKeyboardControl kbd_control; int from_mode,to_mode,from_caps,to_caps; int changed=0; if(to==Mode) return; keymap=GetKeymap(); if(keysyms_per_keycode<4) /* should not happen */ return; from_mode=((Mode&TEMP_LAT)?LAT:Mode&MODE); to_mode=((to&TEMP_LAT)?LAT:to&MODE); from_caps=((Mode&TEMP_LAT)?0:Mode&CAPSLOCK_ON); to_caps=((to&TEMP_LAT)?0:to&CAPSLOCK_ON); for(i=(max_keycode-min_keycode)*keysyms_per_keycode; i>=0; i-=keysyms_per_keycode) { if(from_mode!=to_mode && SwappableKey(keymap+i)) { tmp=keymap[i]; keymap[i]=keymap[i+2]; keymap[i+2]=tmp; tmp=keymap[i+1]; keymap[i+1]=keymap[i+3]; keymap[i+3]=tmp; changed=1; } if(AppData.capsLockEmu && from_caps!=to_caps) { if(CapsLockableKey(keymap+i)) { tmp=keymap[i]; keymap[i]=keymap[i+1]; keymap[i+1]=tmp; changed=1; } if(CapsLockableKey(keymap+i+2)) { tmp=keymap[i+2]; keymap[i+2]=keymap[i+3]; keymap[i+3]=tmp; changed=1; } } } if(changed) { XChangeKeyboardMapping(disp,min_keycode,keysyms_per_keycode, keymap,max_keycode-min_keycode+1); MappingNotifyIgnoreCount++; /* SGI X server goes crazy without this */ XSetModifierMapping(disp,GetModifiers()); } if(AppData.capsLockEmu && from_caps!=to_caps) { if(AppData.capsLockLed>0 && AppData.capsLockLed<=32) { kbd_control.led_mode=(NewMode&CAPSLOCK_ON?LedModeOn:LedModeOff); kbd_control.led=AppData.capsLockLed; XChangeKeyboardControl(disp,KBLed|KBLedMode,&kbd_control); } } if((Mode&MODE)!=(to&MODE)) { if(AppData.led>0 && AppData.led<=32) { kbd_control.led_mode=((to&MODE)==RUS?LedModeOn:LedModeOff); kbd_control.led=AppData.led; XChangeKeyboardControl(disp,KBLed|KBLedMode,&kbd_control); } if(AppData.useBell) XBell(disp,30); } Mode=to; ShowSwitchButton(); } static void InitCapsLockEmu() { KeyCode *lock; int i,changed=0; XModifierKeymap *mod; if(!AppData.capsLockEmu) return; mod=XGetModifierMapping(disp); lock=mod->modifiermap +LockMapIndex*mod->max_keypermod; for(i=0; imax_keypermod; i++) { if(lock[i]) { changed=1; lock[i]=0; } } if(changed && XSetModifierMapping(disp,mod)!=MappingSuccess) { fprintf(stderr,"%s: can't remove Caps_Lock from lock -- CapsLock emulation disabled\n",program); AppData.capsLockEmu=False; } XFreeModifiermap(mod); } #if defined HAVE_MOTIF || defined HAVE_LIBXAW void RaiseButton() { XRaiseWindow(disp,XtWindow(top_level)); } void RaiseButtonOnTimeout(XtPointer closure,XtIntervalId *id) { (void)closure; (void)id; raise_tmout=-1; RaiseButton(); } #endif /* HAVE_MOTIF || HAVE_LIBXAW */ /* shows proper indicator button */ void ShowSwitchButton(void) { #if defined HAVE_MOTIF || defined HAVE_LIBXAW int to_manage=-1; if(!switch_button[0] || !switch_button[1]) return; /* no button to show ! */ if((Mode&MODE)==RUS && !XtIsManaged(switch_button[1])) to_manage=1; else if((Mode&MODE)==LAT && !XtIsManaged(switch_button[0])) to_manage=0; if(to_manage!=-1) { XtSetArg(args[0],XtNallowShellResize,False); XtSetValues(top_level,args,1); XtUnmanageChild(switch_button[1-to_manage]); XtSetArg(args[0],XtNallowShellResize,True); XtSetValues(top_level,args,1); XtManageChild(switch_button[to_manage]); RaiseButton(); } #endif /* HAVE_MOTIF || HAVE_LIBXAW */ } void PerformSwitch(void) { SwitchKeyboard(Mode^MODE); } /* a window is in cache => we have requested all needed input from the window */ /* returns a boolean indicating the window was in cache and adds it to cache if it was not */ int AddToWindowCache(Window w) { time_t t=time(NULL); time_t best_time=t; int best_cell=-1; int i; for(i=0; iwindow_cache[i].time || window_cache[i].win==0) { best_cell=i; best_time=window_cache[i].time; } } window_cache[best_cell].time=t; window_cache[best_cell].win=w; return(1); } /* deletes a window from the cache */ void DelWindow(Window w) { int i; for(i=0; i=4) { for(i=(max_keycode-min_keycode)*keysyms_per_keycode; i>=0; i-=keysyms_per_keycode) { if(keymap[i] && keymap[i+1] && keymap[i+2] && keymap[i+3]) { ok_lat|=(keymap[i+2]>127 && keymap[i+2]<256 && keymap[i+3]>127 && keymap[i+3]<256); ok_rus|=(keymap[i+0]>127 && keymap[i+0]<256 && keymap[i+1]>127 && keymap[i+1]<256); } } } if(ok_rus==ok_lat) { fprintf(stderr,"%s: key mapping is not in proper coding, exiting.\n",program); if(first_time) { fprintf(stderr," You might probably want to load a russian modmap befor xrus starting.\n" " Alternatively, you can specify modmap file name on the command line,\n" " like this: ./xrus jcuken-koi8-xrus.xmm\n"); } else { fprintf(stderr," This means your modmap has changed to single-mode one.\n" " If you were doing xmodmap at the time, check your mapping in\n" " the file. For normal xrus work it is required modmap to have both\n" " russian and latin letters\n"); } Mode=LAT; exit(1); } NewMode=Mode=(Mode&~MODE)|(ok_rus?RUS:LAT); FixNewMode(); SwitchKeyboard(NewMode); ShowSwitchButton(); first_time=0; } void MappingNotifyTimeoutHandler(XtPointer closure,XtIntervalId *id) { (void)closure; (void)id; MappingNotifyTimeout=-1; CheckKeymap(); } #if defined HAVE_MOTIF || HAVE_LIBXAW void UnmapHandler(Widget w,XtPointer closure,XEvent *ev,Boolean *cont) { *cont=True; if(ev->type==UnmapNotify && AppData.alwaysMapped && AppData.icon) { XtMapWidget(top_level); } } void VisibilityChange(Widget w,XtPointer closure,XEvent *ev,Boolean *cont) { (void)w; (void)closure; *cont=True; if(ev->type==VisibilityNotify && ev->xvisibility.state!=VisibilityUnobscured && AppData.alwaysOnTop && raise_tmout==-1) { raise_tmout=XtAppAddTimeOut(app_context,1000, (XtTimerCallbackProc)RaiseButtonOnTimeout,NULL); } } #endif /* HAVE_MOTIF || HAVE_LIBXAW */ void MainLoop(void) { XEvent ev; KeySym ks; int i; int was_caps_locked; for(;;) { if(app_context==NULL) return; XtAppNextEvent(app_context,&ev); was_caps_locked=IsKeySymPressed(XK_Caps_Lock); XtDispatchEvent(&ev); KeepTrackOfKeyboard(&ev); switch(ev.type) { case(CreateNotify): AddWindow(ev.xcreatewindow.window); break; case(MapNotify): /* just to be sure we know the latest input mask */ AddWindow(ev.xmap.window); break; case(DestroyNotify): DelWindow(ev.xdestroywindow.window); break; case(KeyPress): SetAlarm(); ks=XLookupKeysym(&ev.xkey,0); NewMode=Mode; if(ks==XK_Caps_Lock && !was_caps_locked) NewMode^=CAPSLOCK_ON; KeyCombinationProcessKeyPress(&SwitchKeys,ks); KeyCombinationProcessKeyPress(&ToRusKeys,ks); KeyCombinationProcessKeyPress(&ToLatKeys,ks); FixNewMode(); SwitchKeyboard(NewMode); break; case(KeyRelease): SetAlarm(); ks=XLookupKeysym(&ev.xkey,0); NewMode=Mode; i=KeyCombinationProcessKeyRelease(&SwitchKeys,ks,1); i|=KeyCombinationProcessKeyRelease(&ToLatKeys,ks,!i); KeyCombinationProcessKeyRelease(&ToRusKeys,ks,!i); FixNewMode(); SwitchKeyboard(NewMode); break; case(MotionNotify): case(ButtonPress): case(ButtonRelease): SetAlarm(); break; case(MappingNotify): if(ev.xmapping.request==MappingKeyboard) { /* workaround with timeout for xmodmap bug (too many events) */ if(MappingNotifyIgnoreCount==0) { if(MappingNotifyTimeout!=-1) XtRemoveTimeOut(MappingNotifyTimeout); MappingNotifyTimeout=XtAppAddTimeOut(app_context,200, MappingNotifyTimeoutHandler,NULL); } else MappingNotifyIgnoreCount--; } break; } } } void cleanup(void) { if(app_context) SwitchKeyboard(LAT); } void sig_term(int sig) { fprintf(stderr,"%s: caught signal %d, exiting\n",program,sig); cleanup(); exit(1); } void sig_chld(int sig) { int status; if(wait(&status)==LockerRunning) { LockerRunning=0; SetAlarm(); } } void LockScreen() { pid_t pid; SwitchKeyboard(Mode&~MODE); fflush(stderr); switch(pid=fork()) { case(0): execl("/bin/sh","sh","-c",AppData.locker,NULL); fprintf(stderr,"%s: cannot execute /bin/sh - %s\n",program,strerror(errno)); fflush(stderr); _exit(1); case(-1): fprintf(stderr,"%s: cannot do fork() - %s\n",program,strerror(errno)); fflush(stderr); break; default: LockerRunning=pid; } } void AlarmHandler(int sig) { (void)sig; if(AppData.autolock) LockScreen(); } void SetAlarm() { if(AppData.autolock && AppData.timeout>0 && !LockerRunning) alarm(AppData.timeout*60); else alarm(0); } int X_error_handler(Display *d,XErrorEvent *ev) { /* A window can be deleted between CreateNotify and our request */ if(ev->error_code==BadWindow || ev->error_code==BadDrawable || ev->error_code==BadAccess) return(-1); return(old_error_handler(d,ev)); } void hook_signals(void) { struct sigaction act; act.sa_handler=sig_term; sigemptyset(&act.sa_mask); act.sa_flags=0; sigaction(SIGTERM,&act,NULL); sigaction(SIGINT,&act,NULL); sigaction(SIGQUIT,&act,NULL); act.sa_handler=SIG_IGN; sigaction(SIGHUP,&act,NULL); act.sa_handler=AlarmHandler; act.sa_flags=SA_RESTART; sigaction(SIGALRM,&act,NULL); act.sa_handler=sig_chld; act.sa_flags=SA_RESTART|SA_NOCLDSTOP; sigaction(SIGCHLD,&act,NULL); } int main(int argc,char **argv) { Atom ol_decor_del_atom,ol_decor_del[2]; XrmDatabase default_rdb,rdb; char *HOME; char *buf; char err[1024]; int scr; printf("Xrus | Version " VERSION " | Copyright (c) 1995-96 Alexander V. Lukyanov\n"); fflush(stdout); program=argv[0]; hook_signals(); XtToolkitInitialize(); app_context=XtCreateApplicationContext(); disp=XtOpenDisplay(app_context,NULL,"xrus",AppClass,options,XtNumber(options),&argc,argv); if(disp==NULL) { fprintf(stderr,"%s: cannot open display\n",program); exit(1); } if(argc>1 && argv[1][0]=='-') { fprintf(stderr,"Usage: %s [{-|+}autolock] [-locker \"locker cmd\"] [-timeout sec]\n" " [{-|+}bell] [{-|+}icon] [-led led_no] [+led] [modmap_file]\n",program); exit(1); } default_rdb=XrmGetStringDatabase(DefaultResources); rdb=XtDatabase(disp); XrmCombineDatabase(default_rdb,&rdb,False); HOME=getenv("HOME"); if(HOME) { buf=alloca(strlen(HOME)+10); sprintf(buf,"%s/.xrus",HOME); XrmCombineFileDatabase(buf,&rdb,True); } old_error_handler=XSetErrorHandler(X_error_handler); /* add all windows located on the display to the pool and check for other xrus presence */ for(scr=0; scr1) { AppData.xmodmap=argv[1]; } if(AppData.xmodmap[0]) { char *cmd=alloca(strlen(AppData.xmodmap)+9); sprintf(cmd,"xmodmap %s",AppData.xmodmap); system(cmd); } /* initialize switch keys */ SwitchKeys.Fire=SwitchKeysFire; ParseKeyCombination(AppData.switchKeys,&SwitchKeys,err); PrintParseErrors("switchKeys",err); ToLatKeys.Fire=ToLatKeysFire; ParseKeyCombination(AppData.toLatKeys,&ToLatKeys,err); PrintParseErrors("toLatKeys",err); ToRusKeys.Fire=ToRusKeysFire; ParseKeyCombination(AppData.toRusKeys,&ToRusKeys,err); PrintParseErrors("toRusKeys",err); #if !defined(DEBUG) && !defined(DONT_FORK) switch(fork()) { case(0): break; case(-1): perror("fork"); exit(1); default: _exit(0); } #endif #ifdef HAVE_ATEXIT atexit(cleanup); #else # ifdef __sun__ on_exit(cleanup,NULL); /* non-ANSI exit handler */ # endif #endif XtAppAddActions(app_context,Actions,XtNumber(Actions)); #if defined HAVE_MOTIF || defined HAVE_LIBXAW XtAddEventHandler(top_level,StructureNotifyMask,False,UnmapHandler,NULL); #ifdef HAVE_MOTIF StartArgs(); AddArg(XmNresizable,False); form_w=XtCreateManagedWidget("form",xmFormWidgetClass,top_level,args,count); switch_button[0]=XtCreateWidget("modeButton0",xmPushButtonWidgetClass,form_w,NULL,0); switch_button[1]=XtCreateWidget("modeButton1",xmPushButtonWidgetClass,form_w,NULL,0); XtAddCallback(switch_button[0],XmNactivateCallback,(XtCallbackProc)PerformSwitch,NULL); XtAddCallback(switch_button[1],XmNactivateCallback,(XtCallbackProc)PerformSwitch,NULL); #else form_w=XtCreateManagedWidget("form",boxWidgetClass,top_level,NULL,0); switch_button[0]=XtCreateWidget("modeButton0",commandWidgetClass,form_w,NULL,0); switch_button[1]=XtCreateWidget("modeButton1",commandWidgetClass,form_w,NULL,0); XtAddCallback(switch_button[0],XtNcallback,(XtCallbackProc)PerformSwitch,NULL); XtAddCallback(switch_button[1],XtNcallback,(XtCallbackProc)PerformSwitch,NULL); #endif XtManageChild(switch_button[(Mode&MODE)==RUS]); XtAddEventHandler(switch_button[0],VisibilityChangeMask,False,VisibilityChange,NULL); XtAddEventHandler(switch_button[1],VisibilityChangeMask,False,VisibilityChange,NULL); XrusMenuCreate(); #endif ol_decor_del_atom=XInternAtom(disp,"_OL_DECOR_DEL",False); ol_decor_del[0]=XInternAtom(disp,"_OL_DECOR_HEADER",False); ol_decor_del[1]=XInternAtom(disp,"_OL_DECOR_RESIZE",False); wm_delete_window=XInternAtom(disp,"WM_DELETE_WINDOW",False); XtRealizeWidget(top_level); XChangeProperty(disp,XtWindow(top_level),ol_decor_del_atom,XA_ATOM,32, PropModeReplace,(void*)ol_decor_del,2); #ifdef HAVE_LIBXAW { /* we have to emulate motif hints as we don't have Motif */ static CARD32 motif_hints[]={0x2,0xffffffff,0x2,0xffffffff,0x39250}; Atom motif_hints_atom=XInternAtom(disp,"_MOTIF_WM_HINTS",False); XChangeProperty(disp,XtWindow(top_level),motif_hints_atom,motif_hints_atom,32, PropModeReplace,(void*)motif_hints,XtNumber(motif_hints)); /* Xaw' shell don't catches WM_DELETE_WINDOW, have to do it myself */ XSetWMProtocols(disp,XtWindow(top_level),&wm_delete_window,1); } #endif KeyboardStateInit(); InitCapsLockEmu(); CheckKeymap(); #if defined HAVE_MOTIF || defined HAVE_LIBXAW if(AppData.icon) XtMapWidget(top_level); if(AppData.adjustModeButtons) { Dimension w0,h0,w1,h1; StartArgs(); AddArg(XtNwidth,&w0); AddArg(XtNheight,&h0); XtGetValues(switch_button[0],args,count); StartArgs(); AddArg(XtNwidth,&w1); AddArg(XtNheight,&h1); XtGetValues(switch_button[1],args,count); if(w0