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