use the grab.h funcs to grab keys and buttons
[mikachu/openbox.git] / plugins / mouse / mouse.c
1 #include "../../kernel/openbox.h"
2 #include "../../kernel/dispatch.h"
3 #include "../../kernel/action.h"
4 #include "../../kernel/client.h"
5 #include "../../kernel/frame.h"
6 #include "../../kernel/grab.h"
7 #include "../../kernel/engine.h"
8 #include "translate.h"
9 #include "mouse.h"
10 #include <glib.h>
11
12 /* GData of GSList*s of PointerBinding*s. */
13 static GData *bound_contexts;
14
15 struct foreach_grab_temp {
16     Client *client;
17     gboolean grab;
18 };
19
20 static void foreach_grab(GQuark key, gpointer data, gpointer user_data)
21 {
22     struct foreach_grab_temp *d = user_data;
23     GSList *it;
24     for (it = data; it != NULL; it = it->next) {
25         /* grab/ungrab the button */
26         MouseBinding *b = it->data;
27         Window win;
28         int mode;
29         unsigned int mask;
30
31         if (key == g_quark_try_string("frame")) {
32             win = d->client->frame->window;
33             mode = GrabModeAsync;
34             mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
35         } else if (key == g_quark_try_string("client")) {
36             win = d->client->frame->plate;
37             mode = GrabModeSync; /* this is handled in event */
38             mask = ButtonPressMask; /* can't catch more than this with Sync
39                                        mode the release event is manufactured
40                                        in event */
41         } else return;
42
43         if (d->grab)
44             grab_button(b->button, b->state, win, mask, mode);
45         else
46             ungrab_button(b->button, b->state, win);
47     }
48 }
49   
50 static void grab_for_client(Client *client, gboolean grab)
51 {
52     struct foreach_grab_temp bt;
53     bt.client = client;
54     bt.grab = grab;
55     g_datalist_foreach(&bound_contexts, foreach_grab, &bt);
56 }
57
58 static void grab_all_clients(gboolean grab)
59 {
60     GSList *it;
61
62     for (it = client_list; it != NULL; it = it->next)
63         grab_for_client(it->data, grab);
64 }
65
66 static void foreach_clear(GQuark key, gpointer data, gpointer user_data)
67 {
68     GSList *it;
69     user_data = user_data;
70     for (it = data; it != NULL; it = it->next) {
71         int i;
72
73         MouseBinding *b = it->data;
74         for (i = 0; i < NUM_MOUSEACTION; ++i)
75             if (b->action[i] != NULL)
76                 action_free(b->action[i]);
77         g_free(b);
78     }
79     g_slist_free(data);
80 }
81
82 static void fire_button(MouseAction a, GQuark context, Client *c, guint state,
83                         guint button)
84 {
85     GSList *it;
86     MouseBinding *b;
87
88     for (it = g_datalist_id_get_data(&bound_contexts, context);
89          it != NULL; it = it->next) {
90         b = it->data;
91         if (b->state == state && b->button == button)
92             break;
93     }
94     /* if not bound, then nothing to do! */
95     if (it == NULL) return;
96
97     if (b->action[a] != NULL && b->action[a]->func != NULL) {
98         b->action[a]->data.any.c = c;
99
100         g_assert(!(b->action[a]->func == action_move ||
101                    b->action[a]->func == action_resize));
102
103         b->action[a]->func(&b->action[a]->data);
104     }
105 }
106
107 /* corner should be the opposite corner of the window in which the pointer
108    clicked, Corner_TopLeft if a good default if there is no client */
109 static void fire_motion(MouseAction a, GQuark context, Client *c, guint state,
110                         guint button, int cx, int cy, int cw, int ch,
111                         int dx, int dy, gboolean final, Corner corner)
112 {
113     GSList *it;
114     MouseBinding *b;
115
116     for (it = g_datalist_id_get_data(&bound_contexts, context);
117          it != NULL; it = it->next) {
118         b = it->data;
119         if (b->state == state && b->button == button)
120                 break;
121     }
122     /* if not bound, then nothing to do! */
123     if (it == NULL) return;
124
125     if (b->action[a] != NULL && b->action[a]->func != NULL) {
126         b->action[a]->data.any.c = c;
127
128         if (b->action[a]->func == action_move) {
129             b->action[a]->data.move.x = cx + dx;
130             b->action[a]->data.move.y = cy + dy;
131             b->action[a]->data.move.final = final;
132         } else if (b->action[a]->func == action_resize) {
133             b->action[a]->data.resize.corner = corner;
134             switch (corner) {
135             case Corner_TopLeft:
136                 b->action[a]->data.resize.x = cw + dx;
137                 b->action[a]->data.resize.y = ch + dy;
138                 break;
139             case Corner_TopRight:
140                 b->action[a]->data.resize.x = cw - dx;
141                 b->action[a]->data.resize.y = ch + dy;
142                 break;
143             case Corner_BottomLeft:
144                 b->action[a]->data.resize.x = cw + dx;
145                 b->action[a]->data.resize.y = ch - dy;
146                 break;
147             case Corner_BottomRight:
148                 b->action[a]->data.resize.x = cw - dx;
149                 b->action[a]->data.resize.y = ch - dy;
150                 break;
151             }
152             b->action[a]->data.resize.final = final;
153         }
154         b->action[a]->func(&b->action[a]->data);
155     }
156 }
157
158 static Corner pick_corner(int x, int y, int cx, int cy, int cw, int ch)
159 {
160     if (x - cx < cw / 2) {
161         if (y - cy < ch / 2)
162             return Corner_BottomRight;
163         else
164             return Corner_TopRight;
165     } else {
166         if (y - cy < ch / 2)
167             return Corner_BottomLeft;
168         else
169             return Corner_TopLeft;
170     }
171 }
172
173 static void event(ObEvent *e, void *foo)
174 {
175     static Time ltime;
176     static int px, py, cx, cy, cw, ch, dx, dy;
177     static guint button = 0, lbutton = 0;
178     static gboolean drag = FALSE;
179     static Corner corner = Corner_TopLeft;
180     gboolean click = FALSE;
181     gboolean dclick = FALSE;
182     GQuark context;
183
184     switch (e->type) {
185     case Event_Client_Mapped:
186         grab_for_client(e->data.c.client, TRUE);
187         break;
188
189     case Event_Client_Destroy:
190         grab_for_client(e->data.c.client, FALSE);
191         break;
192
193     case Event_X_ButtonPress:
194         if (!button) {
195             if (e->data.x.client != NULL) {
196                 cx = e->data.x.client->frame->area.x;
197                 cy = e->data.x.client->frame->area.y;
198                 cw = e->data.x.client->frame->area.width;
199                 ch = e->data.x.client->frame->area.height;
200                 px = e->data.x.e->xbutton.x_root;
201                 py = e->data.x.e->xbutton.y_root;
202                 corner = pick_corner(px, py, cx, cy, cw, ch);
203             }
204             button = e->data.x.e->xbutton.button;
205         }
206         context = engine_get_context(e->data.x.client,
207                                      e->data.x.e->xbutton.window);
208
209         fire_button(MouseAction_Press, context,
210                     e->data.x.client, e->data.x.e->xbutton.state,
211                     e->data.x.e->xbutton.button);
212
213         if (context == g_quark_try_string("client")) {
214             /* Replay the event, so it goes to the client*/
215             XAllowEvents(ob_display, ReplayPointer, CurrentTime);
216             /* Fall through to the release case! */
217         } else
218             break;
219
220     case Event_X_ButtonRelease:
221         context = engine_get_context(e->data.x.client,
222                                      e->data.x.e->xbutton.window);
223         if (e->data.x.e->xbutton.button == button) {
224             /* end drags */
225             if (drag) {
226                 fire_motion(MouseAction_Motion, context,
227                             e->data.x.client, e->data.x.e->xbutton.state,
228                             e->data.x.e->xbutton.button,
229                             cx, cy, cw, ch, dx, dy, TRUE, corner);
230                 drag = FALSE;
231                 
232                 lbutton = 0;
233             } else {
234                 /* clicks are only valid if its released over the window */
235                 if (e->data.x.e->xbutton.x >= 0 &&
236                     e->data.x.e->xbutton.y >= 0) {
237                     int junk;
238                     Window wjunk;
239                     guint ujunk, w, h;
240                     XGetGeometry(ob_display, e->data.x.e->xbutton.window,
241                                  &wjunk, &junk, &junk, &w, &h, &ujunk, &ujunk);
242                     if (e->data.x.e->xbutton.x < (signed)w &&
243                         e->data.x.e->xbutton.y < (signed)h) {
244                         click =TRUE;
245                         /* double clicks happen if there were 2 in a row! */
246                         if (lbutton == button &&
247                             e->data.x.e->xbutton.time - 300 <= ltime)
248                             dclick = TRUE;
249                     }
250                     lbutton = button;
251                 } else
252                     lbutton = 0;
253             }
254
255             button = 0;
256             ltime = e->data.x.e->xbutton.time;
257         }
258         fire_button(MouseAction_Press, context,
259                     e->data.x.client, e->data.x.e->xbutton.state,
260                     e->data.x.e->xbutton.button);
261         if (click)
262             fire_button(MouseAction_Click, context,
263                         e->data.x.client, e->data.x.e->xbutton.state,
264                         e->data.x.e->xbutton.button);
265         if (dclick)
266             fire_button(MouseAction_DClick, context,
267                         e->data.x.client, e->data.x.e->xbutton.state,
268                         e->data.x.e->xbutton.button);
269         break;
270
271     case Event_X_MotionNotify:
272         if (button) {
273             drag = TRUE;
274             dx = e->data.x.e->xmotion.x_root - px;
275             dy = e->data.x.e->xmotion.y_root - py;
276             context = engine_get_context(e->data.x.client,
277                                          e->data.x.e->xbutton.window);
278             fire_motion(MouseAction_Motion, context,
279                         e->data.x.client, e->data.x.e->xmotion.state,
280                         button, cx, cy, cw, ch, dx, dy, FALSE, corner);
281         }
282         break;
283
284     default:
285         g_assert_not_reached();
286     }
287 }
288
289 static gboolean mbind(char *buttonstr, char *contextstr, MouseAction mact,
290                       Action *action)
291 {
292     guint state, button;
293     GQuark context;
294     MouseBinding *b;
295     GSList *it;
296     guint i;
297
298     if (!translate_button(buttonstr, &state, &button)) {
299         g_warning("invalid button");
300         return FALSE;
301     }
302
303     context = g_quark_try_string(contextstr);
304     if (!context) {
305         g_warning("invalid context");
306         return FALSE;
307     }
308
309     for (it = g_datalist_id_get_data(&bound_contexts, context);
310          it != NULL; it = it->next){
311         b = it->data;
312         if (b->state == state && b->button == button) {
313             /* already bound */
314             if (b->action[mact] != NULL) {
315                 g_warning("duplicate binding");
316                 return FALSE;
317             }
318             b->action[mact] = action;
319             return TRUE;
320         }
321     }
322
323     grab_all_clients(FALSE);
324
325     /* add the binding */
326     b = g_new(MouseBinding, 1);
327     b->state = state;
328     b->button = button;
329     for (i = 0; i < NUM_MOUSEACTION; ++i)
330         b->action[i] = NULL;
331     b->action[mact] = action;
332     g_datalist_id_set_data(&bound_contexts, context, 
333         g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
334
335     grab_all_clients(TRUE);
336
337     return TRUE;
338 }
339
340 static void binddef()
341 {
342     Action *a;
343
344     /* When creating an Action struct, all of the data elements in the
345        appropriate struct need to be set, except the Client*, which will be set
346        at call-time when then action function is used.
347
348        For action_move and action_resize, the 'x', 'y', and 'final' data
349        elements should not be set, as they are set at call-time.       
350     */
351
352     a = action_new(action_move);
353     mbind("1", "titlebar", MouseAction_Motion, a);
354     a = action_new(action_move);
355     mbind("1", "handle", MouseAction_Motion, a);
356     a = action_new(action_move);
357     mbind("A-1", "frame", MouseAction_Motion, a);
358
359     a = action_new(action_resize);
360     mbind("1", "blcorner", MouseAction_Motion, a);
361     a = action_new(action_resize);
362     mbind("1", "brcorner", MouseAction_Motion, a);
363     a = action_new(action_resize);
364     mbind("A-3", "frame", MouseAction_Motion, a);
365
366     a = action_new(action_focusraise);
367     mbind("1", "titlebar", MouseAction_Press, a);
368     a = action_new(action_focusraise);
369     mbind("1", "handle", MouseAction_Press, a);
370     a = action_new(action_lower);
371     mbind("2", "titlebar", MouseAction_Press, a);
372     a = action_new(action_lower);
373     mbind("2", "handle", MouseAction_Press, a);
374     a = action_new(action_raise);
375     mbind("A-1", "frame", MouseAction_Click, a);
376     a = action_new(action_lower);
377     mbind("A-3", "frame", MouseAction_Click, a);
378
379     a = action_new(action_focusraise);
380     mbind("1", "client", MouseAction_Press, a);
381
382     a = action_new(action_toggle_shade);
383     mbind("1", "titlebar", MouseAction_DClick, a);
384     a = action_new(action_shade);
385     mbind("4", "titlebar", MouseAction_Press, a);
386     a = action_new(action_unshade);
387     mbind("5", "titlebar", MouseAction_Click, a);
388
389     a = action_new(action_toggle_maximize_full);
390     mbind("1", "maximize", MouseAction_Click, a);
391     a = action_new(action_toggle_maximize_vert);
392     mbind("2", "maximize", MouseAction_Click, a);
393     a = action_new(action_toggle_maximize_horz);
394     mbind("3", "maximize", MouseAction_Click, a);
395     a = action_new(action_iconify);
396     mbind("1", "iconify", MouseAction_Click, a);
397     a = action_new(action_close);
398     mbind("1", "icon", MouseAction_DClick, a);
399     a = action_new(action_close);
400     mbind("1", "close", MouseAction_Click, a);
401     a = action_new(action_toggle_omnipresent);
402     mbind("1", "alldesktops", MouseAction_Click, a);
403
404     a = action_new(action_next_desktop);
405     a->data.nextprevdesktop.wrap = TRUE;
406     mbind("4", "root", MouseAction_Click, a);
407     a = action_new(action_previous_desktop);
408     a->data.nextprevdesktop.wrap = TRUE;
409     mbind("5", "root", MouseAction_Click, a);
410     a = action_new(action_next_desktop);
411     a->data.nextprevdesktop.wrap = TRUE;
412     mbind("A-4", "root", MouseAction_Click, a);
413     a = action_new(action_previous_desktop);
414     a->data.nextprevdesktop.wrap = TRUE;
415     mbind("A-5", "root", MouseAction_Click, a);
416     a = action_new(action_next_desktop);
417     a->data.nextprevdesktop.wrap = TRUE;
418     mbind("A-4", "frame", MouseAction_Click, a);
419     a = action_new(action_previous_desktop);
420     a->data.nextprevdesktop.wrap = TRUE;
421     mbind("A-5", "frame", MouseAction_Click, a);
422 }
423
424 void plugin_startup()
425 {
426     dispatch_register(Event_Client_Mapped | Event_Client_Destroy |
427                       Event_X_ButtonPress | Event_X_ButtonRelease |
428                       Event_X_MotionNotify, (EventHandler)event, NULL);
429
430     /* XXX parse a config */
431     binddef();
432 }
433
434 void plugin_shutdown()
435 {
436     dispatch_register(0, (EventHandler)event, NULL);
437
438     grab_all_clients(FALSE);
439     g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
440 }