add a leftHanded option for mouse bindings, reverses the left/right keywords
[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/event.h"
5 #include "kernel/client.h"
6 #include "kernel/prop.h"
7 #include "kernel/grab.h"
8 #include "kernel/frame.h"
9 #include "parser/parse.h"
10 #include "translate.h"
11 #include "mouse.h"
12 #include <glib.h>
13
14 static int threshold;
15 static int dclicktime;
16 gboolean mouse_lefthand;
17 /*
18
19 <context name="Titlebar"> 
20   <mousebind button="Left" action="Press">
21     <action name="Raise"></action>
22   </mousebind>
23 </context>
24
25 */
26
27 static void parse_xml(xmlDocPtr doc, xmlNodePtr node, void *d)
28 {
29     xmlNodePtr n, nbut, nact;
30     char *buttonstr;
31     char *contextstr;
32     MouseAction mact;
33     Action *action;
34
35     if ((n = parse_find_node("dragThreshold", node)))
36         threshold = parse_int(doc, n);
37     if ((n = parse_find_node("doubleClickTime", node)))
38         dclicktime = parse_int(doc, n);
39     if ((n = parse_find_node("leftHanded", node)))
40         mouse_lefthand = parse_bool(doc, n);
41
42     n = parse_find_node("context", node);
43     while (n) {
44         if (!parse_attr_string("name", n, &contextstr))
45             goto next_n;
46         nbut = parse_find_node("mousebind", n->xmlChildrenNode);
47         while (nbut) {
48             if (!parse_attr_string("button", nbut, &buttonstr))
49                 goto next_nbut;
50             if (parse_attr_contains("press", nbut, "action"))
51                 mact = MouseAction_Press;
52             else if (parse_attr_contains("release", nbut, "action"))
53                 mact = MouseAction_Release;
54             else if (parse_attr_contains("click", nbut, "action"))
55                 mact = MouseAction_Click;
56             else if (parse_attr_contains("doubleclick", nbut,"action"))
57                 mact = MouseAction_DClick;
58             else if (parse_attr_contains("drag", nbut, "action"))
59                 mact = MouseAction_Motion;
60             else
61                 goto next_nbut;
62             nact = parse_find_node("action", nbut->xmlChildrenNode);
63             while (nact) {
64                 if ((action = action_parse(doc, nact))) {
65                     /* validate that its okay for a mouse binding*/
66                     if (mact == MouseAction_Motion) {
67                         if (action->func != action_moveresize ||
68                             action->data.moveresize.corner ==
69                             prop_atoms.net_wm_moveresize_move_keyboard ||
70                             action->data.moveresize.corner ==
71                             prop_atoms.net_wm_moveresize_size_keyboard) {
72                             action_free(action);
73                             action = NULL;
74                         }
75                     } else {
76                         if (action->func == action_moveresize &&
77                             action->data.moveresize.corner !=
78                             prop_atoms.net_wm_moveresize_move_keyboard &&
79                             action->data.moveresize.corner !=
80                             prop_atoms.net_wm_moveresize_size_keyboard) {
81                             action_free(action);
82                             action = NULL;
83                         }
84                     }
85                     if (action)
86                         mbind(buttonstr, contextstr, mact, action);
87                 }
88                 nact = parse_find_node("action", nact->next);
89             }
90             g_free(buttonstr);
91         next_nbut:
92             nbut = parse_find_node("mousebind", nbut->next);
93         }
94         g_free(contextstr);
95     next_n:
96         n = parse_find_node("context", n->next);
97     }
98 }
99
100 void plugin_setup_config()
101 {
102     threshold = 3;
103     dclicktime = 200;
104     mouse_lefthand = FALSE;
105     parse_register("mouse", parse_xml, NULL);
106 }
107
108 /* Array of GSList*s of PointerBinding*s. */
109 static GSList *bound_contexts[NUM_CONTEXTS];
110
111 static void grab_for_client(Client *client, gboolean grab)
112 {
113     int i;
114     GSList *it;
115
116     for (i = 0; i < NUM_CONTEXTS; ++i)
117         for (it = bound_contexts[i]; it != NULL; it = it->next) {
118             /* grab/ungrab the button */
119             MouseBinding *b = it->data;
120             Window win;
121             int mode;
122             unsigned int mask;
123
124             if (i == Context_Frame) {
125                 win = client->frame->window;
126                 mode = GrabModeAsync;
127                 mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
128             } else if (i == Context_Client) {
129                 win = client->frame->plate;
130                 mode = GrabModeSync; /* this is handled in event */
131                 mask = ButtonPressMask; /* can't catch more than this with Sync
132                                            mode the release event is
133                                            manufactured in event() */
134             } else continue;
135
136             if (grab)
137                 grab_button_full(b->button, b->state, win, mask, mode, None);
138             else
139                 ungrab_button(b->button, b->state, win);
140         }
141 }
142
143 static void grab_all_clients(gboolean grab)
144 {
145     GList *it;
146
147     for (it = client_list; it != NULL; it = it->next)
148         grab_for_client(it->data, grab);
149 }
150
151 static void clearall()
152 {
153     int i;
154     GSList *it;
155     
156     for(i = 0; i < NUM_CONTEXTS; ++i) {
157         for (it = bound_contexts[i]; it != NULL; it = it->next) {
158             int j;
159
160             MouseBinding *b = it->data;
161             for (j = 0; j < NUM_MOUSEACTION; ++j) {
162                 GSList *it;
163                 for (it = b->actions[j]; it; it = it->next) {
164                     action_free(it->data);
165                 }
166                 g_slist_free(b->actions[j]);
167             }
168             g_free(b);
169         }
170         g_slist_free(bound_contexts[i]);
171     }
172 }
173
174 static void fire_button(MouseAction a, Context context, Client *c, guint state,
175                         guint button, int x, int y)
176 {
177     GSList *it;
178     MouseBinding *b;
179
180     for (it = bound_contexts[context]; it != NULL; it = it->next) {
181         b = it->data;
182         if (b->state == state && b->button == button)
183             break;
184     }
185     /* if not bound, then nothing to do! */
186     if (it == NULL) return;
187
188     for (it = b->actions[a]; it; it = it->next) {
189         Action *act = it->data;
190         if (act->func != NULL) {
191             act->data.any.c = c;
192
193             g_assert(act->func != action_moveresize);
194
195             if (act->func == action_showmenu) {
196                 act->data.showmenu.x = x;
197                 act->data.showmenu.y = y;
198             }
199
200             act->func(&act->data);
201         }
202     }
203 }
204
205 static void fire_motion(MouseAction a, Context context, Client *c,
206                         guint state, guint button, int x_root, int y_root,
207                         guint32 corner)
208 {
209     GSList *it;
210     MouseBinding *b;
211
212     for (it = bound_contexts[context]; it != NULL; it = it->next) {
213         b = it->data;
214         if (b->state == state && b->button == button)
215                 break;
216     }
217     /* if not bound, then nothing to do! */
218     if (it == NULL) return;
219
220     for (it = b->actions[a]; it; it = it->next) {
221         Action *act = it->data;
222         if (act->func != NULL) {
223             act->data.any.c = c;
224
225             if (act->func == action_moveresize) {
226                 act->data.moveresize.x = x_root;
227                 act->data.moveresize.y = y_root;
228                 act->data.moveresize.button = button;
229                 if (!(act->data.moveresize.corner ==
230                       prop_atoms.net_wm_moveresize_move ||
231                       act->data.moveresize.corner ==
232                       prop_atoms.net_wm_moveresize_move_keyboard ||
233                       act->data.moveresize.corner ==
234                       prop_atoms.net_wm_moveresize_size_keyboard))
235                     act->data.moveresize.corner = corner;
236             } else
237                 g_assert_not_reached();
238
239             act->func(&act->data);
240         }
241     }
242 }
243
244 static guint32 pick_corner(int x, int y, int cx, int cy, int cw, int ch)
245 {
246     if (x - cx < cw / 2) {
247         if (y - cy < ch / 2)
248             return prop_atoms.net_wm_moveresize_size_topleft;
249         else
250             return prop_atoms.net_wm_moveresize_size_bottomleft;
251     } else {
252         if (y - cy < ch / 2)
253             return prop_atoms.net_wm_moveresize_size_topright;
254         else
255             return prop_atoms.net_wm_moveresize_size_bottomright;
256     }
257 }
258
259 static void event(ObEvent *e, void *foo)
260 {
261     static Time ltime;
262     static guint button = 0, state = 0, lbutton = 0;
263     static int px, py;
264     gboolean click = FALSE;
265     gboolean dclick = FALSE;
266     Context context;
267     
268     switch (e->type) {
269     case Event_Client_Mapped:
270         grab_for_client(e->data.c.client, TRUE);
271         break;
272
273     case Event_Client_Destroy:
274         grab_for_client(e->data.c.client, FALSE);
275         break;
276
277     case Event_X_ButtonPress:
278         context = frame_context(e->data.x.client,
279                                 e->data.x.e->xbutton.window);
280
281         if (!button) {
282             px = e->data.x.e->xbutton.x_root;
283             py = e->data.x.e->xbutton.y_root;
284             button = e->data.x.e->xbutton.button;
285             state = e->data.x.e->xbutton.state;
286         }
287
288         fire_button(MouseAction_Press, context,
289                     e->data.x.client, e->data.x.e->xbutton.state,
290                     e->data.x.e->xbutton.button,
291                     e->data.x.e->xbutton.x_root, e->data.x.e->xbutton.y_root);
292
293         if (context == Context_Client) {
294             /* Replay the event, so it goes to the client*/
295             XAllowEvents(ob_display, ReplayPointer, event_lasttime);
296             /* Fall through to the release case! */
297         } else
298             break;
299
300     case Event_X_ButtonRelease:
301         context = frame_context(e->data.x.client,
302                                 e->data.x.e->xbutton.window);
303         if (e->data.x.e->xbutton.button == button) {
304             /* clicks are only valid if its released over the window */
305             int junk1, junk2;
306             Window wjunk;
307             guint ujunk, b, w, h;
308             XGetGeometry(ob_display, e->data.x.e->xbutton.window,
309                          &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
310             if (e->data.x.e->xbutton.x >= (signed)-b &&
311                 e->data.x.e->xbutton.y >= (signed)-b &&
312                 e->data.x.e->xbutton.x < (signed)(w+b) &&
313                 e->data.x.e->xbutton.y < (signed)(h+b)) {
314                 click = TRUE;
315                 /* double clicks happen if there were 2 in a row! */
316                 if (lbutton == button &&
317                     e->data.x.e->xbutton.time - dclicktime <= ltime) {
318                     dclick = TRUE;
319                     lbutton = 0;
320                 } else
321                     lbutton = button;
322             } else
323                 lbutton = 0;
324
325             button = 0;
326             state = 0;
327             ltime = e->data.x.e->xbutton.time;
328         }
329         fire_button(MouseAction_Release, context,
330                     e->data.x.client, e->data.x.e->xbutton.state,
331                     e->data.x.e->xbutton.button,
332                     e->data.x.e->xbutton.x_root, e->data.x.e->xbutton.y_root);
333         if (click)
334             fire_button(MouseAction_Click, context,
335                         e->data.x.client, e->data.x.e->xbutton.state,
336                         e->data.x.e->xbutton.button,
337                         e->data.x.e->xbutton.x_root,
338                         e->data.x.e->xbutton.y_root);
339         if (dclick)
340             fire_button(MouseAction_DClick, context,
341                         e->data.x.client, e->data.x.e->xbutton.state,
342                         e->data.x.e->xbutton.button,
343                         e->data.x.e->xbutton.x_root,
344                         e->data.x.e->xbutton.y_root);
345         break;
346
347     case Event_X_MotionNotify:
348         if (button) {
349             if (ABS(e->data.x.e->xmotion.x_root - px) >= threshold ||
350                 ABS(e->data.x.e->xmotion.y_root - py) >= threshold) {
351                 guint32 corner;
352
353                 context = frame_context(e->data.x.client,
354                                         e->data.x.e->xmotion.window);
355
356                 /* You can't drag on buttons */
357                 if (context == Context_Maximize ||
358                     context == Context_AllDesktops ||
359                     context == Context_Shade ||
360                     context == Context_Iconify ||
361                     context == Context_Icon ||
362                     context == Context_Close)
363                     break;
364
365                 if (!e->data.x.client)
366                     corner = prop_atoms.net_wm_moveresize_size_bottomright;
367                 else
368                     corner =
369                         pick_corner(e->data.x.e->xmotion.x_root,
370                                     e->data.x.e->xmotion.y_root,
371                                     e->data.x.client->frame->area.x,
372                                     e->data.x.client->frame->area.y,
373                                     /* use the client size because the frame
374                                        can be differently sized (shaded
375                                        windows) and we want this based on the
376                                        clients size */
377                                     e->data.x.client->area.width +
378                                     e->data.x.client->frame->size.left +
379                                     e->data.x.client->frame->size.right,
380                                     e->data.x.client->area.height +
381                                     e->data.x.client->frame->size.top +
382                                     e->data.x.client->frame->size.bottom);
383                 fire_motion(MouseAction_Motion, context,
384                             e->data.x.client, state, button,
385                             e->data.x.e->xmotion.x_root, 
386                             e->data.x.e->xmotion.y_root, corner);
387                 button = 0;
388                 state = 0;
389             }
390         }
391         break;
392
393     default:
394         g_assert_not_reached();
395     }
396 }
397
398 gboolean mbind(char *buttonstr, char *contextstr, MouseAction mact,
399                Action *action)
400 {
401     guint state, button;
402     Context context;
403     MouseBinding *b;
404     GSList *it;
405
406     if (!translate_button(buttonstr, &state, &button)) {
407         g_warning("invalid button '%s'", buttonstr);
408         return FALSE;
409     }
410
411     contextstr = g_ascii_strdown(contextstr, -1);
412     context = frame_context_from_string(contextstr);
413     if (!context) {
414         g_warning("invalid context '%s'", contextstr);
415         g_free(contextstr);
416         return FALSE;
417     }
418     g_free(contextstr);
419
420     for (it = bound_contexts[context]; it != NULL; it = it->next){
421         b = it->data;
422         if (b->state == state && b->button == button) {
423             b->actions[mact] = g_slist_append(b->actions[mact], action);
424             return TRUE;
425         }
426     }
427
428     grab_all_clients(FALSE);
429
430     /* add the binding */
431     b = g_new0(MouseBinding, 1);
432     b->state = state;
433     b->button = button;
434     b->actions[mact] = g_slist_append(NULL, action);
435     bound_contexts[context] = g_slist_append(bound_contexts[context], b);
436
437     grab_all_clients(TRUE);
438
439     return TRUE;
440 }
441
442 void plugin_startup()
443 {
444     dispatch_register(Event_Client_Mapped | Event_Client_Destroy |
445                       Event_X_ButtonPress | Event_X_ButtonRelease |
446                       Event_X_MotionNotify, (EventHandler)event, NULL);
447 }
448
449 void plugin_shutdown()
450 {
451     dispatch_register(0, (EventHandler)event, NULL);
452
453     grab_all_clients(FALSE);
454     clearall();
455 }