let you send windows with the desktop and directionaldesktop actions
[dana/openbox.git] / openbox / action.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    action.c for the Openbox window manager
4    Copyright (c) 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_prev(ObAction **a, ObUserAction uact)
100 {
101     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
102     (*a)->data.sendtodir.inter.any.interactive = TRUE;
103     (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
104     (*a)->data.sendtodir.linear = TRUE;
105     (*a)->data.sendtodir.wrap = TRUE;
106     (*a)->data.sendtodir.follow = TRUE;
107 }
108
109 void setup_action_send_to_desktop_next(ObAction **a, ObUserAction uact)
110 {
111     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
112     (*a)->data.sendtodir.inter.any.interactive = TRUE;
113     (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
114     (*a)->data.sendtodir.linear = TRUE;
115     (*a)->data.sendtodir.wrap = TRUE;
116     (*a)->data.sendtodir.follow = TRUE;
117 }
118
119 void setup_action_send_to_desktop_left(ObAction **a, ObUserAction uact)
120 {
121     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
122     (*a)->data.sendtodir.inter.any.interactive = TRUE;
123     (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
124     (*a)->data.sendtodir.linear = FALSE;
125     (*a)->data.sendtodir.wrap = TRUE;
126     (*a)->data.sendtodir.follow = TRUE;
127 }
128
129 void setup_action_send_to_desktop_right(ObAction **a, ObUserAction uact)
130 {
131     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
132     (*a)->data.sendtodir.inter.any.interactive = TRUE;
133     (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
134     (*a)->data.sendtodir.linear = FALSE;
135     (*a)->data.sendtodir.wrap = TRUE;
136     (*a)->data.sendtodir.follow = TRUE;
137 }
138
139 void setup_action_send_to_desktop_up(ObAction **a, ObUserAction uact)
140 {
141     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
142     (*a)->data.sendtodir.inter.any.interactive = TRUE;
143     (*a)->data.sendtodir.dir = OB_DIRECTION_NORTH;
144     (*a)->data.sendtodir.linear = FALSE;
145     (*a)->data.sendtodir.wrap = TRUE;
146     (*a)->data.sendtodir.follow = TRUE;
147 }
148
149 void setup_action_send_to_desktop_down(ObAction **a, ObUserAction uact)
150 {
151     (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
152     (*a)->data.sendtodir.inter.any.interactive = TRUE;
153     (*a)->data.sendtodir.dir = OB_DIRECTION_SOUTH;
154     (*a)->data.sendtodir.linear = FALSE;
155     (*a)->data.sendtodir.wrap = TRUE;
156     (*a)->data.sendtodir.follow = TRUE;
157 }
158
159 void setup_action_desktop_prev(ObAction **a, ObUserAction uact)
160 {
161     (*a)->data.desktopdir.inter.any.interactive = TRUE;
162     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
163     (*a)->data.desktopdir.linear = TRUE;
164     (*a)->data.desktopdir.wrap = TRUE;
165 }
166
167 void setup_action_desktop_next(ObAction **a, ObUserAction uact)
168 {
169     (*a)->data.desktopdir.inter.any.interactive = TRUE;
170     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
171     (*a)->data.desktopdir.linear = TRUE;
172     (*a)->data.desktopdir.wrap = TRUE;
173 }
174
175 void setup_action_desktop_left(ObAction **a, ObUserAction uact)
176 {
177     (*a)->data.desktopdir.inter.any.interactive = TRUE;
178     (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
179     (*a)->data.desktopdir.linear = FALSE;
180     (*a)->data.desktopdir.wrap = TRUE;
181 }
182
183 void setup_action_desktop_right(ObAction **a, ObUserAction uact)
184 {
185     (*a)->data.desktopdir.inter.any.interactive = TRUE;
186     (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
187     (*a)->data.desktopdir.linear = FALSE;
188     (*a)->data.desktopdir.wrap = TRUE;
189 }
190
191 void setup_action_desktop_up(ObAction **a, ObUserAction uact)
192 {
193     (*a)->data.desktopdir.inter.any.interactive = TRUE;
194     (*a)->data.desktopdir.dir = OB_DIRECTION_NORTH;
195     (*a)->data.desktopdir.linear = FALSE;
196     (*a)->data.desktopdir.wrap = TRUE;
197 }
198
199 void setup_action_desktop_down(ObAction **a, ObUserAction uact)
200 {
201     (*a)->data.desktopdir.inter.any.interactive = TRUE;
202     (*a)->data.desktopdir.dir = OB_DIRECTION_SOUTH;
203     (*a)->data.desktopdir.linear = FALSE;
204     (*a)->data.desktopdir.wrap = TRUE;
205 }
206
207 void setup_action_movefromedge_north(ObAction **a, ObUserAction uact)
208 {
209     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
210     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
211     (*a)->data.diraction.hang = TRUE;
212 }
213
214 void setup_action_movefromedge_south(ObAction **a, ObUserAction uact)
215 {
216     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
217     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
218     (*a)->data.diraction.hang = TRUE;
219 }
220
221 void setup_action_movefromedge_east(ObAction **a, ObUserAction uact)
222 {
223     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
224     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
225     (*a)->data.diraction.hang = TRUE;
226 }
227
228 void setup_action_movefromedge_west(ObAction **a, ObUserAction uact)
229 {
230     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
231     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
232     (*a)->data.diraction.hang = TRUE;
233 }
234
235 void setup_action_movetoedge_north(ObAction **a, ObUserAction uact)
236 {
237     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
238     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
239     (*a)->data.diraction.hang = FALSE;
240 }
241
242 void setup_action_movetoedge_south(ObAction **a, ObUserAction uact)
243 {
244     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
245     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
246     (*a)->data.diraction.hang = FALSE;
247 }
248
249 void setup_action_movetoedge_east(ObAction **a, ObUserAction uact)
250 {
251     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
252     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
253     (*a)->data.diraction.hang = FALSE;
254 }
255
256 void setup_action_movetoedge_west(ObAction **a, ObUserAction uact)
257 {
258     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
259     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
260     (*a)->data.diraction.hang = FALSE;
261 }
262
263 void setup_action_growtoedge_north(ObAction **a, ObUserAction uact)
264 {
265     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
266     (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
267 }
268
269 void setup_action_growtoedge_south(ObAction **a, ObUserAction uact)
270 {
271     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
272     (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
273 }
274
275 void setup_action_growtoedge_east(ObAction **a, ObUserAction uact)
276 {
277     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
278     (*a)->data.diraction.direction = OB_DIRECTION_EAST;
279 }
280
281 void setup_action_growtoedge_west(ObAction **a, ObUserAction uact)
282 {
283     (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
284     (*a)->data.diraction.direction = OB_DIRECTION_WEST;
285 }
286
287 void setup_action_top_layer(ObAction **a, ObUserAction uact)
288 {
289     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
290     (*a)->data.layer.layer = 1;
291 }
292
293 void setup_action_normal_layer(ObAction **a, ObUserAction uact)
294 {
295     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
296     (*a)->data.layer.layer = 0;
297 }
298
299 void setup_action_bottom_layer(ObAction **a, ObUserAction uact)
300 {
301     (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
302     (*a)->data.layer.layer = -1;
303 }
304
305 void setup_action_addremove_desktop_current(ObAction **a, ObUserAction uact)
306 {
307     (*a)->data.addremovedesktop.current = TRUE;
308 }
309
310 void setup_action_addremove_desktop_last(ObAction **a, ObUserAction uact)
311 {
312     (*a)->data.addremovedesktop.current = FALSE;
313 }
314
315 void setup_client_action(ObAction **a, ObUserAction uact)
316 {
317     (*a)->data.any.client_action = OB_CLIENT_ACTION_ALWAYS;
318 }
319
320 ActionString actionstrings[] =
321 {
322     {
323         "shadelower",
324         action_shadelower,
325         setup_client_action
326     },
327     {
328         "unshaderaise",
329         action_unshaderaise,
330         setup_client_action
331     },
332     {
333         "sendtodesktopnext",
334         action_send_to_desktop_dir,
335         setup_action_send_to_desktop_next
336     },
337     {
338         "sendtodesktopprevious",
339         action_send_to_desktop_dir,
340         setup_action_send_to_desktop_prev
341     },
342     {
343         "sendtodesktopright",
344         action_send_to_desktop_dir,
345         setup_action_send_to_desktop_right
346     },
347     {
348         "sendtodesktopleft",
349         action_send_to_desktop_dir,
350         setup_action_send_to_desktop_left
351     },
352     {
353         "sendtodesktopup",
354         action_send_to_desktop_dir,
355         setup_action_send_to_desktop_up
356     },
357     {
358         "sendtodesktopdown",
359         action_send_to_desktop_dir,
360         setup_action_send_to_desktop_down
361     },
362     {
363         "toggledockautohide",
364         action_toggle_dockautohide,
365         NULL
366     },
367     {
368         "sendtotoplayer",
369         action_send_to_layer,
370         setup_action_top_layer
371     },
372     {
373         "togglealwaysontop",
374         action_toggle_layer,
375         setup_action_top_layer
376     },
377     {
378         "sendtonormallayer",
379         action_send_to_layer,
380         setup_action_normal_layer
381     },
382     {
383         "sendtobottomlayer",
384         action_send_to_layer,
385         setup_action_bottom_layer
386     },
387     {
388         "togglealwaysonbottom",
389         action_toggle_layer,
390         setup_action_bottom_layer
391     },
392     {
393         "movefromedgenorth",
394         action_movetoedge,
395         setup_action_movefromedge_north
396     },
397     {
398         "movefromedgesouth",
399         action_movetoedge,
400         setup_action_movefromedge_south
401     },
402     {
403         "movefromedgewest",
404         action_movetoedge,
405         setup_action_movefromedge_west
406     },
407     {
408         "movefromedgeeast",
409         action_movetoedge,
410         setup_action_movefromedge_east
411     },
412     {
413         "movetoedgenorth",
414         action_movetoedge,
415         setup_action_movetoedge_north
416     },
417     {
418         "movetoedgesouth",
419         action_movetoedge,
420         setup_action_movetoedge_south
421     },
422     {
423         "movetoedgewest",
424         action_movetoedge,
425         setup_action_movetoedge_west
426     },
427     {
428         "movetoedgeeast",
429         action_movetoedge,
430         setup_action_movetoedge_east
431     },
432     {
433         "growtoedgenorth",
434         action_growtoedge,
435         setup_action_growtoedge_north
436     },
437     {
438         "growtoedgesouth",
439         action_growtoedge,
440         setup_action_growtoedge_south
441     },
442     {
443         "growtoedgewest",
444         action_growtoedge,
445         setup_action_growtoedge_west
446     },
447     {
448         "growtoedgeeast",
449         action_growtoedge,
450         setup_action_growtoedge_east
451     },
452     {
453         "adddesktoplast",
454         action_add_desktop,
455         setup_action_addremove_desktop_last
456     },
457     {
458         "removedesktoplast",
459         action_remove_desktop,
460         setup_action_addremove_desktop_last
461     },
462     {
463         "adddesktopcurrent",
464         action_add_desktop,
465         setup_action_addremove_desktop_current
466     },
467     {
468         "removedesktopcurrent",
469         action_remove_desktop,
470         setup_action_addremove_desktop_current
471     },
472     {
473         NULL,
474         NULL,
475         NULL
476     }
477 };
478
479 /* only key bindings can be interactive. thus saith the xor.
480    because of how the mouse is grabbed, mouse events dont even get
481    read during interactive events, so no dice! >:) */
482 #define INTERACTIVE_LIMIT(a, uact) \
483     if (uact != OB_USER_ACTION_KEYBOARD_KEY) \
484         a->data.any.interactive = FALSE;
485
486 ObAction *action_from_string(const gchar *name, ObUserAction uact)
487 {
488     ObAction *a = NULL;
489     gboolean exist = FALSE;
490     gint i;
491
492     for (i = 0; actionstrings[i].name; i++)
493         if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
494             exist = TRUE;
495             a = action_new(actionstrings[i].func);
496             if (actionstrings[i].setup)
497                 actionstrings[i].setup(&a, uact);
498             if (a)
499                 INTERACTIVE_LIMIT(a, uact);
500             break;
501         }
502     if (!exist)
503         g_message(_("Invalid action '%s' requested. No such action exists."),
504                   name);
505     if (!a)
506         g_message(_("Invalid use of action '%s'. Action will be ignored."),
507                   name);
508     return a;
509 }
510
511 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
512                        ObUserAction uact)
513 {
514     gchar *actname;
515     ObAction *act = NULL;
516     xmlNodePtr n;
517
518     if (parse_attr_string("name", node, &actname)) {
519         if ((act = action_from_string(actname, uact))) {
520             } else if (act->func == action_desktop) {
521             } else if (act->func == action_send_to_desktop_dir) {
522                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
523                     act->data.sendtodir.wrap = parse_bool(doc, n);
524                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
525                     act->data.sendtodir.follow = parse_bool(doc, n);
526                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
527                     act->data.sendtodir.inter.any.interactive =
528                         parse_bool(doc, n);
529             INTERACTIVE_LIMIT(act, uact);
530         }
531         g_free(actname);
532     }
533     return act;
534 }
535
536
537 void action_unshaderaise(union ActionData *data)
538 {
539     if (data->client.any.c->shaded)
540         action_unshade(data);
541     else
542         action_raise(data);
543 }
544
545 void action_shadelower(union ActionData *data)
546 {
547     if (data->client.any.c->shaded)
548         action_lower(data);
549     else
550         action_shade(data);
551 }
552
553 void action_send_to_desktop_dir(union ActionData *data)
554 {
555     ObClient *c = data->sendtodir.inter.any.c;
556     guint d;
557
558     if (!client_normal(c)) return;
559
560     d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
561                              data->sendtodir.linear,
562                              data->sendtodir.inter.any.interactive,
563                              data->sendtodir.inter.final,
564                              data->sendtodir.inter.cancel);
565     /* only move the desktop when the action is complete. if we switch
566        desktops during the interactive action, focus will move but with
567        NotifyWhileGrabbed and applications don't like that. */
568     if (!data->sendtodir.inter.any.interactive ||
569         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
570     {
571         client_set_desktop(c, d, data->sendtodir.follow, FALSE);
572         if (data->sendtodir.follow && d != screen_desktop)
573             screen_set_desktop(d, TRUE);
574     }
575 }
576
577 void action_directional_focus(union ActionData *data)
578 {
579     /* if using focus_delay, stop the timer now so that focus doesn't go moving
580        on us */
581     event_halt_focus_delay();
582
583     focus_directional_cycle(data->interdiraction.direction,
584                             data->interdiraction.dock_windows,
585                             data->interdiraction.desktop_windows,
586                             data->any.interactive,
587                             data->interdiraction.dialog,
588                             data->interdiraction.inter.final,
589                             data->interdiraction.inter.cancel);
590 }
591
592 void action_movetoedge(union ActionData *data)
593 {
594     gint x, y;
595     ObClient *c = data->diraction.any.c;
596
597     x = c->frame->area.x;
598     y = c->frame->area.y;
599     
600     switch(data->diraction.direction) {
601     case OB_DIRECTION_NORTH:
602         y = client_directional_edge_search(c, OB_DIRECTION_NORTH,
603                                            data->diraction.hang)
604             - (data->diraction.hang ? c->frame->area.height : 0);
605         break;
606     case OB_DIRECTION_WEST:
607         x = client_directional_edge_search(c, OB_DIRECTION_WEST,
608                                            data->diraction.hang)
609             - (data->diraction.hang ? c->frame->area.width : 0);
610         break;
611     case OB_DIRECTION_SOUTH:
612         y = client_directional_edge_search(c, OB_DIRECTION_SOUTH,
613                                            data->diraction.hang)
614             - (data->diraction.hang ? 0 : c->frame->area.height);
615         break;
616     case OB_DIRECTION_EAST:
617         x = client_directional_edge_search(c, OB_DIRECTION_EAST,
618                                            data->diraction.hang)
619             - (data->diraction.hang ? 0 : c->frame->area.width);
620         break;
621     default:
622         g_assert_not_reached();
623     }
624     frame_frame_gravity(c->frame, &x, &y, c->area.width, c->area.height);
625     client_action_start(data);
626     client_move(c, x, y);
627     client_action_end(data, FALSE);
628 }
629
630 void action_growtoedge(union ActionData *data)
631 {
632     gint x, y, width, height, dest;
633     ObClient *c = data->diraction.any.c;
634     Rect *a;
635
636     a = screen_area(c->desktop, SCREEN_AREA_ALL_MONITORS, &c->frame->area);
637     x = c->frame->area.x;
638     y = c->frame->area.y;
639     /* get the unshaded frame's dimensions..if it is shaded */
640     width = c->area.width + c->frame->size.left + c->frame->size.right;
641     height = c->area.height + c->frame->size.top + c->frame->size.bottom;
642
643     switch(data->diraction.direction) {
644     case OB_DIRECTION_NORTH:
645         if (c->shaded) break; /* don't allow vertical resize if shaded */
646
647         dest = client_directional_edge_search(c, OB_DIRECTION_NORTH, FALSE);
648         if (a->y == y)
649             height = height / 2;
650         else {
651             height = c->frame->area.y + height - dest;
652             y = dest;
653         }
654         break;
655     case OB_DIRECTION_WEST:
656         dest = client_directional_edge_search(c, OB_DIRECTION_WEST, FALSE);
657         if (a->x == x)
658             width = width / 2;
659         else {
660             width = c->frame->area.x + width - dest;
661             x = dest;
662         }
663         break;
664     case OB_DIRECTION_SOUTH:
665         if (c->shaded) break; /* don't allow vertical resize if shaded */
666
667         dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH, FALSE);
668         if (a->y + a->height == y + c->frame->area.height) {
669             height = c->frame->area.height / 2;
670             y = a->y + a->height - height;
671         } else
672             height = dest - c->frame->area.y;
673         y += (height - c->frame->area.height) % c->size_inc.height;
674         height -= (height - c->frame->area.height) % c->size_inc.height;
675         break;
676     case OB_DIRECTION_EAST:
677         dest = client_directional_edge_search(c, OB_DIRECTION_EAST, FALSE);
678         if (a->x + a->width == x + c->frame->area.width) {
679             width = c->frame->area.width / 2;
680             x = a->x + a->width - width;
681         } else
682             width = dest - c->frame->area.x;
683         x += (width - c->frame->area.width) % c->size_inc.width;
684         width -= (width - c->frame->area.width) % c->size_inc.width;
685         break;
686     default:
687         g_assert_not_reached();
688     }
689     width -= c->frame->size.left + c->frame->size.right;
690     height -= c->frame->size.top + c->frame->size.bottom;
691     frame_frame_gravity(c->frame, &x, &y, width, height);
692     client_action_start(data);
693     client_move_resize(c, x, y, width, height);
694     client_action_end(data, FALSE);
695     g_free(a);
696 }
697
698 void action_send_to_layer(union ActionData *data)
699 {
700     client_set_layer(data->layer.any.c, data->layer.layer);
701 }
702
703 void action_toggle_layer(union ActionData *data)
704 {
705     ObClient *c = data->layer.any.c;
706
707     client_action_start(data);
708     if (data->layer.layer < 0)
709         client_set_layer(c, c->below ? 0 : -1);
710     else if (data->layer.layer > 0)
711         client_set_layer(c, c->above ? 0 : 1);
712     client_action_end(data, config_focus_under_mouse);
713 }
714
715 void action_toggle_dockautohide(union ActionData *data)
716 {
717     config_dock_hide = !config_dock_hide;
718     dock_configure();
719 }
720
721 void action_add_desktop(union ActionData *data)
722 {
723     client_action_start(data);
724     screen_set_num_desktops(screen_num_desktops+1);
725
726     /* move all the clients over */
727     if (data->addremovedesktop.current) {
728         GList *it;
729
730         for (it = client_list; it; it = g_list_next(it)) {
731             ObClient *c = it->data;
732             if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop)
733                 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
734         }
735     }
736
737     client_action_end(data, config_focus_under_mouse);
738 }
739
740 void action_remove_desktop(union ActionData *data)
741 {
742     guint rmdesktop, movedesktop;
743     GList *it, *stacking_copy;
744
745     if (screen_num_desktops < 2) return;
746
747     client_action_start(data);
748
749     /* what desktop are we removing and moving to? */
750     if (data->addremovedesktop.current)
751         rmdesktop = screen_desktop;
752     else
753         rmdesktop = screen_num_desktops - 1;
754     if (rmdesktop < screen_num_desktops - 1)
755         movedesktop = rmdesktop + 1;
756     else
757         movedesktop = rmdesktop;
758
759     /* make a copy of the list cuz we're changing it */
760     stacking_copy = g_list_copy(stacking_list);
761     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
762         if (WINDOW_IS_CLIENT(it->data)) {
763             ObClient *c = it->data;
764             guint d = c->desktop;
765             if (d != DESKTOP_ALL && d >= movedesktop) {
766                 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
767                 ob_debug("moving window %s\n", c->title);
768             }
769             /* raise all the windows that are on the current desktop which
770                is being merged */
771             if ((screen_desktop == rmdesktop - 1 ||
772                  screen_desktop == rmdesktop) &&
773                 (d == DESKTOP_ALL || d == screen_desktop))
774             {
775                 stacking_raise(CLIENT_AS_WINDOW(c));
776                 ob_debug("raising window %s\n", c->title);
777             }
778         }
779     }
780
781     /* act like we're changing desktops */
782     if (screen_desktop < screen_num_desktops - 1) {
783         gint d = screen_desktop;
784         screen_desktop = screen_last_desktop;
785         screen_set_desktop(d, TRUE);
786         ob_debug("fake desktop change\n");
787     }
788
789     screen_set_num_desktops(screen_num_desktops-1);
790
791     client_action_end(data, config_focus_under_mouse);
792 }