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