add directionaldesktop action
[mikachu/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) 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_prev(ObAction **a, ObUserAction uact)
166 {
167     (*a)->data.desktopdir.inter.any.interactive = TRUE;
168     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
169     (*a)->data.desktopdir.linear = TRUE;
170     (*a)->data.desktopdir.wrap = TRUE;
171 }
172
173 void setup_action_desktop_next(ObAction **a, ObUserAction uact)
174 {
175     (*a)->data.desktopdir.inter.any.interactive = TRUE;
176     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
177     (*a)->data.desktopdir.linear = TRUE;
178     (*a)->data.desktopdir.wrap = TRUE;
179 }
180
181 void setup_action_desktop_left(ObAction **a, ObUserAction uact)
182 {
183     (*a)->data.desktopdir.inter.any.interactive = TRUE;
184     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
185     (*a)->data.desktopdir.linear = FALSE;
186     (*a)->data.desktopdir.wrap = TRUE;
187 }
188
189 void setup_action_desktop_right(ObAction **a, ObUserAction uact)
190 {
191     (*a)->data.desktopdir.inter.any.interactive = TRUE;
192     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
193     (*a)->data.desktopdir.linear = FALSE;
194     (*a)->data.desktopdir.wrap = TRUE;
195 }
196
197 void setup_action_desktop_up(ObAction **a, ObUserAction uact)
198 {
199     (*a)->data.desktopdir.inter.any.interactive = TRUE;
200     (*a)->data.desktopdir.dir = OB_DIRECTION_NORTH;
201     (*a)->data.desktopdir.linear = FALSE;
202     (*a)->data.desktopdir.wrap = TRUE;
203 }
204
205 void setup_action_desktop_down(ObAction **a, ObUserAction uact)
206 {
207     (*a)->data.desktopdir.inter.any.interactive = TRUE;
208     (*a)->data.desktopdir.dir = OB_DIRECTION_SOUTH;
209     (*a)->data.desktopdir.linear = FALSE;
210     (*a)->data.desktopdir.wrap = TRUE;
211 }
212
213 void setup_action_movefromedge_north(ObAction **a, ObUserAction uact)
214 {
215     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
216     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
217     (*a)->data.diraction.hang = TRUE;
218 }
219
220 void setup_action_movefromedge_south(ObAction **a, ObUserAction uact)
221 {
222     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
223     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
224     (*a)->data.diraction.hang = TRUE;
225 }
226
227 void setup_action_movefromedge_east(ObAction **a, ObUserAction uact)
228 {
229     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
230     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
231     (*a)->data.diraction.hang = TRUE;
232 }
233
234 void setup_action_movefromedge_west(ObAction **a, ObUserAction uact)
235 {
236     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
237     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
238     (*a)->data.diraction.hang = TRUE;
239 }
240
241 void setup_action_movetoedge_north(ObAction **a, ObUserAction uact)
242 {
243     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
244     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
245     (*a)->data.diraction.hang = FALSE;
246 }
247
248 void setup_action_movetoedge_south(ObAction **a, ObUserAction uact)
249 {
250     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
251     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
252     (*a)->data.diraction.hang = FALSE;
253 }
254
255 void setup_action_movetoedge_east(ObAction **a, ObUserAction uact)
256 {
257     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
258     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
259     (*a)->data.diraction.hang = FALSE;
260 }
261
262 void setup_action_movetoedge_west(ObAction **a, ObUserAction uact)
263 {
264     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
265     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
266     (*a)->data.diraction.hang = FALSE;
267 }
268
269 void setup_action_growtoedge_north(ObAction **a, ObUserAction uact)
270 {
271     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
272     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
273 }
274
275 void setup_action_growtoedge_south(ObAction **a, ObUserAction uact)
276 {
277     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
278     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
279 }
280
281 void setup_action_growtoedge_east(ObAction **a, ObUserAction uact)
282 {
283     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
284     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
285 }
286
287 void setup_action_growtoedge_west(ObAction **a, ObUserAction uact)
288 {
289     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
290     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
291 }
292
293 void setup_action_top_layer(ObAction **a, ObUserAction uact)
294 {
295     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
296     (*a)->data.layer.layer = 1;
297 }
298
299 void setup_action_normal_layer(ObAction **a, ObUserAction uact)
300 {
301     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
302     (*a)->data.layer.layer = 0;
303 }
304
305 void setup_action_bottom_layer(ObAction **a, ObUserAction uact)
306 {
307     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
308     (*a)->data.layer.layer = -1;
309 }
310
311 void setup_action_resize(ObAction **a, ObUserAction uact)
312 {
313     (*a)->data.moveresize.any.client_action = OB_CLIENT_ACTION_ALWAYS;
314     (*a)->data.moveresize.keyboard =
315         (uact == OB_USER_ACTION_NONE ||
316          uact == OB_USER_ACTION_KEYBOARD_KEY ||
317          uact == OB_USER_ACTION_MENU_SELECTION);
318     (*a)->data.moveresize.corner = 0;
319 }
320
321 void setup_action_addremove_desktop_current(ObAction **a, ObUserAction uact)
322 {
323     (*a)->data.addremovedesktop.current = TRUE;
324 }
325
326 void setup_action_addremove_desktop_last(ObAction **a, ObUserAction uact)
327 {
328     (*a)->data.addremovedesktop.current = FALSE;
329 }
330
331 void setup_client_action(ObAction **a, ObUserAction uact)
332 {
333     (*a)->data.any.client_action = OB_CLIENT_ACTION_ALWAYS;
334 }
335
336 ActionString actionstrings[] =
337 {
338     {
339         "shadelower",
340         action_shadelower,
341         setup_client_action
342     },
343     {
344         "unshaderaise",
345         action_unshaderaise,
346         setup_client_action
347     },
348     {
349         "resizerelativevert",
350         action_resize_relative_vert,
351         setup_client_action
352     },
353     {
354         "resizerelative",
355         action_resize_relative,
356         setup_client_action
357     },
358     {
359         "sendtodesktop",
360         action_send_to_desktop,
361         setup_action_send_to_desktop
362     },
363     {
364         "sendtodesktopnext",
365         action_send_to_desktop_dir,
366         setup_action_send_to_desktop_next
367     },
368     {
369         "sendtodesktopprevious",
370         action_send_to_desktop_dir,
371         setup_action_send_to_desktop_prev
372     },
373     {
374         "sendtodesktopright",
375         action_send_to_desktop_dir,
376         setup_action_send_to_desktop_right
377     },
378     {
379         "sendtodesktopleft",
380         action_send_to_desktop_dir,
381         setup_action_send_to_desktop_left
382     },
383     {
384         "sendtodesktopup",
385         action_send_to_desktop_dir,
386         setup_action_send_to_desktop_up
387     },
388     {
389         "sendtodesktopdown",
390         action_send_to_desktop_dir,
391         setup_action_send_to_desktop_down
392     },
393     {
394         "toggledockautohide",
395         action_toggle_dockautohide,
396         NULL
397     },
398     {
399         "desktoplast",
400         action_desktop_last,
401         NULL
402     },
403     {
404         "sendtotoplayer",
405         action_send_to_layer,
406         setup_action_top_layer
407     },
408     {
409         "togglealwaysontop",
410         action_toggle_layer,
411         setup_action_top_layer
412     },
413     {
414         "sendtonormallayer",
415         action_send_to_layer,
416         setup_action_normal_layer
417     },
418     {
419         "sendtobottomlayer",
420         action_send_to_layer,
421         setup_action_bottom_layer
422     },
423     {
424         "togglealwaysonbottom",
425         action_toggle_layer,
426         setup_action_bottom_layer
427     },
428     {
429         "movefromedgenorth",
430         action_movetoedge,
431         setup_action_movefromedge_north
432     },
433     {
434         "movefromedgesouth",
435         action_movetoedge,
436         setup_action_movefromedge_south
437     },
438     {
439         "movefromedgewest",
440         action_movetoedge,
441         setup_action_movefromedge_west
442     },
443     {
444         "movefromedgeeast",
445         action_movetoedge,
446         setup_action_movefromedge_east
447     },
448     {
449         "movetoedgenorth",
450         action_movetoedge,
451         setup_action_movetoedge_north
452     },
453     {
454         "movetoedgesouth",
455         action_movetoedge,
456         setup_action_movetoedge_south
457     },
458     {
459         "movetoedgewest",
460         action_movetoedge,
461         setup_action_movetoedge_west
462     },
463     {
464         "movetoedgeeast",
465         action_movetoedge,
466         setup_action_movetoedge_east
467     },
468     {
469         "growtoedgenorth",
470         action_growtoedge,
471         setup_action_growtoedge_north
472     },
473     {
474         "growtoedgesouth",
475         action_growtoedge,
476         setup_action_growtoedge_south
477     },
478     {
479         "growtoedgewest",
480         action_growtoedge,
481         setup_action_growtoedge_west
482     },
483     {
484         "growtoedgeeast",
485         action_growtoedge,
486         setup_action_growtoedge_east
487     },
488     {
489         "adddesktoplast",
490         action_add_desktop,
491         setup_action_addremove_desktop_last
492     },
493     {
494         "removedesktoplast",
495         action_remove_desktop,
496         setup_action_addremove_desktop_last
497     },
498     {
499         "adddesktopcurrent",
500         action_add_desktop,
501         setup_action_addremove_desktop_current
502     },
503     {
504         "removedesktopcurrent",
505         action_remove_desktop,
506         setup_action_addremove_desktop_current
507     },
508     {
509         NULL,
510         NULL,
511         NULL
512     }
513 };
514
515 /* only key bindings can be interactive. thus saith the xor.
516    because of how the mouse is grabbed, mouse events dont even get
517    read during interactive events, so no dice! >:) */
518 #define INTERACTIVE_LIMIT(a, uact) \
519     if (uact != OB_USER_ACTION_KEYBOARD_KEY) \
520         a->data.any.interactive = FALSE;
521
522 ObAction *action_from_string(const gchar *name, ObUserAction uact)
523 {
524     ObAction *a = NULL;
525     gboolean exist = FALSE;
526     gint i;
527
528     for (i = 0; actionstrings[i].name; i++)
529         if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
530             exist = TRUE;
531             a = action_new(actionstrings[i].func);
532             if (actionstrings[i].setup)
533                 actionstrings[i].setup(&a, uact);
534             if (a)
535                 INTERACTIVE_LIMIT(a, uact);
536             break;
537         }
538     if (!exist)
539         g_message(_("Invalid action '%s' requested. No such action exists."),
540                   name);
541     if (!a)
542         g_message(_("Invalid use of action '%s'. Action will be ignored."),
543                   name);
544     return a;
545 }
546
547 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
548                        ObUserAction uact)
549 {
550     gchar *actname;
551     ObAction *act = NULL;
552     xmlNodePtr n;
553
554     if (parse_attr_string("name", node, &actname)) {
555         if ((act = action_from_string(actname, uact))) {
556             } else if (act->func == action_resize_relative) {
557                 if ((n = parse_find_node("left", node->xmlChildrenNode)))
558                     act->data.relative.deltaxl = parse_int(doc, n);
559                 if ((n = parse_find_node("up", node->xmlChildrenNode)))
560                     act->data.relative.deltayu = parse_int(doc, n);
561                 if ((n = parse_find_node("right", node->xmlChildrenNode)))
562                     act->data.relative.deltax = parse_int(doc, n);
563                 if ((n = parse_find_node("down", node->xmlChildrenNode)))
564                     act->data.relative.deltay = parse_int(doc, n);
565             } else if (act->func == action_desktop) {
566                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
567                     act->data.desktop.desk = parse_int(doc, n);
568                 if (act->data.desktop.desk > 0) act->data.desktop.desk--;
569 /*
570                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
571                     act->data.desktop.inter.any.interactive =
572                         parse_bool(doc, n);
573 */
574            } else if (act->func == action_send_to_desktop) {
575                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
576                     act->data.sendto.desk = parse_int(doc, n);
577                 if (act->data.sendto.desk > 0) act->data.sendto.desk--;
578                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
579                     act->data.sendto.follow = parse_bool(doc, n);
580             } else if (act->func == action_send_to_desktop_dir) {
581                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
582                     act->data.sendtodir.wrap = parse_bool(doc, n);
583                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
584                     act->data.sendtodir.follow = parse_bool(doc, n);
585                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
586                     act->data.sendtodir.inter.any.interactive =
587                         parse_bool(doc, n);
588             INTERACTIVE_LIMIT(act, uact);
589         }
590         g_free(actname);
591     }
592     return act;
593 }
594
595 void action_run_list(GSList *acts, ObClient *c, ObFrameContext context,
596                      guint state, guint button, gint x, gint y, Time time,
597                      gboolean cancel, gboolean done)
598 {
599     GSList *it;
600     ObAction *a;
601
602     if (!acts)
603         return;
604
605     if (x < 0 && y < 0)
606         screen_pointer_pos(&x, &y);
607
608     for (it = acts; it; it = g_slist_next(it)) {
609         a = it->data;
610
611         if (!(a->data.any.client_action == OB_CLIENT_ACTION_ALWAYS && !c)) {
612             a->data.any.c = a->data.any.client_action ? c : NULL;
613             a->data.any.context = context;
614             a->data.any.x = x;
615             a->data.any.y = y;
616
617             a->data.any.button = button;
618
619             a->data.any.time = time;
620
621             if (a->data.any.interactive) {
622                 a->data.inter.cancel = cancel;
623                 a->data.inter.final = done;
624                 if (!(cancel || done))
625                     if (!keyboard_interactive_grab(state, a->data.any.c, a))
626                         continue;
627             }
628
629             /* XXX UGLY HACK race with motion event starting a move and the
630                button release gettnig processed first. answer: don't queue
631                moveresize starts. UGLY HACK XXX
632
633                XXX ALSO don't queue showmenu events, because on button press
634                events we need to know if a mouse grab is going to take place,
635                and set the button to 0, so that later motion events don't think
636                that a drag is going on. since showmenu grabs the pointer..
637             */
638             if (a->data.any.interactive || a->func == action_move ||
639                 a->func == action_resize || a->func == action_showmenu)
640             {
641                 /* interactive actions are not queued */
642                 a->func(&a->data);
643             } else if (a->func == action_focus ||
644                        a->func == action_activate ||
645                        a->func == action_showmenu)
646             {
647                 /* XXX MORE UGLY HACK
648                    actions from clicks on client windows are NOT queued.
649                    this solves the mysterious click-and-drag-doesnt-work
650                    problem. it was because the window gets focused and stuff
651                    after the button event has already been passed through. i
652                    dont really know why it should care but it does and it makes
653                    a difference.
654
655                    however this very bogus ! !
656                    we want to send the button press to the window BEFORE
657                    we do the action because the action might move the windows
658                    (eg change desktops) and then the button press ends up on
659                    the completely wrong window !
660                    so, this is just for that bug, and it will only NOT queue it
661                    if it is a focusing action that can be used with the mouse
662                    pointer. ugh.
663
664                    also with the menus, there is a race going on. if the
665                    desktop wants to pop up a menu, and we do too, we send them
666                    the button before we pop up the menu, so they pop up their
667                    menu first. but not always. if we pop up our menu before
668                    sending them the button press, then the result is
669                    deterministic. yay.
670
671                    XXX further more. focus actions are not queued at all,
672                    because if you bind focus->showmenu, the menu will get
673                    hidden to do the focusing
674                 */
675                 a->func(&a->data);
676             } else
677                 ob_main_loop_queue_action(ob_main_loop, a);
678         }
679     }
680 }
681
682 void action_run_string(const gchar *name, struct _ObClient *c, Time time)
683 {
684     ObAction *a;
685     GSList *l;
686
687     a = action_from_string(name, OB_USER_ACTION_NONE);
688     g_assert(a);
689
690     l = g_slist_append(NULL, a);
691
692     action_run(l, c, 0, time);
693 }
694
695 void action_unshaderaise(union ActionData *data)
696 {
697     if (data->client.any.c->shaded)
698         action_unshade(data);
699     else
700         action_raise(data);
701 }
702
703 void action_shadelower(union ActionData *data)
704 {
705     if (data->client.any.c->shaded)
706         action_lower(data);
707     else
708         action_shade(data);
709 }
710
711 void action_resize_relative_horz(union ActionData *data)
712 {
713     ObClient *c = data->relative.any.c;
714     client_action_start(data);
715     client_resize(c,
716                   c->area.width + data->relative.deltax * c->size_inc.width,
717                   c->area.height);
718     client_action_end(data, FALSE);
719 }
720
721 void action_resize_relative_vert(union ActionData *data)
722 {
723     ObClient *c = data->relative.any.c;
724     if (!c->shaded) {
725         client_action_start(data);
726         client_resize(c, c->area.width, c->area.height +
727                       data->relative.deltax * c->size_inc.height);
728         client_action_end(data, FALSE);
729     }
730 }
731
732 void action_resize_relative(union ActionData *data)
733 {
734     ObClient *c = data->relative.any.c;
735     gint x, y, ow, xoff, nw, oh, yoff, nh, lw, lh;
736
737     client_action_start(data);
738
739     x = c->area.x;
740     y = c->area.y;
741     ow = c->area.width;
742     xoff = -data->relative.deltaxl * c->size_inc.width;
743     nw = ow + data->relative.deltax * c->size_inc.width
744         + data->relative.deltaxl * c->size_inc.width;
745     oh = c->area.height;
746     yoff = -data->relative.deltayu * c->size_inc.height;
747     nh = oh + data->relative.deltay * c->size_inc.height
748         + data->relative.deltayu * c->size_inc.height;
749
750     g_print("deltax %d %d x %d ow %d xoff %d nw %d\n",
751             data->relative.deltax, 
752             data->relative.deltaxl, 
753             x, ow, xoff, nw);
754     
755     client_try_configure(c, &x, &y, &nw, &nh, &lw, &lh, TRUE);
756     xoff = xoff == 0 ? 0 : (xoff < 0 ? MAX(xoff, ow-nw) : MIN(xoff, ow-nw));
757     yoff = yoff == 0 ? 0 : (yoff < 0 ? MAX(yoff, oh-nh) : MIN(yoff, oh-nh));
758     client_move_resize(c, x + xoff, y + yoff, nw, nh);
759     client_action_end(data, FALSE);
760 }
761
762 void action_send_to_desktop(union ActionData *data)
763 {
764     ObClient *c = data->sendto.any.c;
765
766     if (!client_normal(c)) return;
767
768     if (data->sendto.desk < screen_num_desktops ||
769         data->sendto.desk == DESKTOP_ALL) {
770         client_set_desktop(c, data->sendto.desk, data->sendto.follow, FALSE);
771         if (data->sendto.follow && data->sendto.desk != screen_desktop)
772             screen_set_desktop(data->sendto.desk, TRUE);
773     }
774 }
775
776 void action_desktop_dir(union ActionData *data)
777 {
778     guint d;
779
780     d = screen_cycle_desktop(data->desktopdir.dir,
781                              data->desktopdir.wrap,
782                              data->desktopdir.linear,
783                              data->desktopdir.inter.any.interactive,
784                              data->desktopdir.inter.final,
785                              data->desktopdir.inter.cancel);
786     /* only move the desktop when the action is complete. if we switch
787        desktops during the interactive action, focus will move but with
788        NotifyWhileGrabbed and applications don't like that. */
789     if (!data->sendtodir.inter.any.interactive ||
790         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
791     {
792         if (d != screen_desktop)
793             screen_set_desktop(d, TRUE);
794     }
795 }
796
797 void action_send_to_desktop_dir(union ActionData *data)
798 {
799     ObClient *c = data->sendtodir.inter.any.c;
800     guint d;
801
802     if (!client_normal(c)) return;
803
804     d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
805                              data->sendtodir.linear,
806                              data->sendtodir.inter.any.interactive,
807                              data->sendtodir.inter.final,
808                              data->sendtodir.inter.cancel);
809     /* only move the desktop when the action is complete. if we switch
810        desktops during the interactive action, focus will move but with
811        NotifyWhileGrabbed and applications don't like that. */
812     if (!data->sendtodir.inter.any.interactive ||
813         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
814     {
815         client_set_desktop(c, d, data->sendtodir.follow, FALSE);
816         if (data->sendtodir.follow && d != screen_desktop)
817             screen_set_desktop(d, TRUE);
818     }
819 }
820
821 void action_desktop_last(union ActionData *data)
822 {
823     if (screen_last_desktop < screen_num_desktops)
824         screen_set_desktop(screen_last_desktop, TRUE);
825 }
826
827 void action_directional_focus(union ActionData *data)
828 {
829     /* if using focus_delay, stop the timer now so that focus doesn't go moving
830        on us */
831     event_halt_focus_delay();
832
833     focus_directional_cycle(data->interdiraction.direction,
834                             data->interdiraction.dock_windows,
835                             data->interdiraction.desktop_windows,
836                             data->any.interactive,
837                             data->interdiraction.dialog,
838                             data->interdiraction.inter.final,
839                             data->interdiraction.inter.cancel);
840 }
841
842 void action_movetoedge(union ActionData *data)
843 {
844     gint x, y;
845     ObClient *c = data->diraction.any.c;
846
847     x = c->frame->area.x;
848     y = c->frame->area.y;
849     
850     switch(data->diraction.direction) {
851     case OB_DIRECTION_NORTH:
852         y = client_directional_edge_search(c, OB_DIRECTION_NORTH,
853                                            data->diraction.hang)
854             - (data->diraction.hang ? c->frame->area.height : 0);
855         break;
856     case OB_DIRECTION_WEST:
857         x = client_directional_edge_search(c, OB_DIRECTION_WEST,
858                                            data->diraction.hang)
859             - (data->diraction.hang ? c->frame->area.width : 0);
860         break;
861     case OB_DIRECTION_SOUTH:
862         y = client_directional_edge_search(c, OB_DIRECTION_SOUTH,
863                                            data->diraction.hang)
864             - (data->diraction.hang ? 0 : c->frame->area.height);
865         break;
866     case OB_DIRECTION_EAST:
867         x = client_directional_edge_search(c, OB_DIRECTION_EAST,
868                                            data->diraction.hang)
869             - (data->diraction.hang ? 0 : c->frame->area.width);
870         break;
871     default:
872         g_assert_not_reached();
873     }
874     frame_frame_gravity(c->frame, &x, &y, c->area.width, c->area.height);
875     client_action_start(data);
876     client_move(c, x, y);
877     client_action_end(data, FALSE);
878 }
879
880 void action_growtoedge(union ActionData *data)
881 {
882     gint x, y, width, height, dest;
883     ObClient *c = data->diraction.any.c;
884     Rect *a;
885
886     a = screen_area(c->desktop, SCREEN_AREA_ALL_MONITORS, &c->frame->area);
887     x = c->frame->area.x;
888     y = c->frame->area.y;
889     /* get the unshaded frame's dimensions..if it is shaded */
890     width = c->area.width + c->frame->size.left + c->frame->size.right;
891     height = c->area.height + c->frame->size.top + c->frame->size.bottom;
892
893     switch(data->diraction.direction) {
894     case OB_DIRECTION_NORTH:
895         if (c->shaded) break; /* don't allow vertical resize if shaded */
896
897         dest = client_directional_edge_search(c, OB_DIRECTION_NORTH, FALSE);
898         if (a->y == y)
899             height = height / 2;
900         else {
901             height = c->frame->area.y + height - dest;
902             y = dest;
903         }
904         break;
905     case OB_DIRECTION_WEST:
906         dest = client_directional_edge_search(c, OB_DIRECTION_WEST, FALSE);
907         if (a->x == x)
908             width = width / 2;
909         else {
910             width = c->frame->area.x + width - dest;
911             x = dest;
912         }
913         break;
914     case OB_DIRECTION_SOUTH:
915         if (c->shaded) break; /* don't allow vertical resize if shaded */
916
917         dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH, FALSE);
918         if (a->y + a->height == y + c->frame->area.height) {
919             height = c->frame->area.height / 2;
920             y = a->y + a->height - height;
921         } else
922             height = dest - c->frame->area.y;
923         y += (height - c->frame->area.height) % c->size_inc.height;
924         height -= (height - c->frame->area.height) % c->size_inc.height;
925         break;
926     case OB_DIRECTION_EAST:
927         dest = client_directional_edge_search(c, OB_DIRECTION_EAST, FALSE);
928         if (a->x + a->width == x + c->frame->area.width) {
929             width = c->frame->area.width / 2;
930             x = a->x + a->width - width;
931         } else
932             width = dest - c->frame->area.x;
933         x += (width - c->frame->area.width) % c->size_inc.width;
934         width -= (width - c->frame->area.width) % c->size_inc.width;
935         break;
936     default:
937         g_assert_not_reached();
938     }
939     width -= c->frame->size.left + c->frame->size.right;
940     height -= c->frame->size.top + c->frame->size.bottom;
941     frame_frame_gravity(c->frame, &x, &y, width, height);
942     client_action_start(data);
943     client_move_resize(c, x, y, width, height);
944     client_action_end(data, FALSE);
945     g_free(a);
946 }
947
948 void action_send_to_layer(union ActionData *data)
949 {
950     client_set_layer(data->layer.any.c, data->layer.layer);
951 }
952
953 void action_toggle_layer(union ActionData *data)
954 {
955     ObClient *c = data->layer.any.c;
956
957     client_action_start(data);
958     if (data->layer.layer < 0)
959         client_set_layer(c, c->below ? 0 : -1);
960     else if (data->layer.layer > 0)
961         client_set_layer(c, c->above ? 0 : 1);
962     client_action_end(data, config_focus_under_mouse);
963 }
964
965 void action_toggle_dockautohide(union ActionData *data)
966 {
967     config_dock_hide = !config_dock_hide;
968     dock_configure();
969 }
970
971 void action_add_desktop(union ActionData *data)
972 {
973     client_action_start(data);
974     screen_set_num_desktops(screen_num_desktops+1);
975
976     /* move all the clients over */
977     if (data->addremovedesktop.current) {
978         GList *it;
979
980         for (it = client_list; it; it = g_list_next(it)) {
981             ObClient *c = it->data;
982             if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop)
983                 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
984         }
985     }
986
987     client_action_end(data, config_focus_under_mouse);
988 }
989
990 void action_remove_desktop(union ActionData *data)
991 {
992     guint rmdesktop, movedesktop;
993     GList *it, *stacking_copy;
994
995     if (screen_num_desktops < 2) return;
996
997     client_action_start(data);
998
999     /* what desktop are we removing and moving to? */
1000     if (data->addremovedesktop.current)
1001         rmdesktop = screen_desktop;
1002     else
1003         rmdesktop = screen_num_desktops - 1;
1004     if (rmdesktop < screen_num_desktops - 1)
1005         movedesktop = rmdesktop + 1;
1006     else
1007         movedesktop = rmdesktop;
1008
1009     /* make a copy of the list cuz we're changing it */
1010     stacking_copy = g_list_copy(stacking_list);
1011     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
1012         if (WINDOW_IS_CLIENT(it->data)) {
1013             ObClient *c = it->data;
1014             guint d = c->desktop;
1015             if (d != DESKTOP_ALL && d >= movedesktop) {
1016                 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
1017                 ob_debug("moving window %s\n", c->title);
1018             }
1019             /* raise all the windows that are on the current desktop which
1020                is being merged */
1021             if ((screen_desktop == rmdesktop - 1 ||
1022                  screen_desktop == rmdesktop) &&
1023                 (d == DESKTOP_ALL || d == screen_desktop))
1024             {
1025                 stacking_raise(CLIENT_AS_WINDOW(c));
1026                 ob_debug("raising window %s\n", c->title);
1027             }
1028         }
1029     }
1030
1031     /* act like we're changing desktops */
1032     if (screen_desktop < screen_num_desktops - 1) {
1033         gint d = screen_desktop;
1034         screen_desktop = screen_last_desktop;
1035         screen_set_desktop(d, TRUE);
1036         ob_debug("fake desktop change\n");
1037     }
1038
1039     screen_set_num_desktops(screen_num_desktops-1);
1040
1041     client_action_end(data, config_focus_under_mouse);
1042 }