set lbutton from 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
217         fire_button(MouseAction_Press, context,
218                     e->data.x.client, e->data.x.e->xbutton.state,
219                     e->data.x.e->xbutton.button);
220
221         if (context == g_quark_try_string("client")) {
222             /* Replay the event, so it goes to the client*/
223             XAllowEvents(ob_display, ReplayPointer, event_lasttime);
224             /* Fall through to the release case! */
225         } else
226             break;
227
228     case Event_X_ButtonRelease:
229         context = engine_get_context(e->data.x.client,
230                                      e->data.x.e->xbutton.window);
231         if (e->data.x.e->xbutton.button == button) {
232             /* end drags */
233             if (drag) {
234                 fire_motion(MouseAction_Motion, context,
235                             e->data.x.client, e->data.x.e->xbutton.state,
236                             e->data.x.e->xbutton.button,
237                             cx, cy, cw, ch, dx, dy, TRUE, corner);
238                 drag = FALSE;
239                 
240                 lbutton = 0;
241             } else {
242                 /* clicks are only valid if its released over the window */
243                 int junk;
244                 Window wjunk;
245                 guint ujunk, b, w, h;
246                 XGetGeometry(ob_display, e->data.x.e->xbutton.window,
247                              &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
248                 if (e->data.x.e->xbutton.x >= (signed)-b &&
249                     e->data.x.e->xbutton.y >= (signed)-b &&
250                     e->data.x.e->xbutton.x < (signed)(w+b) &&
251                     e->data.x.e->xbutton.y < (signed)(h+b)) {
252                     click =TRUE;
253                     /* double clicks happen if there were 2 in a row! */
254                     if (lbutton == button &&
255                         e->data.x.e->xbutton.time - 300 <= ltime) {
256                         dclick = TRUE;
257                         lbutton = 0;
258                     } else
259                         lbutton = button;
260                 } else
261                     lbutton = 0;
262             }
263
264             button = 0;
265             ltime = e->data.x.e->xbutton.time;
266         }
267         fire_button(MouseAction_Press, context,
268                     e->data.x.client, e->data.x.e->xbutton.state,
269                     e->data.x.e->xbutton.button);
270         if (click)
271             fire_button(MouseAction_Click, context,
272                         e->data.x.client, e->data.x.e->xbutton.state,
273                         e->data.x.e->xbutton.button);
274         if (dclick)
275             fire_button(MouseAction_DClick, context,
276                         e->data.x.client, e->data.x.e->xbutton.state,
277                         e->data.x.e->xbutton.button);
278         break;
279
280     case Event_X_MotionNotify:
281         if (button) {
282             dx = e->data.x.e->xmotion.x_root - px;
283             dy = e->data.x.e->xmotion.y_root - py;
284             if (ABS(dx) >= drag_threshold || ABS(dy) >= drag_threshold)
285                 drag = TRUE;
286             if (drag) {
287                 context = engine_get_context(e->data.x.client,
288                                              e->data.x.e->xbutton.window);
289                 fire_motion(MouseAction_Motion, context,
290                             e->data.x.client, e->data.x.e->xmotion.state,
291                             button, cx, cy, cw, ch, dx, dy, FALSE, corner);
292             }
293         }
294         break;
295
296     default:
297         g_assert_not_reached();
298     }
299 }
300
301 gboolean mbind(char *buttonstr, char *contextstr, MouseAction mact,
302                Action *action)
303 {
304     guint state, button;
305     GQuark context;
306     MouseBinding *b;
307     GSList *it;
308
309     if (!translate_button(buttonstr, &state, &button)) {
310         g_warning("invalid button '%s'", buttonstr);
311         return FALSE;
312     }
313
314     contextstr = g_ascii_strdown(contextstr, -1);
315     context = g_quark_try_string(contextstr);
316     if (!context) {
317         g_warning("invalid context '%s'", contextstr);
318         g_free(contextstr);
319         return FALSE;
320     }
321     g_free(contextstr);
322
323     for (it = g_datalist_id_get_data(&bound_contexts, context);
324          it != NULL; it = it->next){
325         b = it->data;
326         if (b->state == state && b->button == button) {
327             /* already bound */
328             if (b->action[mact] != NULL) {
329                 g_warning("duplicate binding");
330                 return FALSE;
331             }
332             b->action[mact] = action;
333             return TRUE;
334         }
335     }
336
337     grab_all_clients(FALSE);
338
339     /* add the binding */
340     b = g_new0(MouseBinding, 1);
341     b->state = state;
342     b->button = button;
343     b->action[mact] = action;
344     g_datalist_id_set_data(&bound_contexts, context, 
345         g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
346
347     grab_all_clients(TRUE);
348
349     return TRUE;
350 }
351
352 void plugin_startup()
353 {
354     dispatch_register(Event_Client_Mapped | Event_Client_Destroy |
355                       Event_X_ButtonPress | Event_X_ButtonRelease |
356                       Event_X_MotionNotify, (EventHandler)event, NULL);
357
358     mouserc_parse();
359 }
360
361 void plugin_shutdown()
362 {
363     dispatch_register(0, (EventHandler)event, NULL);
364
365     grab_all_clients(FALSE);
366     g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
367 }