2009-05-23 16:34:03 +02:00
|
|
|
|
/*
|
2010-06-28 21:40:17 +02:00
|
|
|
|
* vim:ts=4:sw=4:expandtab
|
2009-05-23 16:34:03 +02:00
|
|
|
|
*
|
|
|
|
|
* i3 - an improved dynamic tiling window manager
|
2015-04-04 02:17:56 +02:00
|
|
|
|
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
2009-05-23 16:34:03 +02:00
|
|
|
|
*
|
2011-10-23 00:40:02 +02:00
|
|
|
|
* floating.c: Floating windows.
|
2009-05-23 16:34:03 +02:00
|
|
|
|
*
|
|
|
|
|
*/
|
2010-03-27 15:25:51 +01:00
|
|
|
|
#include "all.h"
|
|
|
|
|
|
2016-03-08 02:40:02 +01:00
|
|
|
|
#ifndef MAX
|
|
|
|
|
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
|
|
|
|
#endif
|
|
|
|
|
|
2012-02-16 15:36:46 +01:00
|
|
|
|
/*
|
|
|
|
|
* Calculates sum of heights and sum of widths of all currently active outputs
|
|
|
|
|
*
|
|
|
|
|
*/
|
2012-03-31 10:53:04 +02:00
|
|
|
|
static Rect total_outputs_dimensions(void) {
|
2015-09-20 11:35:26 +02:00
|
|
|
|
if (TAILQ_EMPTY(&outputs))
|
|
|
|
|
return (Rect){0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels};
|
|
|
|
|
|
2012-02-16 15:36:46 +01:00
|
|
|
|
Output *output;
|
|
|
|
|
/* Use Rect to encapsulate dimensions, ignoring x/y */
|
|
|
|
|
Rect outputs_dimensions = {0, 0, 0, 0};
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2012-02-16 15:36:46 +01:00
|
|
|
|
outputs_dimensions.height += output->rect.height;
|
|
|
|
|
outputs_dimensions.width += output->rect.width;
|
|
|
|
|
}
|
|
|
|
|
return outputs_dimensions;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-23 20:12:45 +01:00
|
|
|
|
/*
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-21 12:02:14 +02:00
|
|
|
|
/*
|
2018-10-01 18:47:33 +02:00
|
|
|
|
* Called when a floating window is created or resized. This function resizes
|
|
|
|
|
* the window if its size is higher or lower than the configured maximum/minimum
|
|
|
|
|
* size, respectively or when adjustments are needed to conform to the
|
|
|
|
|
* configured size increments or aspect ratio limits.
|
|
|
|
|
*
|
|
|
|
|
* When prefer_height is true and the window needs to be resized because of the
|
|
|
|
|
* configured aspect ratio, the width is adjusted first, preserving the previous
|
|
|
|
|
* height.
|
2012-11-25 20:55:49 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2018-10-01 18:47:33 +02:00
|
|
|
|
void floating_check_size(Con *floating_con, bool prefer_height) {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
/* Define reasonable minimal and maximal sizes for floating windows */
|
|
|
|
|
const int floating_sane_min_height = 50;
|
|
|
|
|
const int floating_sane_min_width = 75;
|
|
|
|
|
Rect floating_sane_max_dimensions;
|
2013-02-08 17:41:41 +01:00
|
|
|
|
Con *focused_con = con_descend_focused(floating_con);
|
|
|
|
|
|
2016-11-28 22:09:39 +01:00
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:42:53 +02:00
|
|
|
|
i3Window *window = focused_con->window;
|
|
|
|
|
if (window != NULL) {
|
2018-10-01 18:47:33 +02:00
|
|
|
|
/* ICCCM says: If a base size is not provided, the minimum size is to be used in its place
|
|
|
|
|
* and vice versa. */
|
|
|
|
|
int min_width = (window->min_width ? window->min_width : window->base_width);
|
|
|
|
|
int min_height = (window->min_height ? window->min_height : window->base_height);
|
|
|
|
|
int base_width = (window->base_width ? window->base_width : window->min_width);
|
|
|
|
|
int base_height = (window->base_height ? window->base_height : window->min_height);
|
|
|
|
|
|
|
|
|
|
if (min_width) {
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.width -= border_rect.width;
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_con->rect.width = max(floating_con->rect.width, min_width);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.width += border_rect.width;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 18:47:33 +02:00
|
|
|
|
if (min_height) {
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.height -= border_rect.height;
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_con->rect.height = max(floating_con->rect.height, min_height);
|
2018-09-14 01:16:45 +02:00
|
|
|
|
floating_con->rect.height += border_rect.height;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:42:53 +02:00
|
|
|
|
if (window->max_width) {
|
2018-09-14 01:16:45 +02:00
|
|
|
|
floating_con->rect.width -= border_rect.width;
|
2018-10-01 14:42:53 +02:00
|
|
|
|
floating_con->rect.width = min(floating_con->rect.width, window->max_width);
|
2018-09-14 01:16:45 +02:00
|
|
|
|
floating_con->rect.width += border_rect.width;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:42:53 +02:00
|
|
|
|
if (window->max_height) {
|
2018-09-14 01:16:45 +02:00
|
|
|
|
floating_con->rect.height -= border_rect.height;
|
2018-10-01 14:42:53 +02:00
|
|
|
|
floating_con->rect.height = min(floating_con->rect.height, window->max_height);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.height += border_rect.height;
|
|
|
|
|
}
|
2013-02-08 17:41:41 +01:00
|
|
|
|
|
2018-10-01 18:47:33 +02:00
|
|
|
|
/* Obey the aspect ratio, if any, unless we are in fullscreen mode.
|
|
|
|
|
*
|
|
|
|
|
* The spec isn’t explicit on whether the aspect ratio hints should be
|
|
|
|
|
* respected during fullscreen mode. Other WMs such as Openbox don’t do
|
|
|
|
|
* that, and this post suggests that this is the correct way to do it:
|
|
|
|
|
* https://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html
|
|
|
|
|
*
|
|
|
|
|
* Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
|
|
|
|
|
* subtitle rendering, see https://bugs.i3wm.org/594 */
|
|
|
|
|
const double min_ar = window->min_aspect_ratio;
|
|
|
|
|
const double max_ar = window->max_aspect_ratio;
|
|
|
|
|
if (floating_con->fullscreen_mode == CF_NONE && (min_ar > 0 || max_ar > 0)) {
|
|
|
|
|
/* The ICCCM says to subtract the base size from the window size for
|
|
|
|
|
* aspect ratio calculations. However, unlike determining the base
|
|
|
|
|
* size itself we must not fall back to using the minimum size in
|
|
|
|
|
* this case according to the ICCCM. */
|
|
|
|
|
double width = floating_con->rect.width - window->base_width - border_rect.width;
|
|
|
|
|
double height = floating_con->rect.height - window->base_height - border_rect.height;
|
|
|
|
|
const double ar = (double)width / (double)height;
|
|
|
|
|
double new_ar = -1;
|
|
|
|
|
if (min_ar > 0 && ar < min_ar) {
|
|
|
|
|
new_ar = min_ar;
|
|
|
|
|
} else if (max_ar > 0 && ar > max_ar) {
|
|
|
|
|
new_ar = max_ar;
|
|
|
|
|
}
|
|
|
|
|
if (new_ar > 0) {
|
|
|
|
|
if (prefer_height) {
|
|
|
|
|
width = round(height * new_ar);
|
|
|
|
|
height = round(width / new_ar);
|
|
|
|
|
} else {
|
|
|
|
|
height = round(width / new_ar);
|
|
|
|
|
width = round(height * new_ar);
|
|
|
|
|
}
|
|
|
|
|
floating_con->rect.width = width + window->base_width + border_rect.width;
|
|
|
|
|
floating_con->rect.height = height + window->base_height + border_rect.height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:42:53 +02:00
|
|
|
|
if (window->height_increment &&
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_con->rect.height >= base_height + border_rect.height) {
|
|
|
|
|
floating_con->rect.height -= base_height + border_rect.height;
|
2018-10-01 14:42:53 +02:00
|
|
|
|
floating_con->rect.height -= floating_con->rect.height % window->height_increment;
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_con->rect.height += base_height + border_rect.height;
|
2013-02-08 17:41:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:42:53 +02:00
|
|
|
|
if (window->width_increment &&
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_con->rect.width >= base_width + border_rect.width) {
|
|
|
|
|
floating_con->rect.width -= base_width + border_rect.width;
|
2018-10-01 14:42:53 +02:00
|
|
|
|
floating_con->rect.width -= floating_con->rect.width % window->width_increment;
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_con->rect.width += base_width + border_rect.width;
|
2013-02-08 17:41:41 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-11-22 05:15:49 +01:00
|
|
|
|
|
2016-11-28 22:09:39 +01:00
|
|
|
|
/* Unless user requests otherwise (-1), raise the width/height to
|
|
|
|
|
* reasonable minimum dimensions */
|
2012-11-22 05:15:49 +01:00
|
|
|
|
if (config.floating_minimum_height != -1) {
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.height -= border_rect.height;
|
|
|
|
|
if (config.floating_minimum_height == 0) {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
} else {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
}
|
|
|
|
|
floating_con->rect.height += border_rect.height;
|
2012-11-22 05:15:49 +01:00
|
|
|
|
}
|
2016-11-28 22:09:39 +01:00
|
|
|
|
|
2012-11-22 05:15:49 +01:00
|
|
|
|
if (config.floating_minimum_width != -1) {
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.width -= border_rect.width;
|
|
|
|
|
if (config.floating_minimum_width == 0) {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
} else {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
}
|
|
|
|
|
floating_con->rect.width += border_rect.width;
|
2012-11-22 05:15:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-28 22:09:39 +01:00
|
|
|
|
/* Unless user requests otherwise (-1), ensure width/height do not exceed
|
|
|
|
|
* configured maxima or, if unconfigured, limit to combined width of all
|
|
|
|
|
* outputs */
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_sane_max_dimensions = total_outputs_dimensions();
|
|
|
|
|
if (config.floating_maximum_height != -1) {
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.height -= border_rect.height;
|
|
|
|
|
if (config.floating_maximum_height == 0) {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.height = min(floating_con->rect.height, floating_sane_max_dimensions.height);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
} else {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.height = min(floating_con->rect.height, config.floating_maximum_height);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
}
|
|
|
|
|
floating_con->rect.height += border_rect.height;
|
2012-11-22 05:15:49 +01:00
|
|
|
|
}
|
2016-11-28 22:09:39 +01:00
|
|
|
|
|
2012-11-22 05:15:49 +01:00
|
|
|
|
if (config.floating_maximum_width != -1) {
|
2016-11-28 22:09:39 +01:00
|
|
|
|
floating_con->rect.width -= border_rect.width;
|
|
|
|
|
if (config.floating_maximum_width == 0) {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.width = min(floating_con->rect.width, floating_sane_max_dimensions.width);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
} else {
|
2012-11-22 05:15:49 +01:00
|
|
|
|
floating_con->rect.width = min(floating_con->rect.width, config.floating_maximum_width);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
}
|
|
|
|
|
floating_con->rect.width += border_rect.width;
|
2012-11-22 05:15:49 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-28 21:40:17 +02:00
|
|
|
|
void floating_enable(Con *con, bool automatic) {
|
2011-08-27 13:02:44 +02:00
|
|
|
|
bool set_focus = (con == focused);
|
2011-01-08 00:10:30 +01:00
|
|
|
|
|
2015-10-26 18:16:21 +01:00
|
|
|
|
if (con_is_docked(con)) {
|
2011-08-28 15:46:50 +02:00
|
|
|
|
LOG("Container is a dock window, not enabling floating mode.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-30 15:54:34 +02:00
|
|
|
|
if (con_is_floating(con)) {
|
|
|
|
|
LOG("Container is already in floating mode, not doing anything.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2010-12-30 23:09:18 +01:00
|
|
|
|
if (con->type == CT_WORKSPACE) {
|
2016-02-11 20:54:02 +01:00
|
|
|
|
LOG("Container is a workspace, not enabling floating mode.\n");
|
|
|
|
|
return;
|
2010-12-30 23:09:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-14 12:00:14 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-28 21:40:17 +02:00
|
|
|
|
/* 1: detach the container from its parent */
|
2015-12-28 02:58:35 +01:00
|
|
|
|
/* TODO: refactor this with tree_close_internal() */
|
2017-09-14 11:16:19 +02:00
|
|
|
|
con_detach(con);
|
2011-01-26 01:12:43 +01:00
|
|
|
|
con_fix_percent(con->parent);
|
2010-06-28 21:40:17 +02:00
|
|
|
|
|
|
|
|
|
/* 2: create a new container to render the decoration on, add
|
|
|
|
|
* it as a floating window to the workspace */
|
2011-06-02 17:21:38 +02:00
|
|
|
|
Con *nc = con_new(NULL, NULL);
|
2010-11-14 16:41:46 +01:00
|
|
|
|
/* we need to set the parent afterwards instead of passing it as an
|
|
|
|
|
* argument to con_new() because nc would be inserted into the tiling layer
|
|
|
|
|
* otherwise. */
|
2011-08-24 16:57:35 +02:00
|
|
|
|
Con *ws = con_get_workspace(con);
|
|
|
|
|
nc->parent = ws;
|
2012-01-08 13:28:49 +01:00
|
|
|
|
nc->type = CT_FLOATING_CON;
|
Introduce splith/splitv layouts, remove orientation
With this commit, the "default" layout is replaced by the splith and
splitv layouts. splith is equivalent to default with orientation
horizontal and splitv is equivalent to default with orientation
vertical.
The "split h" and "split v" commands continue to work as before, they
split the current container and you will end up in a split container
with layout splith (after "split h") or splitv (after "split v").
To change a splith container into a splitv container, use either "layout
splitv" or "layout toggle split". The latter command is used in the
default config as mod+l (previously "layout default"). In case you have
"layout default" in your config file, it is recommended to just replace
it by "layout toggle split", which will work as "layout default" did
before when pressing it once, but toggle between horizontal/vertical
when pressing it repeatedly.
The rationale behind this commit is that it’s cleaner to have all
parameters that influence how windows are rendered in the layout itself
rather than having a special parameter in combination with only one
layout. This enables us to change existing split containers in all cases
without breaking existing features (see ticket #464). Also, users should
feel more confident about whether they are actually splitting or just
changing an existing split container now.
As a nice side-effect, this commit brings back the "layout toggle"
feature we once had in i3 version 3 (see the userguide).
AFAIK, it is safe to use in-place restart to upgrade into versions
after this commit (switching to an older version will break your layout,
though).
Fixes #464
2012-08-04 03:04:00 +02:00
|
|
|
|
nc->layout = L_SPLITH;
|
2012-01-08 13:28:49 +01:00
|
|
|
|
/* We insert nc already, even though its rect is not yet calculated. This
|
|
|
|
|
* is necessary because otherwise the workspace might be empty (and get
|
2015-12-28 02:58:35 +01:00
|
|
|
|
* closed in tree_close_internal()) even though it’s not. */
|
2017-09-14 12:00:14 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
2016-12-27 19:00:09 +01:00
|
|
|
|
} else {
|
2017-09-14 12:00:14 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
2016-12-27 19:00:09 +01:00
|
|
|
|
}
|
2010-11-14 16:41:46 +01:00
|
|
|
|
|
2011-02-14 16:43:41 +01:00
|
|
|
|
/* check if the parent container is empty and close it if so */
|
2012-01-08 13:28:49 +01:00
|
|
|
|
if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) &&
|
|
|
|
|
con_num_children(con->parent) == 0) {
|
2011-02-14 16:43:41 +01:00
|
|
|
|
DLOG("Old container empty after setting this child to floating, closing\n");
|
Fix an use-after-free bug (#2522)
Fix the issue #2421 (https://github.com/i3/i3/issues/2421).
floating_enable() invokes tree_close_internal() to free con->parent.
After con->parent is freed in tree_close_internal() but before con->parent is reassigned by the caller, con->parent may be dereferenced and causes i3 crash.
The backtrace below is an example.
The already-freed pointer is dereferenced again through the pointer "focused" in x_push_changes().
Reassign con->parent before calling tree_close_internal() to fix this use-after-free bug.
0x0000000000416372 in con_get_workspace (con=0x7ab9c0) at ../i3/src/con.c:375
0x0000000000416103 in con_has_managed_window (con=0x7ab9c0) at ../i3/src/con.c:266
0x000000000042b413 in x_push_changes (con=0x78d190) at ../i3/src/x.c:1132
0x0000000l0004533e8 in tree_render () at ../i3/src/tree.c:504
0x0000000000452b4f in tree_close_internal (con=0x7b67c0, kill_window=DONT_KILL_WINDOW, dont_kill_parent=false, force_set_focus=false)
../i3/src/tree.c:314
0x00000000004196f0 in con_on_remove_child (con=0x7b67c0) at ../i3/src/con.c:1801
0x0000000000452eb7 in tree_close_internal (con=0x783840, kill_window=DONT_KILL_WINDOW, dont_kill_parent=false, force_set_focus=false)
../i3/src/tree.c:364
0x0000000000431516 in floating_enable (con=0x7ab9c0, automatic=false) at ../i3/src/floating.c:183
0x0000000000431eed in toggle_floating_mode (con=0x7ab9c0, automatic=false) at ../i3/src/floating.c:379
0x0000000000420d92 in cmd_floating (current_match=0x679a20 , cmd_output=0x679aa0 , floating_mode=0x7ab8c0 "toggle")
../i3/src/commands.c:1088
0x000000000043e5ae in GENERATED_call (call_identifier=60, result=0x679aa0 ) at include/GENERATED_command_call.h:486
0x000000000043ee19 in next_state (token=0x675d70 ) at ../i3/src/commands_parser.c:187
0x000000000043f2fb in parse_command (input=0x7b4fe0 "floating toggle", gen=0x0) at ../i3/src/commands_parser.c:308
0x00000000004125f8 in run_binding (bind=0x784260, con=0x0) at ../i3/src/bindings.c:792
0x000000000042bace in handle_key_press (event=0x7a01a0) at ../i3/src/key_press.c:33
0x000000000044e6aa in handle_event (type=2, event=0x7a01a0) at ../i3/src/handlers.c:1420
0x0000000000439533 in xcb_check_cb (loop=0x7ffff532f8e0, w=0x68c140, revents=32768) at ../i3/src/main.c:133
0x00007ffff5125d73 in ev_invoke_pending () from /usr/lib/x86_64-linux-gnu/libev.so.4
0x00007ffff51293de in ev_run () from /usr/lib/x86_64-linux-gnu/libev.so.4
0x0000000000439418 in ev_loop (loop=0x7ffff532f8e0, flags=0) at /usr/include/ev.h:835
0x000000000043d51d in main (argc=3, argv=0x7fffffffe0a8) at ../i3/src/main.c:913
2016-11-08 09:56:46 +01:00
|
|
|
|
Con *parent = con->parent;
|
|
|
|
|
/* clear the pointer before calling tree_close_internal in which the memory is freed */
|
|
|
|
|
con->parent = NULL;
|
2018-07-30 00:56:51 +02:00
|
|
|
|
tree_close_internal(parent, DONT_KILL_WINDOW, false);
|
2011-02-14 16:43:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2010-11-14 16:41:46 +01:00
|
|
|
|
char *name;
|
2011-10-23 14:16:56 +02:00
|
|
|
|
sasprintf(&name, "[i3 con] floatingcon around %p", con);
|
2010-11-14 16:41:46 +01:00
|
|
|
|
x_set_name(nc, name);
|
|
|
|
|
free(name);
|
|
|
|
|
|
2010-11-15 00:25:37 +01:00
|
|
|
|
/* find the height for the decorations */
|
2013-02-08 14:23:50 +01:00
|
|
|
|
int deco_height = render_deco_height();
|
2010-11-15 00:25:37 +01:00
|
|
|
|
|
2011-01-21 21:01:02 +01:00
|
|
|
|
DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
|
2011-09-17 15:10:35 +02:00
|
|
|
|
DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
|
2011-03-03 16:22:22 +01:00
|
|
|
|
nc->rect = con->geometry;
|
2011-03-06 02:38:21 +01:00
|
|
|
|
/* If the geometry was not set (split containers), we need to determine a
|
|
|
|
|
* sensible one by combining the geometry of all children */
|
2019-09-03 09:43:36 +02:00
|
|
|
|
if (rect_equals(nc->rect, (Rect){0, 0, 0, 0})) {
|
2011-03-06 02:38:21 +01:00
|
|
|
|
DLOG("Geometry not set, combining children\n");
|
|
|
|
|
Con *child;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
2011-03-06 02:38:21 +01:00
|
|
|
|
DLOG("child geometry: %d x %d\n", child->geometry.width, child->geometry.height);
|
|
|
|
|
nc->rect.width += child->geometry.width;
|
|
|
|
|
nc->rect.height = max(nc->rect.height, child->geometry.height);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-02-16 15:36:46 +01:00
|
|
|
|
|
2016-11-28 22:09:39 +01:00
|
|
|
|
TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
|
|
|
|
|
TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
|
2012-02-16 15:36:46 +01:00
|
|
|
|
|
2012-08-07 01:13:37 +02:00
|
|
|
|
/* 3: attach the child to the new parent container. We need to do this
|
|
|
|
|
* because con_border_style_rect() needs to access con->parent. */
|
|
|
|
|
con->parent = nc;
|
|
|
|
|
con->percent = 1.0;
|
|
|
|
|
con->floating = FLOATING_USER_ON;
|
|
|
|
|
|
2012-08-09 12:32:35 +02:00
|
|
|
|
/* 4: set the border style as specified with new_float */
|
|
|
|
|
if (automatic)
|
|
|
|
|
con->border_style = config.default_floating_border;
|
|
|
|
|
|
2012-08-06 18:39:38 +02:00
|
|
|
|
/* Add pixels for the decoration. */
|
|
|
|
|
Rect border_style_rect = con_border_style_rect(con);
|
|
|
|
|
|
|
|
|
|
nc->rect.height -= border_style_rect.height;
|
|
|
|
|
nc->rect.width -= border_style_rect.width;
|
|
|
|
|
|
|
|
|
|
/* Add some more pixels for the title bar */
|
2016-11-28 22:09:39 +01:00
|
|
|
|
if (con_border_style(con) == BS_NORMAL) {
|
2012-08-06 18:39:38 +02:00
|
|
|
|
nc->rect.height += deco_height;
|
2016-11-28 22:09:39 +01:00
|
|
|
|
}
|
2011-08-24 16:57:35 +02:00
|
|
|
|
|
2011-09-17 15:10:35 +02:00
|
|
|
|
/* Honor the X11 border */
|
|
|
|
|
nc->rect.height += con->border_width * 2;
|
|
|
|
|
nc->rect.width += con->border_width * 2;
|
|
|
|
|
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_check_size(nc, false);
|
2016-11-28 22:09:39 +01:00
|
|
|
|
|
2011-08-24 18:36:18 +02:00
|
|
|
|
/* 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) */
|
|
|
|
|
if (nc->rect.x == 0 && nc->rect.y == 0) {
|
|
|
|
|
Con *leader;
|
|
|
|
|
if (con->window && con->window->leader != XCB_NONE &&
|
2019-10-01 09:24:21 +02:00
|
|
|
|
con->window->id != con->window->leader &&
|
2011-08-24 18:36:18 +02:00
|
|
|
|
(leader = con_by_window_id(con->window->leader)) != NULL) {
|
|
|
|
|
DLOG("Centering above leader\n");
|
2015-03-30 09:10:40 +02:00
|
|
|
|
floating_center(nc, leader->rect);
|
2011-08-24 18:36:18 +02:00
|
|
|
|
} else {
|
|
|
|
|
/* center the window on workspace as fallback */
|
2015-03-30 09:10:40 +02:00
|
|
|
|
floating_center(nc, ws->rect);
|
2011-08-24 18:36:18 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-24 16:57:35 +02:00
|
|
|
|
/* Sanity check: Are the coordinates on the appropriate output? If not, we
|
|
|
|
|
* need to change them */
|
2018-03-17 16:47:16 +01:00
|
|
|
|
Output *current_output = get_output_from_rect(nc->rect);
|
2011-08-24 16:57:35 +02:00
|
|
|
|
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",
|
|
|
|
|
nc->rect.x, nc->rect.y);
|
2012-09-30 06:30:03 +02:00
|
|
|
|
|
|
|
|
|
/* If moving from one output to another, keep the relative position
|
|
|
|
|
* consistent (e.g. a centered dialog will remain centered). */
|
2018-03-17 16:47:16 +01:00
|
|
|
|
if (current_output) {
|
2012-09-30 06:30:03 +02:00
|
|
|
|
floating_fix_coordinates(nc, ¤t_output->con->rect, &correct_output->rect);
|
2018-03-17 16:47:16 +01:00
|
|
|
|
/* 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);
|
2012-09-30 06:30:03 +02:00
|
|
|
|
}
|
2011-08-24 16:57:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-01-21 21:01:02 +01:00
|
|
|
|
DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height);
|
2010-06-28 21:40:17 +02:00
|
|
|
|
|
2012-01-21 15:20:55 +01:00
|
|
|
|
/* 5: Subtract the deco_height in order to make the floating window appear
|
|
|
|
|
* at precisely the position it specified in its original geometry (which
|
|
|
|
|
* is what applications might remember). */
|
2013-02-08 14:23:50 +01:00
|
|
|
|
deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
|
2012-01-21 15:20:55 +01:00
|
|
|
|
nc->rect.y -= deco_height;
|
|
|
|
|
|
|
|
|
|
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
|
|
|
|
|
|
2011-01-21 21:01:02 +01:00
|
|
|
|
/* render the cons to get initial window_rect correct */
|
2018-10-02 01:13:51 +02:00
|
|
|
|
render_con(nc);
|
2011-01-21 21:01:02 +01:00
|
|
|
|
|
2011-01-08 00:10:30 +01:00
|
|
|
|
if (set_focus)
|
2017-12-06 00:58:47 +01:00
|
|
|
|
con_activate(con);
|
2011-04-18 19:28:03 +02:00
|
|
|
|
|
2016-02-23 20:12:45 +01:00
|
|
|
|
floating_set_hint_atom(nc, true);
|
2014-06-20 13:44:08 +02:00
|
|
|
|
ipc_send_window_event("floating", con);
|
2010-06-28 21:40:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void floating_disable(Con *con, bool automatic) {
|
2010-06-30 15:54:34 +02:00
|
|
|
|
if (!con_is_floating(con)) {
|
|
|
|
|
LOG("Container isn't floating, not doing anything.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-16 20:51:29 +02:00
|
|
|
|
Con *ws = con_get_workspace(con);
|
2018-08-28 09:26:07 +02:00
|
|
|
|
if (con_is_internal(ws)) {
|
|
|
|
|
LOG("Can't disable floating for container in internal workspace.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-09-13 02:54:40 +02:00
|
|
|
|
Con *tiling_focused = con_descend_tiling_focused(ws);
|
2011-03-14 23:50:29 +01:00
|
|
|
|
|
2017-09-13 02:54:40 +02:00
|
|
|
|
if (tiling_focused->type == CT_WORKSPACE) {
|
|
|
|
|
Con *parent = con->parent;
|
|
|
|
|
con_detach(con);
|
|
|
|
|
con->parent = NULL;
|
2018-07-30 00:56:51 +02:00
|
|
|
|
tree_close_internal(parent, DONT_KILL_WINDOW, true);
|
2017-09-13 02:54:40 +02:00
|
|
|
|
con_attach(con, tiling_focused, false);
|
|
|
|
|
con->percent = 0.0;
|
|
|
|
|
con_fix_percent(con->parent);
|
|
|
|
|
} else {
|
|
|
|
|
insert_con_into(con, tiling_focused, AFTER);
|
|
|
|
|
}
|
2011-01-27 15:53:14 +01:00
|
|
|
|
|
2010-06-28 21:40:17 +02:00
|
|
|
|
con->floating = FLOATING_USER_OFF;
|
2016-02-23 20:12:45 +01:00
|
|
|
|
floating_set_hint_atom(con, false);
|
2014-06-20 13:44:08 +02:00
|
|
|
|
ipc_send_window_event("floating", con);
|
2010-06-28 21:40:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2009-05-23 16:34:03 +02:00
|
|
|
|
/*
|
2010-03-27 15:25:51 +01:00
|
|
|
|
* Toggles floating mode for the given container.
|
2009-05-23 16:34:03 +02:00
|
|
|
|
*
|
2009-05-31 00:31:18 +02:00
|
|
|
|
* If the automatic flag is set to true, this was an automatic update by a change of the
|
|
|
|
|
* window class from the application which can be overwritten by the user.
|
|
|
|
|
*
|
2009-05-23 16:34:03 +02:00
|
|
|
|
*/
|
2010-03-27 15:25:51 +01:00
|
|
|
|
void toggle_floating_mode(Con *con, bool automatic) {
|
2015-09-22 17:04:10 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-29 22:23:49 +01:00
|
|
|
|
/* see if the client is already floating */
|
|
|
|
|
if (con_is_floating(con)) {
|
|
|
|
|
LOG("already floating, re-setting to tiling\n");
|
2010-03-07 19:00:34 +01:00
|
|
|
|
|
2010-11-29 22:23:49 +01:00
|
|
|
|
floating_disable(con, automatic);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2009-05-23 16:34:03 +02:00
|
|
|
|
|
2010-11-29 22:23:49 +01:00
|
|
|
|
floating_enable(con, automatic);
|
2009-05-23 16:34:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
2010-12-31 01:38:17 +01:00
|
|
|
|
/*
|
|
|
|
|
* Raises the given container in the list of floating containers
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void floating_raise_con(Con *con) {
|
|
|
|
|
DLOG("Raising floating con %p / %s\n", con, con->name);
|
|
|
|
|
TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
|
|
|
|
|
TAILQ_INSERT_TAIL(&(con->parent->floating_head), con, floating_windows);
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-18 19:28:03 +02:00
|
|
|
|
/*
|
|
|
|
|
* Checks if con’s coordinates are within its workspace and re-assigns it to
|
|
|
|
|
* the actual workspace if not.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
bool floating_maybe_reassign_ws(Con *con) {
|
2019-10-15 17:40:59 +02:00
|
|
|
|
if (con_is_internal(con_get_workspace(con))) {
|
|
|
|
|
DLOG("Con in an internal workspace\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-17 19:43:11 +01:00
|
|
|
|
Output *output = get_output_from_rect(con->rect);
|
2011-04-18 18:44:39 +02:00
|
|
|
|
|
|
|
|
|
if (!output) {
|
|
|
|
|
ELOG("No output found at destination coordinates?\n");
|
2011-04-18 19:28:03 +02:00
|
|
|
|
return false;
|
2011-04-18 18:44:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (con_get_output(con) == output->con) {
|
|
|
|
|
DLOG("still the same ws\n");
|
2011-04-18 19:28:03 +02:00
|
|
|
|
return false;
|
2011-04-18 18:44:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DLOG("Need to re-assign!\n");
|
|
|
|
|
|
|
|
|
|
Con *content = output_get_content(output->con);
|
2011-05-02 11:05:50 +02:00
|
|
|
|
Con *ws = TAILQ_FIRST(&(content->focus_head));
|
2011-04-18 18:44:39 +02:00
|
|
|
|
DLOG("Moving con %p / %s to workspace %p / %s\n", con, con->name, ws, ws->name);
|
2015-08-26 21:06:53 +02:00
|
|
|
|
con_move_to_workspace(con, ws, false, true, false);
|
2017-12-09 14:35:05 +01:00
|
|
|
|
workspace_show(ws);
|
2017-12-06 00:58:47 +01:00
|
|
|
|
con_activate(con_descend_focused(con));
|
2011-04-18 19:28:03 +02:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-30 09:10:40 +02:00
|
|
|
|
/*
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-03 00:19:26 +02:00
|
|
|
|
/*
|
|
|
|
|
* 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);
|
2015-05-14 19:07:56 +02:00
|
|
|
|
if (output == NULL) {
|
2015-06-30 20:48:35 +02:00
|
|
|
|
ELOG("The pointer is not on any output, cannot move the container here.\n");
|
2015-05-14 19:07:56 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2015-05-03 00:19:26 +02:00
|
|
|
|
|
|
|
|
|
/* 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});
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-18 19:28:03 +02:00
|
|
|
|
DRAGGING_CB(drag_window_callback) {
|
|
|
|
|
/* Reposition the client correctly while moving */
|
|
|
|
|
con->rect.x = old_rect->x + (new_x - event->root_x);
|
|
|
|
|
con->rect.y = old_rect->y + (new_y - event->root_y);
|
|
|
|
|
|
2018-10-02 01:13:51 +02:00
|
|
|
|
render_con(con);
|
2011-05-01 18:48:30 +02:00
|
|
|
|
x_push_node(con);
|
2011-04-18 19:28:03 +02:00
|
|
|
|
xcb_flush(conn);
|
|
|
|
|
|
|
|
|
|
/* Check if we cross workspace boundaries while moving */
|
|
|
|
|
if (!floating_maybe_reassign_ws(con))
|
|
|
|
|
return;
|
2013-03-15 19:27:08 +01:00
|
|
|
|
/* Ensure not to warp the pointer while dragging */
|
|
|
|
|
x_set_warp_to(NULL);
|
2011-04-18 18:44:39 +02:00
|
|
|
|
tree_render();
|
2010-03-07 19:00:34 +01:00
|
|
|
|
}
|
2009-05-23 16:34:03 +02:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Called when the user clicked on the titlebar of a floating window.
|
|
|
|
|
* Calls the drag_pointer function with the drag_window callback
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-12-12 12:24:03 +01:00
|
|
|
|
void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold) {
|
2010-11-29 22:23:49 +01:00
|
|
|
|
DLOG("floating_drag_window\n");
|
2009-05-23 16:34:03 +02:00
|
|
|
|
|
2011-08-06 18:53:39 +02:00
|
|
|
|
/* Push changes before dragging, so that the window gets raised now and not
|
|
|
|
|
* after the user releases the mouse button */
|
|
|
|
|
tree_render();
|
|
|
|
|
|
2013-11-01 01:36:31 +01:00
|
|
|
|
/* Store the initial rect in case of user revert/cancel */
|
2013-09-26 02:52:59 +02:00
|
|
|
|
Rect initial_rect = con->rect;
|
|
|
|
|
|
2011-08-06 18:53:39 +02:00
|
|
|
|
/* Drag the window */
|
2018-12-12 12:24:03 +01:00
|
|
|
|
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_window_callback, NULL);
|
2013-09-26 02:52:59 +02:00
|
|
|
|
|
2017-09-22 18:25:02 +02:00
|
|
|
|
if (!con_exists(con)) {
|
|
|
|
|
DLOG("The container has been closed in the meantime.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-26 02:52:59 +02:00
|
|
|
|
/* If the user cancelled, undo the changes. */
|
2018-08-21 20:06:00 +02:00
|
|
|
|
if (drag_result == DRAG_REVERT) {
|
2013-09-26 02:52:59 +02:00
|
|
|
|
floating_reposition(con, initial_rect);
|
2018-08-21 20:06:00 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2012-09-24 21:14:40 +02:00
|
|
|
|
|
|
|
|
|
/* If this is a scratchpad window, don't auto center it from now on. */
|
|
|
|
|
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
|
|
|
|
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
|
|
|
|
|
2010-11-29 22:23:49 +01:00
|
|
|
|
tree_render();
|
2010-03-07 19:00:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-12 01:31:48 +01:00
|
|
|
|
/*
|
|
|
|
|
* This is an ugly data structure which we need because there is no standard
|
|
|
|
|
* way of having nested functions (only available as a gcc extension at the
|
|
|
|
|
* moment, clang doesn’t support it) or blocks (only available as a clang
|
|
|
|
|
* extension and only on Mac OS X systems at the moment).
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
struct resize_window_callback_params {
|
2011-11-10 20:17:36 +01:00
|
|
|
|
const border_t corner;
|
|
|
|
|
const bool proportional;
|
2010-03-12 01:31:48 +01:00
|
|
|
|
};
|
|
|
|
|
|
2010-03-07 19:00:34 +01:00
|
|
|
|
DRAGGING_CB(resize_window_callback) {
|
2011-11-10 20:17:36 +01:00
|
|
|
|
const struct resize_window_callback_params *params = extra;
|
2010-06-28 22:23:32 +02:00
|
|
|
|
border_t corner = params->corner;
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2010-06-28 22:23:32 +02:00
|
|
|
|
int32_t dest_x = con->rect.x;
|
|
|
|
|
int32_t dest_y = con->rect.y;
|
|
|
|
|
uint32_t dest_width;
|
|
|
|
|
uint32_t dest_height;
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2014-06-15 19:07:02 +02:00
|
|
|
|
double ratio = (double)old_rect->width / old_rect->height;
|
2010-03-13 10:38:22 +01:00
|
|
|
|
|
2010-06-28 22:23:32 +02:00
|
|
|
|
/* First guess: We resize by exactly the amount the mouse moved,
|
|
|
|
|
* taking into account in which corner the client was grabbed */
|
|
|
|
|
if (corner & BORDER_LEFT)
|
2010-11-29 22:23:49 +01:00
|
|
|
|
dest_width = old_rect->width - (new_x - event->root_x);
|
2014-06-15 19:07:02 +02:00
|
|
|
|
else
|
|
|
|
|
dest_width = old_rect->width + (new_x - event->root_x);
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2010-06-28 22:23:32 +02:00
|
|
|
|
if (corner & BORDER_TOP)
|
2010-11-29 22:23:49 +01:00
|
|
|
|
dest_height = old_rect->height - (new_y - event->root_y);
|
2014-06-15 19:07:02 +02:00
|
|
|
|
else
|
|
|
|
|
dest_height = old_rect->height + (new_y - event->root_y);
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2010-06-28 22:23:32 +02:00
|
|
|
|
/* User wants to keep proportions, so we may have to adjust our values */
|
|
|
|
|
if (params->proportional) {
|
2014-06-15 19:07:02 +02:00
|
|
|
|
dest_width = max(dest_width, (int)(dest_height * ratio));
|
|
|
|
|
dest_height = max(dest_height, (int)(dest_width / ratio));
|
2010-06-28 22:23:32 +02:00
|
|
|
|
}
|
2010-03-07 19:00:34 +01:00
|
|
|
|
|
2015-03-01 17:16:03 +01:00
|
|
|
|
con->rect = (Rect){dest_x, dest_y, dest_width, dest_height};
|
2013-02-08 17:41:41 +01:00
|
|
|
|
|
|
|
|
|
/* Obey window size */
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_check_size(con, false);
|
2013-02-08 17:41:41 +01:00
|
|
|
|
|
2010-06-28 22:23:32 +02:00
|
|
|
|
/* If not the lower right corner is grabbed, we must also reposition
|
|
|
|
|
* the client by exactly the amount we resized it */
|
|
|
|
|
if (corner & BORDER_LEFT)
|
2013-02-08 17:41:41 +01:00
|
|
|
|
dest_x = old_rect->x + (old_rect->width - con->rect.width);
|
2010-03-13 10:38:22 +01:00
|
|
|
|
|
2010-06-28 22:23:32 +02:00
|
|
|
|
if (corner & BORDER_TOP)
|
2013-02-08 17:41:41 +01:00
|
|
|
|
dest_y = old_rect->y + (old_rect->height - con->rect.height);
|
2010-03-13 10:38:22 +01:00
|
|
|
|
|
2013-02-08 17:41:41 +01:00
|
|
|
|
con->rect.x = dest_x;
|
|
|
|
|
con->rect.y = dest_y;
|
2010-03-07 19:00:34 +01:00
|
|
|
|
|
2018-10-02 01:15:59 +02:00
|
|
|
|
render_con(con);
|
2010-06-28 22:23:32 +02:00
|
|
|
|
x_push_changes(croot);
|
2009-05-23 16:34:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
2009-08-22 07:49:28 +02:00
|
|
|
|
/*
|
2010-03-12 02:59:16 +01:00
|
|
|
|
* Called when the user clicked on a floating window while holding the
|
|
|
|
|
* floating_modifier and the right mouse button.
|
2009-08-22 07:49:28 +02:00
|
|
|
|
* Calls the drag_pointer function with the resize_window callback
|
|
|
|
|
*
|
|
|
|
|
*/
|
2011-11-10 20:17:36 +01:00
|
|
|
|
void floating_resize_window(Con *con, const bool proportional,
|
|
|
|
|
const xcb_button_press_event_t *event) {
|
2010-06-28 22:23:32 +02:00
|
|
|
|
DLOG("floating_resize_window\n");
|
2009-08-22 07:49:28 +02:00
|
|
|
|
|
2010-06-28 22:23:32 +02:00
|
|
|
|
/* corner saves the nearest corner to the original click. It contains
|
|
|
|
|
* a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
|
|
|
|
|
border_t corner = 0;
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2013-12-25 20:01:37 +01:00
|
|
|
|
if (event->event_x <= (int16_t)(con->rect.width / 2))
|
2010-11-29 22:23:49 +01:00
|
|
|
|
corner |= BORDER_LEFT;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
else
|
|
|
|
|
corner |= BORDER_RIGHT;
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2012-09-23 21:43:43 +02:00
|
|
|
|
int cursor = 0;
|
2013-12-25 20:01:37 +01:00
|
|
|
|
if (event->event_y <= (int16_t)(con->rect.height / 2)) {
|
2010-11-29 22:23:49 +01:00
|
|
|
|
corner |= BORDER_TOP;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
cursor = (corner & BORDER_LEFT) ? XCURSOR_CURSOR_TOP_LEFT_CORNER : XCURSOR_CURSOR_TOP_RIGHT_CORNER;
|
|
|
|
|
} else {
|
2012-09-23 21:43:43 +02:00
|
|
|
|
corner |= BORDER_BOTTOM;
|
2014-06-15 19:07:02 +02:00
|
|
|
|
cursor = (corner & BORDER_LEFT) ? XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER;
|
2012-09-23 21:43:43 +02:00
|
|
|
|
}
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2018-12-12 09:35:11 +01:00
|
|
|
|
struct resize_window_callback_params params = {corner, proportional};
|
2010-03-12 01:31:48 +01:00
|
|
|
|
|
2013-11-01 01:36:31 +01:00
|
|
|
|
/* get the initial rect in case of revert/cancel */
|
2013-09-26 02:52:59 +02:00
|
|
|
|
Rect initial_rect = con->rect;
|
|
|
|
|
|
2018-12-12 09:35:11 +01:00
|
|
|
|
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, false, resize_window_callback, ¶ms);
|
2013-09-26 02:52:59 +02:00
|
|
|
|
|
2017-09-22 18:25:02 +02:00
|
|
|
|
if (!con_exists(con)) {
|
|
|
|
|
DLOG("The container has been closed in the meantime.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-26 02:52:59 +02:00
|
|
|
|
/* If the user cancels, undo the resize */
|
2013-11-01 01:36:31 +01:00
|
|
|
|
if (drag_result == DRAG_REVERT)
|
2013-09-26 02:52:59 +02:00
|
|
|
|
floating_reposition(con, initial_rect);
|
2012-09-24 21:14:40 +02:00
|
|
|
|
|
|
|
|
|
/* If this is a scratchpad window, don't auto center it from now on. */
|
|
|
|
|
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
|
|
|
|
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
2009-08-22 07:49:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
2011-12-18 18:24:27 +01:00
|
|
|
|
/*
|
|
|
|
|
* Repositions the CT_FLOATING_CON to have the coordinates specified by
|
|
|
|
|
* newrect, but only if the coordinates are not out-of-bounds. Also reassigns
|
|
|
|
|
* the floating con to a different workspace if this move was across different
|
|
|
|
|
* outputs.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-03-18 01:41:12 +01:00
|
|
|
|
bool floating_reposition(Con *con, Rect newrect) {
|
2011-12-18 18:24:27 +01:00
|
|
|
|
/* Sanity check: Are the new coordinates on any output? If not, we
|
|
|
|
|
* ignore that request. */
|
2018-03-17 16:42:49 +01:00
|
|
|
|
if (!output_containing_rect(newrect)) {
|
2011-12-18 18:24:27 +01:00
|
|
|
|
ELOG("No output found at destination coordinates. Not repositioning.\n");
|
2018-03-18 01:41:12 +01:00
|
|
|
|
return false;
|
2011-12-18 18:24:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
con->rect = newrect;
|
|
|
|
|
|
2018-10-05 13:29:29 +02:00
|
|
|
|
bool reassigned = floating_maybe_reassign_ws(con);
|
2012-09-24 21:14:40 +02:00
|
|
|
|
|
|
|
|
|
/* If this is a scratchpad window, don't auto center it from now on. */
|
|
|
|
|
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
|
|
|
|
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
|
|
|
|
|
2018-10-05 13:29:29 +02:00
|
|
|
|
/* Workspace change will already result in a tree_render. */
|
|
|
|
|
if (!reassigned) {
|
2018-12-16 02:27:09 +01:00
|
|
|
|
tree_render();
|
2018-10-05 13:29:29 +02:00
|
|
|
|
}
|
2018-03-18 01:41:12 +01:00
|
|
|
|
return true;
|
2011-12-18 18:24:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-05 08:31:45 +02:00
|
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-10-02 00:52:31 +02:00
|
|
|
|
void floating_resize(Con *floating_con, uint32_t x, uint32_t y) {
|
2015-09-05 08:31:45 +02:00
|
|
|
|
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;
|
2018-10-01 18:47:33 +02:00
|
|
|
|
bool prefer_height = (rect->width == x);
|
2015-09-05 08:31:45 +02:00
|
|
|
|
rect->width = x;
|
|
|
|
|
rect->height = y;
|
|
|
|
|
if (wi)
|
|
|
|
|
rect->width += (wi - 1 - rect->width) % wi;
|
|
|
|
|
if (hi)
|
|
|
|
|
rect->height += (hi - 1 - rect->height) % hi;
|
|
|
|
|
|
2018-10-01 18:47:33 +02:00
|
|
|
|
floating_check_size(floating_con, prefer_height);
|
2015-09-05 08:31:45 +02:00
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-21 16:07:53 +01:00
|
|
|
|
/*
|
|
|
|
|
* Fixes the coordinates of the floating window whenever the window gets
|
|
|
|
|
* reassigned to a different output (or when the output’s rect changes).
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect) {
|
2012-08-04 15:33:50 +02:00
|
|
|
|
DLOG("Fixing coordinates of floating window %p (rect (%d, %d), %d x %d)\n",
|
|
|
|
|
con, con->rect.x, con->rect.y, con->rect.width, con->rect.height);
|
|
|
|
|
DLOG("old_rect = (%d, %d), %d x %d\n",
|
|
|
|
|
old_rect->x, old_rect->y, old_rect->width, old_rect->height);
|
|
|
|
|
DLOG("new_rect = (%d, %d), %d x %d\n",
|
|
|
|
|
new_rect->x, new_rect->y, new_rect->width, new_rect->height);
|
2012-01-21 16:07:53 +01:00
|
|
|
|
/* First we get the x/y coordinates relative to the x/y coordinates
|
|
|
|
|
* of the output on which the window is on */
|
2014-06-15 19:07:02 +02:00
|
|
|
|
int32_t rel_x = con->rect.x - old_rect->x + (int32_t)(con->rect.width / 2);
|
2012-09-30 06:30:03 +02:00
|
|
|
|
int32_t rel_y = con->rect.y - old_rect->y + (int32_t)(con->rect.height / 2);
|
2012-01-21 16:07:53 +01:00
|
|
|
|
/* Then we calculate a fraction, for example 0.63 for a window
|
|
|
|
|
* which is at y = 1212 of a 1920 px high output */
|
|
|
|
|
DLOG("rel_x = %d, rel_y = %d, fraction_x = %f, fraction_y = %f, output->w = %d, output->h = %d\n",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
rel_x, rel_y, (double)rel_x / old_rect->width, (double)rel_y / old_rect->height,
|
|
|
|
|
old_rect->width, old_rect->height);
|
2012-05-06 11:03:17 +02:00
|
|
|
|
/* Here we have to multiply at first. Or we will lose precision when not compiled with -msse2 */
|
2014-06-15 19:07:02 +02:00
|
|
|
|
con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width) / (int32_t)old_rect->width - (int32_t)(con->rect.width / 2);
|
|
|
|
|
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);
|
2012-01-21 16:07:53 +01:00
|
|
|
|
DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y);
|
|
|
|
|
}
|