Merge branch 'backport' into work
[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 #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
49 ObFrameContext mouse_button_frame_context(ObFrameContext context,
50                                           guint button,
51                                           guint state)
52 {
53     GSList *it;
54     ObFrameContext x = context;
55
56     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
57         ObMouseBinding *b = it->data;
58
59         if (b->button == button && b->state == state)
60             return context;
61     }
62
63     switch (context) {
64     case OB_FRAME_CONTEXT_NONE:
65     case OB_FRAME_CONTEXT_DESKTOP:
66     case OB_FRAME_CONTEXT_CLIENT:
67     case OB_FRAME_CONTEXT_TITLEBAR:
68     case OB_FRAME_CONTEXT_FRAME:
69     case OB_FRAME_CONTEXT_MOVE_RESIZE:
70     case OB_FRAME_CONTEXT_LEFT:
71     case OB_FRAME_CONTEXT_RIGHT:
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_free(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_event(ObClient *client, XEvent *e)
203 {
204     static Time ltime;
205     static guint button = 0, state = 0, lbutton = 0;
206     static Window lwindow = None;
207     static gint px, py, pwx = -1, pwy = -1;
208
209     ObFrameContext context;
210     gboolean click = FALSE;
211     gboolean dclick = FALSE;
212
213     switch (e->type) {
214     case ButtonPress:
215         context = frame_context(client, e->xbutton.window,
216                                 e->xbutton.x, e->xbutton.y);
217         context = mouse_button_frame_context(context, e->xbutton.button,
218                                              e->xbutton.state);
219
220         px = e->xbutton.x_root;
221         py = e->xbutton.y_root;
222         if (!button) pwx = e->xbutton.x;
223         if (!button) pwy = e->xbutton.y;
224         button = e->xbutton.button;
225         state = e->xbutton.state;
226
227         /* if the binding was in a client context, then we need to call
228            XAllowEvents with ReplayPointer at some point, to send the event
229            through to the client.  when this happens though depends.  if
230            windows are going to be moved on screen, then the click will end
231            up going somewhere wrong, so have the action system perform the
232            ReplayPointer for us if that is the case. */
233         if (CLIENT_CONTEXT(context, client))
234             actions_set_need_pointer_replay_before_move(TRUE);
235         else
236             actions_set_need_pointer_replay_before_move(FALSE);
237
238         fire_binding(OB_MOUSE_ACTION_PRESS, context,
239                      client, e->xbutton.state,
240                      e->xbutton.button,
241                      e->xbutton.x_root, e->xbutton.y_root);
242
243         /* if the bindings grab the pointer, there won't be a ButtonRelease
244            event for us */
245         if (grab_on_pointer())
246             button = 0;
247
248         /* replay the pointer event if it hasn't been replayed yet (i.e. no
249            windows were moved) */
250         if (actions_get_need_pointer_replay_before_move())
251             XAllowEvents(obt_display, ReplayPointer, event_curtime);
252
253         /* in the client context, we won't get a button release because of the
254            way it is grabbed, so just fake one */
255         if (!CLIENT_CONTEXT(context, client))
256             break;
257
258     case ButtonRelease:
259         /* use where the press occured in the window */
260         context = frame_context(client, e->xbutton.window, pwx, pwy);
261         context = mouse_button_frame_context(context, e->xbutton.button,
262                                              e->xbutton.state);
263
264         if (e->xbutton.button == button)
265             pwx = pwy = -1;
266
267         if (e->xbutton.button == button) {
268             /* clicks are only valid if its released over the window */
269             gint junk1, junk2;
270             Window wjunk;
271             guint ujunk, b, w, h;
272             /* this can cause errors to occur when the window closes */
273             obt_display_ignore_errors(TRUE);
274             junk1 = XGetGeometry(obt_display, e->xbutton.window,
275                                  &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
276             obt_display_ignore_errors(FALSE);
277             if (junk1) {
278                 if (e->xbutton.x >= (signed)-b &&
279                     e->xbutton.y >= (signed)-b &&
280                     e->xbutton.x < (signed)(w+b) &&
281                     e->xbutton.y < (signed)(h+b)) {
282                     click = TRUE;
283                     /* double clicks happen if there were 2 in a row! */
284                     if (lbutton == button &&
285                         lwindow == e->xbutton.window &&
286                         e->xbutton.time - config_mouse_dclicktime <=
287                         ltime) {
288                         dclick = TRUE;
289                         lbutton = 0;
290                     } else {
291                         lbutton = button;
292                         lwindow = e->xbutton.window;
293                     }
294                 } else {
295                     lbutton = 0;
296                     lwindow = None;
297                 }
298             }
299
300             button = 0;
301             state = 0;
302             ltime = e->xbutton.time;
303         }
304         fire_binding(OB_MOUSE_ACTION_RELEASE, context,
305                      client, e->xbutton.state,
306                      e->xbutton.button,
307                      e->xbutton.x_root,
308                      e->xbutton.y_root);
309         if (click)
310             fire_binding(OB_MOUSE_ACTION_CLICK, context,
311                          client, e->xbutton.state,
312                          e->xbutton.button,
313                          e->xbutton.x_root,
314                          e->xbutton.y_root);
315         if (dclick)
316             fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK, context,
317                          client, e->xbutton.state,
318                          e->xbutton.button,
319                          e->xbutton.x_root,
320                          e->xbutton.y_root);
321         break;
322
323     case MotionNotify:
324         if (button) {
325             context = frame_context(client, e->xmotion.window, pwx, pwy);
326             context = mouse_button_frame_context(context, button, state);
327
328             if (ABS(e->xmotion.x_root - px) >= config_mouse_threshold ||
329                 ABS(e->xmotion.y_root - py) >= config_mouse_threshold) {
330
331                 /* You can't drag on buttons */
332                 if (context == OB_FRAME_CONTEXT_MAXIMIZE ||
333                     context == OB_FRAME_CONTEXT_ALLDESKTOPS ||
334                     context == OB_FRAME_CONTEXT_SHADE ||
335                     context == OB_FRAME_CONTEXT_ICONIFY ||
336                     context == OB_FRAME_CONTEXT_ICON ||
337                     context == OB_FRAME_CONTEXT_CLOSE)
338                     break;
339
340                 fire_binding(OB_MOUSE_ACTION_MOTION, context,
341                              client, state, button, px, py);
342                 button = 0;
343                 state = 0;
344             }
345         }
346         break;
347
348     default:
349         g_assert_not_reached();
350     }
351 }
352
353 gboolean mouse_bind(const gchar *buttonstr, const gchar *contextstr,
354                     ObMouseAction mact, ObActionsAct *action)
355 {
356     guint state, button;
357     ObFrameContext context;
358     ObMouseBinding *b;
359     GSList *it;
360
361     if (!translate_button(buttonstr, &state, &button)) {
362         g_message(_("Invalid button '%s' in mouse binding"), buttonstr);
363         return FALSE;
364     }
365
366     context = frame_context_from_string(contextstr);
367     if (!context) {
368         g_message(_("Invalid context '%s' in mouse binding"), contextstr);
369         return FALSE;
370     }
371
372     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
373         b = it->data;
374         if (b->state == state && b->button == button) {
375             b->actions[mact] = g_slist_append(b->actions[mact], action);
376             return TRUE;
377         }
378     }
379
380     /* add the binding */
381     b = g_new0(ObMouseBinding, 1);
382     b->state = state;
383     b->button = button;
384     b->actions[mact] = g_slist_append(NULL, action);
385     bound_contexts[context] = g_slist_append(bound_contexts[context], b);
386
387     return TRUE;
388 }
389
390 void mouse_startup(gboolean reconfig)
391 {
392     grab_all_clients(TRUE);
393 }
394
395 void mouse_shutdown(gboolean reconfig)
396 {
397     grab_all_clients(FALSE);
398     mouse_unbind_all();
399 }