-#undef I3__FILE__
-#define I3__FILE__ "floating.c"
/*
* vim:ts=4:sw=4:expandtab
*
*/
#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
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
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) {
}
}
- /* 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;
}
}
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;
+ LOG("Container is a workspace, not enabling floating mode.\n");
+ return;
+ }
- /* 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);
+ Con *focus_head_placeholder = NULL;
+ bool focus_before_parent = true;
+ if (!set_focus) {
+ /* Find recursively the ancestor container which is a child of our workspace.
+ * We need to reuse its focus position later. */
+ Con *ancestor = con;
+ while (ancestor->parent->type != CT_WORKSPACE) {
+ focus_before_parent &= TAILQ_FIRST(&(ancestor->parent->focus_head)) == ancestor;
+ ancestor = ancestor->parent;
+ }
+ /* Consider the part of the focus stack of our current workspace:
+ * [ ... S_{i-1} S_{i} S_{i+1} ... ]
+ * Where S_{x} is a container tree and the container 'con' that is beeing switched to
+ * floating belongs in S_{i}. The new floating container, 'nc', will have the
+ * workspace as its parent so it needs to be placed in this stack. If C was focused
+ * we just need to call con_focus(). Otherwise, nc must be placed before or after S_{i}.
+ * We should avoid using the S_{i} container for our operations since it might get
+ * killed if it has no other children. So, the two possible positions are after S_{i-1}
+ * or before S_{i+1}.
+ */
+ if (focus_before_parent) {
+ focus_head_placeholder = TAILQ_PREV(ancestor, focus_head, focused);
+ } else {
+ focus_head_placeholder = TAILQ_NEXT(ancestor, focused);
}
-
- /* 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;
}
/* 1: detach the container from its parent */
/* TODO: refactor this with tree_close_internal() */
- TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
- TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
-
+ con_detach(con);
con_fix_percent(con->parent);
/* 2: create a new container to render the decoration on, add
/* 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_internal()) even though it’s not. */
- TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows);
- TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused);
+ TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows);
+
+ struct focus_head *fh = &(ws->focus_head);
+ if (focus_before_parent) {
+ if (focus_head_placeholder) {
+ TAILQ_INSERT_AFTER(fh, focus_head_placeholder, nc, focused);
+ } else {
+ TAILQ_INSERT_HEAD(fh, nc, focused);
+ }
+ } else {
+ if (focus_head_placeholder) {
+ TAILQ_INSERT_BEFORE(focus_head_placeholder, nc, focused);
+ } else {
+ /* Also used for the set_focus case */
+ TAILQ_INSERT_TAIL(fh, 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_internal(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;
}
}
- 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. */
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) */
/* 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",
/* 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);
}
}
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);
}
return;
}
- const bool set_focus = (con == focused);
-
Con *ws = con_get_workspace(con);
-
- /* 1: detach from parent container */
- TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
- TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
-
- /* 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_internal(con->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 */
- Con *focused = con_descend_tiling_focused(ws);
-
- /* if there is no other container on this workspace, focused will be the
- * workspace itself */
- if (focused->type == CT_WORKSPACE)
- con->parent = focused;
- else
- con->parent = focused->parent;
-
- /* con_fix_percent will adjust the percent value */
- con->percent = 0.0;
+ Con *tiling_focused = con_descend_tiling_focused(ws);
+
+ if (tiling_focused->type == CT_WORKSPACE) {
+ Con *parent = con->parent;
+ con_detach(con);
+ con->parent = NULL;
+ tree_close_internal(parent, DONT_KILL_WINDOW, true, false);
+ con_attach(con, tiling_focused, false);
+ con->percent = 0.0;
+ con_fix_percent(con->parent);
+ } else {
+ insert_con_into(con, tiling_focused, AFTER);
+ }
con->floating = FLOATING_USER_OFF;
-
- con_attach(con, con->parent, false);
-
- con_fix_percent(con->parent);
-
- if (set_focus)
- con_focus(con);
-
+ floating_set_hint_atom(con, false);
ipc_send_window_event("floating", 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");
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;
}
/* 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);
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);
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;
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 bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
xcb_motion_notify_event_t *last_motion_notify = NULL;
xcb_generic_event_t *event;
if (last_motion_notify != (xcb_motion_notify_event_t *)event)
free(event);
- if (dragloop->result != DRAGGING)
- return;
+ if (dragloop->result != DRAGGING) {
+ free(last_motion_notify);
+ ev_break(EV_A_ EVBREAK_ONE);
+ return true;
+ }
}
- if (last_motion_notify == NULL)
- return;
+ if (last_motion_notify == NULL) {
+ return true;
+ }
- dragloop->callback(
- dragloop->con,
- &(dragloop->old_rect),
- last_motion_notify->root_x,
- last_motion_notify->root_y,
- dragloop->extra);
- free(last_motion_notify);
+ /* 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);
+ return false;
+}
+
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+ struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+ while (!drain_drag_events(EV_A, dragloop)) {
+ /* repeatedly drain events: draining might produce additional ones */
+ }
}
/*
.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_loop(main_loop, 0);
- 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);
* 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;
con->scratchpad_state = SCRATCHPAD_CHANGED;
tree_render();
+ return true;
}
/*
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