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