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