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-2012 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_atom.h>
16 #include <xcb/xcb_icccm.h>
17 
18 #include <X11/XKBlib.h>
19 
21 
22 /*
23  * Finds the correct pair of first/second cons between the resize will take
24  * place according to the passed border position (top, left, right, bottom),
25  * then calls resize_graphical_handler().
26  *
27  */
28 static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
29  DLOG("border = %d, con = %p\n", border, con);
30  char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n');
31  orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ);
32 
33  /* look for a parent container with the right orientation */
34  Con *first = NULL, *second = NULL;
35  Con *resize_con = con;
36  while (resize_con->type != CT_WORKSPACE &&
37  resize_con->type != CT_FLOATING_CON &&
38  resize_con->parent->orientation != orientation)
39  resize_con = resize_con->parent;
40 
41  DLOG("resize_con = %p\n", resize_con);
42  if (resize_con->type != CT_WORKSPACE &&
43  resize_con->type != CT_FLOATING_CON &&
44  resize_con->parent->orientation == orientation) {
45  first = resize_con;
46  second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
47  if (second == TAILQ_END(&(first->nodes_head))) {
48  second = NULL;
49  }
50  else if (way == 'p') {
51  Con *tmp = first;
52  first = second;
53  second = tmp;
54  }
55  DLOG("first = %p, second = %p, resize_con = %p\n",
56  first, second, resize_con);
57  }
58 
59  if (first == NULL || second == NULL) {
60  DLOG("Resize not possible\n");
61  return false;
62  }
63 
64  assert(first != second);
65  assert(first->parent == second->parent);
66 
67  /* We modify the X/Y position in the event so that the divider line is at
68  * the actual position of the border, not at the position of the click. */
69  if (orientation == HORIZ)
70  event->root_x = second->rect.x;
71  else event->root_y = second->rect.y;
72 
73  resize_graphical_handler(first, second, orientation, event);
74 
75  DLOG("After resize handler, rendering\n");
76  tree_render();
77  return true;
78 }
79 
80 /*
81  * Called when the user clicks using the floating_modifier, but the client is in
82  * tiling layout.
83  *
84  * Returns false if it does not do anything (that is, the click should be sent
85  * to the client).
86  *
87  */
88 static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
89  /* The client is in tiling layout. We can still initiate a resize with the
90  * right mouse button, by chosing the border which is the most near one to
91  * the position of the mouse pointer */
92  int to_right = con->rect.width - event->event_x,
93  to_left = event->event_x,
94  to_top = event->event_y,
95  to_bottom = con->rect.height - event->event_y;
96 
97  DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
98  to_right, to_left, to_top, to_bottom);
99 
100  if (to_right < to_left &&
101  to_right < to_top &&
102  to_right < to_bottom)
103  return tiling_resize_for_border(con, BORDER_RIGHT, event);
104 
105  if (to_left < to_right &&
106  to_left < to_top &&
107  to_left < to_bottom)
108  return tiling_resize_for_border(con, BORDER_LEFT, event);
109 
110  if (to_top < to_right &&
111  to_top < to_left &&
112  to_top < to_bottom)
113  return tiling_resize_for_border(con, BORDER_TOP, event);
114 
115  if (to_bottom < to_right &&
116  to_bottom < to_left &&
117  to_bottom < to_top)
118  return tiling_resize_for_border(con, BORDER_BOTTOM, event);
119 
120  return false;
121 }
122 
123 /*
124  * Finds out which border was clicked on and calls tiling_resize_for_border().
125  *
126  */
127 static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest) {
128  /* check if this was a click on the window border (and on which one) */
129  Rect bsr = con_border_style_rect(con);
130  DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n",
131  event->event_x, event->event_y, con, event->event);
132  DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width);
133  if (dest == CLICK_DECORATION) {
134  /* The user clicked on a window decoration. We ignore the following case:
135  * The container is a h-split, tabbed or stacked container with > 1
136  * window. Decorations will end up next to each other and the user
137  * expects to switch to a window by clicking on its decoration. */
138 
139  /* Since the container might either be the child *or* already a split
140  * container (in the case of a nested split container), we need to make
141  * sure that we are dealing with the split container here. */
142  Con *check_con = con;
143  if (con_is_leaf(check_con) && check_con->parent->type == CT_CON)
144  check_con = check_con->parent;
145 
146  if ((check_con->layout == L_STACKED ||
147  check_con->layout == L_TABBED ||
148  check_con->orientation == HORIZ) &&
149  con_num_children(check_con) > 1) {
150  DLOG("Not handling this resize, this container has > 1 child.\n");
151  return false;
152  }
153  return tiling_resize_for_border(con, BORDER_TOP, event);
154  }
155 
156  if (event->event_x >= 0 && event->event_x <= bsr.x &&
157  event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
158  return tiling_resize_for_border(con, BORDER_LEFT, event);
159 
160  if (event->event_x >= (con->window_rect.x + con->window_rect.width) &&
161  event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height)
162  return tiling_resize_for_border(con, BORDER_RIGHT, event);
163 
164  if (event->event_y >= (con->window_rect.y + con->window_rect.height))
165  return tiling_resize_for_border(con, BORDER_BOTTOM, event);
166 
167  return false;
168 }
169 
170 /*
171  * Being called by handle_button_press, this function calls the appropriate
172  * functions for resizing/dragging.
173  *
174  */
175 static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod_pressed, const click_destination_t dest) {
176  DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest);
177  DLOG("--> OUTCOME = %p\n", con);
178  DLOG("type = %d, name = %s\n", con->type, con->name);
179 
180  /* don’t handle dockarea cons, they must not be focused */
181  if (con->parent->type == CT_DOCKAREA)
182  goto done;
183 
184  /* get the floating con */
185  Con *floatingcon = con_inside_floating(con);
186  const bool proportional = (event->state & BIND_SHIFT);
187  const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
188 
189  /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
190  if (in_stacked &&
191  dest == CLICK_DECORATION &&
192  (event->detail == XCB_BUTTON_INDEX_4 ||
193  event->detail == XCB_BUTTON_INDEX_5)) {
194  DLOG("Scrolling on a window decoration\n");
195  orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
196  /* To prevent scrolling from going outside the container (see ticket
197  * #557), we first check if scrolling is possible at all. */
199  bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
200  bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
201  if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible)
202  tree_next('p', orientation);
203  else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible)
204  tree_next('n', orientation);
205  goto done;
206  }
207 
208  /* 2: focus this con. If the workspace is on another output we need to
209  * do a workspace_show in order for i3bar (and others) to notice the
210  * change in workspace. */
211  Con *ws = con_get_workspace(con);
212  Con *focused_workspace = con_get_workspace(focused);
213 
214  if (ws != focused_workspace)
215  workspace_show(ws);
216  focused_id = XCB_NONE;
217  con_focus(con);
218 
219  /* 3: For floating containers, we also want to raise them on click.
220  * We will skip handling events on floating cons in fullscreen mode */
221  Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
222  if (floatingcon != NULL && fs == NULL) {
223  floating_raise_con(floatingcon);
224 
225  /* 4: floating_modifier plus left mouse button drags */
226  if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) {
227  floating_drag_window(floatingcon, event);
228  return 1;
229  }
230 
231  /* 5: resize (floating) if this was a click on the left/right/bottom
232  * border. also try resizing (tiling) if it was a click on the top
233  * border, but continue if that does not work */
234  if (mod_pressed && event->detail == 3) {
235  DLOG("floating resize due to floatingmodifier\n");
236  floating_resize_window(floatingcon, proportional, event);
237  return 1;
238  }
239 
240  if (!in_stacked && dest == CLICK_DECORATION) {
241  /* try tiling resize, but continue if it doesn’t work */
242  DLOG("tiling resize with fallback\n");
243  if (tiling_resize(con, event, dest))
244  goto done;
245  }
246 
247  if (dest == CLICK_BORDER) {
248  DLOG("floating resize due to border click\n");
249  floating_resize_window(floatingcon, proportional, event);
250  return 1;
251  }
252 
253  /* 6: dragging, if this was a click on a decoration (which did not lead
254  * to a resize) */
255  if (!in_stacked && dest == CLICK_DECORATION) {
256  floating_drag_window(floatingcon, event);
257  return 1;
258  }
259 
260  goto done;
261  }
262 
263  if (in_stacked) {
264  /* for stacked/tabbed cons, the resizing applies to the parent
265  * container */
266  con = con->parent;
267  }
268 
269  /* 7: floating modifier pressed, initiate a resize */
270  if (dest == CLICK_INSIDE && mod_pressed && event->detail == 3) {
271  if (floating_mod_on_tiled_client(con, event))
272  return 1;
273  }
274  /* 8: otherwise, check for border/decoration clicks and resize */
275  else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
276  (event->detail == XCB_BUTTON_INDEX_1 ||
277  event->detail == XCB_BUTTON_INDEX_3)) {
278  DLOG("Trying to resize (tiling)\n");
279  tiling_resize(con, event, dest);
280  }
281 
282 done:
283  xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
284  xcb_flush(conn);
285  tree_render();
286  return 0;
287 }
288 
289 /*
290  * The button press X callback. This function determines whether the floating
291  * modifier is pressed and where the user clicked (decoration, border, inside
292  * the window).
293  *
294  * Then, route_click is called on the appropriate con.
295  *
296  */
297 int handle_button_press(xcb_button_press_event_t *event) {
298  Con *con;
299  DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
300 
301  last_timestamp = event->time;
302 
303  const uint32_t mod = config.floating_modifier;
304  const bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
305  DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
306  if ((con = con_by_window_id(event->event)))
307  return route_click(con, event, mod_pressed, CLICK_INSIDE);
308 
309  if (!(con = con_by_frame_id(event->event))) {
310  ELOG("Clicked into unknown window?!\n");
311  xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
312  xcb_flush(conn);
313  return 0;
314  }
315 
316  /* Check if the click was on the decoration of a child */
317  Con *child;
318  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
319  if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
320  continue;
321 
322  return route_click(child, event, mod_pressed, CLICK_DECORATION);
323  }
324 
325  return route_click(con, event, mod_pressed, CLICK_BORDER);
326 }