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_INDEX_1 ||
182  event->detail == XCB_BUTTON_INDEX_3);
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 ||
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_INDEX_4 ||
232  event->detail == XCB_BUTTON_INDEX_5)) {
233  DLOG("Scrolling on a window decoration\n");
234  orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
235  /* Focus the currently focused container on the same level that the
236  * user scrolled on. e.g. the tabbed decoration contains
237  * "urxvt | i3: V[xterm geeqie] | firefox",
238  * focus is on the xterm, but the user scrolled on urxvt.
239  * The splitv container will be focused. */
240  Con *focused = con->parent;
241  focused = TAILQ_FIRST(&(focused->focus_head));
242  con_focus(focused);
243  /* To prevent scrolling from going outside the container (see ticket
244  * #557), we first check if scrolling is possible at all. */
245  bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
246  bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
247  if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible)
248  tree_next('p', orientation);
249  else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible)
250  tree_next('n', orientation);
251  goto done;
252  }
253 
254  /* 2: focus this con. */
255  con_focus(con);
256 
257  /* 3: For floating containers, we also want to raise them on click.
258  * We will skip handling events on floating cons in fullscreen mode */
259  Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
260  if (floatingcon != NULL && fs != con) {
261  floating_raise_con(floatingcon);
262 
263  /* 4: floating_modifier plus left mouse button drags */
264  if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) {
265  floating_drag_window(floatingcon, event);
266  return 1;
267  }
268 
269  /* 5: resize (floating) if this was a (left or right) click on the
270  * left/right/bottom border, or a right click on the decoration.
271  * also try resizing (tiling) if it was a click on the top */
272  if (mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
273  DLOG("floating resize due to floatingmodifier\n");
274  floating_resize_window(floatingcon, proportional, event);
275  return 1;
276  }
277 
278  if (!in_stacked && dest == CLICK_DECORATION &&
279  is_left_or_right_click) {
280  /* try tiling resize, but continue if it doesn’t work */
281  DLOG("tiling resize with fallback\n");
282  if (tiling_resize(con, event, dest))
283  goto done;
284  }
285 
286  if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_INDEX_3) {
287  DLOG("floating resize due to decoration right click\n");
288  floating_resize_window(floatingcon, proportional, event);
289  return 1;
290  }
291 
292  if (dest == CLICK_BORDER && is_left_or_right_click) {
293  DLOG("floating resize due to border click\n");
294  floating_resize_window(floatingcon, proportional, event);
295  return 1;
296  }
297 
298  /* 6: dragging, if this was a click on a decoration (which did not lead
299  * to a resize) */
300  if (!in_stacked && dest == CLICK_DECORATION &&
301  (event->detail == XCB_BUTTON_INDEX_1)) {
302  floating_drag_window(floatingcon, event);
303  return 1;
304  }
305 
306  goto done;
307  }
308 
309  if (in_stacked) {
310  /* for stacked/tabbed cons, the resizing applies to the parent
311  * container */
312  con = con->parent;
313  }
314 
315  /* 7: floating modifier pressed, initiate a resize */
316  if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
317  if (floating_mod_on_tiled_client(con, event))
318  return 1;
319  }
320  /* 8: otherwise, check for border/decoration clicks and resize */
321  else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
322  is_left_or_right_click) {
323  DLOG("Trying to resize (tiling)\n");
324  tiling_resize(con, event, dest);
325  }
326 
327 done:
328  xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
329  xcb_flush(conn);
330  tree_render();
331 
332  return 0;
333 }
334 
335 /*
336  * The button press X callback. This function determines whether the floating
337  * modifier is pressed and where the user clicked (decoration, border, inside
338  * the window).
339  *
340  * Then, route_click is called on the appropriate con.
341  *
342  */
343 int handle_button_press(xcb_button_press_event_t *event) {
344  Con *con;
345  DLOG("Button %d (state %d) %s on window 0x%08x (child 0x%08x) at (%d, %d) (root %d, %d)\n",
346  event->detail, event->state, (event->response_type == XCB_BUTTON_PRESS ? "press" : "release"),
347  event->event, event->child, event->event_x, event->event_y, event->root_x,
348  event->root_y);
349 
350  last_timestamp = event->time;
351 
352  const uint32_t mod = (config.floating_modifier & 0xFFFF);
353  const bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
354  DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
355  if ((con = con_by_window_id(event->event)))
356  return route_click(con, event, mod_pressed, CLICK_INSIDE);
357 
358  if (!(con = con_by_frame_id(event->event))) {
359  /* Run bindings on the root window as well, see #2097. We only run it
360  * if --whole-window was set as that's the equivalent for a normal
361  * window. */
362  if (event->event == root) {
363  Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
364  if (bind != NULL && bind->whole_window) {
365  CommandResult *result = run_binding(bind, NULL);
366  command_result_free(result);
367  }
368  }
369 
370  /* If the root window is clicked, find the relevant output from the
371  * click coordinates and focus the output's active workspace. */
372  if (event->event == root && event->response_type == XCB_BUTTON_PRESS) {
373  Con *output, *ws;
374  TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
375  if (con_is_internal(output) ||
376  !rect_contains(output->rect, event->event_x, event->event_y))
377  continue;
378 
379  ws = TAILQ_FIRST(&(output_get_content(output)->focus_head));
380  if (ws != con_get_workspace(focused)) {
381  workspace_show(ws);
382  tree_render();
383  }
384  return 1;
385  }
386  return 0;
387  }
388 
389  ELOG("Clicked into unknown window?!\n");
390  xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
391  xcb_flush(conn);
392  return 0;
393  }
394 
395  /* Check if the click was on the decoration of a child */
396  Con *child;
397  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
398  if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
399  continue;
400 
401  return route_click(child, event, mod_pressed, CLICK_DECORATION);
402  }
403 
404  if (event->child != XCB_NONE) {
405  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");
406  return route_click(con, event, mod_pressed, CLICK_INSIDE);
407  }
408 
409  return route_click(con, event, mod_pressed, CLICK_BORDER);
410 }
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:1473
#define ELOG(fmt,...)
Definition: libi3.h:89
void workspace_show(Con *workspace)
Switches to the given workspace.
Definition: workspace.c:489
uint32_t y
Definition: data.h:150
border_t
On which border was the dragging initiated?
Definition: floating.h:25
Holds a keybinding, consisting of a keycode combined with modifiers and the command which is executed...
Definition: data.h:268
uint32_t height
Definition: data.h:152
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction)
Definition: resize.c:50
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:172
static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest)
Definition: click.c:124
uint32_t x
Definition: data.h:149
struct Con * croot
Definition: tree.c:12
#define TAILQ_NEXT(elm, field)
Definition: queue.h:338
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:294
#define TAILQ_PREV(elm, headname, field)
Definition: queue.h:342
struct Con * parent
Definition: data.h:590
int handle_button_press(xcb_button_press_event_t *event)
The button press X callback.
Definition: click.c:343
void con_focus(Con *con)
Sets input focus to the given container.
Definition: con.c:197
bool con_is_leaf(Con *con)
Returns true when this node is a leaf node (has no children)
Definition: con.c:256
xcb_window_t root
Definition: main.c:55
Definition: data.h:60
Con * output_get_content(Con *output)
Returns the output container below the given output container.
Definition: output.c:16
#define LOG(fmt,...)
Definition: libi3.h:84
struct Rect rect
Definition: data.h:594
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:499
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:300
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:530
CommandResult * run_binding(Binding *bind, Con *con)
Runs the given binding and handles parse errors.
Definition: bindings.c:780
Stores a rectangle, for example the size of a window, the child window etc.
Definition: data.h:148
Definition: data.h:57
xcb_connection_t * conn
XCB connection and root screen.
Definition: main.c:42
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:347
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:372
Con * con_get_output(Con *con)
Gets the output container (first container with CT_OUTPUT in hierarchy) this node is on...
Definition: con.c:358
int con_num_children(Con *con)
Returns the number of children of this container.
Definition: con.c:724
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:1226
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:490
orientation_t
Definition: data.h:59
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:543
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:289
char * name
Definition: data.h:604
bool con_is_internal(Con *con)
Returns true if the container is internal, such as __i3_scratch.
Definition: con.c:466
layout_t layout
Definition: data.h:662
static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event)
Definition: click.c:85
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:590
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:52
A struct that contains useful information about the result of a command as a whole (e...
struct Rect deco_rect
Definition: data.h:600
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:495
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event)
Definition: resize.c:98
A &#39;Con&#39; represents everything from the X11 root window down to a single X11 window.
Definition: data.h:558
uint32_t width
Definition: data.h:151
#define DLOG(fmt,...)
Definition: libi3.h:94
bool rect_contains(Rect rect, uint32_t x, uint32_t y)
Definition: util.c:35
Con * con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode)
Returns the first fullscreen node below this node.
Definition: con.c:420
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:672
uint32_t floating_modifier
The modifier which needs to be pressed in combination with your mouse buttons to do things with float...
Config config
Definition: config.c:16
enum Con::@20 type
void floating_raise_con(Con *con)
Raises the given container in the list of floating containers.
Definition: floating.c:390
Definition: data.h:58
click_destination_t
Definition: click.c:19
struct Rect window_rect
Definition: data.h:597
Con * focused
Definition: tree.c:13