Fix ExplainWindowPlacement when using "NoUSPosition" style.
[fvwm.git] / libs / Bindings.c
blobf024659e4a6b957ae7223f6af6db10e56dbee82e
1 /* -*-c-*- */
2 /* This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 #include "config.h"
19 #include <stdio.h>
21 #include <X11/keysym.h>
22 #include <X11/X.h>
23 #include <X11/Xlib.h>
25 #include "libs/fvwmlib.h"
26 #include "libs/Strings.h"
27 #include "libs/wild.h"
28 #include "libs/Grab.h"
29 #include "libs/Bindings.h"
30 #include "libs/charmap.h"
31 #include "libs/wcontext.h"
32 #include "libs/modifiers.h"
34 static Bool is_grabbing_everything = False;
36 static int key_min = 0;
37 static int key_max = 0;
39 /* Free the memory use by a binding. */
40 void FreeBindingStruct(Binding *b)
42 if (b->key_name)
44 free(b->key_name);
46 STROKE_CODE(
47 if (b->Stroke_Seq)
48 free(b->Stroke_Seq);
50 if (b->Action)
52 free(b->Action);
54 if (b->Action2)
56 free(b->Action2);
58 if (b->windowName)
60 free(b->windowName);
62 free(b);
64 return;
67 void FreeBindingList(Binding *b)
69 Binding *t;
71 for (; b != NULL; b = t)
73 t = b->NextBinding;
74 FreeBindingStruct(b);
77 return;
80 /* Unlink a binding b from a binding list pblist. The previous binding in the
81 * list (prev) must be given also. Pass NULL at the beginning of the list.
82 * The *pblist pointer may be modified by this function. */
83 static void UnlinkBinding(Binding **pblist, Binding *b, Binding *prev)
85 Binding *t;
87 if (!prev && b != *pblist)
89 for (t = *pblist; t && t != b; prev = t, t = t->NextBinding)
91 /* Find the previous binding in the list. */
93 if (t == NULL)
95 /* Binding not found */
96 return;
100 if (prev)
102 /* middle of list */
103 prev->NextBinding = b->NextBinding;
105 else
107 /* must have been first one, set new start */
108 *pblist = b->NextBinding;
111 return;
114 /* To remove a binding from the global list (probably needs more processing
115 * for mouse binding lines though, like when context is a title bar button).
116 * Specify either button or keysym, depending on type. */
117 void RemoveBinding(Binding **pblist, Binding *b, Binding *prev)
119 UnlinkBinding(pblist, b, NULL);
120 FreeBindingStruct(b);
122 return;
127 * Actually adds a new binding to a list (pblist) of existing bindings.
128 * Specify either button or keysym/key_name, depending on type.
129 * The parameters action and action2 are assumed to reside in malloced memory
130 * that will be freed in RemoveBinding. The key_name is copied into private
131 * memory and has to be freed by the caller.
134 int AddBinding(
135 Display *dpy, Binding **pblist, binding_t type,
136 STROKE_ARG(void *stroke)
137 int button, KeySym keysym, char *key_name, int modifiers, int contexts,
138 void *action, void *action2, char *windowName)
140 int i;
141 int min;
142 int max;
143 int maxmods;
144 int m;
145 int mask;
146 int count = 0;
147 KeySym tkeysym;
148 Binding *temp;
151 ** Unfortunately a keycode can be bound to multiple keysyms and a
152 ** keysym can bound to multiple keycodes. Thus we have to check every
153 ** keycode with any single modifier.
155 if (BIND_IS_KEY_BINDING(type))
157 if (key_max == 0)
159 XDisplayKeycodes(dpy, &key_min, &key_max);
161 min=key_min;
162 max=key_max;
163 maxmods = 8;
165 else
167 min = button;
168 max = button;
169 maxmods = 0;
171 for (i = min; i <= max; i++)
173 unsigned int bound_mask = 0;
175 /* If this is a mouse binding we'll fall through the for loop
176 * (maxmods is zero) and the if condition is always true (type
177 * is zero). Since min == max == button there is no loop at all
178 * is case of a mouse binding. */
179 for (m = 0, tkeysym = XK_Left;
180 m <= maxmods && tkeysym != NoSymbol; m++)
182 if (BIND_IS_MOUSE_BINDING(type) ||
183 STROKE_CODE(BIND_IS_STROKE_BINDING(type) ||)
184 (tkeysym = XKeycodeToKeysym(dpy, i, m)) == keysym)
186 unsigned int add_modifiers = 0;
187 unsigned int bind_mask = 1;
188 unsigned int check_bound_mask = 0;
190 switch (m)
192 case 0:
193 /* key generates the key sym with no
194 * modifiers depressed - bind it */
195 break;
196 case 1:
197 /* key generates the key sym with shift
198 * depressed */
199 if (modifiers != AnyModifier &&
200 !(modifiers & ShiftMask))
202 add_modifiers = ShiftMask;
203 bind_mask = (1 << m);
204 /* but don't bind it again if
205 * already bound without
206 * modifiers */
207 check_bound_mask = 1;
209 break;
210 default:
211 /* key generates the key sym with
212 * undefined modifiers depressed -
213 * let's make an educated guess at what
214 * modifiers the user expected
215 * based on the XFree86 default
216 * configuration. */
217 mask = modifier_mapindex_to_mask[m - 1];
218 if (modifiers != AnyModifier &&
219 !(modifiers & mask) != mask)
221 add_modifiers = mask;
222 bind_mask = (1 << m);
223 /* but don't bind it again if
224 * already bound without
225 * modifiers */
226 check_bound_mask = 1;
228 break;
230 if ((bind_mask & bound_mask) ||
231 (check_bound_mask & bound_mask))
233 /* already bound, break out */
234 break;
236 temp = *pblist;
237 (*pblist) = (Binding *)safemalloc(
238 sizeof(Binding));
239 (*pblist)->type = type;
240 (*pblist)->Button_Key = i;
241 STROKE_CODE((*pblist)->Stroke_Seq = (stroke) ?
242 (void *)stripcpy((char *)stroke) :
243 NULL);
244 if (BIND_IS_KEY_BINDING(type) &&
245 key_name != NULL)
247 (*pblist)->key_name =
248 stripcpy(key_name);
250 else
252 (*pblist)->key_name = NULL;
254 (*pblist)->Context = contexts;
255 (*pblist)->Modifier = modifiers | add_modifiers;
256 (*pblist)->Action =
257 (action) ? stripcpy(action) : NULL;
258 (*pblist)->Action2 =
259 (action2) ? stripcpy(action2) : NULL;
260 (*pblist)->windowName =
261 windowName ? stripcpy(windowName) :
262 NULL;
263 (*pblist)->NextBinding = temp;
264 bound_mask |= bind_mask;
265 count++;
269 return count;
273 * replacesBinding() - does the new binding, b1, replace a current
274 * binding, b2?
276 static Bool replacesBinding(Binding *b1, Binding *b2)
278 if (b1->type != b2->type)
280 return False;
282 if (b1->Context != b2->Context)
284 return False;
286 if (b1->Modifier != b2->Modifier)
288 return False;
290 if (b1->Button_Key != b2->Button_Key)
292 return False;
295 /* definition: "global binding" => b->windowName == NULL
296 * definition: "window-specific binding" => b->windowName != NULL
298 if (b1->windowName && b2->windowName)
300 /* Both bindings are window-specific. The existing binding, b2,
301 * is only replaced (by b1) if it applies to the same window */
302 if (strcmp(b1->windowName, b2->windowName) != 0)
303 return False;
305 else if (b1->windowName || b2->windowName)
307 /* 1 binding is window-specific, the other is global - no need
308 * to replace this binding. */
309 return False;
312 if (BIND_IS_KEY_BINDING(b1->type) || BIND_IS_MOUSE_BINDING(b1->type))
314 return True;
316 if (1 STROKE_CODE(&& BIND_IS_STROKE_BINDING(b1->type) &&
317 strcmp(b1->Stroke_Seq, b2->Stroke_Seq) == 0))
319 return True;
322 return False;
326 * Does exactly the opposite of AddBinding: It removes the bindings that
327 * AddBinding would have added to the *pblist_src and collects them in the
328 * *pblist_dest. This can be used to remove a binding completely from the
329 * list. The bindings still have to be freed.
331 void CollectBindingList(
332 Display *dpy, Binding **pblist_src, Binding **pblist_dest,
333 binding_t type, STROKE_ARG(void *stroke)
334 int button, KeySym keysym, int modifiers, int contexts,
335 char *windowName)
337 Binding *tmplist = NULL;
338 Binding *btmp;
339 Binding *bold;
340 Binding *tmpprev;
341 Binding *oldprev;
343 /* generate a private list of bindings to be removed */
344 AddBinding(
345 dpy, &tmplist, type, STROKE_ARG(stroke)
346 button, keysym, NULL, modifiers, contexts, NULL, NULL,
347 windowName);
348 /* now find equivalent bindings in the given binding list and move
349 * them to the new clist */
350 for (bold = *pblist_src, oldprev = NULL; bold != NULL;
351 oldprev = bold, bold = bold->NextBinding)
353 for (btmp = tmplist, tmpprev = NULL; btmp != NULL;
354 tmpprev = btmp, btmp = btmp->NextBinding)
356 if (replacesBinding(btmp, bold))
358 /* move matched binding from src list to dest
359 * list */
360 UnlinkBinding(pblist_src, bold, oldprev);
361 bold->NextBinding = *pblist_dest;
362 *pblist_dest = bold;
363 /* throw away the tmp binding */
364 UnlinkBinding(&tmplist, btmp, tmpprev);
365 FreeBindingStruct(btmp);
366 /* stop searching for this binding */
367 break;
371 /* throw away the temporary list */
372 FreeBindingList(tmplist);
374 return;
378 * bindingAppliesToWindow()
380 * The Key/Mouse/PointerKey syntax (optionally) allows a window name
381 * (or class or resource) to be specified with the binding, denoting
382 * which windows the binding can be invoked in. This function determines
383 * if the binding actually applies to a window based on its
384 * name/class/resource.
386 static Bool does_binding_apply_to_window(
387 Binding *binding, const XClassHint *win_class, const char *win_name)
389 /* If no window name is specified with the binding then that means
390 * the binding applies to ALL windows. */
391 if (binding->windowName == NULL)
393 return True;
395 else if (win_class == NULL || win_name == NULL)
397 return False;
399 if (matchWildcards(binding->windowName, win_name) == True ||
400 matchWildcards(binding->windowName, win_class->res_name) == True ||
401 matchWildcards(binding->windowName, win_class->res_class) == True)
403 return True;
406 return False;
409 static Bool __compare_binding(
410 Binding *b, STROKE_ARG(char *stroke)
411 int button_keycode, unsigned int modifier, unsigned int used_modifiers,
412 int Context, binding_t type, const XClassHint *win_class,
413 const char *win_name)
415 if (b->type != type || !(b->Context & Context))
417 return False;
419 if ((b->Modifier & used_modifiers) != modifier &&
420 b->Modifier != AnyModifier)
422 return False;
424 if (BIND_IS_MOUSE_BINDING(type) &&
425 (b->Button_Key != button_keycode && b->Button_Key != 0))
427 return False;
429 else if (BIND_IS_KEY_BINDING(type) && b->Button_Key != button_keycode)
431 return False;
433 #ifdef HAVE_STROKE
434 else if (BIND_IS_STROKE_BINDING(type) &&
435 ((strcmp(b->Stroke_Seq, stroke) != 0) ||
436 b->Button_Key != button_keycode))
438 return False;
440 #endif
441 if (!does_binding_apply_to_window(b, win_class, win_name))
443 return False;
446 return True;
449 /* is_pass_through_action() - returns true if the action indicates that the
450 * binding should be ignored by fvwm & passed through to the underlying
451 * window.
452 * Note: it is only meaningful to check for pass-thru actions on
453 * window-specific bindings. */
454 Bool is_pass_through_action(const char *action)
456 /* action should never be NULL. */
457 return (strncmp(action, "--", 2) == 0);
460 /* Check if something is bound to a key or button press and return the action
461 * to be executed or NULL if not. */
462 void *CheckBinding(
463 Binding *blist, STROKE_ARG(char *stroke)
464 int button_keycode, unsigned int modifier,unsigned int dead_modifiers,
465 int Context, binding_t type, const XClassHint *win_class,
466 const char *win_name)
468 Binding *b;
469 unsigned int used_modifiers = ~dead_modifiers;
470 void *action = NULL;
472 modifier &= (used_modifiers & ALL_MODIFIERS);
473 for (b = blist; b != NULL; b = b->NextBinding)
475 if (__compare_binding(
476 b, STROKE_ARG(stroke) button_keycode, modifier,
477 used_modifiers, Context, type, win_class,
478 win_name) == True)
480 /* If this is a global binding, keep searching <blist>
481 * in the hope of finding a window-specific binding.
482 * If we don't find a win-specific binding, we use the
483 * _first_ matching global binding we hit. */
484 if (action == NULL || b->windowName)
486 action = b->Action;
487 if (b->windowName)
489 if (is_pass_through_action(action))
491 action = NULL;
493 break;
499 return action;
502 void *CheckTwoBindings(
503 Bool *ret_is_second_binding, Binding *blist, STROKE_ARG(char *stroke)
504 int button_keycode, unsigned int modifier,unsigned int dead_modifiers,
505 int Context, binding_t type, const XClassHint *win_class,
506 const char *win_name, int Context2, binding_t type2,
507 const XClassHint *win_class2, const char *win_name2)
509 Binding *b;
510 unsigned int used_modifiers = ~dead_modifiers;
511 void *action = NULL;
513 modifier &= (used_modifiers & ALL_MODIFIERS);
514 for (b = blist; b != NULL; b = b->NextBinding)
516 if (__compare_binding(
517 b, STROKE_ARG(stroke) button_keycode, modifier,
518 used_modifiers, Context, type, win_class, win_name)
519 == True)
521 if (action == NULL || b->windowName)
523 *ret_is_second_binding = False;
524 action = b->Action;
525 if (b->windowName)
527 if (is_pass_through_action(action))
528 action = NULL;
529 break;
533 if (__compare_binding(
534 b, STROKE_ARG(stroke) button_keycode, modifier,
535 used_modifiers, Context2, type2, win_class2,
536 win_name2) == True)
538 if (action == NULL || b->windowName)
540 *ret_is_second_binding = True;
541 action = b->Action;
542 if (b->windowName)
544 if (is_pass_through_action(action))
546 action = NULL;
548 break;
554 return action;
558 * GrabWindowKey - grab needed keys for the window for one binding
559 * GrabAllWindowKeys - grab needed keys for the window for all bindings
560 * in blist
561 * GrabWindowButton - same for mouse buttons
562 * GrabAllWindowButtons - same for mouse buttons
563 * GrabAllWindowKeysAndButtons - both of the above
565 * Inputs:
566 * w - the window to use (the frame window)
567 * grab - 1 to grab, 0 to ungrab
568 * binding - pointer to the bindinge to grab/ungrab
569 * contexts - all context bits that shall receive bindings
570 * dead_modifiers - modifiers to ignore for 'AnyModifier'
571 * cursor - the mouse cursor to use when the pointer is on the
572 * grabbed area (mouse bindings only)
575 void GrabWindowKey(Display *dpy, Window w, Binding *binding,
576 unsigned int contexts, unsigned int dead_modifiers,
577 Bool fGrab)
579 /* remove unnecessary bits from dead_modifiers */
580 dead_modifiers &= ~(binding->Modifier & dead_modifiers);
581 dead_modifiers &= ALL_MODIFIERS;
583 if((binding->Context & contexts) && BIND_IS_KEY_BINDING(binding->type))
585 if (fGrab)
587 XGrabKey(
588 dpy, binding->Button_Key, binding->Modifier, w,
589 True, GrabModeAsync, GrabModeAsync);
591 else
593 XUngrabKey(
594 dpy, binding->Button_Key, binding->Modifier, w);
596 if(binding->Modifier != AnyModifier && dead_modifiers != 0)
598 register unsigned int mods;
599 register unsigned int max = dead_modifiers;
600 register unsigned int living_modifiers =
601 ~dead_modifiers;
603 /* handle all bindings for the dead modifiers */
604 for (mods = 1; mods <= max; mods++)
606 /* Since mods starts with 1 we don't need to
607 * test if mods contains a dead modifier.
608 * Otherwise both, dead and living modifiers
609 * would be zero ==> mods == 0 */
610 if (mods & living_modifiers)
612 continue;
614 if (fGrab)
616 XGrabKey(
617 dpy, binding->Button_Key,
618 mods|binding->Modifier, w,
619 True, GrabModeAsync,
620 GrabModeAsync);
622 else
624 XUngrabKey(
625 dpy, binding->Button_Key,
626 mods|binding->Modifier, w);
630 if (!is_grabbing_everything)
632 XSync(dpy, 0);
636 return;
639 void GrabAllWindowKeys(
640 Display *dpy, Window w, Binding *blist, unsigned int contexts,
641 unsigned int dead_modifiers, Bool fGrab)
643 MyXGrabServer(dpy);
644 is_grabbing_everything = True;
645 for ( ; blist != NULL; blist = blist->NextBinding)
647 GrabWindowKey(dpy, w, blist, contexts, dead_modifiers, fGrab);
649 is_grabbing_everything = False;
650 MyXUngrabServer(dpy);
652 return;
655 void GrabWindowButton(
656 Display *dpy, Window w, Binding *binding, unsigned int contexts,
657 unsigned int dead_modifiers, Cursor cursor, Bool fGrab)
659 if (binding->Action == NULL)
661 return;
664 dead_modifiers &= ~(binding->Modifier & dead_modifiers); /* dje */
665 dead_modifiers &= ALL_MODIFIERS;
667 if ((binding->Context & contexts) &&
668 ((BIND_IS_MOUSE_BINDING(binding->type) ||
669 (BIND_IS_STROKE_BINDING(binding->type) &&
670 binding->Button_Key !=0))))
672 int bmin = 1;
673 int bmax = NUMBER_OF_EXTENDED_MOUSE_BUTTONS;
674 int button;
676 if(binding->Button_Key >0)
678 bmin = bmax = binding->Button_Key;
680 for (button = bmin; button <= bmax; button++)
682 if (fGrab)
684 XGrabButton(
685 dpy, button, binding->Modifier, w,
686 True, ButtonPressMask |
687 ButtonReleaseMask, GrabModeSync,
688 GrabModeAsync, None, cursor);
690 else
692 XUngrabButton(
693 dpy, button, binding->Modifier, w);
695 if (binding->Modifier != AnyModifier &&
696 dead_modifiers != 0)
698 register unsigned int mods;
699 register unsigned int max = dead_modifiers;
700 register unsigned int living_modifiers =
701 ~dead_modifiers;
703 /* handle all bindings for the dead modifiers */
704 for (mods = 1; mods <= max; mods++)
706 /* Since mods starts with 1 we don't
707 * need to test if mods contains a
708 * dead modifier. Otherwise both, dead
709 * and living modifiers would be zero
710 * ==> mods == 0 */
711 if (mods & living_modifiers)
713 continue;
715 if (fGrab)
717 XGrabButton(
718 dpy, button,
719 mods|binding->Modifier,
720 w, True,
721 ButtonPressMask |
722 ButtonReleaseMask,
723 GrabModeSync,
724 GrabModeAsync, None,
725 cursor);
727 else
729 XUngrabButton(
730 dpy, button,
731 mods|binding->Modifier,
736 if (!is_grabbing_everything)
738 XSync(dpy, 0);
743 return;
746 void GrabAllWindowButtons(
747 Display *dpy, Window w, Binding *blist, unsigned int contexts,
748 unsigned int dead_modifiers, Cursor cursor, Bool fGrab)
750 MyXGrabServer(dpy);
751 is_grabbing_everything = True;
752 for ( ; blist != NULL; blist = blist->NextBinding)
754 GrabWindowButton(dpy, w, blist, contexts, dead_modifiers,
755 cursor, fGrab);
757 is_grabbing_everything = False;
758 MyXUngrabServer(dpy);
760 return;
763 void GrabAllWindowKeysAndButtons(
764 Display *dpy, Window w, Binding *blist, unsigned int contexts,
765 unsigned int dead_modifiers, Cursor cursor, Bool fGrab)
767 MyXGrabServer(dpy);
768 is_grabbing_everything = True;
769 for ( ; blist != NULL; blist = blist->NextBinding)
771 if (blist->Context & contexts)
773 if (BIND_IS_MOUSE_BINDING(blist->type) ||
774 BIND_IS_STROKE_BINDING(blist->type))
776 GrabWindowButton(
777 dpy, w, blist, contexts,
778 dead_modifiers, cursor, fGrab);
780 else if (BIND_IS_KEY_BINDING(blist->type))
782 GrabWindowKey(
783 dpy, w, blist, contexts,
784 dead_modifiers, fGrab);
788 is_grabbing_everything = False;
789 MyXUngrabServer(dpy);
791 return;
794 void GrabWindowKeyOrButton(
795 Display *dpy, Window w, Binding *binding, unsigned int contexts,
796 unsigned int dead_modifiers, Cursor cursor, Bool fGrab)
798 if (BIND_IS_MOUSE_BINDING(binding->type) ||
799 BIND_IS_STROKE_BINDING(binding->type))
801 GrabWindowButton(
802 dpy, w, binding, contexts, dead_modifiers, cursor,
803 fGrab);
805 else if (BIND_IS_KEY_BINDING(binding->type))
807 GrabWindowKey(
808 dpy, w, binding, contexts, dead_modifiers, fGrab);
811 return;
816 * Like XStringToKeysym, but allows some typos and does some additional
817 * error checking.
820 KeySym FvwmStringToKeysym(Display *dpy, char *key)
822 KeySym keysym;
823 char *s;
825 if (!isalpha(*key))
827 keysym = XStringToKeysym(key);
829 else
831 s = alloca(strlen(key) + 1);
832 strcpy(s, key);
833 /* always prefer the lower case spelling if it exists */
834 *s = tolower(*s);
835 keysym = XStringToKeysym(s);
836 if (keysym == NoSymbol)
838 *s = toupper(*s);
839 keysym = XStringToKeysym(s);
842 if (keysym == NoSymbol || XKeysymToKeycode(dpy, keysym) == 0)
844 return 0;
847 return keysym;