]> git.sur5r.net Git - i3/i3/blob - src/click.c
Fix dragging floating containers / click handling
[i3/i3] / src / click.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009-2010 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  * src/click.c: Contains the handlers for button press (mouse click) events
11  *              because they are quite large.
12  *
13  */
14 #include <time.h>
15 #include <math.h>
16
17 #include <xcb/xcb_atom.h>
18 #include <xcb/xcb_icccm.h>
19
20 #include <X11/XKBlib.h>
21
22 #include "all.h"
23
24 #if 0
25 static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
26         struct Stack_Window *current;
27
28         SLIST_FOREACH(current, &stack_wins, stack_windows) {
29                 if (current->window != window_id)
30                         continue;
31
32                 return current;
33         }
34
35         return NULL;
36 }
37
38 /*
39  * Checks if the button press was on a stack window, handles focus setting and returns true
40  * if so, or false otherwise.
41  *
42  */
43 static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) {
44         struct Stack_Window *stack_win;
45
46         /* If we find a corresponding stack window, we can handle the event */
47         if ((stack_win = get_stack_window(event->event)) == NULL)
48                 return false;
49
50         /* A stack window was clicked, we check if it was button4 or button5
51            which are scroll up / scroll down. */
52         if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
53                 direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN);
54                 focus_window_in_container(conn, CUR_CELL, direction);
55                 return true;
56         }
57
58         /* It was no scrolling, so we calculate the destination client by
59            dividing the Y position of the event through the height of a window
60            decoration and then set the focus to this client. */
61         i3Font *font = load_font(conn, config.font);
62         int decoration_height = (font->height + 2 + 2);
63         int destination = (event->event_y / decoration_height),
64             c = 0,
65             num_clients = 0;
66         Client *client;
67         Container *container = stack_win->container;
68
69         CIRCLEQ_FOREACH(client, &(container->clients), clients)
70                 num_clients++;
71
72         /* If we don’t have any clients in this container, we cannot do
73          * anything useful anyways. */
74         if (num_clients == 0)
75                 return true;
76
77         if (container->mode == MODE_TABBED)
78                 destination = (event->event_x / (container->width / num_clients));
79         else if (container->mode == MODE_STACK &&
80                  container->stack_limit != STACK_LIMIT_NONE) {
81                 if (container->stack_limit == STACK_LIMIT_COLS) {
82                         int wrap = ceil((float)num_clients / container->stack_limit_value);
83                         int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value));
84                         int clicked_row = (event->event_y / decoration_height);
85                         DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
86                         destination = (wrap * clicked_column) + clicked_row;
87                 } else {
88                         int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
89                         int clicked_column = (event->event_x / width);
90                         int clicked_row = (event->event_y / decoration_height);
91                         DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
92                         destination = (container->stack_limit_value * clicked_column) + clicked_row;
93                 }
94         }
95
96         DLOG("Click on stack_win for client %d\n", destination);
97         CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
98                 if (c++ == destination) {
99                         set_focus(conn, client, true);
100                         return true;
101                 }
102
103         return true;
104 }
105
106 /*
107  * Checks if the button press was on a bar, switches to the workspace and returns true
108  * if so, or false otherwise.
109  *
110  */
111 static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
112         Output *output;
113         TAILQ_FOREACH(output, &outputs, outputs) {
114                 if (output->bar != event->event)
115                         continue;
116
117                 DLOG("Click on a bar\n");
118
119                 /* Check if the button was one of button4 or button5 (scroll up / scroll down) */
120                 if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
121                         Workspace *ws = c_ws;
122                         if (event->detail == XCB_BUTTON_INDEX_5) {
123                                 while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
124                                         if (ws->output == output) {
125                                                 workspace_show(conn, ws->num + 1);
126                                                 return true;
127                                         }
128                                 }
129                         } else {
130                                 while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
131                                         if (ws->output == output) {
132                                                 workspace_show(conn, ws->num + 1);
133                                                 return true;
134                                         }
135                                 }
136                         }
137                         return true;
138                 }
139                 int drawn = 0;
140                 /* Because workspaces can be on different outputs, we need to loop
141                    through all of them and decide to count it based on its ->output */
142                 Workspace *ws;
143                 TAILQ_FOREACH(ws, workspaces, workspaces) {
144                         if (ws->output != output)
145                                 continue;
146                         DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
147                                         ws->num, drawn, ws->text_width);
148                         if (event->event_x > (drawn + 1) &&
149                             event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) {
150                                 workspace_show(conn, ws->num + 1);
151                                 return true;
152                         }
153
154                         drawn += ws->text_width + 5 + 5 + 2;
155                 }
156                 return true;
157         }
158
159         return false;
160 }
161
162 /*
163  * Called when the user clicks using the floating_modifier, but the client is in
164  * tiling layout.
165  *
166  * Returns false if it does not do anything (that is, the click should be sent
167  * to the client).
168  *
169  */
170 static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
171                                          xcb_button_press_event_t *event) {
172         /* Only the right mouse button is interesting for us at the moment */
173         if (event->detail != 3)
174                 return false;
175
176         /* The client is in tiling layout. We can still
177          * initiate a resize with the right mouse button,
178          * by chosing the border which is the most near one
179          * to the position of the mouse pointer */
180         int to_right = client->rect.width - event->event_x,
181             to_left = event->event_x,
182             to_top = event->event_y,
183             to_bottom = client->rect.height - event->event_y;
184         resize_orientation_t orientation = O_VERTICAL;
185         Container *con = client->container;
186         Workspace *ws = con->workspace;
187         int first = 0, second = 0;
188
189         DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
190                         to_right, to_left, to_top, to_bottom);
191
192         if (to_right < to_left &&
193             to_right < to_top &&
194             to_right < to_bottom) {
195                 /* …right border */
196                 first = con->col + (con->colspan - 1);
197                 DLOG("column %d\n", first);
198
199                 if (!cell_exists(ws, first, con->row) ||
200                     (first == (ws->cols-1)))
201                         return false;
202
203                 second = first + 1;
204         } else if (to_left < to_right &&
205                    to_left < to_top &&
206                    to_left < to_bottom) {
207                 /* …left border */
208                 if (con->col == 0)
209                         return false;
210
211                 first = con->col - 1;
212                 second = con->col;
213         } else if (to_top < to_right &&
214                    to_top < to_left &&
215                    to_top < to_bottom) {
216                 /* This was a press on the top border */
217                 if (con->row == 0)
218                         return false;
219                 first = con->row - 1;
220                 second = con->row;
221                 orientation = O_HORIZONTAL;
222         } else if (to_bottom < to_right &&
223                    to_bottom < to_left &&
224                    to_bottom < to_top) {
225                 /* …bottom border */
226                 first = con->row + (con->rowspan - 1);
227                 if (!cell_exists(ws, con->col, first) ||
228                     (first == (ws->rows-1)))
229                         return false;
230
231                 second = first + 1;
232                 orientation = O_HORIZONTAL;
233         }
234
235        return resize_graphical_handler(conn, ws, first, second, orientation, event);
236 }
237 #endif
238
239 int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
240     /* TODO: dragging floating windows by grabbing their decoration does not
241      * work right now. We need to somehow recognize that special case: either
242      * we check if the con with the clicked decoration is the only con inside
243      * its parent (so that you can only drag single floating windows, not
244      * floating containers with multiple windows) *or* we somehow find out
245      * which decoration(s) are at the top and enable grabbing for them while
246      * resizing for the others. Maybe we could process the resizing parameters
247      * first and check if resizing is possible: if yes, resize, if not, drag.
248      *
249      * Also raise on click on decoration is not working. */
250     Con *con;
251     DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
252
253     con = con_by_window_id(event->event);
254     bool border_click = (con == NULL);
255     const uint32_t mod = config.floating_modifier;
256     bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
257
258     if (border_click)
259         con = con_by_frame_id(event->event);
260
261     DLOG("border_click = %d, mod_pressed = %d\n", border_click, mod_pressed);
262
263     Con *clicked_into = NULL;
264
265     Con *child;
266     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
267         if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
268             continue;
269
270         clicked_into = child;
271         break;
272     }
273
274     DLOG("clicked_into = %p\n", clicked_into);
275
276     /* See if this was a click with the configured modifier. If so, we need
277      * to move around the client if it was floating. if not, we just process
278      * as usual. */
279     DLOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier);
280     if (border_click || mod_pressed) {
281         if (con == NULL) {
282             LOG("Not handling, floating_modifier was pressed and no client found\n");
283             return 1;
284         }
285         DLOG("handling\n");
286 #if 0
287         if (con->fullscreen) {
288                 LOG("Not handling, client is in fullscreen mode\n");
289                 return 1;
290         }
291 #endif
292         Con *floatingcon = con;
293         if ((border_click && con->type == CT_FLOATING_CON) ||
294             ((floatingcon = con_inside_floating(con)) != NULL &&
295              clicked_into == NULL)) {
296             LOG("button %d pressed\n", event->detail);
297             if (event->detail == 1) {
298                 LOG("left mouse button, dragging\n");
299                 floating_drag_window(floatingcon, event);
300             }
301             else if (event->detail == 3) {
302                 bool proportional = (event->state & BIND_SHIFT);
303                 DLOG("right mouse button\n");
304                 floating_resize_window(floatingcon, proportional, event);
305             }
306             return 1;
307         }
308     }
309
310     /* click to focus, either on the clicked window or its child if thas was a
311      * click into a child decoration */
312     con_focus((clicked_into ? clicked_into : con));
313
314     /* for floating containers, we also want to raise them on click */
315     Con *floatingcon = con_inside_floating(con);
316     if (floatingcon != NULL)
317         floating_raise_con(floatingcon);
318
319     tree_render();
320
321     /* if we clicked into a child decoration on a stacked/tabbed container, we
322      * are done and don’t want to resize */
323     if (clicked_into &&
324         (con->layout == L_STACKED || con->layout == L_TABBED))
325         return 1;
326
327     /* check if this was a click on the window border (and on which one) */
328     Rect bsr = con_border_style_rect(con);
329     DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n",
330             event->event_x, event->event_y, con, event->event, border_click, clicked_into);
331     DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
332     /* TODO: das problem ist, dass TAILQ_PREV etc. nicht die orientation beachtet. */
333     Con *first = NULL, *second = NULL;
334     if (clicked_into) {
335         DLOG("BORDER top\n");
336         second = clicked_into;
337         if ((first = con_get_next(clicked_into, 'p', VERT)) != NULL)
338             resize_graphical_handler(first, second, VERT, event);
339     } else if (event->event_x >= 0 && event->event_x <= bsr.x &&
340         event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) {
341         DLOG("BORDER left\n");
342         second = con;
343         if ((first = con_get_next(con, 'p', HORIZ)) != NULL)
344             resize_graphical_handler(first, second, HORIZ, event);
345     } else if (event->event_x >= (con->window_rect.x + con->window_rect.width) &&
346         event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) {
347         DLOG("BORDER right\n");
348         first = con;
349         if ((second = con_get_next(con, 'n', HORIZ)) != NULL)
350             resize_graphical_handler(first, second, HORIZ, event);
351     } else if (event->event_y >= (con->window_rect.y + con->window_rect.height)) {
352         DLOG("BORDER bottom\n");
353
354         first = con;
355         if ((second = con_get_next(con, 'n', VERT)) != NULL)
356             resize_graphical_handler(first, second, VERT, event);
357     } else {
358         /* Set first and second to NULL to trigger a replay of the event */
359         first = second = NULL;
360     }
361
362     if (first == NULL || second == NULL) {
363         DLOG("Replaying click\n");
364         /* No border click, replay the click */
365         xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
366         xcb_flush(conn);
367         return 0;
368     }
369
370     DLOG("After resize handler, rendering\n");
371     tree_render();
372
373     return 0;
374 #if 0
375         if (client == NULL) {
376                 /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */
377                 if (button_press_stackwin(conn, event))
378                         return 1;
379
380                 /* Or on a bar? */
381                 if (button_press_bar(conn, event))
382                         return 1;
383
384                 DLOG("Could not handle this button press\n");
385                 return 1;
386         }
387
388         /* Set focus in any case */
389         set_focus(conn, client, true);
390
391         /* Let’s see if this was on the borders (= resize). If not, we’re done */
392         DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
393         resize_orientation_t orientation = O_VERTICAL;
394         Container *con = client->container;
395         int first, second;
396
397         if (client->dock) {
398                 DLOG("dock. done.\n");
399                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
400                 xcb_flush(conn);
401                 return 1;
402         }
403
404         DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
405
406         /* Some clients (xfontsel for example) seem to pass clicks on their
407          * window to the parent window, thus we receive an event here which in
408          * reality is a border_click. Check for the position and fix state. */
409         if (border_click &&
410             event->event_x >= client->child_rect.x &&
411             event->event_x <= (client->child_rect.x + client->child_rect.width) &&
412             event->event_y >= client->child_rect.y &&
413             event->event_y <= (client->child_rect.y + client->child_rect.height)) {
414                 DLOG("Fixing border_click = false because of click in child\n");
415                 border_click = false;
416         }
417
418         if (!border_click) {
419                 DLOG("client. done.\n");
420                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
421                 /* Floating clients should be raised on click */
422                 if (client_is_floating(client))
423                         xcb_raise_window(conn, client->frame);
424                 xcb_flush(conn);
425                 return 1;
426         }
427
428         /* Don’t handle events inside the titlebar, only borders are interesting */
429         i3Font *font = load_font(conn, config.font);
430         if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
431                 DLOG("click on titlebar\n");
432
433                 /* Floating clients can be dragged by grabbing their titlebar */
434                 if (client_is_floating(client)) {
435                         /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
436                         xcb_raise_window(conn, client->frame);
437                         xcb_flush(conn);
438
439                         floating_drag_window(conn, client, event);
440                 }
441                 return 1;
442         }
443
444         if (client_is_floating(client))
445                 return floating_border_click(conn, client, event);
446
447         Workspace *ws = con->workspace;
448
449         if (event->event_y < 2) {
450                 /* This was a press on the top border */
451                 if (con->row == 0)
452                         return 1;
453                 first = con->row - 1;
454                 second = con->row;
455                 orientation = O_HORIZONTAL;
456         } else if (event->event_y >= (client->rect.height - 2)) {
457                 /* …bottom border */
458                 first = con->row + (con->rowspan - 1);
459                 if (!cell_exists(ws, con->col, first) ||
460                     (first == (ws->rows-1)))
461                         return 1;
462
463                 second = first + 1;
464                 orientation = O_HORIZONTAL;
465         } else if (event->event_x <= 2) {
466                 /* …left border */
467                 if (con->col == 0)
468                         return 1;
469
470                 first = con->col - 1;
471                 second = con->col;
472         } else if (event->event_x > 2) {
473                 /* …right border */
474                 first = con->col + (con->colspan - 1);
475                 DLOG("column %d\n", first);
476
477                 if (!cell_exists(ws, first, con->row) ||
478                     (first == (ws->cols-1)))
479                         return 1;
480
481                 second = first + 1;
482         }
483
484         return resize_graphical_handler(conn, ws, first, second, orientation, event);
485 #endif
486 }