]> icculus.org git repositories - mikachu/openbox.git/blob - plugins/mouse/mouse.c
make the drag threshold and double click time into rc options
[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/frame.h"
7 #include "../../kernel/grab.h"
8 #include "../../kernel/engine.h"
9 #include "../../kernel/config.h"
10 #include "translate.h"
11 #include "mouse.h"
12 #include "mouserc_parse.h"
13 #include <glib.h>
14
15 void plugin_setup_config()
16 {
17     config_def_set(config_def_new("mouse.dragThreshold", Config_Integer,
18                                   "Drag Threshold",
19                                   "The drag threshold in pixels before a Drag "
20                                   "event starts."));
21     config_def_set(config_def_new("mouse.doubleClickTime", Config_Integer,
22                                   "Double Click Interval",
23                                   "The amount of time (in milliseconds) in "
24                                   "which two clicks must occur to cause a "
25                                   "DoubleClick event."));
26 }
27
28 /* GData of GSList*s of PointerBinding*s. */
29 static GData *bound_contexts;
30
31 struct foreach_grab_temp {
32     Client *client;
33     gboolean grab;
34 };
35
36 static void foreach_grab(GQuark key, gpointer data, gpointer user_data)
37 {
38     struct foreach_grab_temp *d = user_data;
39     GSList *it;
40     for (it = data; it != NULL; it = it->next) {
41         /* grab/ungrab the button */
42         MouseBinding *b = it->data;
43         Window win;
44         int mode;
45         unsigned int mask;
46
47         if (key == g_quark_try_string("frame")) {
48             win = d->client->frame->window;
49             mode = GrabModeAsync;
50             mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
51         } else if (key == g_quark_try_string("client")) {
52             win = d->client->frame->plate;
53             mode = GrabModeSync; /* this is handled in event */
54             mask = ButtonPressMask; /* can't catch more than this with Sync
55                                        mode the release event is manufactured
56                                        in event */
57         } else return;
58
59         if (d->grab)
60             grab_button(b->button, b->state, win, mask, mode);
61         else
62             ungrab_button(b->button, b->state, win);
63     }
64 }
65   
66 static void grab_for_client(Client *client, gboolean grab)
67 {
68     struct foreach_grab_temp bt;
69     bt.client = client;
70     bt.grab = grab;
71     g_datalist_foreach(&bound_contexts, foreach_grab, &bt);
72 }
73
74 static void grab_all_clients(gboolean grab)
75 {
76     GSList *it;
77
78     for (it = client_list; it != NULL; it = it->next)
79         grab_for_client(it->data, grab);
80 }
81
82 static void foreach_clear(GQuark key, gpointer data, gpointer user_data)
83 {
84     GSList *it;
85     user_data = user_data;
86     for (it = data; it != NULL; it = it->next) {
87         int i;
88
89         MouseBinding *b = it->data;
90         for (i = 0; i < NUM_MOUSEACTION; ++i)
91             if (b->action[i] != NULL)
92                 action_free(b->action[i]);
93         g_free(b);
94     }
95     g_slist_free(data);
96 }
97
98 static void fire_button(MouseAction a, GQuark context, Client *c, guint state,
99                         guint button)
100 {
101     GSList *it;
102     MouseBinding *b;
103
104     for (it = g_datalist_id_get_data(&bound_contexts, context);
105          it != NULL; it = it->next) {
106         b = it->data;
107         if (b->state == state && b->button == button)
108             break;
109     }
110     /* if not bound, then nothing to do! */
111     if (it == NULL) return;
112
113     if (b->action[a] != NULL && b->action[a]->func != NULL) {
114         b->action[a]->data.any.c = c;
115
116         g_assert(!(b->action[a]->func == action_move ||
117                    b->action[a]->func == action_resize));
118
119         b->action[a]->func(&b->action[a]->data);
120     }
121 }
122
123 /* corner should be the opposite corner of the window in which the pointer
124    clicked, Corner_TopLeft if a good default if there is no client 
125    Returns True or False for if a binding existed for the action or not.
126 */
127 static gboolean fire_motion(MouseAction a, GQuark context, Client *c,
128                             guint state, guint button, int cx, int cy,
129                             int cw, int ch, int dx, int dy,
130                             gboolean final, Corner corner)
131 {
132     GSList *it;
133     MouseBinding *b;
134
135     for (it = g_datalist_id_get_data(&bound_contexts, context);
136          it != NULL; it = it->next) {
137         b = it->data;
138         if (b->state == state && b->button == button)
139                 break;
140     }
141     /* if not bound, then nothing to do! */
142     if (it == NULL) return FALSE;
143
144     if (b->action[a] != NULL && b->action[a]->func != NULL) {
145         b->action[a]->data.any.c = c;
146
147         if (b->action[a]->func == action_move) {
148             b->action[a]->data.move.x = cx + dx;
149             b->action[a]->data.move.y = cy + dy;
150             b->action[a]->data.move.final = final;
151         } else if (b->action[a]->func == action_resize) {
152             b->action[a]->data.resize.corner = corner;
153             switch (corner) {
154             case Corner_TopLeft:
155                 b->action[a]->data.resize.x = cw + dx;
156                 b->action[a]->data.resize.y = ch + dy;
157                 break;
158             case Corner_TopRight:
159                 b->action[a]->data.resize.x = cw - dx;
160                 b->action[a]->data.resize.y = ch + dy;
161                 break;
162             case Corner_BottomLeft:
163                 b->action[a]->data.resize.x = cw + dx;
164                 b->action[a]->data.resize.y = ch - dy;
165                 break;
166             case Corner_BottomRight:
167                 b->action[a]->data.resize.x = cw - dx;
168                 b->action[a]->data.resize.y = ch - dy;
169                 break;
170             }
171             b->action[a]->data.resize.final = final;
172         }
173         b->action[a]->func(&b->action[a]->data);
174         return TRUE;
175     }
176     return FALSE;
177 }
178
179 static Corner pick_corner(int x, int y, int cx, int cy, int cw, int ch)
180 {
181     if (x - cx < cw / 2) {
182         if (y - cy < ch / 2)
183             return Corner_BottomRight;
184         else
185             return Corner_TopRight;
186     } else {
187         if (y - cy < ch / 2)
188             return Corner_BottomLeft;
189         else
190             return Corner_TopLeft;
191     }
192 }
193
194 static void event(ObEvent *e, void *foo)
195 {
196     static Time ltime;
197     static int px, py, cx, cy, cw, ch, dx, dy;
198     static guint button = 0, lbutton = 0;
199     static gboolean drag = FALSE, drag_used = FALSE;
200     static Corner corner = Corner_TopLeft;
201     ConfigValue doubleclicktime;
202     ConfigValue dragthreshold;
203     gboolean click = FALSE;
204     gboolean dclick = FALSE;
205     GQuark context;
206     
207     if (!config_get("mouse.dragThreshold", Config_Integer, &dragthreshold))
208         dragthreshold.integer = 3; /* default */
209     if (!config_get("mouse.doubleClickTime", Config_Integer, &doubleclicktime))
210         doubleclicktime.integer = 200; /* default */
211
212     switch (e->type) {
213     case Event_Client_Mapped:
214         grab_for_client(e->data.c.client, TRUE);
215         break;
216
217     case Event_Client_Destroy:
218         grab_for_client(e->data.c.client, FALSE);
219         break;
220
221     case Event_X_ButtonPress:
222         if (!button) {
223             if (e->data.x.client != NULL) {
224                 cx = e->data.x.client->frame->area.x;
225                 cy = e->data.x.client->frame->area.y;
226                 /* use the client size because the frame can be differently
227                    sized (shaded windows) and we want this based on the clients
228                    size */
229                 cw = e->data.x.client->area.width + 
230                     e->data.x.client->frame->size.left +
231                     e->data.x.client->frame->size.right;
232                 ch = e->data.x.client->area.height +
233                     e->data.x.client->frame->size.top +
234                     e->data.x.client->frame->size.bottom;
235                 px = e->data.x.e->xbutton.x_root;
236                 py = e->data.x.e->xbutton.y_root;
237                 corner = pick_corner(px, py, cx, cy, cw, ch);
238             }
239             button = e->data.x.e->xbutton.button;
240         }
241         context = engine_get_context(e->data.x.client,
242                                      e->data.x.e->xbutton.window);
243
244         fire_button(MouseAction_Press, context,
245                     e->data.x.client, e->data.x.e->xbutton.state,
246                     e->data.x.e->xbutton.button);
247
248         if (context == g_quark_try_string("client")) {
249             /* Replay the event, so it goes to the client*/
250             XAllowEvents(ob_display, ReplayPointer, event_lasttime);
251             /* Fall through to the release case! */
252         } else
253             break;
254
255     case Event_X_ButtonRelease:
256         context = engine_get_context(e->data.x.client,
257                                      e->data.x.e->xbutton.window);
258         if (e->data.x.e->xbutton.button == button) {
259             /* end drags */
260             if (drag_used) {
261                 fire_motion(MouseAction_Motion, context,
262                             e->data.x.client, e->data.x.e->xbutton.state,
263                             e->data.x.e->xbutton.button,
264                             cx, cy, cw, ch, dx, dy, TRUE, corner);
265                 drag = drag_used = FALSE;
266                 
267                 lbutton = 0;
268             } else {
269                 /* clicks are only valid if its released over the window */
270                 int junk;
271                 Window wjunk;
272                 guint ujunk, b, w, h;
273                 XGetGeometry(ob_display, e->data.x.e->xbutton.window,
274                              &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
275                 if (e->data.x.e->xbutton.x >= (signed)-b &&
276                     e->data.x.e->xbutton.y >= (signed)-b &&
277                     e->data.x.e->xbutton.x < (signed)(w+b) &&
278                     e->data.x.e->xbutton.y < (signed)(h+b)) {
279                     click = TRUE;
280                     /* double clicks happen if there were 2 in a row! */
281                     if (lbutton == button &&
282                         e->data.x.e->xbutton.time - doubleclicktime.integer <=
283                         ltime) {
284                         dclick = TRUE;
285                         lbutton = 0;
286                     } else
287                         lbutton = button;
288                 } else
289                     lbutton = 0;
290             }
291
292             button = 0;
293             ltime = e->data.x.e->xbutton.time;
294         }
295         fire_button(MouseAction_Release, context,
296                     e->data.x.client, e->data.x.e->xbutton.state,
297                     e->data.x.e->xbutton.button);
298         if (click)
299             fire_button(MouseAction_Click, context,
300                         e->data.x.client, e->data.x.e->xbutton.state,
301                         e->data.x.e->xbutton.button);
302         if (dclick)
303             fire_button(MouseAction_DClick, context,
304                         e->data.x.client, e->data.x.e->xbutton.state,
305                         e->data.x.e->xbutton.button);
306         break;
307
308     case Event_X_MotionNotify:
309         if (button) {
310             dx = e->data.x.e->xmotion.x_root - px;
311             dy = e->data.x.e->xmotion.y_root - py;
312             if (!drag &&
313                 (ABS(dx) >= dragthreshold.integer ||
314                  ABS(dy) >= dragthreshold.integer))
315                 drag = TRUE;
316             if (drag) {
317                 context = engine_get_context(e->data.x.client,
318                                              e->data.x.e->xbutton.window);
319                 drag_used = fire_motion(MouseAction_Motion, context,
320                                         e->data.x.client,
321                                         e->data.x.e->xmotion.state,
322                                         button, cx, cy, cw, ch, dx, dy,
323                                         FALSE, corner);
324             }
325         }
326         break;
327
328     default:
329         g_assert_not_reached();
330     }
331 }
332
333 gboolean mbind(char *buttonstr, char *contextstr, MouseAction mact,
334                Action *action)
335 {
336     guint state, button;
337     GQuark context;
338     MouseBinding *b;
339     GSList *it;
340
341     if (!translate_button(buttonstr, &state, &button)) {
342         g_warning("invalid button '%s'", buttonstr);
343         return FALSE;
344     }
345
346     contextstr = g_ascii_strdown(contextstr, -1);
347     context = g_quark_try_string(contextstr);
348     if (!context) {
349         g_warning("invalid context '%s'", contextstr);
350         g_free(contextstr);
351         return FALSE;
352     }
353     g_free(contextstr);
354
355     for (it = g_datalist_id_get_data(&bound_contexts, context);
356          it != NULL; it = it->next){
357         b = it->data;
358         if (b->state == state && b->button == button) {
359             /* already bound */
360             if (b->action[mact] != NULL) {
361                 g_warning("duplicate binding");
362                 return FALSE;
363             }
364             b->action[mact] = action;
365             return TRUE;
366         }
367     }
368
369     grab_all_clients(FALSE);
370
371     /* add the binding */
372     b = g_new0(MouseBinding, 1);
373     b->state = state;
374     b->button = button;
375     b->action[mact] = action;
376     g_datalist_id_set_data(&bound_contexts, context, 
377         g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
378
379     grab_all_clients(TRUE);
380
381     return TRUE;
382 }
383
384 void plugin_startup()
385 {
386     dispatch_register(Event_Client_Mapped | Event_Client_Destroy |
387                       Event_X_ButtonPress | Event_X_ButtonRelease |
388                       Event_X_MotionNotify, (EventHandler)event, NULL);
389
390     mouserc_parse();
391 }
392
393 void plugin_shutdown()
394 {
395     dispatch_register(0, (EventHandler)event, NULL);
396
397     grab_all_clients(FALSE);
398     g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
399 }