frame context fallbacks when there is no binding on the context
[mikachu/openbox.git] / openbox / mouse.c
1 #include "openbox.h"
2 #include "config.h"
3 #include "xerror.h"
4 #include "action.h"
5 #include "event.h"
6 #include "client.h"
7 #include "prop.h"
8 #include "grab.h"
9 #include "frame.h"
10 #include "translate.h"
11 #include "mouse.h"
12 #include <glib.h>
13
14 typedef struct {
15     guint state;
16     guint button;
17     GSList *actions[OB_NUM_MOUSE_ACTIONS]; /* lists of Action pointers */
18 } ObMouseBinding;
19
20 #define FRAME_CONTEXT(co, cl) ((cl && cl->type != OB_CLIENT_TYPE_DESKTOP) ? \
21                                co == OB_FRAME_CONTEXT_FRAME : FALSE)
22 #define CLIENT_CONTEXT(co, cl) ((cl && cl->type == OB_CLIENT_TYPE_DESKTOP) ? \
23                                 co == OB_FRAME_CONTEXT_DESKTOP : \
24                                 co == OB_FRAME_CONTEXT_CLIENT)
25
26 /* Array of GSList*s of ObMouseBinding*s. */
27 static GSList *bound_contexts[OB_FRAME_NUM_CONTEXTS];
28
29 ObFrameContext mouse_button_frame_context(ObFrameContext context,
30                                           guint button)
31 {
32     GSList *it;
33     ObFrameContext x = context;
34
35     for (it = bound_contexts[context]; it; it = g_slist_next(it)) {
36         ObMouseBinding *b = it->data;
37
38         if (b->button == button)
39             return context;
40     }
41
42     switch (context) {
43     case OB_FRAME_CONTEXT_NONE:
44     case OB_FRAME_CONTEXT_DESKTOP:
45     case OB_FRAME_CONTEXT_CLIENT:
46     case OB_FRAME_CONTEXT_TITLEBAR:
47     case OB_FRAME_CONTEXT_HANDLE:
48     case OB_FRAME_CONTEXT_FRAME:
49         break;
50     case OB_FRAME_CONTEXT_BLCORNER:
51     case OB_FRAME_CONTEXT_BRCORNER:
52         x = OB_FRAME_CONTEXT_HANDLE;
53         break;
54     case OB_FRAME_CONTEXT_TLCORNER:
55     case OB_FRAME_CONTEXT_TRCORNER:
56     case OB_FRAME_CONTEXT_MAXIMIZE:
57     case OB_FRAME_CONTEXT_ALLDESKTOPS:
58     case OB_FRAME_CONTEXT_SHADE:
59     case OB_FRAME_CONTEXT_ICONIFY:
60     case OB_FRAME_CONTEXT_ICON:
61     case OB_FRAME_CONTEXT_CLOSE:
62         x = OB_FRAME_CONTEXT_TITLEBAR;
63         break;
64     case OB_FRAME_NUM_CONTEXTS:
65         g_assert_not_reached();
66     }
67
68     return x;
69 }
70
71 void mouse_grab_for_client(ObClient *client, gboolean grab)
72 {
73     int i;
74     GSList *it;
75
76     for (i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i)
77         for (it = bound_contexts[i]; it != NULL; it = g_slist_next(it)) {
78             /* grab/ungrab the button */
79             ObMouseBinding *b = it->data;
80             Window win;
81             int mode;
82             unsigned int mask;
83
84             if (FRAME_CONTEXT(i, client)) {
85                 win = client->frame->window;
86                 mode = GrabModeAsync;
87                 mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
88             } else if (CLIENT_CONTEXT(i, client)) {
89                 win = client->frame->plate;
90                 mode = GrabModeSync; /* this is handled in event */
91                 mask = ButtonPressMask; /* can't catch more than this with Sync
92                                            mode the release event is
93                                            manufactured in event() */
94             } else continue;
95
96             if (grab)
97                 grab_button_full(b->button, b->state, win, mask, mode,
98                                  OB_CURSOR_NONE);
99             else
100                 ungrab_button(b->button, b->state, win);
101         }
102 }
103
104 static void grab_all_clients(gboolean grab)
105 {
106     GList *it;
107
108     for (it = client_list; it != NULL; it = it->next)
109         mouse_grab_for_client(it->data, grab);
110 }
111
112 static void clearall()
113 {
114     int i;
115     GSList *it;
116     
117     for(i = 0; i < OB_FRAME_NUM_CONTEXTS; ++i) {
118         for (it = bound_contexts[i]; it != NULL; it = it->next) {
119             ObMouseBinding *b = it->data;
120             int j;
121
122             for (j = 0; j < OB_NUM_MOUSE_ACTIONS; ++j) {
123                 GSList *it;
124
125                 for (it = b->actions[j]; it; it = it->next)
126                     action_free(it->data);
127                 g_slist_free(b->actions[j]);
128             }
129             g_free(b);
130         }
131         g_slist_free(bound_contexts[i]);
132         bound_contexts[i] = NULL;
133     }
134 }
135
136 static gboolean fire_binding(ObMouseAction a, ObFrameContext context,
137                              ObClient *c, guint state,
138                              guint button, int x, int y)
139 {
140     GSList *it;
141     ObMouseBinding *b;
142
143     for (it = bound_contexts[context]; it != NULL; it = it->next) {
144         b = it->data;
145         if (b->state == state && b->button == button)
146             break;
147     }
148     /* if not bound, then nothing to do! */
149     if (it == NULL) return FALSE;
150
151     for (it = b->actions[a]; it; it = it->next)
152         action_run_mouse(it->data, c, state, button, x, y);
153     return TRUE;
154 }
155
156 void mouse_event(ObClient *client, XEvent *e)
157 {
158     static Time ltime;
159     static guint button = 0, state = 0, lbutton = 0;
160     static Window lwindow = None;
161     static int px, py;
162
163     ObFrameContext context;
164     gboolean click = FALSE;
165     gboolean dclick = FALSE;
166
167     switch (e->type) {
168     case ButtonPress:
169         context = frame_context(client, e->xany.window);
170         context = mouse_button_frame_context(context, e->xbutton.button);
171
172         px = e->xbutton.x_root;
173         py = e->xbutton.y_root;
174         button = e->xbutton.button;
175         state = e->xbutton.state;
176
177         fire_binding(OB_MOUSE_ACTION_PRESS, context,
178                      client, e->xbutton.state,
179                      e->xbutton.button,
180                      e->xbutton.x_root, e->xbutton.y_root);
181
182         if (CLIENT_CONTEXT(context, client)) {
183             /* Replay the event, so it goes to the client*/
184             XAllowEvents(ob_display, ReplayPointer, event_lasttime);
185             /* Fall through to the release case! */
186         } else
187             break;
188
189     case ButtonRelease:
190         context = frame_context(client, e->xany.window);
191         context = mouse_button_frame_context(context, e->xbutton.button);
192
193         if (e->xbutton.button == button) {
194             /* clicks are only valid if its released over the window */
195             int junk1, junk2;
196             Window wjunk;
197             guint ujunk, b, w, h;
198             xerror_set_ignore(TRUE);
199             junk1 = XGetGeometry(ob_display, e->xbutton.window,
200                                  &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
201             xerror_set_ignore(FALSE);
202             if (junk1) {
203                 if (e->xbutton.x >= (signed)-b &&
204                     e->xbutton.y >= (signed)-b &&
205                     e->xbutton.x < (signed)(w+b) &&
206                     e->xbutton.y < (signed)(h+b)) {
207                     click = TRUE;
208                     /* double clicks happen if there were 2 in a row! */
209                     if (lbutton == button &&
210                         lwindow == e->xbutton.window &&
211                         e->xbutton.time - config_mouse_dclicktime <=
212                         ltime) {
213                         dclick = TRUE;
214                         lbutton = 0;
215                     } else {
216                         lbutton = button;
217                         lwindow = e->xbutton.window;
218                     }
219                 } else {
220                     lbutton = 0;
221                     lwindow = None;
222                 }
223             }
224
225             button = 0;
226             state = 0;
227             ltime = e->xbutton.time;
228         }
229         fire_binding(OB_MOUSE_ACTION_RELEASE, context,
230                      client, e->xbutton.state,
231                      e->xbutton.button,
232                      e->xbutton.x_root, e->xbutton.y_root);
233         if (click)
234             fire_binding(OB_MOUSE_ACTION_CLICK, context,
235                          client, e->xbutton.state,
236                          e->xbutton.button,
237                          e->xbutton.x_root,
238                          e->xbutton.y_root);
239         if (dclick)
240             fire_binding(OB_MOUSE_ACTION_DOUBLE_CLICK, context,
241                          client, e->xbutton.state,
242                          e->xbutton.button,
243                          e->xbutton.x_root,
244                          e->xbutton.y_root);
245         break;
246
247     case MotionNotify:
248         if (button) {
249             context = frame_context(client, e->xany.window);
250             context = mouse_button_frame_context(context, button);
251
252             if (ABS(e->xmotion.x_root - px) >=
253                 config_mouse_threshold ||
254                 ABS(e->xmotion.y_root - py) >=
255                 config_mouse_threshold) {
256
257                 /* You can't drag on buttons */
258                 if (context == OB_FRAME_CONTEXT_MAXIMIZE ||
259                     context == OB_FRAME_CONTEXT_ALLDESKTOPS ||
260                     context == OB_FRAME_CONTEXT_SHADE ||
261                     context == OB_FRAME_CONTEXT_ICONIFY ||
262                     context == OB_FRAME_CONTEXT_ICON ||
263                     context == OB_FRAME_CONTEXT_CLOSE)
264                     break;
265
266                 fire_binding(OB_MOUSE_ACTION_MOTION, context,
267                              client, state, button, px, py);
268                 button = 0;
269                 state = 0;
270             }
271         }
272         break;
273
274     default:
275         g_assert_not_reached();
276     }
277 }
278
279 gboolean mouse_bind(char *buttonstr, char *contextstr, ObMouseAction mact,
280                     ObAction *action)
281 {
282     guint state, button;
283     ObFrameContext context;
284     ObMouseBinding *b;
285     GSList *it;
286
287     if (!translate_button(buttonstr, &state, &button)) {
288         g_warning("invalid button '%s'", buttonstr);
289         return FALSE;
290     }
291
292     contextstr = g_ascii_strdown(contextstr, -1);
293     context = frame_context_from_string(contextstr);
294     if (!context) {
295         g_warning("invalid context '%s'", contextstr);
296         g_free(contextstr);
297         return FALSE;
298     }
299     g_free(contextstr);
300
301     for (it = bound_contexts[context]; it != NULL; it = it->next){
302         b = it->data;
303         if (b->state == state && b->button == button) {
304             b->actions[mact] = g_slist_append(b->actions[mact], action);
305             return TRUE;
306         }
307     }
308
309     /* when there are no modifiers in the binding, then the action cannot
310        be interactive */
311     if (!state && action->data.any.interactive) {
312         action->data.any.interactive = FALSE;
313         action->data.inter.final = TRUE;
314     }
315
316     /* add the binding */
317     b = g_new0(ObMouseBinding, 1);
318     b->state = state;
319     b->button = button;
320     b->actions[mact] = g_slist_append(NULL, action);
321     bound_contexts[context] = g_slist_append(bound_contexts[context], b);
322
323     return TRUE;
324 }
325
326 void mouse_startup(gboolean reconfig)
327 {
328     grab_all_clients(TRUE);
329 }
330
331 void mouse_shutdown(gboolean reconfig)
332 {
333     grab_all_clients(FALSE);
334     clearall();
335 }