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