X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Ffloating.c;h=fc68df8e6b587482ab5043751a56bc711c23db85;hb=83ac1c0e72f314152e559d33f062677dc89cd99e;hp=d7a330678d1d2d8227e3037c9a690c3d30bbb861;hpb=039494165a6ab1b98bb14225e76827d904fa0e69;p=i3%2Fi3 diff --git a/src/floating.c b/src/floating.c index d7a33067..fc68df8e 100644 --- a/src/floating.c +++ b/src/floating.c @@ -1,5 +1,3 @@ -#undef I3__FILE__ -#define I3__FILE__ "floating.c" /* * vim:ts=4:sw=4:expandtab * @@ -11,7 +9,9 @@ */ #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 @@ -31,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 @@ -44,18 +72,29 @@ 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->window != NULL && (focused_con->window->height_increment || focused_con->window->width_increment)) { - Rect border_rect = con_border_style_rect(focused_con); + 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(); + } - /* 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->window->height_increment && floating_con->rect.height >= focused_con->window->base_height + border_rect.height) { @@ -72,43 +111,57 @@ void floating_check_size(Con *floating_con) { } } - /* 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; } @@ -118,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); @@ -180,15 +195,22 @@ 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. */ - TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); + * closed in tree_close_internal()) even though it’s not. */ + if (set_focus) { + TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); + } else { + TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows); + } TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused); /* check if the parent container is empty and close it if so */ 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; @@ -215,7 +237,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. */ @@ -234,13 +257,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) */ @@ -258,10 +284,7 @@ void floating_enable(Con *con, bool automatic) { /* Sanity check: Are the coordinates on the appropriate output? If not, we * need to change them */ - Output *current_output = get_output_containing(nc->rect.x + - (nc->rect.width / 2), - nc->rect.y + (nc->rect.height / 2)); - + Output *current_output = get_output_from_rect(nc->rect); Con *correct_output = con_get_output(ws); if (!current_output || current_output->con != correct_output) { DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n", @@ -269,11 +292,13 @@ void floating_enable(Con *con, bool automatic) { /* If moving from one output to another, keep the relative position * consistent (e.g. a centered dialog will remain centered). */ - if (current_output) + if (current_output) { floating_fix_coordinates(nc, ¤t_output->con->rect, &correct_output->rect); - else { - nc->rect.x = correct_output->rect.x; - nc->rect.y = correct_output->rect.y; + /* Make sure that the result is in the correct output. */ + current_output = get_output_from_rect(nc->rect); + } + if (!current_output || current_output->con != correct_output) { + floating_center(nc, ws->rect); } } @@ -287,32 +312,14 @@ 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); if (set_focus) - con_focus(con); - - /* 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; - } - - /* 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; - } - - ELOG("No output found at destination coordinates, centering floating window on current ws\n"); - floating_center(nc, ws->rect); + con_activate(con); + floating_set_hint_atom(nc, true); ipc_send_window_event("floating", con); } @@ -325,6 +332,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 +341,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 */ @@ -356,8 +366,9 @@ void floating_disable(Con *con, bool automatic) { con_fix_percent(con->parent); if (set_focus) - con_focus(con); + con_activate(con); + floating_set_hint_atom(con, false); ipc_send_window_event("floating", con); } @@ -402,9 +413,7 @@ void floating_raise_con(Con *con) { * */ bool floating_maybe_reassign_ws(Con *con) { - Output *output = get_output_containing( - con->rect.x + (con->rect.width / 2), - con->rect.y + (con->rect.height / 2)); + Output *output = get_output_from_rect(con->rect); if (!output) { ELOG("No output found at destination coordinates?\n"); @@ -422,7 +431,8 @@ bool floating_maybe_reassign_ws(Con *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, false); - con_focus(con_descend_focused(con)); + workspace_show(ws); + con_activate(con_descend_focused(con)); return true; } @@ -508,6 +518,11 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { /* Drag the window */ drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event); + if (!con_exists(con)) { + DLOG("The container has been closed in the meantime.\n"); + return; + } + /* If the user cancelled, undo the changes. */ if (drag_result == DRAG_REVERT) floating_reposition(con, initial_rect); @@ -619,6 +634,11 @@ void floating_resize_window(Con *con, const bool proportional, drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms); + if (!con_exists(con)) { + DLOG("The container has been closed in the meantime.\n"); + return; + } + /* If the user cancels, undo the resize */ if (drag_result == DRAG_REVERT) floating_reposition(con, initial_rect); @@ -628,9 +648,9 @@ 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; + ev_prepare prepare; /* Whether this modal event loop should be exited and with which result. */ drag_result_t result; @@ -649,8 +669,8 @@ struct drag_x11_cb { const void *extra; }; -static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { - struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w; +static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { + 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; @@ -709,20 +729,29 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { if (last_motion_notify != (xcb_motion_notify_event_t *)event) free(event); - if (dragloop->result != DRAGGING) + if (dragloop->result != DRAGGING) { + free(last_motion_notify); return; + } } if (last_motion_notify == NULL) return; - dragloop->callback( - dragloop->con, - &(dragloop->old_rect), - last_motion_notify->root_x, - last_motion_notify->root_y, - dragloop->extra); + /* Ensure that we are either dragging the resize handle (con is NULL) or that the + * container still exists. The latter might not be true, e.g., if the window closed + * for any reason while the user was dragging it. */ + if (!dragloop->con || con_exists(dragloop->con)) { + dragloop->callback( + dragloop->con, + &(dragloop->old_rect), + last_motion_notify->root_x, + last_motion_notify->root_y, + dragloop->extra); + } free(last_motion_notify); + + xcb_flush(conn); } /* @@ -789,16 +818,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ .callback = callback, .extra = extra, }; + ev_prepare *prepare = &loop.prepare; if (con) loop.old_rect = con->rect; - ev_check_init(&loop.check, xcb_drag_check_cb); + ev_prepare_init(prepare, xcb_drag_prepare_cb); + prepare->data = &loop; main_set_x11_cb(false); - ev_check_start(main_loop, &loop.check); + ev_prepare_start(main_loop, prepare); while (loop.result == DRAGGING) ev_run(main_loop, EVRUN_ONCE); - ev_check_stop(main_loop, &loop.check); + ev_prepare_stop(main_loop, prepare); main_set_x11_cb(true); xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); @@ -815,12 +846,12 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ * outputs. * */ -void floating_reposition(Con *con, Rect newrect) { +bool floating_reposition(Con *con, Rect newrect) { /* Sanity check: Are the new coordinates on any output? If not, we * ignore that request. */ - if (!contained_by_output(newrect)) { + if (!output_containing_rect(newrect)) { ELOG("No output found at destination coordinates. Not repositioning.\n"); - return; + return false; } con->rect = newrect; @@ -832,6 +863,7 @@ void floating_reposition(Con *con, Rect newrect) { con->scratchpad_state = SCRATCHPAD_CHANGED; tree_render(); + return true; } /* @@ -891,80 +923,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