i3
|
00001 /* 00002 * vim:ts=4:sw=4:expandtab 00003 * 00004 * i3 - an improved dynamic tiling window manager 00005 * © 2009-2011 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 if (event->detail == XCB_BUTTON_INDEX_4) 00197 tree_next('p', orientation); 00198 else tree_next('n', orientation); 00199 goto done; 00200 } 00201 00202 /* 2: focus this con */ 00203 con_focus(con); 00204 00205 /* 3: For floating containers, we also want to raise them on click. 00206 * We will skip handling events on floating cons in fullscreen mode */ 00207 Con *ws = con_get_workspace(con); 00208 Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL); 00209 if (floatingcon != NULL && fs == NULL) { 00210 floating_raise_con(floatingcon); 00211 00212 /* 4: floating_modifier plus left mouse button drags */ 00213 if (mod_pressed && event->detail == 1) { 00214 floating_drag_window(floatingcon, event); 00215 return 1; 00216 } 00217 00218 /* 5: resize (floating) if this was a click on the left/right/bottom 00219 * border. also try resizing (tiling) if it was a click on the top 00220 * border, but continue if that does not work */ 00221 if (mod_pressed && event->detail == 3) { 00222 DLOG("floating resize due to floatingmodifier\n"); 00223 floating_resize_window(floatingcon, proportional, event); 00224 return 1; 00225 } 00226 00227 if (!in_stacked && dest == CLICK_DECORATION) { 00228 /* try tiling resize, but continue if it doesn’t work */ 00229 DLOG("tiling resize with fallback\n"); 00230 if (tiling_resize(con, event, dest)) 00231 goto done; 00232 } 00233 00234 if (dest == CLICK_BORDER) { 00235 DLOG("floating resize due to border click\n"); 00236 floating_resize_window(floatingcon, proportional, event); 00237 return 1; 00238 } 00239 00240 /* 6: dragging, if this was a click on a decoration (which did not lead 00241 * to a resize) */ 00242 if (!in_stacked && dest == CLICK_DECORATION) { 00243 floating_drag_window(floatingcon, event); 00244 return 1; 00245 } 00246 00247 goto done; 00248 } 00249 00250 if (in_stacked) { 00251 /* for stacked/tabbed cons, the resizing applies to the parent 00252 * container */ 00253 con = con->parent; 00254 } 00255 00256 /* 7: floating modifier pressed, initiate a resize */ 00257 if (dest == CLICK_INSIDE && mod_pressed && event->detail == 3) { 00258 if (floating_mod_on_tiled_client(con, event)) 00259 return 1; 00260 } 00261 /* 8: otherwise, check for border/decoration clicks and resize */ 00262 else if (dest == CLICK_BORDER || dest == CLICK_DECORATION) { 00263 DLOG("Trying to resize (tiling)\n"); 00264 tiling_resize(con, event, dest); 00265 } 00266 00267 done: 00268 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); 00269 xcb_flush(conn); 00270 tree_render(); 00271 return 0; 00272 } 00273 00274 /* 00275 * The button press X callback. This function determines whether the floating 00276 * modifier is pressed and where the user clicked (decoration, border, inside 00277 * the window). 00278 * 00279 * Then, route_click is called on the appropriate con. 00280 * 00281 */ 00282 int handle_button_press(xcb_button_press_event_t *event) { 00283 Con *con; 00284 DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); 00285 00286 last_timestamp = event->time; 00287 00288 const uint32_t mod = config.floating_modifier; 00289 const bool mod_pressed = (mod != 0 && (event->state & mod) == mod); 00290 DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); 00291 if ((con = con_by_window_id(event->event))) 00292 return route_click(con, event, mod_pressed, CLICK_INSIDE); 00293 00294 if (!(con = con_by_frame_id(event->event))) { 00295 ELOG("Clicked into unknown window?!\n"); 00296 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); 00297 xcb_flush(conn); 00298 return 0; 00299 } 00300 00301 /* Check if the click was on the decoration of a child */ 00302 Con *child; 00303 TAILQ_FOREACH(child, &(con->nodes_head), nodes) { 00304 if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) 00305 continue; 00306 00307 return route_click(child, event, mod_pressed, CLICK_DECORATION); 00308 } 00309 00310 return route_click(con, event, mod_pressed, CLICK_BORDER); 00311 }