focus on middle click on client
[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/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                 if (e->data.x.e->xbutton.x >= 0 &&
244                     e->data.x.e->xbutton.y >= 0) {
245                     int junk;
246                     Window wjunk;
247                     guint ujunk, w, h;
248                     XGetGeometry(ob_display, e->data.x.e->xbutton.window,
249                                  &wjunk, &junk, &junk, &w, &h, &ujunk, &ujunk);
250                     if (e->data.x.e->xbutton.x < (signed)w &&
251                         e->data.x.e->xbutton.y < (signed)h) {
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                     }
258                     lbutton = button;
259                 } else
260                     lbutton = 0;
261             }
262
263             button = 0;
264             ltime = e->data.x.e->xbutton.time;
265         }
266         fire_button(MouseAction_Press, context,
267                     e->data.x.client, e->data.x.e->xbutton.state,
268                     e->data.x.e->xbutton.button);
269         if (click)
270             fire_button(MouseAction_Click, context,
271                         e->data.x.client, e->data.x.e->xbutton.state,
272                         e->data.x.e->xbutton.button);
273         if (dclick)
274             fire_button(MouseAction_DClick, context,
275                         e->data.x.client, e->data.x.e->xbutton.state,
276                         e->data.x.e->xbutton.button);
277         break;
278
279     case Event_X_MotionNotify:
280         if (button) {
281             dx = e->data.x.e->xmotion.x_root - px;
282             dy = e->data.x.e->xmotion.y_root - py;
283             if (ABS(dx) >= drag_threshold || ABS(dy) >= drag_threshold)
284                 drag = TRUE;
285             if (drag) {
286                 context = engine_get_context(e->data.x.client,
287                                              e->data.x.e->xbutton.window);
288                 fire_motion(MouseAction_Motion, context,
289                             e->data.x.client, e->data.x.e->xmotion.state,
290                             button, cx, cy, cw, ch, dx, dy, FALSE, corner);
291             }
292         }
293         break;
294
295     default:
296         g_assert_not_reached();
297     }
298 }
299
300 gboolean mbind(char *buttonstr, char *contextstr, MouseAction mact,
301                Action *action)
302 {
303     guint state, button;
304     GQuark context;
305     MouseBinding *b;
306     GSList *it;
307
308     if (!translate_button(buttonstr, &state, &button)) {
309         g_warning("invalid button '%s'", buttonstr);
310         return FALSE;
311     }
312
313     contextstr = g_ascii_strdown(contextstr, -1);
314     context = g_quark_try_string(contextstr);
315     if (!context) {
316         g_warning("invalid context '%s'", contextstr);
317         g_free(contextstr);
318         return FALSE;
319     }
320     g_free(contextstr);
321
322     for (it = g_datalist_id_get_data(&bound_contexts, context);
323          it != NULL; it = it->next){
324         b = it->data;
325         if (b->state == state && b->button == button) {
326             /* already bound */
327             if (b->action[mact] != NULL) {
328                 g_warning("duplicate binding");
329                 return FALSE;
330             }
331             b->action[mact] = action;
332             return TRUE;
333         }
334     }
335
336     grab_all_clients(FALSE);
337
338     /* add the binding */
339     b = g_new0(MouseBinding, 1);
340     b->state = state;
341     b->button = button;
342     b->action[mact] = action;
343     g_datalist_id_set_data(&bound_contexts, context, 
344         g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
345
346     grab_all_clients(TRUE);
347
348     return TRUE;
349 }
350
351 void plugin_startup()
352 {
353     dispatch_register(Event_Client_Mapped | Event_Client_Destroy |
354                       Event_X_ButtonPress | Event_X_ButtonRelease |
355                       Event_X_MotionNotify, (EventHandler)event, NULL);
356
357     mouserc_parse();
358 }
359
360 void plugin_shutdown()
361 {
362     dispatch_register(0, (EventHandler)event, NULL);
363
364     grab_all_clients(FALSE);
365     g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
366 }