gri3-wm/src/randr.c

1020 lines
36 KiB
C
Raw Normal View History

/*
2011-01-04 22:51:42 +01:00
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* For more information on RandR, please see the X.org RandR specification at
* http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
* (take your time to read it completely, it answers all questions).
*
*/
#include "all.h"
#include <time.h>
#include <xcb/randr.h>
2011-01-27 15:40:02 +01:00
/* Pointer to the result of the query for primary output */
xcb_randr_get_output_primary_reply_t *primary;
/* Stores all outputs available in your current session. */
struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
/* This is the output covering the root window */
static Output *root_output;
static bool has_randr_1_5 = false;
/*
* Get a specific output by its internal X11 id. Used by randr_query_outputs
* to check if the output is new (only in the first scan) or if we are
* re-scanning.
*
*/
static Output *get_output_by_id(xcb_randr_output_t id) {
2011-01-04 22:51:42 +01:00
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->id == id)
return output;
2011-01-04 22:51:42 +01:00
return NULL;
}
/*
* Returns the output with the given name or NULL.
* If require_active is true, only active outputs are considered.
*
*/
Output *get_output_by_name(const char *name, const bool require_active) {
2011-01-04 22:51:42 +01:00
Output *output;
bool get_primary = (strcasecmp("primary", name) == 0);
TAILQ_FOREACH(output, &outputs, outputs) {
if ((output->primary && get_primary) ||
((!require_active || output->active) && strcasecmp(output->name, name) == 0)) {
return output;
}
}
2011-01-04 22:51:42 +01:00
return NULL;
}
/*
* Returns the first output which is active.
*
*/
Output *get_first_output(void) {
2011-01-04 22:51:42 +01:00
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->active)
return output;
die("No usable outputs available.\n");
}
/*
* Check whether there are any active outputs (excluding the root output).
*
*/
static bool any_randr_output_active(void) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (output != root_output && !output->to_be_disabled && output->active)
return true;
}
return false;
}
/*
* Returns the active (!) output which contains the coordinates x, y or NULL
* if there is no output which contains these coordinates.
*
*/
Output *get_output_containing(unsigned int x, unsigned int y) {
2011-01-04 22:51:42 +01:00
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
2011-01-04 22:51:42 +01:00
if (!output->active)
continue;
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
2011-01-04 22:51:42 +01:00
if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
y >= output->rect.y && y < (output->rect.y + output->rect.height))
return output;
}
return NULL;
}
/*
* Returns the active output which spans exactly the area specified by
* rect or NULL if there is no output like this.
*
*/
Output *get_output_with_dimensions(Rect rect) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
DLOG("comparing x=%d y=%d %dx%d with x=%d and y=%d %dx%d\n",
rect.x, rect.y, rect.width, rect.height,
output->rect.x, output->rect.y, output->rect.width, output->rect.height);
if (rect.x == output->rect.x && rect.width == output->rect.width &&
rect.y == output->rect.y && rect.height == output->rect.height)
return output;
}
return NULL;
}
/*
* In contained_by_output, we check if any active output contains part of the container.
* We do this by checking if the output rect is intersected by the Rect.
* This is the 2-dimensional counterpart of get_output_containing.
* Since we don't actually need the outputs intersected by the given Rect (There could
* be many), we just return true or false for convenience.
*
*/
bool contained_by_output(Rect rect) {
Output *output;
int lx = rect.x, uy = rect.y;
int rx = rect.x + rect.width, by = rect.y + rect.height;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
return true;
}
return false;
}
/*
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
*
* For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
* get_output_next_wrap(D_DOWN, x) will return the topmost output.
*
* This function always returns a output: if no active outputs can be found,
* current itself is returned.
*
*/
Output *get_output_next_wrap(direction_t direction, Output *current) {
Output *best = get_output_next(direction, current, CLOSEST_OUTPUT);
/* If no output can be found, wrap */
if (!best) {
direction_t opposite;
if (direction == D_RIGHT)
opposite = D_LEFT;
else if (direction == D_LEFT)
opposite = D_RIGHT;
else if (direction == D_DOWN)
opposite = D_UP;
else
opposite = D_DOWN;
best = get_output_next(opposite, current, FARTHEST_OUTPUT);
}
if (!best)
best = current;
DLOG("current = %s, best = %s\n", current->name, best->name);
return best;
}
/*
* Gets the output which is the next one in the given direction.
*
* If close_far == CLOSEST_OUTPUT, then the output next to the current one will
* selected. If close_far == FARTHEST_OUTPUT, the output which is the last one
* in the given direction will be selected.
*
* NULL will be returned when no active outputs are present in the direction
* specified (note that current counts as such an output).
*
*/
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
Rect *cur = &(current->rect),
*other;
Output *output,
*best = NULL;
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
other = &(output->rect);
if ((direction == D_RIGHT && other->x > cur->x) ||
(direction == D_LEFT && other->x < cur->x)) {
/* Skip the output when it doesnt overlap the other ones y
* coordinate at all. */
if ((other->y + other->height) <= cur->y ||
(cur->y + cur->height) <= other->y)
continue;
} else if ((direction == D_DOWN && other->y > cur->y) ||
(direction == D_UP && other->y < cur->y)) {
/* Skip the output when it doesnt overlap the other ones x
* coordinate at all. */
if ((other->x + other->width) <= cur->x ||
(cur->x + cur->width) <= other->x)
continue;
} else
continue;
/* No candidate yet? Start with this one. */
if (!best) {
best = output;
continue;
}
if (close_far == CLOSEST_OUTPUT) {
/* Is this output better (closer to the current output) than our
* current best bet? */
if ((direction == D_RIGHT && other->x < best->rect.x) ||
(direction == D_LEFT && other->x > best->rect.x) ||
(direction == D_DOWN && other->y < best->rect.y) ||
(direction == D_UP && other->y > best->rect.y)) {
best = output;
continue;
}
} else {
/* Is this output better (farther to the current output) than our
* current best bet? */
if ((direction == D_RIGHT && other->x > best->rect.x) ||
(direction == D_LEFT && other->x < best->rect.x) ||
(direction == D_DOWN && other->y > best->rect.y) ||
(direction == D_UP && other->y < best->rect.y)) {
best = output;
continue;
}
}
}
DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL"));
return best;
}
/*
* Creates an output covering the root window.
*
*/
Output *create_root_output(xcb_connection_t *conn) {
Output *s = scalloc(1, sizeof(Output));
s->active = false;
s->rect.x = 0;
s->rect.y = 0;
s->rect.width = root_screen->width_in_pixels;
s->rect.height = root_screen->height_in_pixels;
s->name = "xroot-0";
return s;
}
2011-01-05 00:16:10 +01:00
/*
* Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
* before) to use for the given Output.
*
*/
void output_init_con(Output *output) {
Con *con = NULL, *current;
bool reused = false;
DLOG("init_con for output %s\n", output->name);
/* Search for a Con with that name directly below the root node. There
* might be one from a restored layout. */
TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
2011-01-05 00:16:10 +01:00
if (strcmp(current->name, output->name) != 0)
continue;
con = current;
reused = true;
DLOG("Using existing con %p / %s\n", con, con->name);
break;
}
if (con == NULL) {
con = con_new(croot, NULL);
2011-01-05 00:16:10 +01:00
FREE(con->name);
con->name = sstrdup(output->name);
con->type = CT_OUTPUT;
con->layout = L_OUTPUT;
con_fix_percent(croot);
2011-01-05 00:16:10 +01:00
}
con->rect = output->rect;
output->con = con;
char *name;
sasprintf(&name, "[i3 con] output %s", con->name);
2011-01-05 00:16:10 +01:00
x_set_name(con, name);
FREE(name);
2011-01-05 00:16:10 +01:00
if (reused) {
DLOG("Not adding workspace, this was a reused con\n");
return;
}
DLOG("Changing layout, adding top/bottom dockarea\n");
Con *topdock = con_new(NULL, NULL);
topdock->type = CT_DOCKAREA;
topdock->layout = L_DOCKAREA;
/* this container swallows dock clients */
Match *match = scalloc(1, sizeof(Match));
match_init(match);
match->dock = M_DOCK_TOP;
match->insert_where = M_BELOW;
TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches);
FREE(topdock->name);
topdock->name = sstrdup("topdock");
sasprintf(&name, "[i3 con] top dockarea %s", con->name);
x_set_name(topdock, name);
FREE(name);
DLOG("attaching\n");
con_attach(topdock, con, false);
/* content container */
DLOG("adding main content container\n");
Con *content = con_new(NULL, NULL);
content->type = CT_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
content->layout = L_SPLITH;
FREE(content->name);
content->name = sstrdup("content");
sasprintf(&name, "[i3 con] content %s", con->name);
x_set_name(content, name);
FREE(name);
con_attach(content, con, false);
/* bottom dock container */
Con *bottomdock = con_new(NULL, NULL);
bottomdock->type = CT_DOCKAREA;
bottomdock->layout = L_DOCKAREA;
/* this container swallows dock clients */
match = scalloc(1, sizeof(Match));
match_init(match);
match->dock = M_DOCK_BOTTOM;
match->insert_where = M_BELOW;
TAILQ_INSERT_TAIL(&(bottomdock->swallow_head), match, matches);
FREE(bottomdock->name);
bottomdock->name = sstrdup("bottomdock");
sasprintf(&name, "[i3 con] bottom dockarea %s", con->name);
x_set_name(bottomdock, name);
FREE(name);
DLOG("attaching\n");
con_attach(bottomdock, con, false);
}
/*
* Initializes at least one workspace for this output, trying the following
* steps until there is at least one workspace:
*
* Move existing workspaces, which are assigned to be on the given output, to
* the output.
* Create the first assigned workspace for this output.
* Create the first unused workspace.
*
*/
void init_ws_for_output(Output *output, Con *content) {
/* go through all assignments and move the existing workspaces to this output */
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcmp(assignment->output, output->name) != 0)
continue;
/* check if this workspace actually exists */
Con *workspace = NULL, *out;
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(out),
!strcasecmp(child->name, assignment->name));
if (workspace == NULL)
continue;
/* check that this workspace is not already attached (that means the
* user configured this assignment twice) */
Con *workspace_out = con_get_output(workspace);
if (workspace_out == output->con) {
LOG("Workspace \"%s\" assigned to output \"%s\", but it is already "
"there. Do you have two assignment directives for the same "
"workspace in your configuration file?\n",
workspace->name, output->name);
continue;
}
/* if so, move it over */
LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
workspace->name, workspace_out->name, output->name);
/* if the workspace is currently visible on that output, we need to
* switch to a different workspace - otherwise the output would end up
* with no active workspace */
bool visible = workspace_is_visible(workspace);
Con *previous = NULL;
if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
previous->name, workspace_out->name);
2011-10-02 17:54:23 +02:00
workspace_show(previous);
}
/* Render the output on which the workspace was to get correct Rects.
* Then, we need to work with the "content" container, since we cannot
* be sure that the workspace itself was rendered at all (in case its
* invisible, it wont be rendered). */
render_con(workspace_out, false);
Con *ws_out_content = output_get_content(workspace_out);
Con *floating_con;
TAILQ_FOREACH(floating_con, &(workspace->floating_head), floating_windows)
/* NB: We use output->con here because content is not yet rendered,
* so it has a rect of {0, 0, 0, 0}. */
floating_fix_coordinates(floating_con, &(ws_out_content->rect), &(output->con->rect));
con_detach(workspace);
con_attach(workspace, content, false);
/* In case the workspace we just moved was visible but there was no
* other workspace to switch to, we need to initialize the source
2016-04-04 09:33:59 +02:00
* output as well */
if (visible && previous == NULL) {
LOG("There is no workspace left on \"%s\", re-initializing\n",
workspace_out->name);
init_ws_for_output(get_output_by_name(workspace_out->name, true),
output_get_content(workspace_out));
DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
}
}
/* if a workspace exists, we are done now */
if (!TAILQ_EMPTY(&(content->nodes_head))) {
/* ensure that one of the workspaces is actually visible (in fullscreen
* mode), if they were invisible before, this might not be the case. */
Con *visible = NULL;
GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT);
if (!visible) {
visible = TAILQ_FIRST(&(content->nodes_head));
focused = content;
2011-10-02 17:54:23 +02:00
workspace_show(visible);
}
return;
}
/* otherwise, we create the first assigned ws for this output */
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcmp(assignment->output, output->name) != 0)
continue;
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
assignment->name, assignment->output);
focused = content;
2011-10-02 17:54:23 +02:00
workspace_show_by_name(assignment->name);
return;
}
/* if there is still no workspace, we create the first free workspace */
2011-01-05 00:16:10 +01:00
DLOG("Now adding a workspace\n");
Con *ws = create_workspace_on_output(output, content);
2011-01-05 00:16:10 +01:00
/* TODO: Set focus in main.c */
con_focus(ws);
}
/*
* This function needs to be called when changing the mode of an output when
* it already has some workspaces (or a bar window) assigned.
*
* It reconfigures the bar window for the new mode, copies the new rect into
* each workspace on this output and forces all windows on the affected
* workspaces to be reconfigured.
*
* It is necessary to call render_layout() afterwards.
*
*/
static void output_change_mode(xcb_connection_t *conn, Output *output) {
2011-01-05 00:16:10 +01:00
DLOG("Output mode changed, updating rect\n");
assert(output->con != NULL);
output->con->rect = output->rect;
Con *content, *workspace, *child;
/* Point content to the container of the workspaces */
content = output_get_content(output->con);
/* Fix the position of all floating windows on this output.
* The 'rect' of each workspace will be updated in src/render.c. */
TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
floating_fix_coordinates(child, &(workspace->rect), &(output->con->rect));
}
}
/* If default_orientation is NO_ORIENTATION, we change the orientation of
* the workspaces and their childs depending on output resolution. This is
* only done for workspaces with maximum one child. */
if (config.default_orientation == NO_ORIENTATION) {
TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
/* Workspaces with more than one child are left untouched because
* we do not want to change an existing layout. */
if (con_num_children(workspace) > 1)
continue;
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
workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout);
if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
if (child->layout == L_SPLITV || child->layout == L_SPLITH)
child->layout = workspace->layout;
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
DLOG("Setting child [%d,%s]'s layout to %d.\n", child->num, child->name, child->layout);
}
}
}
}
/*
* randr_query_outputs_15 uses RandR 1.5 to update outputs.
*
*/
static bool randr_query_outputs_15(void) {
#if XCB_RANDR_MINOR_VERSION < 5
return false;
#else
/* RandR 1.5 available at compile-time, i.e. libxcb is new enough */
if (!has_randr_1_5) {
return false;
}
/* RandR 1.5 available at run-time (supported by the server and not
* disabled by the user) */
DLOG("Querying outputs using RandR 1.5\n");
xcb_generic_error_t *err;
xcb_randr_get_monitors_reply_t *monitors =
xcb_randr_get_monitors_reply(
conn, xcb_randr_get_monitors(conn, root, true), &err);
if (err != NULL) {
ELOG("Could not get RandR monitors: X11 error code %d\n", err->error_code);
free(err);
/* Fall back to RandR ≤ 1.4 */
return false;
}
/* Mark all outputs as to_be_disabled, since xcb_randr_get_monitors() will
* only return active outputs. */
Output *output;
TAILQ_FOREACH(output, &outputs, outputs) {
if (output != root_output) {
output->to_be_disabled = true;
}
}
DLOG("%d RandR monitors found (timestamp %d)\n",
xcb_randr_get_monitors_monitors_length(monitors),
monitors->timestamp);
xcb_randr_monitor_info_iterator_t iter;
for (iter = xcb_randr_get_monitors_monitors_iterator(monitors);
iter.rem;
xcb_randr_monitor_info_next(&iter)) {
const xcb_randr_monitor_info_t *monitor_info = iter.data;
xcb_get_atom_name_reply_t *atom_reply =
xcb_get_atom_name_reply(
conn, xcb_get_atom_name(conn, monitor_info->name), &err);
if (err != NULL) {
ELOG("Could not get RandR monitor name: X11 error code %d\n", err->error_code);
free(err);
continue;
}
char *name;
sasprintf(&name, "%.*s",
xcb_get_atom_name_name_length(atom_reply),
xcb_get_atom_name_name(atom_reply));
free(atom_reply);
Output *new = get_output_by_name(name, false);
if (new == NULL) {
new = scalloc(1, sizeof(Output));
new->name = sstrdup(name);
if (monitor_info->primary) {
TAILQ_INSERT_HEAD(&outputs, new, outputs);
} else {
TAILQ_INSERT_TAIL(&outputs, new, outputs);
}
}
/* We specified get_active == true in xcb_randr_get_monitors(), so we
* will only receive active outputs. */
new->active = true;
new->to_be_disabled = false;
new->primary = monitor_info->primary;
new->changed =
update_if_necessary(&(new->rect.x), monitor_info->x) |
update_if_necessary(&(new->rect.y), monitor_info->y) |
update_if_necessary(&(new->rect.width), monitor_info->width) |
update_if_necessary(&(new->rect.height), monitor_info->height);
DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n",
name,
monitor_info->x, monitor_info->y, monitor_info->width, monitor_info->height,
monitor_info->width_in_millimeters, monitor_info->height_in_millimeters,
monitor_info->primary, monitor_info->automatic);
free(name);
}
free(monitors);
return true;
#endif
}
/*
* Gets called by randr_query_outputs_14() for each output. The function adds
* new outputs to the list of outputs, checks if the mode of existing outputs
* has been changed or if an existing output has been disabled. It will then
* change either the "changed" or the "to_be_deleted" flag of the output, if
* appropriate.
*
*/
static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
xcb_randr_get_output_info_reply_t *output,
xcb_timestamp_t cts,
xcb_randr_get_screen_resources_current_reply_t *res) {
2011-01-04 22:51:42 +01:00
/* each CRT controller has a position in which we are interested in */
xcb_randr_get_crtc_info_reply_t *crtc;
2011-01-04 22:51:42 +01:00
Output *new = get_output_by_id(id);
bool existing = (new != NULL);
if (!existing)
new = scalloc(1, sizeof(Output));
2011-01-04 22:51:42 +01:00
new->id = id;
2011-01-27 15:40:02 +01:00
new->primary = (primary && primary->output == id);
2011-01-04 22:51:42 +01:00
FREE(new->name);
sasprintf(&new->name, "%.*s",
xcb_randr_get_output_info_name_length(output),
xcb_randr_get_output_info_name(output));
2011-01-04 22:51:42 +01:00
DLOG("found output with name %s\n", new->name);
/* Even if no CRTC is used at the moment, we store the output so that
* we do not need to change the list ever again (we only update the
* position/size) */
if (output->crtc == XCB_NONE) {
2011-01-27 15:40:02 +01:00
if (!existing) {
if (new->primary)
TAILQ_INSERT_HEAD(&outputs, new, outputs);
else
TAILQ_INSERT_TAIL(&outputs, new, outputs);
2011-01-27 15:40:02 +01:00
} else if (new->active)
2011-01-04 22:51:42 +01:00
new->to_be_disabled = true;
return;
}
xcb_randr_get_crtc_info_cookie_t icookie;
icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
DLOG("Skipping output %s: could not get CRTC (%p)\n",
new->name, crtc);
free(new);
return;
}
2011-01-04 22:51:42 +01:00
bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
update_if_necessary(&(new->rect.y), crtc->y) |
update_if_necessary(&(new->rect.width), crtc->width) |
update_if_necessary(&(new->rect.height), crtc->height);
free(crtc);
new->active = (new->rect.width != 0 && new->rect.height != 0);
if (!new->active) {
DLOG("width/height 0/0, disabling output\n");
return;
}
DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
new->rect.x, new->rect.y);
2011-01-04 22:51:42 +01:00
/* If we dont need to change an existing output or if the output
* does not exist in the first place, the case is simple: we either
* need to insert the new output or we are done. */
if (!updated || !existing) {
2011-01-27 15:40:02 +01:00
if (!existing) {
if (new->primary)
TAILQ_INSERT_HEAD(&outputs, new, outputs);
else
TAILQ_INSERT_TAIL(&outputs, new, outputs);
2011-01-27 15:40:02 +01:00
}
2011-01-04 22:51:42 +01:00
return;
}
2011-01-04 22:51:42 +01:00
new->changed = true;
}
/*
* randr_query_outputs_14 uses RandR 1.4 to update outputs.
*
*/
static void randr_query_outputs_14(void) {
DLOG("Querying outputs using RandR ≤ 1.4\n");
2011-01-04 22:51:42 +01:00
2011-01-27 15:40:02 +01:00
/* Get screen resources (primary output, crtcs, outputs, modes) */
xcb_randr_get_screen_resources_current_cookie_t rcookie;
2011-01-04 22:51:42 +01:00
rcookie = xcb_randr_get_screen_resources_current(conn, root);
xcb_randr_get_output_primary_cookie_t pcookie;
2011-01-27 15:40:02 +01:00
pcookie = xcb_randr_get_output_primary(conn, root);
if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL)
ELOG("Could not get RandR primary output\n");
else
DLOG("primary output is %08x\n", primary->output);
xcb_randr_get_screen_resources_current_reply_t *res =
xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
if (res == NULL) {
ELOG("Could not query screen resources.\n");
return;
}
2011-01-04 22:51:42 +01:00
/* timestamp of the configuration so that we get consistent replies to all
* requests (if the configuration changes between our different calls) */
const xcb_timestamp_t cts = res->config_timestamp;
2011-01-04 22:51:42 +01:00
const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
2011-01-04 22:51:42 +01:00
/* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
2011-01-04 22:51:42 +01:00
/* Request information for each output */
xcb_randr_get_output_info_cookie_t ocookie[len];
for (int i = 0; i < len; i++)
ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
2011-01-04 22:51:42 +01:00
/* Loop through all outputs available for this X11 screen */
for (int i = 0; i < len; i++) {
xcb_randr_get_output_info_reply_t *output;
if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
continue;
handle_output(conn, randr_outputs[i], output, cts, res);
free(output);
}
FREE(res);
}
/*
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
*
* If no outputs are found use the root window.
*
*/
void randr_query_outputs(void) {
Output *output, *other;
if (!randr_query_outputs_15()) {
randr_query_outputs_14();
2011-01-04 22:51:42 +01:00
}
/* If there's no randr output, enable the output covering the root window. */
if (any_randr_output_active()) {
DLOG("Active RandR output found. Disabling root output.\n");
if (root_output->active)
root_output->to_be_disabled = true;
} else {
DLOG("No active RandR output found. Enabling root output.\n");
root_output->active = true;
}
2011-01-04 22:51:42 +01:00
/* Check for clones, disable the clones and reduce the mode to the
* lowest common mode */
TAILQ_FOREACH(output, &outputs, outputs) {
2011-01-04 22:51:42 +01:00
if (!output->active || output->to_be_disabled)
continue;
2011-01-27 15:40:02 +01:00
DLOG("output %p / %s, position (%d, %d), checking for clones\n",
output, output->name, output->rect.x, output->rect.y);
2011-01-04 22:51:42 +01:00
for (other = output;
other != TAILQ_END(&outputs);
other = TAILQ_NEXT(other, outputs)) {
if (other == output || !other->active || other->to_be_disabled)
continue;
if (other->rect.x != output->rect.x ||
other->rect.y != output->rect.y)
continue;
DLOG("output %p has the same position, his mode = %d x %d\n",
other, other->rect.width, other->rect.height);
2011-01-04 22:51:42 +01:00
uint32_t width = min(other->rect.width, output->rect.width);
uint32_t height = min(other->rect.height, output->rect.height);
if (update_if_necessary(&(output->rect.width), width) |
update_if_necessary(&(output->rect.height), height))
output->changed = true;
update_if_necessary(&(other->rect.width), width);
update_if_necessary(&(other->rect.height), height);
DLOG("disabling output %p (%s)\n", other, other->name);
other->to_be_disabled = true;
DLOG("new output mode %d x %d, other mode %d x %d\n",
output->rect.width, output->rect.height,
other->rect.width, other->rect.height);
}
2011-01-04 22:51:42 +01:00
}
/* Ensure that all outputs which are active also have a con. This is
* necessary because in the next step, a clone might get disabled. Example:
* LVDS1 active, VGA1 gets activated as a clone of LVDS1 (has no con).
* LVDS1 gets disabled. */
TAILQ_FOREACH(output, &outputs, outputs) {
if (output->active && output->con == NULL) {
DLOG("Need to initialize a Con for output %s\n", output->name);
output_init_con(output);
output->changed = false;
}
}
2011-01-04 22:51:42 +01:00
/* Handle outputs which have a new mode or are disabled now (either
* because the user disabled them or because they are clones) */
TAILQ_FOREACH(output, &outputs, outputs) {
2011-01-04 22:51:42 +01:00
if (output->to_be_disabled) {
randr_disable_output(output);
2011-01-05 00:16:10 +01:00
}
if (output->changed) {
2011-01-04 22:51:42 +01:00
output_change_mode(conn, output);
output->changed = false;
}
2011-01-04 22:51:42 +01:00
}
/* Just go through each active output and assign one workspace */
TAILQ_FOREACH(output, &outputs, outputs) {
if (!output->active)
continue;
Con *content = output_get_content(output->con);
if (!TAILQ_EMPTY(&(content->nodes_head)))
continue;
DLOG("Should add ws for output %s\n", output->name);
init_ws_for_output(output, content);
2011-01-04 22:51:42 +01:00
}
2011-01-27 15:40:02 +01:00
/* Focus the primary screen, if possible */
TAILQ_FOREACH(output, &outputs, outputs) {
2011-01-27 15:40:02 +01:00
if (!output->primary || !output->con)
continue;
DLOG("Focusing primary output %s\n", output->name);
con_focus(con_descend_focused(output->con));
2011-01-27 15:40:02 +01:00
}
2011-01-04 22:51:42 +01:00
/* render_layout flushes */
tree_render();
2011-01-27 15:40:02 +01:00
FREE(primary);
}
/*
* Disables the output and moves its content.
*
*/
void randr_disable_output(Output *output) {
assert(output->to_be_disabled);
output->active = false;
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
Output *first = get_first_output();
/* TODO: refactor the following code into a nice function. maybe
* use an on_destroy callback which is implement differently for
* different container types (CT_CONTENT vs. CT_DOCKAREA)? */
Con *first_content = output_get_content(first->con);
if (output->con != NULL) {
/* We need to move the workspaces from the disappearing output to the first output */
/* 1: Get the con to focus next, if the disappearing ws is focused */
Con *next = NULL;
if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
DLOG("This output (%p) was focused! Getting next\n", output->con);
next = focused;
DLOG("next = %p\n", next);
}
/* 2: iterate through workspaces and re-assign them, fixing the coordinates
* of floating containers as we go */
Con *current;
Con *old_content = output_get_content(output->con);
while (!TAILQ_EMPTY(&(old_content->nodes_head))) {
current = TAILQ_FIRST(&(old_content->nodes_head));
if (current != next && TAILQ_EMPTY(&(current->focus_head))) {
/* the workspace is empty and not focused, get rid of it */
DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name);
tree_close_internal(current, DONT_KILL_WINDOW, false, false);
continue;
}
DLOG("Detaching current = %p / %s\n", current, current->name);
con_detach(current);
DLOG("Re-attaching current = %p / %s\n", current, current->name);
con_attach(current, first_content, false);
DLOG("Fixing the coordinates of floating containers\n");
Con *floating_con;
TAILQ_FOREACH(floating_con, &(current->floating_head), floating_windows) {
floating_fix_coordinates(floating_con, &(output->con->rect), &(first->con->rect));
}
DLOG("Done, next\n");
}
DLOG("re-attached all workspaces\n");
if (next) {
DLOG("now focusing next = %p\n", next);
con_focus(next);
workspace_show(con_get_workspace(next));
}
/* 3: move the dock clients to the first output */
Con *child;
TAILQ_FOREACH(child, &(output->con->nodes_head), nodes) {
if (child->type != CT_DOCKAREA)
continue;
DLOG("Handling dock con %p\n", child);
Con *dock;
while (!TAILQ_EMPTY(&(child->nodes_head))) {
dock = TAILQ_FIRST(&(child->nodes_head));
Con *nc;
Match *match;
nc = con_for_window(first->con, dock->window, &match);
DLOG("Moving dock client %p to nc %p\n", dock, nc);
con_detach(dock);
DLOG("Re-attaching\n");
con_attach(dock, nc, false);
DLOG("Done\n");
}
}
DLOG("destroying disappearing con %p\n", output->con);
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 *con = output->con;
/* clear the pointer before calling tree_close_internal in which the memory is freed */
output->con = NULL;
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
tree_close_internal(con, DONT_KILL_WINDOW, true, false);
DLOG("Done. Should be fine now\n");
}
output->to_be_disabled = false;
output->changed = false;
}
static void fallback_to_root_output(void) {
root_output->active = true;
output_init_con(root_output);
init_ws_for_output(root_output, output_get_content(root_output->con));
}
/*
* We have just established a connection to the X server and need the initial
* XRandR information to setup workspaces for each screen.
*
*/
void randr_init(int *event_base, const bool disable_randr15) {
2011-01-04 22:51:42 +01:00
const xcb_query_extension_reply_t *extreply;
root_output = create_root_output(conn);
TAILQ_INSERT_TAIL(&outputs, root_output, outputs);
2011-01-04 22:51:42 +01:00
extreply = xcb_get_extension_data(conn, &xcb_randr_id);
if (!extreply->present) {
DLOG("RandR is not present, activating root output.\n");
fallback_to_root_output();
return;
}
xcb_generic_error_t *err;
xcb_randr_query_version_reply_t *randr_version =
xcb_randr_query_version_reply(
conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err);
if (err != NULL) {
free(err);
ELOG("Could not query RandR version: X11 error code %d\n", err->error_code);
fallback_to_root_output();
return;
}
has_randr_1_5 = (randr_version->major_version >= 1) &&
(randr_version->minor_version >= 5) &&
!disable_randr15;
free(randr_version);
randr_query_outputs();
2011-01-04 22:51:42 +01:00
if (event_base != NULL)
*event_base = extreply->first_event;
2011-01-04 22:51:42 +01:00
xcb_randr_select_input(conn, root,
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
2011-01-04 22:51:42 +01:00
xcb_flush(conn);
}