Add reminder to investigate recursive commands for 2.6
[fvwm.git] / fvwm / bindings.c
blob4cde68067847409389baa981218ec1695955b6c5
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);
233 return;
237 return;
240 /* Parses a mouse or key binding */
241 static int ParseBinding(
242 Display *dpy, Binding **pblist, char *tline, binding_t type,
243 int *nr_left_buttons, int *nr_right_buttons,
244 unsigned short *buttons_grabbed, Bool is_silent)
246 char *action;
247 char context_string[20];
248 char modifier_string[20];
249 char *ptr;
250 char *token;
251 char key_string[201] = "";
252 char buffer[80];
253 char *window_name = NULL;
254 char *p;
255 int button = 0;
256 int n1 = 0;
257 int n2 = 0;
258 int n3 = 0;
259 int context;
260 int modifier;
261 int rc;
262 KeySym keysym = NoSymbol;
263 Bool is_unbind_request = False;
264 Bool is_pass_through = False;
265 Bool is_binding_removed = False;
266 Binding *b;
267 Binding *rmlist = NULL;
268 STROKE_CODE(char stroke[STROKE_MAX_SEQUENCE + 1] = "");
269 STROKE_CODE(int n4 = 0);
270 STROKE_CODE(int i);
272 /* tline points after the key word "Mouse" or "Key" */
273 token = p = PeekToken(tline, &ptr);
274 /* check to see if a window name has been specified. */
275 if (p == NULL)
277 fvwm_msg(
278 ERR, "ParseBinding", "empty %s binding, ignored\n",
279 tline);
281 return 0;
283 if (*p == '(')
285 /* A window name has been specified for the binding. */
286 sscanf(p + 1, "%79s", buffer);
287 p = buffer;
288 while (*p != ')')
290 if (*p == '\0')
292 if (!is_silent)
294 fvwm_msg(
295 ERR, "ParseBinding",
296 "Syntax error in line %s -"
297 " missing ')'", tline);
300 return 0;
302 ++p;
304 *p++ = '\0';
305 window_name = buffer;
306 if (*p != '\0')
308 if (!is_silent)
310 fvwm_msg(
311 ERR, "ParseBinding",
312 "Syntax error in line %s - trailing"
313 " text after specified window", tline);
316 return 0;
318 token = PeekToken(ptr, &ptr);
321 if (token != NULL)
323 if (BIND_IS_KEY_BINDING(type))
325 /* see len of key_string above */
326 n1 = sscanf(token,"%200s", key_string);
328 #ifdef HAVE_STROKE
329 else if (BIND_IS_STROKE_BINDING(type))
331 int num = 0;
332 int j;
334 n1 = 1;
335 i = 0;
336 if (token[0] == 'N' && token[1] != '\0')
338 num = 1;
340 j=i+num;
341 while (n1 && token[j] != '\0' &&
342 i < STROKE_MAX_SEQUENCE)
344 if (!isdigit(token[j]))
346 n1 = 0;
348 if (num)
350 /* Numeric pad to Telephone */
351 if ('7' <= token[j] && token[j] <= '9')
353 token[j] -= 6;
355 else if ('1' <= token[j] &&
356 token[j] <= '3')
358 token[j] += 6;
361 stroke[i] = token[j];
362 i++;
363 j=i+num;
365 stroke[i] = '\0';
366 if (strlen(token) > STROKE_MAX_SEQUENCE + num)
368 if (!is_silent)
370 fvwm_msg(
371 WARN, "ParseBinding",
372 "Too long stroke sequence in"
373 " line %s. Only %i elements"
374 " will be taken into"
375 " account.\n",
376 tline, STROKE_MAX_SEQUENCE);
380 #endif /* HAVE_STROKE */
381 else
383 n1 = sscanf(token, "%d", &button);
384 if (button < 0)
386 if (!is_silent)
388 fvwm_msg(
389 ERR, "ParseBinding",
390 "Illegal mouse button in line"
391 " %s", tline);
393 return 0;
395 if (button > NUMBER_OF_MOUSE_BUTTONS)
397 if (!is_silent)
399 fvwm_msg(
400 WARN, "ParseBinding",
401 "Got mouse button %d when the"
402 " maximum is %d.\n You can't"
403 " bind complex functions to"
404 " this button. To suppress"
405 " this warning, use:\n"
406 " Silent Mouse %s", button,
407 NUMBER_OF_MOUSE_BUTTONS,
408 tline);
414 #ifdef HAVE_STROKE
415 if (BIND_IS_STROKE_BINDING(type))
417 token = PeekToken(ptr, &ptr);
418 if (token != NULL)
420 n4 = sscanf(token,"%d", &button);
423 #endif /* HAVE_STROKE */
425 token = PeekToken(ptr, &ptr);
426 if (token != NULL)
428 n2 = sscanf(token, "%19s", context_string);
430 token = PeekToken(ptr, &action);
431 if (token != NULL)
433 n3 = sscanf(token, "%19s", modifier_string);
436 if (n1 != 1 || n2 != 1 || n3 != 1
437 STROKE_CODE(|| (BIND_IS_STROKE_BINDING(type) && n4 != 1)))
439 if (!is_silent)
441 fvwm_msg(
442 ERR, "ParseBinding", "Syntax error in line %s",
443 tline);
445 return 0;
448 if (wcontext_string_to_wcontext(
449 context_string, &context) && !is_silent)
451 fvwm_msg(
452 WARN, "ParseBinding", "Illegal context in line %s",
453 tline);
455 if (modifiers_string_to_modmask(modifier_string, &modifier) &&
456 !is_silent)
458 fvwm_msg(
459 WARN, "ParseBinding", "Illegal modifier in line %s",
460 tline);
463 if (BIND_IS_KEY_BINDING(type))
465 keysym = FvwmStringToKeysym(dpy, key_string);
466 /* Don't let a 0 keycode go through, since that means AnyKey
467 * to the XGrabKey call. */
468 if (keysym == 0)
470 if (!is_silent)
472 fvwm_msg(
473 ERR, "ParseBinding", "No such key: %s",
474 key_string);
476 return 0;
481 ** strip leading whitespace from action if necessary
483 while (*action && (*action == ' ' || *action == '\t'))
485 action++;
488 if (action)
490 is_pass_through = is_pass_through_action(action);
491 if (is_pass_through)
493 /* pass-through actions indicate that the event be
494 * allowed to pass through to the underlying window. */
495 if (window_name == NULL)
497 /* It doesn't make sense to have a pass-through
498 * action on global bindings. */
499 if (!is_silent)
501 fvwm_msg(
502 ERR, "ParseBinding",
503 "Invalid action for global "
504 "binding: %s", tline);
507 return 0;
511 /* see if it is an unbind request */
512 if (!action || (action[0] == '-' && !is_pass_through))
514 is_unbind_request = True;
517 /* short circuit menu bindings for now. */
518 if ((context & C_MENU) == C_MENU)
520 menu_binding(
521 dpy, type, button, keysym, context, modifier, action,
522 window_name);
523 /* ParseBinding returns the number of new bindings in pblist
524 * menu bindings does not add to pblist, and should return 0 */
526 return 0;
528 /* short circuit placement bindings for now. */
529 if ((context & C_PLACEMENT) == C_PLACEMENT)
531 placement_binding(button,keysym,modifier,action);
532 /* ParseBinding returns the number of new bindings in pblist
533 * placement bindings does not add to pblist, and should
534 * return 0 */
536 return 0;
539 ** Remove the "old" bindings if any
541 /* BEGIN remove */
542 CollectBindingList(
543 dpy, pblist, &rmlist, type, STROKE_ARG((void *)stroke)
544 button, keysym, modifier, context, window_name);
545 if (rmlist != NULL)
547 is_binding_removed = True;
548 if (is_unbind_request)
550 int rc = 0;
552 /* remove the grabs for the key for unbind
553 * requests */
554 for (b = rmlist; b != NULL; b = b->NextBinding)
556 /* release the grab */
557 rc |= activate_binding(b, type, False);
559 if (rc)
561 __rebind_global_key(
562 pblist, rmlist->Button_Key);
565 FreeBindingList(rmlist);
567 if (is_binding_removed)
569 int bcontext;
571 bcontext = bind_get_bound_button_contexts(
572 pblist, buttons_grabbed);
573 update_nr_buttons(
574 bcontext, nr_left_buttons, nr_right_buttons,
575 True);
577 /* return if it is an unbind request */
578 if (is_unbind_request)
580 return 0;
582 /* END remove */
584 update_nr_buttons(context, nr_left_buttons, nr_right_buttons, False);
585 if ((modifier & AnyModifier)&&(modifier&(~AnyModifier)))
587 fvwm_msg(
588 WARN, "ParseBinding", "Binding specified AnyModifier"
589 " and other modifers too. Excess modifiers are"
590 " ignored.");
591 modifier = AnyModifier;
593 if (
594 (BIND_IS_MOUSE_BINDING(type) ||
595 (BIND_IS_STROKE_BINDING(type) && button != 0)) &&
596 (context & (C_WINDOW | C_EWMH_DESKTOP)) &&
597 buttons_grabbed != NULL)
599 if (button == 0)
601 *buttons_grabbed |=
602 ((1 << NUMBER_OF_EXTENDED_MOUSE_BUTTONS) - 1);
604 else
606 *buttons_grabbed |= (1 << (button - 1));
609 rc = AddBinding(
610 dpy, pblist, type, STROKE_ARG((void *)stroke)
611 button, keysym, key_string, modifier, context, (void *)action,
612 NULL, window_name);
614 return rc;
617 static void binding_cmd(F_CMD_ARGS, binding_t type)
619 Binding *b;
620 int count;
621 unsigned short btg = Scr.buttons2grab;
623 count = ParseBinding(
624 dpy, &Scr.AllBindings, action, type, &Scr.nr_left_buttons,
625 &Scr.nr_right_buttons, &btg, Scr.flags.are_functions_silent);
626 if (btg != Scr.buttons2grab)
628 Scr.flags.do_need_window_update = 1;
629 Scr.flags.has_mouse_binding_changed = 1;
630 Scr.buttons2grab = btg;
632 for (
633 b = Scr.AllBindings; count > 0 && b != NULL;
634 count--, b = b->NextBinding)
636 activate_binding(b, type, True);
639 return;
642 void print_bindings(void)
644 Binding *b;
646 fprintf(stderr, "Current list of bindings:\n\n");
647 for (b = Scr.AllBindings; b != NULL; b = b->NextBinding)
649 switch (b->type)
651 case BIND_KEYPRESS:
652 fprintf(stderr, "Key");
653 break;
654 case BIND_PKEYPRESS:
655 fprintf(stderr, "PointerKey");
656 break;
657 case BIND_BUTTONPRESS:
658 case BIND_BUTTONRELEASE:
659 fprintf(stderr, "Mouse");
660 break;
661 case BIND_STROKE:
662 fprintf(stderr, "Stroke");
663 break;
664 default:
665 fvwm_msg(
666 ERR, "print_bindings",
667 "invalid binding type %d", b->type);
668 continue;
670 if (b->windowName != NULL)
672 fprintf(stderr, " (%s)", b->windowName);
674 switch (b->type)
676 case BIND_KEYPRESS:
677 case BIND_PKEYPRESS:
678 fprintf(stderr, "\t%s", b->key_name);
679 break;
680 case BIND_BUTTONPRESS:
681 case BIND_BUTTONRELEASE:
682 fprintf(stderr, "\t%d", b->Button_Key);
683 break;
684 case BIND_STROKE:
685 STROKE_CODE(
686 fprintf(
687 stderr, "\t%s\t%d",
688 (char *)b->Stroke_Seq, b->Button_Key));
689 break;
692 char *mod_string;
693 char *context_string;
695 mod_string = charmap_table_to_string(
696 MaskUsedModifiers(b->Modifier),key_modifiers);
697 context_string = charmap_table_to_string(
698 b->Context, win_contexts);
699 fprintf(
700 stderr, "\t%s\t%s\t%s\n", context_string,
701 mod_string, (char *)b->Action);
702 free(mod_string);
703 free(context_string);
707 return;
710 /* ---------------------------- interface functions ------------------------ */
712 /* Removes all unused modifiers from in_modifiers */
713 unsigned int MaskUsedModifiers(unsigned int in_modifiers)
715 return in_modifiers & ~mods_unused;
718 unsigned int GetUnusedModifiers(void)
720 return mods_unused;
723 /* ---------------------------- builtin commands --------------------------- */
725 void CMD_Key(F_CMD_ARGS)
727 binding_cmd(F_PASS_ARGS, BIND_KEYPRESS);
729 return;
732 void CMD_PointerKey(F_CMD_ARGS)
734 binding_cmd(F_PASS_ARGS, BIND_PKEYPRESS);
736 return;
739 void CMD_Mouse(F_CMD_ARGS)
741 binding_cmd(F_PASS_ARGS, BIND_BUTTONPRESS);
743 return;
746 #ifdef HAVE_STROKE
747 void CMD_Stroke(F_CMD_ARGS)
749 binding_cmd(F_PASS_ARGS, BIND_STROKE);
751 return;
753 #endif /* HAVE_STROKE */
755 /* Declares which X modifiers are actually locks and should be ignored when
756 * testing mouse/key binding modifiers. */
757 void CMD_IgnoreModifiers(F_CMD_ARGS)
759 char *token;
760 int mods_unused_old = mods_unused;
762 token = PeekToken(action, &action);
763 if (!token)
765 mods_unused = 0;
767 else if (StrEquals(token, "default"))
769 mods_unused = DEFAULT_MODS_UNUSED;
771 else if (modifiers_string_to_modmask(token, &mods_unused))
773 fvwm_msg(
774 ERR, "ignore_modifiers",
775 "illegal modifier in line %s\n", action);
777 if (mods_unused != mods_unused_old)
779 /* broadcast config to modules */
780 broadcast_ignore_modifiers();
783 return;