]> git.sur5r.net Git - i3/i3/blob - src/click.c
Move handler for clicking to its own file
[i3/i3] / src / click.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 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 <stdio.h>
15 #include <assert.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <time.h>
19 #include <stdbool.h>
20
21 #include <xcb/xcb.h>
22 #include <xcb/xcb_atom.h>
23 #include <xcb/xcb_icccm.h>
24
25 #include <X11/XKBlib.h>
26
27 #include "i3.h"
28 #include "queue.h"
29 #include "table.h"
30 #include "config.h"
31 #include "util.h"
32 #include "xcb.h"
33 #include "client.h"
34 #include "workspace.h"
35 #include "commands.h"
36 #include "floating.h"
37 #include "resize.h"
38
39 /*
40  * Checks if the button press was on a stack window, handles focus setting and returns true
41  * if so, or false otherwise.
42  *
43  */
44 static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) {
45         struct Stack_Window *stack_win;
46         SLIST_FOREACH(stack_win, &stack_wins, stack_windows) {
47                 if (stack_win->window != event->event)
48                         continue;
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
68                 CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
69                         num_clients++;
70
71                 if (stack_win->container->mode == MODE_TABBED)
72                         destination = (event->event_x / (stack_win->container->width / num_clients));
73
74                 LOG("Click on stack_win for client %d\n", destination);
75                 CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
76                         if (c++ == destination) {
77                                 set_focus(conn, client, true);
78                                 return true;
79                         }
80
81                 return true;
82         }
83
84         return false;
85 }
86
87 /*
88  * Checks if the button press was on a bar, switches to the workspace and returns true
89  * if so, or false otherwise.
90  *
91  */
92 static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
93         i3Screen *screen;
94         TAILQ_FOREACH(screen, virtual_screens, screens) {
95                 if (screen->bar != event->event)
96                         continue;
97
98                 LOG("Click on a bar\n");
99
100                 /* Check if the button was one of button4 or button5 (scroll up / scroll down) */
101                 if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
102                         int add = (event->detail == XCB_BUTTON_INDEX_4 ? -1 : 1);
103                         for (int i = c_ws->num + add; (i >= 0) && (i < 10); i += add)
104                                 if (workspaces[i].screen == screen) {
105                                         workspace_show(conn, i+1);
106                                         return true;
107                                 }
108                         return true;
109                 }
110                 int drawn = 0;
111                 /* Because workspaces can be on different screens, we need to loop
112                    through all of them and decide to count it based on its ->screen */
113                 for (int i = 0; i < 10; i++) {
114                         if (workspaces[i].screen != screen)
115                                 continue;
116                         LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
117                                         i, drawn, workspaces[i].text_width);
118                         if (event->event_x > (drawn + 1) &&
119                             event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) {
120                                 workspace_show(conn, i+1);
121                                 return true;
122                         }
123
124                         drawn += workspaces[i].text_width + 5 + 5 + 2;
125                 }
126                 return true;
127         }
128
129         return false;
130 }
131
132 int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
133         LOG("Button %d pressed\n", event->state);
134         /* This was either a focus for a client’s parent (= titlebar)… */
135         Client *client = table_get(&by_child, event->event);
136         bool border_click = false;
137         if (client == NULL) {
138                 client = table_get(&by_parent, event->event);
139                 border_click = true;
140         }
141         /* See if this was a click with the configured modifier. If so, we need
142          * to move around the client if it was floating. if not, we just process
143          * as usual. */
144         if (config.floating_modifier != 0 &&
145             (event->state & config.floating_modifier) != 0) {
146                 if (client == NULL) {
147                         LOG("Not handling, floating_modifier was pressed and no client found\n");
148                         return 1;
149                 }
150                 if (client_is_floating(client)) {
151                         LOG("button %d pressed\n", event->detail);
152                         if (event->detail == 1) {
153                                 LOG("left mouse button, dragging\n");
154                                 floating_drag_window(conn, client, event);
155                         } else if (event->detail == 3) {
156                                 LOG("right mouse button\n");
157                                 floating_resize_window(conn, client, event);
158                         }
159                         return 1;
160                 } else {
161                         /* The client is in tiling layout. We can still
162                          * initiate a resize with the right mouse button,
163                          * by chosing the border which is the most near one
164                          * to the position of the mouse pointer */
165                         if (event->detail == 3) {
166                                 int to_right = client->rect.width - event->event_x,
167                                     to_left = event->event_x,
168                                     to_top = event->event_y,
169                                     to_bottom = client->rect.height - event->event_y;
170                                 resize_orientation_t orientation = O_VERTICAL;
171                                 Container *con = client->container;
172                                 int first = 0, second = 0;
173
174                                 LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
175                                                 to_right, to_left, to_top, to_bottom);
176
177                                 if (to_right < to_left &&
178                                     to_right < to_top &&
179                                     to_right < to_bottom) {
180                                         /* …right border */
181                                         first = con->col + (con->colspan - 1);
182                                         LOG("column %d\n", first);
183
184                                         if (!cell_exists(first, con->row) ||
185                                             (first == (con->workspace->cols-1)))
186                                                 return 1;
187
188                                         second = first + 1;
189                                 } else if (to_left < to_right &&
190                                            to_left < to_top &&
191                                            to_left < to_bottom) {
192                                         /* …left border */
193                                         if (con->col == 0)
194                                                 return 1;
195
196                                         first = con->col - 1;
197                                         second = con->col;
198                                 } else if (to_top < to_right &&
199                                            to_top < to_left &&
200                                            to_top < to_bottom) {
201                                         /* This was a press on the top border */
202                                         if (con->row == 0)
203                                                 return 1;
204                                         first = con->row - 1;
205                                         second = con->row;
206                                         orientation = O_HORIZONTAL;
207                                 } else if (to_bottom < to_right &&
208                                            to_bottom < to_left &&
209                                            to_bottom < to_top) {
210                                         /* …bottom border */
211                                         first = con->row + (con->rowspan - 1);
212                                         if (!cell_exists(con->col, first) ||
213                                             (first == (con->workspace->rows-1)))
214                                                 return 1;
215
216                                         second = first + 1;
217                                         orientation = O_HORIZONTAL;
218                                 }
219
220                                return resize_graphical_handler(conn, con->workspace, first, second, orientation, event);
221                         }
222                 }
223         }
224
225         if (client == NULL) {
226                 /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */
227                 if (button_press_stackwin(conn, event))
228                         return 1;
229
230                 /* Or on a bar? */
231                 if (button_press_bar(conn, event))
232                         return 1;
233
234                 LOG("Could not handle this button press\n");
235                 return 1;
236         }
237
238         /* Set focus in any case */
239         set_focus(conn, client, true);
240
241         /* Let’s see if this was on the borders (= resize). If not, we’re done */
242         LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
243         resize_orientation_t orientation = O_VERTICAL;
244         Container *con = client->container;
245         int first, second;
246
247         if (client->dock) {
248                 LOG("dock. done.\n");
249                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
250                 xcb_flush(conn);
251                 return 1;
252         }
253
254         LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
255
256         /* Some clients (xfontsel for example) seem to pass clicks on their
257          * window to the parent window, thus we receive an event here which in
258          * reality is a border_click. Check for the position and fix state. */
259         if (border_click &&
260             event->event_x >= client->child_rect.x &&
261             event->event_x <= (client->child_rect.x + client->child_rect.width) &&
262             event->event_y >= client->child_rect.y &&
263             event->event_y <= (client->child_rect.y + client->child_rect.height)) {
264                 LOG("Fixing border_click = false because of click in child\n");
265                 border_click = false;
266         }
267
268         if (!border_click) {
269                 LOG("client. done.\n");
270                 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
271                 /* Floating clients should be raised on click */
272                 if (client_is_floating(client))
273                         xcb_raise_window(conn, client->frame);
274                 xcb_flush(conn);
275                 return 1;
276         }
277
278         /* Don’t handle events inside the titlebar, only borders are interesting */
279         i3Font *font = load_font(conn, config.font);
280         if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
281                 LOG("click on titlebar\n");
282
283                 /* Floating clients can be dragged by grabbing their titlebar */
284                 if (client_is_floating(client)) {
285                         /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
286                         xcb_raise_window(conn, client->frame);
287                         xcb_flush(conn);
288
289                         floating_drag_window(conn, client, event);
290                 }
291                 return 1;
292         }
293
294         if (client_is_floating(client))
295                 return floating_border_click(conn, client, event);
296
297         if (event->event_y < 2) {
298                 /* This was a press on the top border */
299                 if (con->row == 0)
300                         return 1;
301                 first = con->row - 1;
302                 second = con->row;
303                 orientation = O_HORIZONTAL;
304         } else if (event->event_y >= (client->rect.height - 2)) {
305                 /* …bottom border */
306                 first = con->row + (con->rowspan - 1);
307                 if (!cell_exists(con->col, first) ||
308                     (first == (con->workspace->rows-1)))
309                         return 1;
310
311                 second = first + 1;
312                 orientation = O_HORIZONTAL;
313         } else if (event->event_x <= 2) {
314                 /* …left border */
315                 if (con->col == 0)
316                         return 1;
317
318                 first = con->col - 1;
319                 second = con->col;
320         } else if (event->event_x > 2) {
321                 /* …right border */
322                 first = con->col + (con->colspan - 1);
323                 LOG("column %d\n", first);
324
325                 if (!cell_exists(first, con->row) ||
326                     (first == (con->workspace->cols-1)))
327                         return 1;
328
329                 second = first + 1;
330         }
331
332         return resize_graphical_handler(conn, con->workspace, first, second, orientation, event);
333 }