1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 action.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
23 #include "focus_cycle.h"
24 #include "moveresize.h"
37 #include "startupnotify.h"
47 void (*func)(union ActionData *);
48 void (*setup)(ObAction **, ObUserAction uact);
51 static ObAction *action_new(void (*func)(union ActionData *data))
53 ObAction *a = g_new0(ObAction, 1);
60 void action_ref(ObAction *a)
65 void action_unref(ObAction *a)
67 if (a == NULL) return;
69 if (--a->ref > 0) return;
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);
82 ObAction* action_copy(const ObAction *src)
84 ObAction *a = action_new(src->func);
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);
99 void setup_action_send_to_desktop_prev(ObAction **a, ObUserAction uact)
101 (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
102 (*a)->data.sendtodir.inter.any.interactive = TRUE;
103 (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
104 (*a)->data.sendtodir.linear = TRUE;
105 (*a)->data.sendtodir.wrap = TRUE;
106 (*a)->data.sendtodir.follow = TRUE;
109 void setup_action_send_to_desktop_next(ObAction **a, ObUserAction uact)
111 (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
112 (*a)->data.sendtodir.inter.any.interactive = TRUE;
113 (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
114 (*a)->data.sendtodir.linear = TRUE;
115 (*a)->data.sendtodir.wrap = TRUE;
116 (*a)->data.sendtodir.follow = TRUE;
119 void setup_action_send_to_desktop_left(ObAction **a, ObUserAction uact)
121 (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
122 (*a)->data.sendtodir.inter.any.interactive = TRUE;
123 (*a)->data.sendtodir.dir = OB_DIRECTION_WEST;
124 (*a)->data.sendtodir.linear = FALSE;
125 (*a)->data.sendtodir.wrap = TRUE;
126 (*a)->data.sendtodir.follow = TRUE;
129 void setup_action_send_to_desktop_right(ObAction **a, ObUserAction uact)
131 (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
132 (*a)->data.sendtodir.inter.any.interactive = TRUE;
133 (*a)->data.sendtodir.dir = OB_DIRECTION_EAST;
134 (*a)->data.sendtodir.linear = FALSE;
135 (*a)->data.sendtodir.wrap = TRUE;
136 (*a)->data.sendtodir.follow = TRUE;
139 void setup_action_send_to_desktop_up(ObAction **a, ObUserAction uact)
141 (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
142 (*a)->data.sendtodir.inter.any.interactive = TRUE;
143 (*a)->data.sendtodir.dir = OB_DIRECTION_NORTH;
144 (*a)->data.sendtodir.linear = FALSE;
145 (*a)->data.sendtodir.wrap = TRUE;
146 (*a)->data.sendtodir.follow = TRUE;
149 void setup_action_send_to_desktop_down(ObAction **a, ObUserAction uact)
151 (*a)->data.sendtodir.inter.any.client_action = OB_CLIENT_ACTION_ALWAYS;
152 (*a)->data.sendtodir.inter.any.interactive = TRUE;
153 (*a)->data.sendtodir.dir = OB_DIRECTION_SOUTH;
154 (*a)->data.sendtodir.linear = FALSE;
155 (*a)->data.sendtodir.wrap = TRUE;
156 (*a)->data.sendtodir.follow = TRUE;
159 void setup_action_desktop_prev(ObAction **a, ObUserAction uact)
161 (*a)->data.desktopdir.inter.any.interactive = TRUE;
162 (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
163 (*a)->data.desktopdir.linear = TRUE;
164 (*a)->data.desktopdir.wrap = TRUE;
167 void setup_action_desktop_next(ObAction **a, ObUserAction uact)
169 (*a)->data.desktopdir.inter.any.interactive = TRUE;
170 (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
171 (*a)->data.desktopdir.linear = TRUE;
172 (*a)->data.desktopdir.wrap = TRUE;
175 void setup_action_desktop_left(ObAction **a, ObUserAction uact)
177 (*a)->data.desktopdir.inter.any.interactive = TRUE;
178 (*a)->data.desktopdir.dir = OB_DIRECTION_WEST;
179 (*a)->data.desktopdir.linear = FALSE;
180 (*a)->data.desktopdir.wrap = TRUE;
183 void setup_action_desktop_right(ObAction **a, ObUserAction uact)
185 (*a)->data.desktopdir.inter.any.interactive = TRUE;
186 (*a)->data.desktopdir.dir = OB_DIRECTION_EAST;
187 (*a)->data.desktopdir.linear = FALSE;
188 (*a)->data.desktopdir.wrap = TRUE;
191 void setup_action_desktop_up(ObAction **a, ObUserAction uact)
193 (*a)->data.desktopdir.inter.any.interactive = TRUE;
194 (*a)->data.desktopdir.dir = OB_DIRECTION_NORTH;
195 (*a)->data.desktopdir.linear = FALSE;
196 (*a)->data.desktopdir.wrap = TRUE;
199 void setup_action_desktop_down(ObAction **a, ObUserAction uact)
201 (*a)->data.desktopdir.inter.any.interactive = TRUE;
202 (*a)->data.desktopdir.dir = OB_DIRECTION_SOUTH;
203 (*a)->data.desktopdir.linear = FALSE;
204 (*a)->data.desktopdir.wrap = TRUE;
207 void setup_action_movefromedge_north(ObAction **a, ObUserAction uact)
209 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
210 (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
211 (*a)->data.diraction.hang = TRUE;
214 void setup_action_movefromedge_south(ObAction **a, ObUserAction uact)
216 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
217 (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
218 (*a)->data.diraction.hang = TRUE;
221 void setup_action_movefromedge_east(ObAction **a, ObUserAction uact)
223 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
224 (*a)->data.diraction.direction = OB_DIRECTION_EAST;
225 (*a)->data.diraction.hang = TRUE;
228 void setup_action_movefromedge_west(ObAction **a, ObUserAction uact)
230 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
231 (*a)->data.diraction.direction = OB_DIRECTION_WEST;
232 (*a)->data.diraction.hang = TRUE;
235 void setup_action_movetoedge_north(ObAction **a, ObUserAction uact)
237 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
238 (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
239 (*a)->data.diraction.hang = FALSE;
242 void setup_action_movetoedge_south(ObAction **a, ObUserAction uact)
244 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
245 (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
246 (*a)->data.diraction.hang = FALSE;
249 void setup_action_movetoedge_east(ObAction **a, ObUserAction uact)
251 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
252 (*a)->data.diraction.direction = OB_DIRECTION_EAST;
253 (*a)->data.diraction.hang = FALSE;
256 void setup_action_movetoedge_west(ObAction **a, ObUserAction uact)
258 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
259 (*a)->data.diraction.direction = OB_DIRECTION_WEST;
260 (*a)->data.diraction.hang = FALSE;
263 void setup_action_growtoedge_north(ObAction **a, ObUserAction uact)
265 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
266 (*a)->data.diraction.direction = OB_DIRECTION_NORTH;
269 void setup_action_growtoedge_south(ObAction **a, ObUserAction uact)
271 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
272 (*a)->data.diraction.direction = OB_DIRECTION_SOUTH;
275 void setup_action_growtoedge_east(ObAction **a, ObUserAction uact)
277 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
278 (*a)->data.diraction.direction = OB_DIRECTION_EAST;
281 void setup_action_growtoedge_west(ObAction **a, ObUserAction uact)
283 (*a)->data.diraction.any.client_action = OB_CLIENT_ACTION_ALWAYS;
284 (*a)->data.diraction.direction = OB_DIRECTION_WEST;
287 void setup_action_top_layer(ObAction **a, ObUserAction uact)
289 (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
290 (*a)->data.layer.layer = 1;
293 void setup_action_normal_layer(ObAction **a, ObUserAction uact)
295 (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
296 (*a)->data.layer.layer = 0;
299 void setup_action_bottom_layer(ObAction **a, ObUserAction uact)
301 (*a)->data.layer.any.client_action = OB_CLIENT_ACTION_ALWAYS;
302 (*a)->data.layer.layer = -1;
305 void setup_action_addremove_desktop_current(ObAction **a, ObUserAction uact)
307 (*a)->data.addremovedesktop.current = TRUE;
310 void setup_action_addremove_desktop_last(ObAction **a, ObUserAction uact)
312 (*a)->data.addremovedesktop.current = FALSE;
315 void setup_client_action(ObAction **a, ObUserAction uact)
317 (*a)->data.any.client_action = OB_CLIENT_ACTION_ALWAYS;
320 ActionString actionstrings[] =
334 action_send_to_desktop_dir,
335 setup_action_send_to_desktop_next
338 "sendtodesktopprevious",
339 action_send_to_desktop_dir,
340 setup_action_send_to_desktop_prev
343 "sendtodesktopright",
344 action_send_to_desktop_dir,
345 setup_action_send_to_desktop_right
349 action_send_to_desktop_dir,
350 setup_action_send_to_desktop_left
354 action_send_to_desktop_dir,
355 setup_action_send_to_desktop_up
359 action_send_to_desktop_dir,
360 setup_action_send_to_desktop_down
363 "toggledockautohide",
364 action_toggle_dockautohide,
369 action_send_to_layer,
370 setup_action_top_layer
375 setup_action_top_layer
379 action_send_to_layer,
380 setup_action_normal_layer
384 action_send_to_layer,
385 setup_action_bottom_layer
388 "togglealwaysonbottom",
390 setup_action_bottom_layer
395 setup_action_movefromedge_north
400 setup_action_movefromedge_south
405 setup_action_movefromedge_west
410 setup_action_movefromedge_east
415 setup_action_movetoedge_north
420 setup_action_movetoedge_south
425 setup_action_movetoedge_west
430 setup_action_movetoedge_east
435 setup_action_growtoedge_north
440 setup_action_growtoedge_south
445 setup_action_growtoedge_west
450 setup_action_growtoedge_east
455 setup_action_addremove_desktop_last
459 action_remove_desktop,
460 setup_action_addremove_desktop_last
465 setup_action_addremove_desktop_current
468 "removedesktopcurrent",
469 action_remove_desktop,
470 setup_action_addremove_desktop_current
479 /* only key bindings can be interactive. thus saith the xor.
480 because of how the mouse is grabbed, mouse events dont even get
481 read during interactive events, so no dice! >:) */
482 #define INTERACTIVE_LIMIT(a, uact) \
483 if (uact != OB_USER_ACTION_KEYBOARD_KEY) \
484 a->data.any.interactive = FALSE;
486 ObAction *action_from_string(const gchar *name, ObUserAction uact)
489 gboolean exist = FALSE;
492 for (i = 0; actionstrings[i].name; i++)
493 if (!g_ascii_strcasecmp(name, actionstrings[i].name)) {
495 a = action_new(actionstrings[i].func);
496 if (actionstrings[i].setup)
497 actionstrings[i].setup(&a, uact);
499 INTERACTIVE_LIMIT(a, uact);
503 g_message(_("Invalid action '%s' requested. No such action exists."),
506 g_message(_("Invalid use of action '%s'. Action will be ignored."),
511 ObAction *action_parse(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
515 ObAction *act = NULL;
518 if (parse_attr_string("name", node, &actname)) {
519 if ((act = action_from_string(actname, uact))) {
520 } else if (act->func == action_desktop) {
521 } else if (act->func == action_send_to_desktop_dir) {
522 if ((n = parse_find_node("wrap", node->xmlChildrenNode)))
523 act->data.sendtodir.wrap = parse_bool(doc, n);
524 if ((n = parse_find_node("follow", node->xmlChildrenNode)))
525 act->data.sendtodir.follow = parse_bool(doc, n);
526 if ((n = parse_find_node("dialog", node->xmlChildrenNode)))
527 act->data.sendtodir.inter.any.interactive =
529 INTERACTIVE_LIMIT(act, uact);
537 void action_unshaderaise(union ActionData *data)
539 if (data->client.any.c->shaded)
540 action_unshade(data);
545 void action_shadelower(union ActionData *data)
547 if (data->client.any.c->shaded)
553 void action_send_to_desktop_dir(union ActionData *data)
555 ObClient *c = data->sendtodir.inter.any.c;
558 if (!client_normal(c)) return;
560 d = screen_cycle_desktop(data->sendtodir.dir, data->sendtodir.wrap,
561 data->sendtodir.linear,
562 data->sendtodir.inter.any.interactive,
563 data->sendtodir.inter.final,
564 data->sendtodir.inter.cancel);
565 /* only move the desktop when the action is complete. if we switch
566 desktops during the interactive action, focus will move but with
567 NotifyWhileGrabbed and applications don't like that. */
568 if (!data->sendtodir.inter.any.interactive ||
569 (data->sendtodir.inter.final && !data->sendtodir.inter.cancel))
571 client_set_desktop(c, d, data->sendtodir.follow, FALSE);
572 if (data->sendtodir.follow && d != screen_desktop)
573 screen_set_desktop(d, TRUE);
577 void action_directional_focus(union ActionData *data)
579 /* if using focus_delay, stop the timer now so that focus doesn't go moving
581 event_halt_focus_delay();
583 focus_directional_cycle(data->interdiraction.direction,
584 data->interdiraction.dock_windows,
585 data->interdiraction.desktop_windows,
586 data->any.interactive,
587 data->interdiraction.dialog,
588 data->interdiraction.inter.final,
589 data->interdiraction.inter.cancel);
592 void action_movetoedge(union ActionData *data)
595 ObClient *c = data->diraction.any.c;
597 x = c->frame->area.x;
598 y = c->frame->area.y;
600 switch(data->diraction.direction) {
601 case OB_DIRECTION_NORTH:
602 y = client_directional_edge_search(c, OB_DIRECTION_NORTH,
603 data->diraction.hang)
604 - (data->diraction.hang ? c->frame->area.height : 0);
606 case OB_DIRECTION_WEST:
607 x = client_directional_edge_search(c, OB_DIRECTION_WEST,
608 data->diraction.hang)
609 - (data->diraction.hang ? c->frame->area.width : 0);
611 case OB_DIRECTION_SOUTH:
612 y = client_directional_edge_search(c, OB_DIRECTION_SOUTH,
613 data->diraction.hang)
614 - (data->diraction.hang ? 0 : c->frame->area.height);
616 case OB_DIRECTION_EAST:
617 x = client_directional_edge_search(c, OB_DIRECTION_EAST,
618 data->diraction.hang)
619 - (data->diraction.hang ? 0 : c->frame->area.width);
622 g_assert_not_reached();
624 frame_frame_gravity(c->frame, &x, &y, c->area.width, c->area.height);
625 client_action_start(data);
626 client_move(c, x, y);
627 client_action_end(data, FALSE);
630 void action_growtoedge(union ActionData *data)
632 gint x, y, width, height, dest;
633 ObClient *c = data->diraction.any.c;
636 a = screen_area(c->desktop, SCREEN_AREA_ALL_MONITORS, &c->frame->area);
637 x = c->frame->area.x;
638 y = c->frame->area.y;
639 /* get the unshaded frame's dimensions..if it is shaded */
640 width = c->area.width + c->frame->size.left + c->frame->size.right;
641 height = c->area.height + c->frame->size.top + c->frame->size.bottom;
643 switch(data->diraction.direction) {
644 case OB_DIRECTION_NORTH:
645 if (c->shaded) break; /* don't allow vertical resize if shaded */
647 dest = client_directional_edge_search(c, OB_DIRECTION_NORTH, FALSE);
651 height = c->frame->area.y + height - dest;
655 case OB_DIRECTION_WEST:
656 dest = client_directional_edge_search(c, OB_DIRECTION_WEST, FALSE);
660 width = c->frame->area.x + width - dest;
664 case OB_DIRECTION_SOUTH:
665 if (c->shaded) break; /* don't allow vertical resize if shaded */
667 dest = client_directional_edge_search(c, OB_DIRECTION_SOUTH, FALSE);
668 if (a->y + a->height == y + c->frame->area.height) {
669 height = c->frame->area.height / 2;
670 y = a->y + a->height - height;
672 height = dest - c->frame->area.y;
673 y += (height - c->frame->area.height) % c->size_inc.height;
674 height -= (height - c->frame->area.height) % c->size_inc.height;
676 case OB_DIRECTION_EAST:
677 dest = client_directional_edge_search(c, OB_DIRECTION_EAST, FALSE);
678 if (a->x + a->width == x + c->frame->area.width) {
679 width = c->frame->area.width / 2;
680 x = a->x + a->width - width;
682 width = dest - c->frame->area.x;
683 x += (width - c->frame->area.width) % c->size_inc.width;
684 width -= (width - c->frame->area.width) % c->size_inc.width;
687 g_assert_not_reached();
689 width -= c->frame->size.left + c->frame->size.right;
690 height -= c->frame->size.top + c->frame->size.bottom;
691 frame_frame_gravity(c->frame, &x, &y, width, height);
692 client_action_start(data);
693 client_move_resize(c, x, y, width, height);
694 client_action_end(data, FALSE);
698 void action_send_to_layer(union ActionData *data)
700 client_set_layer(data->layer.any.c, data->layer.layer);
703 void action_toggle_layer(union ActionData *data)
705 ObClient *c = data->layer.any.c;
707 client_action_start(data);
708 if (data->layer.layer < 0)
709 client_set_layer(c, c->below ? 0 : -1);
710 else if (data->layer.layer > 0)
711 client_set_layer(c, c->above ? 0 : 1);
712 client_action_end(data, config_focus_under_mouse);
715 void action_toggle_dockautohide(union ActionData *data)
717 config_dock_hide = !config_dock_hide;
721 void action_add_desktop(union ActionData *data)
723 client_action_start(data);
724 screen_set_num_desktops(screen_num_desktops+1);
726 /* move all the clients over */
727 if (data->addremovedesktop.current) {
730 for (it = client_list; it; it = g_list_next(it)) {
731 ObClient *c = it->data;
732 if (c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop)
733 client_set_desktop(c, c->desktop+1, FALSE, TRUE);
737 client_action_end(data, config_focus_under_mouse);
740 void action_remove_desktop(union ActionData *data)
742 guint rmdesktop, movedesktop;
743 GList *it, *stacking_copy;
745 if (screen_num_desktops < 2) return;
747 client_action_start(data);
749 /* what desktop are we removing and moving to? */
750 if (data->addremovedesktop.current)
751 rmdesktop = screen_desktop;
753 rmdesktop = screen_num_desktops - 1;
754 if (rmdesktop < screen_num_desktops - 1)
755 movedesktop = rmdesktop + 1;
757 movedesktop = rmdesktop;
759 /* make a copy of the list cuz we're changing it */
760 stacking_copy = g_list_copy(stacking_list);
761 for (it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
762 if (WINDOW_IS_CLIENT(it->data)) {
763 ObClient *c = it->data;
764 guint d = c->desktop;
765 if (d != DESKTOP_ALL && d >= movedesktop) {
766 client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
767 ob_debug("moving window %s\n", c->title);
769 /* raise all the windows that are on the current desktop which
771 if ((screen_desktop == rmdesktop - 1 ||
772 screen_desktop == rmdesktop) &&
773 (d == DESKTOP_ALL || d == screen_desktop))
775 stacking_raise(CLIENT_AS_WINDOW(c));
776 ob_debug("raising window %s\n", c->title);
781 /* act like we're changing desktops */
782 if (screen_desktop < screen_num_desktops - 1) {
783 gint d = screen_desktop;
784 screen_desktop = screen_last_desktop;
785 screen_set_desktop(d, TRUE);
786 ob_debug("fake desktop change\n");
789 screen_set_num_desktops(screen_num_desktops-1);
791 client_action_end(data, config_focus_under_mouse);