/* -*-c-*- */ /* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include #include #include "libs/fvwmlib.h" #include "libs/Strings.h" #include "libs/wild.h" #include "libs/Grab.h" #include "libs/Bindings.h" #include "libs/charmap.h" #include "libs/wcontext.h" #include "libs/modifiers.h" static Bool is_grabbing_everything = False; static int key_min = 0; static int key_max = 0; /* Free the memory use by a binding. */ void FreeBindingStruct(Binding *b) { if (b->key_name) { free(b->key_name); } STROKE_CODE( if (b->Stroke_Seq) free(b->Stroke_Seq); ); if (b->Action) { free(b->Action); } if (b->Action2) { free(b->Action2); } if (b->windowName) { free(b->windowName); } free(b); return; } void FreeBindingList(Binding *b) { Binding *t; for (; b != NULL; b = t) { t = b->NextBinding; FreeBindingStruct(b); } return; } /* Unlink a binding b from a binding list pblist. The previous binding in the * list (prev) must be given also. Pass NULL at the beginning of the list. * The *pblist pointer may be modified by this function. */ static void UnlinkBinding(Binding **pblist, Binding *b, Binding *prev) { Binding *t; if (!prev && b != *pblist) { for (t = *pblist; t && t != b; prev = t, t = t->NextBinding) { /* Find the previous binding in the list. */ } if (t == NULL) { /* Binding not found */ return; } } if (prev) { /* middle of list */ prev->NextBinding = b->NextBinding; } else { /* must have been first one, set new start */ *pblist = b->NextBinding; } return; } /* To remove a binding from the global list (probably needs more processing * for mouse binding lines though, like when context is a title bar button). * Specify either button or keysym, depending on type. */ void RemoveBinding(Binding **pblist, Binding *b, Binding *prev) { UnlinkBinding(pblist, b, NULL); FreeBindingStruct(b); return; } /* * * Actually adds a new binding to a list (pblist) of existing bindings. * Specify either button or keysym/key_name, depending on type. * The parameters action and action2 are assumed to reside in malloced memory * that will be freed in RemoveBinding. The key_name is copied into private * memory and has to be freed by the caller. * */ int AddBinding( Display *dpy, Binding **pblist, binding_t type, STROKE_ARG(void *stroke) int button, KeySym keysym, char *key_name, int modifiers, int contexts, void *action, void *action2, char *windowName) { int i; int min; int max; int maxmods; int m; int mask; int count = 0; KeySym tkeysym; Binding *temp; /* ** Unfortunately a keycode can be bound to multiple keysyms and a ** keysym can bound to multiple keycodes. Thus we have to check every ** keycode with any single modifier. */ if (BIND_IS_KEY_BINDING(type)) { if (key_max == 0) { XDisplayKeycodes(dpy, &key_min, &key_max); } min=key_min; max=key_max; maxmods = 8; } else { min = button; max = button; maxmods = 0; } for (i = min; i <= max; i++) { unsigned int bound_mask = 0; /* If this is a mouse binding we'll fall through the for loop * (maxmods is zero) and the if condition is always true (type * is zero). Since min == max == button there is no loop at all * is case of a mouse binding. */ for (m = 0, tkeysym = XK_Left; m <= maxmods && tkeysym != NoSymbol; m++) { if (BIND_IS_MOUSE_BINDING(type) || STROKE_CODE(BIND_IS_STROKE_BINDING(type) ||) (tkeysym = XKeycodeToKeysym(dpy, i, m)) == keysym) { unsigned int add_modifiers = 0; unsigned int bind_mask = 1; unsigned int check_bound_mask = 0; switch (m) { case 0: /* key generates the key sym with no * modifiers depressed - bind it */ break; case 1: /* key generates the key sym with shift * depressed */ if (modifiers != AnyModifier && !(modifiers & ShiftMask)) { add_modifiers = ShiftMask; bind_mask = (1 << m); /* but don't bind it again if * already bound without * modifiers */ check_bound_mask = 1; } break; default: /* key generates the key sym with * undefined modifiers depressed - * let's make an educated guess at what * modifiers the user expected * based on the XFree86 default * configuration. */ mask = modifier_mapindex_to_mask[m - 1]; if (modifiers != AnyModifier && !(modifiers & mask) != mask) { add_modifiers = mask; bind_mask = (1 << m); /* but don't bind it again if * already bound without * modifiers */ check_bound_mask = 1; } break; } if ((bind_mask & bound_mask) || (check_bound_mask & bound_mask)) { /* already bound, break out */ break; } temp = *pblist; (*pblist) = (Binding *)safemalloc( sizeof(Binding)); (*pblist)->type = type; (*pblist)->Button_Key = i; STROKE_CODE((*pblist)->Stroke_Seq = (stroke) ? (void *)stripcpy((char *)stroke) : NULL); if (BIND_IS_KEY_BINDING(type) && key_name != NULL) { (*pblist)->key_name = stripcpy(key_name); } else { (*pblist)->key_name = NULL; } (*pblist)->Context = contexts; (*pblist)->Modifier = modifiers | add_modifiers; (*pblist)->Action = (action) ? stripcpy(action) : NULL; (*pblist)->Action2 = (action2) ? stripcpy(action2) : NULL; (*pblist)->windowName = windowName ? stripcpy(windowName) : NULL; (*pblist)->NextBinding = temp; bound_mask |= bind_mask; count++; } } } return count; } /* * replacesBinding() - does the new binding, b1, replace a current * binding, b2? */ static Bool replacesBinding(Binding *b1, Binding *b2) { if (b1->type != b2->type) { return False; } if (b1->Context != b2->Context) { return False; } if (b1->Modifier != b2->Modifier) { return False; } if (b1->Button_Key != b2->Button_Key) { return False; } /* definition: "global binding" => b->windowName == NULL * definition: "window-specific binding" => b->windowName != NULL */ if (b1->windowName && b2->windowName) { /* Both bindings are window-specific. The existing binding, b2, * is only replaced (by b1) if it applies to the same window */ if (strcmp(b1->windowName, b2->windowName) != 0) return False; } else if (b1->windowName || b2->windowName) { /* 1 binding is window-specific, the other is global - no need * to replace this binding. */ return False; } if (BIND_IS_KEY_BINDING(b1->type) || BIND_IS_MOUSE_BINDING(b1->type)) { return True; } if (1 STROKE_CODE(&& BIND_IS_STROKE_BINDING(b1->type) && strcmp(b1->Stroke_Seq, b2->Stroke_Seq) == 0)) { return True; } return False; } /* * Does exactly the opposite of AddBinding: It removes the bindings that * AddBinding would have added to the *pblist_src and collects them in the * *pblist_dest. This can be used to remove a binding completely from the * list. The bindings still have to be freed. */ void CollectBindingList( Display *dpy, Binding **pblist_src, Binding **pblist_dest, binding_t type, STROKE_ARG(void *stroke) int button, KeySym keysym, int modifiers, int contexts, char *windowName) { Binding *tmplist = NULL; Binding *btmp; Binding *bold; Binding *tmpprev; Binding *oldprev; /* generate a private list of bindings to be removed */ AddBinding( dpy, &tmplist, type, STROKE_ARG(stroke) button, keysym, NULL, modifiers, contexts, NULL, NULL, windowName); /* now find equivalent bindings in the given binding list and move * them to the new clist */ for (bold = *pblist_src, oldprev = NULL; bold != NULL; oldprev = bold, bold = bold->NextBinding) { for (btmp = tmplist, tmpprev = NULL; btmp != NULL; tmpprev = btmp, btmp = btmp->NextBinding) { if (replacesBinding(btmp, bold)) { /* move matched binding from src list to dest * list */ UnlinkBinding(pblist_src, bold, oldprev); bold->NextBinding = *pblist_dest; *pblist_dest = bold; /* throw away the tmp binding */ UnlinkBinding(&tmplist, btmp, tmpprev); FreeBindingStruct(btmp); /* stop searching for this binding */ break; } } } /* throw away the temporary list */ FreeBindingList(tmplist); return; } /* * bindingAppliesToWindow() * * The Key/Mouse/PointerKey syntax (optionally) allows a window name * (or class or resource) to be specified with the binding, denoting * which windows the binding can be invoked in. This function determines * if the binding actually applies to a window based on its * name/class/resource. */ static Bool does_binding_apply_to_window( Binding *binding, const XClassHint *win_class, const char *win_name) { /* If no window name is specified with the binding then that means * the binding applies to ALL windows. */ if (binding->windowName == NULL) { return True; } else if (win_class == NULL || win_name == NULL) { return False; } if (matchWildcards(binding->windowName, win_name) == True || matchWildcards(binding->windowName, win_class->res_name) == True || matchWildcards(binding->windowName, win_class->res_class) == True) { return True; } return False; } static Bool __compare_binding( Binding *b, STROKE_ARG(char *stroke) int button_keycode, unsigned int modifier, unsigned int used_modifiers, int Context, binding_t type, const XClassHint *win_class, const char *win_name) { if (b->type != type || !(b->Context & Context)) { return False; } if ((b->Modifier & used_modifiers) != modifier && b->Modifier != AnyModifier) { return False; } if (BIND_IS_MOUSE_BINDING(type) && (b->Button_Key != button_keycode && b->Button_Key != 0)) { return False; } else if (BIND_IS_KEY_BINDING(type) && b->Button_Key != button_keycode) { return False; } #ifdef HAVE_STROKE else if (BIND_IS_STROKE_BINDING(type) && ((strcmp(b->Stroke_Seq, stroke) != 0) || b->Button_Key != button_keycode)) { return False; } #endif if (!does_binding_apply_to_window(b, win_class, win_name)) { return False; } return True; } /* is_pass_through_action() - returns true if the action indicates that the * binding should be ignored by fvwm & passed through to the underlying * window. * Note: it is only meaningful to check for pass-thru actions on * window-specific bindings. */ Bool is_pass_through_action(const char *action) { /* action should never be NULL. */ return (strncmp(action, "--", 2) == 0); } /* Check if something is bound to a key or button press and return the action * to be executed or NULL if not. */ void *CheckBinding( Binding *blist, STROKE_ARG(char *stroke) int button_keycode, unsigned int modifier,unsigned int dead_modifiers, int Context, binding_t type, const XClassHint *win_class, const char *win_name) { Binding *b; unsigned int used_modifiers = ~dead_modifiers; void *action = NULL; modifier &= (used_modifiers & ALL_MODIFIERS); for (b = blist; b != NULL; b = b->NextBinding) { if (__compare_binding( b, STROKE_ARG(stroke) button_keycode, modifier, used_modifiers, Context, type, win_class, win_name) == True) { /* If this is a global binding, keep searching * in the hope of finding a window-specific binding. * If we don't find a win-specific binding, we use the * _first_ matching global binding we hit. */ if (action == NULL || b->windowName) { action = b->Action; if (b->windowName) { if (is_pass_through_action(action)) { action = NULL; } break; } } } } return action; } void *CheckTwoBindings( Bool *ret_is_second_binding, Binding *blist, STROKE_ARG(char *stroke) int button_keycode, unsigned int modifier,unsigned int dead_modifiers, int Context, binding_t type, const XClassHint *win_class, const char *win_name, int Context2, binding_t type2, const XClassHint *win_class2, const char *win_name2) { Binding *b; unsigned int used_modifiers = ~dead_modifiers; void *action = NULL; modifier &= (used_modifiers & ALL_MODIFIERS); for (b = blist; b != NULL; b = b->NextBinding) { if (__compare_binding( b, STROKE_ARG(stroke) button_keycode, modifier, used_modifiers, Context, type, win_class, win_name) == True) { if (action == NULL || b->windowName) { *ret_is_second_binding = False; action = b->Action; if (b->windowName) { if (is_pass_through_action(action)) action = NULL; break; } } } if (__compare_binding( b, STROKE_ARG(stroke) button_keycode, modifier, used_modifiers, Context2, type2, win_class2, win_name2) == True) { if (action == NULL || b->windowName) { *ret_is_second_binding = True; action = b->Action; if (b->windowName) { if (is_pass_through_action(action)) { action = NULL; } break; } } } } return action; } /* * GrabWindowKey - grab needed keys for the window for one binding * GrabAllWindowKeys - grab needed keys for the window for all bindings * in blist * GrabWindowButton - same for mouse buttons * GrabAllWindowButtons - same for mouse buttons * GrabAllWindowKeysAndButtons - both of the above * * Inputs: * w - the window to use (the frame window) * grab - 1 to grab, 0 to ungrab * binding - pointer to the bindinge to grab/ungrab * contexts - all context bits that shall receive bindings * dead_modifiers - modifiers to ignore for 'AnyModifier' * cursor - the mouse cursor to use when the pointer is on the * grabbed area (mouse bindings only) * */ void GrabWindowKey(Display *dpy, Window w, Binding *binding, unsigned int contexts, unsigned int dead_modifiers, Bool fGrab) { /* remove unnecessary bits from dead_modifiers */ dead_modifiers &= ~(binding->Modifier & dead_modifiers); dead_modifiers &= ALL_MODIFIERS; if((binding->Context & contexts) && BIND_IS_KEY_BINDING(binding->type)) { if (fGrab) { XGrabKey( dpy, binding->Button_Key, binding->Modifier, w, True, GrabModeAsync, GrabModeAsync); } else { XUngrabKey( dpy, binding->Button_Key, binding->Modifier, w); } if(binding->Modifier != AnyModifier && dead_modifiers != 0) { register unsigned int mods; register unsigned int max = dead_modifiers; register unsigned int living_modifiers = ~dead_modifiers; /* handle all bindings for the dead modifiers */ for (mods = 1; mods <= max; mods++) { /* Since mods starts with 1 we don't need to * test if mods contains a dead modifier. * Otherwise both, dead and living modifiers * would be zero ==> mods == 0 */ if (mods & living_modifiers) { continue; } if (fGrab) { XGrabKey( dpy, binding->Button_Key, mods|binding->Modifier, w, True, GrabModeAsync, GrabModeAsync); } else { XUngrabKey( dpy, binding->Button_Key, mods|binding->Modifier, w); } } } if (!is_grabbing_everything) { XSync(dpy, 0); } } return; } void GrabAllWindowKeys( Display *dpy, Window w, Binding *blist, unsigned int contexts, unsigned int dead_modifiers, Bool fGrab) { MyXGrabServer(dpy); is_grabbing_everything = True; for ( ; blist != NULL; blist = blist->NextBinding) { GrabWindowKey(dpy, w, blist, contexts, dead_modifiers, fGrab); } is_grabbing_everything = False; MyXUngrabServer(dpy); return; } void GrabWindowButton( Display *dpy, Window w, Binding *binding, unsigned int contexts, unsigned int dead_modifiers, Cursor cursor, Bool fGrab) { if (binding->Action == NULL) { return; } dead_modifiers &= ~(binding->Modifier & dead_modifiers); /* dje */ dead_modifiers &= ALL_MODIFIERS; if ((binding->Context & contexts) && ((BIND_IS_MOUSE_BINDING(binding->type) || (BIND_IS_STROKE_BINDING(binding->type) && binding->Button_Key !=0)))) { int bmin = 1; int bmax = NUMBER_OF_EXTENDED_MOUSE_BUTTONS; int button; if(binding->Button_Key >0) { bmin = bmax = binding->Button_Key; } for (button = bmin; button <= bmax; button++) { if (fGrab) { XGrabButton( dpy, button, binding->Modifier, w, True, ButtonPressMask | ButtonReleaseMask, GrabModeSync, GrabModeAsync, None, cursor); } else { XUngrabButton( dpy, button, binding->Modifier, w); } if (binding->Modifier != AnyModifier && dead_modifiers != 0) { register unsigned int mods; register unsigned int max = dead_modifiers; register unsigned int living_modifiers = ~dead_modifiers; /* handle all bindings for the dead modifiers */ for (mods = 1; mods <= max; mods++) { /* Since mods starts with 1 we don't * need to test if mods contains a * dead modifier. Otherwise both, dead * and living modifiers would be zero * ==> mods == 0 */ if (mods & living_modifiers) { continue; } if (fGrab) { XGrabButton( dpy, button, mods|binding->Modifier, w, True, ButtonPressMask | ButtonReleaseMask, GrabModeSync, GrabModeAsync, None, cursor); } else { XUngrabButton( dpy, button, mods|binding->Modifier, w); } } } if (!is_grabbing_everything) { XSync(dpy, 0); } } } return; } void GrabAllWindowButtons( Display *dpy, Window w, Binding *blist, unsigned int contexts, unsigned int dead_modifiers, Cursor cursor, Bool fGrab) { MyXGrabServer(dpy); is_grabbing_everything = True; for ( ; blist != NULL; blist = blist->NextBinding) { GrabWindowButton(dpy, w, blist, contexts, dead_modifiers, cursor, fGrab); } is_grabbing_everything = False; MyXUngrabServer(dpy); return; } void GrabAllWindowKeysAndButtons( Display *dpy, Window w, Binding *blist, unsigned int contexts, unsigned int dead_modifiers, Cursor cursor, Bool fGrab) { MyXGrabServer(dpy); is_grabbing_everything = True; for ( ; blist != NULL; blist = blist->NextBinding) { if (blist->Context & contexts) { if (BIND_IS_MOUSE_BINDING(blist->type) || BIND_IS_STROKE_BINDING(blist->type)) { GrabWindowButton( dpy, w, blist, contexts, dead_modifiers, cursor, fGrab); } else if (BIND_IS_KEY_BINDING(blist->type)) { GrabWindowKey( dpy, w, blist, contexts, dead_modifiers, fGrab); } } } is_grabbing_everything = False; MyXUngrabServer(dpy); return; } void GrabWindowKeyOrButton( Display *dpy, Window w, Binding *binding, unsigned int contexts, unsigned int dead_modifiers, Cursor cursor, Bool fGrab) { if (BIND_IS_MOUSE_BINDING(binding->type) || BIND_IS_STROKE_BINDING(binding->type)) { GrabWindowButton( dpy, w, binding, contexts, dead_modifiers, cursor, fGrab); } else if (BIND_IS_KEY_BINDING(binding->type)) { GrabWindowKey( dpy, w, binding, contexts, dead_modifiers, fGrab); } return; } /* * * Like XStringToKeysym, but allows some typos and does some additional * error checking. * */ KeySym FvwmStringToKeysym(Display *dpy, char *key) { KeySym keysym; char *s; if (!isalpha(*key)) { keysym = XStringToKeysym(key); } else { s = alloca(strlen(key) + 1); strcpy(s, key); /* always prefer the lower case spelling if it exists */ *s = tolower(*s); keysym = XStringToKeysym(s); if (keysym == NoSymbol) { *s = toupper(*s); keysym = XStringToKeysym(s); } } if (keysym == NoSymbol || XKeysymToKeycode(dpy, keysym) == 0) { return 0; } return keysym; }