1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 mouse.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.
27 #include "translate.h"
30 #include "obt/display.h"
37 GSList *actions[OB_NUM_MOUSE_ACTIONS]; /* lists of Action pointers */
40 /* Array of GSList*s of ObMouseBinding*s. */
41 static GSList *bound_contexts[OB_FRAME_NUM_CONTEXTS];
42 /* TRUE when we have a grab on the pointer and need to replay the pointer event
43 to send it to other applications */
44 static gboolean replay_pointer_needed;
48 ObFrameContext mouse_button_frame_context(ObFrameContext context,
53 ObFrameContext x = context;
55 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
56 ObMouseBinding *b = it->data;
58 if (b->button == button && b->state == state)
63 case OB_FRAME_CONTEXT_NONE:
64 case OB_FRAME_CONTEXT_DESKTOP:
65 case OB_FRAME_CONTEXT_CLIENT:
66 case OB_FRAME_CONTEXT_TITLEBAR:
67 case OB_FRAME_CONTEXT_FRAME:
68 case OB_FRAME_CONTEXT_MOVE_RESIZE:
69 case OB_FRAME_CONTEXT_LEFT:
70 case OB_FRAME_CONTEXT_RIGHT:
71 case OB_FRAME_CONTEXT_DOCK:
73 case OB_FRAME_CONTEXT_ROOT:
74 x = OB_FRAME_CONTEXT_DESKTOP;
76 case OB_FRAME_CONTEXT_BOTTOM:
77 case OB_FRAME_CONTEXT_BLCORNER:
78 case OB_FRAME_CONTEXT_BRCORNER:
79 x = OB_FRAME_CONTEXT_BOTTOM;
81 case OB_FRAME_CONTEXT_TLCORNER:
82 case OB_FRAME_CONTEXT_TRCORNER:
83 case OB_FRAME_CONTEXT_TOP:
84 case OB_FRAME_CONTEXT_MAXIMIZE:
85 case OB_FRAME_CONTEXT_ALLDESKTOPS:
86 case OB_FRAME_CONTEXT_SHADE:
87 case OB_FRAME_CONTEXT_ICONIFY:
88 case OB_FRAME_CONTEXT_ICON:
89 case OB_FRAME_CONTEXT_CLOSE:
90 x = OB_FRAME_CONTEXT_TITLEBAR;
92 case OB_FRAME_NUM_CONTEXTS:
93 g_assert_not_reached();
96 /* allow for multiple levels of fall-through */
98 return mouse_button_frame_context(x, button, state);
103 void mouse_grab_for_client(ObClient *client, gboolean grab)
108 for (i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i)
109 for (it = bound_contexts[i]; it; it = g_slist_next(it)) {
110 /* grab/ungrab the button */
111 ObMouseBinding *b = it->data;
116 if (FRAME_CONTEXT(i, client)) {
117 win = client->frame->window;
118 mode = GrabModeAsync;
119 mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
120 } else if (CLIENT_CONTEXT(i, client)) {
121 win = client->window;
122 mode = GrabModeSync; /* this is handled in event */
123 mask = ButtonPressMask; /* can't catch more than this with Sync
124 mode the release event is
125 manufactured in event() */
129 grab_button_full(b->button, b->state, win, mask, mode,
132 ungrab_button(b->button, b->state, win);
136 static void grab_all_clients(gboolean grab)
140 for (it = client_list; it; it = g_list_next(it))
141 mouse_grab_for_client(it->data, grab);
144 void mouse_unbind_all(void)
149 for(i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i) {
150 for (it = bound_contexts[i]; it; it = g_slist_next(it)) {
151 ObMouseBinding *b = it->data;
154 for (j = 0; j < OB_NUM_MOUSE_ACTIONS; ++j) {
157 for (jt = b->actions[j]; jt; jt = g_slist_next(jt))
158 actions_act_unref(jt->data);
159 g_slist_free(b->actions[j]);
161 g_slice_free(ObMouseBinding, b);
163 g_slist_free(bound_contexts[i]);
164 bound_contexts[i] = NULL;
168 static ObUserAction mouse_action_to_user_action(ObMouseAction a)
171 case OB_MOUSE_ACTION_PRESS: return OB_USER_ACTION_MOUSE_PRESS;
172 case OB_MOUSE_ACTION_RELEASE: return OB_USER_ACTION_MOUSE_RELEASE;
173 case OB_MOUSE_ACTION_CLICK: return OB_USER_ACTION_MOUSE_CLICK;
174 case OB_MOUSE_ACTION_DOUBLE_CLICK:
175 return OB_USER_ACTION_MOUSE_DOUBLE_CLICK;
176 case OB_MOUSE_ACTION_MOTION: return OB_USER_ACTION_MOUSE_MOTION;
178 g_assert_not_reached();
182 static gboolean fire_binding(ObMouseAction a, ObFrameContext context,
183 ObClient *c, guint state,
184 guint button, gint x, gint y)
189 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
191 if (b->state == state && b->button == button)
194 /* if not bound, then nothing to do! */
195 if (it == NULL) return FALSE;
197 actions_run_acts(b->actions[a], mouse_action_to_user_action(a),
198 state, x, y, button, context, c);
202 void mouse_replay_pointer(void)
204 if (replay_pointer_needed) {
205 /* replay the pointer event before any windows move */
206 XAllowEvents(obt_display, ReplayPointer, event_time());
207 replay_pointer_needed = FALSE;
211 gboolean mouse_event(ObClient *client, XEvent *e)
214 static guint state = 0, lbutton = 0;
215 static Window lwindow = None;
216 static gint px, py, pwx = -1, pwy = -1, lx = -10, ly = -10;
217 gboolean used = FALSE;
219 ObFrameContext context;
220 gboolean click = FALSE;
221 gboolean dclick = FALSE;
225 context = frame_context(client, e->xbutton.window,
226 e->xbutton.x, e->xbutton.y);
227 context = mouse_button_frame_context(context, e->xbutton.button,
230 px = e->xbutton.x_root;
231 py = e->xbutton.y_root;
232 if (!button) pwx = e->xbutton.x;
233 if (!button) pwy = e->xbutton.y;
234 button = e->xbutton.button;
235 state = e->xbutton.state;
237 /* if the binding was in a client context, then we need to call
238 XAllowEvents with ReplayPointer at some point, to send the event
239 through to the client. when this happens though depends. if
240 windows are going to be moved on screen, then the click will end
241 up going somewhere wrong, set that we need it, and if nothing
242 else causes the replay pointer to be run, then we will do it
243 after all the actions are finished.
245 (We do it after all the actions because FocusIn interrupts
246 dragging for kdesktop, so if we send the button event now, and
247 then they get a focus event after, it breaks. Instead, wait to send
248 the button press until after the actions when possible.)
250 if (CLIENT_CONTEXT(context, client))
251 replay_pointer_needed = TRUE;
253 used = fire_binding(OB_MOUSE_ACTION_PRESS, context,
254 client, e->xbutton.state,
256 e->xbutton.x_root, e->xbutton.y_root) || used;
258 /* if the bindings grab the pointer, there won't be a ButtonRelease
260 if (grab_on_pointer())
263 /* replay the pointer event if it hasn't been replayed yet (i.e. no
264 windows were moved) */
265 mouse_replay_pointer();
267 /* in the client context, we won't get a button release because of the
268 way it is grabbed, so just fake one */
269 if (!CLIENT_CONTEXT(context, client))
273 /* use where the press occured in the window */
274 context = frame_context(client, e->xbutton.window, pwx, pwy);
275 context = mouse_button_frame_context(context, e->xbutton.button,
278 if (e->xbutton.button == button)
281 if (e->xbutton.button == button) {
282 /* clicks are only valid if its released over the window */
285 guint ujunk, b, w, h;
286 /* this can cause errors to occur when the window closes */
287 obt_display_ignore_errors(TRUE);
288 junk1 = XGetGeometry(obt_display, e->xbutton.window,
289 &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
290 obt_display_ignore_errors(FALSE);
292 if (e->xbutton.x >= (signed)-b &&
293 e->xbutton.y >= (signed)-b &&
294 e->xbutton.x < (signed)(w+b) &&
295 e->xbutton.y < (signed)(h+b))
298 /* double clicks happen if there were 2 in a row! */
299 if (lbutton == button &&
300 lwindow == e->xbutton.window &&
301 e->xbutton.time - config_mouse_dclicktime <=
303 ABS(e->xbutton.x - lx) < 8 &&
304 ABS(e->xbutton.y - ly) < 8)
310 lwindow = e->xbutton.window;
322 ltime = e->xbutton.time;
324 used = fire_binding(OB_MOUSE_ACTION_RELEASE, context,
325 client, e->xbutton.state,
328 e->xbutton.y_root) || used;
330 used = fire_binding(OB_MOUSE_ACTION_CLICK, context,
331 client, e->xbutton.state,
334 e->xbutton.y_root) || used;
336 used = fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK, context,
337 client, e->xbutton.state,
340 e->xbutton.y_root) || used;
345 context = frame_context(client, e->xmotion.window, pwx, pwy);
346 context = mouse_button_frame_context(context, button, state);
348 if (ABS(e->xmotion.x_root - px) >= config_mouse_threshold ||
349 ABS(e->xmotion.y_root - py) >= config_mouse_threshold) {
351 used = fire_binding(OB_MOUSE_ACTION_MOTION, context,
352 client, state, button, px, py);
360 g_assert_not_reached();
365 gboolean mouse_bind(const gchar *buttonstr, ObFrameContext context,
366 ObMouseAction mact, ObActionsAct *action)
368 guint state = 0, button = 0;
372 g_assert(context != OB_FRAME_CONTEXT_NONE);
374 if (!translate_button(buttonstr, &state, &button)) {
375 g_message(_("Invalid button \"%s\" in mouse binding"), buttonstr);
379 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
381 if (b->state == state && b->button == button) {
382 b->actions[mact] = g_slist_append(b->actions[mact], action);
387 /* add the binding */
388 b = g_slice_new0(ObMouseBinding);
391 b->actions[mact] = g_slist_append(NULL, action);
392 bound_contexts[context] = g_slist_append(bound_contexts[context], b);
397 void mouse_startup(gboolean reconfig)
399 grab_all_clients(TRUE);
402 void mouse_shutdown(gboolean reconfig)
404 grab_all_clients(FALSE);