i3
|
00001 /* 00002 * vim:ts=4:sw=4:expandtab 00003 * 00004 * i3 - an improved dynamic tiling window manager 00005 * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) 00006 * 00007 * click.c: Button press (mouse click) events. 00008 * 00009 */ 00010 #include "all.h" 00011 00012 #include <time.h> 00013 #include <math.h> 00014 00015 #include <xcb/xcb_atom.h> 00016 #include <xcb/xcb_icccm.h> 00017 00018 #include <X11/XKBlib.h> 00019 00020 typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_destination_t; 00021 00022 /* 00023 * Finds the correct pair of first/second cons between the resize will take 00024 * place according to the passed border position (top, left, right, bottom), 00025 * then calls resize_graphical_handler(). 00026 * 00027 */ 00028 static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) { 00029 DLOG("border = %d, con = %p\n", border, con); 00030 char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n'); 00031 orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ); 00032 00033 /* look for a parent container with the right orientation */ 00034 Con *first = NULL, *second = NULL; 00035 Con *resize_con = con; 00036 while (resize_con->type != CT_WORKSPACE && 00037 resize_con->type != CT_FLOATING_CON && 00038 resize_con->parent->orientation != orientation) 00039 resize_con = resize_con->parent; 00040 00041 DLOG("resize_con = %p\n", resize_con); 00042 if (resize_con->type != CT_WORKSPACE && 00043 resize_con->type != CT_FLOATING_CON && 00044 resize_con->parent->orientation == orientation) { 00045 first = resize_con; 00046 second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); 00047 if (second == TAILQ_END(&(first->nodes_head))) { 00048 second = NULL; 00049 } 00050 else if (way == 'p') { 00051 Con *tmp = first; 00052 first = second; 00053 second = tmp; 00054 } 00055 DLOG("first = %p, second = %p, resize_con = %p\n", 00056 first, second, resize_con); 00057 } 00058 00059 if (first == NULL || second == NULL) { 00060 DLOG("Resize not possible\n"); 00061 return false; 00062 } 00063 00064 assert(first != second); 00065 assert(first->parent == second->parent); 00066 00067 /* We modify the X/Y position in the event so that the divider line is at 00068 * the actual position of the border, not at the position of the click. */ 00069 if (orientation == HORIZ) 00070 event->root_x = second->rect.x; 00071 else event->root_y = second->rect.y; 00072 00073 resize_graphical_handler(first, second, orientation, event); 00074 00075 DLOG("After resize handler, rendering\n"); 00076 tree_render(); 00077 return true; 00078 } 00079 00080 /* 00081 * Called when the user clicks using the floating_modifier, but the client is in 00082 * tiling layout. 00083 * 00084 * Returns false if it does not do anything (that is, the click should be sent 00085 * to the client). 00086 * 00087 */ 00088 static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) { 00089 /* The client is in tiling layout. We can still initiate a resize with the 00090 * right mouse button, by chosing the border which is the most near one to 00091 * the position of the mouse pointer */ 00092 int to_right = con->rect.width - event->event_x, 00093 to_left = event->event_x, 00094 to_top = event->event_y, 00095 to_bottom = con->rect.height - event->event_y; 00096 00097 DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", 00098 to_right, to_left, to_top, to_bottom); 00099 00100 if (to_right < to_left && 00101 to_right < to_top && 00102 to_right < to_bottom) 00103 return tiling_resize_for_border(con, BORDER_RIGHT, event); 00104 00105 if (to_left < to_right && 00106 to_left < to_top && 00107 to_left < to_bottom) 00108 return tiling_resize_for_border(con, BORDER_LEFT, event); 00109 00110 if (to_top < to_right && 00111 to_top < to_left && 00112 to_top < to_bottom) 00113 return tiling_resize_for_border(con, BORDER_TOP, event); 00114 00115 if (to_bottom < to_right && 00116 to_bottom < to_left && 00117 to_bottom < to_top) 00118 return tiling_resize_for_border(con, BORDER_BOTTOM, event); 00119 00120 return false; 00121 } 00122 00123 /* 00124 * Finds out which border was clicked on and calls tiling_resize_for_border(). 00125 * 00126 */ 00127 static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest) { 00128 /* check if this was a click on the window border (and on which one) */ 00129 Rect bsr = con_border_style_rect(con); 00130 DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n", 00131 event->event_x, event->event_y, con, event->event); 00132 DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); 00133 if (dest == CLICK_DECORATION) { 00134 /* The user clicked on a window decoration. We ignore the following case: 00135 * The container is a h-split, tabbed or stacked container with > 1 00136 * window. Decorations will end up next to each other and the user 00137 * expects to switch to a window by clicking on its decoration. */ 00138 00139 /* Since the container might either be the child *or* already a split 00140 * container (in the case of a nested split container), we need to make 00141 * sure that we are dealing with the split container here. */ 00142 Con *check_con = con; 00143 if (con_is_leaf(check_con) && check_con->parent->type == CT_CON) 00144 check_con = check_con->parent; 00145 00146 if ((check_con->layout == L_STACKED || 00147 check_con->layout == L_TABBED || 00148 check_con->orientation == HORIZ) && 00149 con_num_children(check_con) > 1) { 00150 DLOG("Not handling this resize, this container has > 1 child.\n"); 00151 return false; 00152 } 00153 return tiling_resize_for_border(con, BORDER_TOP, event); 00154 } 00155 00156 if (event->event_x >= 0 && event->event_x <= bsr.x && 00157 event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) 00158 return tiling_resize_for_border(con, BORDER_LEFT, event); 00159 00160 if (event->event_x >= (con->window_rect.x + con->window_rect.width) && 00161 event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) 00162 return tiling_resize_for_border(con, BORDER_RIGHT, event); 00163 00164 if (event->event_y >= (con->window_rect.y + con->window_rect.height)) 00165 return tiling_resize_for_border(con, BORDER_BOTTOM, event); 00166 00167 return false; 00168 } 00169 00170 /* 00171 * Being called by handle_button_press, this function calls the appropriate 00172 * functions for resizing/dragging. 00173 * 00174 */ 00175 static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod_pressed, const click_destination_t dest) { 00176 DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest); 00177 DLOG("--> OUTCOME = %p\n", con); 00178 DLOG("type = %d, name = %s\n", con->type, con->name); 00179 00180 /* don’t handle dockarea cons, they must not be focused */ 00181 if (con->parent->type == CT_DOCKAREA) 00182 goto done; 00183 00184 /* get the floating con */ 00185 Con *floatingcon = con_inside_floating(con); 00186 const bool proportional = (event->state & BIND_SHIFT); 00187 const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); 00188 00189 /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ 00190 if (in_stacked && 00191 dest == CLICK_DECORATION && 00192 (event->detail == XCB_BUTTON_INDEX_4 || 00193 event->detail == XCB_BUTTON_INDEX_5)) { 00194 DLOG("Scrolling on a window decoration\n"); 00195 orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); 00196 /* To prevent scrolling from going outside the container (see ticket 00197 * #557), we first check if scrolling is possible at all. */ 00198 Con *focused = con_descend_focused(con->parent); 00199 bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); 00200 bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL); 00201 if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible) 00202 tree_next('p', orientation); 00203 else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible) 00204 tree_next('n', orientation); 00205 goto done; 00206 } 00207 00208 /* 2: focus this con. If the workspace is on another output we need to 00209 * do a workspace_show in order for i3bar (and others) to notice the 00210 * change in workspace. */ 00211 Con *ws = con_get_workspace(con); 00212 Con *focused_workspace = con_get_workspace(focused); 00213 00214 if (ws != focused_workspace) 00215 workspace_show(ws); 00216 focused_id = XCB_NONE; 00217 con_focus(con); 00218 00219 /* 3: For floating containers, we also want to raise them on click. 00220 * We will skip handling events on floating cons in fullscreen mode */ 00221 Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL); 00222 if (floatingcon != NULL && fs == NULL) { 00223 floating_raise_con(floatingcon); 00224 00225 /* 4: floating_modifier plus left mouse button drags */ 00226 if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) { 00227 floating_drag_window(floatingcon, event); 00228 return 1; 00229 } 00230 00231 /* 5: resize (floating) if this was a click on the left/right/bottom 00232 * border. also try resizing (tiling) if it was a click on the top 00233 * border, but continue if that does not work */ 00234 if (mod_pressed && event->detail == 3) { 00235 DLOG("floating resize due to floatingmodifier\n"); 00236 floating_resize_window(floatingcon, proportional, event); 00237 return 1; 00238 } 00239 00240 if (!in_stacked && dest == CLICK_DECORATION) { 00241 /* try tiling resize, but continue if it doesn’t work */ 00242 DLOG("tiling resize with fallback\n"); 00243 if (tiling_resize(con, event, dest)) 00244 goto done; 00245 } 00246 00247 if (dest == CLICK_BORDER) { 00248 DLOG("floating resize due to border click\n"); 00249 floating_resize_window(floatingcon, proportional, event); 00250 return 1; 00251 } 00252 00253 /* 6: dragging, if this was a click on a decoration (which did not lead 00254 * to a resize) */ 00255 if (!in_stacked && dest == CLICK_DECORATION) { 00256 floating_drag_window(floatingcon, event); 00257 return 1; 00258 } 00259 00260 goto done; 00261 } 00262 00263 if (in_stacked) { 00264 /* for stacked/tabbed cons, the resizing applies to the parent 00265 * container */ 00266 con = con->parent; 00267 } 00268 00269 /* 7: floating modifier pressed, initiate a resize */ 00270 if (dest == CLICK_INSIDE && mod_pressed && event->detail == 3) { 00271 if (floating_mod_on_tiled_client(con, event)) 00272 return 1; 00273 } 00274 /* 8: otherwise, check for border/decoration clicks and resize */ 00275 else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) && 00276 (event->detail == XCB_BUTTON_INDEX_1 || 00277 event->detail == XCB_BUTTON_INDEX_3)) { 00278 DLOG("Trying to resize (tiling)\n"); 00279 tiling_resize(con, event, dest); 00280 } 00281 00282 done: 00283 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); 00284 xcb_flush(conn); 00285 tree_render(); 00286 return 0; 00287 } 00288 00289 /* 00290 * The button press X callback. This function determines whether the floating 00291 * modifier is pressed and where the user clicked (decoration, border, inside 00292 * the window). 00293 * 00294 * Then, route_click is called on the appropriate con. 00295 * 00296 */ 00297 int handle_button_press(xcb_button_press_event_t *event) { 00298 Con *con; 00299 DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); 00300 00301 last_timestamp = event->time; 00302 00303 const uint32_t mod = config.floating_modifier; 00304 const bool mod_pressed = (mod != 0 && (event->state & mod) == mod); 00305 DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); 00306 if ((con = con_by_window_id(event->event))) 00307 return route_click(con, event, mod_pressed, CLICK_INSIDE); 00308 00309 if (!(con = con_by_frame_id(event->event))) { 00310 ELOG("Clicked into unknown window?!\n"); 00311 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); 00312 xcb_flush(conn); 00313 return 0; 00314 } 00315 00316 /* Check if the click was on the decoration of a child */ 00317 Con *child; 00318 TAILQ_FOREACH(child, &(con->nodes_head), nodes) { 00319 if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) 00320 continue; 00321 00322 return route_click(child, event, mod_pressed, CLICK_DECORATION); 00323 } 00324 00325 return route_click(con, event, mod_pressed, CLICK_BORDER); 00326 }