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