add the Shade context to the comments
[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    Returns True or False for if a binding existed for the action or not.
118 */
119 static gboolean fire_motion(MouseAction a, GQuark context, Client *c,
120                             guint state, guint button, int cx, int cy,
121                             int cw, int ch, int dx, int dy,
122                             gboolean final, Corner corner)
123 {
124     GSList *it;
125     MouseBinding *b;
126
127     for (it = g_datalist_id_get_data(&bound_contexts, context);
128          it != NULL; it = it->next) {
129         b = it->data;
130         if (b->state == state && b->button == button)
131                 break;
132     }
133     /* if not bound, then nothing to do! */
134     if (it == NULL) return FALSE;
135
136     if (b->action[a] != NULL && b->action[a]->func != NULL) {
137         b->action[a]->data.any.c = c;
138
139         if (b->action[a]->func == action_move) {
140             b->action[a]->data.move.x = cx + dx;
141             b->action[a]->data.move.y = cy + dy;
142             b->action[a]->data.move.final = final;
143         } else if (b->action[a]->func == action_resize) {
144             b->action[a]->data.resize.corner = corner;
145             switch (corner) {
146             case Corner_TopLeft:
147                 b->action[a]->data.resize.x = cw + dx;
148                 b->action[a]->data.resize.y = ch + dy;
149                 break;
150             case Corner_TopRight:
151                 b->action[a]->data.resize.x = cw - dx;
152                 b->action[a]->data.resize.y = ch + dy;
153                 break;
154             case Corner_BottomLeft:
155                 b->action[a]->data.resize.x = cw + dx;
156                 b->action[a]->data.resize.y = ch - dy;
157                 break;
158             case Corner_BottomRight:
159                 b->action[a]->data.resize.x = cw - dx;
160                 b->action[a]->data.resize.y = ch - dy;
161                 break;
162             }
163             b->action[a]->data.resize.final = final;
164         }
165         b->action[a]->func(&b->action[a]->data);
166         return TRUE;
167     }
168     return FALSE;
169 }
170
171 static Corner pick_corner(int x, int y, int cx, int cy, int cw, int ch)
172 {
173     if (x - cx < cw / 2) {
174         if (y - cy < ch / 2)
175             return Corner_BottomRight;
176         else
177             return Corner_TopRight;
178     } else {
179         if (y - cy < ch / 2)
180             return Corner_BottomLeft;
181         else
182             return Corner_TopLeft;
183     }
184 }
185
186 static void event(ObEvent *e, void *foo)
187 {
188     static Time ltime;
189     static int px, py, cx, cy, cw, ch, dx, dy;
190     static guint button = 0, lbutton = 0;
191     static gboolean drag = FALSE, drag_used = FALSE;
192     static Corner corner = Corner_TopLeft;
193     gboolean click = FALSE;
194     gboolean dclick = FALSE;
195     GQuark context;
196
197     switch (e->type) {
198     case Event_Client_Mapped:
199         grab_for_client(e->data.c.client, TRUE);
200         break;
201
202     case Event_Client_Destroy:
203         grab_for_client(e->data.c.client, FALSE);
204         break;
205
206     case Event_X_ButtonPress:
207         if (!button) {
208             if (e->data.x.client != NULL) {
209                 cx = e->data.x.client->frame->area.x;
210                 cy = e->data.x.client->frame->area.y;
211                 /* use the client size because the frame can be differently
212                    sized (shaded windows) and we want this based on the clients
213                    size */
214                 cw = e->data.x.client->area.width + 
215                     e->data.x.client->frame->size.left +
216                     e->data.x.client->frame->size.right;
217                 ch = e->data.x.client->area.height +
218                     e->data.x.client->frame->size.top +
219                     e->data.x.client->frame->size.bottom;
220                 px = e->data.x.e->xbutton.x_root;
221                 py = e->data.x.e->xbutton.y_root;
222                 corner = pick_corner(px, py, cx, cy, cw, ch);
223             }
224             button = e->data.x.e->xbutton.button;
225         }
226         context = engine_get_context(e->data.x.client,
227                                      e->data.x.e->xbutton.window);
228
229         fire_button(MouseAction_Press, context,
230                     e->data.x.client, e->data.x.e->xbutton.state,
231                     e->data.x.e->xbutton.button);
232
233         if (context == g_quark_try_string("client")) {
234             /* Replay the event, so it goes to the client*/
235             XAllowEvents(ob_display, ReplayPointer, event_lasttime);
236             /* Fall through to the release case! */
237         } else
238             break;
239
240     case Event_X_ButtonRelease:
241         context = engine_get_context(e->data.x.client,
242                                      e->data.x.e->xbutton.window);
243         if (e->data.x.e->xbutton.button == button) {
244             /* end drags */
245             if (drag_used) {
246                 fire_motion(MouseAction_Motion, context,
247                             e->data.x.client, e->data.x.e->xbutton.state,
248                             e->data.x.e->xbutton.button,
249                             cx, cy, cw, ch, dx, dy, TRUE, corner);
250                 drag = drag_used = FALSE;
251                 
252                 lbutton = 0;
253             } else {
254                 /* clicks are only valid if its released over the window */
255                 int junk;
256                 Window wjunk;
257                 guint ujunk, b, w, h;
258                 XGetGeometry(ob_display, e->data.x.e->xbutton.window,
259                              &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
260                 if (e->data.x.e->xbutton.x >= (signed)-b &&
261                     e->data.x.e->xbutton.y >= (signed)-b &&
262                     e->data.x.e->xbutton.x < (signed)(w+b) &&
263                     e->data.x.e->xbutton.y < (signed)(h+b)) {
264                     click = TRUE;
265                     /* double clicks happen if there were 2 in a row! */
266                     if (lbutton == button &&
267                         e->data.x.e->xbutton.time - 300 <= ltime) {
268                         dclick = TRUE;
269                         lbutton = 0;
270                     } else
271                         lbutton = button;
272                 } else
273                     lbutton = 0;
274             }
275
276             button = 0;
277             ltime = e->data.x.e->xbutton.time;
278         }
279         fire_button(MouseAction_Release, context,
280                     e->data.x.client, e->data.x.e->xbutton.state,
281                     e->data.x.e->xbutton.button);
282         if (click)
283             fire_button(MouseAction_Click, context,
284                         e->data.x.client, e->data.x.e->xbutton.state,
285                         e->data.x.e->xbutton.button);
286         if (dclick)
287             fire_button(MouseAction_DClick, context,
288                         e->data.x.client, e->data.x.e->xbutton.state,
289                         e->data.x.e->xbutton.button);
290         break;
291
292     case Event_X_MotionNotify:
293         if (button) {
294             dx = e->data.x.e->xmotion.x_root - px;
295             dy = e->data.x.e->xmotion.y_root - py;
296             if (!drag &&
297                 (ABS(dx) >= drag_threshold || ABS(dy) >= drag_threshold))
298                 drag = TRUE;
299             if (drag) {
300                 context = engine_get_context(e->data.x.client,
301                                              e->data.x.e->xbutton.window);
302                 drag_used = fire_motion(MouseAction_Motion, context,
303                                         e->data.x.client,
304                                         e->data.x.e->xmotion.state,
305                                         button, cx, cy, cw, ch, dx, dy,
306                                         FALSE, corner);
307             }
308         }
309         break;
310
311     default:
312         g_assert_not_reached();
313     }
314 }
315
316 gboolean mbind(char *buttonstr, char *contextstr, MouseAction mact,
317                Action *action)
318 {
319     guint state, button;
320     GQuark context;
321     MouseBinding *b;
322     GSList *it;
323
324     if (!translate_button(buttonstr, &state, &button)) {
325         g_warning("invalid button '%s'", buttonstr);
326         return FALSE;
327     }
328
329     contextstr = g_ascii_strdown(contextstr, -1);
330     context = g_quark_try_string(contextstr);
331     if (!context) {
332         g_warning("invalid context '%s'", contextstr);
333         g_free(contextstr);
334         return FALSE;
335     }
336     g_free(contextstr);
337
338     for (it = g_datalist_id_get_data(&bound_contexts, context);
339          it != NULL; it = it->next){
340         b = it->data;
341         if (b->state == state && b->button == button) {
342             /* already bound */
343             if (b->action[mact] != NULL) {
344                 g_warning("duplicate binding");
345                 return FALSE;
346             }
347             b->action[mact] = action;
348             return TRUE;
349         }
350     }
351
352     grab_all_clients(FALSE);
353
354     /* add the binding */
355     b = g_new0(MouseBinding, 1);
356     b->state = state;
357     b->button = button;
358     b->action[mact] = action;
359     g_datalist_id_set_data(&bound_contexts, context, 
360         g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
361
362     grab_all_clients(TRUE);
363
364     return TRUE;
365 }
366
367 void plugin_startup()
368 {
369     dispatch_register(Event_Client_Mapped | Event_Client_Destroy |
370                       Event_X_ButtonPress | Event_X_ButtonRelease |
371                       Event_X_MotionNotify, (EventHandler)event, NULL);
372
373     mouserc_parse();
374 }
375
376 void plugin_shutdown()
377 {
378     dispatch_register(0, (EventHandler)event, NULL);
379
380     grab_all_clients(FALSE);
381     g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
382 }