X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fclick.c;h=23b6be4f15483d417101221f8f7a778c9cf0c2c5;hb=884627ef20dfb2edae9a5c99bb470bfbf0301904;hp=dc73cbdc7ddc36711e948fb00dde2f725cabbede;hpb=574e6b51d18cffb9bffcdd7dcde0e2df92560ae6;p=i3%2Fi3 diff --git a/src/click.c b/src/click.c index dc73cbdc..23b6be4f 100644 --- a/src/click.c +++ b/src/click.c @@ -1,16 +1,16 @@ +#undef I3__FILE__ +#define I3__FILE__ "click.c" /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * - * © 2009-2010 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - * src/click.c: Contains the handlers for button press (mouse click) events - * because they are quite large. + * click.c: Button press (mouse click) events. * */ +#include "all.h" + #include #include @@ -19,144 +19,64 @@ #include -#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, con = %p\n", border, con); + char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n'); + orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ); + + /* 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->type != CT_FLOATING_CON && + con_orientation(resize_con->parent) != orientation) + resize_con = resize_con->parent; + + DLOG("resize_con = %p\n", resize_con); + if (resize_con->type != CT_WORKSPACE && + resize_con->type != CT_FLOATING_CON && + con_orientation(resize_con->parent) == 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; } - - /* 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; - } + else if (way == 'p') { + Con *tmp = first; + first = second; + second = tmp; } + DLOG("first = %p, second = %p, resize_con = %p\n", + first, second, resize_con); + } - 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; - } + if (first == NULL || second == NULL) { + DLOG("Resize not possible\n"); + return false; + } - return true; -} + assert(first != second); + assert(first->parent == second->parent); -/* - * 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; - } + /* We modify the X/Y position in the event so that the divider line is at + * the actual position of the border, not at the position of the click. */ + if (orientation == HORIZ) + event->root_x = second->rect.x; + else event->root_y = second->rect.y; - return false; + resize_graphical_handler(first, second, orientation, event); + + DLOG("After resize handler, rendering\n"); + tree_render(); + return true; } /* @@ -167,254 +87,242 @@ 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; +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; +} + +/* + * 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, const 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) { + /* The user clicked on a window decoration. We ignore the following case: + * The container is a h-split, tabbed or stacked container with > 1 + * window. Decorations will end up next to each other and the user + * expects to switch to a window by clicking on its decoration. */ + + /* Since the container might either be the child *or* already a split + * container (in the case of a nested split container), we need to make + * sure that we are dealing with the split container here. */ + Con *check_con = con; + if (con_is_leaf(check_con) && check_con->parent->type == CT_CON) + check_con = check_con->parent; + + if ((check_con->layout == L_STACKED || + check_con->layout == L_TABBED || + con_orientation(check_con) == HORIZ) && + con_num_children(check_con) > 1) { + DLOG("Not handling this resize, this container has > 1 child.\n"); + return false; } + return tiling_resize_for_border(con, BORDER_TOP, event); + } - return resize_graphical_handler(conn, ws, first, second, orientation, event); -} -#endif + 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); -int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { - Con *con; - LOG("Button %d pressed on window 0x%08x\n", event->state, 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); - con = con_by_window_id(event->event); - bool border_click = false; - if (con == NULL) { - con = con_by_frame_id(event->event); - border_click = true; - } - LOG("border_click = %d\n", border_click); - //if (con && con->type == CT_FLOATING_CON) - //con = TAILQ_FIRST(&(con->nodes_head)); - - /* 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. */ - LOG("state = %d, floating_modifier = %d\n", event->state, config.floating_modifier); - if (border_click || - (config.floating_modifier != 0 && - (event->state & config.floating_modifier) == config.floating_modifier)) { - if (con == NULL) { - LOG("Not handling, floating_modifier was pressed and no client found\n"); - return 1; - } - LOG("handling\n"); -#if 0 - if (con->fullscreen) { - LOG("Not handling, client is in fullscreen mode\n"); - return 1; - } -#endif - if ((border_click && con->type == CT_FLOATING_CON) || - (!border_click && con_is_floating(con))) { - /* floating operations are always on the container around - * the "payload container", so make sure we use the right one */ - Con *floatingcon = (border_click ? con : con->parent); - 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); - } - return 1; - } - -#if 0 - if (!floating_mod_on_tiled_client(conn, client, event)) { -#endif - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); -#if 0 - } -#endif - - return 1; - } + if (event->event_y >= (con->window_rect.y + con->window_rect.height)) + return tiling_resize_for_border(con, BORDER_BOTTOM, event); - /* click to focus */ - con_focus(con); - tree_render(); + return false; +} - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - 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; +/* + * 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, const bool mod_pressed, const 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); + + /* don’t handle dockarea cons, they must not be focused */ + if (con->parent->type == CT_DOCKAREA) + goto done; + + /* get the floating con */ + Con *floatingcon = con_inside_floating(con); + const bool proportional = (event->state & BIND_SHIFT); + const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); + + /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ + if (in_stacked && + dest == CLICK_DECORATION && + (event->detail == XCB_BUTTON_INDEX_4 || + event->detail == XCB_BUTTON_INDEX_5)) { + DLOG("Scrolling on a window decoration\n"); + orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); + /* To prevent scrolling from going outside the container (see ticket + * #557), we first check if scrolling is possible at all. */ + Con *focused = con_descend_focused(con->parent); + bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); + bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL); + if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible) + tree_next('p', orientation); + else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible) + tree_next('n', orientation); + goto done; + } + + /* 2: focus this con. If the workspace is on another output we need to + * do a workspace_show in order for i3bar (and others) to notice the + * change in workspace. */ + Con *ws = con_get_workspace(con); + Con *focused_workspace = con_get_workspace(focused); + + if (ws != focused_workspace) + workspace_show(ws); + focused_id = XCB_NONE; + con_focus(con); + + /* 3: For floating containers, we also want to raise them on click. + * We will skip handling events on floating cons in fullscreen mode */ + Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL); + if (floatingcon != NULL && fs == NULL) { + floating_raise_con(floatingcon); + + /* 4: floating_modifier plus left mouse button drags */ + if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) { + floating_drag_window(floatingcon, event); + 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; + /* 5: 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 (client->dock) { - DLOG("dock. done.\n"); - xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); - xcb_flush(conn); - return 1; + 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; } - 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 (dest == CLICK_BORDER) { + DLOG("floating resize due to border click\n"); + floating_resize_window(floatingcon, proportional, event); + return 1; } - 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; + /* 6: 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; } - /* 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"); + goto done; + } + + if (in_stacked) { + /* for stacked/tabbed cons, the resizing applies to the parent + * container */ + con = con->parent; + } + + /* 7: floating modifier pressed, initiate a resize */ + if (dest == CLICK_INSIDE && mod_pressed && event->detail == 3) { + if (floating_mod_on_tiled_client(con, event)) + return 1; + } + /* 8: otherwise, check for border/decoration clicks and resize */ + else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) && + (event->detail == XCB_BUTTON_INDEX_1 || + event->detail == XCB_BUTTON_INDEX_3)) { + DLOG("Trying to resize (tiling)\n"); + tiling_resize(con, event, dest); + } + +done: + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + tree_render(); + return 0; +} - /* 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); +/* + * 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(xcb_button_press_event_t *event) { + Con *con; + DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); - floating_drag_window(conn, client, event); - } - return 1; - } + last_timestamp = event->time; - 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; - } + const uint32_t mod = config.floating_modifier; + const 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); + + 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; + } + + /* 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; + + 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); }