/* -*-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, see:
*/
#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 = fvwm_KeycodeToKeysym(dpy, i, m, 0)) == 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;
}
/* returns 1 if binding b1 is identical to binding b2 (except action)
* returns 2 if b1 and b2 are identical except that b1 has a different window
* name than b2 and both are not NULL
* returns 0 otherwise
*/
static int compare_bindings(Binding *b1, Binding *b2)
{
if (b1->type != b2->type)
{
return 0;
}
if (b1->Context != b2->Context)
{
return 0;
}
if (b1->Modifier != b2->Modifier)
{
return 0;
}
if (b1->Button_Key != b2->Button_Key)
{
return 0;
}
/* 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 2;
}
}
else if (b1->windowName || b2->windowName)
{
/* 1 binding is window-specific, the other is global - no need
* to replace this binding. */
return 0;
}
if (BIND_IS_KEY_BINDING(b1->type) || BIND_IS_MOUSE_BINDING(b1->type))
{
return 1;
}
if (1 STROKE_CODE(&& BIND_IS_STROKE_BINDING(b1->type) &&
strcmp(b1->Stroke_Seq, b2->Stroke_Seq) == 0))
{
return 1;
}
return 0;
}
/*
* 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,
Bool *ret_are_similar_bindings_left, binding_t type,
STROKE_ARG(void *stroke) int button, KeySym keysym,
int modifiers, int contexts, char *windowName)
{
Binding *tmplist = NULL;
Binding *bold;
Binding *oldprev;
*ret_are_similar_bindings_left = False;
/* 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; )
{
Binding *btmp;
Binding *bfound;
bfound = NULL;
for (btmp = tmplist;
btmp != NULL && (
bfound == NULL ||
*ret_are_similar_bindings_left == False);
btmp = btmp->NextBinding)
{
int rc;
rc = compare_bindings(btmp, bold);
if (rc == 1)
{
bfound = btmp;
}
else if (rc == 2)
{
*ret_are_similar_bindings_left = True;
if (bfound != NULL)
{
break;
}
}
}
if (bfound != NULL)
{
Binding *next;
/* move matched binding from src list to dest list */
UnlinkBinding(pblist_src, bold, oldprev);
next = bold->NextBinding;
bold->NextBinding = *pblist_dest;
*pblist_dest = bold;
/* oldprev is unchanged */
bold = next;
}
else
{
oldprev = bold;
bold = bold->NextBinding;
}
}
/* 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;
}