]> icculus.org git repositories - manmower/openbox.git/blob - openbox/action.c
add resizerelative action
[manmower/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         "sendtodesktop",
350         action_send_to_desktop,
351         setup_action_send_to_desktop
352     },
353     {
354         "sendtodesktopnext",
355         action_send_to_desktop_dir,
356         setup_action_send_to_desktop_next
357     },
358     {
359         "sendtodesktopprevious",
360         action_send_to_desktop_dir,
361         setup_action_send_to_desktop_prev
362     },
363     {
364         "sendtodesktopright",
365         action_send_to_desktop_dir,
366         setup_action_send_to_desktop_right
367     },
368     {
369         "sendtodesktopleft",
370         action_send_to_desktop_dir,
371         setup_action_send_to_desktop_left
372     },
373     {
374         "sendtodesktopup",
375         action_send_to_desktop_dir,
376         setup_action_send_to_desktop_up
377     },
378     {
379         "sendtodesktopdown",
380         action_send_to_desktop_dir,
381         setup_action_send_to_desktop_down
382     },
383     {
384         "toggledockautohide",
385         action_toggle_dockautohide,
386         NULL
387     },
388     {
389         "sendtotoplayer",
390         action_send_to_layer,
391         setup_action_top_layer
392     },
393     {
394         "togglealwaysontop",
395         action_toggle_layer,
396         setup_action_top_layer
397     },
398     {
399         "sendtonormallayer",
400         action_send_to_layer,
401         setup_action_normal_layer
402     },
403     {
404         "sendtobottomlayer",
405         action_send_to_layer,
406         setup_action_bottom_layer
407     },
408     {
409         "togglealwaysonbottom",
410         action_toggle_layer,
411         setup_action_bottom_layer
412     },
413     {
414         "movefromedgenorth",
415         action_movetoedge,
416         setup_action_movefromedge_north
417     },
418     {
419         "movefromedgesouth",
420         action_movetoedge,
421         setup_action_movefromedge_south
422     },
423     {
424         "movefromedgewest",
425         action_movetoedge,
426         setup_action_movefromedge_west
427     },
428     {
429         "movefromedgeeast",
430         action_movetoedge,
431         setup_action_movefromedge_east
432     },
433     {
434         "movetoedgenorth",
435         action_movetoedge,
436         setup_action_movetoedge_north
437     },
438     {
439         "movetoedgesouth",
440         action_movetoedge,
441         setup_action_movetoedge_south
442     },
443     {
444         "movetoedgewest",
445         action_movetoedge,
446         setup_action_movetoedge_west
447     },
448     {
449         "movetoedgeeast",
450         action_movetoedge,
451         setup_action_movetoedge_east
452     },
453     {
454         "growtoedgenorth",
455         action_growtoedge,
456         setup_action_growtoedge_north
457     },
458     {
459         "growtoedgesouth",
460         action_growtoedge,
461         setup_action_growtoedge_south
462     },
463     {
464         "growtoedgewest",
465         action_growtoedge,
466         setup_action_growtoedge_west
467     },
468     {
469         "growtoedgeeast",
470         action_growtoedge,
471         setup_action_growtoedge_east
472     },
473     {
474         "adddesktoplast",
475         action_add_desktop,
476         setup_action_addremove_desktop_last
477     },
478     {
479         "removedesktoplast",
480         action_remove_desktop,
481         setup_action_addremove_desktop_last
482     },
483     {
484         "adddesktopcurrent",
485         action_add_desktop,
486         setup_action_addremove_desktop_current
487     },
488     {
489         "removedesktopcurrent",
490         action_remove_desktop,
491         setup_action_addremove_desktop_current
492     },
493     {
494         NULL,
495         NULL,
496         NULL
497     }
498 };
499
500 /* only key bindings can be interactive. thus saith the xor.
501    because of how the mouse is grabbed, mouse events dont even get
502    read during interactive events, so no dice! >:) */
503 #define INTERACTIVE_LIMIT(a, uact) \
504     if (uact != OB_USER_ACTION_KEYBOARD_KEY) \
505         a->data.any.interactive = FALSE;
506
507 ObAction *action_from_string(const gchar *name, ObUserAction uact)
508 {
509     ObAction *a = NULL;
510     gboolean exist = FALSE;
511     gint i;
512
513     for (i = 0; actionstrings[i].name; i++)
514         if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
515             exist = TRUE;
516             a = action_new(actionstrings[i].func);
517             if (actionstrings[i].setup)
518                 actionstrings[i].setup(&a, uact);
519             if (a)
520                 INTERACTIVE_LIMIT(a, uact);
521             break;
522         }
523     if (!exist)
524         g_message(_("Invalid action '%s' requested. No such action exists."),
525                   name);
526     if (!a)
527         g_message(_("Invalid use of action '%s'. Action will be ignored."),
528                   name);
529     return a;
530 }
531
532 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
533                        ObUserAction uact)
534 {
535     gchar *actname;
536     ObAction *act = NULL;
537     xmlNodePtr n;
538
539     if (parse_attr_string("name", node, &actname)) {
540         if ((act = action_from_string(actname, uact))) {
541             } else if (act->func == action_desktop) {
542            } else if (act->func == action_send_to_desktop) {
543                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
544                     act->data.sendto.desk = parse_int(doc, n);
545                 if (act->data.sendto.desk > 0) act->data.sendto.desk--;
546                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
547                     act->data.sendto.follow = parse_bool(doc, n);
548             } else if (act->func == action_send_to_desktop_dir) {
549                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
550                     act->data.sendtodir.wrap = parse_bool(doc, n);
551                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
552                     act->data.sendtodir.follow = parse_bool(doc, n);
553                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
554                     act->data.sendtodir.inter.any.interactive =
555                         parse_bool(doc, n);
556             INTERACTIVE_LIMIT(act, uact);
557         }
558         g_free(actname);
559     }
560     return act;
561 }
562
563 void action_run_list(GSList *acts, ObClient *c, ObFrameContext context,
564                      guint state, guint button, gint x, gint y, Time time,
565                      gboolean cancel, gboolean done)
566 {
567     GSList *it;
568     ObAction *a;
569
570     if (!acts)
571         return;
572
573     if (x < 0 && y < 0)
574         screen_pointer_pos(&x, &y);
575
576     for (it = acts; it; it = g_slist_next(it)) {
577         a = it->data;
578
579         if (!(a->data.any.client_action == OB_CLIENT_ACTION_ALWAYS && !c)) {
580             a->data.any.c = a->data.any.client_action ? c : NULL;
581             a->data.any.context = context;
582             a->data.any.x = x;
583             a->data.any.y = y;
584
585             a->data.any.button = button;
586
587             a->data.any.time = time;
588
589             if (a->data.any.interactive) {
590                 a->data.inter.cancel = cancel;
591                 a->data.inter.final = done;
592                 if (!(cancel || done))
593                     if (!keyboard_interactive_grab(state, a->data.any.c, a))
594                         continue;
595             }
596
597             /* XXX UGLY HACK race with motion event starting a move and the
598                button release gettnig processed first. answer: don't queue
599                moveresize starts. UGLY HACK XXX
600
601                XXX ALSO don't queue showmenu events, because on button press
602                events we need to know if a mouse grab is going to take place,
603                and set the button to 0, so that later motion events don't think
604                that a drag is going on. since showmenu grabs the pointer..
605             */
606             if (a->data.any.interactive || a->func == action_move ||
607                 a->func == action_resize || a->func == action_showmenu)
608             {
609                 /* interactive actions are not queued */
610                 a->func(&a->data);
611             } else if (a->func == action_focus ||
612                        a->func == action_activate ||
613                        a->func == action_showmenu)
614             {
615                 /* XXX MORE UGLY HACK
616                    actions from clicks on client windows are NOT queued.
617                    this solves the mysterious click-and-drag-doesnt-work
618                    problem. it was because the window gets focused and stuff
619                    after the button event has already been passed through. i
620                    dont really know why it should care but it does and it makes
621                    a difference.
622
623                    however this very bogus ! !
624                    we want to send the button press to the window BEFORE
625                    we do the action because the action might move the windows
626                    (eg change desktops) and then the button press ends up on
627                    the completely wrong window !
628                    so, this is just for that bug, and it will only NOT queue it
629                    if it is a focusing action that can be used with the mouse
630                    pointer. ugh.
631
632                    also with the menus, there is a race going on. if the
633                    desktop wants to pop up a menu, and we do too, we send them
634                    the button before we pop up the menu, so they pop up their
635                    menu first. but not always. if we pop up our menu before
636                    sending them the button press, then the result is
637                    deterministic. yay.
638
639                    XXX further more. focus actions are not queued at all,
640                    because if you bind focus->showmenu, the menu will get
641                    hidden to do the focusing
642                 */
643                 a->func(&a->data);
644             } else
645                 ob_main_loop_queue_action(ob_main_loop, a);
646         }
647     }
648 }
649
650 void action_run_string(const gchar *name, struct _ObClient *c, Time time)
651 {
652     ObAction *a;
653     GSList *l;
654
655     a = action_from_string(name, OB_USER_ACTION_NONE);
656     g_assert(a);
657
658     l = g_slist_append(NULL, a);
659
660     action_run(l, c, 0, time);
661 }
662
663 void action_unshaderaise(union ActionData *data)
664 {
665     if (data->client.any.c->shaded)
666         action_unshade(data);
667     else
668         action_raise(data);
669 }
670
671 void action_shadelower(union ActionData *data)
672 {
673     if (data->client.any.c->shaded)
674         action_lower(data);
675     else
676         action_shade(data);
677 }
678
679 void action_resize_relative(union ActionData *data)
680 {
681 }
682
683 void action_send_to_desktop(union ActionData *data)
684 {
685     ObClient *c = data->sendto.any.c;
686
687     if (!client_normal(c)) return;
688
689     if (data->sendto.desk < screen_num_desktops ||
690         data->sendto.desk == DESKTOP_ALL) {
691         client_set_desktop(c, data->sendto.desk, data->sendto.follow, FALSE);
692         if (data->sendto.follow && data->sendto.desk != screen_desktop)
693             screen_set_desktop(data->sendto.desk, TRUE);
694     }
695 }
696
697 void action_send_to_desktop_dir(union ActionData *data)
698 {
699     ObClient *c = data->sendtodir.inter.any.c;
700     guint d;
701
702     if (!client_normal(c)) return;
703
704     d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
705                              data->sendtodir.linear,
706                              data->sendtodir.inter.any.interactive,
707                              data->sendtodir.inter.final,
708                              data->sendtodir.inter.cancel);
709     /* only move the desktop when the action is complete. if we switch
710        desktops during the interactive action, focus will move but with
711        NotifyWhileGrabbed and applications don't like that. */
712     if (!data->sendtodir.inter.any.interactive ||
713         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
714     {
715         client_set_desktop(c, d, data->sendtodir.follow, FALSE);
716         if (data->sendtodir.follow && d != screen_desktop)
717             screen_set_desktop(d, TRUE);
718     }
719 }
720
721 void action_directional_focus(union ActionData *data)
722 {
723     /* if using focus_delay, stop the timer now so that focus doesn't go moving
724        on us */
725     event_halt_focus_delay();
726
727     focus_directional_cycle(data->interdiraction.direction,
728                             data->interdiraction.dock_windows,
729                             data->interdiraction.desktop_windows,
730                             data->any.interactive,
731                             data->interdiraction.dialog,
732                             data->interdiraction.inter.final,
733                             data->interdiraction.inter.cancel);
734 }
735
736 void action_movetoedge(union ActionData *data)
737 {
738     gint x, y;
739     ObClient *c = data->diraction.any.c;
740
741     x = c->frame->area.x;
742     y = c->frame->area.y;
743     
744     switch(data->diraction.direction) {
745     case OB_DIRECTION_NORTH:
746         y = client_directional_edge_search(c, OB_DIRECTION_NORTH,
747                                            data->diraction.hang)
748             - (data->diraction.hang ? c->frame->area.height : 0);
749         break;
750     case OB_DIRECTION_WEST:
751         x = client_directional_edge_search(c, OB_DIRECTION_WEST,
752                                            data->diraction.hang)
753             - (data->diraction.hang ? c->frame->area.width : 0);
754         break;
755     case OB_DIRECTION_SOUTH:
756         y = client_directional_edge_search(c, OB_DIRECTION_SOUTH,
757                                            data->diraction.hang)
758             - (data->diraction.hang ? 0 : c->frame->area.height);
759         break;
760     case OB_DIRECTION_EAST:
761         x = client_directional_edge_search(c, OB_DIRECTION_EAST,
762                                            data->diraction.hang)
763             - (data->diraction.hang ? 0 : c->frame->area.width);
764         break;
765     default:
766         g_assert_not_reached();
767     }
768     frame_frame_gravity(c->frame, &x, &y, c->area.width, c->area.height);
769     client_action_start(data);
770     client_move(c, x, y);
771     client_action_end(data, FALSE);
772 }
773
774 void action_growtoedge(union ActionData *data)
775 {
776     gint x, y, width, height, dest;
777     ObClient *c = data->diraction.any.c;
778     Rect *a;
779
780     a = screen_area(c->desktop, SCREEN_AREA_ALL_MONITORS, &c->frame->area);
781     x = c->frame->area.x;
782     y = c->frame->area.y;
783     /* get the unshaded frame's dimensions..if it is shaded */
784     width = c->area.width + c->frame->size.left + c->frame->size.right;
785     height = c->area.height + c->frame->size.top + c->frame->size.bottom;
786
787     switch(data->diraction.direction) {
788     case OB_DIRECTION_NORTH:
789         if (c->shaded) break; /* don't allow vertical resize if shaded */
790
791         dest = client_directional_edge_search(c, OB_DIRECTION_NORTH, FALSE);
792         if (a->y == y)
793             height = height / 2;
794         else {
795             height = c->frame->area.y + height - dest;
796             y = dest;
797         }
798         break;
799     case OB_DIRECTION_WEST:
800         dest = client_directional_edge_search(c, OB_DIRECTION_WEST, FALSE);
801         if (a->x == x)
802             width = width / 2;
803         else {
804             width = c->frame->area.x + width - dest;
805             x = dest;
806         }
807         break;
808     case OB_DIRECTION_SOUTH:
809         if (c->shaded) break; /* don't allow vertical resize if shaded */
810
811         dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH, FALSE);
812         if (a->y + a->height == y + c->frame->area.height) {
813             height = c->frame->area.height / 2;
814             y = a->y + a->height - height;
815         } else
816             height = dest - c->frame->area.y;
817         y += (height - c->frame->area.height) % c->size_inc.height;
818         height -= (height - c->frame->area.height) % c->size_inc.height;
819         break;
820     case OB_DIRECTION_EAST:
821         dest = client_directional_edge_search(c, OB_DIRECTION_EAST, FALSE);
822         if (a->x + a->width == x + c->frame->area.width) {
823             width = c->frame->area.width / 2;
824             x = a->x + a->width - width;
825         } else
826             width = dest - c->frame->area.x;
827         x += (width - c->frame->area.width) % c->size_inc.width;
828         width -= (width - c->frame->area.width) % c->size_inc.width;
829         break;
830     default:
831         g_assert_not_reached();
832     }
833     width -= c->frame->size.left + c->frame->size.right;
834     height -= c->frame->size.top + c->frame->size.bottom;
835     frame_frame_gravity(c->frame, &x, &y, width, height);
836     client_action_start(data);
837     client_move_resize(c, x, y, width, height);
838     client_action_end(data, FALSE);
839     g_free(a);
840 }
841
842 void action_send_to_layer(union ActionData *data)
843 {
844     client_set_layer(data->layer.any.c, data->layer.layer);
845 }
846
847 void action_toggle_layer(union ActionData *data)
848 {
849     ObClient *c = data->layer.any.c;
850
851     client_action_start(data);
852     if (data->layer.layer < 0)
853         client_set_layer(c, c->below ? 0 : -1);
854     else if (data->layer.layer > 0)
855         client_set_layer(c, c->above ? 0 : 1);
856     client_action_end(data, config_focus_under_mouse);
857 }
858
859 void action_toggle_dockautohide(union ActionData *data)
860 {
861     config_dock_hide = !config_dock_hide;
862     dock_configure();
863 }
864
865 void action_add_desktop(union ActionData *data)
866 {
867     client_action_start(data);
868     screen_set_num_desktops(screen_num_desktops+1);
869
870     /* move all the clients over */
871     if (data->addremovedesktop.current) {
872         GList *it;
873
874         for (it = client_list; it; it = g_list_next(it)) {
875             ObClient *c = it->data;
876             if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop)
877                 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
878         }
879     }
880
881     client_action_end(data, config_focus_under_mouse);
882 }
883
884 void action_remove_desktop(union ActionData *data)
885 {
886     guint rmdesktop, movedesktop;
887     GList *it, *stacking_copy;
888
889     if (screen_num_desktops < 2) return;
890
891     client_action_start(data);
892
893     /* what desktop are we removing and moving to? */
894     if (data->addremovedesktop.current)
895         rmdesktop = screen_desktop;
896     else
897         rmdesktop = screen_num_desktops - 1;
898     if (rmdesktop < screen_num_desktops - 1)
899         movedesktop = rmdesktop + 1;
900     else
901         movedesktop = rmdesktop;
902
903     /* make a copy of the list cuz we're changing it */
904     stacking_copy = g_list_copy(stacking_list);
905     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
906         if (WINDOW_IS_CLIENT(it->data)) {
907             ObClient *c = it->data;
908             guint d = c->desktop;
909             if (d != DESKTOP_ALL && d >= movedesktop) {
910                 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
911                 ob_debug("moving window %s\n", c->title);
912             }
913             /* raise all the windows that are on the current desktop which
914                is being merged */
915             if ((screen_desktop == rmdesktop - 1 ||
916                  screen_desktop == rmdesktop) &&
917                 (d == DESKTOP_ALL || d == screen_desktop))
918             {
919                 stacking_raise(CLIENT_AS_WINDOW(c));
920                 ob_debug("raising window %s\n", c->title);
921             }
922         }
923     }
924
925     /* act like we're changing desktops */
926     if (screen_desktop < screen_num_desktops - 1) {
927         gint d = screen_desktop;
928         screen_desktop = screen_last_desktop;
929         screen_set_desktop(d, TRUE);
930         ob_debug("fake desktop change\n");
931     }
932
933     screen_set_num_desktops(screen_num_desktops-1);
934
935     client_action_end(data, config_focus_under_mouse);
936 }