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