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.
6 *
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.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, see: <http://www.gnu.org/licenses/>
14 */
15
16 /* ---------------------------- included header files ---------------------- */
17
18 #include "config.h"
19
20 #include <stdio.h>
21
22 #include "libs/fvwmlib.h"
23 #include "libs/charmap.h"
24 #include "libs/wcontext.h"
25 #include "libs/modifiers.h"
26 #include "libs/Parse.h"
27 #include "libs/Strings.h"
28 #include "libs/defaults.h"
29 #include "fvwm.h"
30 #include "externs.h"
31 #include "cursor.h"
32 #include "functions.h"
33 #include "bindings.h"
34 #include "module_interface.h"
35 #include "misc.h"
36 #include "screen.h"
37 #include "focus.h"
38 #include "menubindings.h"
39 #include "move_resize.h" /* for placement_binding */
40 #ifdef HAVE_STROKE
41 #include "stroke.h"
42 #endif /* HAVE_STROKE */
43
44 /* ---------------------------- local definitions -------------------------- */
45
46 /* ---------------------------- local macros ------------------------------- */
47
48 /* ---------------------------- imports ------------------------------------ */
49
50 /* ---------------------------- included code files ------------------------ */
51
52 /* ---------------------------- local types -------------------------------- */
53
54 /* ---------------------------- forward declarations ----------------------- */
55
56 /* ---------------------------- local variables ---------------------------- */
57
58 static int mods_unused = DEFAULT_MODS_UNUSED;
59
60 /* ---------------------------- exported variables (globals) --------------- */
61
62 /* ---------------------------- local functions ---------------------------- */
63
update_nr_buttons(int contexts,int * nr_left_buttons,int * nr_right_buttons,Bool do_set)64 static void update_nr_buttons(
65 int contexts, int *nr_left_buttons, int *nr_right_buttons, Bool do_set)
66 {
67 int i;
68 int l = *nr_left_buttons;
69 int r = *nr_right_buttons;
70
71 if (contexts == C_ALL)
72 {
73 return;
74 }
75 /* check for nr_left_buttons */
76 for (i = 0; i < NUMBER_OF_TITLE_BUTTONS; i += 2)
77 {
78 if ((contexts & (C_L1 << i)))
79 {
80 if (do_set || *nr_left_buttons <= i / 2)
81 {
82 *nr_left_buttons = i / 2 + 1;
83 }
84 }
85 }
86 /* check for nr_right_buttons */
87 for (i = 1; i < NUMBER_OF_TITLE_BUTTONS; i += 2)
88 {
89 if ((contexts & (C_L1 << i)))
90 {
91 if (do_set || *nr_right_buttons <= i / 2)
92 {
93 *nr_right_buttons = i / 2 + 1;
94 }
95 }
96 }
97 if (*nr_left_buttons != l || *nr_right_buttons != r)
98 {
99 Scr.flags.do_need_window_update = 1;
100 Scr.flags.has_nr_buttons_changed = 1;
101 }
102
103 return;
104 }
105
activate_binding(Binding * binding,binding_t type,Bool do_grab)106 static int activate_binding(Binding *binding, binding_t type, Bool do_grab)
107 {
108 FvwmWindow *t;
109 Bool rc = 0;
110
111 if (binding == NULL)
112 {
113 return rc;
114 }
115 if (BIND_IS_PKEY_BINDING(type) || binding->Context == C_ALL)
116 {
117 /* necessary for key bindings that work over unfocused windows
118 */
119 GrabWindowKeyOrButton(
120 dpy, Scr.Root, binding,
121 C_WINDOW | C_DECOR | C_ROOT | C_ICON | C_EWMH_DESKTOP,
122 GetUnusedModifiers(), None, do_grab);
123 if (do_grab == False)
124 {
125 rc = 1;
126 }
127 }
128 if (do_grab == False && BIND_IS_KEY_BINDING(type) &&
129 (binding->Context & C_ROOT))
130 {
131 rc = 1;
132 }
133 if (fFvwmInStartup == True)
134 {
135 return rc;
136 }
137
138 /* grab keys immediately */
139 for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
140 {
141 if (!IS_EWMH_DESKTOP(FW_W(t)) &&
142 (binding->Context & (C_WINDOW | C_DECOR)) &&
143 BIND_IS_KEY_BINDING(type))
144 {
145 GrabWindowKey(
146 dpy, FW_W_FRAME(t), binding,
147 C_WINDOW | C_DECOR, GetUnusedModifiers(),
148 do_grab);
149 }
150 if (binding->Context & C_ICON)
151 {
152 if (FW_W_ICON_TITLE(t) != None)
153 {
154 GrabWindowKeyOrButton(
155 dpy, FW_W_ICON_TITLE(t), binding,
156 C_ICON, GetUnusedModifiers(), None,
157 do_grab);
158 }
159 if (FW_W_ICON_PIXMAP(t) != None)
160 {
161 GrabWindowKeyOrButton(
162 dpy, FW_W_ICON_PIXMAP(t), binding,
163 C_ICON, GetUnusedModifiers(), None,
164 do_grab);
165 }
166 }
167 if (IS_EWMH_DESKTOP(FW_W(t)) &&
168 (binding->Context & C_EWMH_DESKTOP))
169 {
170 GrabWindowKeyOrButton(
171 dpy, FW_W_PARENT(t), binding, C_EWMH_DESKTOP,
172 GetUnusedModifiers(), None, do_grab);
173 }
174 }
175
176 return rc;
177 }
178
bind_get_bound_button_contexts(Binding ** pblist,unsigned short * buttons_grabbed)179 static int bind_get_bound_button_contexts(
180 Binding **pblist, unsigned short *buttons_grabbed)
181 {
182 int bcontext = 0;
183 Binding *b;
184
185 if (buttons_grabbed)
186 {
187 *buttons_grabbed = 0;
188 }
189 for (b = *pblist; b != NULL; b = b->NextBinding)
190 {
191 if (!BIND_IS_MOUSE_BINDING(b->type) &&
192 !BIND_IS_STROKE_BINDING(b->type))
193 {
194 continue;
195 }
196 if ((b->Context & (C_WINDOW | C_EWMH_DESKTOP)) &&
197 !(BIND_IS_STROKE_BINDING(b->type) && b->Button_Key == 0) &&
198 buttons_grabbed != NULL)
199 {
200 if (b->Button_Key == 0)
201 {
202 *buttons_grabbed |=
203 ((1 <<
204 NUMBER_OF_EXTENDED_MOUSE_BUTTONS) -
205 1);
206 }
207 else
208 {
209 *buttons_grabbed |= (1 << (b->Button_Key - 1));
210 }
211 }
212 if (b->Context != C_ALL && (b->Context & (C_LALL | C_RALL)))
213 {
214 bcontext |= b->Context;
215 }
216 }
217
218 return bcontext;
219 }
220
__rebind_global_key(Binding ** pblist,int Button_Key)221 static void __rebind_global_key(Binding **pblist, int Button_Key)
222 {
223 Binding *b;
224
225 for (b = *pblist; b != NULL; b = b->NextBinding)
226 {
227 if (b->Button_Key == Button_Key &&
228 (BIND_IS_PKEY_BINDING(b->type) || b->Context == C_ALL))
229 {
230 activate_binding(b, b->type, True);
231
232 return;
233 }
234 }
235
236 return;
237 }
238
239 /* Parses a mouse or key binding */
ParseBinding(Display * dpy,Binding ** pblist,char * tline,binding_t type,int * nr_left_buttons,int * nr_right_buttons,unsigned short * buttons_grabbed,Bool is_silent)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)
244 {
245 char *action;
246 char context_string[20];
247 char modifier_string[20];
248 char *ptr;
249 char *token;
250 char key_string[201] = "";
251 char buffer[80];
252 char *window_name = NULL;
253 char *p;
254 int button = 0;
255 int n1 = 0;
256 int n2 = 0;
257 int n3 = 0;
258 int context;
259 int modifier;
260 int rc;
261 KeySym keysym = NoSymbol;
262 Bool is_unbind_request = False;
263 Bool is_pass_through = False;
264 Bool are_similar_bindings_left;
265 Binding *b;
266 Binding *rmlist = NULL;
267 STROKE_CODE(char stroke[STROKE_MAX_SEQUENCE + 1] = "");
268 STROKE_CODE(int n4 = 0);
269 STROKE_CODE(int i);
270
271 /* tline points after the key word "Mouse" or "Key" */
272 token = p = PeekToken(tline, &ptr);
273 /* check to see if a window name has been specified. */
274 if (p == NULL)
275 {
276 fvwm_msg(
277 ERR, "ParseBinding", "empty %s binding, ignored\n",
278 tline);
279
280 return 0;
281 }
282 if (*p == '(')
283 {
284 /* A window name has been specified for the binding. */
285 sscanf(p + 1, "%79s", buffer);
286 p = buffer;
287 while (*p != ')')
288 {
289 if (*p == '\0')
290 {
291 if (!is_silent)
292 {
293 fvwm_msg(
294 ERR, "ParseBinding",
295 "Syntax error in line %s -"
296 " missing ')'", tline);
297 }
298
299 return 0;
300 }
301 ++p;
302 }
303 *p++ = '\0';
304 window_name = buffer;
305 if (*p != '\0')
306 {
307 if (!is_silent)
308 {
309 fvwm_msg(
310 ERR, "ParseBinding",
311 "Syntax error in line %s - trailing"
312 " text after specified window", tline);
313 }
314
315 return 0;
316 }
317 token = PeekToken(ptr, &ptr);
318 }
319
320 if (token != NULL)
321 {
322 if (BIND_IS_KEY_BINDING(type))
323 {
324 /* see len of key_string above */
325 n1 = sscanf(token,"%200s", key_string);
326 }
327 #ifdef HAVE_STROKE
328 else if (BIND_IS_STROKE_BINDING(type))
329 {
330 int num = 0;
331 int j;
332
333 n1 = 1;
334 i = 0;
335 if (token[0] == 'N' && token[1] != '\0')
336 {
337 num = 1;
338 }
339 j=i+num;
340 while (n1 && token[j] != '\0' &&
341 i < STROKE_MAX_SEQUENCE)
342 {
343 if (!isdigit(token[j]))
344 {
345 n1 = 0;
346 }
347 if (num)
348 {
349 /* Numeric pad to Telephone */
350 if ('7' <= token[j] && token[j] <= '9')
351 {
352 token[j] -= 6;
353 }
354 else if ('1' <= token[j] &&
355 token[j] <= '3')
356 {
357 token[j] += 6;
358 }
359 }
360 stroke[i] = token[j];
361 i++;
362 j=i+num;
363 }
364 stroke[i] = '\0';
365 if (strlen(token) > STROKE_MAX_SEQUENCE + num)
366 {
367 if (!is_silent)
368 {
369 fvwm_msg(
370 WARN, "ParseBinding",
371 "Too long stroke sequence in"
372 " line %s. Only %i elements"
373 " will be taken into"
374 " account.\n",
375 tline, STROKE_MAX_SEQUENCE);
376 }
377 }
378 }
379 #endif /* HAVE_STROKE */
380 else
381 {
382 n1 = sscanf(token, "%d", &button);
383 if (button < 0)
384 {
385 if (!is_silent)
386 {
387 fvwm_msg(
388 ERR, "ParseBinding",
389 "Illegal mouse button in line"
390 " %s", tline);
391 }
392 return 0;
393 }
394 if (button > NUMBER_OF_MOUSE_BUTTONS)
395 {
396 if (!is_silent)
397 {
398 fvwm_msg(
399 WARN, "ParseBinding",
400 "Got mouse button %d when the"
401 " maximum is %d.\n You can't"
402 " bind complex functions to"
403 " this button. To suppress"
404 " this warning, use:\n"
405 " Silent Mouse %s", button,
406 NUMBER_OF_MOUSE_BUTTONS,
407 tline);
408 }
409 }
410 }
411 }
412
413 #ifdef HAVE_STROKE
414 if (BIND_IS_STROKE_BINDING(type))
415 {
416 token = PeekToken(ptr, &ptr);
417 if (token != NULL)
418 {
419 n4 = sscanf(token,"%d", &button);
420 }
421 }
422 #endif /* HAVE_STROKE */
423
424 token = PeekToken(ptr, &ptr);
425 if (token != NULL)
426 {
427 n2 = sscanf(token, "%19s", context_string);
428 }
429 token = PeekToken(ptr, &action);
430 if (token != NULL)
431 {
432 n3 = sscanf(token, "%19s", modifier_string);
433 }
434
435 if (n1 != 1 || n2 != 1 || n3 != 1
436 STROKE_CODE(|| (BIND_IS_STROKE_BINDING(type) && n4 != 1)))
437 {
438 if (!is_silent)
439 {
440 fvwm_msg(
441 ERR, "ParseBinding", "Syntax error in line %s",
442 tline);
443 }
444 return 0;
445 }
446
447 if (wcontext_string_to_wcontext(
448 context_string, &context) && !is_silent)
449 {
450 fvwm_msg(
451 WARN, "ParseBinding", "Illegal context in line %s",
452 tline);
453 }
454 if (modifiers_string_to_modmask(modifier_string, &modifier) &&
455 !is_silent)
456 {
457 fvwm_msg(
458 WARN, "ParseBinding", "Illegal modifier in line %s",
459 tline);
460 }
461
462 if (BIND_IS_KEY_BINDING(type))
463 {
464 keysym = FvwmStringToKeysym(dpy, key_string);
465 /* Don't let a 0 keycode go through, since that means AnyKey
466 * to the XGrabKey call. */
467 if (keysym == 0)
468 {
469 if (!is_silent)
470 {
471 fvwm_msg(
472 ERR, "ParseBinding", "No such key: %s",
473 key_string);
474 }
475 return 0;
476 }
477 }
478
479 if (action != NULL)
480 {
481 action = SkipSpaces(action, NULL, 0);
482 }
483 if (
484 action == NULL || *action == 0 ||
485 (action[0] == '-' && !is_pass_through))
486 {
487 is_unbind_request = True;
488 }
489 else
490 {
491 is_pass_through = is_pass_through_action(action);
492 if (is_pass_through)
493 {
494 /* pass-through actions indicate that the event be
495 * allowed to pass through to the underlying window. */
496 if (window_name == NULL)
497 {
498 /* It doesn't make sense to have a pass-through
499 * action on global bindings. */
500 if (!is_silent)
501 {
502 fvwm_msg(
503 ERR, "ParseBinding",
504 "Invalid action for global "
505 "binding: %s", tline);
506 }
507
508 return 0;
509 }
510 }
511 }
512
513 /* short circuit menu bindings for now. */
514 if ((context & C_MENU) == C_MENU)
515 {
516 menu_binding(
517 dpy, type, button, keysym, context, modifier, action,
518 window_name);
519 /* ParseBinding returns the number of new bindings in pblist
520 * menu bindings does not add to pblist, and should return 0 */
521
522 return 0;
523 }
524 /* short circuit placement bindings for now. */
525 if ((context & C_PLACEMENT) == C_PLACEMENT)
526 {
527 placement_binding(button,keysym,modifier,action);
528 /* ParseBinding returns the number of new bindings in pblist
529 * placement bindings does not add to pblist, and should
530 * return 0 */
531
532 return 0;
533 }
534 /*
535 ** Remove the "old" bindings if any
536 */
537 /* BEGIN remove */
538 CollectBindingList(
539 dpy, pblist, &rmlist, &are_similar_bindings_left, type,
540 STROKE_ARG((void *)stroke)
541 button, keysym, modifier, context, window_name);
542 if (rmlist != NULL)
543 {
544 int bcontext;
545
546 if (is_unbind_request && are_similar_bindings_left == False)
547 {
548 int rc = 0;
549
550 for (b = rmlist; b != NULL; b = b->NextBinding)
551 {
552 /* release the grab */
553 rc |= activate_binding(b, type, False);
554 }
555 if (rc)
556 {
557 __rebind_global_key(
558 pblist, rmlist->Button_Key);
559 }
560 }
561 FreeBindingList(rmlist);
562 bcontext = bind_get_bound_button_contexts(
563 pblist, buttons_grabbed);
564 update_nr_buttons(
565 bcontext, nr_left_buttons, nr_right_buttons,
566 True);
567 }
568 /* return if it is an unbind request */
569 if (is_unbind_request)
570 {
571 return 0;
572 }
573 /* END remove */
574
575 update_nr_buttons(context, nr_left_buttons, nr_right_buttons, False);
576 if ((modifier & AnyModifier)&&(modifier&(~AnyModifier)))
577 {
578 fvwm_msg(
579 WARN, "ParseBinding", "Binding specified AnyModifier"
580 " and other modifers too. Excess modifiers are"
581 " ignored.");
582 modifier = AnyModifier;
583 }
584 if (
585 (BIND_IS_MOUSE_BINDING(type) ||
586 (BIND_IS_STROKE_BINDING(type) && button != 0)) &&
587 (context & (C_WINDOW | C_EWMH_DESKTOP)) &&
588 buttons_grabbed != NULL)
589 {
590 if (button == 0)
591 {
592 *buttons_grabbed |=
593 ((1 << NUMBER_OF_EXTENDED_MOUSE_BUTTONS) - 1);
594 }
595 else
596 {
597 *buttons_grabbed |= (1 << (button - 1));
598 }
599 }
600 rc = AddBinding(
601 dpy, pblist, type, STROKE_ARG((void *)stroke)
602 button, keysym, key_string, modifier, context, (void *)action,
603 NULL, window_name);
604
605 return rc;
606 }
607
binding_cmd(F_CMD_ARGS,binding_t type)608 static void binding_cmd(F_CMD_ARGS, binding_t type)
609 {
610 Binding *b;
611 int count;
612 unsigned short btg = Scr.buttons2grab;
613
614 count = ParseBinding(
615 dpy, &Scr.AllBindings, action, type, &Scr.nr_left_buttons,
616 &Scr.nr_right_buttons, &btg, Scr.flags.are_functions_silent);
617 if (btg != Scr.buttons2grab)
618 {
619 Scr.flags.do_need_window_update = 1;
620 Scr.flags.has_mouse_binding_changed = 1;
621 Scr.buttons2grab = btg;
622 }
623 for (
624 b = Scr.AllBindings; count > 0 && b != NULL;
625 count--, b = b->NextBinding)
626 {
627 activate_binding(b, type, True);
628 }
629
630 return;
631 }
632
print_bindings(void)633 void print_bindings(void)
634 {
635 Binding *b;
636
637 fprintf(stderr, "Current list of bindings:\n\n");
638 for (b = Scr.AllBindings; b != NULL; b = b->NextBinding)
639 {
640 switch (b->type)
641 {
642 case BIND_KEYPRESS:
643 fprintf(stderr, "Key");
644 break;
645 case BIND_PKEYPRESS:
646 fprintf(stderr, "PointerKey");
647 break;
648 case BIND_BUTTONPRESS:
649 case BIND_BUTTONRELEASE:
650 fprintf(stderr, "Mouse");
651 break;
652 case BIND_STROKE:
653 fprintf(stderr, "Stroke");
654 break;
655 default:
656 fvwm_msg(
657 ERR, "print_bindings",
658 "invalid binding type %d", b->type);
659 continue;
660 }
661 if (b->windowName != NULL)
662 {
663 fprintf(stderr, " (%s)", b->windowName);
664 }
665 switch (b->type)
666 {
667 case BIND_KEYPRESS:
668 case BIND_PKEYPRESS:
669 fprintf(stderr, "\t%s", b->key_name);
670 break;
671 case BIND_BUTTONPRESS:
672 case BIND_BUTTONRELEASE:
673 fprintf(stderr, "\t%d", b->Button_Key);
674 break;
675 case BIND_STROKE:
676 STROKE_CODE(
677 fprintf(
678 stderr, "\t%s\t%d",
679 (char *)b->Stroke_Seq, b->Button_Key));
680 break;
681 }
682 {
683 char *mod_string;
684 char *context_string;
685
686 mod_string = charmap_table_to_string(
687 MaskUsedModifiers(b->Modifier),key_modifiers);
688 context_string = charmap_table_to_string(
689 b->Context, win_contexts);
690 fprintf(
691 stderr, "\t%s\t%s\t%s\n", context_string,
692 mod_string, (char *)b->Action);
693 free(mod_string);
694 free(context_string);
695 }
696 }
697
698 return;
699 }
700
701 /* ---------------------------- interface functions ------------------------ */
702
703 /* Removes all unused modifiers from in_modifiers */
MaskUsedModifiers(unsigned int in_modifiers)704 unsigned int MaskUsedModifiers(unsigned int in_modifiers)
705 {
706 return in_modifiers & ~mods_unused;
707 }
708
GetUnusedModifiers(void)709 unsigned int GetUnusedModifiers(void)
710 {
711 return mods_unused;
712 }
713
714 /* ---------------------------- builtin commands --------------------------- */
715
CMD_Key(F_CMD_ARGS)716 void CMD_Key(F_CMD_ARGS)
717 {
718 binding_cmd(F_PASS_ARGS, BIND_KEYPRESS);
719
720 return;
721 }
722
CMD_PointerKey(F_CMD_ARGS)723 void CMD_PointerKey(F_CMD_ARGS)
724 {
725 binding_cmd(F_PASS_ARGS, BIND_PKEYPRESS);
726
727 return;
728 }
729
CMD_Mouse(F_CMD_ARGS)730 void CMD_Mouse(F_CMD_ARGS)
731 {
732 binding_cmd(F_PASS_ARGS, BIND_BUTTONPRESS);
733
734 return;
735 }
736
737 #ifdef HAVE_STROKE
CMD_Stroke(F_CMD_ARGS)738 void CMD_Stroke(F_CMD_ARGS)
739 {
740 binding_cmd(F_PASS_ARGS, BIND_STROKE);
741
742 return;
743 }
744 #endif /* HAVE_STROKE */
745
746 /* Declares which X modifiers are actually locks and should be ignored when
747 * testing mouse/key binding modifiers. */
CMD_IgnoreModifiers(F_CMD_ARGS)748 void CMD_IgnoreModifiers(F_CMD_ARGS)
749 {
750 char *token;
751 int mods_unused_old = mods_unused;
752
753 token = PeekToken(action, &action);
754 if (!token)
755 {
756 mods_unused = 0;
757 }
758 else if (StrEquals(token, "default"))
759 {
760 mods_unused = DEFAULT_MODS_UNUSED;
761 }
762 else if (modifiers_string_to_modmask(token, &mods_unused))
763 {
764 fvwm_msg(
765 ERR, "ignore_modifiers",
766 "illegal modifier in line %s\n", action);
767 }
768 if (mods_unused != mods_unused_old)
769 {
770 /* broadcast config to modules */
771 broadcast_ignore_modifiers();
772 }
773
774 return;
775 }
776