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