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