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