X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Ffloating.c;h=f29943393c208c60913449fedbfe04036ba5fb2d;hb=50edf495aa3971bfb67471c3aaf2eb72e7abd443;hp=e6ca2d7188d6392c172aa09bf17d6eb0588f6544;hpb=487ccb536a6746656c1635078c7ad44892703d71;p=i3%2Fi3 diff --git a/src/floating.c b/src/floating.c index e6ca2d71..f2994339 100644 --- a/src/floating.c +++ b/src/floating.c @@ -1,23 +1,26 @@ -#undef I3__FILE__ -#define I3__FILE__ "floating.c" /* * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * floating.c: Floating windows. * */ #include "all.h" -extern xcb_connection_t *conn; +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif /* * Calculates sum of heights and sum of widths of all currently active outputs * */ static Rect total_outputs_dimensions(void) { + if (TAILQ_EMPTY(&outputs)) + return (Rect){0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels}; + Output *output; /* Use Rect to encapsulate dimensions, ignoring x/y */ Rect outputs_dimensions = {0, 0, 0, 0}; @@ -28,6 +31,34 @@ static Rect total_outputs_dimensions(void) { return outputs_dimensions; } +/* + * Updates I3_FLOATING_WINDOW by either setting or removing it on the con and + * all its children. + * + */ +static void floating_set_hint_atom(Con *con, bool floating) { + if (!con_is_leaf(con)) { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + floating_set_hint_atom(child, floating); + } + } + + if (con->window == NULL) { + return; + } + + if (floating) { + uint32_t val = 1; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id, + A_I3_FLOATING_WINDOW, XCB_ATOM_CARDINAL, 32, 1, &val); + } else { + xcb_delete_property(conn, con->window->id, A_I3_FLOATING_WINDOW); + } + + xcb_flush(conn); +} + /** * Called when a floating window is created or resized. * This function resizes the window if its size is higher or lower than the @@ -41,71 +72,96 @@ void floating_check_size(Con *floating_con) { Rect floating_sane_max_dimensions; Con *focused_con = con_descend_focused(floating_con); - /* obey size increments */ - if (focused_con->height_increment || focused_con->width_increment) { - Rect border_rect = con_border_style_rect(focused_con); - - /* We have to do the opposite calculations that render_con() do - * to get the exact size we want. */ - border_rect.width = -border_rect.width; - border_rect.width += 2 * focused_con->border_width; - border_rect.height = -border_rect.height; - border_rect.height += 2 * focused_con->border_width; - if (con_border_style(focused_con) == BS_NORMAL) - border_rect.height += render_deco_height(); - - if (focused_con->height_increment && - floating_con->rect.height >= focused_con->base_height + border_rect.height) { - floating_con->rect.height -= focused_con->base_height + border_rect.height; - floating_con->rect.height -= floating_con->rect.height % focused_con->height_increment; - floating_con->rect.height += focused_con->base_height + border_rect.height; + Rect border_rect = con_border_style_rect(focused_con); + /* We have to do the opposite calculations that render_con() do + * to get the exact size we want. */ + border_rect.width = -border_rect.width; + border_rect.width += 2 * focused_con->border_width; + border_rect.height = -border_rect.height; + border_rect.height += 2 * focused_con->border_width; + if (con_border_style(focused_con) == BS_NORMAL) { + border_rect.height += render_deco_height(); + } + + if (focused_con->window != NULL) { + if (focused_con->window->min_width) { + floating_con->rect.width -= border_rect.width; + floating_con->rect.width = max(floating_con->rect.width, focused_con->window->min_width); + floating_con->rect.width += border_rect.width; + } + + if (focused_con->window->min_height) { + floating_con->rect.height -= border_rect.height; + floating_con->rect.height = max(floating_con->rect.height, focused_con->window->min_height); + floating_con->rect.height += border_rect.height; } - if (focused_con->width_increment && - floating_con->rect.width >= focused_con->base_width + border_rect.width) { - floating_con->rect.width -= focused_con->base_width + border_rect.width; - floating_con->rect.width -= floating_con->rect.width % focused_con->width_increment; - floating_con->rect.width += focused_con->base_width + border_rect.width; + if (focused_con->window->height_increment && + floating_con->rect.height >= focused_con->window->base_height + border_rect.height) { + floating_con->rect.height -= focused_con->window->base_height + border_rect.height; + floating_con->rect.height -= floating_con->rect.height % focused_con->window->height_increment; + floating_con->rect.height += focused_con->window->base_height + border_rect.height; + } + + if (focused_con->window->width_increment && + floating_con->rect.width >= focused_con->window->base_width + border_rect.width) { + floating_con->rect.width -= focused_con->window->base_width + border_rect.width; + floating_con->rect.width -= floating_con->rect.width % focused_con->window->width_increment; + floating_con->rect.width += focused_con->window->base_width + border_rect.width; } } - /* Unless user requests otherwise (-1), ensure width/height do not exceed - * configured maxima or, if unconfigured, limit to combined width of all - * outputs */ + /* Unless user requests otherwise (-1), raise the width/height to + * reasonable minimum dimensions */ if (config.floating_minimum_height != -1) { - if (config.floating_minimum_height == 0) + floating_con->rect.height -= border_rect.height; + if (config.floating_minimum_height == 0) { floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height); - else + } else { floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height); + } + floating_con->rect.height += border_rect.height; } + if (config.floating_minimum_width != -1) { - if (config.floating_minimum_width == 0) + floating_con->rect.width -= border_rect.width; + if (config.floating_minimum_width == 0) { floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width); - else + } else { floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width); + } + floating_con->rect.width += border_rect.width; } - /* Unless user requests otherwise (-1), raise the width/height to - * reasonable minimum dimensions */ + /* Unless user requests otherwise (-1), ensure width/height do not exceed + * configured maxima or, if unconfigured, limit to combined width of all + * outputs */ floating_sane_max_dimensions = total_outputs_dimensions(); if (config.floating_maximum_height != -1) { - if (config.floating_maximum_height == 0) + floating_con->rect.height -= border_rect.height; + if (config.floating_maximum_height == 0) { floating_con->rect.height = min(floating_con->rect.height, floating_sane_max_dimensions.height); - else + } else { floating_con->rect.height = min(floating_con->rect.height, config.floating_maximum_height); + } + floating_con->rect.height += border_rect.height; } + if (config.floating_maximum_width != -1) { - if (config.floating_maximum_width == 0) + floating_con->rect.width -= border_rect.width; + if (config.floating_maximum_width == 0) { floating_con->rect.width = min(floating_con->rect.width, floating_sane_max_dimensions.width); - else + } else { floating_con->rect.width = min(floating_con->rect.width, config.floating_maximum_width); + } + floating_con->rect.width += border_rect.width; } } void floating_enable(Con *con, bool automatic) { bool set_focus = (con == focused); - if (con->parent && con->parent->type == CT_DOCKAREA) { + if (con_is_docked(con)) { LOG("Container is a dock window, not enabling floating mode.\n"); return; } @@ -115,51 +171,13 @@ void floating_enable(Con *con, bool automatic) { return; } - /* 1: If the container is a workspace container, we need to create a new - * split-container with the same layout and make that one floating. We - * cannot touch the workspace container itself because floating containers - * are children of the workspace. */ if (con->type == CT_WORKSPACE) { - LOG("This is a workspace, creating new container around content\n"); - if (con_num_children(con) == 0) { - LOG("Workspace is empty, aborting\n"); - return; - } - /* TODO: refactor this with src/con.c:con_set_layout */ - Con *new = con_new(NULL, NULL); - new->parent = con; - new->layout = con->layout; - - /* since the new container will be set into floating mode directly - * afterwards, we need to copy the workspace rect. */ - memcpy(&(new->rect), &(con->rect), sizeof(Rect)); - - Con *old_focused = TAILQ_FIRST(&(con->focus_head)); - if (old_focused == TAILQ_END(&(con->focus_head))) - old_focused = NULL; - - /* 4: move the existing cons of this workspace below the new con */ - DLOG("Moving cons\n"); - Con *child; - while (!TAILQ_EMPTY(&(con->nodes_head))) { - child = TAILQ_FIRST(&(con->nodes_head)); - con_detach(child); - con_attach(child, new, true); - } - - /* 4: attach the new split container to the workspace */ - DLOG("Attaching new split to ws\n"); - con_attach(new, con, false); - - if (old_focused) - con_focus(old_focused); - - con = new; - set_focus = false; + LOG("Container is a workspace, not enabling floating mode.\n"); + return; } /* 1: detach the container from its parent */ - /* TODO: refactor this with tree_close() */ + /* TODO: refactor this with tree_close_internal() */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); @@ -177,7 +195,7 @@ void floating_enable(Con *con, bool automatic) { nc->layout = L_SPLITH; /* We insert nc already, even though its rect is not yet calculated. This * is necessary because otherwise the workspace might be empty (and get - * closed in tree_close()) even though it’s not. */ + * closed in tree_close_internal()) even though it’s not. */ TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused); @@ -185,7 +203,10 @@ void floating_enable(Con *con, bool automatic) { if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && con_num_children(con->parent) == 0) { DLOG("Old container empty after setting this child to floating, closing\n"); - tree_close(con->parent, DONT_KILL_WINDOW, false, false); + Con *parent = con->parent; + /* clear the pointer before calling tree_close_internal in which the memory is freed */ + con->parent = NULL; + tree_close_internal(parent, DONT_KILL_WINDOW, false, false); } char *name; @@ -212,7 +233,8 @@ void floating_enable(Con *con, bool automatic) { } } - floating_check_size(nc); + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); /* 3: attach the child to the new parent container. We need to do this * because con_border_style_rect() needs to access con->parent. */ @@ -231,13 +253,16 @@ void floating_enable(Con *con, bool automatic) { nc->rect.width -= border_style_rect.width; /* Add some more pixels for the title bar */ - if (con_border_style(con) == BS_NORMAL) + if (con_border_style(con) == BS_NORMAL) { nc->rect.height += deco_height; + } /* Honor the X11 border */ nc->rect.height += con->border_width * 2; nc->rect.width += con->border_width * 2; + floating_check_size(nc); + /* Some clients (like GIMP’s color picker window) get mapped * to (0, 0), so we push them to a reasonable position * (centered over their leader) */ @@ -246,12 +271,10 @@ void floating_enable(Con *con, bool automatic) { if (con->window && con->window->leader != XCB_NONE && (leader = con_by_window_id(con->window->leader)) != NULL) { DLOG("Centering above leader\n"); - nc->rect.x = leader->rect.x + (leader->rect.width / 2) - (nc->rect.width / 2); - nc->rect.y = leader->rect.y + (leader->rect.height / 2) - (nc->rect.height / 2); + floating_center(nc, leader->rect); } else { /* center the window on workspace as fallback */ - nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2); - nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2); + floating_center(nc, ws->rect); } } @@ -286,9 +309,6 @@ void floating_enable(Con *con, bool automatic) { DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height); - TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); - TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); - /* render the cons to get initial window_rect correct */ render_con(nc, false); render_con(con, false); @@ -299,20 +319,19 @@ void floating_enable(Con *con, bool automatic) { /* Check if we need to re-assign it to a different workspace because of its * coordinates and exit if that was done successfully. */ if (floating_maybe_reassign_ws(nc)) { - ipc_send_window_event("floating", con); - return; + goto done; } /* Sanitize coordinates: Check if they are on any output */ if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) { - ipc_send_window_event("floating", con); - return; + goto done; } ELOG("No output found at destination coordinates, centering floating window on current ws\n"); - nc->rect.x = ws->rect.x + (ws->rect.width / 2) - (nc->rect.width / 2); - nc->rect.y = ws->rect.y + (ws->rect.height / 2) - (nc->rect.height / 2); + floating_center(nc, ws->rect); +done: + floating_set_hint_atom(nc, true); ipc_send_window_event("floating", con); } @@ -325,6 +344,7 @@ void floating_disable(Con *con, bool automatic) { const bool set_focus = (con == focused); Con *ws = con_get_workspace(con); + Con *parent = con->parent; /* 1: detach from parent container */ TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes); @@ -333,7 +353,9 @@ void floating_disable(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, DONT_KILL_WINDOW, true, false); + /* clear the pointer before calling tree_close_internal in which the memory is freed */ + con->parent = NULL; + tree_close_internal(parent, DONT_KILL_WINDOW, true, false); /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ @@ -358,6 +380,7 @@ void floating_disable(Con *con, bool automatic) { if (set_focus) con_focus(con); + floating_set_hint_atom(con, false); ipc_send_window_event("floating", con); } @@ -369,6 +392,12 @@ void floating_disable(Con *con, bool automatic) { * */ void toggle_floating_mode(Con *con, bool automatic) { + /* forbid the command to toggle floating on a CT_FLOATING_CON */ + if (con->type == CT_FLOATING_CON) { + ELOG("Cannot toggle floating mode on con = %p because it is of type CT_FLOATING_CON.\n", con); + return; + } + /* see if the client is already floating */ if (con_is_floating(con)) { LOG("already floating, re-setting to tiling\n"); @@ -415,11 +444,56 @@ bool floating_maybe_reassign_ws(Con *con) { Con *content = output_get_content(output->con); Con *ws = TAILQ_FIRST(&(content->focus_head)); DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name); - con_move_to_workspace(con, ws, false, true); + con_move_to_workspace(con, ws, false, true, false); con_focus(con_descend_focused(con)); return true; } +/* + * Centers a floating con above the specified rect. + * + */ +void floating_center(Con *con, Rect rect) { + con->rect.x = rect.x + (rect.width / 2) - (con->rect.width / 2); + con->rect.y = rect.y + (rect.height / 2) - (con->rect.height / 2); +} + +/* + * Moves the given floating con to the current pointer position. + * + */ +void floating_move_to_pointer(Con *con) { + assert(con->type == CT_FLOATING_CON); + + xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL); + if (reply == NULL) { + ELOG("could not query pointer position, not moving this container\n"); + return; + } + + Output *output = get_output_containing(reply->root_x, reply->root_y); + if (output == NULL) { + ELOG("The pointer is not on any output, cannot move the container here.\n"); + return; + } + + /* Determine where to put the window. */ + int32_t x = reply->root_x - con->rect.width / 2; + int32_t y = reply->root_y - con->rect.height / 2; + FREE(reply); + + /* Correct target coordinates to be in-bounds. */ + x = MAX(x, (int32_t)output->rect.x); + y = MAX(y, (int32_t)output->rect.y); + if (x + con->rect.width > output->rect.x + output->rect.width) + x = output->rect.x + output->rect.width - con->rect.width; + if (y + con->rect.height > output->rect.y + output->rect.height) + y = output->rect.y + output->rect.height - con->rect.height; + + /* Update container's coordinates to position it correctly. */ + floating_reposition(con, (Rect){x, y, con->rect.width, con->rect.height}); +} + DRAGGING_CB(drag_window_callback) { const struct xcb_button_press_event_t *event = extra; @@ -577,7 +651,7 @@ void floating_resize_window(Con *con, const bool proportional, con->scratchpad_state = SCRATCHPAD_CHANGED; } -/* As endorsed by “ASSOCIATING CUSTOM DATA WITH A WATCHER” in ev(3) */ +/* Custom data structure used to track dragging-related events. */ struct drag_x11_cb { ev_check check; @@ -599,7 +673,7 @@ struct drag_x11_cb { }; static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { - struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w; + struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data; xcb_motion_notify_event_t *last_motion_notify = NULL; xcb_generic_event_t *event; @@ -621,7 +695,7 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { break; case XCB_KEY_PRESS: - DLOG("A key was pressed during drag, reverting changes."); + DLOG("A key was pressed during drag, reverting changes.\n"); dragloop->result = DRAG_REVERT; handle_event(type, event); break; @@ -738,16 +812,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ .callback = callback, .extra = extra, }; + ev_check *check = &loop.check; if (con) loop.old_rect = con->rect; - ev_check_init(&loop.check, xcb_drag_check_cb); + ev_check_init(check, xcb_drag_check_cb); + check->data = &loop; main_set_x11_cb(false); - ev_check_start(main_loop, &loop.check); + ev_check_start(main_loop, check); while (loop.result == DRAGGING) ev_run(main_loop, EVRUN_ONCE); - ev_check_stop(main_loop, &loop.check); + ev_check_stop(main_loop, check); main_set_x11_cb(true); xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); @@ -783,6 +859,37 @@ void floating_reposition(Con *con, Rect newrect) { tree_render(); } +/* + * Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the + * actual size with regard to size constraints taken from user settings. + * Additionally, the dimensions may be upscaled until they're divisible by the + * window's size hints. + * + */ +void floating_resize(Con *floating_con, int x, int y) { + DLOG("floating resize to %dx%d px\n", x, y); + Rect *rect = &floating_con->rect; + Con *focused_con = con_descend_focused(floating_con); + if (focused_con->window == NULL) { + DLOG("No window is focused. Not resizing.\n"); + return; + } + int wi = focused_con->window->width_increment; + int hi = focused_con->window->height_increment; + rect->width = x; + rect->height = y; + if (wi) + rect->width += (wi - 1 - rect->width) % wi; + if (hi) + rect->height += (hi - 1 - rect->height) % hi; + + floating_check_size(floating_con); + + /* If this is a scratchpad window, don't auto center it from now on. */ + if (floating_con->scratchpad_state == SCRATCHPAD_FRESH) + floating_con->scratchpad_state = SCRATCHPAD_CHANGED; +} + /* * Fixes the coordinates of the floating window whenever the window gets * reassigned to a different output (or when the output’s rect changes). @@ -809,80 +916,3 @@ void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect) { con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) / (int32_t)old_rect->height - (int32_t)(con->rect.height / 2); DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y); } - -#if 0 -/* - * Moves the client 10px to the specified direction. - * - */ -void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { - DLOG("floating move\n"); - - Rect destination = currently_focused->rect; - Rect *screen = &(currently_focused->workspace->output->rect); - - switch (direction) { - case D_LEFT: - destination.x -= 10; - break; - case D_RIGHT: - destination.x += 10; - break; - case D_UP: - destination.y -= 10; - break; - case D_DOWN: - destination.y += 10; - break; - /* to make static analyzers happy */ - default: - break; - } - - /* Prevent windows from vanishing completely */ - if ((int32_t)(destination.x + destination.width - 5) <= (int32_t)screen->x || - (int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) || - (int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y || - (int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) { - DLOG("boundary check failed, not moving\n"); - return; - } - - currently_focused->rect = destination; - reposition_client(conn, currently_focused); - - /* Because reposition_client does not send a faked configure event (only resize does), - * we need to initiate that on our own */ - fake_absolute_configure_notify(conn, currently_focused); - /* fake_absolute_configure_notify flushes */ -} - -/* - * Hides all floating clients (or show them if they are currently hidden) on - * the specified workspace. - * - */ -void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { - Client *client; - - workspace->floating_hidden = !workspace->floating_hidden; - DLOG("floating_hidden is now: %d\n", workspace->floating_hidden); - TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) { - if (workspace->floating_hidden) - client_unmap(conn, client); - else client_map(conn, client); - } - - /* If we just unmapped all floating windows we should ensure that the focus - * is set correctly, that ist, to the first non-floating client in stack */ - if (workspace->floating_hidden) - SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) { - if (client_is_floating(client)) - continue; - set_focus(conn, client, true); - return; - } - - xcb_flush(conn); -} -#endif