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