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