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 /*
17 * fvwm built-in functions and complex functions
18 */
19
20 /* ---------------------------- included header files ---------------------- */
21
22 #include "config.h"
23
24 #include <stdio.h>
25
26 #include <X11/keysym.h>
27
28 #include "libs/fvwmlib.h"
29 #include "libs/charmap.h"
30 #include "libs/wcontext.h"
31 #include "libs/Grab.h"
32 #include "libs/Parse.h"
33 #include "libs/Strings.h"
34 #include "libs/Event.h"
35 #include "fvwm.h"
36 #include "externs.h"
37 #include "cursor.h"
38 #include "execcontext.h"
39 #include "functions.h"
40 #include "commands.h"
41 #include "functable.h"
42 #include "events.h"
43 #include "modconf.h"
44 #include "module_list.h"
45 #include "misc.h"
46 #include "screen.h"
47 #include "repeat.h"
48 #include "expand.h"
49 #include "menus.h"
50
51 /* ---------------------------- local definitions -------------------------- */
52
53 /* ---------------------------- local macros ------------------------------- */
54
55 /* ---------------------------- imports ------------------------------------ */
56
57 /* ---------------------------- included code files ------------------------ */
58
59 /* ---------------------------- local types -------------------------------- */
60
61 typedef struct FunctionItem
62 {
63 struct FvwmFunction *func; /* the function this item is in */
64 struct FunctionItem *next_item; /* next function item */
65 char condition; /* the character string displayed on
66 * left*/
67 char *action; /* action to be performed */
68 short type; /* type of built in function */
69 FUNC_FLAGS_TYPE flags;
70 } FunctionItem;
71
72 typedef struct FvwmFunction
73 {
74 struct FvwmFunction *next_func; /* next in list of root menus */
75 FunctionItem *first_item; /* first item in function */
76 FunctionItem *last_item; /* last item in function */
77 char *name; /* function name */
78 int use_depth;
79 } FvwmFunction;
80
81 /* Types of events for the FUNCTION builtin */
82 typedef enum
83 {
84 CF_IMMEDIATE = 'i',
85 CF_LATE_IMMEDIATE = 'j',
86 CF_MOTION = 'm',
87 CF_HOLD = 'h',
88 CF_CLICK = 'c',
89 CF_DOUBLE_CLICK = 'd',
90 CF_TIMEOUT = '-'
91 } cfunc_action_t;
92
93 /* ---------------------------- forward declarations ----------------------- */
94
95 static void execute_complex_function(
96 cond_rc_t *cond_rc, const exec_context_t *exc, char *action,
97 Bool *desperate, Bool has_ref_window_moved);
98
99 /* ---------------------------- local variables ---------------------------- */
100
101 /* ---------------------------- exported variables (globals) --------------- */
102
103 /* ---------------------------- local functions ---------------------------- */
104
__context_has_window(const exec_context_t * exc,execute_flags_t flags)105 static int __context_has_window(
106 const exec_context_t *exc, execute_flags_t flags)
107 {
108 if (exc->w.fw != NULL)
109 {
110 return 1;
111 }
112 else if ((flags & FUNC_ALLOW_UNMANAGED) && exc->w.w != None)
113 {
114 return 1;
115 }
116
117 return 0;
118 }
119
120 /*
121 * Defer the execution of a function to the next button press if the context is
122 * C_ROOT
123 *
124 * Inputs:
125 * cursor - the cursor to display while waiting
126 */
DeferExecution(exec_context_changes_t * ret_ecc,exec_context_change_mask_t * ret_mask,cursor_t cursor,int trigger_evtype,int do_allow_unmanaged)127 static Bool DeferExecution(
128 exec_context_changes_t *ret_ecc, exec_context_change_mask_t *ret_mask,
129 cursor_t cursor, int trigger_evtype, int do_allow_unmanaged)
130 {
131 int done;
132 int finished = 0;
133 int just_waiting_for_finish = 0;
134 Window dummy;
135 Window original_w;
136 static XEvent e;
137 Window w;
138 int wcontext;
139 FvwmWindow *fw;
140 int FinishEvent;
141
142 fw = ret_ecc->w.fw;
143 w = ret_ecc->w.w;
144 original_w = w;
145 wcontext = ret_ecc->w.wcontext;
146 FinishEvent = ((fw != NULL) ? ButtonRelease : ButtonPress);
147 if (wcontext == C_UNMANAGED && do_allow_unmanaged)
148 {
149 return False;
150 }
151 if (wcontext != C_ROOT && wcontext != C_NO_CONTEXT && fw != NULL &&
152 wcontext != C_EWMH_DESKTOP)
153 {
154 if (FinishEvent == ButtonPress ||
155 (FinishEvent == ButtonRelease &&
156 trigger_evtype != ButtonPress))
157 {
158 return False;
159 }
160 else if (FinishEvent == ButtonRelease)
161 {
162 /* We are only waiting until the user releases the
163 * button. Do not change the cursor. */
164 cursor = CRS_NONE;
165 just_waiting_for_finish = 1;
166 }
167 }
168 if (Scr.flags.are_functions_silent)
169 {
170 return True;
171 }
172 if (!GrabEm(cursor, GRAB_NORMAL))
173 {
174 XBell(dpy, 0);
175 return True;
176 }
177 MyXGrabKeyboard(dpy);
178 while (!finished)
179 {
180 done = 0;
181 /* block until there is an event */
182 FMaskEvent(
183 dpy, ButtonPressMask | ButtonReleaseMask |
184 ExposureMask | KeyPressMask | VisibilityChangeMask |
185 ButtonMotionMask | PointerMotionMask
186 /* | EnterWindowMask | LeaveWindowMask*/, &e);
187
188 if (e.type == KeyPress)
189 {
190 KeySym keysym = XLookupKeysym(&e.xkey, 0);
191 if (keysym == XK_Escape)
192 {
193 ret_ecc->x.etrigger = &e;
194 *ret_mask |= ECC_ETRIGGER;
195 UngrabEm(GRAB_NORMAL);
196 MyXUngrabKeyboard(dpy);
197 return True;
198 }
199 Keyboard_shortcuts(&e, NULL, NULL, NULL, FinishEvent);
200 }
201 if (e.type == FinishEvent)
202 {
203 finished = 1;
204 }
205 switch (e.type)
206 {
207 case KeyPress:
208 case ButtonPress:
209 if (e.type != FinishEvent)
210 {
211 original_w = e.xany.window;
212 }
213 done = 1;
214 break;
215 case ButtonRelease:
216 done = 1;
217 break;
218 default:
219 break;
220 }
221 if (!done)
222 {
223 dispatch_event(&e);
224 }
225 }
226 MyXUngrabKeyboard(dpy);
227 UngrabEm(GRAB_NORMAL);
228 if (just_waiting_for_finish)
229 {
230 return False;
231 }
232 w = e.xany.window;
233 ret_ecc->x.etrigger = &e;
234 *ret_mask |= ECC_ETRIGGER | ECC_W | ECC_WCONTEXT;
235 if ((w == Scr.Root || w == Scr.NoFocusWin) &&
236 e.xbutton.subwindow != None)
237 {
238 w = e.xbutton.subwindow;
239 e.xany.window = w;
240 }
241 if (w == Scr.Root || IS_EWMH_DESKTOP(w))
242 {
243 ret_ecc->w.w = w;
244 ret_ecc->w.wcontext = C_ROOT;
245 XBell(dpy, 0);
246 return True;
247 }
248 *ret_mask |= ECC_FW;
249 if (XFindContext(dpy, w, FvwmContext, (caddr_t *)&fw) == XCNOENT)
250 {
251 ret_ecc->w.fw = NULL;
252 ret_ecc->w.w = w;
253 ret_ecc->w.wcontext = C_ROOT;
254 XBell(dpy, 0);
255 return (True);
256 }
257 if (w == FW_W_PARENT(fw))
258 {
259 w = FW_W(fw);
260 }
261 if (original_w == FW_W_PARENT(fw))
262 {
263 original_w = FW_W(fw);
264 }
265 /* this ugly mess attempts to ensure that the release and press
266 * are in the same window. */
267 if (w != original_w && original_w != Scr.Root &&
268 original_w != None && original_w != Scr.NoFocusWin &&
269 !IS_EWMH_DESKTOP(original_w))
270 {
271 if (w != FW_W_FRAME(fw) || original_w != FW_W(fw))
272 {
273 ret_ecc->w.fw = fw;
274 ret_ecc->w.w = w;
275 ret_ecc->w.wcontext = C_ROOT;
276 XBell(dpy, 0);
277 return True;
278 }
279 }
280
281 if (IS_EWMH_DESKTOP(FW_W(fw)))
282 {
283 ret_ecc->w.fw = fw;
284 ret_ecc->w.w = w;
285 ret_ecc->w.wcontext = C_ROOT;
286 XBell(dpy, 0);
287 return True;
288 }
289 wcontext = GetContext(NULL, fw, &e, &dummy);
290 ret_ecc->w.fw = fw;
291 ret_ecc->w.w = w;
292 ret_ecc->w.wcontext = C_ROOT;
293
294 return False;
295 }
296
297 /*
298 ** do binary search on func list
299 */
func_comp(const void * a,const void * b)300 static int func_comp(const void *a, const void *b)
301 {
302 return (strcmp((char *)a, ((func_t *)b)->keyword));
303 }
304
find_builtin_function(char * func)305 static const func_t *find_builtin_function(char *func)
306 {
307 static int nfuncs = 0;
308 func_t *ret_func;
309 char *temp;
310 char *s;
311
312 if (!func || func[0] == 0)
313 {
314 return NULL;
315 }
316
317 /* since a lot of lines in a typical rc are probably menu/func
318 * continues: */
319 if (func[0]=='+' || (func[0] == ' ' && func[1] == '+'))
320 {
321 return &(func_table[0]);
322 }
323
324 temp = safestrdup(func);
325 for (s = temp; *s != 0; s++)
326 {
327 if (isupper(*s))
328 {
329 *s = tolower(*s);
330 }
331 }
332 if (nfuncs == 0)
333 {
334 for ( ; (func_table[nfuncs]).action != NULL; nfuncs++)
335 {
336 /* nothing to do here */
337 }
338 }
339 ret_func = (func_t *)bsearch(
340 temp, func_table, nfuncs, sizeof(func_t), func_comp);
341 free(temp);
342
343 return ret_func;
344 }
345
__execute_function(cond_rc_t * cond_rc,const exec_context_t * exc,char * action,FUNC_FLAGS_TYPE exec_flags,char * args[],Bool has_ref_window_moved)346 static void __execute_function(
347 cond_rc_t *cond_rc, const exec_context_t *exc, char *action,
348 FUNC_FLAGS_TYPE exec_flags, char *args[], Bool has_ref_window_moved)
349 {
350 static int func_depth = 0;
351 cond_rc_t *func_rc = NULL;
352 cond_rc_t dummy_rc;
353 Window w;
354 int j;
355 char *function;
356 char *taction;
357 char *trash;
358 char *trash2;
359 char *expaction = NULL;
360 char *arguments[11];
361 const func_t *bif;
362 Bool set_silent;
363 Bool must_free_string = False;
364 Bool must_free_function = False;
365 Bool do_keep_rc = False;
366 /* needed to be able to avoid resize to use moved windows for base */
367 extern Window PressedW;
368 Window dummy_w;
369
370 if (!action)
371 {
372 return;
373 }
374 /* ignore whitespace at the beginning of all config lines */
375 action = SkipSpaces(action, NULL, 0);
376 if (!action || action[0] == 0)
377 {
378 /* impossibly short command */
379 return;
380 }
381 if (action[0] == '#')
382 {
383 /* a comment */
384 return;
385 }
386
387 func_depth++;
388 if (func_depth > MAX_FUNCTION_DEPTH)
389 {
390 fvwm_msg(
391 ERR, "__execute_function",
392 "Function '%s' called with a depth of %i, "
393 "stopping function execution!",
394 action, func_depth);
395 func_depth--;
396 return;
397 }
398 if (args)
399 {
400 for (j = 0; j < 11; j++)
401 {
402 arguments[j] = args[j];
403 }
404 }
405 else
406 {
407 for (j = 0; j < 11; j++)
408 {
409 arguments[j] = NULL;
410 }
411 }
412
413 if (exc->w.fw == NULL || IS_EWMH_DESKTOP(FW_W(exc->w.fw)))
414 {
415 if (exec_flags & FUNC_IS_UNMANAGED)
416 {
417 w = exc->w.w;
418 }
419 else
420 {
421 w = Scr.Root;
422 }
423 }
424 else
425 {
426 FvwmWindow *tw;
427
428 w = GetSubwindowFromEvent(dpy, exc->x.elast);
429 if (w == None)
430 {
431 w = exc->x.elast->xany.window;
432 }
433 tw = NULL;
434 if (w != None)
435 {
436 if (XFindContext(
437 dpy, w, FvwmContext, (caddr_t *)&tw) ==
438 XCNOENT)
439 {
440 tw = NULL;
441 }
442 }
443 if (w == None || tw != exc->w.fw)
444 {
445 w = FW_W(exc->w.fw);
446 }
447 }
448
449 set_silent = False;
450 if (action[0] == '-')
451 {
452 exec_flags |= FUNC_DONT_EXPAND_COMMAND;
453 action++;
454 }
455
456 taction = action;
457 /* parse prefixes */
458 trash = PeekToken(taction, &trash2);
459 while (trash)
460 {
461 if (StrEquals(trash, PRE_SILENT))
462 {
463 if (Scr.flags.are_functions_silent == 0)
464 {
465 set_silent = 1;
466 Scr.flags.are_functions_silent = 1;
467 }
468 taction = trash2;
469 trash = PeekToken(taction, &trash2);
470 }
471 else if (StrEquals(trash, PRE_KEEPRC))
472 {
473 do_keep_rc = True;
474 taction = trash2;
475 trash = PeekToken(taction, &trash2);
476 }
477 else
478 {
479 break;
480 }
481 }
482 if (taction == NULL)
483 {
484 if (set_silent)
485 {
486 Scr.flags.are_functions_silent = 0;
487 }
488 func_depth--;
489 return;
490 }
491 if (cond_rc == NULL || do_keep_rc == True)
492 {
493 condrc_init(&dummy_rc);
494 func_rc = &dummy_rc;
495 }
496 else
497 {
498 func_rc = cond_rc;
499 }
500
501 GetNextToken(taction, &function);
502 if (function)
503 {
504 char *tmp = function;
505 function = expand_vars(
506 function, arguments, False, False, func_rc, exc);
507 free(tmp);
508 }
509 if (function && function[0] != '*')
510 {
511 #if 1
512 /* DV: with this piece of code it is impossible to have a
513 * complex function with embedded whitespace that begins with a
514 * builtin function name, e.g. a function "echo hello". */
515 /* DV: ... and without it some of the complex functions will
516 * fail */
517 char *tmp = function;
518
519 while (*tmp && !isspace(*tmp))
520 {
521 tmp++;
522 }
523 *tmp = 0;
524 #endif
525 bif = find_builtin_function(function);
526 must_free_function = True;
527 }
528 else
529 {
530 bif = NULL;
531 if (function)
532 {
533 free(function);
534 }
535 function = "";
536 }
537
538 if (Scr.cur_decor && Scr.cur_decor != &Scr.DefaultDecor &&
539 (!bif || !(bif->flags & FUNC_DECOR)))
540 {
541 fvwm_msg(
542 ERR, "__execute_function",
543 "Command can not be added to a decor; executing"
544 " command now: '%s'", action);
545 }
546
547 if (!(exec_flags & FUNC_DONT_EXPAND_COMMAND))
548 {
549 expaction = expand_vars(
550 taction, arguments, (bif) ?
551 !!(bif->flags & FUNC_ADD_TO) :
552 False, (taction[0] == '*'), func_rc, exc);
553 if (func_depth <= 1)
554 {
555 must_free_string = set_repeat_data(
556 expaction, REPEAT_COMMAND, bif);
557 }
558 else
559 {
560 must_free_string = True;
561 }
562 }
563 else
564 {
565 expaction = taction;
566 }
567
568 #ifdef FVWM_COMMAND_LOG
569 fvwm_msg(INFO, "LOG", "%c: %s", (char)exc->type, expaction);
570 #endif
571
572 /* Note: the module config command, "*" can not be handled by the
573 * regular command table because there is no required white space after
574 * the asterisk. */
575 if (expaction[0] == '*')
576 {
577 if (Scr.cur_decor && Scr.cur_decor != &Scr.DefaultDecor)
578 {
579 fvwm_msg(
580 WARN, "__execute_function",
581 "Command can not be added to a decor;"
582 " executing command now: '%s'", expaction);
583 }
584
585 /* process a module config command */
586 ModuleConfig(expaction);
587 }
588 else
589 {
590 const exec_context_t *exc2;
591 exec_context_changes_t ecc;
592 exec_context_change_mask_t mask;
593
594 mask = (w != exc->w.w) ? ECC_W : 0;
595 ecc.w.fw = exc->w.fw;
596 ecc.w.w = w;
597 ecc.w.wcontext = exc->w.wcontext;
598 if (bif && bif->func_t != F_FUNCTION)
599 {
600 char *runaction;
601 Bool rc = False;
602
603 runaction = SkipNTokens(expaction, 1);
604 if ((bif->flags & FUNC_NEEDS_WINDOW) &&
605 !(exec_flags & FUNC_DONT_DEFER))
606 {
607 rc = DeferExecution(
608 &ecc, &mask, bif->cursor,
609 exc->x.elast->type,
610 (bif->flags & FUNC_ALLOW_UNMANAGED));
611 }
612 else if ((bif->flags & FUNC_NEEDS_WINDOW) &&
613 !__context_has_window(
614 exc,
615 bif->flags & FUNC_ALLOW_UNMANAGED))
616 {
617 /* no context window and not allowed to defer,
618 * skip command */
619 rc = True;
620 }
621 if (rc == False)
622 {
623 exc2 = exc_clone_context(exc, &ecc, mask);
624 if (has_ref_window_moved &&
625 (bif->func_t == F_ANIMATED_MOVE ||
626 bif->func_t == F_MOVE ||
627 bif->func_t == F_RESIZE ||
628 bif->func_t == F_RESIZEMOVE ||
629 bif->func_t == F_RESIZE_MAXIMIZE ||
630 bif->func_t == F_RESIZEMOVE_MAXIMIZE))
631 {
632 dummy_w = PressedW;
633 PressedW = None;
634 bif->action(func_rc, exc2, runaction);
635 PressedW = dummy_w;
636 }
637 else
638 {
639 bif->action(func_rc, exc2, runaction);
640 }
641 exc_destroy_context(exc2);
642 }
643 }
644 else
645 {
646 Bool desperate = 1;
647 char *runaction;
648
649 if (bif)
650 {
651 /* strip "function" command */
652 runaction = SkipNTokens(expaction, 1);
653 }
654 else
655 {
656 runaction = expaction;
657 }
658 exc2 = exc_clone_context(exc, &ecc, mask);
659 execute_complex_function(
660 func_rc, exc2, runaction, &desperate,
661 has_ref_window_moved);
662 if (!bif && desperate)
663 {
664 if (executeModuleDesperate(
665 func_rc, exc, runaction) == NULL &&
666 *function != 0 && !set_silent)
667 {
668 fvwm_msg(
669 ERR, "__execute_function",
670 "No such command '%s'",
671 function);
672 }
673 }
674 exc_destroy_context(exc2);
675 }
676 }
677
678 if (set_silent)
679 {
680 Scr.flags.are_functions_silent = 0;
681 }
682 if (cond_rc != NULL)
683 {
684 cond_rc->break_levels = func_rc->break_levels;
685 }
686 if (must_free_string)
687 {
688 free(expaction);
689 }
690 if (must_free_function)
691 {
692 free(function);
693 }
694 func_depth--;
695
696 return;
697 }
698
699 /* find_complex_function expects a token as the input. Make sure you have used
700 * GetNextToken before passing a function name to remove quotes */
find_complex_function(const char * function_name)701 static FvwmFunction *find_complex_function(const char *function_name)
702 {
703 FvwmFunction *func;
704
705 if (function_name == NULL || *function_name == 0)
706 {
707 return NULL;
708 }
709 func = Scr.functions;
710 while (func != NULL)
711 {
712 if (func->name != NULL)
713 {
714 if (strcasecmp(function_name, func->name) == 0)
715 {
716 return func;
717 }
718 }
719 func = func->next_func;
720 }
721
722 return NULL;
723 }
724
725 /*
726 * Builtin which determines if the button press was a click or double click...
727 * Waits Scr.ClickTime, or until it is evident that the user is not
728 * clicking, but is moving the cursor
729 */
CheckActionType(int x,int y,XEvent * d,Bool may_time_out,Bool is_button_pressed,int * ret_button)730 static cfunc_action_t CheckActionType(
731 int x, int y, XEvent *d, Bool may_time_out, Bool is_button_pressed,
732 int *ret_button)
733 {
734 int xcurrent,ycurrent,total = 0;
735 Time t0;
736 int dist;
737 Bool do_sleep = False;
738
739 xcurrent = x;
740 ycurrent = y;
741 t0 = fev_get_evtime();
742 dist = Scr.MoveThreshold;
743
744 while ((total < Scr.ClickTime &&
745 fev_get_evtime() - t0 < Scr.ClickTime) || !may_time_out)
746 {
747 if (!(x - xcurrent <= dist && xcurrent - x <= dist &&
748 y - ycurrent <= dist && ycurrent - y <= dist))
749 {
750 return (is_button_pressed) ? CF_MOTION : CF_TIMEOUT;
751 }
752
753 if (do_sleep)
754 {
755 usleep(20000);
756 }
757 else
758 {
759 usleep(1);
760 do_sleep = 1;
761 }
762 total += 20;
763 if (FCheckMaskEvent(
764 dpy, ButtonReleaseMask|ButtonMotionMask|
765 PointerMotionMask|ButtonPressMask|ExposureMask, d))
766 {
767 do_sleep = 0;
768 switch (d->xany.type)
769 {
770 case ButtonRelease:
771 *ret_button = d->xbutton.button;
772 return CF_CLICK;
773 case MotionNotify:
774 if (d->xmotion.same_screen == False)
775 {
776 break;
777 }
778 if ((d->xmotion.state &
779 DEFAULT_ALL_BUTTONS_MASK) ||
780 !is_button_pressed)
781 {
782 xcurrent = d->xmotion.x_root;
783 ycurrent = d->xmotion.y_root;
784 }
785 else
786 {
787 return CF_CLICK;
788 }
789 break;
790 case ButtonPress:
791 *ret_button = d->xbutton.button;
792 if (may_time_out)
793 {
794 is_button_pressed = True;
795 }
796 break;
797 case Expose:
798 /* must handle expose here so that raising a
799 * window with "I" works */
800 dispatch_event(d);
801 break;
802 default:
803 /* can't happen */
804 break;
805 }
806 }
807 }
808
809 return (is_button_pressed) ? CF_HOLD : CF_TIMEOUT;
810 }
811
__run_complex_function_items(cond_rc_t * cond_rc,char cond,FvwmFunction * func,const exec_context_t * exc,char * args[],Bool has_ref_window_moved)812 static void __run_complex_function_items(
813 cond_rc_t *cond_rc, char cond, FvwmFunction *func,
814 const exec_context_t *exc, char *args[], Bool has_ref_window_moved)
815 {
816 char c;
817 FunctionItem *fi;
818 int x0, y0, x, y;
819 extern Window PressedW;
820
821 if (!(!has_ref_window_moved && PressedW && XTranslateCoordinates(
822 dpy, PressedW , Scr.Root, 0, 0, &x0, &y0,
823 &JunkChild)))
824 {
825 x0 = y0 = 0;
826 }
827
828 for (fi = func->first_item; fi != NULL && cond_rc->break_levels == 0; )
829 {
830 /* make lower case */
831 c = fi->condition;
832 if (isupper(c))
833 {
834 c = tolower(c);
835 }
836 if (c == cond)
837 {
838 __execute_function(
839 cond_rc, exc, fi->action, FUNC_DONT_DEFER,
840 args, has_ref_window_moved);
841 if (!has_ref_window_moved && PressedW &&
842 XTranslateCoordinates(
843 dpy, PressedW , Scr.Root, 0, 0, &x, &y,
844 &JunkChild))
845 {
846 has_ref_window_moved =(x != x0 || y != y0);
847 }
848 }
849 fi = fi->next_item;
850 }
851
852 return;
853 }
854
__cf_cleanup(int * depth,char ** arguments,cond_rc_t * cond_rc)855 static void __cf_cleanup(
856 int *depth, char **arguments, cond_rc_t *cond_rc)
857 {
858 int i;
859
860 (*depth)--;
861 if (!(*depth))
862 {
863 Scr.flags.is_executing_complex_function = 0;
864 }
865 for (i = 0; i < 11; i++)
866 {
867 if (arguments[i] != NULL)
868 {
869 free(arguments[i]);
870 }
871 }
872 if (cond_rc->break_levels > 0)
873 {
874 cond_rc->break_levels--;
875 }
876
877 return;
878 }
879
execute_complex_function(cond_rc_t * cond_rc,const exec_context_t * exc,char * action,Bool * desperate,Bool has_ref_window_moved)880 static void execute_complex_function(
881 cond_rc_t *cond_rc, const exec_context_t *exc, char *action,
882 Bool *desperate, Bool has_ref_window_moved)
883 {
884 cond_rc_t tmp_rc;
885 cfunc_action_t type = CF_MOTION;
886 char c;
887 FunctionItem *fi;
888 Bool Persist = False;
889 Bool HaveDoubleClick = False;
890 Bool HaveHold = False;
891 Bool NeedsTarget = False;
892 Bool ImmediateNeedsTarget = False;
893 int has_immediate = 0;
894 int do_run_late_immediate = 0;
895 int do_allow_unmanaged = FUNC_ALLOW_UNMANAGED;
896 int do_allow_unmanaged_immediate = FUNC_ALLOW_UNMANAGED;
897 char *arguments[11], *taction;
898 char *func_name;
899 int x, y ,i;
900 XEvent d;
901 FvwmFunction *func;
902 static int depth = 0;
903 const exec_context_t *exc2;
904 exec_context_changes_t ecc;
905 exec_context_change_mask_t mask;
906 int trigger_evtype;
907 int button;
908 XEvent *te;
909
910 if (cond_rc == NULL)
911 {
912 condrc_init(&tmp_rc);
913 cond_rc = &tmp_rc;
914 }
915 cond_rc->rc = COND_RC_OK;
916 mask = 0;
917 d.type = 0;
918 ecc.w.fw = exc->w.fw;
919 ecc.w.w = exc->w.w;
920 ecc.w.wcontext = exc->w.wcontext;
921 /* find_complex_function expects a token, not just a quoted string */
922 func_name = PeekToken(action, &taction);
923 if (!func_name)
924 {
925 return;
926 }
927 func = find_complex_function(func_name);
928 if (func == NULL)
929 {
930 if (*desperate == 0)
931 {
932 fvwm_msg(
933 ERR, "ComplexFunction", "No such function %s",
934 action);
935 }
936 return;
937 }
938 if (!depth)
939 {
940 Scr.flags.is_executing_complex_function = 1;
941 }
942 depth++;
943 *desperate = 0;
944 /* duplicate the whole argument list for use as '$*' */
945 if (taction)
946 {
947 arguments[0] = safestrdup(taction);
948 /* strip trailing newline */
949 if (arguments[0][0])
950 {
951 int l= strlen(arguments[0]);
952
953 if (arguments[0][l - 1] == '\n')
954 {
955 arguments[0][l - 1] = 0;
956 }
957 }
958 /* Get the argument list */
959 for (i = 1; i < 11; i++)
960 {
961 taction = GetNextToken(taction, &arguments[i]);
962 }
963 }
964 else
965 {
966 for (i = 0; i < 11; i++)
967 {
968 arguments[i] = NULL;
969 }
970 }
971 /* In case we want to perform an action on a button press, we
972 * need to fool other routines */
973 te = exc->x.elast;
974 if (te->type == ButtonPress)
975 {
976 trigger_evtype = ButtonRelease;
977 }
978 else
979 {
980 trigger_evtype = te->type;
981 }
982 func->use_depth++;
983
984 for (fi = func->first_item; fi != NULL; fi = fi->next_item)
985 {
986 if (fi->condition == CF_IMMEDIATE)
987 {
988 has_immediate = 1;
989 }
990 if (fi->flags & FUNC_NEEDS_WINDOW)
991 {
992 NeedsTarget = True;
993 do_allow_unmanaged &= fi->flags;
994 if (fi->condition == CF_IMMEDIATE)
995 {
996 do_allow_unmanaged_immediate &= fi->flags;
997 ImmediateNeedsTarget = True;
998 }
999 }
1000 }
1001
1002 if (ImmediateNeedsTarget)
1003 {
1004 if (DeferExecution(
1005 &ecc, &mask, CRS_SELECT, trigger_evtype,
1006 do_allow_unmanaged_immediate))
1007 {
1008 func->use_depth--;
1009 __cf_cleanup(&depth, arguments, cond_rc);
1010 return;
1011 }
1012 NeedsTarget = False;
1013 }
1014 else
1015 {
1016 ecc.w.w = (ecc.w.fw) ? FW_W_FRAME(ecc.w.fw) : None;
1017 mask |= ECC_W;
1018 }
1019
1020 /* we have to grab buttons before executing immediate actions because
1021 * these actions can move the window away from the pointer so that a
1022 * button release would go to the application below. */
1023 if (!GrabEm(CRS_NONE, GRAB_NORMAL))
1024 {
1025 func->use_depth--;
1026 fvwm_msg(
1027 ERR,
1028 "ComplexFunction", "Grab failed in function %s,"
1029 " unable to execute immediate action", action);
1030 __cf_cleanup(&depth, arguments, cond_rc);
1031 return;
1032 }
1033 if (has_immediate)
1034 {
1035 exc2 = exc_clone_context(exc, &ecc, mask);
1036 __run_complex_function_items(
1037 cond_rc, CF_IMMEDIATE, func, exc2, arguments,
1038 has_ref_window_moved);
1039 exc_destroy_context(exc2);
1040 }
1041 for (fi = func->first_item;
1042 fi != NULL && cond_rc->break_levels == 0;
1043 fi = fi->next_item)
1044 {
1045 /* c is already lowercase here */
1046 c = fi->condition;
1047 switch (c)
1048 {
1049 case CF_IMMEDIATE:
1050 break;
1051 case CF_LATE_IMMEDIATE:
1052 do_run_late_immediate = 1;
1053 break;
1054 case CF_DOUBLE_CLICK:
1055 HaveDoubleClick = True;
1056 Persist = True;
1057 break;
1058 case CF_HOLD:
1059 HaveHold = True;
1060 Persist = True;
1061 break;
1062 default:
1063 Persist = True;
1064 break;
1065 }
1066 }
1067
1068 if (!Persist || cond_rc->break_levels != 0)
1069 {
1070 func->use_depth--;
1071 __cf_cleanup(&depth, arguments, cond_rc);
1072 UngrabEm(GRAB_NORMAL);
1073 return;
1074 }
1075
1076 /* Only defer execution if there is a possibility of needing
1077 * a window to operate on */
1078 if (NeedsTarget)
1079 {
1080 if (DeferExecution(
1081 &ecc, &mask, CRS_SELECT, trigger_evtype,
1082 do_allow_unmanaged))
1083 {
1084 func->use_depth--;
1085 __cf_cleanup(&depth, arguments, cond_rc);
1086 UngrabEm(GRAB_NORMAL);
1087 return;
1088 }
1089 }
1090
1091 te = (mask & ECC_ETRIGGER) ? ecc.x.etrigger : exc->x.elast;
1092 switch (te->xany.type)
1093 {
1094 case ButtonPress:
1095 case ButtonRelease:
1096 x = te->xbutton.x_root;
1097 y = te->xbutton.y_root;
1098 button = te->xbutton.button;
1099 /* Take the click which started this fuction off the
1100 * Event queue. -DDN- Dan D Niles dniles@iname.com */
1101 FCheckMaskEvent(dpy, ButtonPressMask, &d);
1102 break;
1103 default:
1104 if (FQueryPointer(
1105 dpy, Scr.Root, &JunkRoot, &JunkChild, &x, &y,
1106 &JunkX, &JunkY, &JunkMask) == False)
1107 {
1108 /* pointer is on a different screen */
1109 x = 0;
1110 y = 0;
1111 }
1112 button = 0;
1113 break;
1114 }
1115
1116 /* Wait and see if we have a click, or a move */
1117 /* wait forever, see if the user releases the button */
1118 type = CheckActionType(x, y, &d, HaveHold, True, &button);
1119 if (do_run_late_immediate)
1120 {
1121 exc2 = exc_clone_context(exc, &ecc, mask);
1122 __run_complex_function_items(
1123 cond_rc, CF_LATE_IMMEDIATE, func, exc2, arguments,
1124 has_ref_window_moved);
1125 exc_destroy_context(exc2);
1126 do_run_late_immediate = 0;
1127 }
1128 if (type == CF_CLICK)
1129 {
1130 int button2;
1131
1132 /* If it was a click, wait to see if its a double click */
1133 if (HaveDoubleClick)
1134 {
1135 type = CheckActionType(
1136 x, y, &d, True, False, &button2);
1137 switch (type)
1138 {
1139 case CF_HOLD:
1140 case CF_MOTION:
1141 case CF_CLICK:
1142 if (button == button2)
1143 {
1144 type = CF_DOUBLE_CLICK;
1145 }
1146 else
1147 {
1148 type = CF_CLICK;
1149 }
1150 break;
1151 case CF_TIMEOUT:
1152 type = CF_CLICK;
1153 break;
1154 default:
1155 /* can't happen */
1156 break;
1157 }
1158 }
1159 }
1160 else if (type == CF_TIMEOUT)
1161 {
1162 type = CF_HOLD;
1163 }
1164
1165 /* some functions operate on button release instead of presses. These
1166 * gets really weird for complex functions ... */
1167 if (d.type == ButtonPress)
1168 {
1169 d.type = ButtonRelease;
1170 if (d.xbutton.button > 0 &&
1171 d.xbutton.button <= NUMBER_OF_MOUSE_BUTTONS)
1172 {
1173 d.xbutton.state &=
1174 (~(Button1Mask >> (d.xbutton.button - 1)));
1175 }
1176 }
1177
1178 #ifdef BUGGY_CODE
1179 /* domivogt (11-Apr-2000): The pointer ***must not*** be ungrabbed
1180 * here. If it is, any window that the mouse enters during the
1181 * function will receive MotionNotify events with a button held down!
1182 * The results are unpredictable. E.g. rxvt interprets the
1183 * ButtonMotion as user input to select text. */
1184 UngrabEm(GRAB_NORMAL);
1185 #endif
1186 fev_set_evpos(&d, x, y);
1187 fev_fake_event(&d);
1188 ecc.x.etrigger = &d;
1189 ecc.w.w = (ecc.w.fw) ? FW_W_FRAME(ecc.w.fw) : None;
1190 mask |= ECC_ETRIGGER | ECC_W;
1191 exc2 = exc_clone_context(exc, &ecc, mask);
1192 if (do_run_late_immediate)
1193 {
1194 __run_complex_function_items(
1195 cond_rc, CF_LATE_IMMEDIATE, func, exc2, arguments,
1196 has_ref_window_moved);
1197 }
1198 __run_complex_function_items(
1199 cond_rc, type, func, exc2, arguments, has_ref_window_moved);
1200 exc_destroy_context(exc2);
1201 /* This is the right place to ungrab the pointer (see comment above).
1202 */
1203 func->use_depth--;
1204 __cf_cleanup(&depth, arguments, cond_rc);
1205 UngrabEm(GRAB_NORMAL);
1206
1207 return;
1208 }
1209
1210 /*
1211 * create a new FvwmFunction
1212 */
NewFvwmFunction(const char * name)1213 static FvwmFunction *NewFvwmFunction(const char *name)
1214 {
1215 FvwmFunction *tmp;
1216
1217 tmp = (FvwmFunction *)safemalloc(sizeof(FvwmFunction));
1218 tmp->next_func = Scr.functions;
1219 tmp->first_item = NULL;
1220 tmp->last_item = NULL;
1221 tmp->name = stripcpy(name);
1222 tmp->use_depth = 0;
1223 Scr.functions = tmp;
1224
1225 return tmp;
1226 }
1227
DestroyFunction(FvwmFunction * func)1228 static void DestroyFunction(FvwmFunction *func)
1229 {
1230 FunctionItem *fi,*tmp2;
1231 FvwmFunction *tmp, *prev;
1232
1233 if (func == NULL)
1234 {
1235 return;
1236 }
1237
1238 tmp = Scr.functions;
1239 prev = NULL;
1240 while (tmp && tmp != func)
1241 {
1242 prev = tmp;
1243 tmp = tmp->next_func;
1244 }
1245 if (tmp != func)
1246 {
1247 return;
1248 }
1249
1250 if (func->use_depth != 0)
1251 {
1252 fvwm_msg(
1253 ERR,"DestroyFunction",
1254 "Function %s is in use (depth %d)", func->name,
1255 func->use_depth);
1256 return;
1257 }
1258
1259 if (prev == NULL)
1260 {
1261 Scr.functions = func->next_func;
1262 }
1263 else
1264 {
1265 prev->next_func = func->next_func;
1266 }
1267
1268 free(func->name);
1269
1270 fi = func->first_item;
1271 while (fi != NULL)
1272 {
1273 tmp2 = fi->next_item;
1274 if (fi->action != NULL)
1275 {
1276 free(fi->action);
1277 }
1278 free(fi);
1279 fi = tmp2;
1280 }
1281 free(func);
1282
1283 return;
1284 }
1285
1286 /* ---------------------------- interface functions ------------------------ */
1287
functions_is_complex_function(const char * function_name)1288 Bool functions_is_complex_function(const char *function_name)
1289 {
1290 if (find_complex_function(function_name) != NULL)
1291 {
1292 return True;
1293 }
1294
1295 return False;
1296 }
1297
execute_function(cond_rc_t * cond_rc,const exec_context_t * exc,char * action,FUNC_FLAGS_TYPE exec_flags)1298 void execute_function(
1299 cond_rc_t *cond_rc, const exec_context_t *exc, char *action,
1300 FUNC_FLAGS_TYPE exec_flags)
1301 {
1302 __execute_function(cond_rc, exc, action, exec_flags, NULL, False);
1303
1304 return;
1305 }
1306
execute_function_override_wcontext(cond_rc_t * cond_rc,const exec_context_t * exc,char * action,FUNC_FLAGS_TYPE exec_flags,int wcontext)1307 void execute_function_override_wcontext(
1308 cond_rc_t *cond_rc, const exec_context_t *exc, char *action,
1309 FUNC_FLAGS_TYPE exec_flags, int wcontext)
1310 {
1311 const exec_context_t *exc2;
1312 exec_context_changes_t ecc;
1313
1314 ecc.w.wcontext = wcontext;
1315 exc2 = exc_clone_context(exc, &ecc, ECC_WCONTEXT);
1316 execute_function(cond_rc, exc2, action, exec_flags);
1317 exc_destroy_context(exc2);
1318
1319 return;
1320 }
1321
execute_function_override_window(cond_rc_t * cond_rc,const exec_context_t * exc,char * action,FUNC_FLAGS_TYPE exec_flags,FvwmWindow * fw)1322 void execute_function_override_window(
1323 cond_rc_t *cond_rc, const exec_context_t *exc, char *action,
1324 FUNC_FLAGS_TYPE exec_flags, FvwmWindow *fw)
1325 {
1326 const exec_context_t *exc2;
1327 exec_context_changes_t ecc;
1328
1329 ecc.w.fw = fw;
1330 if (fw != NULL)
1331 {
1332 ecc.w.w = FW_W(fw);
1333 ecc.w.wcontext = C_WINDOW;
1334 exec_flags |= FUNC_DONT_DEFER;
1335 }
1336 else
1337 {
1338 ecc.w.w = None;
1339 ecc.w.wcontext = C_ROOT;
1340 }
1341 if (exc != NULL)
1342 {
1343 exc2 = exc_clone_context(
1344 exc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
1345 }
1346 else
1347 {
1348 ecc.type = EXCT_NULL;
1349 exc2 = exc_create_context(
1350 &ecc, ECC_TYPE | ECC_FW | ECC_W | ECC_WCONTEXT);
1351 }
1352 execute_function(cond_rc, exc2, action, exec_flags);
1353 exc_destroy_context(exc2);
1354
1355 return;
1356 }
1357
find_func_t(char * action,short * func_t,unsigned char * flags)1358 void find_func_t(char *action, short *func_t, unsigned char *flags)
1359 {
1360 int j, len = 0;
1361 char *endtok = action;
1362 Bool matched;
1363 int mlen;
1364
1365 if (action)
1366 {
1367 while (*endtok && !isspace((unsigned char)*endtok))
1368 {
1369 ++endtok;
1370 }
1371 len = endtok - action;
1372 j=0;
1373 matched = False;
1374 while (!matched && (mlen = strlen(func_table[j].keyword)) > 0)
1375 {
1376 if (mlen == len &&
1377 strncasecmp(action,func_table[j].keyword,mlen) ==
1378 0)
1379 {
1380 matched=True;
1381 /* found key word */
1382 if (func_t)
1383 {
1384 *func_t = func_table[j].func_t;
1385 }
1386 if (flags)
1387 {
1388 *flags = func_table[j].flags;
1389 }
1390 return;
1391 }
1392 else
1393 {
1394 j++;
1395 }
1396 }
1397 /* No clue what the function is. Just return "BEEP" */
1398 }
1399 if (func_t)
1400 {
1401 *func_t = F_BEEP;
1402 }
1403 if (flags)
1404 {
1405 *flags = 0;
1406 }
1407
1408 return;
1409 }
1410
1411
1412 /*
1413 * add an item to a FvwmFunction
1414 *
1415 * Inputs:
1416 * func - pointer to the FvwmFunction to add the item
1417 * action - the definition string from the config line
1418 */
AddToFunction(FvwmFunction * func,char * action)1419 void AddToFunction(FvwmFunction *func, char *action)
1420 {
1421 FunctionItem *tmp;
1422 char *token = NULL;
1423 char condition;
1424
1425 token = PeekToken(action, &action);
1426 if (!token)
1427 return;
1428 condition = token[0];
1429 if (isupper(condition))
1430 condition = tolower(condition);
1431 if (condition != CF_IMMEDIATE &&
1432 condition != CF_LATE_IMMEDIATE &&
1433 condition != CF_MOTION &&
1434 condition != CF_HOLD &&
1435 condition != CF_CLICK &&
1436 condition != CF_DOUBLE_CLICK)
1437 {
1438 fvwm_msg(
1439 ERR, "AddToFunction",
1440 "Got '%s' instead of a valid function specifier",
1441 token);
1442 return;
1443 }
1444 if (token[0] != 0 && token[1] != 0 &&
1445 (find_builtin_function(token) || find_complex_function(token)))
1446 {
1447 fvwm_msg(
1448 WARN, "AddToFunction",
1449 "Got the command or function name '%s' instead of a"
1450 " function specifier. This may indicate a syntax"
1451 " error in the configuration file. Using %c as the"
1452 " specifier.", token, token[0]);
1453 }
1454 if (!action)
1455 {
1456 return;
1457 }
1458 while (isspace(*action))
1459 {
1460 action++;
1461 }
1462 if (*action == 0)
1463 {
1464 return;
1465 }
1466
1467 tmp = (FunctionItem *)safemalloc(sizeof(FunctionItem));
1468 tmp->next_item = NULL;
1469 tmp->func = func;
1470 if (func->first_item == NULL)
1471 {
1472 func->first_item = tmp;
1473 func->last_item = tmp;
1474 }
1475 else
1476 {
1477 func->last_item->next_item = tmp;
1478 func->last_item = tmp;
1479 }
1480
1481 tmp->condition = condition;
1482 tmp->action = stripcpy(action);
1483
1484 find_func_t(tmp->action, NULL, &(tmp->flags));
1485
1486 return;
1487 }
1488
1489 /* ---------------------------- builtin commands --------------------------- */
1490
CMD_DestroyFunc(F_CMD_ARGS)1491 void CMD_DestroyFunc(F_CMD_ARGS)
1492 {
1493 FvwmFunction *func;
1494 char *token;
1495
1496 token = PeekToken(action, NULL);
1497 if (!token)
1498 {
1499 return;
1500 }
1501 func = find_complex_function(token);
1502 if (!func)
1503 {
1504 return;
1505 }
1506 if (Scr.last_added_item.type == ADDED_FUNCTION)
1507 {
1508 set_last_added_item(ADDED_NONE, NULL);
1509 }
1510 DestroyFunction(func);
1511
1512 return;
1513 }
1514
CMD_AddToFunc(F_CMD_ARGS)1515 void CMD_AddToFunc(F_CMD_ARGS)
1516 {
1517 FvwmFunction *func;
1518 char *token;
1519
1520 action = GetNextToken(action,&token);
1521 if (!token)
1522 {
1523 return;
1524 }
1525 func = find_complex_function(token);
1526 if (func == NULL)
1527 {
1528 func = NewFvwmFunction(token);
1529 }
1530
1531 /* Set + state to last function */
1532 set_last_added_item(ADDED_FUNCTION, func);
1533
1534 free(token);
1535 AddToFunction(func, action);
1536
1537 return;
1538 }
1539
CMD_Plus(F_CMD_ARGS)1540 void CMD_Plus(F_CMD_ARGS)
1541 {
1542 if (Scr.last_added_item.type == ADDED_MENU)
1543 {
1544 add_another_menu_item(action);
1545 }
1546 else if (Scr.last_added_item.type == ADDED_FUNCTION)
1547 {
1548 AddToFunction(Scr.last_added_item.item, action);
1549 }
1550 else if (Scr.last_added_item.type == ADDED_DECOR)
1551 {
1552 FvwmDecor *tmp = &Scr.DefaultDecor;
1553 for ( ; tmp && tmp != Scr.last_added_item.item; tmp = tmp->next)
1554 {
1555 /* nothing to do here */
1556 }
1557 if (!tmp)
1558 {
1559 return;
1560 }
1561 AddToDecor(F_PASS_ARGS, tmp);
1562 }
1563
1564 return;
1565 }
1566
CMD_EchoFuncDefinition(F_CMD_ARGS)1567 void CMD_EchoFuncDefinition(F_CMD_ARGS)
1568 {
1569 FvwmFunction *func;
1570 const func_t *bif;
1571 FunctionItem *fi;
1572 char *token;
1573
1574 GetNextToken(action, &token);
1575 if (!token)
1576 {
1577 fvwm_msg(ERR, "EchoFuncDefinition", "Missing argument");
1578
1579 return;
1580 }
1581 bif = find_builtin_function(token);
1582 if (bif != NULL)
1583 {
1584 fvwm_msg(
1585 INFO, "EchoFuncDefinition",
1586 "function '%s' is a built in command", token);
1587 free(token);
1588
1589 return;
1590 }
1591 func = find_complex_function(token);
1592 if (!func)
1593 {
1594 fvwm_msg(
1595 INFO, "EchoFuncDefinition",
1596 "function '%s' not defined", token);
1597 free(token);
1598
1599 return;
1600 }
1601 fvwm_msg(
1602 INFO, "EchoFuncDefinition", "definition of function '%s':",
1603 token);
1604 for (fi = func->first_item; fi != NULL; fi = fi->next_item)
1605 {
1606 fvwm_msg(
1607 INFO, "EchoFuncDefinition", " %c %s", fi->condition,
1608 (fi->action == 0) ? "(null)" : fi->action);
1609 }
1610 fvwm_msg(INFO, "EchoFuncDefinition", "end of definition");
1611 free(token);
1612
1613 return;
1614 }
1615
1616 /* dummy commands */
CMD_Title(F_CMD_ARGS)1617 void CMD_Title(F_CMD_ARGS) { }
CMD_TearMenuOff(F_CMD_ARGS)1618 void CMD_TearMenuOff(F_CMD_ARGS) { }
CMD_KeepRc(F_CMD_ARGS)1619 void CMD_KeepRc(F_CMD_ARGS) { }
CMD_Silent(F_CMD_ARGS)1620 void CMD_Silent(F_CMD_ARGS) { }
CMD_Function(F_CMD_ARGS)1621 void CMD_Function(F_CMD_ARGS) { }
1622