if windows on screen are going to be moved, then do the ReplayPointer before that...
[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 "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
50 ObFrameContext mouse_button_frame_context(ObFrameContext context,
51                                           guint button,
52                                           guint state)
53 {
54     GSList *it;
55     ObFrameContext x = context;
56
57     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
58         ObMouseBinding *b = it->data;
59
60         if (b->button == button && b->state == state)
61             return context;
62     }
63
64     switch (context) {
65     case OB_FRAME_CONTEXT_NONE:
66     case OB_FRAME_CONTEXT_DESKTOP:
67     case OB_FRAME_CONTEXT_CLIENT:
68     case OB_FRAME_CONTEXT_TITLEBAR:
69     case OB_FRAME_CONTEXT_FRAME:
70     case OB_FRAME_CONTEXT_MOVE_RESIZE:
71     case OB_FRAME_CONTEXT_LEFT:
72     case OB_FRAME_CONTEXT_RIGHT:
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_free(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_event(ObClient *client, XEvent *e)
204 {
205     static Time ltime;
206     static guint button = 0, state = 0, lbutton = 0;
207     static Window lwindow = None;
208     static gint px, py, pwx = -1, pwy = -1;
209
210     ObFrameContext context;
211     gboolean click = FALSE;
212     gboolean dclick = FALSE;
213
214     switch (e->type) {
215     case ButtonPress:
216         context = frame_context(client, e->xbutton.window,
217                                 e->xbutton.x, e->xbutton.y);
218         context = mouse_button_frame_context(context, e->xbutton.button,
219                                              e->xbutton.state);
220
221         px = e->xbutton.x_root;
222         py = e->xbutton.y_root;
223         if (!button) pwx = e->xbutton.x;
224         if (!button) pwy = e->xbutton.y;
225         button = e->xbutton.button;
226         state = e->xbutton.state;
227
228         /* if the binding was in a client context, then we need to call
229            XAllowEvents with ReplayPointer at some point, to send the event
230            through to the client.  when this happens though depends.  if
231            windows are going to be moved on screen, then the click will end
232            up going somewhere wrong, so have the action system perform the
233            ReplayPointer for us if that is the case. */
234         if (CLIENT_CONTEXT(context, client))
235             actions_set_need_pointer_replay_before_move(TRUE);
236         else
237             actions_set_need_pointer_replay_before_move(FALSE);
238
239         fire_binding(OB_MOUSE_ACTION_PRESS, context,
240                      client, e->xbutton.state,
241                      e->xbutton.button,
242                      e->xbutton.x_root, e->xbutton.y_root);
243
244         /* if the bindings grab the pointer, there won't be a ButtonRelease
245            event for us */
246         if (grab_on_pointer())
247             button = 0;
248
249         /* replay the pointer event if it hasn't been replayed yet (i.e. no
250            windows were moved) */
251         if (actions_get_need_pointer_replay_before_move())
252             XAllowEvents(ob_display, ReplayPointer, event_curtime);
253
254         /* in the client context, we won't get a button release because of the
255            way it is grabbed, so just fake one */
256         if (!CLIENT_CONTEXT(context, client))
257             break;
258
259     case ButtonRelease:
260         /* use where the press occured in the window */
261         context = frame_context(client, e->xbutton.window, pwx, pwy);
262         context = mouse_button_frame_context(context, e->xbutton.button,
263                                              e->xbutton.state);
264
265         if (e->xbutton.button == button)
266             pwx = pwy = -1;
267
268         if (e->xbutton.button == button) {
269             /* clicks are only valid if its released over the window */
270             gint junk1, junk2;
271             Window wjunk;
272             guint ujunk, b, w, h;
273             /* this can cause errors to occur when the window closes */
274             xerror_set_ignore(TRUE);
275             junk1 = XGetGeometry(ob_display, e->xbutton.window,
276                                  &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
277             xerror_set_ignore(FALSE);
278             if (junk1) {
279                 if (e->xbutton.x >= (signed)-b &&
280                     e->xbutton.y >= (signed)-b &&
281                     e->xbutton.x < (signed)(w+b) &&
282                     e->xbutton.y < (signed)(h+b)) {
283                     click = TRUE;
284                     /* double clicks happen if there were 2 in a row! */
285                     if (lbutton == button &&
286                         lwindow == e->xbutton.window &&
287                         e->xbutton.time - config_mouse_dclicktime <=
288                         ltime) {
289                         dclick = TRUE;
290                         lbutton = 0;
291                     } else {
292                         lbutton = button;
293                         lwindow = e->xbutton.window;
294                     }
295                 } else {
296                     lbutton = 0;
297                     lwindow = None;
298                 }
299             }
300
301             button = 0;
302             state = 0;
303             ltime = e->xbutton.time;
304         }
305         fire_binding(OB_MOUSE_ACTION_RELEASE, context,
306                      client, e->xbutton.state,
307                      e->xbutton.button,
308                      e->xbutton.x_root,
309                      e->xbutton.y_root);
310         if (click)
311             fire_binding(OB_MOUSE_ACTION_CLICK, context,
312                          client, e->xbutton.state,
313                          e->xbutton.button,
314                          e->xbutton.x_root,
315                          e->xbutton.y_root);
316         if (dclick)
317             fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK, context,
318                          client, e->xbutton.state,
319                          e->xbutton.button,
320                          e->xbutton.x_root,
321                          e->xbutton.y_root);
322         break;
323
324     case MotionNotify:
325         if (button) {
326             context = frame_context(client, e->xmotion.window, pwx, pwy);
327             context = mouse_button_frame_context(context, button, state);
328
329             if (ABS(e->xmotion.x_root - px) >= config_mouse_threshold ||
330                 ABS(e->xmotion.y_root - py) >= config_mouse_threshold) {
331
332                 /* You can't drag on buttons */
333                 if (context == OB_FRAME_CONTEXT_MAXIMIZE ||
334                     context == OB_FRAME_CONTEXT_ALLDESKTOPS ||
335                     context == OB_FRAME_CONTEXT_SHADE ||
336                     context == OB_FRAME_CONTEXT_ICONIFY ||
337                     context == OB_FRAME_CONTEXT_ICON ||
338                     context == OB_FRAME_CONTEXT_CLOSE)
339                     break;
340
341                 fire_binding(OB_MOUSE_ACTION_MOTION, context,
342                              client, state, button, px, py);
343                 button = 0;
344                 state = 0;
345             }
346         }
347         break;
348
349     default:
350         g_assert_not_reached();
351     }
352 }
353
354 gboolean mouse_bind(const gchar *buttonstr, const gchar *contextstr,
355                     ObMouseAction mact, ObActionsAct *action)
356 {
357     guint state, button;
358     ObFrameContext context;
359     ObMouseBinding *b;
360     GSList *it;
361
362     if (!translate_button(buttonstr, &state, &button)) {
363         g_message(_("Invalid button '%s' in mouse binding"), buttonstr);
364         return FALSE;
365     }
366
367     context = frame_context_from_string(contextstr);
368     if (!context) {
369         g_message(_("Invalid context '%s' in mouse binding"), contextstr);
370         return FALSE;
371     }
372
373     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
374         b = it->data;
375         if (b->state == state && b->button == button) {
376             b->actions[mact] = g_slist_append(b->actions[mact], action);
377             return TRUE;
378         }
379     }
380
381     /* add the binding */
382     b = g_new0(ObMouseBinding, 1);
383     b->state = state;
384     b->button = button;
385     b->actions[mact] = g_slist_append(NULL, action);
386     bound_contexts[context] = g_slist_append(bound_contexts[context], b);
387
388     return TRUE;
389 }
390
391 void mouse_startup(gboolean reconfig)
392 {
393     grab_all_clients(TRUE);
394 }
395
396 void mouse_shutdown(gboolean reconfig)
397 {
398     grab_all_clients(FALSE);
399     mouse_unbind_all();
400 }