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