* detect and disallow circular transient-for hints
[fvwm.git] / fvwm / bindings.c
blob6d2da06f8f4a23386ab5ba9081599a35da7195ca
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 /* ---------------------------- included header files ---------------------- */
19 #include "config.h"
21 #include <stdio.h>
23 #include "libs/fvwmlib.h"
24 #include "libs/charmap.h"
25 #include "libs/wcontext.h"
26 #include "libs/modifiers.h"
27 #include "libs/Parse.h"
28 #include "libs/Strings.h"
29 #include "libs/defaults.h"
30 #include "fvwm.h"
31 #include "externs.h"
32 #include "cursor.h"
33 #include "functions.h"
34 #include "bindings.h"
35 #include "module_interface.h"
36 #include "misc.h"
37 #include "screen.h"
38 #include "focus.h"
39 #include "menubindings.h"
40 #include "move_resize.h" /* for placement_binding */
41 #ifdef HAVE_STROKE
42 #include "stroke.h"
43 #endif /* HAVE_STROKE */
45 /* ---------------------------- local definitions -------------------------- */
47 /* ---------------------------- local macros ------------------------------- */
49 /* ---------------------------- imports ------------------------------------ */
51 /* ---------------------------- included code files ------------------------ */
53 /* ---------------------------- local types -------------------------------- */
55 /* ---------------------------- forward declarations ----------------------- */
57 /* ---------------------------- local variables ---------------------------- */
59 static int mods_unused = DEFAULT_MODS_UNUSED;
61 /* ---------------------------- exported variables (globals) --------------- */
63 /* ---------------------------- local functions ---------------------------- */
65 static void update_nr_buttons(
66 int contexts, int *nr_left_buttons, int *nr_right_buttons, Bool do_set)
68 int i;
69 int l = *nr_left_buttons;
70 int r = *nr_right_buttons;
72 if (contexts == C_ALL)
74 return;
76 /* check for nr_left_buttons */
77 for (i = 0; i < NUMBER_OF_TITLE_BUTTONS; i += 2)
79 if ((contexts & (C_L1 << i)))
81 if (do_set || *nr_left_buttons <= i / 2)
83 *nr_left_buttons = i / 2 + 1;
87 /* check for nr_right_buttons */
88 for (i = 1; i < NUMBER_OF_TITLE_BUTTONS; i += 2)
90 if ((contexts & (C_L1 << i)))
92 if (do_set || *nr_right_buttons <= i / 2)
94 *nr_right_buttons = i / 2 + 1;
98 if (*nr_left_buttons != l || *nr_right_buttons != r)
100 Scr.flags.do_need_window_update = 1;
101 Scr.flags.has_nr_buttons_changed = 1;
104 return;
107 static int activate_binding(Binding *binding, binding_t type, Bool do_grab)
109 FvwmWindow *t;
110 Bool rc = 0;
112 if (binding == NULL)
114 return rc;
116 if (BIND_IS_PKEY_BINDING(type) || binding->Context == C_ALL)
118 /* necessary for key bindings that work over unfocused windows
120 GrabWindowKeyOrButton(
121 dpy, Scr.Root, binding,
122 C_WINDOW | C_DECOR | C_ROOT | C_ICON | C_EWMH_DESKTOP,
123 GetUnusedModifiers(), None, do_grab);
124 if (do_grab == False)
126 rc = 1;
129 if (do_grab == False && BIND_IS_KEY_BINDING(type) &&
130 (binding->Context & C_ROOT))
132 rc = 1;
134 if (fFvwmInStartup == True)
136 return rc;
139 /* grab keys immediately */
140 for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
142 if (!IS_EWMH_DESKTOP(FW_W(t)) &&
143 (binding->Context & (C_WINDOW | C_DECOR)) &&
144 BIND_IS_KEY_BINDING(type))
146 GrabWindowKey(
147 dpy, FW_W_FRAME(t), binding,
148 C_WINDOW | C_DECOR, GetUnusedModifiers(),
149 do_grab);
151 if (binding->Context & C_ICON)
153 if (FW_W_ICON_TITLE(t) != None)
155 GrabWindowKeyOrButton(
156 dpy, FW_W_ICON_TITLE(t), binding,
157 C_ICON, GetUnusedModifiers(), None,
158 do_grab);
160 if (FW_W_ICON_PIXMAP(t) != None)
162 GrabWindowKeyOrButton(
163 dpy, FW_W_ICON_PIXMAP(t), binding,
164 C_ICON, GetUnusedModifiers(), None,
165 do_grab);
168 if (IS_EWMH_DESKTOP(FW_W(t)) &&
169 (binding->Context & C_EWMH_DESKTOP))
171 GrabWindowKeyOrButton(
172 dpy, FW_W_PARENT(t), binding, C_EWMH_DESKTOP,
173 GetUnusedModifiers(), None, do_grab);
177 return rc;
180 static int bind_get_bound_button_contexts(
181 Binding **pblist, unsigned short *buttons_grabbed)
183 int bcontext = 0;
184 Binding *b;
186 if (buttons_grabbed)
188 *buttons_grabbed = 0;
190 for (b = *pblist; b != NULL; b = b->NextBinding)
192 if (!BIND_IS_MOUSE_BINDING(b->type) &&
193 !BIND_IS_STROKE_BINDING(b->type))
195 continue;
197 if ((b->Context & (C_WINDOW | C_EWMH_DESKTOP)) &&
198 !(BIND_IS_STROKE_BINDING(b->type) && b->Button_Key == 0) &&
199 buttons_grabbed != NULL)
201 if (b->Button_Key == 0)
203 *buttons_grabbed |=
204 ((1 <<
205 NUMBER_OF_EXTENDED_MOUSE_BUTTONS) -
208 else
210 *buttons_grabbed |= (1 << (b->Button_Key - 1));
213 if (b->Context != C_ALL && (b->Context & (C_LALL | C_RALL)))
215 bcontext |= b->Context;
219 return bcontext;
222 static void __rebind_global_key(Binding **pblist, int Button_Key)
224 Binding *b;
226 for (b = *pblist; b != NULL; b = b->NextBinding)
228 if (b->Button_Key == Button_Key &&
229 (BIND_IS_PKEY_BINDING(b->type) || b->Context == C_ALL))
231 activate_binding(b, b->type, True);
232 return;
236 return;
239 /* Parses a mouse or key binding */
240 static int ParseBinding(
241 Display *dpy, Binding **pblist, char *tline, binding_t type,
242 int *nr_left_buttons, int *nr_right_buttons,
243 unsigned short *buttons_grabbed, Bool is_silent)
245 char *action, context_string[20], modifier_string[20], *ptr, *token;
246 char key_string[201] = "", buffer[80], *windowName = NULL, *p;
247 int button = 0;
248 int n1=0,n2=0,n3=0;
249 int context;
250 int modifier;
251 int rc;
252 KeySym keysym = NoSymbol;
253 Bool is_unbind_request = False;
254 Bool is_pass_through = False;
255 Bool is_binding_removed = False;
256 Binding *b;
257 Binding *rmlist = NULL;
258 STROKE_CODE(char stroke[STROKE_MAX_SEQUENCE + 1] = "");
259 STROKE_CODE(int n4=0);
260 STROKE_CODE(int i);
262 /* tline points after the key word "Mouse" or "Key" */
263 token = p = PeekToken(tline, &ptr);
264 /* check to see if a window name has been specified. */
265 if (p == NULL)
267 fvwm_msg(
268 ERR, "ParseBinding", "empty %s binding, ignored\n",tline);
269 return 0;
271 if (*p == '(')
273 /* A window name has been specified for the binding. */
274 sscanf(p+1, "%79s",buffer);
275 p = buffer;
276 while (*p != ')')
278 if (*p == '\0')
280 if (!is_silent)
282 fvwm_msg(
283 ERR, "ParseBinding",
284 "Syntax error in line %s -"
285 " missing ')'", tline);
287 return 0;
289 ++p;
291 *p++ = '\0';
292 windowName = buffer;
293 if (*p != '\0')
295 if (!is_silent)
297 fvwm_msg(
298 ERR, "ParseBinding",
299 "Syntax error in line %s - trailing"
300 " text after specified window", tline);
302 return 0;
304 token = PeekToken(ptr, &ptr);
307 if (token != NULL)
309 if (BIND_IS_KEY_BINDING(type))
311 /* see len of key_string above */
312 n1 = sscanf(token,"%200s", key_string);
314 #ifdef HAVE_STROKE
315 else if (BIND_IS_STROKE_BINDING(type))
317 int num = 0;
318 int j;
320 n1 = 1;
321 i = 0;
322 if (token[0] == 'N' && token[1] != '\0')
324 num = 1;
326 j=i+num;
327 while (n1 && token[j] != '\0' &&
328 i < STROKE_MAX_SEQUENCE)
330 if (!isdigit(token[j]))
332 n1 = 0;
334 if (num)
336 /* Numeric pad to Telephone */
337 if ('7' <= token[j] && token[j] <= '9')
339 token[j] -= 6;
341 else if ('1' <= token[j] &&
342 token[j] <= '3')
344 token[j] += 6;
347 stroke[i] = token[j];
348 i++;
349 j=i+num;
351 stroke[i] = '\0';
352 if (strlen(token) > STROKE_MAX_SEQUENCE + num)
354 if (!is_silent)
356 fvwm_msg(
357 WARN, "ParseBinding",
358 "Too long stroke sequence in"
359 " line %s. Only %i elements"
360 " will be taken into"
361 " account.\n",
362 tline, STROKE_MAX_SEQUENCE);
366 #endif /* HAVE_STROKE */
367 else
369 n1 = sscanf(token, "%d", &button);
370 if (button < 0)
372 if (!is_silent)
374 fvwm_msg(
375 ERR, "ParseBinding",
376 "Illegal mouse button in line"
377 " %s", tline);
379 return 0;
381 if (button > NUMBER_OF_MOUSE_BUTTONS)
383 if (!is_silent)
385 fvwm_msg(
386 WARN, "ParseBinding",
387 "Got mouse button %d when the"
388 " maximum is %d.\n You can't"
389 " bind complex functions to"
390 " this button. To suppress"
391 " this warning, use:\n"
392 " Silent Mouse %s", button,
393 NUMBER_OF_MOUSE_BUTTONS, tline);
399 #ifdef HAVE_STROKE
400 if (BIND_IS_STROKE_BINDING(type))
402 token = PeekToken(ptr, &ptr);
403 if (token != NULL)
405 n4 = sscanf(token,"%d", &button);
408 #endif /* HAVE_STROKE */
410 token = PeekToken(ptr, &ptr);
411 if (token != NULL)
413 n2 = sscanf(token, "%19s", context_string);
415 token = PeekToken(ptr, &action);
416 if (token != NULL)
418 n3 = sscanf(token, "%19s", modifier_string);
421 if (n1 != 1 || n2 != 1 || n3 != 1
422 STROKE_CODE(|| (BIND_IS_STROKE_BINDING(type) && n4 != 1)))
424 if (!is_silent)
426 fvwm_msg(
427 ERR, "ParseBinding", "Syntax error in line %s",
428 tline);
430 return 0;
433 if (wcontext_string_to_wcontext(
434 context_string, &context) && !is_silent)
436 fvwm_msg(
437 WARN, "ParseBinding", "Illegal context in line %s",
438 tline);
440 if (modifiers_string_to_modmask(modifier_string, &modifier) &&
441 !is_silent)
443 fvwm_msg(
444 WARN, "ParseBinding", "Illegal modifier in line %s",
445 tline);
448 if (BIND_IS_KEY_BINDING(type))
450 keysym = FvwmStringToKeysym(dpy, key_string);
451 /* Don't let a 0 keycode go through, since that means AnyKey
452 * to the XGrabKey call. */
453 if (keysym == 0)
455 if (!is_silent)
457 fvwm_msg(
458 ERR, "ParseBinding", "No such key: %s",
459 key_string);
461 return 0;
466 ** strip leading whitespace from action if necessary
468 while (*action && (*action == ' ' || *action == '\t'))
470 action++;
473 if (action)
475 is_pass_through = is_pass_through_action(action);
476 if (is_pass_through)
478 /* pass-through actions indicate that the event be
479 * allowed to pass through to the underlying window. */
480 if (windowName == NULL)
482 /* It doesn't make sense to have a pass-through
483 * action on global bindings. */
484 if (!is_silent)
485 fvwm_msg(ERR, "ParseBinding",
486 "Illegal action for global "
487 "binding: %s", tline);
488 return 0;
492 /* see if it is an unbind request */
493 if (!action || (action[0] == '-' && !is_pass_through))
495 is_unbind_request = True;
498 /* short circuit menu bindings for now. */
499 if ((context & C_MENU) == C_MENU)
501 menu_binding(dpy, type, button, keysym, context,
502 modifier, action, windowName);
503 /* ParseBinding returns the number of new bindings in pblist
504 * menu bindings does not add to pblist, and should return 0 */
505 return 0;
507 /* short circuit placement bindings for now. */
508 if ((context & C_PLACEMENT) == C_PLACEMENT)
510 placement_binding(button,keysym,modifier,action);
511 /* ParseBinding returns the number of new bindings in pblist
512 * placement bindings does not add to pblist, and should
513 * return 0 */
514 return 0;
517 ** Remove the "old" bindings if any
519 /* BEGIN remove */
520 CollectBindingList(
521 dpy, pblist, &rmlist, type, STROKE_ARG((void *)stroke)
522 button, keysym, modifier, context, windowName);
523 if (rmlist != NULL)
525 is_binding_removed = True;
526 if (is_unbind_request)
528 int rc = 0;
530 /* remove the grabs for the key for unbind
531 * requests */
532 for (b = rmlist; b != NULL; b = b->NextBinding)
534 /* release the grab */
535 rc |= activate_binding(b, type, False);
537 if (rc)
539 __rebind_global_key(pblist, rmlist->Button_Key);
542 FreeBindingList(rmlist);
544 if (is_binding_removed)
546 int bcontext;
548 bcontext = bind_get_bound_button_contexts(
549 pblist, buttons_grabbed);
550 update_nr_buttons(
551 bcontext, nr_left_buttons, nr_right_buttons,
552 True);
554 /* return if it is an unbind request */
555 if (is_unbind_request)
557 return 0;
559 /* END remove */
561 update_nr_buttons(context, nr_left_buttons, nr_right_buttons, False);
562 if ((modifier & AnyModifier)&&(modifier&(~AnyModifier)))
564 fvwm_msg(
565 WARN, "ParseBinding", "Binding specified AnyModifier"
566 " and other modifers too. Excess modifiers are"
567 " ignored.");
568 modifier = AnyModifier;
570 if ((BIND_IS_MOUSE_BINDING(type) ||
571 (BIND_IS_STROKE_BINDING(type) && button != 0)) &&
572 (context & (C_WINDOW | C_EWMH_DESKTOP)) && buttons_grabbed != NULL)
574 if (button == 0)
576 *buttons_grabbed |=
577 ((1 << NUMBER_OF_EXTENDED_MOUSE_BUTTONS) - 1);
579 else
581 *buttons_grabbed |= (1 << (button - 1));
584 rc = AddBinding(
585 dpy, pblist, type, STROKE_ARG((void *)stroke)
586 button, keysym, key_string, modifier, context, (void *)action,
587 NULL, windowName);
589 return rc;
592 static void binding_cmd(F_CMD_ARGS, binding_t type)
594 Binding *b;
595 int count;
596 unsigned short btg = Scr.buttons2grab;
598 count = ParseBinding(
599 dpy, &Scr.AllBindings, action, type, &Scr.nr_left_buttons,
600 &Scr.nr_right_buttons, &btg, Scr.flags.are_functions_silent);
601 if (btg != Scr.buttons2grab)
603 Scr.flags.do_need_window_update = 1;
604 Scr.flags.has_mouse_binding_changed = 1;
605 Scr.buttons2grab = btg;
607 for (b = Scr.AllBindings; count > 0 && b != NULL;
608 count--, b = b->NextBinding)
610 activate_binding(b, type, True);
613 return;
616 /* ---------------------------- interface functions ------------------------ */
618 /* Removes all unused modifiers from in_modifiers */
619 unsigned int MaskUsedModifiers(unsigned int in_modifiers)
621 return in_modifiers & ~mods_unused;
624 unsigned int GetUnusedModifiers(void)
626 return mods_unused;
629 /* ---------------------------- builtin commands --------------------------- */
631 void CMD_Key(F_CMD_ARGS)
633 binding_cmd(F_PASS_ARGS, BIND_KEYPRESS);
635 return;
638 void CMD_PointerKey(F_CMD_ARGS)
640 binding_cmd(F_PASS_ARGS, BIND_PKEYPRESS);
642 return;
645 void CMD_Mouse(F_CMD_ARGS)
647 binding_cmd(F_PASS_ARGS, BIND_BUTTONPRESS);
649 return;
652 #ifdef HAVE_STROKE
653 void CMD_Stroke(F_CMD_ARGS)
655 binding_cmd(F_PASS_ARGS, BIND_STROKE);
657 return;
659 #endif /* HAVE_STROKE */
661 /* Declares which X modifiers are actually locks and should be ignored when
662 * testing mouse/key binding modifiers. */
663 void CMD_IgnoreModifiers(F_CMD_ARGS)
665 char *token;
666 int mods_unused_old = mods_unused;
668 token = PeekToken(action, &action);
669 if (!token)
671 mods_unused = 0;
673 else if (StrEquals(token, "default"))
675 mods_unused = DEFAULT_MODS_UNUSED;
677 else if (modifiers_string_to_modmask(token, &mods_unused))
679 fvwm_msg(
680 ERR, "ignore_modifiers",
681 "illegal modifier in line %s\n", action);
683 if (mods_unused != mods_unused_old)
685 /* broadcast config to modules */
686 broadcast_ignore_modifiers();
689 return;