]> icculus.org git repositories - dana/openbox.git/blob - openbox/action.c
only allow running lists of actions at a time, policy is to always allow multiple...
[dana/openbox.git] / openbox / action.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    action.c for the Openbox window manager
4    Copyright (c) 2003        Ben Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "debug.h"
20 #include "client.h"
21 #include "focus.h"
22 #include "moveresize.h"
23 #include "menu.h"
24 #include "prop.h"
25 #include "stacking.h"
26 #include "screen.h"
27 #include "action.h"
28 #include "openbox.h"
29 #include "grab.h"
30 #include "keyboard.h"
31 #include "event.h"
32
33 #include <glib.h>
34
35 typedef struct ActionString {
36     const gchar *name;
37     void (*func)(union ActionData *);
38     void (*setup)(ObAction **, ObUserAction uact);
39 } ActionString;
40
41 static ObAction *action_new(void (*func)(union ActionData *data),
42                             ObUserAction uact)
43 {
44     ObAction *a = g_new0(ObAction, 1);
45     a->func = func;
46
47     return a;
48 }
49
50 void action_free(ObAction *a)
51 {
52     if (a == NULL) return;
53
54     /* deal with pointers */
55     if (a->func == action_execute || a->func == action_restart)
56         g_free(a->data.execute.path);
57     else if (a->func == action_showmenu)
58         g_free(a->data.showmenu.name);
59
60     g_free(a);
61 }
62
63 void setup_action_directional_focus_north(ObAction **a, ObUserAction uact)
64
65     (*a)->data.interdiraction.inter.any.interactive = TRUE;
66     (*a)->data.interdiraction.direction = OB_DIRECTION_NORTH;
67 }
68
69 void setup_action_directional_focus_east(ObAction **a, ObUserAction uact)
70 {
71     (*a)->data.interdiraction.inter.any.interactive = TRUE;
72     (*a)->data.interdiraction.direction = OB_DIRECTION_EAST;
73 }
74
75 void setup_action_directional_focus_south(ObAction **a, ObUserAction uact)
76 {
77     (*a)->data.interdiraction.inter.any.interactive = TRUE;
78     (*a)->data.interdiraction.direction = OB_DIRECTION_SOUTH;
79 }
80
81 void setup_action_directional_focus_west(ObAction **a, ObUserAction uact)
82 {
83     (*a)->data.interdiraction.inter.any.interactive = TRUE;
84     (*a)->data.interdiraction.direction = OB_DIRECTION_WEST;
85 }
86
87 void setup_action_directional_focus_northeast(ObAction **a, ObUserAction uact)
88 {
89     (*a)->data.interdiraction.inter.any.interactive = TRUE;
90     (*a)->data.interdiraction.direction = OB_DIRECTION_NORTHEAST;
91 }
92
93 void setup_action_directional_focus_southeast(ObAction **a, ObUserAction uact)
94 {
95     (*a)->data.interdiraction.inter.any.interactive = TRUE;
96     (*a)->data.interdiraction.direction = OB_DIRECTION_SOUTHEAST;
97 }
98
99 void setup_action_directional_focus_southwest(ObAction **a, ObUserAction uact)
100 {
101     (*a)->data.interdiraction.inter.any.interactive = TRUE;
102     (*a)->data.interdiraction.direction = OB_DIRECTION_SOUTHWEST;
103 }
104
105 void setup_action_directional_focus_northwest(ObAction **a, ObUserAction uact)
106 {
107     (*a)->data.interdiraction.inter.any.interactive = TRUE;
108     (*a)->data.interdiraction.direction = OB_DIRECTION_NORTHWEST;
109 }
110
111 void setup_action_send_to_desktop(ObAction **a, ObUserAction uact)
112 {
113     (*a)->data.sendto.follow = TRUE;
114 }
115
116 void setup_action_send_to_desktop_prev(ObAction **a, ObUserAction uact)
117 {
118     (*a)->data.sendtodir.inter.any.interactive = TRUE;
119     (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
120     (*a)->data.sendtodir.linear = TRUE;
121     (*a)->data.sendtodir.wrap = TRUE;
122     (*a)->data.sendtodir.follow = TRUE;
123 }
124
125 void setup_action_send_to_desktop_next(ObAction **a, ObUserAction uact)
126 {
127     (*a)->data.sendtodir.inter.any.interactive = TRUE;
128     (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
129     (*a)->data.sendtodir.linear = TRUE;
130     (*a)->data.sendtodir.wrap = TRUE;
131     (*a)->data.sendtodir.follow = TRUE;
132 }
133
134 void setup_action_send_to_desktop_left(ObAction **a, ObUserAction uact)
135 {
136     (*a)->data.sendtodir.inter.any.interactive = TRUE;
137     (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
138     (*a)->data.sendtodir.linear = FALSE;
139     (*a)->data.sendtodir.wrap = TRUE;
140     (*a)->data.sendtodir.follow = TRUE;
141 }
142
143 void setup_action_send_to_desktop_right(ObAction **a, ObUserAction uact)
144 {
145     (*a)->data.sendtodir.inter.any.interactive = TRUE;
146     (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
147     (*a)->data.sendtodir.linear = FALSE;
148     (*a)->data.sendtodir.wrap = TRUE;
149     (*a)->data.sendtodir.follow = TRUE;
150 }
151
152 void setup_action_send_to_desktop_up(ObAction **a, ObUserAction uact)
153 {
154     (*a)->data.sendtodir.inter.any.interactive = TRUE;
155     (*a)->data.sendtodir.dir = OB_DIRECTION_NORTH;
156     (*a)->data.sendtodir.linear = FALSE;
157     (*a)->data.sendtodir.wrap = TRUE;
158     (*a)->data.sendtodir.follow = TRUE;
159 }
160
161 void setup_action_send_to_desktop_down(ObAction **a, ObUserAction uact)
162 {
163     (*a)->data.sendtodir.inter.any.interactive = TRUE;
164     (*a)->data.sendtodir.dir = OB_DIRECTION_SOUTH;
165     (*a)->data.sendtodir.linear = FALSE;
166     (*a)->data.sendtodir.wrap = TRUE;
167     (*a)->data.sendtodir.follow = TRUE;
168 }
169
170 void setup_action_desktop_prev(ObAction **a, ObUserAction uact)
171 {
172     (*a)->data.desktopdir.inter.any.interactive = TRUE;
173     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
174     (*a)->data.desktopdir.linear = TRUE;
175     (*a)->data.desktopdir.wrap = TRUE;
176 }
177
178 void setup_action_desktop_next(ObAction **a, ObUserAction uact)
179 {
180     (*a)->data.desktopdir.inter.any.interactive = TRUE;
181     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
182     (*a)->data.desktopdir.linear = TRUE;
183     (*a)->data.desktopdir.wrap = TRUE;
184 }
185
186 void setup_action_desktop_left(ObAction **a, ObUserAction uact)
187 {
188     (*a)->data.desktopdir.inter.any.interactive = TRUE;
189     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
190     (*a)->data.desktopdir.linear = FALSE;
191     (*a)->data.desktopdir.wrap = TRUE;
192 }
193
194 void setup_action_desktop_right(ObAction **a, ObUserAction uact)
195 {
196     (*a)->data.desktopdir.inter.any.interactive = TRUE;
197     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
198     (*a)->data.desktopdir.linear = FALSE;
199     (*a)->data.desktopdir.wrap = TRUE;
200 }
201
202 void setup_action_desktop_up(ObAction **a, ObUserAction uact)
203 {
204     (*a)->data.desktopdir.inter.any.interactive = TRUE;
205     (*a)->data.desktopdir.dir = OB_DIRECTION_NORTH;
206     (*a)->data.desktopdir.linear = FALSE;
207     (*a)->data.desktopdir.wrap = TRUE;
208 }
209
210 void setup_action_desktop_down(ObAction **a, ObUserAction uact)
211 {
212     (*a)->data.desktopdir.inter.any.interactive = TRUE;
213     (*a)->data.desktopdir.dir = OB_DIRECTION_SOUTH;
214     (*a)->data.desktopdir.linear = FALSE;
215     (*a)->data.desktopdir.wrap = TRUE;
216 }
217
218 void setup_action_cycle_windows_next(ObAction **a, ObUserAction uact)
219 {
220     (*a)->data.cycle.inter.any.interactive = TRUE;
221     (*a)->data.cycle.linear = FALSE;
222     (*a)->data.cycle.forward = TRUE;
223 }
224
225 void setup_action_cycle_windows_previous(ObAction **a, ObUserAction uact)
226 {
227     (*a)->data.cycle.inter.any.interactive = TRUE;
228     (*a)->data.cycle.linear = FALSE;
229     (*a)->data.cycle.forward = FALSE;
230 }
231
232 void setup_action_movetoedge_north(ObAction **a, ObUserAction uact)
233 {
234     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
235 }
236
237 void setup_action_movetoedge_south(ObAction **a, ObUserAction uact)
238 {
239     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
240 }
241
242 void setup_action_movetoedge_east(ObAction **a, ObUserAction uact)
243 {
244     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
245 }
246
247 void setup_action_movetoedge_west(ObAction **a, ObUserAction uact)
248 {
249     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
250 }
251
252 void setup_action_growtoedge_north(ObAction **a, ObUserAction uact)
253 {
254     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
255 }
256
257 void setup_action_growtoedge_south(ObAction **a, ObUserAction uact)
258 {
259     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
260 }
261
262 void setup_action_growtoedge_east(ObAction **a, ObUserAction uact)
263 {
264     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
265 }
266
267 void setup_action_growtoedge_west(ObAction **a, ObUserAction uact)
268 {
269     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
270 }
271
272 void setup_action_top_layer(ObAction **a, ObUserAction uact)
273 {
274     (*a)->data.layer.layer = 1;
275 }
276
277 void setup_action_normal_layer(ObAction **a, ObUserAction uact)
278 {
279     (*a)->data.layer.layer = 0;
280 }
281
282 void setup_action_bottom_layer(ObAction **a, ObUserAction uact)
283 {
284     (*a)->data.layer.layer = -1;
285 }
286
287 void setup_action_move(ObAction **a, ObUserAction uact)
288 {
289     (*a)->data.moveresize.move = TRUE;
290     (*a)->data.moveresize.keyboard =
291         (uact == OB_USER_ACTION_KEYBOARD_KEY ||
292          uact == OB_USER_ACTION_MENU_SELECTION);
293 }
294
295 void setup_action_resize(ObAction **a, ObUserAction uact)
296 {
297     (*a)->data.moveresize.move = FALSE;
298     (*a)->data.moveresize.keyboard =
299         (uact == OB_USER_ACTION_KEYBOARD_KEY ||
300          uact == OB_USER_ACTION_MENU_SELECTION);
301 }
302
303 void setup_action_showmenu(ObAction **a, ObUserAction uact)
304 {
305     /* you cannot call ShowMenu from inside a menu, cuz the menu code makes
306        assumptions that there is only one menu (and submenus) open at
307        a time! */
308     if (uact == OB_USER_ACTION_MENU_SELECTION) {
309         action_free(*a);
310         a = NULL;
311     }
312 }
313
314 ActionString actionstrings[] =
315 {
316     {
317         "execute", 
318         action_execute, 
319         NULL
320     },
321     {
322         "directionalfocusnorth", 
323         action_directional_focus, 
324         setup_action_directional_focus_north
325     },
326     {
327         "directionalfocuseast", 
328         action_directional_focus, 
329         setup_action_directional_focus_east
330     },
331     {
332         "directionalfocussouth", 
333         action_directional_focus, 
334         setup_action_directional_focus_south
335     },
336     {
337         "directionalfocuswest",
338         action_directional_focus,
339         setup_action_directional_focus_west
340     },
341     {
342         "directionalfocusnortheast",
343         action_directional_focus,
344         setup_action_directional_focus_northeast
345     },
346     {
347         "directionalfocussoutheast",
348         action_directional_focus,
349         setup_action_directional_focus_southeast
350     },
351     {
352         "directionalfocussouthwest",
353         action_directional_focus,
354         setup_action_directional_focus_southwest
355     },
356     {
357         "directionalfocusnorthwest",
358         action_directional_focus,
359         setup_action_directional_focus_northwest
360     },
361     {
362         "activate",
363         action_activate,
364         NULL
365     },
366     {
367         "focus",
368         action_focus,
369         NULL
370     },
371     {
372         "unfocus",
373         action_unfocus,
374         NULL
375     },
376     {
377         "iconify",
378         action_iconify,
379         NULL
380     },
381     {
382         "raiselower",
383         action_raiselower,
384         NULL
385     },
386     {
387         "raise",
388         action_raise,
389         NULL
390     },
391     {
392         "lower",
393         action_lower,
394         NULL
395     },
396     {
397         "close",
398         action_close,
399         NULL
400     },
401     {
402         "kill",
403         action_kill,
404         NULL
405     },
406     {
407         "shadelower",
408         action_shadelower,
409         NULL
410     },
411     {
412         "unshaderaise",
413         action_unshaderaise,
414         NULL
415     },
416     {
417         "shade",
418         action_shade,
419         NULL
420     },
421     {
422         "unshade",
423         action_unshade,
424         NULL
425     },
426     {
427         "toggleshade",
428         action_toggle_shade,
429         NULL
430     },
431     {
432         "toggleomnipresent",
433         action_toggle_omnipresent,
434         NULL
435     },
436     {
437         "moverelativehorz",
438         action_move_relative_horz,
439         NULL
440     },
441     {
442         "moverelativevert",
443         action_move_relative_vert,
444         NULL
445     },
446     {
447         "resizerelativehorz",
448         action_resize_relative_horz,
449         NULL
450     },
451     {
452         "resizerelativevert",
453         action_resize_relative_vert,
454         NULL
455     },
456     {
457         "maximizefull",
458         action_maximize_full,
459         NULL
460     },
461     {
462         "unmaximizefull",
463         action_unmaximize_full,
464         NULL
465     },
466     {
467         "togglemaximizefull",
468         action_toggle_maximize_full,
469         NULL
470     },
471     {
472         "maximizehorz",
473         action_maximize_horz,
474         NULL
475     },
476     {
477         "unmaximizehorz",
478         action_unmaximize_horz,
479         NULL
480     },
481     {
482         "togglemaximizehorz",
483         action_toggle_maximize_horz,
484         NULL
485     },
486     {
487         "maximizevert",
488         action_maximize_vert,
489         NULL
490     },
491     {
492         "unmaximizevert",
493         action_unmaximize_vert,
494         NULL
495     },
496     {
497         "togglemaximizevert",
498         action_toggle_maximize_vert,
499         NULL
500     },
501     {
502         "sendtodesktop",
503         action_send_to_desktop,
504         setup_action_send_to_desktop
505     },
506     {
507         "sendtodesktopnext",
508         action_send_to_desktop_dir,
509         setup_action_send_to_desktop_next
510     },
511     {
512         "sendtodesktopprevious",
513         action_send_to_desktop_dir,
514         setup_action_send_to_desktop_prev
515     },
516     {
517         "sendtodesktopright",
518         action_send_to_desktop_dir,
519         setup_action_send_to_desktop_right
520     },
521     {
522         "sendtodesktopleft",
523         action_send_to_desktop_dir,
524         setup_action_send_to_desktop_left
525     },
526     {
527         "sendtodesktopup",
528         action_send_to_desktop_dir,
529         setup_action_send_to_desktop_up
530     },
531     {
532         "sendtodesktopdown",
533         action_send_to_desktop_dir,
534         setup_action_send_to_desktop_down
535     },
536     {
537         "desktop",
538         action_desktop,
539         NULL
540     },
541     {
542         "desktopnext",
543         action_desktop_dir,
544         setup_action_desktop_next
545     },
546     {
547         "desktopprevious",
548         action_desktop_dir,
549         setup_action_desktop_prev
550     },
551     {
552         "desktopright",
553         action_desktop_dir,
554         setup_action_desktop_right
555     },
556     {
557         "desktopleft",
558         action_desktop_dir,
559         setup_action_desktop_left
560     },
561     {
562         "desktopup",
563         action_desktop_dir,
564         setup_action_desktop_up
565     },
566     {
567         "desktopdown",
568         action_desktop_dir,
569         setup_action_desktop_down
570     },
571     {
572         "toggledecorations",
573         action_toggle_decorations,
574         NULL
575     },
576     {
577         "move",
578         action_moveresize,
579         setup_action_move
580     },
581     {
582         "resize",
583         action_moveresize,
584         setup_action_resize
585     },
586     {
587         "toggleshowdesktop",
588         action_toggle_show_desktop,
589         NULL
590     },
591     {
592         "showdesktop",
593         action_show_desktop,
594         NULL
595     },
596     {
597         "unshowdesktop",
598         action_unshow_desktop,
599         NULL
600     },
601     {
602         "desktoplast",
603         action_desktop_last,
604         NULL
605     },
606     {
607         "reconfigure",
608         action_reconfigure,
609         NULL
610     },
611     {
612         "restart",
613         action_restart,
614         NULL
615     },
616     {
617         "exit",
618         action_exit,
619         NULL
620     },
621     {
622         "showmenu",
623         action_showmenu,
624         setup_action_showmenu
625     },
626     {
627         "sendtotoplayer",
628         action_send_to_layer,
629         setup_action_top_layer
630     },
631     {
632         "togglealwaysontop",
633         action_toggle_layer,
634         setup_action_top_layer
635     },
636     {
637         "sendtonormallayer",
638         action_send_to_layer,
639         setup_action_normal_layer
640     },
641     {
642         "sendtobottomlayer",
643         action_send_to_layer,
644         setup_action_bottom_layer
645     },
646     {
647         "togglealwaysonbottom",
648         action_toggle_layer,
649         setup_action_bottom_layer
650     },
651     {
652         "nextwindow",
653         action_cycle_windows,
654         setup_action_cycle_windows_next
655     },
656     {
657         "previouswindow",
658         action_cycle_windows,
659         setup_action_cycle_windows_previous
660     },
661     {
662         "movetoedgenorth",
663         action_movetoedge,
664         setup_action_movetoedge_north
665     },
666     {
667         "movetoedgesouth",
668         action_movetoedge,
669         setup_action_movetoedge_south
670     },
671     {
672         "movetoedgewest",
673         action_movetoedge,
674         setup_action_movetoedge_west
675     },
676     {
677         "movetoedgeeast",
678         action_movetoedge,
679         setup_action_movetoedge_east
680     },
681     {
682         "growtoedgenorth",
683         action_growtoedge,
684         setup_action_growtoedge_north
685     },
686     {
687         "growtoedgesouth",
688         action_growtoedge,
689         setup_action_growtoedge_south
690     },
691     {
692         "growtoedgewest",
693         action_growtoedge,
694         setup_action_growtoedge_west
695     },
696     {
697         "growtoedgeeast",
698         action_growtoedge,
699         setup_action_growtoedge_east
700     },
701     {
702         NULL,
703         NULL,
704         NULL
705     }
706 };
707
708 ObAction *action_from_string(const gchar *name, ObUserAction uact)
709 {
710     ObAction *a = NULL;
711     gboolean exist = FALSE;
712     int i;
713
714     for (i = 0; actionstrings[i].name; i++)
715         if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
716             exist = TRUE;
717             a = action_new(actionstrings[i].func, uact);
718             if (actionstrings[i].setup)
719                 actionstrings[i].setup(&a, uact);
720             /* only key bindings can be interactive. thus saith the xor. */
721             if (uact != OB_USER_ACTION_KEYBOARD_KEY)
722                 a->data.any.interactive = FALSE;
723             break;
724         }
725     if (!exist)
726         g_warning("Invalid action '%s' requested. No such action exists.",
727                   name);
728     if (!a)
729         g_warning("Invalid use of action '%s'. Action will be ignored.", name);
730     return a;
731 }
732
733 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
734                        ObUserAction uact)
735 {
736     char *actname;
737     ObAction *act = NULL;
738     xmlNodePtr n;
739
740     if (parse_attr_string("name", node, &actname)) {
741         if ((act = action_from_string(actname, uact))) {
742             if (act->func == action_execute || act->func == action_restart) {
743                 if ((n = parse_find_node("execute", node->xmlChildrenNode))) {
744                     gchar *s = parse_string(doc, n);
745                     act->data.execute.path = parse_expand_tilde(s);
746                     g_free(s);
747                 }
748             } else if (act->func == action_showmenu) {
749                 if ((n = parse_find_node("menu", node->xmlChildrenNode)))
750                     act->data.showmenu.name = parse_string(doc, n);
751             } else if (act->func == action_move_relative_horz ||
752                        act->func == action_move_relative_vert ||
753                        act->func == action_resize_relative_horz ||
754                        act->func == action_resize_relative_vert) {
755                 if ((n = parse_find_node("delta", node->xmlChildrenNode)))
756                     act->data.relative.delta = parse_int(doc, n);
757             } else if (act->func == action_desktop) {
758                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
759                     act->data.desktop.desk = parse_int(doc, n);
760                 if (act->data.desktop.desk > 0) act->data.desktop.desk--;
761            } else if (act->func == action_send_to_desktop) {
762                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
763                     act->data.sendto.desk = parse_int(doc, n);
764                 if (act->data.sendto.desk > 0) act->data.sendto.desk--;
765                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
766                     act->data.sendto.follow = parse_bool(doc, n);
767             } else if (act->func == action_desktop_dir) {
768                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
769                     act->data.desktopdir.wrap = parse_bool(doc, n); 
770             } else if (act->func == action_send_to_desktop_dir) {
771                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
772                     act->data.sendtodir.wrap = parse_bool(doc, n);
773                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
774                     act->data.sendtodir.follow = parse_bool(doc, n);
775             } else if (act->func == action_activate) {
776                 if ((n = parse_find_node("here", node->xmlChildrenNode)))
777                     act->data.activate.here = parse_bool(doc, n);
778             } else if (act->func == action_cycle_windows) {
779                 if ((n = parse_find_node("linear", node->xmlChildrenNode)))
780                     act->data.cycle.linear = parse_bool(doc, n);
781             }
782         }
783         g_free(actname);
784     }
785     return act;
786 }
787
788 void action_run_list(GSList *acts, struct _ObClient *c,
789                      guint state, guint button, gint x, gint y,
790                      gboolean cancel, gboolean done)
791 {
792     GSList *it;
793     ObAction *a;
794     gboolean inter = FALSE;
795
796     if (x < 0 && y < 0)
797         screen_pointer_pos(&x, &y);
798
799     for (it = acts; it; it = g_slist_next(it)) {
800         a = it->data;
801         if (a->data.any.interactive) {
802             inter = TRUE;
803             break;
804         }
805     }
806
807     if (!inter) {
808         /* sometimes when we execute another app as an action,
809            it won't work right unless we XUngrabKeyboard first,
810            even though we grabbed the key/button Asychronously.
811            e.g. "gnome-panel-control --main-menu" */
812         XUngrabKeyboard(ob_display, event_lasttime);
813     }
814
815     for (it = acts; it; it = g_slist_next(it)) {
816         a = it->data;
817
818         a->data.any.c = c;
819         a->data.any.x = x;
820         a->data.any.y = y;
821
822         a->data.any.button = button;
823
824         if (a->data.any.interactive) {
825             a->data.inter.cancel = cancel;
826             a->data.inter.final = done;
827             if (!(cancel || done))
828                 keyboard_interactive_grab(state, c, a);
829         }
830
831         a->func(&a->data);
832     }
833 }
834
835 void action_execute(union ActionData *data)
836 {
837     GError *e = NULL;
838     char *cmd;
839     if (data->execute.path) {
840         cmd = g_filename_from_utf8(data->execute.path, -1, NULL, NULL, NULL);
841         if (cmd) {
842             if (!g_spawn_command_line_async(cmd, &e)) {
843                 g_warning("failed to execute '%s': %s",
844                           cmd, e->message);
845             }
846             g_free(cmd);
847         } else {
848             g_warning("failed to convert '%s' from utf8", data->execute.path);
849         }
850     }
851 }
852
853 void action_activate(union ActionData *data)
854 {
855     if (data->activate.any.c)
856         client_activate(data->activate.any.c, data->activate.here);
857 }
858
859 void action_focus(union ActionData *data)
860 {
861     if (data->client.any.c)
862         client_focus(data->client.any.c);
863 }
864
865 void action_unfocus (union ActionData *data)
866 {
867     if (data->client.any.c)
868         client_unfocus(data->client.any.c);
869 }
870
871 void action_iconify(union ActionData *data)
872 {
873     if (data->client.any.c)
874         client_iconify(data->client.any.c, TRUE, TRUE);
875 }
876
877 void action_raiselower(union ActionData *data)
878 {
879     ObClient *c = data->client.any.c;
880     GList *it;
881     gboolean raise = FALSE;
882
883     if (!c) return;
884
885     for (it = stacking_list; it; it = g_list_next(it)) {
886         ObClient *cit = it->data;
887
888         if (cit == c) break;
889         if (client_normal(cit) == client_normal(c) &&
890             cit->layer == c->layer &&
891             cit->frame->visible)
892         {
893             if (RECT_INTERSECTS_RECT(cit->frame->area, c->frame->area)) {
894                 raise = TRUE;
895                 break;
896             }
897         }
898     }
899
900     if (raise)
901         stacking_raise(CLIENT_AS_WINDOW(c));
902     else
903         stacking_lower(CLIENT_AS_WINDOW(c));
904 }
905
906 void action_raise(union ActionData *data)
907 {
908     if (data->client.any.c)
909         stacking_raise(CLIENT_AS_WINDOW(data->client.any.c));
910 }
911
912 void action_unshaderaise(union ActionData *data)
913 {
914     if (data->client.any.c) {
915         if (data->client.any.c->shaded) {
916             grab_pointer(TRUE, OB_CURSOR_NONE);
917             client_shade(data->client.any.c, FALSE);
918             grab_pointer(FALSE, OB_CURSOR_NONE);
919         } else
920             stacking_raise(CLIENT_AS_WINDOW(data->client.any.c));
921     }
922 }
923
924 void action_shadelower(union ActionData *data)
925 {
926     if (data->client.any.c) {
927         if (data->client.any.c->shaded)
928             stacking_lower(CLIENT_AS_WINDOW(data->client.any.c));
929         else {
930             grab_pointer(TRUE, OB_CURSOR_NONE);
931             client_shade(data->client.any.c, TRUE);
932             grab_pointer(FALSE, OB_CURSOR_NONE);
933         }
934     }
935 }
936
937 void action_lower(union ActionData *data)
938 {
939     if (data->client.any.c)
940         stacking_lower(CLIENT_AS_WINDOW(data->client.any.c));
941 }
942
943 void action_close(union ActionData *data)
944 {
945     if (data->client.any.c)
946         client_close(data->client.any.c);
947 }
948
949 void action_kill(union ActionData *data)
950 {
951     if (data->client.any.c)
952         client_kill(data->client.any.c);
953 }
954
955 void action_shade(union ActionData *data)
956 {
957     if (data->client.any.c) { 
958         grab_pointer(TRUE, OB_CURSOR_NONE);
959         client_shade(data->client.any.c, TRUE);
960         grab_pointer(FALSE, OB_CURSOR_NONE);
961     }
962 }
963
964 void action_unshade(union ActionData *data)
965 {
966     if (data->client.any.c) {
967         grab_pointer(TRUE, OB_CURSOR_NONE);
968         client_shade(data->client.any.c, FALSE);
969         grab_pointer(FALSE, OB_CURSOR_NONE);
970     }
971 }
972
973 void action_toggle_shade(union ActionData *data)
974 {
975     if (data->client.any.c) {
976         grab_pointer(TRUE, OB_CURSOR_NONE);
977         client_shade(data->client.any.c, !data->client.any.c->shaded);
978         grab_pointer(FALSE, OB_CURSOR_NONE);
979     }
980 }
981
982 void action_toggle_omnipresent(union ActionData *data)
983
984     if (data->client.any.c)
985         client_set_desktop(data->client.any.c,
986                            data->client.any.c->desktop == DESKTOP_ALL ?
987                            screen_desktop : DESKTOP_ALL, FALSE);
988 }
989
990 void action_move_relative_horz(union ActionData *data)
991 {
992     ObClient *c = data->relative.any.c;
993     if (c) {
994         grab_pointer(TRUE, OB_CURSOR_NONE);
995         client_move(c, c->area.x + data->relative.delta, c->area.y);
996         grab_pointer(FALSE, OB_CURSOR_NONE);
997     }
998 }
999
1000 void action_move_relative_vert(union ActionData *data)
1001 {
1002     ObClient *c = data->relative.any.c;
1003     if (c) {
1004         grab_pointer(TRUE, OB_CURSOR_NONE);
1005         client_move(c, c->area.x, c->area.y + data->relative.delta);
1006         grab_pointer(FALSE, OB_CURSOR_NONE);
1007     }
1008 }
1009
1010 void action_resize_relative_horz(union ActionData *data)
1011 {
1012     ObClient *c = data->relative.any.c;
1013     if (c) {
1014         grab_pointer(TRUE, OB_CURSOR_NONE);
1015         client_resize(c,
1016                       c->area.width + data->relative.delta * c->size_inc.width,
1017                       c->area.height);
1018         grab_pointer(FALSE, OB_CURSOR_NONE);
1019     }
1020 }
1021
1022 void action_resize_relative_vert(union ActionData *data)
1023 {
1024     ObClient *c = data->relative.any.c;
1025     if (c && !c->shaded) {
1026         grab_pointer(TRUE, OB_CURSOR_NONE);
1027         client_resize(c, c->area.width, c->area.height +
1028                       data->relative.delta * c->size_inc.height);
1029         grab_pointer(FALSE, OB_CURSOR_NONE);
1030     }
1031 }
1032
1033 void action_maximize_full(union ActionData *data)
1034 {
1035     if (data->client.any.c)
1036         client_maximize(data->client.any.c, TRUE, 0, TRUE);
1037 }
1038
1039 void action_unmaximize_full(union ActionData *data)
1040 {
1041     if (data->client.any.c)
1042         client_maximize(data->client.any.c, FALSE, 0, TRUE);
1043 }
1044
1045 void action_toggle_maximize_full(union ActionData *data)
1046 {
1047     if (data->client.any.c)
1048         client_maximize(data->client.any.c,
1049                         !(data->client.any.c->max_horz ||
1050                           data->client.any.c->max_vert),
1051                         0, TRUE);
1052 }
1053
1054 void action_maximize_horz(union ActionData *data)
1055 {
1056     if (data->client.any.c)
1057         client_maximize(data->client.any.c, TRUE, 1, TRUE);
1058 }
1059
1060 void action_unmaximize_horz(union ActionData *data)
1061 {
1062     if (data->client.any.c)
1063         client_maximize(data->client.any.c, FALSE, 1, TRUE);
1064 }
1065
1066 void action_toggle_maximize_horz(union ActionData *data)
1067 {
1068     if (data->client.any.c)
1069         client_maximize(data->client.any.c,
1070                         !data->client.any.c->max_horz, 1, TRUE);
1071 }
1072
1073 void action_maximize_vert(union ActionData *data)
1074 {
1075     if (data->client.any.c)
1076         client_maximize(data->client.any.c, TRUE, 2, TRUE);
1077 }
1078
1079 void action_unmaximize_vert(union ActionData *data)
1080 {
1081     if (data->client.any.c)
1082         client_maximize(data->client.any.c, FALSE, 2, TRUE);
1083 }
1084
1085 void action_toggle_maximize_vert(union ActionData *data)
1086 {
1087     if (data->client.any.c)
1088         client_maximize(data->client.any.c, !data->client.any.c->max_vert, 2, TRUE);
1089 }
1090
1091 void action_send_to_desktop(union ActionData *data)
1092 {
1093     ObClient *c = data->sendto.any.c;
1094
1095     if (!c || !client_normal(c)) return;
1096
1097     if (data->sendto.desk < screen_num_desktops ||
1098         data->sendto.desk == DESKTOP_ALL) {
1099         client_set_desktop(c, data->sendto.desk, data->sendto.follow);
1100         if (data->sendto.follow)
1101             screen_set_desktop(data->sendto.desk);
1102     }
1103 }
1104
1105 void action_desktop(union ActionData *data)
1106 {
1107     if (data->desktop.desk < screen_num_desktops ||
1108         data->desktop.desk == DESKTOP_ALL)
1109         screen_set_desktop(data->desktop.desk);
1110 }
1111
1112 void action_desktop_dir(union ActionData *data)
1113 {
1114     guint d;
1115
1116     d = screen_cycle_desktop(data->desktopdir.dir,
1117                              data->desktopdir.wrap,
1118                              data->sendtodir.linear,
1119                              data->desktopdir.inter.any.interactive,
1120                              data->desktopdir.inter.final,
1121                              data->desktopdir.inter.cancel);
1122     screen_set_desktop(d);
1123 }
1124
1125 void action_send_to_desktop_dir(union ActionData *data)
1126 {
1127     ObClient *c = data->sendtodir.inter.any.c;
1128     guint d;
1129
1130     if (!c || !client_normal(c)) return;
1131
1132     d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
1133                              data->sendtodir.linear,
1134                              data->sendtodir.inter.any.interactive,
1135                              data->sendtodir.inter.final,
1136                              data->sendtodir.inter.cancel);
1137     client_set_desktop(c, d, data->sendtodir.follow);
1138     if (data->sendtodir.follow)
1139         screen_set_desktop(d);
1140 }
1141
1142 void action_desktop_last(union ActionData *data)
1143 {
1144     screen_set_desktop(screen_last_desktop);
1145 }
1146
1147 void action_toggle_decorations(union ActionData *data)
1148 {
1149     ObClient *c = data->client.any.c;
1150
1151     if (!c) return;
1152
1153     c->decorate = !c->decorate;
1154     client_setup_decor_and_functions(c);
1155 }
1156
1157 static guint32 pick_corner(int x, int y, int cx, int cy, int cw, int ch)
1158 {
1159     if (x - cx > cw / 2) {
1160         if (y - cy > ch / 2)
1161             return prop_atoms.net_wm_moveresize_size_bottomright;
1162         else
1163             return prop_atoms.net_wm_moveresize_size_topright;
1164     } else {
1165         if (y - cy > ch / 2)
1166             return prop_atoms.net_wm_moveresize_size_bottomleft;
1167         else
1168             return prop_atoms.net_wm_moveresize_size_topleft;
1169     }
1170 }
1171
1172 void action_moveresize(union ActionData *data)
1173 {
1174     ObClient *c = data->moveresize.any.c;
1175     guint32 corner;
1176
1177     if (!c || !client_normal(c)) return;
1178
1179     if (data->moveresize.keyboard) {
1180         corner = (data->moveresize.move ?
1181                   prop_atoms.net_wm_moveresize_move_keyboard :
1182                   prop_atoms.net_wm_moveresize_size_keyboard);
1183     } else {
1184         corner = (data->moveresize.move ?
1185                   prop_atoms.net_wm_moveresize_move :
1186                   pick_corner(data->any.x, data->any.y,
1187                               c->frame->area.x, c->frame->area.y,
1188                               /* use the client size because the frame
1189                                  can be differently sized (shaded
1190                                  windows) and we want this based on the
1191                                  clients size */
1192                               c->area.width + c->frame->size.left +
1193                               c->frame->size.right,
1194                               c->area.height + c->frame->size.top +
1195                               c->frame->size.bottom));
1196     }
1197
1198     moveresize_start(c, data->any.x, data->any.y, data->any.button, corner);
1199 }
1200
1201 void action_reconfigure(union ActionData *data)
1202 {
1203     ob_reconfigure();
1204 }
1205
1206 void action_restart(union ActionData *data)
1207 {
1208     ob_restart_other(data->execute.path);
1209 }
1210
1211 void action_exit(union ActionData *data)
1212 {
1213     ob_exit(0);
1214 }
1215
1216 void action_showmenu(union ActionData *data)
1217 {
1218     if (data->showmenu.name) {
1219         menu_show(data->showmenu.name, data->any.x, data->any.y,
1220                   data->showmenu.any.c);
1221     }
1222 }
1223
1224 void action_cycle_windows(union ActionData *data)
1225 {
1226     focus_cycle(data->cycle.forward, data->cycle.linear,
1227                 data->cycle.inter.any.interactive,
1228                 data->cycle.inter.final, data->cycle.inter.cancel);
1229 }
1230
1231 void action_directional_focus(union ActionData *data)
1232 {
1233     focus_directional_cycle(data->interdiraction.direction,
1234                             data->interdiraction.inter.any.interactive,
1235                             data->interdiraction.inter.final,
1236                             data->interdiraction.inter.cancel);
1237 }
1238
1239 void action_movetoedge(union ActionData *data)
1240 {
1241     int x, y;
1242     ObClient *c = data->diraction.any.c;
1243
1244     if (!c)
1245         return;
1246     x = c->frame->area.x;
1247     y = c->frame->area.y;
1248     
1249     switch(data->diraction.direction) {
1250     case OB_DIRECTION_NORTH:
1251         y = client_directional_edge_search(c, OB_DIRECTION_NORTH);
1252         break;
1253     case OB_DIRECTION_WEST:
1254         x = client_directional_edge_search(c, OB_DIRECTION_WEST);
1255         break;
1256     case OB_DIRECTION_SOUTH:
1257         y = client_directional_edge_search(c, OB_DIRECTION_SOUTH) -
1258             c->frame->area.height;
1259         break;
1260     case OB_DIRECTION_EAST:
1261         x = client_directional_edge_search(c, OB_DIRECTION_EAST) -
1262             c->frame->area.width;
1263         break;
1264     default:
1265         g_assert_not_reached();
1266     }
1267     frame_frame_gravity(c->frame, &x, &y);
1268     grab_pointer(TRUE, OB_CURSOR_NONE);
1269     client_move(c, x, y);
1270     grab_pointer(FALSE, OB_CURSOR_NONE);
1271
1272 }
1273
1274 void action_growtoedge(union ActionData *data)
1275 {
1276     int x, y, width, height, dest;
1277     ObClient *c = data->diraction.any.c;
1278     Rect *a;
1279
1280     if (!c)
1281         return;
1282     
1283     a = screen_area(c->desktop);
1284     x = c->frame->area.x;
1285     y = c->frame->area.y;
1286     width = c->frame->area.width;
1287     height = c->frame->area.height;
1288
1289     switch(data->diraction.direction) {
1290     case OB_DIRECTION_NORTH:
1291         dest = client_directional_edge_search(c, OB_DIRECTION_NORTH);
1292         if (a->y == y)
1293             height = c->frame->area.height / 2;
1294         else {
1295             height = c->frame->area.y + c->frame->area.height - dest;
1296             y = dest;
1297         }
1298         break;
1299     case OB_DIRECTION_WEST:
1300         dest = client_directional_edge_search(c, OB_DIRECTION_WEST);
1301         if (a->x == x)
1302             width = c->frame->area.width / 2;
1303         else {
1304             width = c->frame->area.x + c->frame->area.width - dest;
1305             x = dest;
1306         }
1307         break;
1308     case OB_DIRECTION_SOUTH:
1309         dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH);
1310         if (a->y + a->height == y + c->frame->area.height) {
1311             height = c->frame->area.height / 2;
1312             y = a->y + a->height - height;
1313         } else
1314             height = dest - c->frame->area.y;
1315         y += (height - c->frame->area.height) % c->size_inc.height;
1316         height -= (height - c->frame->area.height) % c->size_inc.height;
1317         break;
1318     case OB_DIRECTION_EAST:
1319         dest = client_directional_edge_search(c, OB_DIRECTION_EAST);
1320         if (a->x + a->width == x + c->frame->area.width) {
1321             width = c->frame->area.width / 2;
1322             x = a->x + a->width - width;
1323         } else
1324             width = dest - c->frame->area.x;
1325         x += (width - c->frame->area.width) % c->size_inc.width;
1326         width -= (width - c->frame->area.width) % c->size_inc.width;
1327         break;
1328     default:
1329         g_assert_not_reached();
1330     }
1331     frame_frame_gravity(c->frame, &x, &y);
1332     width -= c->frame->size.left + c->frame->size.right;
1333     height -= c->frame->size.top + c->frame->size.bottom;
1334     grab_pointer(TRUE, OB_CURSOR_NONE);
1335     client_move_resize(c, x, y, width, height);
1336     grab_pointer(FALSE, OB_CURSOR_NONE);
1337 }
1338
1339 void action_send_to_layer(union ActionData *data)
1340 {
1341     if (data->layer.any.c)
1342         client_set_layer(data->layer.any.c, data->layer.layer);
1343 }
1344
1345 void action_toggle_layer(union ActionData *data)
1346 {
1347     ObClient *c = data->layer.any.c;
1348
1349     if (c) {
1350         if (data->layer.layer < 0)
1351             client_set_layer(c, c->below ? 0 : -1);
1352         else if (data->layer.layer > 0)
1353             client_set_layer(c, c->above ? 0 : 1);
1354     }
1355 }
1356
1357 void action_toggle_show_desktop(union ActionData *data)
1358 {
1359     screen_show_desktop(!screen_showing_desktop);
1360 }
1361
1362 void action_show_desktop(union ActionData *data)
1363 {
1364     screen_show_desktop(TRUE);
1365 }
1366
1367 void action_unshow_desktop(union ActionData *data)
1368 {
1369     screen_show_desktop(FALSE);
1370 }