g_message with variable
[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 /* Array of GSList*s of ObMouseBinding*s. */
42 static GSList *bound_contexts[OB_FRAME_NUM_CONTEXTS];
43 /* TRUE when we have a grab on the pointer and need to replay the pointer event
44    to send it to other applications */
45 static gboolean replay_pointer_needed;
46
47 ObFrameContext mouse_button_frame_context(ObFrameContext context,
48                                           guint button,
49                                           guint state)
50 {
51     GSList *it;
52     ObFrameContext x = context;
53
54     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
55         ObMouseBinding *b = it->data;
56
57         if (b->button == button && b->state == state)
58             return context;
59     }
60
61     switch (context) {
62     case OB_FRAME_CONTEXT_NONE:
63     case OB_FRAME_CONTEXT_DESKTOP:
64     case OB_FRAME_CONTEXT_CLIENT:
65     case OB_FRAME_CONTEXT_TITLEBAR:
66     case OB_FRAME_CONTEXT_FRAME:
67     case OB_FRAME_CONTEXT_MOVE_RESIZE:
68     case OB_FRAME_CONTEXT_LEFT:
69     case OB_FRAME_CONTEXT_RIGHT:
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_free(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(ob_display, ReplayPointer, event_curtime);
205         replay_pointer_needed = FALSE;
206     }
207 }
208
209 void 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;
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         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);
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             xerror_set_ignore(TRUE);
285             junk1 = XGetGeometry(ob_display, e->xbutton.window,
286                                  &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
287             xerror_set_ignore(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         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);
320         if (click)
321             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);
326         if (dclick)
327             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);
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                 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 }
363
364 gboolean mouse_bind(const gchar *buttonstr, const gchar *contextstr,
365                     ObMouseAction mact, ObActionsAct *action)
366 {
367     guint state, button;
368     ObFrameContext context;
369     ObMouseBinding *b;
370     GSList *it;
371
372     if (!translate_button(buttonstr, &state, &button)) {
373         g_message(_("Invalid button \"%s\" in mouse binding"), buttonstr);
374         return FALSE;
375     }
376
377     context = frame_context_from_string(contextstr);
378     if (!context) {
379         g_message(_("Invalid context \"%s\" in mouse binding"), contextstr);
380         return FALSE;
381     }
382
383     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
384         b = it->data;
385         if (b->state == state && b->button == button) {
386             b->actions[mact] = g_slist_append(b->actions[mact], action);
387             return TRUE;
388         }
389     }
390
391     /* add the binding */
392     b = g_new0(ObMouseBinding, 1);
393     b->state = state;
394     b->button = button;
395     b->actions[mact] = g_slist_append(NULL, action);
396     bound_contexts[context] = g_slist_append(bound_contexts[context], b);
397
398     return TRUE;
399 }
400
401 void mouse_startup(gboolean reconfig)
402 {
403     grab_all_clients(TRUE);
404 }
405
406 void mouse_shutdown(gboolean reconfig)
407 {
408     grab_all_clients(FALSE);
409     mouse_unbind_all();
410 }