From 24463718cc2ae6d44d75104bb8ade636985ff86c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 5 Mar 2011 20:23:29 +0100 Subject: [PATCH] refactor the click handling completely Also re-implements floating modifier on tiling cons. Fixes: #288 and a lot of headache :) --- src/click.c | 626 ++++++++++++++++------------------------------------ 1 file changed, 190 insertions(+), 436 deletions(-) diff --git a/src/click.c b/src/click.c index 51472954..8c0d7f66 100644 --- a/src/click.c +++ b/src/click.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -21,142 +21,53 @@ #include "all.h" -#if 0 -static struct Stack_Window *get_stack_window(xcb_window_t window_id) { - struct Stack_Window *current; - SLIST_FOREACH(current, &stack_wins, stack_windows) { - if (current->window != window_id) - continue; - - return current; - } - - return NULL; -} +typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_destination_t; /* - * Checks if the button press was on a stack window, handles focus setting and returns true - * if so, or false otherwise. + * Finds the correct pair of first/second cons between the resize will take + * place according to the passed border position (top, left, right, bottom), + * then calls resize_graphical_handler(). * */ -static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) { - struct Stack_Window *stack_win; - - /* If we find a corresponding stack window, we can handle the event */ - if ((stack_win = get_stack_window(event->event)) == NULL) - return false; - - /* A stack window was clicked, we check if it was button4 or button5 - which are scroll up / scroll down. */ - if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { - direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN); - focus_window_in_container(conn, CUR_CELL, direction); - return true; - } +static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) { + DLOG("border = %d\n", border); + char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n'); + orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ); - /* It was no scrolling, so we calculate the destination client by - dividing the Y position of the event through the height of a window - decoration and then set the focus to this client. */ - i3Font *font = load_font(conn, config.font); - int decoration_height = (font->height + 2 + 2); - int destination = (event->event_y / decoration_height), - c = 0, - num_clients = 0; - Client *client; - Container *container = stack_win->container; - - CIRCLEQ_FOREACH(client, &(container->clients), clients) - num_clients++; - - /* If we don’t have any clients in this container, we cannot do - * anything useful anyways. */ - if (num_clients == 0) - return true; - - if (container->mode == MODE_TABBED) - destination = (event->event_x / (container->width / num_clients)); - else if (container->mode == MODE_STACK && - container->stack_limit != STACK_LIMIT_NONE) { - if (container->stack_limit == STACK_LIMIT_COLS) { - int wrap = ceil((float)num_clients / container->stack_limit_value); - int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value)); - int clicked_row = (event->event_y / decoration_height); - DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); - destination = (wrap * clicked_column) + clicked_row; - } else { - int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); - int clicked_column = (event->event_x / width); - int clicked_row = (event->event_y / decoration_height); - DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); - destination = (container->stack_limit_value * clicked_column) + clicked_row; - } + /* look for a parent container with the right orientation */ + Con *first = NULL, *second = NULL; + Con *resize_con = con; + while (resize_con->type != CT_WORKSPACE && + resize_con->parent->orientation != orientation) + resize_con = resize_con->parent; + if (resize_con->type != CT_WORKSPACE && + resize_con->parent->orientation == orientation) { + first = resize_con; + second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); + if (second == TAILQ_END(&(first->nodes_head))) { + second = NULL; } - - DLOG("Click on stack_win for client %d\n", destination); - CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) - if (c++ == destination) { - set_focus(conn, client, true); - return true; - } - - return true; -} - -/* - * Checks if the button press was on a bar, switches to the workspace and returns true - * if so, or false otherwise. - * - */ -static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) { - Output *output; - TAILQ_FOREACH(output, &outputs, outputs) { - if (output->bar != event->event) - continue; - - DLOG("Click on a bar\n"); - - /* Check if the button was one of button4 or button5 (scroll up / scroll down) */ - if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { - Workspace *ws = c_ws; - if (event->detail == XCB_BUTTON_INDEX_5) { - while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { - if (ws->output == output) { - workspace_show(conn, ws->num + 1); - return true; - } - } - } else { - while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { - if (ws->output == output) { - workspace_show(conn, ws->num + 1); - return true; - } - } - } - return true; - } - int drawn = 0; - /* Because workspaces can be on different outputs, we need to loop - through all of them and decide to count it based on its ->output */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != output) - continue; - DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", - ws->num, drawn, ws->text_width); - if (event->event_x > (drawn + 1) && - event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) { - workspace_show(conn, ws->num + 1); - return true; - } - - drawn += ws->text_width + 5 + 5 + 2; - } - return true; + else if (way == 'p') { + Con *tmp = first; + first = second; + second = tmp; } + } + if (first == NULL || second == NULL) { + DLOG("Resize not possible\n"); return false; + } + else { + assert(first != second); + assert(first->parent == second->parent); + resize_graphical_handler(first, second, orientation, event); + } + + DLOG("After resize handler, rendering\n"); + tree_render(); + return true; } /* @@ -167,346 +78,189 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e * to the client). * */ -static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, - xcb_button_press_event_t *event) { - /* Only the right mouse button is interesting for us at the moment */ - if (event->detail != 3) - return false; - - /* The client is in tiling layout. We can still - * initiate a resize with the right mouse button, - * by chosing the border which is the most near one - * to the position of the mouse pointer */ - int to_right = client->rect.width - event->event_x, - to_left = event->event_x, - to_top = event->event_y, - to_bottom = client->rect.height - event->event_y; - resize_orientation_t orientation = O_VERTICAL; - Container *con = client->container; - Workspace *ws = con->workspace; - int first = 0, second = 0; - - DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", - to_right, to_left, to_top, to_bottom); - - if (to_right < to_left && - to_right < to_top && - to_right < to_bottom) { - /* …right border */ - first = con->col + (con->colspan - 1); - DLOG("column %d\n", first); - - if (!cell_exists(ws, first, con->row) || - (first == (ws->cols-1))) - return false; - - second = first + 1; - } else if (to_left < to_right && - to_left < to_top && - to_left < to_bottom) { - /* …left border */ - if (con->col == 0) - return false; - - first = con->col - 1; - second = con->col; - } else if (to_top < to_right && - to_top < to_left && - to_top < to_bottom) { - /* This was a press on the top border */ - if (con->row == 0) - return false; - first = con->row - 1; - second = con->row; - orientation = O_HORIZONTAL; - } else if (to_bottom < to_right && - to_bottom < to_left && - to_bottom < to_top) { - /* …bottom border */ - first = con->row + (con->rowspan - 1); - if (!cell_exists(ws, con->col, first) || - (first == (ws->rows-1))) - return false; - - second = first + 1; - orientation = O_HORIZONTAL; - } - - return resize_graphical_handler(conn, ws, first, second, orientation, event); +static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) { + /* The client is in tiling layout. We can still initiate a resize with the + * right mouse button, by chosing the border which is the most near one to + * the position of the mouse pointer */ + int to_right = con->rect.width - event->event_x, + to_left = event->event_x, + to_top = event->event_y, + to_bottom = con->rect.height - event->event_y; + + DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", + to_right, to_left, to_top, to_bottom); + + if (to_right < to_left && + to_right < to_top && + to_right < to_bottom) + return tiling_resize_for_border(con, BORDER_RIGHT, event); + + if (to_left < to_right && + to_left < to_top && + to_left < to_bottom) + return tiling_resize_for_border(con, BORDER_LEFT, event); + + if (to_top < to_right && + to_top < to_left && + to_top < to_bottom) + return tiling_resize_for_border(con, BORDER_TOP, event); + + if (to_bottom < to_right && + to_bottom < to_left && + to_bottom < to_top) + return tiling_resize_for_border(con, BORDER_BOTTOM, event); + + return false; } -#endif -int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { - /* TODO: dragging floating windows by grabbing their decoration does not - * work right now. We need to somehow recognize that special case: either - * we check if the con with the clicked decoration is the only con inside - * its parent (so that you can only drag single floating windows, not - * floating containers with multiple windows) *or* we somehow find out - * which decoration(s) are at the top and enable grabbing for them while - * resizing for the others. Maybe we could process the resizing parameters - * first and check if resizing is possible: if yes, resize, if not, drag. - * - * Also raise on click on decoration is not working. */ - Con *con; - DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); +/* + * Finds out which border was clicked on and calls tiling_resize_for_border(). + * + */ +static bool tiling_resize(Con *con, xcb_button_press_event_t *event, click_destination_t dest) { + /* check if this was a click on the window border (and on which one) */ + Rect bsr = con_border_style_rect(con); + DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n", + event->event_x, event->event_y, con, event->event); + DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); + if (dest == CLICK_DECORATION) + return tiling_resize_for_border(con, BORDER_TOP, event); - con = con_by_window_id(event->event); - bool border_click = (con == NULL); - const uint32_t mod = config.floating_modifier; - bool mod_pressed = (mod != 0 && (event->state & mod) == mod); + if (event->event_x >= 0 && event->event_x <= bsr.x && + event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) + return tiling_resize_for_border(con, BORDER_LEFT, event); - if (border_click) - con = con_by_frame_id(event->event); + if (event->event_x >= (con->window_rect.x + con->window_rect.width) && + event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) + return tiling_resize_for_border(con, BORDER_RIGHT, event); - DLOG("border_click = %d, mod_pressed = %d\n", border_click, mod_pressed); + if (event->event_y >= (con->window_rect.y + con->window_rect.height)) + return tiling_resize_for_border(con, BORDER_BOTTOM, event); - Con *clicked_into = NULL; + return false; +} - Con *child; - TAILQ_FOREACH(child, &(con->nodes_head), nodes) { - if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) - continue; +/* + * Being called by handle_button_press, this function calls the appropriate + * functions for resizing/dragging. + * + */ +static int route_click(Con *con, xcb_button_press_event_t *event, bool mod_pressed, click_destination_t dest) { + DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest); + DLOG("--> OUTCOME = %p\n", con); + DLOG("type = %d, name = %s\n", con->type, con->name); - clicked_into = child; - break; - } + /* get the floating con */ + Con *floatingcon = con_inside_floating(con); + const bool proportional = (event->state & BIND_SHIFT); + + /* 1: focus this con */ + con_focus(con); + + const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); - DLOG("clicked_into = %p\n", clicked_into); + /* 2: for floating containers, we also want to raise them on click */ + if (floatingcon != NULL) { + floating_raise_con(floatingcon); - /* See if this was a click with the configured modifier. If so, we need - * to move around the client if it was floating. if not, we just process - * as usual. */ - DLOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); - if (border_click || mod_pressed) { - if (con == NULL) { - LOG("Not handling, floating_modifier was pressed and no client found\n"); + /* 3: floating_modifier plus left mouse button drags */ + if (mod_pressed && event->detail == 1) { + floating_drag_window(floatingcon, event); return 1; } - DLOG("handling\n"); -#if 0 - if (con->fullscreen) { - LOG("Not handling, client is in fullscreen mode\n"); - return 1; - } -#endif - Con *floatingcon = con; - if ((border_click && con->type == CT_FLOATING_CON) || - ((floatingcon = con_inside_floating(con)) != NULL && - clicked_into == NULL)) { - LOG("button %d pressed\n", event->detail); - if (event->detail == 1) { - LOG("left mouse button, dragging\n"); - floating_drag_window(floatingcon, event); - } - else if (event->detail == 3) { - bool proportional = (event->state & BIND_SHIFT); - DLOG("right mouse button\n"); - floating_resize_window(floatingcon, proportional, event); - } + + /* 3: resize (floating) if this was a click on the left/right/bottom + * border. also try resizing (tiling) if it was a click on the top + * border, but continue if that does not work */ + if (mod_pressed && event->detail == 3) { + DLOG("floating resize due to floatingmodifier\n"); + floating_resize_window(floatingcon, proportional, event); return 1; } - } - - if ((clicked_into ? clicked_into->parent->type : con->parent->type) == CT_DOCKAREA) { - DLOG("Not handling, this client is inside a dockarea\n"); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 0; - } - - /* click to focus, either on the clicked window or its child if thas was a - * click into a child decoration */ - con_focus((clicked_into ? clicked_into : con)); - /* for floating containers, we also want to raise them on click */ - Con *floatingcon = con_inside_floating(con); - if (floatingcon != NULL) - floating_raise_con(floatingcon); + if (!in_stacked && dest == CLICK_DECORATION) { + /* try tiling resize, but continue if it doesn’t work */ + DLOG("tiling resize with fallback\n"); + if (tiling_resize(con, event, dest)) + goto done; + } - tree_render(); + if (dest == CLICK_BORDER) { + DLOG("floating resize due to border click\n"); + floating_resize_window(floatingcon, proportional, event); + return 1; + } - /* if we clicked into a child decoration on a stacked/tabbed container, we - * are done and don’t want to resize */ - if (clicked_into && - (con->layout == L_STACKED || con->layout == L_TABBED)) - return 1; + /* 4: dragging, if this was a click on a decoration (which did not lead + * to a resize) */ + if (!in_stacked && dest == CLICK_DECORATION) { + floating_drag_window(floatingcon, event); + return 1; + } - /* check if this was a click on the window border (and on which one) */ - Rect bsr = con_border_style_rect(con); - DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x, border_click = %d, clicked_into = %p\n", - event->event_x, event->event_y, con, event->event, border_click, clicked_into); - DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); - char way = '\0'; - int orientation; - if (clicked_into) { - DLOG("BORDER top\n"); - way = 'p'; - orientation = VERT; - } else if (event->event_x >= 0 && event->event_x <= bsr.x && - event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { - DLOG("BORDER left\n"); - way = 'p'; - orientation = HORIZ; - } else if (event->event_x >= (con->window_rect.x + con->window_rect.width) && - event->event_y >= bsr.y && event->event_y <= con->rect.height + bsr.height) { - DLOG("BORDER right\n"); - way = 'n'; - orientation = HORIZ; - } else if (event->event_y >= (con->window_rect.y + con->window_rect.height)) { - DLOG("BORDER bottom\n"); - way = 'n'; - orientation = VERT; + goto done; } - /* look for a parent container with the right orientation */ - Con *first = NULL, *second = NULL; - if (way != '\0') { - Con *resize_con = clicked_into ? clicked_into : con; - while (resize_con->type != CT_WORKSPACE && - resize_con->parent->orientation != orientation) - resize_con = resize_con->parent; - if (resize_con->type != CT_WORKSPACE && - resize_con->parent->orientation == orientation) { - first = resize_con; - second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); - if (second == TAILQ_END(&(first->nodes_head))) { - second = NULL; - } - else if (way == 'p') { - Con *tmp = first; - first = second; - second = tmp; - } - } + if (in_stacked) { + /* for stacked/tabbed cons, floating modifier + right click resizes the + * parent container */ + if (mod_pressed && event->detail == 3) + if (floating_mod_on_tiled_client(con->parent, event)) + return 1; + goto done; } - if (first == NULL || second == NULL) { - DLOG("Replaying click\n"); - /* No border click, replay the click */ - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 0; + /* 3: floating modifier pressed, initiate a resize */ + if (mod_pressed && event->detail == 3) { + if (floating_mod_on_tiled_client(con, event)) + return 1; } - else { - assert(first != second); - assert(first->parent == second->parent); - resize_graphical_handler(first, second, orientation, event); + /* 4: otherwise, check for border/decoration clicks and resize */ + else if (dest == CLICK_BORDER || dest == CLICK_DECORATION) { + DLOG("Should trry resizing (tiling)\n"); + tiling_resize(con, event, dest); } - DLOG("After resize handler, rendering\n"); +done: + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); tree_render(); - return 0; -#if 0 - if (client == NULL) { - /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */ - if (button_press_stackwin(conn, event)) - return 1; - - /* Or on a bar? */ - if (button_press_bar(conn, event)) - return 1; - - DLOG("Could not handle this button press\n"); - return 1; - } - - /* Set focus in any case */ - set_focus(conn, client, true); - - /* Let’s see if this was on the borders (= resize). If not, we’re done */ - DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); - resize_orientation_t orientation = O_VERTICAL; - Container *con = client->container; - int first, second; - - if (client->dock) { - DLOG("dock. done.\n"); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 1; - } - - DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); - - /* Some clients (xfontsel for example) seem to pass clicks on their - * window to the parent window, thus we receive an event here which in - * reality is a border_click. Check for the position and fix state. */ - if (border_click && - event->event_x >= client->child_rect.x && - event->event_x <= (client->child_rect.x + client->child_rect.width) && - event->event_y >= client->child_rect.y && - event->event_y <= (client->child_rect.y + client->child_rect.height)) { - DLOG("Fixing border_click = false because of click in child\n"); - border_click = false; - } +} - if (!border_click) { - DLOG("client. done.\n"); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - /* Floating clients should be raised on click */ - if (client_is_floating(client)) - xcb_raise_window(conn, client->frame); - xcb_flush(conn); - return 1; - } +/* + * The button press X callback. This function determines whether the floating + * modifier is pressed and where the user clicked (decoration, border, inside + * the window). + * + * Then, route_click is called on the appropriate con. + * + */ +int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { + Con *con; + DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); - /* Don’t handle events inside the titlebar, only borders are interesting */ - i3Font *font = load_font(conn, config.font); - if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { - DLOG("click on titlebar\n"); + const uint32_t mod = config.floating_modifier; + bool mod_pressed = (mod != 0 && (event->state & mod) == mod); + DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); + if ((con = con_by_window_id(event->event))) + return route_click(con, event, mod_pressed, CLICK_INSIDE); - /* Floating clients can be dragged by grabbing their titlebar */ - if (client_is_floating(client)) { - /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ - xcb_raise_window(conn, client->frame); - xcb_flush(conn); + if (!(con = con_by_frame_id(event->event))) { + ELOG("Clicked into unknown window?!\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + return 0; + } - floating_drag_window(conn, client, event); - } - return 1; - } + /* Check if the click was on the decoration of a child */ + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) + continue; - if (client_is_floating(client)) - return floating_border_click(conn, client, event); - - Workspace *ws = con->workspace; - - if (event->event_y < 2) { - /* This was a press on the top border */ - if (con->row == 0) - return 1; - first = con->row - 1; - second = con->row; - orientation = O_HORIZONTAL; - } else if (event->event_y >= (client->rect.height - 2)) { - /* …bottom border */ - first = con->row + (con->rowspan - 1); - if (!cell_exists(ws, con->col, first) || - (first == (ws->rows-1))) - return 1; - - second = first + 1; - orientation = O_HORIZONTAL; - } else if (event->event_x <= 2) { - /* …left border */ - if (con->col == 0) - return 1; - - first = con->col - 1; - second = con->col; - } else if (event->event_x > 2) { - /* …right border */ - first = con->col + (con->colspan - 1); - DLOG("column %d\n", first); - - if (!cell_exists(ws, first, con->row) || - (first == (ws->cols-1))) - return 1; - - second = first + 1; - } + return route_click(child, event, mod_pressed, CLICK_DECORATION); + } - return resize_graphical_handler(conn, ws, first, second, orientation, event); -#endif + return route_click(con, event, mod_pressed, CLICK_BORDER); } -- 2.39.2