]> icculus.org git repositories - dana/openbox.git/blob - plugins/mouse/mouse.c
take borders into account for figuring clicks
[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 static void fire_motion(MouseAction a, GQuark context, Client *c, guint state,
118                         guint button, int cx, int cy, int cw, int ch,
119                         int dx, int dy, gboolean final, Corner corner)
120 {
121     GSList *it;
122     MouseBinding *b;
123
124     for (it = g_datalist_id_get_data(&bound_contexts, context);
125          it != NULL; it = it->next) {
126         b = it->data;
127         if (b->state == state && b->button == button)
128                 break;
129     }
130     /* if not bound, then nothing to do! */
131     if (it == NULL) return;
132
133     if (b->action[a] != NULL && b->action[a]->func != NULL) {
134         b->action[a]->data.any.c = c;
135
136         if (b->action[a]->func == action_move) {
137             b->action[a]->data.move.x = cx + dx;
138             b->action[a]->data.move.y = cy + dy;
139             b->action[a]->data.move.final = final;
140         } else if (b->action[a]->func == action_resize) {
141             b->action[a]->data.resize.corner = corner;
142             switch (corner) {
143             case Corner_TopLeft:
144                 b->action[a]->data.resize.x = cw + dx;
145                 b->action[a]->data.resize.y = ch + dy;
146                 break;
147             case Corner_TopRight:
148                 b->action[a]->data.resize.x = cw - dx;
149                 b->action[a]->data.resize.y = ch + dy;
150                 break;
151             case Corner_BottomLeft:
152                 b->action[a]->data.resize.x = cw + dx;
153                 b->action[a]->data.resize.y = ch - dy;
154                 break;
155             case Corner_BottomRight:
156                 b->action[a]->data.resize.x = cw - dx;
157                 b->action[a]->data.resize.y = ch - dy;
158                 break;
159             }
160             b->action[a]->data.resize.final = final;
161         }
162         b->action[a]->func(&b->action[a]->data);
163     }
164 }
165
166 static Corner pick_corner(int x, int y, int cx, int cy, int cw, int ch)
167 {
168     if (x - cx < cw / 2) {
169         if (y - cy < ch / 2)
170             return Corner_BottomRight;
171         else
172             return Corner_TopRight;
173     } else {
174         if (y - cy < ch / 2)
175             return Corner_BottomLeft;
176         else
177             return Corner_TopLeft;
178     }
179 }
180
181 static void event(ObEvent *e, void *foo)
182 {
183     static Time ltime;
184     static int px, py, cx, cy, cw, ch, dx, dy;
185     static guint button = 0, lbutton = 0;
186     static gboolean drag = FALSE;
187     static Corner corner = Corner_TopLeft;
188     gboolean click = FALSE;
189     gboolean dclick = FALSE;
190     GQuark context;
191
192     switch (e->type) {
193     case Event_Client_Mapped:
194         grab_for_client(e->data.c.client, TRUE);
195         break;
196
197     case Event_Client_Destroy:
198         grab_for_client(e->data.c.client, FALSE);
199         break;
200
201     case Event_X_ButtonPress:
202         if (!button) {
203             if (e->data.x.client != NULL) {
204                 cx = e->data.x.client->frame->area.x;
205                 cy = e->data.x.client->frame->area.y;
206                 cw = e->data.x.client->frame->area.width;
207                 ch = e->data.x.client->frame->area.height;
208                 px = e->data.x.e->xbutton.x_root;
209                 py = e->data.x.e->xbutton.y_root;
210                 corner = pick_corner(px, py, cx, cy, cw, ch);
211             }
212             button = e->data.x.e->xbutton.button;
213         }
214         context = engine_get_context(e->data.x.client,
215                                      e->data.x.e->xbutton.window);
216         g_message("press %d frame %d client %d",
217                   context, 
218                   g_quark_try_string("frame"),
219                   g_quark_try_string("client"));
220
221         fire_button(MouseAction_Press, context,
222                     e->data.x.client, e->data.x.e->xbutton.state,
223                     e->data.x.e->xbutton.button);
224
225         if (context == g_quark_try_string("client")) {
226             /* Replay the event, so it goes to the client*/
227             XAllowEvents(ob_display, ReplayPointer, event_lasttime);
228             /* Fall through to the release case! */
229         } else
230             break;
231
232     case Event_X_ButtonRelease:
233         context = engine_get_context(e->data.x.client,
234                                      e->data.x.e->xbutton.window);
235         g_message("release %d frame %d client %d",
236                   context, 
237                   g_quark_try_string("frame"),
238                   g_quark_try_string("client"));
239         if (e->data.x.e->xbutton.button == button) {
240             /* end drags */
241             if (drag) {
242                 fire_motion(MouseAction_Motion, context,
243                             e->data.x.client, e->data.x.e->xbutton.state,
244                             e->data.x.e->xbutton.button,
245                             cx, cy, cw, ch, dx, dy, TRUE, corner);
246                 drag = FALSE;
247                 
248                 lbutton = 0;
249             } else {
250                 /* clicks are only valid if its released over the window */
251                 int junk;
252                 Window wjunk;
253                 guint ujunk, b, w, h;
254                 XGetGeometry(ob_display, e->data.x.e->xbutton.window,
255                              &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
256                 if (e->data.x.e->xbutton.x >= (signed)-b &&
257                     e->data.x.e->xbutton.y >= (signed)-b &&
258                     e->data.x.e->xbutton.x < (signed)(w+b) &&
259                     e->data.x.e->xbutton.y < (signed)(h+b)) {
260                     click =TRUE;
261                     /* double clicks happen if there were 2 in a row! */
262                     if (lbutton == button &&
263                         e->data.x.e->xbutton.time - 300 <= ltime)
264                         dclick = TRUE;
265                 } else
266                     lbutton = 0;
267             }
268
269             button = 0;
270             ltime = e->data.x.e->xbutton.time;
271         }
272         fire_button(MouseAction_Press, 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 (ABS(dx) >= drag_threshold || ABS(dy) >= drag_threshold)
290                 drag = TRUE;
291             if (drag) {
292                 context = engine_get_context(e->data.x.client,
293                                              e->data.x.e->xbutton.window);
294                 fire_motion(MouseAction_Motion, context,
295                             e->data.x.client, e->data.x.e->xmotion.state,
296                             button, cx, cy, cw, ch, dx, dy, FALSE, corner);
297             }
298         }
299         break;
300
301     default:
302         g_assert_not_reached();
303     }
304 }
305
306 gboolean mbind(char *buttonstr, char *contextstr, MouseAction mact,
307                Action *action)
308 {
309     guint state, button;
310     GQuark context;
311     MouseBinding *b;
312     GSList *it;
313
314     if (!translate_button(buttonstr, &state, &button)) {
315         g_warning("invalid button '%s'", buttonstr);
316         return FALSE;
317     }
318
319     contextstr = g_ascii_strdown(contextstr, -1);
320     context = g_quark_try_string(contextstr);
321     if (!context) {
322         g_warning("invalid context '%s'", contextstr);
323         g_free(contextstr);
324         return FALSE;
325     }
326     g_free(contextstr);
327
328     for (it = g_datalist_id_get_data(&bound_contexts, context);
329          it != NULL; it = it->next){
330         b = it->data;
331         if (b->state == state && b->button == button) {
332             /* already bound */
333             if (b->action[mact] != NULL) {
334                 g_warning("duplicate binding");
335                 return FALSE;
336             }
337             b->action[mact] = action;
338             return TRUE;
339         }
340     }
341
342     grab_all_clients(FALSE);
343
344     /* add the binding */
345     b = g_new0(MouseBinding, 1);
346     b->state = state;
347     b->button = button;
348     b->action[mact] = action;
349     g_datalist_id_set_data(&bound_contexts, context, 
350         g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
351
352     grab_all_clients(TRUE);
353
354     return TRUE;
355 }
356
357 void plugin_startup()
358 {
359     dispatch_register(Event_Client_Mapped | Event_Client_Destroy |
360                       Event_X_ButtonPress | Event_X_ButtonRelease |
361                       Event_X_MotionNotify, (EventHandler)event, NULL);
362
363     mouserc_parse();
364 }
365
366 void plugin_shutdown()
367 {
368     dispatch_register(0, (EventHandler)event, NULL);
369
370     grab_all_clients(FALSE);
371     g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
372 }