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