add the resize 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(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         "toggledecorations",
437         action_toggle_decorations,
438         setup_client_action
439     },
440     {
441         "toggledockautohide",
442         action_toggle_dockautohide,
443         NULL
444     },
445     {
446         "desktoplast",
447         action_desktop_last,
448         NULL
449     },
450     {
451         "sendtotoplayer",
452         action_send_to_layer,
453         setup_action_top_layer
454     },
455     {
456         "togglealwaysontop",
457         action_toggle_layer,
458         setup_action_top_layer
459     },
460     {
461         "sendtonormallayer",
462         action_send_to_layer,
463         setup_action_normal_layer
464     },
465     {
466         "sendtobottomlayer",
467         action_send_to_layer,
468         setup_action_bottom_layer
469     },
470     {
471         "togglealwaysonbottom",
472         action_toggle_layer,
473         setup_action_bottom_layer
474     },
475     {
476         "movefromedgenorth",
477         action_movetoedge,
478         setup_action_movefromedge_north
479     },
480     {
481         "movefromedgesouth",
482         action_movetoedge,
483         setup_action_movefromedge_south
484     },
485     {
486         "movefromedgewest",
487         action_movetoedge,
488         setup_action_movefromedge_west
489     },
490     {
491         "movefromedgeeast",
492         action_movetoedge,
493         setup_action_movefromedge_east
494     },
495     {
496         "movetoedgenorth",
497         action_movetoedge,
498         setup_action_movetoedge_north
499     },
500     {
501         "movetoedgesouth",
502         action_movetoedge,
503         setup_action_movetoedge_south
504     },
505     {
506         "movetoedgewest",
507         action_movetoedge,
508         setup_action_movetoedge_west
509     },
510     {
511         "movetoedgeeast",
512         action_movetoedge,
513         setup_action_movetoedge_east
514     },
515     {
516         "growtoedgenorth",
517         action_growtoedge,
518         setup_action_growtoedge_north
519     },
520     {
521         "growtoedgesouth",
522         action_growtoedge,
523         setup_action_growtoedge_south
524     },
525     {
526         "growtoedgewest",
527         action_growtoedge,
528         setup_action_growtoedge_west
529     },
530     {
531         "growtoedgeeast",
532         action_growtoedge,
533         setup_action_growtoedge_east
534     },
535     {
536         "adddesktoplast",
537         action_add_desktop,
538         setup_action_addremove_desktop_last
539     },
540     {
541         "removedesktoplast",
542         action_remove_desktop,
543         setup_action_addremove_desktop_last
544     },
545     {
546         "adddesktopcurrent",
547         action_add_desktop,
548         setup_action_addremove_desktop_current
549     },
550     {
551         "removedesktopcurrent",
552         action_remove_desktop,
553         setup_action_addremove_desktop_current
554     },
555     {
556         NULL,
557         NULL,
558         NULL
559     }
560 };
561
562 /* only key bindings can be interactive. thus saith the xor.
563    because of how the mouse is grabbed, mouse events dont even get
564    read during interactive events, so no dice! >:) */
565 #define INTERACTIVE_LIMIT(a, uact) \
566     if (uact != OB_USER_ACTION_KEYBOARD_KEY) \
567         a->data.any.interactive = FALSE;
568
569 ObAction *action_from_string(const gchar *name, ObUserAction uact)
570 {
571     ObAction *a = NULL;
572     gboolean exist = FALSE;
573     gint i;
574
575     for (i = 0; actionstrings[i].name; i++)
576         if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
577             exist = TRUE;
578             a = action_new(actionstrings[i].func);
579             if (actionstrings[i].setup)
580                 actionstrings[i].setup(&a, uact);
581             if (a)
582                 INTERACTIVE_LIMIT(a, uact);
583             break;
584         }
585     if (!exist)
586         g_message(_("Invalid action '%s' requested. No such action exists."),
587                   name);
588     if (!a)
589         g_message(_("Invalid use of action '%s'. Action will be ignored."),
590                   name);
591     return a;
592 }
593
594 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
595                        ObUserAction uact)
596 {
597     gchar *actname;
598     ObAction *act = NULL;
599     xmlNodePtr n;
600
601     if (parse_attr_string("name", node, &actname)) {
602         if ((act = action_from_string(actname, uact))) {
603             } else if (act->func == action_resize_relative) {
604                 if ((n = parse_find_node("left", node->xmlChildrenNode)))
605                     act->data.relative.deltaxl = parse_int(doc, n);
606                 if ((n = parse_find_node("up", node->xmlChildrenNode)))
607                     act->data.relative.deltayu = parse_int(doc, n);
608                 if ((n = parse_find_node("right", node->xmlChildrenNode)))
609                     act->data.relative.deltax = parse_int(doc, n);
610                 if ((n = parse_find_node("down", node->xmlChildrenNode)))
611                     act->data.relative.deltay = parse_int(doc, n);
612             } else if (act->func == action_desktop) {
613                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
614                     act->data.desktop.desk = parse_int(doc, n);
615                 if (act->data.desktop.desk > 0) act->data.desktop.desk--;
616 /*
617                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
618                     act->data.desktop.inter.any.interactive =
619                         parse_bool(doc, n);
620 */
621            } else if (act->func == action_send_to_desktop) {
622                 if ((n = parse_find_node("desktop", node->xmlChildrenNode)))
623                     act->data.sendto.desk = parse_int(doc, n);
624                 if (act->data.sendto.desk > 0) act->data.sendto.desk--;
625                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
626                     act->data.sendto.follow = parse_bool(doc, n);
627             } else if (act->func == action_desktop_dir) {
628                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
629                     act->data.desktopdir.wrap = parse_bool(doc, n); 
630                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
631                     act->data.desktopdir.inter.any.interactive =
632                         parse_bool(doc, n);
633             } else if (act->func == action_send_to_desktop_dir) {
634                 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
635                     act->data.sendtodir.wrap = parse_bool(doc, n);
636                 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
637                     act->data.sendtodir.follow = parse_bool(doc, n);
638                 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
639                     act->data.sendtodir.inter.any.interactive =
640                         parse_bool(doc, n);
641             INTERACTIVE_LIMIT(act, uact);
642         }
643         g_free(actname);
644     }
645     return act;
646 }
647
648 void action_run_list(GSList *acts, ObClient *c, ObFrameContext context,
649                      guint state, guint button, gint x, gint y, Time time,
650                      gboolean cancel, gboolean done)
651 {
652     GSList *it;
653     ObAction *a;
654
655     if (!acts)
656         return;
657
658     if (x < 0 && y < 0)
659         screen_pointer_pos(&x, &y);
660
661     for (it = acts; it; it = g_slist_next(it)) {
662         a = it->data;
663
664         if (!(a->data.any.client_action == OB_CLIENT_ACTION_ALWAYS && !c)) {
665             a->data.any.c = a->data.any.client_action ? c : NULL;
666             a->data.any.context = context;
667             a->data.any.x = x;
668             a->data.any.y = y;
669
670             a->data.any.button = button;
671
672             a->data.any.time = time;
673
674             if (a->data.any.interactive) {
675                 a->data.inter.cancel = cancel;
676                 a->data.inter.final = done;
677                 if (!(cancel || done))
678                     if (!keyboard_interactive_grab(state, a->data.any.c, a))
679                         continue;
680             }
681
682             /* XXX UGLY HACK race with motion event starting a move and the
683                button release gettnig processed first. answer: don't queue
684                moveresize starts. UGLY HACK XXX
685
686                XXX ALSO don't queue showmenu events, because on button press
687                events we need to know if a mouse grab is going to take place,
688                and set the button to 0, so that later motion events don't think
689                that a drag is going on. since showmenu grabs the pointer..
690             */
691             if (a->data.any.interactive || a->func == action_move ||
692                 a->func == action_resize || a->func == action_showmenu)
693             {
694                 /* interactive actions are not queued */
695                 a->func(&a->data);
696             } else if (a->func == action_focus ||
697                        a->func == action_activate ||
698                        a->func == action_showmenu)
699             {
700                 /* XXX MORE UGLY HACK
701                    actions from clicks on client windows are NOT queued.
702                    this solves the mysterious click-and-drag-doesnt-work
703                    problem. it was because the window gets focused and stuff
704                    after the button event has already been passed through. i
705                    dont really know why it should care but it does and it makes
706                    a difference.
707
708                    however this very bogus ! !
709                    we want to send the button press to the window BEFORE
710                    we do the action because the action might move the windows
711                    (eg change desktops) and then the button press ends up on
712                    the completely wrong window !
713                    so, this is just for that bug, and it will only NOT queue it
714                    if it is a focusing action that can be used with the mouse
715                    pointer. ugh.
716
717                    also with the menus, there is a race going on. if the
718                    desktop wants to pop up a menu, and we do too, we send them
719                    the button before we pop up the menu, so they pop up their
720                    menu first. but not always. if we pop up our menu before
721                    sending them the button press, then the result is
722                    deterministic. yay.
723
724                    XXX further more. focus actions are not queued at all,
725                    because if you bind focus->showmenu, the menu will get
726                    hidden to do the focusing
727                 */
728                 a->func(&a->data);
729             } else
730                 ob_main_loop_queue_action(ob_main_loop, a);
731         }
732     }
733 }
734
735 void action_run_string(const gchar *name, struct _ObClient *c, Time time)
736 {
737     ObAction *a;
738     GSList *l;
739
740     a = action_from_string(name, OB_USER_ACTION_NONE);
741     g_assert(a);
742
743     l = g_slist_append(NULL, a);
744
745     action_run(l, c, 0, time);
746 }
747
748 void action_unshaderaise(union ActionData *data)
749 {
750     if (data->client.any.c->shaded)
751         action_unshade(data);
752     else
753         action_raise(data);
754 }
755
756 void action_shadelower(union ActionData *data)
757 {
758     if (data->client.any.c->shaded)
759         action_lower(data);
760     else
761         action_shade(data);
762 }
763
764 void action_resize_relative_horz(union ActionData *data)
765 {
766     ObClient *c = data->relative.any.c;
767     client_action_start(data);
768     client_resize(c,
769                   c->area.width + data->relative.deltax * c->size_inc.width,
770                   c->area.height);
771     client_action_end(data, FALSE);
772 }
773
774 void action_resize_relative_vert(union ActionData *data)
775 {
776     ObClient *c = data->relative.any.c;
777     if (!c->shaded) {
778         client_action_start(data);
779         client_resize(c, c->area.width, c->area.height +
780                       data->relative.deltax * c->size_inc.height);
781         client_action_end(data, FALSE);
782     }
783 }
784
785 void action_resize_relative(union ActionData *data)
786 {
787     ObClient *c = data->relative.any.c;
788     gint x, y, ow, xoff, nw, oh, yoff, nh, lw, lh;
789
790     client_action_start(data);
791
792     x = c->area.x;
793     y = c->area.y;
794     ow = c->area.width;
795     xoff = -data->relative.deltaxl * c->size_inc.width;
796     nw = ow + data->relative.deltax * c->size_inc.width
797         + data->relative.deltaxl * c->size_inc.width;
798     oh = c->area.height;
799     yoff = -data->relative.deltayu * c->size_inc.height;
800     nh = oh + data->relative.deltay * c->size_inc.height
801         + data->relative.deltayu * c->size_inc.height;
802
803     g_print("deltax %d %d x %d ow %d xoff %d nw %d\n",
804             data->relative.deltax, 
805             data->relative.deltaxl, 
806             x, ow, xoff, nw);
807     
808     client_try_configure(c, &x, &y, &nw, &nh, &lw, &lh, TRUE);
809     xoff = xoff == 0 ? 0 : (xoff < 0 ? MAX(xoff, ow-nw) : MIN(xoff, ow-nw));
810     yoff = yoff == 0 ? 0 : (yoff < 0 ? MAX(yoff, oh-nh) : MIN(yoff, oh-nh));
811     client_move_resize(c, x + xoff, y + yoff, nw, nh);
812     client_action_end(data, FALSE);
813 }
814
815 void action_send_to_desktop(union ActionData *data)
816 {
817     ObClient *c = data->sendto.any.c;
818
819     if (!client_normal(c)) return;
820
821     if (data->sendto.desk < screen_num_desktops ||
822         data->sendto.desk == DESKTOP_ALL) {
823         client_set_desktop(c, data->sendto.desk, data->sendto.follow, FALSE);
824         if (data->sendto.follow && data->sendto.desk != screen_desktop)
825             screen_set_desktop(data->sendto.desk, TRUE);
826     }
827 }
828
829 void action_desktop(union ActionData *data)
830 {
831     /* XXX add the interactive/dialog option back again once the dialog
832        has been made to not use grabs */
833     if (data->desktop.desk < screen_num_desktops ||
834         data->desktop.desk == DESKTOP_ALL)
835     {
836         screen_set_desktop(data->desktop.desk, TRUE);
837         if (data->inter.any.interactive)
838             screen_desktop_popup(data->desktop.desk, TRUE);
839     }
840 }
841
842 void action_desktop_dir(union ActionData *data)
843 {
844     guint d;
845
846     d = screen_cycle_desktop(data->desktopdir.dir,
847                              data->desktopdir.wrap,
848                              data->desktopdir.linear,
849                              data->desktopdir.inter.any.interactive,
850                              data->desktopdir.inter.final,
851                              data->desktopdir.inter.cancel);
852     /* only move the desktop when the action is complete. if we switch
853        desktops during the interactive action, focus will move but with
854        NotifyWhileGrabbed and applications don't like that. */
855     if (!data->sendtodir.inter.any.interactive ||
856         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
857     {
858         if (d != screen_desktop)
859             screen_set_desktop(d, TRUE);
860     }
861 }
862
863 void action_send_to_desktop_dir(union ActionData *data)
864 {
865     ObClient *c = data->sendtodir.inter.any.c;
866     guint d;
867
868     if (!client_normal(c)) return;
869
870     d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
871                              data->sendtodir.linear,
872                              data->sendtodir.inter.any.interactive,
873                              data->sendtodir.inter.final,
874                              data->sendtodir.inter.cancel);
875     /* only move the desktop when the action is complete. if we switch
876        desktops during the interactive action, focus will move but with
877        NotifyWhileGrabbed and applications don't like that. */
878     if (!data->sendtodir.inter.any.interactive ||
879         (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
880     {
881         client_set_desktop(c, d, data->sendtodir.follow, FALSE);
882         if (data->sendtodir.follow && d != screen_desktop)
883             screen_set_desktop(d, TRUE);
884     }
885 }
886
887 void action_desktop_last(union ActionData *data)
888 {
889     if (screen_last_desktop < screen_num_desktops)
890         screen_set_desktop(screen_last_desktop, TRUE);
891 }
892
893 void action_toggle_decorations(union ActionData *data)
894 {
895     ObClient *c = data->client.any.c;
896
897     client_action_start(data);
898     client_set_undecorated(c, !c->undecorated);
899     client_action_end(data, FALSE);
900 }
901
902
903 void action_directional_focus(union ActionData *data)
904 {
905     /* if using focus_delay, stop the timer now so that focus doesn't go moving
906        on us */
907     event_halt_focus_delay();
908
909     focus_directional_cycle(data->interdiraction.direction,
910                             data->interdiraction.dock_windows,
911                             data->interdiraction.desktop_windows,
912                             data->any.interactive,
913                             data->interdiraction.dialog,
914                             data->interdiraction.inter.final,
915                             data->interdiraction.inter.cancel);
916 }
917
918 void action_movetoedge(union ActionData *data)
919 {
920     gint x, y;
921     ObClient *c = data->diraction.any.c;
922
923     x = c->frame->area.x;
924     y = c->frame->area.y;
925     
926     switch(data->diraction.direction) {
927     case OB_DIRECTION_NORTH:
928         y = client_directional_edge_search(c, OB_DIRECTION_NORTH,
929                                            data->diraction.hang)
930             - (data->diraction.hang ? c->frame->area.height : 0);
931         break;
932     case OB_DIRECTION_WEST:
933         x = client_directional_edge_search(c, OB_DIRECTION_WEST,
934                                            data->diraction.hang)
935             - (data->diraction.hang ? c->frame->area.width : 0);
936         break;
937     case OB_DIRECTION_SOUTH:
938         y = client_directional_edge_search(c, OB_DIRECTION_SOUTH,
939                                            data->diraction.hang)
940             - (data->diraction.hang ? 0 : c->frame->area.height);
941         break;
942     case OB_DIRECTION_EAST:
943         x = client_directional_edge_search(c, OB_DIRECTION_EAST,
944                                            data->diraction.hang)
945             - (data->diraction.hang ? 0 : c->frame->area.width);
946         break;
947     default:
948         g_assert_not_reached();
949     }
950     frame_frame_gravity(c->frame, &x, &y, c->area.width, c->area.height);
951     client_action_start(data);
952     client_move(c, x, y);
953     client_action_end(data, FALSE);
954 }
955
956 void action_growtoedge(union ActionData *data)
957 {
958     gint x, y, width, height, dest;
959     ObClient *c = data->diraction.any.c;
960     Rect *a;
961
962     a = screen_area(c->desktop, SCREEN_AREA_ALL_MONITORS, &c->frame->area);
963     x = c->frame->area.x;
964     y = c->frame->area.y;
965     /* get the unshaded frame's dimensions..if it is shaded */
966     width = c->area.width + c->frame->size.left + c->frame->size.right;
967     height = c->area.height + c->frame->size.top + c->frame->size.bottom;
968
969     switch(data->diraction.direction) {
970     case OB_DIRECTION_NORTH:
971         if (c->shaded) break; /* don't allow vertical resize if shaded */
972
973         dest = client_directional_edge_search(c, OB_DIRECTION_NORTH, FALSE);
974         if (a->y == y)
975             height = height / 2;
976         else {
977             height = c->frame->area.y + height - dest;
978             y = dest;
979         }
980         break;
981     case OB_DIRECTION_WEST:
982         dest = client_directional_edge_search(c, OB_DIRECTION_WEST, FALSE);
983         if (a->x == x)
984             width = width / 2;
985         else {
986             width = c->frame->area.x + width - dest;
987             x = dest;
988         }
989         break;
990     case OB_DIRECTION_SOUTH:
991         if (c->shaded) break; /* don't allow vertical resize if shaded */
992
993         dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH, FALSE);
994         if (a->y + a->height == y + c->frame->area.height) {
995             height = c->frame->area.height / 2;
996             y = a->y + a->height - height;
997         } else
998             height = dest - c->frame->area.y;
999         y += (height - c->frame->area.height) % c->size_inc.height;
1000         height -= (height - c->frame->area.height) % c->size_inc.height;
1001         break;
1002     case OB_DIRECTION_EAST:
1003         dest = client_directional_edge_search(c, OB_DIRECTION_EAST, FALSE);
1004         if (a->x + a->width == x + c->frame->area.width) {
1005             width = c->frame->area.width / 2;
1006             x = a->x + a->width - width;
1007         } else
1008             width = dest - c->frame->area.x;
1009         x += (width - c->frame->area.width) % c->size_inc.width;
1010         width -= (width - c->frame->area.width) % c->size_inc.width;
1011         break;
1012     default:
1013         g_assert_not_reached();
1014     }
1015     width -= c->frame->size.left + c->frame->size.right;
1016     height -= c->frame->size.top + c->frame->size.bottom;
1017     frame_frame_gravity(c->frame, &x, &y, width, height);
1018     client_action_start(data);
1019     client_move_resize(c, x, y, width, height);
1020     client_action_end(data, FALSE);
1021     g_free(a);
1022 }
1023
1024 void action_send_to_layer(union ActionData *data)
1025 {
1026     client_set_layer(data->layer.any.c, data->layer.layer);
1027 }
1028
1029 void action_toggle_layer(union ActionData *data)
1030 {
1031     ObClient *c = data->layer.any.c;
1032
1033     client_action_start(data);
1034     if (data->layer.layer < 0)
1035         client_set_layer(c, c->below ? 0 : -1);
1036     else if (data->layer.layer > 0)
1037         client_set_layer(c, c->above ? 0 : 1);
1038     client_action_end(data, config_focus_under_mouse);
1039 }
1040
1041 void action_toggle_dockautohide(union ActionData *data)
1042 {
1043     config_dock_hide = !config_dock_hide;
1044     dock_configure();
1045 }
1046
1047 void action_add_desktop(union ActionData *data)
1048 {
1049     client_action_start(data);
1050     screen_set_num_desktops(screen_num_desktops+1);
1051
1052     /* move all the clients over */
1053     if (data->addremovedesktop.current) {
1054         GList *it;
1055
1056         for (it = client_list; it; it = g_list_next(it)) {
1057             ObClient *c = it->data;
1058             if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop)
1059                 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
1060         }
1061     }
1062
1063     client_action_end(data, config_focus_under_mouse);
1064 }
1065
1066 void action_remove_desktop(union ActionData *data)
1067 {
1068     guint rmdesktop, movedesktop;
1069     GList *it, *stacking_copy;
1070
1071     if (screen_num_desktops < 2) return;
1072
1073     client_action_start(data);
1074
1075     /* what desktop are we removing and moving to? */
1076     if (data->addremovedesktop.current)
1077         rmdesktop = screen_desktop;
1078     else
1079         rmdesktop = screen_num_desktops - 1;
1080     if (rmdesktop < screen_num_desktops - 1)
1081         movedesktop = rmdesktop + 1;
1082     else
1083         movedesktop = rmdesktop;
1084
1085     /* make a copy of the list cuz we're changing it */
1086     stacking_copy = g_list_copy(stacking_list);
1087     for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
1088         if (WINDOW_IS_CLIENT(it->data)) {
1089             ObClient *c = it->data;
1090             guint d = c->desktop;
1091             if (d != DESKTOP_ALL && d >= movedesktop) {
1092                 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
1093                 ob_debug("moving window %s\n", c->title);
1094             }
1095             /* raise all the windows that are on the current desktop which
1096                is being merged */
1097             if ((screen_desktop == rmdesktop - 1 ||
1098                  screen_desktop == rmdesktop) &&
1099                 (d == DESKTOP_ALL || d == screen_desktop))
1100             {
1101                 stacking_raise(CLIENT_AS_WINDOW(c));
1102                 ob_debug("raising window %s\n", c->title);
1103             }
1104         }
1105     }
1106
1107     /* act like we're changing desktops */
1108     if (screen_desktop < screen_num_desktops - 1) {
1109         gint d = screen_desktop;
1110         screen_desktop = screen_last_desktop;
1111         screen_set_desktop(d, TRUE);
1112         ob_debug("fake desktop change\n");
1113     }
1114
1115     screen_set_num_desktops(screen_num_desktops-1);
1116
1117     client_action_end(data, config_focus_under_mouse);
1118 }