]> Dogcows Code - chaz/openbox/blob - openbox/mouse.c
Merge branch 'master' into chaz
[chaz/openbox] / openbox / mouse.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 mouse.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 "openbox.h"
21 #include "config.h"
22 #include "actions.h"
23 #include "event.h"
24 #include "client.h"
25 #include "grab.h"
26 #include "frame.h"
27 #include "translate.h"
28 #include "mouse.h"
29 #include "gettext.h"
30 #include "obt/display.h"
31
32 #include <glib.h>
33
34 typedef struct {
35 guint state;
36 guint button;
37 GSList *actions[OB_NUM_MOUSE_ACTIONS]; /* lists of Action pointers */
38 } ObMouseBinding;
39
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;
45
46 ObFrameContext mouse_button_frame_context(ObFrameContext context,
47 guint button,
48 guint state)
49 {
50 GSList *it;
51 ObFrameContext x = context;
52
53 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
54 ObMouseBinding *b = it->data;
55
56 if (b->button == button && b->state == state)
57 return context;
58 }
59
60 switch (context) {
61 case OB_FRAME_CONTEXT_NONE:
62 case OB_FRAME_CONTEXT_DESKTOP:
63 case OB_FRAME_CONTEXT_CLIENT:
64 case OB_FRAME_CONTEXT_TITLEBAR:
65 case OB_FRAME_CONTEXT_FRAME:
66 case OB_FRAME_CONTEXT_MOVE_RESIZE:
67 case OB_FRAME_CONTEXT_LEFT:
68 case OB_FRAME_CONTEXT_RIGHT:
69 case OB_FRAME_CONTEXT_DOCK:
70 break;
71 case OB_FRAME_CONTEXT_ROOT:
72 x = OB_FRAME_CONTEXT_DESKTOP;
73 break;
74 case OB_FRAME_CONTEXT_BOTTOM:
75 case OB_FRAME_CONTEXT_BLCORNER:
76 case OB_FRAME_CONTEXT_BRCORNER:
77 x = OB_FRAME_CONTEXT_BOTTOM;
78 break;
79 case OB_FRAME_CONTEXT_TLCORNER:
80 case OB_FRAME_CONTEXT_TRCORNER:
81 case OB_FRAME_CONTEXT_TOP:
82 case OB_FRAME_CONTEXT_MAXIMIZE:
83 case OB_FRAME_CONTEXT_ALLDESKTOPS:
84 case OB_FRAME_CONTEXT_SHADE:
85 case OB_FRAME_CONTEXT_ICONIFY:
86 case OB_FRAME_CONTEXT_ICON:
87 case OB_FRAME_CONTEXT_CLOSE:
88 x = OB_FRAME_CONTEXT_TITLEBAR;
89 break;
90 case OB_FRAME_NUM_CONTEXTS:
91 g_assert_not_reached();
92 }
93
94 /* allow for multiple levels of fall-through */
95 if (x != context)
96 return mouse_button_frame_context(x, button, state);
97 else
98 return x;
99 }
100
101 void mouse_grab_for_client(ObClient *client, gboolean grab)
102 {
103 gint i;
104 GSList *it;
105
106 for (i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i)
107 for (it = bound_contexts[i]; it; it = g_slist_next(it)) {
108 /* grab/ungrab the button */
109 ObMouseBinding *b = it->data;
110 Window win;
111 gint mode;
112 guint mask;
113
114 if (FRAME_CONTEXT(i, client)) {
115 win = client->frame->window;
116 mode = GrabModeAsync;
117 mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
118 } else if (CLIENT_CONTEXT(i, client)) {
119 win = client->window;
120 mode = GrabModeSync; /* this is handled in event */
121 mask = ButtonPressMask; /* can't catch more than this with Sync
122 mode the release event is
123 manufactured in event() */
124 } else continue;
125
126 if (grab)
127 grab_button_full(b->button, b->state, win, mask, mode,
128 OB_CURSOR_NONE);
129 else
130 ungrab_button(b->button, b->state, win);
131 }
132 }
133
134 static void grab_all_clients(gboolean grab)
135 {
136 GList *it;
137
138 for (it = client_list; it; it = g_list_next(it))
139 mouse_grab_for_client(it->data, grab);
140 }
141
142 void mouse_unbind_all(void)
143 {
144 gint i;
145 GSList *it;
146
147 for(i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i) {
148 for (it = bound_contexts[i]; it; it = g_slist_next(it)) {
149 ObMouseBinding *b = it->data;
150 gint j;
151
152 for (j = 0; j < OB_NUM_MOUSE_ACTIONS; ++j) {
153 GSList *jt;
154
155 for (jt = b->actions[j]; jt; jt = g_slist_next(jt))
156 actions_act_unref(jt->data);
157 g_slist_free(b->actions[j]);
158 }
159 g_slice_free(ObMouseBinding, b);
160 }
161 g_slist_free(bound_contexts[i]);
162 bound_contexts[i] = NULL;
163 }
164 }
165
166 static ObUserAction mouse_action_to_user_action(ObMouseAction a)
167 {
168 switch (a) {
169 case OB_MOUSE_ACTION_PRESS: return OB_USER_ACTION_MOUSE_PRESS;
170 case OB_MOUSE_ACTION_RELEASE: return OB_USER_ACTION_MOUSE_RELEASE;
171 case OB_MOUSE_ACTION_CLICK: return OB_USER_ACTION_MOUSE_CLICK;
172 case OB_MOUSE_ACTION_DOUBLE_CLICK:
173 return OB_USER_ACTION_MOUSE_DOUBLE_CLICK;
174 case OB_MOUSE_ACTION_MOTION: return OB_USER_ACTION_MOUSE_MOTION;
175 default:
176 g_assert_not_reached();
177 }
178 }
179
180 static gboolean fire_binding(ObMouseAction a, ObFrameContext context,
181 ObClient *c, guint state,
182 guint button, gint x, gint y)
183 {
184 GSList *it;
185 ObMouseBinding *b;
186
187 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
188 b = it->data;
189 if (b->state == state && b->button == button)
190 break;
191 }
192 /* if not bound, then nothing to do! */
193 if (it == NULL) return FALSE;
194
195 actions_run_acts(b->actions[a], mouse_action_to_user_action(a),
196 state, x, y, button, context, c);
197 return TRUE;
198 }
199
200 void mouse_replay_pointer(void)
201 {
202 if (replay_pointer_needed) {
203 /* replay the pointer event before any windows move */
204 XAllowEvents(obt_display, ReplayPointer, event_time());
205 replay_pointer_needed = FALSE;
206 }
207 }
208
209 gboolean mouse_event(ObClient *client, XEvent *e)
210 {
211 static Time ltime;
212 static guint button = 0, state = 0, lbutton = 0;
213 static Window lwindow = None;
214 static gint px, py, pwx = -1, pwy = -1, lx = -10, ly = -10;
215 gboolean used = FALSE;
216
217 ObFrameContext context;
218 gboolean click = FALSE;
219 gboolean dclick = FALSE;
220
221 switch (e->type) {
222 case ButtonPress:
223 context = frame_context(client, e->xbutton.window,
224 e->xbutton.x, e->xbutton.y);
225 context = mouse_button_frame_context(context, e->xbutton.button,
226 e->xbutton.state);
227
228 px = e->xbutton.x_root;
229 py = e->xbutton.y_root;
230 if (!button) pwx = e->xbutton.x;
231 if (!button) pwy = e->xbutton.y;
232 button = e->xbutton.button;
233 state = e->xbutton.state;
234
235 /* if the binding was in a client context, then we need to call
236 XAllowEvents with ReplayPointer at some point, to send the event
237 through to the client. when this happens though depends. if
238 windows are going to be moved on screen, then the click will end
239 up going somewhere wrong, set that we need it, and if nothing
240 else causes the replay pointer to be run, then we will do it
241 after all the actions are finished.
242
243 (We do it after all the actions because FocusIn interrupts
244 dragging for kdesktop, so if we send the button event now, and
245 then they get a focus event after, it breaks. Instead, wait to send
246 the button press until after the actions when possible.)
247 */
248 if (CLIENT_CONTEXT(context, client))
249 replay_pointer_needed = TRUE;
250
251 used = fire_binding(OB_MOUSE_ACTION_PRESS, context,
252 client, e->xbutton.state,
253 e->xbutton.button,
254 e->xbutton.x_root, e->xbutton.y_root) || used;
255
256 /* if the bindings grab the pointer, there won't be a ButtonRelease
257 event for us */
258 if (grab_on_pointer())
259 button = 0;
260
261 /* replay the pointer event if it hasn't been replayed yet (i.e. no
262 windows were moved) */
263 mouse_replay_pointer();
264
265 /* in the client context, we won't get a button release because of the
266 way it is grabbed, so just fake one */
267 if (!CLIENT_CONTEXT(context, client))
268 break;
269
270 case ButtonRelease:
271 /* use where the press occured in the window */
272 context = frame_context(client, e->xbutton.window, pwx, pwy);
273 context = mouse_button_frame_context(context, e->xbutton.button,
274 e->xbutton.state);
275
276 if (e->xbutton.button == button)
277 pwx = pwy = -1;
278
279 if (e->xbutton.button == button) {
280 /* clicks are only valid if its released over the window */
281 gint junk1, junk2;
282 Window wjunk;
283 guint ujunk, b, w, h;
284 /* this can cause errors to occur when the window closes */
285 obt_display_ignore_errors(TRUE);
286 junk1 = XGetGeometry(obt_display, e->xbutton.window,
287 &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
288 obt_display_ignore_errors(FALSE);
289 if (junk1) {
290 if (e->xbutton.x >= (signed)-b &&
291 e->xbutton.y >= (signed)-b &&
292 e->xbutton.x < (signed)(w+b) &&
293 e->xbutton.y < (signed)(h+b))
294 {
295 click = TRUE;
296 /* double clicks happen if there were 2 in a row! */
297 if (lbutton == button &&
298 lwindow == e->xbutton.window &&
299 e->xbutton.time - config_mouse_dclicktime <=
300 ltime &&
301 ABS(e->xbutton.x - lx) < 8 &&
302 ABS(e->xbutton.y - ly) < 8)
303 {
304 dclick = TRUE;
305 lbutton = 0;
306 } else {
307 lbutton = button;
308 lwindow = e->xbutton.window;
309 lx = e->xbutton.x;
310 ly = e->xbutton.y;
311 }
312 } else {
313 lbutton = 0;
314 lwindow = None;
315 }
316 }
317
318 button = 0;
319 state = 0;
320 ltime = e->xbutton.time;
321 }
322 used = fire_binding(OB_MOUSE_ACTION_RELEASE, context,
323 client, e->xbutton.state,
324 e->xbutton.button,
325 e->xbutton.x_root,
326 e->xbutton.y_root) || used;
327 if (click)
328 used = fire_binding(OB_MOUSE_ACTION_CLICK, context,
329 client, e->xbutton.state,
330 e->xbutton.button,
331 e->xbutton.x_root,
332 e->xbutton.y_root) || used;
333 if (dclick)
334 used = fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK, context,
335 client, e->xbutton.state,
336 e->xbutton.button,
337 e->xbutton.x_root,
338 e->xbutton.y_root) || used;
339 break;
340
341 case MotionNotify:
342 if (button) {
343 context = frame_context(client, e->xmotion.window, pwx, pwy);
344 context = mouse_button_frame_context(context, button, state);
345
346 if (ABS(e->xmotion.x_root - px) >= config_mouse_threshold ||
347 ABS(e->xmotion.y_root - py) >= config_mouse_threshold) {
348
349 /* You can't drag on buttons */
350 if (context == OB_FRAME_CONTEXT_MAXIMIZE ||
351 context == OB_FRAME_CONTEXT_ALLDESKTOPS ||
352 context == OB_FRAME_CONTEXT_SHADE ||
353 context == OB_FRAME_CONTEXT_ICONIFY ||
354 context == OB_FRAME_CONTEXT_ICON ||
355 context == OB_FRAME_CONTEXT_CLOSE)
356 break;
357
358 used = fire_binding(OB_MOUSE_ACTION_MOTION, context,
359 client, state, button, px, py);
360 button = 0;
361 state = 0;
362 }
363 }
364 break;
365
366 default:
367 g_assert_not_reached();
368 }
369 return used;
370 }
371
372 gboolean mouse_bind(const gchar *buttonstr, ObFrameContext context,
373 ObMouseAction mact, ObActionsAct *action)
374 {
375 guint state, button;
376 ObMouseBinding *b;
377 GSList *it;
378
379 g_assert(context != OB_FRAME_CONTEXT_NONE);
380
381 if (!translate_button(buttonstr, &state, &button)) {
382 g_message(_("Invalid button \"%s\" in mouse binding"), buttonstr);
383 return FALSE;
384 }
385
386 for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
387 b = it->data;
388 if (b->state == state && b->button == button) {
389 b->actions[mact] = g_slist_append(b->actions[mact], action);
390 return TRUE;
391 }
392 }
393
394 /* add the binding */
395 b = g_slice_new0(ObMouseBinding);
396 b->state = state;
397 b->button = button;
398 b->actions[mact] = g_slist_append(NULL, action);
399 bound_contexts[context] = g_slist_append(bound_contexts[context], b);
400
401 return TRUE;
402 }
403
404 void mouse_startup(gboolean reconfig)
405 {
406 grab_all_clients(TRUE);
407 }
408
409 void mouse_shutdown(gboolean reconfig)
410 {
411 grab_all_clients(FALSE);
412 mouse_unbind_all();
413 }
This page took 0.050625 seconds and 4 git commands to generate.