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