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;
46 /* this is the static button from mouse_event, moved here so that event.c can clear it */
49 ObFrameContext mouse_button_frame_context(ObFrameContext context,
54 ObFrameContext x = context;
56 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
57 ObMouseBinding *b = it->data;
59 if (b->button == button && b->state == state)
64 case OB_FRAME_CONTEXT_NONE:
65 case OB_FRAME_CONTEXT_DESKTOP:
66 case OB_FRAME_CONTEXT_CLIENT:
67 case OB_FRAME_CONTEXT_TITLEBAR:
68 case OB_FRAME_CONTEXT_FRAME:
69 case OB_FRAME_CONTEXT_MOVE_RESIZE:
70 case OB_FRAME_CONTEXT_LEFT:
71 case OB_FRAME_CONTEXT_RIGHT:
72 case OB_FRAME_CONTEXT_DOCK:
74 case OB_FRAME_CONTEXT_ROOT:
75 x = OB_FRAME_CONTEXT_DESKTOP;
77 case OB_FRAME_CONTEXT_BOTTOM:
78 case OB_FRAME_CONTEXT_BLCORNER:
79 case OB_FRAME_CONTEXT_BRCORNER:
80 x = OB_FRAME_CONTEXT_BOTTOM;
82 case OB_FRAME_CONTEXT_TLCORNER:
83 case OB_FRAME_CONTEXT_TRCORNER:
84 case OB_FRAME_CONTEXT_TOP:
85 case OB_FRAME_CONTEXT_MAXIMIZE:
86 case OB_FRAME_CONTEXT_ALLDESKTOPS:
87 case OB_FRAME_CONTEXT_SHADE:
88 case OB_FRAME_CONTEXT_ICONIFY:
89 case OB_FRAME_CONTEXT_ICON:
90 case OB_FRAME_CONTEXT_CLOSE:
91 x = OB_FRAME_CONTEXT_TITLEBAR;
93 case OB_FRAME_NUM_CONTEXTS:
94 g_assert_not_reached();
97 /* allow for multiple levels of fall-through */
99 return mouse_button_frame_context(x, button, state);
104 void mouse_grab_for_client(ObClient *client, gboolean grab)
109 for (i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i)
110 for (it = bound_contexts[i]; it; it = g_slist_next(it)) {
111 /* grab/ungrab the button */
112 ObMouseBinding *b = it->data;
117 if (FRAME_CONTEXT(i, client)) {
118 win = client->frame->window;
119 mode = GrabModeAsync;
120 mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
121 } else if (CLIENT_CONTEXT(i, client)) {
122 win = client->window;
123 mode = GrabModeSync; /* this is handled in event */
124 mask = ButtonPressMask; /* can't catch more than this with Sync
125 mode the release event is
126 manufactured in event() */
130 grab_button_full(b->button, b->state, win, mask, mode,
133 ungrab_button(b->button, b->state, win);
137 static void grab_all_clients(gboolean grab)
141 for (it = client_list; it; it = g_list_next(it))
142 mouse_grab_for_client(it->data, grab);
145 void mouse_unbind_all(void)
150 for(i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i) {
151 for (it = bound_contexts[i]; it; it = g_slist_next(it)) {
152 ObMouseBinding *b = it->data;
155 for (j = 0; j < OB_NUM_MOUSE_ACTIONS; ++j) {
158 for (jt = b->actions[j]; jt; jt = g_slist_next(jt))
159 actions_act_unref(jt->data);
160 g_slist_free(b->actions[j]);
162 g_slice_free(ObMouseBinding, b);
164 g_slist_free(bound_contexts[i]);
165 bound_contexts[i] = NULL;
169 static ObUserAction mouse_action_to_user_action(ObMouseAction a)
172 case OB_MOUSE_ACTION_PRESS: return OB_USER_ACTION_MOUSE_PRESS;
173 case OB_MOUSE_ACTION_RELEASE: return OB_USER_ACTION_MOUSE_RELEASE;
174 case OB_MOUSE_ACTION_CLICK: return OB_USER_ACTION_MOUSE_CLICK;
175 case OB_MOUSE_ACTION_DOUBLE_CLICK:
176 return OB_USER_ACTION_MOUSE_DOUBLE_CLICK;
177 case OB_MOUSE_ACTION_MOTION: return OB_USER_ACTION_MOUSE_MOTION;
179 g_assert_not_reached();
183 static gboolean fire_binding(ObMouseAction a, ObFrameContext context,
184 ObClient *c, guint state,
185 guint button, gint x, gint y)
190 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
192 if (b->state == state && b->button == button)
195 /* if not bound, then nothing to do! */
196 if (it == NULL) return FALSE;
198 actions_run_acts(b->actions[a], mouse_action_to_user_action(a),
199 state, x, y, button, context, c);
203 void mouse_replay_pointer(void)
205 if (replay_pointer_needed) {
206 /* replay the pointer event before any windows move */
207 XAllowEvents(obt_display, ReplayPointer, event_time());
208 replay_pointer_needed = FALSE;
212 gboolean mouse_event(ObClient *client, XEvent *e)
215 static guint state = 0, lbutton = 0;
216 static Window lwindow = None;
217 static gint px, py, pwx = -1, pwy = -1, lx = -10, ly = -10;
218 gboolean used = FALSE;
220 ObFrameContext context;
221 gboolean click = FALSE;
222 gboolean dclick = FALSE;
226 context = frame_context(client, e->xbutton.window,
227 e->xbutton.x, e->xbutton.y);
228 context = mouse_button_frame_context(context, e->xbutton.button,
231 px = e->xbutton.x_root;
232 py = e->xbutton.y_root;
233 if (!button) pwx = e->xbutton.x;
234 if (!button) pwy = e->xbutton.y;
235 button = e->xbutton.button;
236 state = e->xbutton.state;
238 /* if the binding was in a client context, then we need to call
239 XAllowEvents with ReplayPointer at some point, to send the event
240 through to the client. when this happens though depends. if
241 windows are going to be moved on screen, then the click will end
242 up going somewhere wrong, set that we need it, and if nothing
243 else causes the replay pointer to be run, then we will do it
244 after all the actions are finished.
246 (We do it after all the actions because FocusIn interrupts
247 dragging for kdesktop, so if we send the button event now, and
248 then they get a focus event after, it breaks. Instead, wait to send
249 the button press until after the actions when possible.)
251 if (CLIENT_CONTEXT(context, client))
252 replay_pointer_needed = TRUE;
254 used = fire_binding(OB_MOUSE_ACTION_PRESS, context,
255 client, e->xbutton.state,
257 e->xbutton.x_root, e->xbutton.y_root) || used;
259 /* if the bindings grab the pointer, there won't be a ButtonRelease
261 if (grab_on_pointer())
264 /* replay the pointer event if it hasn't been replayed yet (i.e. no
265 windows were moved) */
266 mouse_replay_pointer();
268 /* in the client context, we won't get a button release because of the
269 way it is grabbed, so just fake one */
270 if (!CLIENT_CONTEXT(context, client))
274 /* use where the press occured in the window */
275 context = frame_context(client, e->xbutton.window, pwx, pwy);
276 context = mouse_button_frame_context(context, e->xbutton.button,
279 if (e->xbutton.button == button)
282 if (e->xbutton.button == button) {
283 /* clicks are only valid if its released over the window */
286 guint ujunk, b, w, h;
287 /* this can cause errors to occur when the window closes */
288 obt_display_ignore_errors(TRUE);
289 junk1 = XGetGeometry(obt_display, e->xbutton.window,
290 &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
291 obt_display_ignore_errors(FALSE);
293 if (e->xbutton.x >= (signed)-b &&
294 e->xbutton.y >= (signed)-b &&
295 e->xbutton.x < (signed)(w+b) &&
296 e->xbutton.y < (signed)(h+b))
299 /* double clicks happen if there were 2 in a row! */
300 if (lbutton == button &&
301 lwindow == e->xbutton.window &&
302 e->xbutton.time - config_mouse_dclicktime <=
304 ABS(e->xbutton.x - lx) < 8 &&
305 ABS(e->xbutton.y - ly) < 8)
311 lwindow = e->xbutton.window;
323 ltime = e->xbutton.time;
325 used = fire_binding(OB_MOUSE_ACTION_RELEASE, context,
326 client, e->xbutton.state,
329 e->xbutton.y_root) || used;
331 used = fire_binding(OB_MOUSE_ACTION_CLICK, context,
332 client, e->xbutton.state,
335 e->xbutton.y_root) || used;
337 used = fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK, context,
338 client, e->xbutton.state,
341 e->xbutton.y_root) || used;
346 context = frame_context(client, e->xmotion.window, pwx, pwy);
347 context = mouse_button_frame_context(context, button, state);
349 if (ABS(e->xmotion.x_root - px) >= config_mouse_threshold ||
350 ABS(e->xmotion.y_root - py) >= config_mouse_threshold) {
352 /* You can't drag on buttons */
353 if (context == OB_FRAME_CONTEXT_MAXIMIZE ||
354 context == OB_FRAME_CONTEXT_ALLDESKTOPS ||
355 context == OB_FRAME_CONTEXT_SHADE ||
356 context == OB_FRAME_CONTEXT_ICONIFY ||
357 context == OB_FRAME_CONTEXT_ICON ||
358 context == OB_FRAME_CONTEXT_CLOSE)
361 used = fire_binding(OB_MOUSE_ACTION_MOTION, context,
362 client, state, button, px, py);
370 g_assert_not_reached();
375 gboolean mouse_bind(const gchar *buttonstr, ObFrameContext context,
376 ObMouseAction mact, ObActionsAct *action)
378 guint state = 0, button = 0;
382 g_assert(context != OB_FRAME_CONTEXT_NONE);
384 if (!translate_button(buttonstr, &state, &button)) {
385 g_message(_("Invalid button \"%s\" in mouse binding"), buttonstr);
389 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
391 if (b->state == state && b->button == button) {
392 b->actions[mact] = g_slist_append(b->actions[mact], action);
397 /* add the binding */
398 b = g_slice_new0(ObMouseBinding);
401 b->actions[mact] = g_slist_append(NULL, action);
402 bound_contexts[context] = g_slist_append(bound_contexts[context], b);
407 void mouse_startup(gboolean reconfig)
409 grab_all_clients(TRUE);
412 void mouse_shutdown(gboolean reconfig)
414 grab_all_clients(FALSE);