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