add the moveto action
[dana/openbox-history.git] / openbox / action.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    action.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "debug.h"
21 #include "client.h"
22 #include "focus.h"
23 #include "focus_cycle.h"
24 #include "moveresize.h"
25 #include "menu.h"
26 #include "prop.h"
27 #include "stacking.h"
28 #include "screen.h"
29 #include "action.h"
30 #include "openbox.h"
31 #include "grab.h"
32 #include "keyboard.h"
33 #include "event.h"
34 #include "dock.h"
35 #include "config.h"
36 #include "mainloop.h"
37 #include "startupnotify.h"
38 #include "gettext.h"
39
40 #include <glib.h>
41
42
43
44 typedef struct
45 {
46     const gchar *name;
47     void (*func)(union ActionData *);
48     void (*setup)(ObAction **, ObUserAction uact);
49 } ActionString;
50
51 static ObAction *action_new(void (*func)(union ActionData *data))
52 {
53     ObAction *a = g_new0(ObAction, 1);
54     a->ref = 1;
55     a->func = func;
56
57     return a;
58 }
59
60 void action_ref(ObAction *a)
61 {
62     ++a->ref;
63 }
64
65 void action_unref(ObAction *a)
66 {
67     if (a == NULL) return;
68
69     if (--a->ref > 0) return;
70
71     /* deal with pointers */
72     if (a->func == action_execute || a->func == action_restart)
73         g_free(a->data.execute.path);
74     else if (a->func == action_debug)
75         g_free(a->data.debug.string);
76     else if (a->func == action_showmenu)
77         g_free(a->data.showmenu.name);
78
79     g_free(a);
80 }
81
82 ObAction* action_copy(const ObAction *src)
83 {
84     ObAction *a = action_new(src->func);
85
86     a->data = src->data;
87
88     /* deal with pointers */
89     if (a->func == action_execute || a->func == action_restart)
90         a->data.execute.path = g_strdup(a->data.execute.path);
91     else if (a->func == action_debug)
92         a->data.debug.string = g_strdup(a->data.debug.string);
93     else if (a->func == action_showmenu)
94         a->data.showmenu.name = g_strdup(a->data.showmenu.name);
95
96     return a;
97 }
98
99 void setup_action_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         "kill",
459         action_kill,
460         setup_client_action
461     },
462     {
463         "shadelower",
464         action_shadelower,
465         setup_client_action
466     },
467     {
468         "unshaderaise",
469         action_unshaderaise,
470         setup_client_action
471     },
472     {
473         "shade",
474         action_shade,
475         setup_client_action
476     },
477     {
478         "unshade",
479         action_unshade,
480         setup_client_action
481     },
482     {
483         "toggleshade",
484         action_toggle_shade,
485         setup_client_action
486     },
487     {
488         "toggleomnipresent",
489         action_toggle_omnipresent,
490         setup_client_action
491     },
492     {
493         "moverelativehorz",
494         action_move_relative_horz,
495         setup_client_action
496     },
497     {
498         "moverelativevert",
499         action_move_relative_vert,
500         setup_client_action
501     },
502     {
503         "resizerelativehorz",
504         action_resize_relative_horz,
505         setup_client_action
506     },
507     {
508         "resizerelativevert",
509         action_resize_relative_vert,
510         setup_client_action
511     },
512     {
513         "moverelative",
514         action_move_relative,
515         setup_client_action
516     },
517     {
518         "resizerelative",
519         action_resize_relative,
520         setup_client_action
521     },
522     {
523         "sendtodesktop",
524         action_send_to_desktop,
525         setup_action_send_to_desktop
526     },
527     {
528         "sendtodesktopnext",
529         action_send_to_desktop_dir,
530         setup_action_send_to_desktop_next
531     },
532     {
533         "sendtodesktopprevious",
534         action_send_to_desktop_dir,
535         setup_action_send_to_desktop_prev
536     },
537     {
538         "sendtodesktopright",
539         action_send_to_desktop_dir,
540         setup_action_send_to_desktop_right
541     },
542     {
543         "sendtodesktopleft",
544         action_send_to_desktop_dir,
545         setup_action_send_to_desktop_left
546     },
547     {
548         "sendtodesktopup",
549         action_send_to_desktop_dir,
550         setup_action_send_to_desktop_up
551     },
552     {
553         "sendtodesktopdown",
554         action_send_to_desktop_dir,
555         setup_action_send_to_desktop_down
556     },
557     {
558         "desktop",
559         action_desktop,
560         setup_action_desktop
561     },
562     {
563         "desktopnext",
564         action_desktop_dir,
565         setup_action_desktop_next
566     },
567     {
568         "desktopprevious",
569         action_desktop_dir,
570         setup_action_desktop_prev
571     },
572     {
573         "desktopright",
574         action_desktop_dir,
575         setup_action_desktop_right
576     },
577     {
578         "desktopleft",
579         action_desktop_dir,
580         setup_action_desktop_left
581     },
582     {
583         "desktopup",
584         action_desktop_dir,
585         setup_action_desktop_up
586     },
587     {
588         "desktopdown",
589         action_desktop_dir,
590         setup_action_desktop_down
591     },
592     {
593         "toggledecorations",
594         action_toggle_decorations,
595         setup_client_action
596     },
597     {
598         "resize",
599         action_resize,
600         setup_action_resize
601     },
602     {
603         "toggledockautohide",
604         action_toggle_dockautohide,
605         NULL
606     },
607     {
608         "desktoplast",
609         action_desktop_last,
610         NULL
611     },
612     {
613         "sendtotoplayer",
614         action_send_to_layer,
615         setup_action_top_layer
616     },
617     {
618         "togglealwaysontop",
619         action_toggle_layer,
620         setup_action_top_layer
621     },
622     {
623         "sendtonormallayer",
624         action_send_to_layer,
625         setup_action_normal_layer
626     },
627     {
628         "sendtobottomlayer",
629         action_send_to_layer,
630         setup_action_bottom_layer
631     },
632     {
633         "togglealwaysonbottom",
634         action_toggle_layer,
635         setup_action_bottom_layer
636     },
637     {
638         "movefromedgenorth",
639         action_movetoedge,
640         setup_action_movefromedge_north
641     },
642     {
643         "movefromedgesouth",
644         action_movetoedge,
645         setup_action_movefromedge_south
646     },
647     {
648         "movefromedgewest",
649         action_movetoedge,
650         setup_action_movefromedge_west
651     },
652     {
653         "movefromedgeeast",
654         action_movetoedge,
655         setup_action_movefromedge_east
656     },
657     {
658         "movetoedgenorth",
659         action_movetoedge,
660         setup_action_movetoedge_north
661     },
662     {
663         "movetoedgesouth",
664         action_movetoedge,
665         setup_action_movetoedge_south
666     },
667     {
668         "movetoedgewest",
669         action_movetoedge,
670         setup_action_movetoedge_west
671     },
672     {
673         "movetoedgeeast",
674         action_movetoedge,
675         setup_action_movetoedge_east
676     },
677     {
678         "growtoedgenorth",
679         action_growtoedge,
680         setup_action_growtoedge_north
681     },
682     {
683         "growtoedgesouth",
684         action_growtoedge,
685         setup_action_growtoedge_south
686     },
687     {
688         "growtoedgewest",
689         action_growtoedge,
690         setup_action_growtoedge_west
691     },
692     {
693         "growtoedgeeast",
694         action_growtoedge,
695         setup_action_growtoedge_east
696     },
697     {
698         "adddesktoplast",
699         action_add_desktop,
700         setup_action_addremove_desktop_last
701     },
702     {
703         "removedesktoplast",
704         action_remove_desktop,
705         setup_action_addremove_desktop_last
706     },
707     {
708         "adddesktopcurrent",
709         action_add_desktop,
710         setup_action_addremove_desktop_current
711     },
712     {
713         "removedesktopcurrent",
714         action_remove_desktop,
715         setup_action_addremove_desktop_current
716     },
717     {
718         NULL,
719         NULL,
720         NULL
721     }
722 };
723
724 /* only key bindings can be interactive. thus saith the xor.
725    because of how the mouse is grabbed, mouse events dont even get
726    read during interactive events, so no dice! >:) */
727 #define INTERACTIVE_LIMIT(a, uact) \
728     if (uact != OB_USER_ACTION_KEYBOARD_KEY) \
729         a->data.any.interactive = FALSE;
730
731 ObAction *action_from_string(const gchar *name, ObUserAction uact)
732 {
733     ObAction *a = NULL;
734     gboolean exist = FALSE;
735     gint i;
736
737     for (i = 0; actionstrings[i].name; i++)
738         if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
739             exist = TRUE;
740             a = action_new(actionstrings[i].func);
741             if (actionstrings[i].setup)
742                 actionstrings[i].setup(&a, uact);
743             if (a)
744                 INTERACTIVE_LIMIT(a, uact);
745             break;
746         }
747     if (!exist)
748         g_message(_("Invalid action '%s' requested. No such action exists."),
749                   name);
750     if (!a)
751         g_message(_("Invalid use of action '%s'. Action will be ignored."),
752                   name);
753     return a;
754 }
755
756 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
757                        ObUserAction uact)
758 {
759     gchar *actname;
760     ObAction *act = NULL;
761     xmlNodePtr n;
762
763     if (parse_attr_string("name", node, &actname)) {
764         if ((act = action_from_string(actname, uact))) {
765             } else if (act->func == action_move_relative_horz ||
766                        act->func == action_move_relative_vert ||
767                        act->func == action_resize_relative_horz ||
768                        act->func == action_resize_relative_vert) {
769                 if ((n = parse_find_node("delta", node->xmlChildrenNode)))
770                     act->data.relative.deltax = parse_int(doc, n);
771             } else if (act->func == action_move_relative) {
772                 if ((n = parse_find_node("x", node->xmlChildrenNode)))
773                     act->data.relative.deltax = parse_int(doc, n);
774                 if ((n = parse_find_node("y", node->xmlChildrenNode)))
775                     act->data.relative.deltay = parse_int(doc, n);
776             } else if (act->func == action_resize_relative) {
777                 if ((n = parse_find_node("left", node->xmlChildrenNode)))
778                     act->data.relative.deltaxl = parse_int(doc, n);
779                 if ((n = parse_find_node("up", node->xmlChildrenNode)))
780                     act->data.relative.deltayu = parse_int(doc, n);
781                 if ((n = parse_find_node("right", node->xmlChildrenNode)))
782                     act->data.relative.deltax = parse_int(doc, n);
783                 if ((n = parse_find_node("down", node->xmlChildrenNode)))
784                     act->data.relative.deltay = parse_int(doc, n);
785             } else if (act->func == action_desktop) {
786                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
787                     act->data.desktop.desk = parse_int(doc, n);
788                 if (act->data.desktop.desk > 0) act->data.desktop.desk--;
789 /*
790                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
791                     act->data.desktop.inter.any.interactive =
792                         parse_bool(doc, n);
793 */
794            } else if (act->func == action_send_to_desktop) {
795                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
796                     act->data.sendto.desk = parse_int(doc, n);
797                 if (act->data.sendto.desk > 0) act->data.sendto.desk--;
798                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
799                     act->data.sendto.follow = parse_bool(doc, n);
800             } else if (act->func == action_desktop_dir) {
801                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
802                     act->data.desktopdir.wrap = parse_bool(doc, n); 
803                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
804                     act->data.desktopdir.inter.any.interactive =
805                         parse_bool(doc, n);
806             } else if (act->func == action_send_to_desktop_dir) {
807                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
808                     act->data.sendtodir.wrap = parse_bool(doc, n);
809                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
810                     act->data.sendtodir.follow = parse_bool(doc, n);
811                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
812                     act->data.sendtodir.inter.any.interactive =
813                         parse_bool(doc, n);
814             } else if (act->func == action_directional_focus) {
815                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
816                     act->data.interdiraction.dialog = parse_bool(doc, n);
817                 if ((n = parse_find_node("panels", node->xmlChildrenNode)))
818                     act->data.interdiraction.dock_windows = parse_bool(doc, n);
819                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
820                     act->data.interdiraction.desktop_windows =
821                         parse_bool(doc, n);
822             } else if (act->func == action_resize) {
823                 if ((n = parse_find_node("edge", node->xmlChildrenNode))) {
824                     gchar *s = parse_string(doc, n);
825                     if (!g_ascii_strcasecmp(s, "top"))
826                         act->data.moveresize.corner =
827                             prop_atoms.net_wm_moveresize_size_top;
828                     else if (!g_ascii_strcasecmp(s, "bottom"))
829                         act->data.moveresize.corner =
830                             prop_atoms.net_wm_moveresize_size_bottom;
831                     else if (!g_ascii_strcasecmp(s, "left"))
832                         act->data.moveresize.corner =
833                             prop_atoms.net_wm_moveresize_size_left;
834                     else if (!g_ascii_strcasecmp(s, "right"))
835                         act->data.moveresize.corner =
836                             prop_atoms.net_wm_moveresize_size_right;
837                     else if (!g_ascii_strcasecmp(s, "topleft"))
838                         act->data.moveresize.corner =
839                             prop_atoms.net_wm_moveresize_size_topleft;
840                     else if (!g_ascii_strcasecmp(s, "topright"))
841                         act->data.moveresize.corner =
842                             prop_atoms.net_wm_moveresize_size_topright;
843                     else if (!g_ascii_strcasecmp(s, "bottomleft"))
844                         act->data.moveresize.corner =
845                             prop_atoms.net_wm_moveresize_size_bottomleft;
846                     else if (!g_ascii_strcasecmp(s, "bottomright"))
847                         act->data.moveresize.corner =
848                             prop_atoms.net_wm_moveresize_size_bottomright;
849                     g_free(s);
850                 }
851             INTERACTIVE_LIMIT(act, uact);
852         }
853         g_free(actname);
854     }
855     return act;
856 }
857
858 void action_run_list(GSList *acts, ObClient *c, ObFrameContext context,
859                      guint state, guint button, gint x, gint y, Time time,
860                      gboolean cancel, gboolean done)
861 {
862     GSList *it;
863     ObAction *a;
864
865     if (!acts)
866         return;
867
868     if (x < 0 && y < 0)
869         screen_pointer_pos(&x, &y);
870
871     for (it = acts; it; it = g_slist_next(it)) {
872         a = it->data;
873
874         if (!(a->data.any.client_action == OB_CLIENT_ACTION_ALWAYS && !c)) {
875             a->data.any.c = a->data.any.client_action ? c : NULL;
876             a->data.any.context = context;
877             a->data.any.x = x;
878             a->data.any.y = y;
879
880             a->data.any.button = button;
881
882             a->data.any.time = time;
883
884             if (a->data.any.interactive) {
885                 a->data.inter.cancel = cancel;
886                 a->data.inter.final = done;
887                 if (!(cancel || done))
888                     if (!keyboard_interactive_grab(state, a->data.any.c, a))
889                         continue;
890             }
891
892             /* XXX UGLY HACK race with motion event starting a move and the
893                button release gettnig processed first. answer: don't queue
894                moveresize starts. UGLY HACK XXX
895
896                XXX ALSO don't queue showmenu events, because on button press
897                events we need to know if a mouse grab is going to take place,
898                and set the button to 0, so that later motion events don't think
899                that a drag is going on. since showmenu grabs the pointer..
900             */
901             if (a->data.any.interactive || a->func == action_move ||
902                 a->func == action_resize || a->func == action_showmenu)
903             {
904                 /* interactive actions are not queued */
905                 a->func(&a->data);
906             } else if (a->func == action_focus ||
907                        a->func == action_activate ||
908                        a->func == action_showmenu)
909             {
910                 /* XXX MORE UGLY HACK
911                    actions from clicks on client windows are NOT queued.
912                    this solves the mysterious click-and-drag-doesnt-work
913                    problem. it was because the window gets focused and stuff
914                    after the button event has already been passed through. i
915                    dont really know why it should care but it does and it makes
916                    a difference.
917
918                    however this very bogus ! !
919                    we want to send the button press to the window BEFORE
920                    we do the action because the action might move the windows
921                    (eg change desktops) and then the button press ends up on
922                    the completely wrong window !
923                    so, this is just for that bug, and it will only NOT queue it
924                    if it is a focusing action that can be used with the mouse
925                    pointer. ugh.
926
927                    also with the menus, there is a race going on. if the
928                    desktop wants to pop up a menu, and we do too, we send them
929                    the button before we pop up the menu, so they pop up their
930                    menu first. but not always. if we pop up our menu before
931                    sending them the button press, then the result is
932                    deterministic. yay.
933
934                    XXX further more. focus actions are not queued at all,
935                    because if you bind focus->showmenu, the menu will get
936                    hidden to do the focusing
937                 */
938                 a->func(&a->data);
939             } else
940                 ob_main_loop_queue_action(ob_main_loop, a);
941         }
942     }
943 }
944
945 void action_run_string(const gchar *name, struct _ObClient *c, Time time)
946 {
947     ObAction *a;
948     GSList *l;
949
950     a = action_from_string(name, OB_USER_ACTION_NONE);
951     g_assert(a);
952
953     l = g_slist_append(NULL, a);
954
955     action_run(l, c, 0, time);
956 }
957
958 void action_unshaderaise(union ActionData *data)
959 {
960     if (data->client.any.c->shaded)
961         action_unshade(data);
962     else
963         action_raise(data);
964 }
965
966 void action_shadelower(union ActionData *data)
967 {
968     if (data->client.any.c->shaded)
969         action_lower(data);
970     else
971         action_shade(data);
972 }
973
974 void action_kill(union ActionData *data)
975 {
976     client_kill(data->client.any.c);
977 }
978
979 void action_shade(union ActionData *data)
980 {
981     client_action_start(data);
982     client_shade(data->client.any.c, TRUE);
983     client_action_end(data, config_focus_under_mouse);
984 }
985
986 void action_unshade(union ActionData *data)
987 {
988     client_action_start(data);
989     client_shade(data->client.any.c, FALSE);
990     client_action_end(data, config_focus_under_mouse);
991 }
992
993 void action_toggle_shade(union ActionData *data)
994 {
995     client_action_start(data);
996     client_shade(data->client.any.c, !data->client.any.c->shaded);
997     client_action_end(data, config_focus_under_mouse);
998 }
999
1000 void action_toggle_omnipresent(union ActionData *data)
1001
1002     client_set_desktop(data->client.any.c,
1003                        data->client.any.c->desktop == DESKTOP_ALL ?
1004                        screen_desktop : DESKTOP_ALL, FALSE, TRUE);
1005 }
1006
1007 void action_move_relative_horz(union ActionData *data)
1008 {
1009     ObClient *c = data->relative.any.c;
1010     client_action_start(data);
1011     client_move(c, c->area.x + data->relative.deltax, c->area.y);
1012     client_action_end(data, FALSE);
1013 }
1014
1015 void action_move_relative_vert(union ActionData *data)
1016 {
1017     ObClient *c = data->relative.any.c;
1018     client_action_start(data);
1019     client_move(c, c->area.x, c->area.y + data->relative.deltax);
1020     client_action_end(data, FALSE);
1021 }
1022
1023 void action_resize_relative_horz(union ActionData *data)
1024 {
1025     ObClient *c = data->relative.any.c;
1026     client_action_start(data);
1027     client_resize(c,
1028                   c->area.width + data->relative.deltax * c->size_inc.width,
1029                   c->area.height);
1030     client_action_end(data, FALSE);
1031 }
1032
1033 void action_resize_relative_vert(union ActionData *data)
1034 {
1035     ObClient *c = data->relative.any.c;
1036     if (!c->shaded) {
1037         client_action_start(data);
1038         client_resize(c, c->area.width, c->area.height +
1039                       data->relative.deltax * c->size_inc.height);
1040         client_action_end(data, FALSE);
1041     }
1042 }
1043
1044 void action_move_relative(union ActionData *data)
1045 {
1046     ObClient *c = data->relative.any.c;
1047     client_action_start(data);
1048     client_move(c, c->area.x + data->relative.deltax, c->area.y +
1049                 data->relative.deltay);
1050     client_action_end(data, FALSE);
1051 }
1052
1053 void action_resize_relative(union ActionData *data)
1054 {
1055     ObClient *c = data->relative.any.c;
1056     gint x, y, ow, xoff, nw, oh, yoff, nh, lw, lh;
1057
1058     client_action_start(data);
1059
1060     x = c->area.x;
1061     y = c->area.y;
1062     ow = c->area.width;
1063     xoff = -data->relative.deltaxl * c->size_inc.width;
1064     nw = ow + data->relative.deltax * c->size_inc.width
1065         + data->relative.deltaxl * c->size_inc.width;
1066     oh = c->area.height;
1067     yoff = -data->relative.deltayu * c->size_inc.height;
1068     nh = oh + data->relative.deltay * c->size_inc.height
1069         + data->relative.deltayu * c->size_inc.height;
1070
1071     g_print("deltax %d %d x %d ow %d xoff %d nw %d\n",
1072             data->relative.deltax, 
1073             data->relative.deltaxl, 
1074             x, ow, xoff, nw);
1075     
1076     client_try_configure(c, &x, &y, &nw, &nh, &lw, &lh, TRUE);
1077     xoff = xoff == 0 ? 0 : (xoff < 0 ? MAX(xoff, ow-nw) : MIN(xoff, ow-nw));
1078     yoff = yoff == 0 ? 0 : (yoff < 0 ? MAX(yoff, oh-nh) : MIN(yoff, oh-nh));
1079     client_move_resize(c, x + xoff, y + yoff, nw, nh);
1080     client_action_end(data, FALSE);
1081 }
1082
1083 void action_send_to_desktop(union ActionData *data)
1084 {
1085     ObClient *c = data->sendto.any.c;
1086
1087     if (!client_normal(c)) return;
1088
1089     if (data->sendto.desk < screen_num_desktops ||
1090         data->sendto.desk == DESKTOP_ALL) {
1091         client_set_desktop(c, data->sendto.desk, data->sendto.follow, FALSE);
1092         if (data->sendto.follow && data->sendto.desk != screen_desktop)
1093             screen_set_desktop(data->sendto.desk, TRUE);
1094     }
1095 }
1096
1097 void action_desktop(union ActionData *data)
1098 {
1099     /* XXX add the interactive/dialog option back again once the dialog
1100        has been made to not use grabs */
1101     if (data->desktop.desk < screen_num_desktops ||
1102         data->desktop.desk == DESKTOP_ALL)
1103     {
1104         screen_set_desktop(data->desktop.desk, TRUE);
1105         if (data->inter.any.interactive)
1106             screen_desktop_popup(data->desktop.desk, TRUE);
1107     }
1108 }
1109
1110 void action_desktop_dir(union ActionData *data)
1111 {
1112     guint d;
1113
1114     d = screen_cycle_desktop(data->desktopdir.dir,
1115                              data->desktopdir.wrap,
1116                              data->desktopdir.linear,
1117                              data->desktopdir.inter.any.interactive,
1118                              data->desktopdir.inter.final,
1119                              data->desktopdir.inter.cancel);
1120     /* only move the desktop when the action is complete. if we switch
1121        desktops during the interactive action, focus will move but with
1122        NotifyWhileGrabbed and applications don't like that. */
1123     if (!data->sendtodir.inter.any.interactive ||
1124         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
1125     {
1126         if (d != screen_desktop)
1127             screen_set_desktop(d, TRUE);
1128     }
1129 }
1130
1131 void action_send_to_desktop_dir(union ActionData *data)
1132 {
1133     ObClient *c = data->sendtodir.inter.any.c;
1134     guint d;
1135
1136     if (!client_normal(c)) return;
1137
1138     d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
1139                              data->sendtodir.linear,
1140                              data->sendtodir.inter.any.interactive,
1141                              data->sendtodir.inter.final,
1142                              data->sendtodir.inter.cancel);
1143     /* only move the desktop when the action is complete. if we switch
1144        desktops during the interactive action, focus will move but with
1145        NotifyWhileGrabbed and applications don't like that. */
1146     if (!data->sendtodir.inter.any.interactive ||
1147         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
1148     {
1149         client_set_desktop(c, d, data->sendtodir.follow, FALSE);
1150         if (data->sendtodir.follow && d != screen_desktop)
1151             screen_set_desktop(d, TRUE);
1152     }
1153 }
1154
1155 void action_desktop_last(union ActionData *data)
1156 {
1157     if (screen_last_desktop < screen_num_desktops)
1158         screen_set_desktop(screen_last_desktop, TRUE);
1159 }
1160
1161 void action_toggle_decorations(union ActionData *data)
1162 {
1163     ObClient *c = data->client.any.c;
1164
1165     client_action_start(data);
1166     client_set_undecorated(c, !c->undecorated);
1167     client_action_end(data, FALSE);
1168 }
1169
1170 static guint32 pick_corner(gint x, gint y, gint cx, gint cy, gint cw, gint ch,
1171                            gboolean shaded)
1172 {
1173     /* let's make x and y client relative instead of screen relative */
1174     x = x - cx;
1175     y = ch - (y - cy); /* y is inverted, 0 is at the bottom of the window */
1176
1177 #define X x*ch/cw
1178 #define A -4*X + 7*ch/3
1179 #define B  4*X -15*ch/9
1180 #define C -X/4 + 2*ch/3
1181 #define D  X/4 + 5*ch/12
1182 #define E  X/4 +   ch/3
1183 #define F -X/4 + 7*ch/12
1184 #define G  4*X - 4*ch/3
1185 #define H -4*X + 8*ch/3
1186 #define a (y > 5*ch/9)
1187 #define b (x < 4*cw/9)
1188 #define c (x > 5*cw/9)
1189 #define d (y < 4*ch/9)
1190
1191     /*
1192       Each of these defines (except X which is just there for fun), represents
1193       the equation of a line. The lines they represent are shown in the diagram
1194       below. Checking y against these lines, we are able to choose a region
1195       of the window as shown.
1196
1197       +---------------------A-------|-------|-------B---------------------+
1198       |                     |A                     B|                     |
1199       |                     |A      |       |      B|                     |
1200       |                     | A                   B |                     |
1201       |                     | A     |       |     B |                     |
1202       |                     |  A                 B  |                     |
1203       |                     |  A    |       |    B  |                     |
1204       |        northwest    |   A     north     B   |   northeast         |
1205       |                     |   A   |       |   B   |                     |
1206       |                     |    A             B    |                     |
1207       C---------------------+----A--+-------+--B----+---------------------D
1208       |CCCCCCC              |     A           B     |              DDDDDDD|
1209       |       CCCCCCCC      |     A |       | B     |      DDDDDDDD       |
1210       |               CCCCCCC      A         B      DDDDDDD               |
1211       - - - - - - - - - - - +CCCCCCC+aaaaaaa+DDDDDDD+ - - - - - - - - - - - -
1212       |                     |       b       c       |                     | sh
1213       |             west    |       b  move c       |   east              | ad
1214       |                     |       b       c       |                     | ed
1215       - - - - - - - - - - - +EEEEEEE+ddddddd+FFFFFFF+- - - - - - - - - - -  -
1216       |               EEEEEEE      G         H      FFFFFFF               |
1217       |       EEEEEEEE      |     G |       | H     |      FFFFFFFF       |
1218       |EEEEEEE              |     G           H     |              FFFFFFF|
1219       E---------------------+----G--+-------+--H----+---------------------F
1220       |                     |    G             H    |                     |
1221       |                     |   G   |       |   H   |                     |
1222       |        southwest    |   G     south     H   |   southeast         |
1223       |                     |  G    |       |    H  |                     |
1224       |                     |  G                 H  |                     |
1225       |                     | G     |       |     H |                     |
1226       |                     | G                   H |                     |
1227       |                     |G      |       |      H|                     |
1228       |                     |G                     H|                     |
1229       +---------------------G-------|-------|-------H---------------------+
1230     */
1231
1232     if (shaded) {
1233         /* for shaded windows, you can only resize west/east and move */
1234         if (b)
1235             return prop_atoms.net_wm_moveresize_size_left;
1236         if (c)
1237             return prop_atoms.net_wm_moveresize_size_right;
1238         return prop_atoms.net_wm_moveresize_move;
1239     }
1240
1241     if (y < A && y >= C)
1242         return prop_atoms.net_wm_moveresize_size_topleft;
1243     else if (y >= A && y >= B && a)
1244         return prop_atoms.net_wm_moveresize_size_top;
1245     else if (y < B && y >= D)
1246         return prop_atoms.net_wm_moveresize_size_topright;
1247     else if (y < C && y >= E && b)
1248         return prop_atoms.net_wm_moveresize_size_left;
1249     else if (y < D && y >= F && c)
1250         return prop_atoms.net_wm_moveresize_size_right;
1251     else if (y < E && y >= G)
1252         return prop_atoms.net_wm_moveresize_size_bottomleft;
1253     else if (y < G && y < H && d)
1254         return prop_atoms.net_wm_moveresize_size_bottom;
1255     else if (y >= H && y < F)
1256         return prop_atoms.net_wm_moveresize_size_bottomright;
1257     else
1258         return prop_atoms.net_wm_moveresize_move;
1259
1260 #undef X
1261 #undef A
1262 #undef B
1263 #undef C
1264 #undef D
1265 #undef E
1266 #undef F
1267 #undef G
1268 #undef H
1269 #undef a
1270 #undef b
1271 #undef c
1272 #undef d
1273 }
1274
1275 void action_resize(union ActionData *data)
1276 {
1277     ObClient *c = data->moveresize.any.c;
1278     guint32 corner;
1279
1280     if (data->moveresize.keyboard)
1281         corner = prop_atoms.net_wm_moveresize_size_keyboard;
1282     else if (data->moveresize.corner)
1283         corner = data->moveresize.corner; /* it was specified in the binding */
1284     else
1285         corner = pick_corner(data->any.x, data->any.y,
1286                              c->frame->area.x, c->frame->area.y,
1287                              /* use the client size because the frame
1288                                 can be differently sized (shaded
1289                                 windows) and we want this based on the
1290                                 clients size */
1291                              c->area.width + c->frame->size.left +
1292                              c->frame->size.right,
1293                              c->area.height + c->frame->size.top +
1294                              c->frame->size.bottom, c->shaded);
1295
1296     moveresize_start(c, data->any.x, data->any.y, data->any.button, corner);
1297 }
1298
1299 void action_directional_focus(union ActionData *data)
1300 {
1301     /* if using focus_delay, stop the timer now so that focus doesn't go moving
1302        on us */
1303     event_halt_focus_delay();
1304
1305     focus_directional_cycle(data->interdiraction.direction,
1306                             data->interdiraction.dock_windows,
1307                             data->interdiraction.desktop_windows,
1308                             data->any.interactive,
1309                             data->interdiraction.dialog,
1310                             data->interdiraction.inter.final,
1311                             data->interdiraction.inter.cancel);
1312 }
1313
1314 void action_movetoedge(union ActionData *data)
1315 {
1316     gint x, y;
1317     ObClient *c = data->diraction.any.c;
1318
1319     x = c->frame->area.x;
1320     y = c->frame->area.y;
1321     
1322     switch(data->diraction.direction) {
1323     case OB_DIRECTION_NORTH:
1324         y = client_directional_edge_search(c, OB_DIRECTION_NORTH,
1325                                            data->diraction.hang)
1326             - (data->diraction.hang ? c->frame->area.height : 0);
1327         break;
1328     case OB_DIRECTION_WEST:
1329         x = client_directional_edge_search(c, OB_DIRECTION_WEST,
1330                                            data->diraction.hang)
1331             - (data->diraction.hang ? c->frame->area.width : 0);
1332         break;
1333     case OB_DIRECTION_SOUTH:
1334         y = client_directional_edge_search(c, OB_DIRECTION_SOUTH,
1335                                            data->diraction.hang)
1336             - (data->diraction.hang ? 0 : c->frame->area.height);
1337         break;
1338     case OB_DIRECTION_EAST:
1339         x = client_directional_edge_search(c, OB_DIRECTION_EAST,
1340                                            data->diraction.hang)
1341             - (data->diraction.hang ? 0 : c->frame->area.width);
1342         break;
1343     default:
1344         g_assert_not_reached();
1345     }
1346     frame_frame_gravity(c->frame, &x, &y, c->area.width, c->area.height);
1347     client_action_start(data);
1348     client_move(c, x, y);
1349     client_action_end(data, FALSE);
1350 }
1351
1352 void action_growtoedge(union ActionData *data)
1353 {
1354     gint x, y, width, height, dest;
1355     ObClient *c = data->diraction.any.c;
1356     Rect *a;
1357
1358     a = screen_area(c->desktop, SCREEN_AREA_ALL_MONITORS, &c->frame->area);
1359     x = c->frame->area.x;
1360     y = c->frame->area.y;
1361     /* get the unshaded frame's dimensions..if it is shaded */
1362     width = c->area.width + c->frame->size.left + c->frame->size.right;
1363     height = c->area.height + c->frame->size.top + c->frame->size.bottom;
1364
1365     switch(data->diraction.direction) {
1366     case OB_DIRECTION_NORTH:
1367         if (c->shaded) break; /* don't allow vertical resize if shaded */
1368
1369         dest = client_directional_edge_search(c, OB_DIRECTION_NORTH, FALSE);
1370         if (a->y == y)
1371             height = height / 2;
1372         else {
1373             height = c->frame->area.y + height - dest;
1374             y = dest;
1375         }
1376         break;
1377     case OB_DIRECTION_WEST:
1378         dest = client_directional_edge_search(c, OB_DIRECTION_WEST, FALSE);
1379         if (a->x == x)
1380             width = width / 2;
1381         else {
1382             width = c->frame->area.x + width - dest;
1383             x = dest;
1384         }
1385         break;
1386     case OB_DIRECTION_SOUTH:
1387         if (c->shaded) break; /* don't allow vertical resize if shaded */
1388
1389         dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH, FALSE);
1390         if (a->y + a->height == y + c->frame->area.height) {
1391             height = c->frame->area.height / 2;
1392             y = a->y + a->height - height;
1393         } else
1394             height = dest - c->frame->area.y;
1395         y += (height - c->frame->area.height) % c->size_inc.height;
1396         height -= (height - c->frame->area.height) % c->size_inc.height;
1397         break;
1398     case OB_DIRECTION_EAST:
1399         dest = client_directional_edge_search(c, OB_DIRECTION_EAST, FALSE);
1400         if (a->x + a->width == x + c->frame->area.width) {
1401             width = c->frame->area.width / 2;
1402             x = a->x + a->width - width;
1403         } else
1404             width = dest - c->frame->area.x;
1405         x += (width - c->frame->area.width) % c->size_inc.width;
1406         width -= (width - c->frame->area.width) % c->size_inc.width;
1407         break;
1408     default:
1409         g_assert_not_reached();
1410     }
1411     width -= c->frame->size.left + c->frame->size.right;
1412     height -= c->frame->size.top + c->frame->size.bottom;
1413     frame_frame_gravity(c->frame, &x, &y, width, height);
1414     client_action_start(data);
1415     client_move_resize(c, x, y, width, height);
1416     client_action_end(data, FALSE);
1417     g_free(a);
1418 }
1419
1420 void action_send_to_layer(union ActionData *data)
1421 {
1422     client_set_layer(data->layer.any.c, data->layer.layer);
1423 }
1424
1425 void action_toggle_layer(union ActionData *data)
1426 {
1427     ObClient *c = data->layer.any.c;
1428
1429     client_action_start(data);
1430     if (data->layer.layer < 0)
1431         client_set_layer(c, c->below ? 0 : -1);
1432     else if (data->layer.layer > 0)
1433         client_set_layer(c, c->above ? 0 : 1);
1434     client_action_end(data, config_focus_under_mouse);
1435 }
1436
1437 void action_toggle_dockautohide(union ActionData *data)
1438 {
1439     config_dock_hide = !config_dock_hide;
1440     dock_configure();
1441 }
1442
1443 void action_add_desktop(union ActionData *data)
1444 {
1445     client_action_start(data);
1446     screen_set_num_desktops(screen_num_desktops+1);
1447
1448     /* move all the clients over */
1449     if (data->addremovedesktop.current) {
1450         GList *it;
1451
1452         for (it = client_list; it; it = g_list_next(it)) {
1453             ObClient *c = it->data;
1454             if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop)
1455                 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
1456         }
1457     }
1458
1459     client_action_end(data, config_focus_under_mouse);
1460 }
1461
1462 void action_remove_desktop(union ActionData *data)
1463 {
1464     guint rmdesktop, movedesktop;
1465     GList *it, *stacking_copy;
1466
1467     if (screen_num_desktops < 2) return;
1468
1469     client_action_start(data);
1470
1471     /* what desktop are we removing and moving to? */
1472     if (data->addremovedesktop.current)
1473         rmdesktop = screen_desktop;
1474     else
1475         rmdesktop = screen_num_desktops - 1;
1476     if (rmdesktop < screen_num_desktops - 1)
1477         movedesktop = rmdesktop + 1;
1478     else
1479         movedesktop = rmdesktop;
1480
1481     /* make a copy of the list cuz we're changing it */
1482     stacking_copy = g_list_copy(stacking_list);
1483     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
1484         if (WINDOW_IS_CLIENT(it->data)) {
1485             ObClient *c = it->data;
1486             guint d = c->desktop;
1487             if (d != DESKTOP_ALL && d >= movedesktop) {
1488                 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
1489                 ob_debug("moving window %s\n", c->title);
1490             }
1491             /* raise all the windows that are on the current desktop which
1492                is being merged */
1493             if ((screen_desktop == rmdesktop - 1 ||
1494                  screen_desktop == rmdesktop) &&
1495                 (d == DESKTOP_ALL || d == screen_desktop))
1496             {
1497                 stacking_raise(CLIENT_AS_WINDOW(c));
1498                 ob_debug("raising window %s\n", c->title);
1499             }
1500         }
1501     }
1502
1503     /* act like we're changing desktops */
1504     if (screen_desktop < screen_num_desktops - 1) {
1505         gint d = screen_desktop;
1506         screen_desktop = screen_last_desktop;
1507         screen_set_desktop(d, TRUE);
1508         ob_debug("fake desktop change\n");
1509     }
1510
1511     screen_set_num_desktops(screen_num_desktops-1);
1512
1513     client_action_end(data, config_focus_under_mouse);
1514 }