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