Make the dock a context and add actions LowerDock and RaiseDock
[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;
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                     click = TRUE;
295                     /* double clicks happen if there were 2 in a row! */
296                     if (lbutton == button &&
297                         lwindow == e->xbutton.window &&
298                         e->xbutton.time - config_mouse_dclicktime <=
299                         ltime) {
300                         dclick = TRUE;
301                         lbutton = 0;
302                     } else {
303                         lbutton = button;
304                         lwindow = e->xbutton.window;
305                     }
306                 } else {
307                     lbutton = 0;
308                     lwindow = None;
309                 }
310             }
311
312             button = 0;
313             state = 0;
314             ltime = e->xbutton.time;
315         }
316         used = fire_binding(OB_MOUSE_ACTION_RELEASE, context,
317                             client, e->xbutton.state,
318                             e->xbutton.button,
319                             e->xbutton.x_root,
320                             e->xbutton.y_root) || used;
321         if (click)
322             used = fire_binding(OB_MOUSE_ACTION_CLICK, context,
323                                 client, e->xbutton.state,
324                                 e->xbutton.button,
325                                 e->xbutton.x_root,
326                                 e->xbutton.y_root) || used;
327         if (dclick)
328             used = fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK, context,
329                                 client, e->xbutton.state,
330                                 e->xbutton.button,
331                                 e->xbutton.x_root,
332                                 e->xbutton.y_root) || used;
333         break;
334
335     case MotionNotify:
336         if (button) {
337             context = frame_context(client, e->xmotion.window, pwx, pwy);
338             context = mouse_button_frame_context(context, button, state);
339
340             if (ABS(e->xmotion.x_root - px) >= config_mouse_threshold ||
341                 ABS(e->xmotion.y_root - py) >= config_mouse_threshold) {
342
343                 /* You can't drag on buttons */
344                 if (context == OB_FRAME_CONTEXT_MAXIMIZE ||
345                     context == OB_FRAME_CONTEXT_ALLDESKTOPS ||
346                     context == OB_FRAME_CONTEXT_SHADE ||
347                     context == OB_FRAME_CONTEXT_ICONIFY ||
348                     context == OB_FRAME_CONTEXT_ICON ||
349                     context == OB_FRAME_CONTEXT_CLOSE)
350                     break;
351
352                 used = fire_binding(OB_MOUSE_ACTION_MOTION, context,
353                                     client, state, button, px, py);
354                 button = 0;
355                 state = 0;
356             }
357         }
358         break;
359
360     default:
361         g_assert_not_reached();
362     }
363     return used;
364 }
365
366 gboolean mouse_bind(const gchar *buttonstr, ObFrameContext context,
367                     ObMouseAction mact, ObActionsAct *action)
368 {
369     guint state, button;
370     ObMouseBinding *b;
371     GSList *it;
372
373     g_assert(context != OB_FRAME_CONTEXT_NONE);
374
375     if (!translate_button(buttonstr, &state, &button)) {
376         g_message(_("Invalid button \"%s\" in mouse binding"), buttonstr);
377         return FALSE;
378     }
379
380     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
381         b = it->data;
382         if (b->state == state && b->button == button) {
383             b->actions[mact] = g_slist_append(b->actions[mact], action);
384             return TRUE;
385         }
386     }
387
388     /* add the binding */
389     b = g_slice_new0(ObMouseBinding);
390     b->state = state;
391     b->button = button;
392     b->actions[mact] = g_slist_append(NULL, action);
393     bound_contexts[context] = g_slist_append(bound_contexts[context], b);
394
395     return TRUE;
396 }
397
398 void mouse_startup(gboolean reconfig)
399 {
400     grab_all_clients(TRUE);
401 }
402
403 void mouse_shutdown(gboolean reconfig)
404 {
405     grab_all_clients(FALSE);
406     mouse_unbind_all();
407 }