add the directionalcyclewindows action
[dana/openbox-history.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) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "debug.h"
21 #include "client.h"
22 #include "focus.h"
23 #include "focus_cycle.h"
24 #include "moveresize.h"
25 #include "menu.h"
26 #include "prop.h"
27 #include "stacking.h"
28 #include "screen.h"
29 #include "action.h"
30 #include "openbox.h"
31 #include "grab.h"
32 #include "keyboard.h"
33 #include "event.h"
34 #include "dock.h"
35 #include "config.h"
36 #include "mainloop.h"
37 #include "startupnotify.h"
38 #include "gettext.h"
39
40 #include <glib.h>
41
42
43
44 typedef struct
45 {
46     const gchar *name;
47     void (*func)(union ActionData *);
48     void (*setup)(ObAction **, ObUserAction uact);
49 } ActionString;
50
51 static ObAction *action_new(void (*func)(union ActionData *data))
52 {
53     ObAction *a = g_new0(ObAction, 1);
54     a->ref = 1;
55     a->func = func;
56
57     return a;
58 }
59
60 void action_ref(ObAction *a)
61 {
62     ++a->ref;
63 }
64
65 void action_unref(ObAction *a)
66 {
67     if (a == NULL) return;
68
69     if (--a->ref > 0) return;
70
71     /* deal with pointers */
72     if (a->func == action_execute || a->func == action_restart)
73         g_free(a->data.execute.path);
74     else if (a->func == action_debug)
75         g_free(a->data.debug.string);
76     else if (a->func == action_showmenu)
77         g_free(a->data.showmenu.name);
78
79     g_free(a);
80 }
81
82 ObAction* action_copy(const ObAction *src)
83 {
84     ObAction *a = action_new(src->func);
85
86     a->data = src->data;
87
88     /* deal with pointers */
89     if (a->func == action_execute || a->func == action_restart)
90         a->data.execute.path = g_strdup(a->data.execute.path);
91     else if (a->func == action_debug)
92         a->data.debug.string = g_strdup(a->data.debug.string);
93     else if (a->func == action_showmenu)
94         a->data.showmenu.name = g_strdup(a->data.showmenu.name);
95
96     return a;
97 }
98
99 void setup_action_send_to_desktop(ObAction **a, ObUserAction uact)
100 {
101     (*a)->data.sendto.any.client_action = OB_CLIENT_ACTION_ALWAYS;
102     (*a)->data.sendto.follow = TRUE;
103 }
104
105 void setup_action_send_to_desktop_prev(ObAction **a, ObUserAction uact)
106 {
107     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
108     (*a)->data.sendtodir.inter.any.interactive = TRUE;
109     (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
110     (*a)->data.sendtodir.linear = TRUE;
111     (*a)->data.sendtodir.wrap = TRUE;
112     (*a)->data.sendtodir.follow = TRUE;
113 }
114
115 void setup_action_send_to_desktop_next(ObAction **a, ObUserAction uact)
116 {
117     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
118     (*a)->data.sendtodir.inter.any.interactive = TRUE;
119     (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
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_left(ObAction **a, ObUserAction uact)
126 {
127     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
128     (*a)->data.sendtodir.inter.any.interactive = TRUE;
129     (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
130     (*a)->data.sendtodir.linear = FALSE;
131     (*a)->data.sendtodir.wrap = TRUE;
132     (*a)->data.sendtodir.follow = TRUE;
133 }
134
135 void setup_action_send_to_desktop_right(ObAction **a, ObUserAction uact)
136 {
137     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
138     (*a)->data.sendtodir.inter.any.interactive = TRUE;
139     (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
140     (*a)->data.sendtodir.linear = FALSE;
141     (*a)->data.sendtodir.wrap = TRUE;
142     (*a)->data.sendtodir.follow = TRUE;
143 }
144
145 void setup_action_send_to_desktop_up(ObAction **a, ObUserAction uact)
146 {
147     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
148     (*a)->data.sendtodir.inter.any.interactive = TRUE;
149     (*a)->data.sendtodir.dir = OB_DIRECTION_NORTH;
150     (*a)->data.sendtodir.linear = FALSE;
151     (*a)->data.sendtodir.wrap = TRUE;
152     (*a)->data.sendtodir.follow = TRUE;
153 }
154
155 void setup_action_send_to_desktop_down(ObAction **a, ObUserAction uact)
156 {
157     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
158     (*a)->data.sendtodir.inter.any.interactive = TRUE;
159     (*a)->data.sendtodir.dir = OB_DIRECTION_SOUTH;
160     (*a)->data.sendtodir.linear = FALSE;
161     (*a)->data.sendtodir.wrap = TRUE;
162     (*a)->data.sendtodir.follow = TRUE;
163 }
164
165 void setup_action_desktop(ObAction **a, ObUserAction uact)
166 {
167 /*
168     (*a)->data.desktop.inter.any.interactive = FALSE;
169 */
170 }
171
172 void setup_action_desktop_prev(ObAction **a, ObUserAction uact)
173 {
174     (*a)->data.desktopdir.inter.any.interactive = TRUE;
175     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
176     (*a)->data.desktopdir.linear = TRUE;
177     (*a)->data.desktopdir.wrap = TRUE;
178 }
179
180 void setup_action_desktop_next(ObAction **a, ObUserAction uact)
181 {
182     (*a)->data.desktopdir.inter.any.interactive = TRUE;
183     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
184     (*a)->data.desktopdir.linear = TRUE;
185     (*a)->data.desktopdir.wrap = TRUE;
186 }
187
188 void setup_action_desktop_left(ObAction **a, ObUserAction uact)
189 {
190     (*a)->data.desktopdir.inter.any.interactive = TRUE;
191     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
192     (*a)->data.desktopdir.linear = FALSE;
193     (*a)->data.desktopdir.wrap = TRUE;
194 }
195
196 void setup_action_desktop_right(ObAction **a, ObUserAction uact)
197 {
198     (*a)->data.desktopdir.inter.any.interactive = TRUE;
199     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
200     (*a)->data.desktopdir.linear = FALSE;
201     (*a)->data.desktopdir.wrap = TRUE;
202 }
203
204 void setup_action_desktop_up(ObAction **a, ObUserAction uact)
205 {
206     (*a)->data.desktopdir.inter.any.interactive = TRUE;
207     (*a)->data.desktopdir.dir = OB_DIRECTION_NORTH;
208     (*a)->data.desktopdir.linear = FALSE;
209     (*a)->data.desktopdir.wrap = TRUE;
210 }
211
212 void setup_action_desktop_down(ObAction **a, ObUserAction uact)
213 {
214     (*a)->data.desktopdir.inter.any.interactive = TRUE;
215     (*a)->data.desktopdir.dir = OB_DIRECTION_SOUTH;
216     (*a)->data.desktopdir.linear = FALSE;
217     (*a)->data.desktopdir.wrap = TRUE;
218 }
219
220 void setup_action_movefromedge_north(ObAction **a, ObUserAction uact)
221 {
222     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
223     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
224     (*a)->data.diraction.hang = TRUE;
225 }
226
227 void setup_action_movefromedge_south(ObAction **a, ObUserAction uact)
228 {
229     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
230     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
231     (*a)->data.diraction.hang = TRUE;
232 }
233
234 void setup_action_movefromedge_east(ObAction **a, ObUserAction uact)
235 {
236     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
237     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
238     (*a)->data.diraction.hang = TRUE;
239 }
240
241 void setup_action_movefromedge_west(ObAction **a, ObUserAction uact)
242 {
243     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
244     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
245     (*a)->data.diraction.hang = TRUE;
246 }
247
248 void setup_action_movetoedge_north(ObAction **a, ObUserAction uact)
249 {
250     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
251     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
252     (*a)->data.diraction.hang = FALSE;
253 }
254
255 void setup_action_movetoedge_south(ObAction **a, ObUserAction uact)
256 {
257     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
258     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
259     (*a)->data.diraction.hang = FALSE;
260 }
261
262 void setup_action_movetoedge_east(ObAction **a, ObUserAction uact)
263 {
264     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
265     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
266     (*a)->data.diraction.hang = FALSE;
267 }
268
269 void setup_action_movetoedge_west(ObAction **a, ObUserAction uact)
270 {
271     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
272     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
273     (*a)->data.diraction.hang = FALSE;
274 }
275
276 void setup_action_growtoedge_north(ObAction **a, ObUserAction uact)
277 {
278     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
279     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
280 }
281
282 void setup_action_growtoedge_south(ObAction **a, ObUserAction uact)
283 {
284     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
285     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
286 }
287
288 void setup_action_growtoedge_east(ObAction **a, ObUserAction uact)
289 {
290     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
291     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
292 }
293
294 void setup_action_growtoedge_west(ObAction **a, ObUserAction uact)
295 {
296     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
297     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
298 }
299
300 void setup_action_top_layer(ObAction **a, ObUserAction uact)
301 {
302     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
303     (*a)->data.layer.layer = 1;
304 }
305
306 void setup_action_normal_layer(ObAction **a, ObUserAction uact)
307 {
308     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
309     (*a)->data.layer.layer = 0;
310 }
311
312 void setup_action_bottom_layer(ObAction **a, ObUserAction uact)
313 {
314     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
315     (*a)->data.layer.layer = -1;
316 }
317
318 void setup_action_resize(ObAction **a, ObUserAction uact)
319 {
320     (*a)->data.moveresize.any.client_action = OB_CLIENT_ACTION_ALWAYS;
321     (*a)->data.moveresize.keyboard =
322         (uact == OB_USER_ACTION_NONE ||
323          uact == OB_USER_ACTION_KEYBOARD_KEY ||
324          uact == OB_USER_ACTION_MENU_SELECTION);
325     (*a)->data.moveresize.corner = 0;
326 }
327
328 void setup_action_addremove_desktop_current(ObAction **a, ObUserAction uact)
329 {
330     (*a)->data.addremovedesktop.current = TRUE;
331 }
332
333 void setup_action_addremove_desktop_last(ObAction **a, ObUserAction uact)
334 {
335     (*a)->data.addremovedesktop.current = FALSE;
336 }
337
338 void setup_client_action(ObAction **a, ObUserAction uact)
339 {
340     (*a)->data.any.client_action = OB_CLIENT_ACTION_ALWAYS;
341 }
342
343 ActionString actionstrings[] =
344 {
345     {
346         "shadelower",
347         action_shadelower,
348         setup_client_action
349     },
350     {
351         "unshaderaise",
352         action_unshaderaise,
353         setup_client_action
354     },
355     {
356         "resizerelativevert",
357         action_resize_relative_vert,
358         setup_client_action
359     },
360     {
361         "resizerelative",
362         action_resize_relative,
363         setup_client_action
364     },
365     {
366         "sendtodesktop",
367         action_send_to_desktop,
368         setup_action_send_to_desktop
369     },
370     {
371         "sendtodesktopnext",
372         action_send_to_desktop_dir,
373         setup_action_send_to_desktop_next
374     },
375     {
376         "sendtodesktopprevious",
377         action_send_to_desktop_dir,
378         setup_action_send_to_desktop_prev
379     },
380     {
381         "sendtodesktopright",
382         action_send_to_desktop_dir,
383         setup_action_send_to_desktop_right
384     },
385     {
386         "sendtodesktopleft",
387         action_send_to_desktop_dir,
388         setup_action_send_to_desktop_left
389     },
390     {
391         "sendtodesktopup",
392         action_send_to_desktop_dir,
393         setup_action_send_to_desktop_up
394     },
395     {
396         "sendtodesktopdown",
397         action_send_to_desktop_dir,
398         setup_action_send_to_desktop_down
399     },
400     {
401         "desktop",
402         action_desktop,
403         setup_action_desktop
404     },
405     {
406         "desktopnext",
407         action_desktop_dir,
408         setup_action_desktop_next
409     },
410     {
411         "desktopprevious",
412         action_desktop_dir,
413         setup_action_desktop_prev
414     },
415     {
416         "desktopright",
417         action_desktop_dir,
418         setup_action_desktop_right
419     },
420     {
421         "desktopleft",
422         action_desktop_dir,
423         setup_action_desktop_left
424     },
425     {
426         "desktopup",
427         action_desktop_dir,
428         setup_action_desktop_up
429     },
430     {
431         "desktopdown",
432         action_desktop_dir,
433         setup_action_desktop_down
434     },
435     {
436         "toggledecorations",
437         action_toggle_decorations,
438         setup_client_action
439     },
440     {
441         "resize",
442         action_resize,
443         setup_action_resize
444     },
445     {
446         "toggledockautohide",
447         action_toggle_dockautohide,
448         NULL
449     },
450     {
451         "desktoplast",
452         action_desktop_last,
453         NULL
454     },
455     {
456         "sendtotoplayer",
457         action_send_to_layer,
458         setup_action_top_layer
459     },
460     {
461         "togglealwaysontop",
462         action_toggle_layer,
463         setup_action_top_layer
464     },
465     {
466         "sendtonormallayer",
467         action_send_to_layer,
468         setup_action_normal_layer
469     },
470     {
471         "sendtobottomlayer",
472         action_send_to_layer,
473         setup_action_bottom_layer
474     },
475     {
476         "togglealwaysonbottom",
477         action_toggle_layer,
478         setup_action_bottom_layer
479     },
480     {
481         "movefromedgenorth",
482         action_movetoedge,
483         setup_action_movefromedge_north
484     },
485     {
486         "movefromedgesouth",
487         action_movetoedge,
488         setup_action_movefromedge_south
489     },
490     {
491         "movefromedgewest",
492         action_movetoedge,
493         setup_action_movefromedge_west
494     },
495     {
496         "movefromedgeeast",
497         action_movetoedge,
498         setup_action_movefromedge_east
499     },
500     {
501         "movetoedgenorth",
502         action_movetoedge,
503         setup_action_movetoedge_north
504     },
505     {
506         "movetoedgesouth",
507         action_movetoedge,
508         setup_action_movetoedge_south
509     },
510     {
511         "movetoedgewest",
512         action_movetoedge,
513         setup_action_movetoedge_west
514     },
515     {
516         "movetoedgeeast",
517         action_movetoedge,
518         setup_action_movetoedge_east
519     },
520     {
521         "growtoedgenorth",
522         action_growtoedge,
523         setup_action_growtoedge_north
524     },
525     {
526         "growtoedgesouth",
527         action_growtoedge,
528         setup_action_growtoedge_south
529     },
530     {
531         "growtoedgewest",
532         action_growtoedge,
533         setup_action_growtoedge_west
534     },
535     {
536         "growtoedgeeast",
537         action_growtoedge,
538         setup_action_growtoedge_east
539     },
540     {
541         "adddesktoplast",
542         action_add_desktop,
543         setup_action_addremove_desktop_last
544     },
545     {
546         "removedesktoplast",
547         action_remove_desktop,
548         setup_action_addremove_desktop_last
549     },
550     {
551         "adddesktopcurrent",
552         action_add_desktop,
553         setup_action_addremove_desktop_current
554     },
555     {
556         "removedesktopcurrent",
557         action_remove_desktop,
558         setup_action_addremove_desktop_current
559     },
560     {
561         NULL,
562         NULL,
563         NULL
564     }
565 };
566
567 /* only key bindings can be interactive. thus saith the xor.
568    because of how the mouse is grabbed, mouse events dont even get
569    read during interactive events, so no dice! >:) */
570 #define INTERACTIVE_LIMIT(a, uact) \
571     if (uact != OB_USER_ACTION_KEYBOARD_KEY) \
572         a->data.any.interactive = FALSE;
573
574 ObAction *action_from_string(const gchar *name, ObUserAction uact)
575 {
576     ObAction *a = NULL;
577     gboolean exist = FALSE;
578     gint i;
579
580     for (i = 0; actionstrings[i].name; i++)
581         if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
582             exist = TRUE;
583             a = action_new(actionstrings[i].func);
584             if (actionstrings[i].setup)
585                 actionstrings[i].setup(&a, uact);
586             if (a)
587                 INTERACTIVE_LIMIT(a, uact);
588             break;
589         }
590     if (!exist)
591         g_message(_("Invalid action '%s' requested. No such action exists."),
592                   name);
593     if (!a)
594         g_message(_("Invalid use of action '%s'. Action will be ignored."),
595                   name);
596     return a;
597 }
598
599 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
600                        ObUserAction uact)
601 {
602     gchar *actname;
603     ObAction *act = NULL;
604     xmlNodePtr n;
605
606     if (parse_attr_string("name", node, &actname)) {
607         if ((act = action_from_string(actname, uact))) {
608             } else if (act->func == action_resize_relative) {
609                 if ((n = parse_find_node("left", node->xmlChildrenNode)))
610                     act->data.relative.deltaxl = parse_int(doc, n);
611                 if ((n = parse_find_node("up", node->xmlChildrenNode)))
612                     act->data.relative.deltayu = parse_int(doc, n);
613                 if ((n = parse_find_node("right", node->xmlChildrenNode)))
614                     act->data.relative.deltax = parse_int(doc, n);
615                 if ((n = parse_find_node("down", node->xmlChildrenNode)))
616                     act->data.relative.deltay = parse_int(doc, n);
617             } else if (act->func == action_desktop) {
618                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
619                     act->data.desktop.desk = parse_int(doc, n);
620                 if (act->data.desktop.desk > 0) act->data.desktop.desk--;
621 /*
622                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
623                     act->data.desktop.inter.any.interactive =
624                         parse_bool(doc, n);
625 */
626            } else if (act->func == action_send_to_desktop) {
627                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
628                     act->data.sendto.desk = parse_int(doc, n);
629                 if (act->data.sendto.desk > 0) act->data.sendto.desk--;
630                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
631                     act->data.sendto.follow = parse_bool(doc, n);
632             } else if (act->func == action_desktop_dir) {
633                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
634                     act->data.desktopdir.wrap = parse_bool(doc, n); 
635                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
636                     act->data.desktopdir.inter.any.interactive =
637                         parse_bool(doc, n);
638             } else if (act->func == action_send_to_desktop_dir) {
639                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
640                     act->data.sendtodir.wrap = parse_bool(doc, n);
641                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
642                     act->data.sendtodir.follow = parse_bool(doc, n);
643                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
644                     act->data.sendtodir.inter.any.interactive =
645                         parse_bool(doc, n);
646             } else if (act->func == action_resize) {
647                 if ((n = parse_find_node("edge", node->xmlChildrenNode))) {
648                     gchar *s = parse_string(doc, n);
649                     if (!g_ascii_strcasecmp(s, "top"))
650                         act->data.moveresize.corner =
651                             prop_atoms.net_wm_moveresize_size_top;
652                     else if (!g_ascii_strcasecmp(s, "bottom"))
653                         act->data.moveresize.corner =
654                             prop_atoms.net_wm_moveresize_size_bottom;
655                     else if (!g_ascii_strcasecmp(s, "left"))
656                         act->data.moveresize.corner =
657                             prop_atoms.net_wm_moveresize_size_left;
658                     else if (!g_ascii_strcasecmp(s, "right"))
659                         act->data.moveresize.corner =
660                             prop_atoms.net_wm_moveresize_size_right;
661                     else if (!g_ascii_strcasecmp(s, "topleft"))
662                         act->data.moveresize.corner =
663                             prop_atoms.net_wm_moveresize_size_topleft;
664                     else if (!g_ascii_strcasecmp(s, "topright"))
665                         act->data.moveresize.corner =
666                             prop_atoms.net_wm_moveresize_size_topright;
667                     else if (!g_ascii_strcasecmp(s, "bottomleft"))
668                         act->data.moveresize.corner =
669                             prop_atoms.net_wm_moveresize_size_bottomleft;
670                     else if (!g_ascii_strcasecmp(s, "bottomright"))
671                         act->data.moveresize.corner =
672                             prop_atoms.net_wm_moveresize_size_bottomright;
673                     g_free(s);
674                 }
675             INTERACTIVE_LIMIT(act, uact);
676         }
677         g_free(actname);
678     }
679     return act;
680 }
681
682 void action_run_list(GSList *acts, ObClient *c, ObFrameContext context,
683                      guint state, guint button, gint x, gint y, Time time,
684                      gboolean cancel, gboolean done)
685 {
686     GSList *it;
687     ObAction *a;
688
689     if (!acts)
690         return;
691
692     if (x < 0 && y < 0)
693         screen_pointer_pos(&x, &y);
694
695     for (it = acts; it; it = g_slist_next(it)) {
696         a = it->data;
697
698         if (!(a->data.any.client_action == OB_CLIENT_ACTION_ALWAYS && !c)) {
699             a->data.any.c = a->data.any.client_action ? c : NULL;
700             a->data.any.context = context;
701             a->data.any.x = x;
702             a->data.any.y = y;
703
704             a->data.any.button = button;
705
706             a->data.any.time = time;
707
708             if (a->data.any.interactive) {
709                 a->data.inter.cancel = cancel;
710                 a->data.inter.final = done;
711                 if (!(cancel || done))
712                     if (!keyboard_interactive_grab(state, a->data.any.c, a))
713                         continue;
714             }
715
716             /* XXX UGLY HACK race with motion event starting a move and the
717                button release gettnig processed first. answer: don't queue
718                moveresize starts. UGLY HACK XXX
719
720                XXX ALSO don't queue showmenu events, because on button press
721                events we need to know if a mouse grab is going to take place,
722                and set the button to 0, so that later motion events don't think
723                that a drag is going on. since showmenu grabs the pointer..
724             */
725             if (a->data.any.interactive || a->func == action_move ||
726                 a->func == action_resize || a->func == action_showmenu)
727             {
728                 /* interactive actions are not queued */
729                 a->func(&a->data);
730             } else if (a->func == action_focus ||
731                        a->func == action_activate ||
732                        a->func == action_showmenu)
733             {
734                 /* XXX MORE UGLY HACK
735                    actions from clicks on client windows are NOT queued.
736                    this solves the mysterious click-and-drag-doesnt-work
737                    problem. it was because the window gets focused and stuff
738                    after the button event has already been passed through. i
739                    dont really know why it should care but it does and it makes
740                    a difference.
741
742                    however this very bogus ! !
743                    we want to send the button press to the window BEFORE
744                    we do the action because the action might move the windows
745                    (eg change desktops) and then the button press ends up on
746                    the completely wrong window !
747                    so, this is just for that bug, and it will only NOT queue it
748                    if it is a focusing action that can be used with the mouse
749                    pointer. ugh.
750
751                    also with the menus, there is a race going on. if the
752                    desktop wants to pop up a menu, and we do too, we send them
753                    the button before we pop up the menu, so they pop up their
754                    menu first. but not always. if we pop up our menu before
755                    sending them the button press, then the result is
756                    deterministic. yay.
757
758                    XXX further more. focus actions are not queued at all,
759                    because if you bind focus->showmenu, the menu will get
760                    hidden to do the focusing
761                 */
762                 a->func(&a->data);
763             } else
764                 ob_main_loop_queue_action(ob_main_loop, a);
765         }
766     }
767 }
768
769 void action_run_string(const gchar *name, struct _ObClient *c, Time time)
770 {
771     ObAction *a;
772     GSList *l;
773
774     a = action_from_string(name, OB_USER_ACTION_NONE);
775     g_assert(a);
776
777     l = g_slist_append(NULL, a);
778
779     action_run(l, c, 0, time);
780 }
781
782 void action_unshaderaise(union ActionData *data)
783 {
784     if (data->client.any.c->shaded)
785         action_unshade(data);
786     else
787         action_raise(data);
788 }
789
790 void action_shadelower(union ActionData *data)
791 {
792     if (data->client.any.c->shaded)
793         action_lower(data);
794     else
795         action_shade(data);
796 }
797
798 void action_resize_relative_horz(union ActionData *data)
799 {
800     ObClient *c = data->relative.any.c;
801     client_action_start(data);
802     client_resize(c,
803                   c->area.width + data->relative.deltax * c->size_inc.width,
804                   c->area.height);
805     client_action_end(data, FALSE);
806 }
807
808 void action_resize_relative_vert(union ActionData *data)
809 {
810     ObClient *c = data->relative.any.c;
811     if (!c->shaded) {
812         client_action_start(data);
813         client_resize(c, c->area.width, c->area.height +
814                       data->relative.deltax * c->size_inc.height);
815         client_action_end(data, FALSE);
816     }
817 }
818
819 void action_resize_relative(union ActionData *data)
820 {
821     ObClient *c = data->relative.any.c;
822     gint x, y, ow, xoff, nw, oh, yoff, nh, lw, lh;
823
824     client_action_start(data);
825
826     x = c->area.x;
827     y = c->area.y;
828     ow = c->area.width;
829     xoff = -data->relative.deltaxl * c->size_inc.width;
830     nw = ow + data->relative.deltax * c->size_inc.width
831         + data->relative.deltaxl * c->size_inc.width;
832     oh = c->area.height;
833     yoff = -data->relative.deltayu * c->size_inc.height;
834     nh = oh + data->relative.deltay * c->size_inc.height
835         + data->relative.deltayu * c->size_inc.height;
836
837     g_print("deltax %d %d x %d ow %d xoff %d nw %d\n",
838             data->relative.deltax, 
839             data->relative.deltaxl, 
840             x, ow, xoff, nw);
841     
842     client_try_configure(c, &x, &y, &nw, &nh, &lw, &lh, TRUE);
843     xoff = xoff == 0 ? 0 : (xoff < 0 ? MAX(xoff, ow-nw) : MIN(xoff, ow-nw));
844     yoff = yoff == 0 ? 0 : (yoff < 0 ? MAX(yoff, oh-nh) : MIN(yoff, oh-nh));
845     client_move_resize(c, x + xoff, y + yoff, nw, nh);
846     client_action_end(data, FALSE);
847 }
848
849 void action_send_to_desktop(union ActionData *data)
850 {
851     ObClient *c = data->sendto.any.c;
852
853     if (!client_normal(c)) return;
854
855     if (data->sendto.desk < screen_num_desktops ||
856         data->sendto.desk == DESKTOP_ALL) {
857         client_set_desktop(c, data->sendto.desk, data->sendto.follow, FALSE);
858         if (data->sendto.follow && data->sendto.desk != screen_desktop)
859             screen_set_desktop(data->sendto.desk, TRUE);
860     }
861 }
862
863 void action_desktop(union ActionData *data)
864 {
865     /* XXX add the interactive/dialog option back again once the dialog
866        has been made to not use grabs */
867     if (data->desktop.desk < screen_num_desktops ||
868         data->desktop.desk == DESKTOP_ALL)
869     {
870         screen_set_desktop(data->desktop.desk, TRUE);
871         if (data->inter.any.interactive)
872             screen_desktop_popup(data->desktop.desk, TRUE);
873     }
874 }
875
876 void action_desktop_dir(union ActionData *data)
877 {
878     guint d;
879
880     d = screen_cycle_desktop(data->desktopdir.dir,
881                              data->desktopdir.wrap,
882                              data->desktopdir.linear,
883                              data->desktopdir.inter.any.interactive,
884                              data->desktopdir.inter.final,
885                              data->desktopdir.inter.cancel);
886     /* only move the desktop when the action is complete. if we switch
887        desktops during the interactive action, focus will move but with
888        NotifyWhileGrabbed and applications don't like that. */
889     if (!data->sendtodir.inter.any.interactive ||
890         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
891     {
892         if (d != screen_desktop)
893             screen_set_desktop(d, TRUE);
894     }
895 }
896
897 void action_send_to_desktop_dir(union ActionData *data)
898 {
899     ObClient *c = data->sendtodir.inter.any.c;
900     guint d;
901
902     if (!client_normal(c)) return;
903
904     d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
905                              data->sendtodir.linear,
906                              data->sendtodir.inter.any.interactive,
907                              data->sendtodir.inter.final,
908                              data->sendtodir.inter.cancel);
909     /* only move the desktop when the action is complete. if we switch
910        desktops during the interactive action, focus will move but with
911        NotifyWhileGrabbed and applications don't like that. */
912     if (!data->sendtodir.inter.any.interactive ||
913         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
914     {
915         client_set_desktop(c, d, data->sendtodir.follow, FALSE);
916         if (data->sendtodir.follow && d != screen_desktop)
917             screen_set_desktop(d, TRUE);
918     }
919 }
920
921 void action_desktop_last(union ActionData *data)
922 {
923     if (screen_last_desktop < screen_num_desktops)
924         screen_set_desktop(screen_last_desktop, TRUE);
925 }
926
927 void action_toggle_decorations(union ActionData *data)
928 {
929     ObClient *c = data->client.any.c;
930
931     client_action_start(data);
932     client_set_undecorated(c, !c->undecorated);
933     client_action_end(data, FALSE);
934 }
935
936 static guint32 pick_corner(gint x, gint y, gint cx, gint cy, gint cw, gint ch,
937                            gboolean shaded)
938 {
939     /* let's make x and y client relative instead of screen relative */
940     x = x - cx;
941     y = ch - (y - cy); /* y is inverted, 0 is at the bottom of the window */
942
943 #define X x*ch/cw
944 #define A -4*X + 7*ch/3
945 #define B  4*X -15*ch/9
946 #define C -X/4 + 2*ch/3
947 #define D  X/4 + 5*ch/12
948 #define E  X/4 +   ch/3
949 #define F -X/4 + 7*ch/12
950 #define G  4*X - 4*ch/3
951 #define H -4*X + 8*ch/3
952 #define a (y > 5*ch/9)
953 #define b (x < 4*cw/9)
954 #define c (x > 5*cw/9)
955 #define d (y < 4*ch/9)
956
957     /*
958       Each of these defines (except X which is just there for fun), represents
959       the equation of a line. The lines they represent are shown in the diagram
960       below. Checking y against these lines, we are able to choose a region
961       of the window as shown.
962
963       +---------------------A-------|-------|-------B---------------------+
964       |                     |A                     B|                     |
965       |                     |A      |       |      B|                     |
966       |                     | A                   B |                     |
967       |                     | A     |       |     B |                     |
968       |                     |  A                 B  |                     |
969       |                     |  A    |       |    B  |                     |
970       |        northwest    |   A     north     B   |   northeast         |
971       |                     |   A   |       |   B   |                     |
972       |                     |    A             B    |                     |
973       C---------------------+----A--+-------+--B----+---------------------D
974       |CCCCCCC              |     A           B     |              DDDDDDD|
975       |       CCCCCCCC      |     A |       | B     |      DDDDDDDD       |
976       |               CCCCCCC      A         B      DDDDDDD               |
977       - - - - - - - - - - - +CCCCCCC+aaaaaaa+DDDDDDD+ - - - - - - - - - - - -
978       |                     |       b       c       |                     | sh
979       |             west    |       b  move c       |   east              | ad
980       |                     |       b       c       |                     | ed
981       - - - - - - - - - - - +EEEEEEE+ddddddd+FFFFFFF+- - - - - - - - - - -  -
982       |               EEEEEEE      G         H      FFFFFFF               |
983       |       EEEEEEEE      |     G |       | H     |      FFFFFFFF       |
984       |EEEEEEE              |     G           H     |              FFFFFFF|
985       E---------------------+----G--+-------+--H----+---------------------F
986       |                     |    G             H    |                     |
987       |                     |   G   |       |   H   |                     |
988       |        southwest    |   G     south     H   |   southeast         |
989       |                     |  G    |       |    H  |                     |
990       |                     |  G                 H  |                     |
991       |                     | G     |       |     H |                     |
992       |                     | G                   H |                     |
993       |                     |G      |       |      H|                     |
994       |                     |G                     H|                     |
995       +---------------------G-------|-------|-------H---------------------+
996     */
997
998     if (shaded) {
999         /* for shaded windows, you can only resize west/east and move */
1000         if (b)
1001             return prop_atoms.net_wm_moveresize_size_left;
1002         if (c)
1003             return prop_atoms.net_wm_moveresize_size_right;
1004         return prop_atoms.net_wm_moveresize_move;
1005     }
1006
1007     if (y < A && y >= C)
1008         return prop_atoms.net_wm_moveresize_size_topleft;
1009     else if (y >= A && y >= B && a)
1010         return prop_atoms.net_wm_moveresize_size_top;
1011     else if (y < B && y >= D)
1012         return prop_atoms.net_wm_moveresize_size_topright;
1013     else if (y < C && y >= E && b)
1014         return prop_atoms.net_wm_moveresize_size_left;
1015     else if (y < D && y >= F && c)
1016         return prop_atoms.net_wm_moveresize_size_right;
1017     else if (y < E && y >= G)
1018         return prop_atoms.net_wm_moveresize_size_bottomleft;
1019     else if (y < G && y < H && d)
1020         return prop_atoms.net_wm_moveresize_size_bottom;
1021     else if (y >= H && y < F)
1022         return prop_atoms.net_wm_moveresize_size_bottomright;
1023     else
1024         return prop_atoms.net_wm_moveresize_move;
1025
1026 #undef X
1027 #undef A
1028 #undef B
1029 #undef C
1030 #undef D
1031 #undef E
1032 #undef F
1033 #undef G
1034 #undef H
1035 #undef a
1036 #undef b
1037 #undef c
1038 #undef d
1039 }
1040
1041 void action_resize(union ActionData *data)
1042 {
1043     ObClient *c = data->moveresize.any.c;
1044     guint32 corner;
1045
1046     if (data->moveresize.keyboard)
1047         corner = prop_atoms.net_wm_moveresize_size_keyboard;
1048     else if (data->moveresize.corner)
1049         corner = data->moveresize.corner; /* it was specified in the binding */
1050     else
1051         corner = pick_corner(data->any.x, data->any.y,
1052                              c->frame->area.x, c->frame->area.y,
1053                              /* use the client size because the frame
1054                                 can be differently sized (shaded
1055                                 windows) and we want this based on the
1056                                 clients size */
1057                              c->area.width + c->frame->size.left +
1058                              c->frame->size.right,
1059                              c->area.height + c->frame->size.top +
1060                              c->frame->size.bottom, c->shaded);
1061
1062     moveresize_start(c, data->any.x, data->any.y, data->any.button, corner);
1063 }
1064
1065 void action_directional_focus(union ActionData *data)
1066 {
1067     /* if using focus_delay, stop the timer now so that focus doesn't go moving
1068        on us */
1069     event_halt_focus_delay();
1070
1071     focus_directional_cycle(data->interdiraction.direction,
1072                             data->interdiraction.dock_windows,
1073                             data->interdiraction.desktop_windows,
1074                             data->any.interactive,
1075                             data->interdiraction.dialog,
1076                             data->interdiraction.inter.final,
1077                             data->interdiraction.inter.cancel);
1078 }
1079
1080 void action_movetoedge(union ActionData *data)
1081 {
1082     gint x, y;
1083     ObClient *c = data->diraction.any.c;
1084
1085     x = c->frame->area.x;
1086     y = c->frame->area.y;
1087     
1088     switch(data->diraction.direction) {
1089     case OB_DIRECTION_NORTH:
1090         y = client_directional_edge_search(c, OB_DIRECTION_NORTH,
1091                                            data->diraction.hang)
1092             - (data->diraction.hang ? c->frame->area.height : 0);
1093         break;
1094     case OB_DIRECTION_WEST:
1095         x = client_directional_edge_search(c, OB_DIRECTION_WEST,
1096                                            data->diraction.hang)
1097             - (data->diraction.hang ? c->frame->area.width : 0);
1098         break;
1099     case OB_DIRECTION_SOUTH:
1100         y = client_directional_edge_search(c, OB_DIRECTION_SOUTH,
1101                                            data->diraction.hang)
1102             - (data->diraction.hang ? 0 : c->frame->area.height);
1103         break;
1104     case OB_DIRECTION_EAST:
1105         x = client_directional_edge_search(c, OB_DIRECTION_EAST,
1106                                            data->diraction.hang)
1107             - (data->diraction.hang ? 0 : c->frame->area.width);
1108         break;
1109     default:
1110         g_assert_not_reached();
1111     }
1112     frame_frame_gravity(c->frame, &x, &y, c->area.width, c->area.height);
1113     client_action_start(data);
1114     client_move(c, x, y);
1115     client_action_end(data, FALSE);
1116 }
1117
1118 void action_growtoedge(union ActionData *data)
1119 {
1120     gint x, y, width, height, dest;
1121     ObClient *c = data->diraction.any.c;
1122     Rect *a;
1123
1124     a = screen_area(c->desktop, SCREEN_AREA_ALL_MONITORS, &c->frame->area);
1125     x = c->frame->area.x;
1126     y = c->frame->area.y;
1127     /* get the unshaded frame's dimensions..if it is shaded */
1128     width = c->area.width + c->frame->size.left + c->frame->size.right;
1129     height = c->area.height + c->frame->size.top + c->frame->size.bottom;
1130
1131     switch(data->diraction.direction) {
1132     case OB_DIRECTION_NORTH:
1133         if (c->shaded) break; /* don't allow vertical resize if shaded */
1134
1135         dest = client_directional_edge_search(c, OB_DIRECTION_NORTH, FALSE);
1136         if (a->y == y)
1137             height = height / 2;
1138         else {
1139             height = c->frame->area.y + height - dest;
1140             y = dest;
1141         }
1142         break;
1143     case OB_DIRECTION_WEST:
1144         dest = client_directional_edge_search(c, OB_DIRECTION_WEST, FALSE);
1145         if (a->x == x)
1146             width = width / 2;
1147         else {
1148             width = c->frame->area.x + width - dest;
1149             x = dest;
1150         }
1151         break;
1152     case OB_DIRECTION_SOUTH:
1153         if (c->shaded) break; /* don't allow vertical resize if shaded */
1154
1155         dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH, FALSE);
1156         if (a->y + a->height == y + c->frame->area.height) {
1157             height = c->frame->area.height / 2;
1158             y = a->y + a->height - height;
1159         } else
1160             height = dest - c->frame->area.y;
1161         y += (height - c->frame->area.height) % c->size_inc.height;
1162         height -= (height - c->frame->area.height) % c->size_inc.height;
1163         break;
1164     case OB_DIRECTION_EAST:
1165         dest = client_directional_edge_search(c, OB_DIRECTION_EAST, FALSE);
1166         if (a->x + a->width == x + c->frame->area.width) {
1167             width = c->frame->area.width / 2;
1168             x = a->x + a->width - width;
1169         } else
1170             width = dest - c->frame->area.x;
1171         x += (width - c->frame->area.width) % c->size_inc.width;
1172         width -= (width - c->frame->area.width) % c->size_inc.width;
1173         break;
1174     default:
1175         g_assert_not_reached();
1176     }
1177     width -= c->frame->size.left + c->frame->size.right;
1178     height -= c->frame->size.top + c->frame->size.bottom;
1179     frame_frame_gravity(c->frame, &x, &y, width, height);
1180     client_action_start(data);
1181     client_move_resize(c, x, y, width, height);
1182     client_action_end(data, FALSE);
1183     g_free(a);
1184 }
1185
1186 void action_send_to_layer(union ActionData *data)
1187 {
1188     client_set_layer(data->layer.any.c, data->layer.layer);
1189 }
1190
1191 void action_toggle_layer(union ActionData *data)
1192 {
1193     ObClient *c = data->layer.any.c;
1194
1195     client_action_start(data);
1196     if (data->layer.layer < 0)
1197         client_set_layer(c, c->below ? 0 : -1);
1198     else if (data->layer.layer > 0)
1199         client_set_layer(c, c->above ? 0 : 1);
1200     client_action_end(data, config_focus_under_mouse);
1201 }
1202
1203 void action_toggle_dockautohide(union ActionData *data)
1204 {
1205     config_dock_hide = !config_dock_hide;
1206     dock_configure();
1207 }
1208
1209 void action_add_desktop(union ActionData *data)
1210 {
1211     client_action_start(data);
1212     screen_set_num_desktops(screen_num_desktops+1);
1213
1214     /* move all the clients over */
1215     if (data->addremovedesktop.current) {
1216         GList *it;
1217
1218         for (it = client_list; it; it = g_list_next(it)) {
1219             ObClient *c = it->data;
1220             if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop)
1221                 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
1222         }
1223     }
1224
1225     client_action_end(data, config_focus_under_mouse);
1226 }
1227
1228 void action_remove_desktop(union ActionData *data)
1229 {
1230     guint rmdesktop, movedesktop;
1231     GList *it, *stacking_copy;
1232
1233     if (screen_num_desktops < 2) return;
1234
1235     client_action_start(data);
1236
1237     /* what desktop are we removing and moving to? */
1238     if (data->addremovedesktop.current)
1239         rmdesktop = screen_desktop;
1240     else
1241         rmdesktop = screen_num_desktops - 1;
1242     if (rmdesktop < screen_num_desktops - 1)
1243         movedesktop = rmdesktop + 1;
1244     else
1245         movedesktop = rmdesktop;
1246
1247     /* make a copy of the list cuz we're changing it */
1248     stacking_copy = g_list_copy(stacking_list);
1249     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
1250         if (WINDOW_IS_CLIENT(it->data)) {
1251             ObClient *c = it->data;
1252             guint d = c->desktop;
1253             if (d != DESKTOP_ALL && d >= movedesktop) {
1254                 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
1255                 ob_debug("moving window %s\n", c->title);
1256             }
1257             /* raise all the windows that are on the current desktop which
1258                is being merged */
1259             if ((screen_desktop == rmdesktop - 1 ||
1260                  screen_desktop == rmdesktop) &&
1261                 (d == DESKTOP_ALL || d == screen_desktop))
1262             {
1263                 stacking_raise(CLIENT_AS_WINDOW(c));
1264                 ob_debug("raising window %s\n", c->title);
1265             }
1266         }
1267     }
1268
1269     /* act like we're changing desktops */
1270     if (screen_desktop < screen_num_desktops - 1) {
1271         gint d = screen_desktop;
1272         screen_desktop = screen_last_desktop;
1273         screen_set_desktop(d, TRUE);
1274         ob_debug("fake desktop change\n");
1275     }
1276
1277     screen_set_num_desktops(screen_num_desktops-1);
1278
1279     client_action_end(data, config_focus_under_mouse);
1280 }