]> git.sur5r.net Git - i3/i3/blob - src/click.c
Fix the choice of the right containers for resizing.
[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     char way = '\0';
333     int orientation;
334     if (clicked_into) {
335         DLOG("BORDER top\n");
336         way = 'p';
337         orientation = VERT;
338     } else if (event->event_x >= 0 && event->event_x <= bsr.x &&
339         event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) {
340         DLOG("BORDER left\n");
341         way = 'p';
342         orientation = HORIZ;
343     } else if (event->event_x >= (con->window_rect.x + con->window_rect.width) &&
344         event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) {
345         DLOG("BORDER right\n");
346         way = 'n';
347         orientation = HORIZ;
348     } else if (event->event_y >= (con->window_rect.y + con->window_rect.height)) {
349         DLOG("BORDER bottom\n");
350         way = 'n';
351         orientation = VERT;
352     }
353
354     /* look for a parent container with the right orientation */
355     Con *first = NULL, *second = NULL;
356     if (way != '\0') {
357         Con *resize_con = clicked_into ? clicked_into : con;
358         while (resize_con->type != CT_WORKSPACE &&
359             resize_con->parent->orientation != orientation)
360             resize_con = resize_con->parent;
361         if (resize_con->type != CT_WORKSPACE &&
362             resize_con->parent->orientation == orientation) {
363             first = resize_con;
364             second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
365             if (second == TAILQ_END(&(first->nodes_head))) {
366                 second = NULL;
367             }
368             else if (way == 'p') {
369                 Con *tmp = first;
370                 first = second;
371                 second = tmp;
372             }
373         }
374     }
375
376     if (first == NULL || second == NULL) {
377         DLOG("Replaying click\n");
378         /* No border click, replay the click */
379         xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
380         xcb_flush(conn);
381         return 0;
382     }
383     else {
384         assert(first != second);
385         assert(first->parent == second->parent);
386         resize_graphical_handler(first, second, orientation, event);
387     }
388
389     DLOG("After resize handler, rendering\n");
390     tree_render();
391
392     return 0;
393 #if 0
394         if (client == NULL) {
395                 /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */
396                 if (button_press_stackwin(conn, event))
397                         return 1;
398
399                 /* Or on a bar? */
400                 if (button_press_bar(conn, event))
401                         return 1;
402
403                 DLOG("Could not handle this button press\n");
404                 return 1;
405         }
406
407         /* Set focus in any case */
408         set_focus(conn, client, true);
409
410         /* Let’s see if this was on the borders (= resize). If not, we’re done */
411         DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
412         resize_orientation_t orientation = O_VERTICAL;
413         Container *con = client->container;
414         int first, second;
415
416         if (client->dock) {
417                 DLOG("dock. done.\n");
418                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
419                 xcb_flush(conn);
420                 return 1;
421         }
422
423         DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
424
425         /* Some clients (xfontsel for example) seem to pass clicks on their
426          * window to the parent window, thus we receive an event here which in
427          * reality is a border_click. Check for the position and fix state. */
428         if (border_click &&
429             event->event_x >= client->child_rect.x &&
430             event->event_x <= (client->child_rect.x + client->child_rect.width) &&
431             event->event_y >= client->child_rect.y &&
432             event->event_y <= (client->child_rect.y + client->child_rect.height)) {
433                 DLOG("Fixing border_click = false because of click in child\n");
434                 border_click = false;
435         }
436
437         if (!border_click) {
438                 DLOG("client. done.\n");
439                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
440                 /* Floating clients should be raised on click */
441                 if (client_is_floating(client))
442                         xcb_raise_window(conn, client->frame);
443                 xcb_flush(conn);
444                 return 1;
445         }
446
447         /* Don’t handle events inside the titlebar, only borders are interesting */
448         i3Font *font = load_font(conn, config.font);
449         if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
450                 DLOG("click on titlebar\n");
451
452                 /* Floating clients can be dragged by grabbing their titlebar */
453                 if (client_is_floating(client)) {
454                         /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
455                         xcb_raise_window(conn, client->frame);
456                         xcb_flush(conn);
457
458                         floating_drag_window(conn, client, event);
459                 }
460                 return 1;
461         }
462
463         if (client_is_floating(client))
464                 return floating_border_click(conn, client, event);
465
466         Workspace *ws = con->workspace;
467
468         if (event->event_y < 2) {
469                 /* This was a press on the top border */
470                 if (con->row == 0)
471                         return 1;
472                 first = con->row - 1;
473                 second = con->row;
474                 orientation = O_HORIZONTAL;
475         } else if (event->event_y >= (client->rect.height - 2)) {
476                 /* …bottom border */
477                 first = con->row + (con->rowspan - 1);
478                 if (!cell_exists(ws, con->col, first) ||
479                     (first == (ws->rows-1)))
480                         return 1;
481
482                 second = first + 1;
483                 orientation = O_HORIZONTAL;
484         } else if (event->event_x <= 2) {
485                 /* …left border */
486                 if (con->col == 0)
487                         return 1;
488
489                 first = con->col - 1;
490                 second = con->col;
491         } else if (event->event_x > 2) {
492                 /* …right border */
493                 first = con->col + (con->colspan - 1);
494                 DLOG("column %d\n", first);
495
496                 if (!cell_exists(ws, first, con->row) ||
497                     (first == (ws->cols-1)))
498                         return 1;
499
500                 second = first + 1;
501         }
502
503         return resize_graphical_handler(conn, ws, first, second, orientation, event);
504 #endif
505 }