Merge branch 'backport' into work
[dana/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 /* 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 }