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