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