i3
click.c
Go to the documentation of this file.
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  * click.c: Button press (mouse click) events.
8  *
9  */
10 #include "all.h"
11 
12 #include <time.h>
13 #include <math.h>
14 
15 #include <xcb/xcb_icccm.h>
16 
17 #include <X11/XKBlib.h>
18 
19 typedef enum { CLICK_BORDER = 0,
22 
23 /*
24  * Finds the correct pair of first/second cons between the resize will take
25  * place according to the passed border position (top, left, right, bottom),
26  * then calls resize_graphical_handler().
27  *
28  */
29 static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
30  DLOG("border = %d, con = %p\n", border, con);
31  Con *second = NULL;
32  Con *first = con;
33  direction_t search_direction;
34  switch (border) {
35  case BORDER_LEFT:
36  search_direction = D_LEFT;
37  break;
38  case BORDER_RIGHT:
39  search_direction = D_RIGHT;
40  break;
41  case BORDER_TOP:
42  search_direction = D_UP;
43  break;
44  case BORDER_BOTTOM:
45  search_direction = D_DOWN;
46  break;
47  }
48 
49  bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
50  if (!res) {
51  LOG("No second container in this direction found.\n");
52  return false;
53  }
54 
55  assert(first != second);
56  assert(first->parent == second->parent);
57 
58  /* The first container should always be in front of the second container */
59  if (search_direction == D_UP || search_direction == D_LEFT) {
60  Con *tmp = first;
61  first = second;
62  second = tmp;
63  }
64 
65  const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT);
66 
67  resize_graphical_handler(first, second, orientation, event);
68 
69  DLOG("After resize handler, rendering\n");
70  tree_render();
71  return true;
72 }
73 
74 /*
75  * Called when the user clicks using the floating_modifier, but the client is in
76  * tiling layout.
77  *
78  * Returns false if it does not do anything (that is, the click should be sent
79  * to the client).
80  *
81  */
82 static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
83  /* The client is in tiling layout. We can still initiate a resize with the
84  * right mouse button, by chosing the border which is the most near one to
85  * the position of the mouse pointer */
86  int to_right = con->rect.width - event->event_x,
87  to_left = event->event_x,
88  to_top = event->event_y,
89  to_bottom = con->rect.height - event->event_y;
90 
91  DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
92  to_right, to_left, to_top, to_bottom);
93 
94  if (to_right < to_left &&
95  to_right < to_top &&
96  to_right < to_bottom)
97  return tiling_resize_for_border(con, BORDER_RIGHT, event);
98 
99  if (to_left < to_right &&
100  to_left < to_top &&
101  to_left < to_bottom)
102  return tiling_resize_for_border(con, BORDER_LEFT, event);
103 
104  if (to_top < to_right &&
105  to_top < to_left &&
106  to_top < to_bottom)
107  return tiling_resize_for_border(con, BORDER_TOP, event);
108 
109  if (to_bottom < to_right &&
110  to_bottom < to_left &&
111  to_bottom < to_top)
112  return tiling_resize_for_border(con, BORDER_BOTTOM, event);
113 
114  return false;
115 }
116 
117 /*
118  * Finds out which border was clicked on and calls tiling_resize_for_border().
119  *
120  */
121 static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest) {
122  /* check if this was a click on the window border (and on which one) */
123  Rect bsr = con_border_style_rect(con);
124  DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
125  event->event_x, event->event_y, con, event->event);
126  DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
127  if (dest == CLICK_DECORATION) {
128  /* The user clicked on a window decoration. We ignore the following case:
129  * The container is a h-split, tabbed or stacked container with > 1
130  * window. Decorations will end up next to each other and the user
131  * expects to switch to a window by clicking on its decoration. */
132 
133  /* Since the container might either be the child *or* already a split
134  * container (in the case of a nested split container), we need to make
135  * sure that we are dealing with the split container here. */
136  Con *check_con = con;
137  if (con_is_leaf(check_con) && check_con->parent->type == CT_CON)
138  check_con = check_con->parent;
139 
140  if ((check_con->layout == L_STACKED ||
141  check_con->layout == L_TABBED ||
142  con_orientation(check_con) == HORIZ) &&
143  con_num_children(check_con) > 1) {
144  DLOG("Not handling this resize, this container has > 1 child.\n");
145  return false;
146  }
147  return tiling_resize_for_border(con, BORDER_TOP, event);
148  }
149 
150  if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x &&
151  event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
152  return tiling_resize_for_border(con, BORDER_LEFT, event);
153 
154  if (event->event_x >= (int32_t)(con->window_rect.x + con->window_rect.width) &&
155  event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height))
156  return tiling_resize_for_border(con, BORDER_RIGHT, event);
157 
158  if (event->event_y >= (int32_t)(con->window_rect.y + con->window_rect.height))
159  return tiling_resize_for_border(con, BORDER_BOTTOM, event);
160 
161  return false;
162 }
163 
164 /*
165  * Being called by handle_button_press, this function calls the appropriate
166  * functions for resizing/dragging.
167  *
168  */
169 static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod_pressed, const click_destination_t dest) {
170  DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest);
171  DLOG("--> OUTCOME = %p\n", con);
172  DLOG("type = %d, name = %s\n", con->type, con->name);
173 
174  /* don’t handle dockarea cons, they must not be focused */
175  if (con->parent->type == CT_DOCKAREA)
176  goto done;
177 
178  const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT ||
179  event->detail == XCB_BUTTON_CLICK_RIGHT);
180 
181  /* if the user has bound an action to this click, it should override the
182  * default behavior. */
183  if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
184  Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
185 
186  if (bind != NULL && ((dest == CLICK_DECORATION && !bind->exclude_titlebar) ||
187  (dest == CLICK_INSIDE && bind->whole_window) ||
188  (dest == CLICK_BORDER && bind->border))) {
189  CommandResult *result = run_binding(bind, con);
190 
191  /* ASYNC_POINTER eats the event */
192  xcb_allow_events(conn, XCB_ALLOW_ASYNC_POINTER, event->time);
193  xcb_flush(conn);
194 
195  command_result_free(result);
196  return 0;
197  }
198  }
199 
200  /* There is no default behavior for button release events so we are done. */
201  if (event->response_type == XCB_BUTTON_RELEASE) {
202  goto done;
203  }
204 
205  /* Any click in a workspace should focus that workspace. If the
206  * workspace is on another output we need to do a workspace_show in
207  * order for i3bar (and others) to notice the change in workspace. */
208  Con *ws = con_get_workspace(con);
209  Con *focused_workspace = con_get_workspace(focused);
210 
211  if (!ws) {
212  ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head));
213  if (!ws)
214  goto done;
215  }
216 
217  if (ws != focused_workspace)
218  workspace_show(ws);
219 
220  /* get the floating con */
221  Con *floatingcon = con_inside_floating(con);
222  const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
223  const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
224 
225  /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
226  if (in_stacked &&
227  dest == CLICK_DECORATION &&
228  (event->detail == XCB_BUTTON_SCROLL_UP ||
229  event->detail == XCB_BUTTON_SCROLL_DOWN ||
230  event->detail == XCB_BUTTON_SCROLL_LEFT ||
231  event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
232  DLOG("Scrolling on a window decoration\n");
233  orientation_t orientation = con_orientation(con->parent);
234  /* Use the focused child of the tabbed / stacked container, not the
235  * container the user scrolled on. */
236  Con *focused = con->parent;
239  /* To prevent scrolling from going outside the container (see ticket
240  * #557), we first check if scrolling is possible at all. */
241  bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
242  bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
243  if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) {
244  tree_next('p', orientation);
245  } else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) {
246  tree_next('n', orientation);
247  }
248 
249  goto done;
250  }
251 
252  /* 2: focus this con. */
253  con_activate(con);
254 
255  /* 3: For floating containers, we also want to raise them on click.
256  * We will skip handling events on floating cons in fullscreen mode */
258  if (floatingcon != NULL && fs != con) {
259  /* 4: floating_modifier plus left mouse button drags */
260  if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
261  floating_drag_window(floatingcon, event);
262  return 1;
263  }
264 
265  /* 5: resize (floating) if this was a (left or right) click on the
266  * left/right/bottom border, or a right click on the decoration.
267  * also try resizing (tiling) if it was a click on the top */
268  if (mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
269  DLOG("floating resize due to floatingmodifier\n");
270  floating_resize_window(floatingcon, proportional, event);
271  return 1;
272  }
273 
274  if (!in_stacked && dest == CLICK_DECORATION &&
275  is_left_or_right_click) {
276  /* try tiling resize, but continue if it doesn’t work */
277  DLOG("tiling resize with fallback\n");
278  if (tiling_resize(con, event, dest))
279  goto done;
280  }
281 
282  if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_RIGHT) {
283  DLOG("floating resize due to decoration right click\n");
284  floating_resize_window(floatingcon, proportional, event);
285  return 1;
286  }
287 
288  if (dest == CLICK_BORDER && is_left_or_right_click) {
289  DLOG("floating resize due to border click\n");
290  floating_resize_window(floatingcon, proportional, event);
291  return 1;
292  }
293 
294  /* 6: dragging, if this was a click on a decoration (which did not lead
295  * to a resize) */
296  if (!in_stacked && dest == CLICK_DECORATION &&
297  (event->detail == XCB_BUTTON_CLICK_LEFT)) {
298  floating_drag_window(floatingcon, event);
299  return 1;
300  }
301 
302  goto done;
303  }
304 
305  /* 7: floating modifier pressed, initiate a resize */
306  if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
307  if (floating_mod_on_tiled_client(con, event))
308  return 1;
309  }
310  /* 8: otherwise, check for border/decoration clicks and resize */
311  else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
312  is_left_or_right_click) {
313  DLOG("Trying to resize (tiling)\n");
314  tiling_resize(con, event, dest);
315  }
316 
317 done:
318  xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
319  xcb_flush(conn);
320  tree_render();
321 
322  return 0;
323 }
324 
325 /*
326  * The button press X callback. This function determines whether the floating
327  * modifier is pressed and where the user clicked (decoration, border, inside
328  * the window).
329  *
330  * Then, route_click is called on the appropriate con.
331  *
332  */
333 int handle_button_press(xcb_button_press_event_t *event) {
334  Con *con;
335  DLOG("Button %d (state %d) %s on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n",
336  event->detail, event->state, (event->response_type == XCB_BUTTON_PRESS ? "press" : "release"),
337  event->event, event->child, event->event_x, event->event_y, event->root_x,
338  event->root_y);
339 
340  last_timestamp = event->time;
341 
342  const uint32_t mod = (config.floating_modifier & 0xFFFF);
343  const bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
344  DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
345  if ((con = con_by_window_id(event->event)))
346  return route_click(con, event, mod_pressed, CLICK_INSIDE);
347 
348  if (!(con = con_by_frame_id(event->event))) {
349  /* Run bindings on the root window as well, see #2097. We only run it
350  * if --whole-window was set as that's the equivalent for a normal
351  * window. */
352  if (event->event == root) {
353  Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
354  if (bind != NULL && bind->whole_window) {
355  CommandResult *result = run_binding(bind, NULL);
356  command_result_free(result);
357  }
358  }
359 
360  /* If the root window is clicked, find the relevant output from the
361  * click coordinates and focus the output's active workspace. */
362  if (event->event == root && event->response_type == XCB_BUTTON_PRESS) {
363  Con *output, *ws;
364  TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
365  if (con_is_internal(output) ||
366  !rect_contains(output->rect, event->event_x, event->event_y))
367  continue;
368 
369  ws = TAILQ_FIRST(&(output_get_content(output)->focus_head));
370  if (ws != con_get_workspace(focused)) {
371  workspace_show(ws);
372  tree_render();
373  }
374  return 1;
375  }
376  return 0;
377  }
378 
379  ELOG("Clicked into unknown window?!\n");
380  xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
381  xcb_flush(conn);
382  return 0;
383  }
384 
385  /* Check if the click was on the decoration of a child */
386  Con *child;
387  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
388  if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
389  continue;
390 
391  return route_click(child, event, mod_pressed, CLICK_DECORATION);
392  }
393 
394  if (event->child != XCB_NONE) {
395  DLOG("event->child not XCB_NONE, so this is an event which originated from a click into the application, but the application did not handle it.\n");
396  return route_click(con, event, mod_pressed, CLICK_INSIDE);
397  }
398 
399  return route_click(con, event, mod_pressed, CLICK_BORDER);
400 }
Rect con_border_style_rect(Con *con)
Returns a "relative" Rect which contains the amount of pixels that need to be added to the original R...
Definition: con.c:1598
xcb_window_t root
Definition: main.c:57
bool border
If this is true for a mouse binding, the binding should be executed when the button is pressed over t...
Definition: data.h:303
uint32_t height
Definition: data.h:161
void workspace_show(Con *workspace)
Switches to the given workspace.
Definition: workspace.c:412
Definition: data.h:56
static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod_pressed, const click_destination_t dest)
Definition: click.c:169
static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest)
Definition: click.c:121
struct Con * croot
Definition: tree.c:12
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides)
Definition: resize.c:50
#define DLOG(fmt,...)
Definition: libi3.h:104
void floating_drag_window(Con *con, const xcb_button_press_event_t *event)
Called when the user clicked on the titlebar of a floating window.
Definition: floating.c:587
int handle_button_press(xcb_button_press_event_t *event)
The button press X callback.
Definition: click.c:333
xcb_connection_t * conn
XCB connection and root screen.
Definition: main.c:44
uint32_t floating_modifier
The modifier which needs to be pressed in combination with your mouse buttons to do things with float...
#define XCB_BUTTON_SCROLL_RIGHT
Definition: libi3.h:35
bool con_is_leaf(Con *con)
Returns true when this node is a leaf node (has no children)
Definition: con.c:302
Definition: data.h:60
#define XCB_BUTTON_CLICK_RIGHT
Definition: libi3.h:30
enum Con::@20 type
struct Con * parent
Definition: data.h:645
Definition: data.h:61
Con * con_inside_floating(Con *con)
Checks if the given container is either floating or inside some floating container.
Definition: con.c:564
Stores a rectangle, for example the size of a window, the child window etc.
Definition: data.h:157
Binding * get_binding_from_xcb_event(xcb_generic_event_t *event)
Returns a pointer to the Binding that matches the given xcb event or NULL if no such binding exists...
Definition: bindings.c:302
Con * con_by_window_id(xcb_window_t window)
Returns the container with the given client window ID or NULL if no such container exists...
Definition: con.c:612
uint32_t x
Definition: data.h:158
CommandResult * run_binding(Binding *bind, Con *con)
Runs the given binding and handles parse errors.
Definition: bindings.c:815
#define XCB_BUTTON_SCROLL_DOWN
Definition: libi3.h:32
nodes_head
Definition: data.h:694
struct Rect rect
Definition: data.h:649
A &#39;Con&#39; represents everything from the X11 root window down to a single X11 window.
Definition: data.h:613
#define TAILQ_FIRST(head)
Definition: queue.h:336
Definition: data.h:57
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:418
Con * con_get_output(Con *con)
Gets the output container (first container with CT_OUTPUT in hierarchy) this node is on...
Definition: con.c:404
int con_num_children(Con *con)
Returns the number of children of this container.
Definition: con.c:887
#define XCB_BUTTON_SCROLL_LEFT
Definition: libi3.h:34
void command_result_free(CommandResult *result)
Frees a CommandResult.
orientation_t con_orientation(Con *con)
Returns the orientation of the given container (for stacked containers, vertical orientation is used ...
Definition: con.c:1418
#define TAILQ_NEXT(elm, field)
Definition: queue.h:338
void tree_render(void)
Renders the tree, that is rendering all outputs using render_con() and pushing the changes to X11 usi...
Definition: tree.c:449
orientation_t
Definition: data.h:59
bool exclude_titlebar
If this is true for a mouse binding, the binding should only be executed if the button press was not ...
Definition: data.h:312
Con * con_by_frame_id(xcb_window_t frame)
Returns the container with the given frame ID or NULL if no such container exists.
Definition: con.c:650
focus_head
Definition: data.h:697
char * name
Definition: data.h:659
uint32_t y
Definition: data.h:159
bool con_is_internal(Con *con)
Returns true if the container is internal, such as __i3_scratch.
Definition: con.c:532
#define LOG(fmt,...)
Definition: libi3.h:94
Con * output_get_content(Con *output)
Returns the output container below the given output container.
Definition: output.c:16
uint32_t width
Definition: data.h:160
struct Rect window_rect
Definition: data.h:652
static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event)
Definition: click.c:82
void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event)
Called when the user clicked on a floating window while holding the floating_modifier and the right m...
Definition: floating.c:687
A struct that contains useful information about the result of a command as a whole (e...
Holds a keybinding, consisting of a keycode combined with modifiers and the command which is executed...
Definition: data.h:282
struct Con * focused
Definition: tree.c:13
void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event)
Definition: resize.c:151
Con * con_descend_focused(Con *con)
Returns the focused con inside this client, descending the tree as far as possible.
Definition: con.c:1491
border_t
On which border was the dragging initiated?
Definition: floating.h:25
bool whole_window
If this is true for a mouse binding, the binding should be executed when the button is pressed over a...
Definition: data.h:308
#define XCB_BUTTON_CLICK_LEFT
Mouse buttons.
Definition: libi3.h:28
#define ELOG(fmt,...)
Definition: libi3.h:99
bool rect_contains(Rect rect, uint32_t x, uint32_t y)
Definition: util.c:35
#define TAILQ_PREV(elm, headname, field)
Definition: queue.h:342
Definition: data.h:94
direction_t
Definition: data.h:55
static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event)
Definition: click.c:29
Definition: data.h:55
Definition: data.h:93
void tree_next(char way, orientation_t orientation)
Changes focus in the given way (next/previous) and given orientation (horizontal/vertical).
Definition: tree.c:626
void con_activate(Con *con)
Sets input focus to the given container and raises it to the top.
Definition: con.c:263
xcb_timestamp_t last_timestamp
The last timestamp we got from X11 (timestamps are included in some events and are used for some thin...
Definition: main.c:54
Config config
Definition: config.c:17
Definition: data.h:58
Con * con_get_fullscreen_covering_ws(Con *ws)
Returns the fullscreen node that covers the given workspace if it exists.
Definition: con.c:517
click_destination_t
Definition: click.c:19
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:347
struct Rect deco_rect
Definition: data.h:655
#define XCB_BUTTON_SCROLL_UP
Definition: libi3.h:31
layout_t layout
Definition: data.h:723