2010-03-02 12:47:21 +01:00
|
|
|
|
/*
|
2011-01-04 22:51:42 +01:00
|
|
|
|
* vim:ts=4:sw=4:expandtab
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
* i3 - an improved dynamic tiling window manager
|
2015-04-04 02:17:56 +02:00
|
|
|
|
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
* For more information on RandR, please see the X.org RandR specification at
|
2017-09-24 10:19:07 +02:00
|
|
|
|
* https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
|
2010-03-02 12:47:21 +01:00
|
|
|
|
* (take your time to read it completely, it answers all questions).
|
|
|
|
|
*
|
|
|
|
|
*/
|
2011-10-23 00:40:02 +02:00
|
|
|
|
#include "all.h"
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-10-23 00:40:02 +02:00
|
|
|
|
#include <time.h>
|
2010-03-02 12:47:21 +01:00
|
|
|
|
#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;
|
|
|
|
|
|
2010-03-02 12:47:21 +01:00
|
|
|
|
/* Stores all outputs available in your current session. */
|
|
|
|
|
struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
|
|
|
|
|
|
2015-09-14 22:12:47 +02:00
|
|
|
|
/* This is the output covering the root window */
|
|
|
|
|
static Output *root_output;
|
2016-11-28 18:20:46 +01:00
|
|
|
|
static bool has_randr_1_5 = false;
|
2010-03-05 16:18:41 +01:00
|
|
|
|
|
2010-03-02 12:47:21 +01:00
|
|
|
|
/*
|
2010-03-05 16:18:41 +01:00
|
|
|
|
* Get a specific output by its internal X11 id. Used by randr_query_outputs
|
2010-03-02 13:35:43 +01:00
|
|
|
|
* to check if the output is new (only in the first scan) or if we are
|
|
|
|
|
* re-scanning.
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2010-03-02 13:35:43 +01:00
|
|
|
|
static Output *get_output_by_id(xcb_randr_output_t id) {
|
2011-01-04 22:51:42 +01:00
|
|
|
|
Output *output;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs)
|
|
|
|
|
if (output->id == id)
|
|
|
|
|
return output;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-01-04 22:51:42 +01:00
|
|
|
|
return NULL;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2017-08-20 00:19:45 +02:00
|
|
|
|
* Returns the output with the given name or NULL.
|
|
|
|
|
* If require_active is true, only active outputs are considered.
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2017-08-20 00:19:45 +02:00
|
|
|
|
Output *get_output_by_name(const char *name, const bool require_active) {
|
2011-01-04 22:51:42 +01:00
|
|
|
|
Output *output;
|
2017-05-14 10:05:29 +02:00
|
|
|
|
bool get_primary = (strcasecmp("primary", name) == 0);
|
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2017-09-09 11:23:50 +02:00
|
|
|
|
if (output->primary && get_primary) {
|
2017-05-14 10:05:29 +02:00
|
|
|
|
return output;
|
|
|
|
|
}
|
2017-09-09 11:23:50 +02:00
|
|
|
|
if (require_active && !output->active) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
struct output_name *output_name;
|
|
|
|
|
SLIST_FOREACH(output_name, &output->names_head, names) {
|
|
|
|
|
if (strcasecmp(output_name->name, name) == 0) {
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-14 10:05:29 +02:00
|
|
|
|
}
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-01-04 22:51:42 +01:00
|
|
|
|
return NULL;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Returns the first output which is active.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2012-03-31 10:53:04 +02:00
|
|
|
|
Output *get_first_output(void) {
|
2011-01-04 22:51:42 +01:00
|
|
|
|
Output *output;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs)
|
|
|
|
|
if (output->active)
|
|
|
|
|
return output;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2015-09-14 22:34:05 +02:00
|
|
|
|
die("No usable outputs available.\n");
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-14 22:12:47 +02:00
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-02 12:47:21 +01:00
|
|
|
|
/*
|
2010-03-05 16:18:41 +01:00
|
|
|
|
* Returns the active (!) output which contains the coordinates x, y or NULL
|
|
|
|
|
* if there is no output which contains these coordinates.
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2013-12-25 20:01:37 +01:00
|
|
|
|
Output *get_output_containing(unsigned int x, unsigned int y) {
|
2011-01-04 22:51:42 +01:00
|
|
|
|
Output *output;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
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",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
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;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-17 19:42:54 +01:00
|
|
|
|
/*
|
|
|
|
|
* Returns the active output which contains the midpoint of the given rect. If
|
|
|
|
|
* such an output doesn't exist, returns the output which contains most of the
|
|
|
|
|
* rectangle or NULL if there is no output which intersects with it.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
Output *get_output_from_rect(Rect rect) {
|
|
|
|
|
unsigned int mid_x = rect.x + rect.width / 2;
|
|
|
|
|
unsigned int mid_y = rect.y + rect.height / 2;
|
|
|
|
|
Output *output = get_output_containing(mid_x, mid_y);
|
|
|
|
|
|
|
|
|
|
return output ? output : output_containing_rect(rect);
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-06 21:19:10 +02:00
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-21 18:36:40 +01:00
|
|
|
|
/*
|
2018-03-17 16:42:49 +01:00
|
|
|
|
* In output_containing_rect, we check if any active output contains part of the container.
|
2013-03-21 18:36:40 +01:00
|
|
|
|
* We do this by checking if the output rect is intersected by the Rect.
|
|
|
|
|
* This is the 2-dimensional counterpart of get_output_containing.
|
2018-03-17 16:42:49 +01:00
|
|
|
|
* Returns the output with the maximum intersecting area.
|
2013-03-21 18:36:40 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2018-03-17 16:42:49 +01:00
|
|
|
|
Output *output_containing_rect(Rect rect) {
|
2013-03-21 18:36:40 +01:00
|
|
|
|
Output *output;
|
|
|
|
|
int lx = rect.x, uy = rect.y;
|
|
|
|
|
int rx = rect.x + rect.width, by = rect.y + rect.height;
|
2018-03-17 16:42:49 +01:00
|
|
|
|
long max_area = 0;
|
|
|
|
|
Output *result = NULL;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2013-03-21 18:36:40 +01:00
|
|
|
|
if (!output->active)
|
|
|
|
|
continue;
|
2018-03-17 16:42:49 +01:00
|
|
|
|
int lx_o = (int)output->rect.x, uy_o = (int)output->rect.y;
|
|
|
|
|
int rx_o = (int)(output->rect.x + output->rect.width), by_o = (int)(output->rect.y + output->rect.height);
|
2013-03-21 18:36:40 +01:00
|
|
|
|
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
|
2018-03-17 16:42:49 +01:00
|
|
|
|
int left = max(lx, lx_o);
|
|
|
|
|
int right = min(rx, rx_o);
|
|
|
|
|
int bottom = min(by, by_o);
|
|
|
|
|
int top = max(uy, uy_o);
|
|
|
|
|
if (left < right && bottom > top) {
|
|
|
|
|
long area = (right - left) * (bottom - top);
|
|
|
|
|
if (area > max_area) {
|
|
|
|
|
result = output;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-03-21 18:36:40 +01:00
|
|
|
|
}
|
2018-03-17 16:42:49 +01:00
|
|
|
|
return result;
|
2013-03-21 18:36:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-02 12:47:21 +01:00
|
|
|
|
/*
|
2013-01-25 00:02:09 +01:00
|
|
|
|
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
2013-01-25 00:02:09 +01:00
|
|
|
|
* 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.
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2013-01-25 00:02:09 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
2012-09-22 16:49:37 +02:00
|
|
|
|
if (!best)
|
|
|
|
|
best = current;
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("current = %s, best = %s\n", output_primary_name(current), output_primary_name(best));
|
2012-09-22 16:49:37 +02:00
|
|
|
|
return best;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2011-08-06 18:28:05 +02:00
|
|
|
|
/*
|
|
|
|
|
* Gets the output which is the next one in the given direction.
|
|
|
|
|
*
|
2013-01-25 00:02:09 +01:00
|
|
|
|
* 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).
|
|
|
|
|
*
|
2011-08-06 18:28:05 +02:00
|
|
|
|
*/
|
2012-09-22 16:49:37 +02:00
|
|
|
|
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
|
|
|
|
|
Rect *cur = &(current->rect),
|
|
|
|
|
*other;
|
|
|
|
|
Output *output,
|
2014-06-15 19:07:02 +02:00
|
|
|
|
*best = NULL;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2011-08-06 18:28:05 +02:00
|
|
|
|
if (!output->active)
|
|
|
|
|
continue;
|
|
|
|
|
|
2012-09-22 16:49:37 +02:00
|
|
|
|
other = &(output->rect);
|
|
|
|
|
|
|
|
|
|
if ((direction == D_RIGHT && other->x > cur->x) ||
|
2014-06-15 19:07:02 +02:00
|
|
|
|
(direction == D_LEFT && other->x < cur->x)) {
|
2012-09-22 16:49:37 +02:00
|
|
|
|
/* Skip the output when it doesn’t overlap the other one’s y
|
|
|
|
|
* coordinate at all. */
|
2012-09-22 18:05:22 +02:00
|
|
|
|
if ((other->y + other->height) <= cur->y ||
|
2014-06-15 19:07:02 +02:00
|
|
|
|
(cur->y + cur->height) <= other->y)
|
2012-09-22 16:49:37 +02:00
|
|
|
|
continue;
|
|
|
|
|
} else if ((direction == D_DOWN && other->y > cur->y) ||
|
2014-06-15 19:07:02 +02:00
|
|
|
|
(direction == D_UP && other->y < cur->y)) {
|
2012-09-22 16:49:37 +02:00
|
|
|
|
/* Skip the output when it doesn’t overlap the other one’s x
|
|
|
|
|
* coordinate at all. */
|
2012-09-22 18:05:22 +02:00
|
|
|
|
if ((other->x + other->width) <= cur->x ||
|
2014-06-15 19:07:02 +02:00
|
|
|
|
(cur->x + cur->width) <= other->x)
|
2012-09-22 16:49:37 +02:00
|
|
|
|
continue;
|
|
|
|
|
} else
|
2011-08-06 18:28:05 +02:00
|
|
|
|
continue;
|
|
|
|
|
|
2012-09-22 16:49:37 +02:00
|
|
|
|
/* No candidate yet? Start with this one. */
|
|
|
|
|
if (!best) {
|
|
|
|
|
best = output;
|
2011-08-06 18:28:05 +02:00
|
|
|
|
continue;
|
2012-09-22 16:49:37 +02:00
|
|
|
|
}
|
2011-08-06 18:28:05 +02:00
|
|
|
|
|
2012-09-22 16:49:37 +02:00
|
|
|
|
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) ||
|
2014-06-15 19:07:02 +02:00
|
|
|
|
(direction == D_LEFT && other->x > best->rect.x) ||
|
|
|
|
|
(direction == D_DOWN && other->y < best->rect.y) ||
|
|
|
|
|
(direction == D_UP && other->y > best->rect.y)) {
|
2012-09-22 16:49:37 +02:00
|
|
|
|
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) ||
|
2014-06-15 19:07:02 +02:00
|
|
|
|
(direction == D_LEFT && other->x < best->rect.x) ||
|
|
|
|
|
(direction == D_DOWN && other->y > best->rect.y) ||
|
|
|
|
|
(direction == D_UP && other->y < best->rect.y)) {
|
2012-09-22 16:49:37 +02:00
|
|
|
|
best = output;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2011-08-06 18:28:05 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("current = %s, best = %s\n", output_primary_name(current), (best ? output_primary_name(best) : "NULL"));
|
2012-09-22 16:49:37 +02:00
|
|
|
|
return best;
|
2011-08-06 18:28:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-02 12:47:21 +01:00
|
|
|
|
/*
|
2015-09-14 22:12:47 +02:00
|
|
|
|
* Creates an output covering the root window.
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
2015-09-21 11:44:39 +02:00
|
|
|
|
Output *create_root_output(xcb_connection_t *conn) {
|
|
|
|
|
Output *s = scalloc(1, sizeof(Output));
|
2015-09-14 22:12:47 +02:00
|
|
|
|
|
2015-09-21 11:44:39 +02:00
|
|
|
|
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;
|
2017-09-09 09:37:37 +02:00
|
|
|
|
|
|
|
|
|
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
|
|
|
|
output_name->name = "xroot-0";
|
|
|
|
|
SLIST_INIT(&s->names_head);
|
|
|
|
|
SLIST_INSERT_HEAD(&s->names_head, output_name, names);
|
2015-09-21 11:44:39 +02:00
|
|
|
|
|
|
|
|
|
return s;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("init_con for output %s\n", output_primary_name(output));
|
2011-01-05 00:16:10 +01:00
|
|
|
|
|
|
|
|
|
/* Search for a Con with that name directly below the root node. There
|
|
|
|
|
* might be one from a restored layout. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
|
2017-09-09 09:18:29 +02:00
|
|
|
|
if (strcmp(current->name, output_primary_name(output)) != 0)
|
2011-01-05 00:16:10 +01:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
con = current;
|
|
|
|
|
reused = true;
|
|
|
|
|
DLOG("Using existing con %p / %s\n", con, con->name);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (con == NULL) {
|
2011-06-02 17:21:38 +02:00
|
|
|
|
con = con_new(croot, NULL);
|
2011-01-05 00:16:10 +01:00
|
|
|
|
FREE(con->name);
|
2017-09-09 09:18:29 +02:00
|
|
|
|
con->name = sstrdup(output_primary_name(output));
|
2011-01-05 00:16:10 +01:00
|
|
|
|
con->type = CT_OUTPUT;
|
2011-02-20 23:43:03 +01:00
|
|
|
|
con->layout = L_OUTPUT;
|
2011-06-11 14:36:09 +02:00
|
|
|
|
con_fix_percent(croot);
|
2011-01-05 00:16:10 +01:00
|
|
|
|
}
|
|
|
|
|
con->rect = output->rect;
|
|
|
|
|
output->con = con;
|
|
|
|
|
|
|
|
|
|
char *name;
|
2011-10-23 14:16:56 +02:00
|
|
|
|
sasprintf(&name, "[i3 con] output %s", con->name);
|
2011-01-05 00:16:10 +01:00
|
|
|
|
x_set_name(con, name);
|
2011-02-20 23:43:03 +01:00
|
|
|
|
FREE(name);
|
2011-01-05 00:16:10 +01:00
|
|
|
|
|
|
|
|
|
if (reused) {
|
|
|
|
|
DLOG("Not adding workspace, this was a reused con\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2011-02-20 23:43:03 +01:00
|
|
|
|
|
|
|
|
|
DLOG("Changing layout, adding top/bottom dockarea\n");
|
2011-06-02 17:21:38 +02:00
|
|
|
|
Con *topdock = con_new(NULL, NULL);
|
2011-02-20 23:43:03 +01:00
|
|
|
|
topdock->type = CT_DOCKAREA;
|
|
|
|
|
topdock->layout = L_DOCKAREA;
|
|
|
|
|
/* this container swallows dock clients */
|
2015-08-03 11:50:13 +02:00
|
|
|
|
Match *match = scalloc(1, sizeof(Match));
|
2011-02-20 23:43:03 +01:00
|
|
|
|
match_init(match);
|
2011-02-21 14:27:32 +01:00
|
|
|
|
match->dock = M_DOCK_TOP;
|
2011-02-20 23:43:03 +01:00
|
|
|
|
match->insert_where = M_BELOW;
|
|
|
|
|
TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches);
|
|
|
|
|
|
2011-07-31 17:11:51 +02:00
|
|
|
|
FREE(topdock->name);
|
2011-02-20 23:43:03 +01:00
|
|
|
|
topdock->name = sstrdup("topdock");
|
|
|
|
|
|
2011-10-23 14:16:56 +02:00
|
|
|
|
sasprintf(&name, "[i3 con] top dockarea %s", con->name);
|
2011-02-20 23:43:03 +01:00
|
|
|
|
x_set_name(topdock, name);
|
|
|
|
|
FREE(name);
|
|
|
|
|
DLOG("attaching\n");
|
|
|
|
|
con_attach(topdock, con, false);
|
|
|
|
|
|
2011-02-21 14:27:32 +01:00
|
|
|
|
/* content container */
|
|
|
|
|
|
2011-02-20 23:43:03 +01:00
|
|
|
|
DLOG("adding main content container\n");
|
2011-06-02 17:21:38 +02:00
|
|
|
|
Con *content = con_new(NULL, NULL);
|
2011-02-20 23:43:03 +01:00
|
|
|
|
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;
|
2011-07-31 17:11:51 +02:00
|
|
|
|
FREE(content->name);
|
2011-02-20 23:43:03 +01:00
|
|
|
|
content->name = sstrdup("content");
|
|
|
|
|
|
2011-10-23 14:16:56 +02:00
|
|
|
|
sasprintf(&name, "[i3 con] content %s", con->name);
|
2011-02-20 23:43:03 +01:00
|
|
|
|
x_set_name(content, name);
|
|
|
|
|
FREE(name);
|
|
|
|
|
con_attach(content, con, false);
|
|
|
|
|
|
2011-02-21 14:27:32 +01:00
|
|
|
|
/* bottom dock container */
|
2011-06-02 17:21:38 +02:00
|
|
|
|
Con *bottomdock = con_new(NULL, NULL);
|
2011-02-21 14:27:32 +01:00
|
|
|
|
bottomdock->type = CT_DOCKAREA;
|
|
|
|
|
bottomdock->layout = L_DOCKAREA;
|
|
|
|
|
/* this container swallows dock clients */
|
2015-08-03 11:50:13 +02:00
|
|
|
|
match = scalloc(1, sizeof(Match));
|
2011-02-21 14:27:32 +01:00
|
|
|
|
match_init(match);
|
|
|
|
|
match->dock = M_DOCK_BOTTOM;
|
|
|
|
|
match->insert_where = M_BELOW;
|
|
|
|
|
TAILQ_INSERT_TAIL(&(bottomdock->swallow_head), match, matches);
|
|
|
|
|
|
2011-07-31 17:11:51 +02:00
|
|
|
|
FREE(bottomdock->name);
|
2011-02-21 14:27:32 +01:00
|
|
|
|
bottomdock->name = sstrdup("bottomdock");
|
|
|
|
|
|
2011-10-23 14:16:56 +02:00
|
|
|
|
sasprintf(&name, "[i3 con] bottom dockarea %s", con->name);
|
2011-02-21 14:27:32 +01:00
|
|
|
|
x_set_name(bottomdock, name);
|
|
|
|
|
FREE(name);
|
|
|
|
|
DLOG("attaching\n");
|
|
|
|
|
con_attach(bottomdock, con, false);
|
2017-09-18 16:37:34 +02:00
|
|
|
|
|
|
|
|
|
/* Change focus to the content container */
|
|
|
|
|
TAILQ_REMOVE(&(con->focus_head), content, focused);
|
|
|
|
|
TAILQ_INSERT_HEAD(&(con->focus_head), content, focused);
|
2011-05-14 22:13:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-09-12 15:53:20 +02:00
|
|
|
|
void init_ws_for_output(Output *output) {
|
|
|
|
|
Con *content = output_get_content(output->con);
|
2018-09-11 19:39:33 +02:00
|
|
|
|
Con *previous_focus = con_get_workspace(focused);
|
|
|
|
|
|
2011-05-14 22:13:29 +02:00
|
|
|
|
/* go through all assignments and move the existing workspaces to this output */
|
|
|
|
|
struct Workspace_Assignment *assignment;
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
2018-03-27 23:55:20 +02:00
|
|
|
|
if (!output_triggers_assignment(output, assignment)) {
|
2011-05-14 22:13:29 +02:00
|
|
|
|
continue;
|
2018-03-27 23:55:20 +02:00
|
|
|
|
}
|
2018-03-27 21:18:17 +02:00
|
|
|
|
Con *workspace = get_existing_workspace_by_name(assignment->name);
|
2011-05-14 22:13:29 +02:00
|
|
|
|
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",
|
2017-09-09 09:18:29 +02:00
|
|
|
|
workspace->name, output_primary_name(output));
|
2011-05-14 22:13:29 +02:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-11 18:11:05 +02:00
|
|
|
|
DLOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
|
|
|
|
|
workspace->name, workspace_out->name, output_primary_name(output));
|
|
|
|
|
/* Need to copy output's rect since content is not yet rendered. We
|
|
|
|
|
* can't call render_con here because render_output only proceeds if a
|
|
|
|
|
* workspace exists. */
|
|
|
|
|
content->rect = output->con->rect;
|
|
|
|
|
workspace_move_to_output(workspace, output);
|
2011-05-14 22:13:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-09-11 19:39:33 +02:00
|
|
|
|
/* Temporarily set the focused container, might not be initialized yet. */
|
|
|
|
|
focused = content;
|
|
|
|
|
|
2011-05-14 22:13:29 +02:00
|
|
|
|
/* 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));
|
2011-10-02 17:54:23 +02:00
|
|
|
|
workspace_show(visible);
|
2011-05-14 22:13:29 +02:00
|
|
|
|
}
|
2018-09-11 19:39:33 +02:00
|
|
|
|
goto restore_focus;
|
2011-05-14 22:13:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* otherwise, we create the first assigned ws for this output */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
2018-03-27 23:55:20 +02:00
|
|
|
|
if (!output_triggers_assignment(output, assignment)) {
|
2011-05-14 22:13:29 +02:00
|
|
|
|
continue;
|
2018-03-27 23:55:20 +02:00
|
|
|
|
}
|
2011-05-14 22:13:29 +02:00
|
|
|
|
|
|
|
|
|
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
|
|
|
|
|
assignment->name, assignment->output);
|
2011-10-02 17:54:23 +02:00
|
|
|
|
workspace_show_by_name(assignment->name);
|
2018-09-11 19:39:33 +02:00
|
|
|
|
goto restore_focus;
|
2011-05-14 22:13:29 +02:00
|
|
|
|
}
|
2011-02-21 14:27:32 +01:00
|
|
|
|
|
2011-05-14 22:13:29 +02:00
|
|
|
|
/* 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");
|
2018-09-11 19:39:33 +02:00
|
|
|
|
workspace_show(create_workspace_on_output(output, content));
|
2011-01-05 00:16:10 +01:00
|
|
|
|
|
2018-09-11 19:39:33 +02:00
|
|
|
|
restore_focus:
|
|
|
|
|
if (previous_focus) {
|
|
|
|
|
workspace_show(previous_focus);
|
|
|
|
|
}
|
2011-01-05 00:16:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-02 12:47:21 +01:00
|
|
|
|
/*
|
|
|
|
|
* 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;
|
2011-03-16 11:56:51 +01:00
|
|
|
|
|
2011-03-17 17:53:56 +01:00
|
|
|
|
Con *content, *workspace, *child;
|
2011-03-16 11:56:51 +01:00
|
|
|
|
|
2011-03-17 17:53:56 +01:00
|
|
|
|
/* Point content to the container of the workspaces */
|
|
|
|
|
content = output_get_content(output->con);
|
2011-03-16 11:56:51 +01:00
|
|
|
|
|
2012-01-21 16:07:53 +01:00
|
|
|
|
/* Fix the position of all floating windows on this output.
|
|
|
|
|
* The 'rect' of each workspace will be updated in src/render.c. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
|
|
|
|
|
TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
|
2012-01-21 16:07:53 +01:00
|
|
|
|
floating_fix_coordinates(child, &(workspace->rect), &(output->con->rect));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-16 11:56:51 +01:00
|
|
|
|
/* If default_orientation is NO_ORIENTATION, we change the orientation of
|
2018-10-10 17:31:03 +02:00
|
|
|
|
* the workspaces and their children depending on output resolution. This is
|
2011-03-16 11:56:51 +01:00
|
|
|
|
* only done for workspaces with maximum one child. */
|
|
|
|
|
if (config.default_orientation == NO_ORIENTATION) {
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
|
2011-03-17 17:53:56 +01:00
|
|
|
|
/* 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);
|
2011-03-17 17:53:56 +01:00
|
|
|
|
if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
|
2012-08-17 01:53:45 +02:00
|
|
|
|
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);
|
2011-03-17 17:53:56 +01:00
|
|
|
|
}
|
2011-03-16 11:56:51 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2016-11-28 18:20:46 +01:00
|
|
|
|
* 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);
|
|
|
|
|
|
2017-08-20 00:19:45 +02:00
|
|
|
|
Output *new = get_output_by_name(name, false);
|
2016-11-28 18:20:46 +01:00
|
|
|
|
if (new == NULL) {
|
|
|
|
|
new = scalloc(1, sizeof(Output));
|
2017-09-09 09:37:37 +02:00
|
|
|
|
|
2017-09-09 11:00:22 +02:00
|
|
|
|
SLIST_INIT(&new->names_head);
|
|
|
|
|
|
|
|
|
|
/* Register associated output names in addition to the monitor name */
|
|
|
|
|
xcb_randr_output_t *randr_outputs = xcb_randr_monitor_info_outputs(monitor_info);
|
|
|
|
|
int randr_output_len = xcb_randr_monitor_info_outputs_length(monitor_info);
|
|
|
|
|
for (int i = 0; i < randr_output_len; i++) {
|
|
|
|
|
xcb_randr_output_t randr_output = randr_outputs[i];
|
|
|
|
|
|
|
|
|
|
xcb_randr_get_output_info_reply_t *info =
|
|
|
|
|
xcb_randr_get_output_info_reply(conn,
|
|
|
|
|
xcb_randr_get_output_info(conn, randr_output, monitors->timestamp),
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
if (info != NULL && info->crtc != XCB_NONE) {
|
|
|
|
|
char *oname;
|
|
|
|
|
sasprintf(&oname, "%.*s",
|
|
|
|
|
xcb_randr_get_output_info_name_length(info),
|
|
|
|
|
xcb_randr_get_output_info_name(info));
|
|
|
|
|
|
|
|
|
|
if (strcmp(name, oname) != 0) {
|
|
|
|
|
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
|
|
|
|
output_name->name = sstrdup(oname);
|
|
|
|
|
SLIST_INSERT_HEAD(&new->names_head, output_name, names);
|
|
|
|
|
} else {
|
|
|
|
|
free(oname);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FREE(info);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Insert the monitor name last, so that it's used as the primary name */
|
2017-09-09 09:37:37 +02:00
|
|
|
|
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
|
|
|
|
output_name->name = sstrdup(name);
|
|
|
|
|
SLIST_INSERT_HEAD(&new->names_head, output_name, names);
|
|
|
|
|
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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
|
2010-03-05 14:32:48 +01:00
|
|
|
|
* appropriate.
|
2010-03-02 12:47:21 +01:00
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
|
|
|
|
xcb_randr_get_output_info_reply_t *output,
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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 */
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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);
|
2017-09-09 09:37:37 +02:00
|
|
|
|
if (!existing) {
|
2015-08-03 11:50:13 +02:00
|
|
|
|
new = scalloc(1, sizeof(Output));
|
2017-09-09 09:37:37 +02:00
|
|
|
|
SLIST_INIT(&new->names_head);
|
|
|
|
|
}
|
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);
|
2017-09-09 09:37:37 +02:00
|
|
|
|
while (!SLIST_EMPTY(&new->names_head)) {
|
|
|
|
|
FREE(SLIST_FIRST(&new->names_head)->name);
|
|
|
|
|
struct output_name *old_head = SLIST_FIRST(&new->names_head);
|
|
|
|
|
SLIST_REMOVE_HEAD(&new->names_head, names);
|
|
|
|
|
FREE(old_head);
|
|
|
|
|
}
|
|
|
|
|
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
|
|
|
|
sasprintf(&output_name->name, "%.*s",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
xcb_randr_get_output_info_name_length(output),
|
|
|
|
|
xcb_randr_get_output_info_name(output));
|
2017-09-09 09:37:37 +02:00
|
|
|
|
SLIST_INSERT_HEAD(&new->names_head, output_name, names);
|
2011-01-04 22:51:42 +01:00
|
|
|
|
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("found output with name %s\n", output_primary_name(new));
|
2011-01-04 22:51:42 +01:00
|
|
|
|
|
|
|
|
|
/* 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);
|
2014-06-15 19:07:02 +02:00
|
|
|
|
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",
|
2017-09-09 09:18:29 +02:00
|
|
|
|
output_primary_name(new), crtc);
|
2011-01-04 22:51:42 +01:00
|
|
|
|
free(new);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-04-24 00:21:15 +02:00
|
|
|
|
|
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,
|
2014-06-15 19:07:02 +02:00
|
|
|
|
new->rect.x, new->rect.y);
|
2011-01-04 22:51:42 +01:00
|
|
|
|
|
|
|
|
|
/* If we don’t 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);
|
2014-06-15 19:07:02 +02:00
|
|
|
|
else
|
|
|
|
|
TAILQ_INSERT_TAIL(&outputs, new, outputs);
|
2011-01-27 15:40:02 +01:00
|
|
|
|
}
|
2011-01-04 22:51:42 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-01-04 22:51:42 +01:00
|
|
|
|
new->changed = true;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-14 22:34:05 +02:00
|
|
|
|
/*
|
2016-11-28 18:20:46 +01:00
|
|
|
|
* randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
|
2015-09-14 22:12:47 +02:00
|
|
|
|
*
|
2015-09-14 22:34:05 +02:00
|
|
|
|
*/
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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) */
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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);
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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");
|
2014-06-15 19:07:02 +02:00
|
|
|
|
else
|
|
|
|
|
DLOG("primary output is %08x\n", primary->output);
|
2015-09-14 22:12:47 +02:00
|
|
|
|
|
2016-11-28 18:20:46 +01:00
|
|
|
|
xcb_randr_get_screen_resources_current_reply_t *res =
|
|
|
|
|
xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
|
2015-10-19 21:17:35 +02:00
|
|
|
|
if (res == NULL) {
|
|
|
|
|
ELOG("Could not query screen resources.\n");
|
2016-11-28 18:20:46 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
2011-01-04 22:51:42 +01:00
|
|
|
|
|
2016-11-28 18:20:46 +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
|
|
|
|
|
2016-11-28 18:20:46 +01:00
|
|
|
|
const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
|
2011-01-04 22:51:42 +01:00
|
|
|
|
|
2016-11-28 18:20:46 +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
|
|
|
|
|
2016-11-28 18:20:46 +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
|
|
|
|
|
2016-11-28 18:20:46 +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
|
|
|
|
}
|
|
|
|
|
|
2015-09-14 22:12:47 +02: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");
|
2018-04-30 02:54:38 +02:00
|
|
|
|
if (root_output && root_output->active) {
|
2015-09-14 22:12:47 +02:00
|
|
|
|
root_output->to_be_disabled = true;
|
2018-04-30 02:54:38 +02:00
|
|
|
|
}
|
2015-09-14 22:12:47 +02:00
|
|
|
|
} 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 */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
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",
|
2017-09-09 09:18:29 +02:00
|
|
|
|
output, output_primary_name(output), 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",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("disabling output %p (%s)\n", other, output_primary_name(other));
|
2011-01-04 22:51:42 +01:00
|
|
|
|
other->to_be_disabled = true;
|
|
|
|
|
|
|
|
|
|
DLOG("new output mode %d x %d, other mode %d x %d\n",
|
2014-06-15 19:07:02 +02:00
|
|
|
|
output->rect.width, output->rect.height,
|
|
|
|
|
other->rect.width, other->rect.height);
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
2011-01-04 22:51:42 +01:00
|
|
|
|
}
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-04-27 10:18:46 +02: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. */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2011-04-27 10:18:46 +02:00
|
|
|
|
if (output->active && output->con == NULL) {
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("Need to initialize a Con for output %s\n", output_primary_name(output));
|
2011-04-27 10:18:46 +02:00
|
|
|
|
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) */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2011-01-04 22:51:42 +01:00
|
|
|
|
if (output->to_be_disabled) {
|
2016-05-08 12:49:24 +02:00
|
|
|
|
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;
|
2010-03-05 14:32:48 +01:00
|
|
|
|
}
|
2011-01-04 22:51:42 +01:00
|
|
|
|
}
|
2010-03-05 14:32:48 +01:00
|
|
|
|
|
2011-05-14 22:13:29 +02:00
|
|
|
|
/* Just go through each active output and assign one workspace */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2011-05-14 22:13:29 +02:00
|
|
|
|
if (!output->active)
|
|
|
|
|
continue;
|
|
|
|
|
Con *content = output_get_content(output->con);
|
|
|
|
|
if (!TAILQ_EMPTY(&(content->nodes_head)))
|
|
|
|
|
continue;
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("Should add ws for output %s\n", output_primary_name(output));
|
2018-09-12 15:53:20 +02:00
|
|
|
|
init_ws_for_output(output);
|
2011-01-04 22:51:42 +01:00
|
|
|
|
}
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-01-27 15:40:02 +01:00
|
|
|
|
/* Focus the primary screen, if possible */
|
2014-06-19 11:20:32 +02:00
|
|
|
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
2011-01-27 15:40:02 +01:00
|
|
|
|
if (!output->primary || !output->con)
|
|
|
|
|
continue;
|
|
|
|
|
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("Focusing primary output %s\n", output_primary_name(output));
|
2018-08-17 05:06:56 +02:00
|
|
|
|
Con *content = output_get_content(output->con);
|
|
|
|
|
Con *ws = TAILQ_FIRST(&(content)->focus_head);
|
|
|
|
|
workspace_show(ws);
|
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);
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-08 12:49:24 +02:00
|
|
|
|
/*
|
|
|
|
|
* Disables the output and moves its content.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void randr_disable_output(Output *output) {
|
|
|
|
|
assert(output->to_be_disabled);
|
|
|
|
|
|
|
|
|
|
output->active = false;
|
2017-09-09 09:18:29 +02:00
|
|
|
|
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output_primary_name(output));
|
2016-05-08 12:49:24 +02:00
|
|
|
|
|
|
|
|
|
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 */
|
2018-09-11 19:09:16 +02:00
|
|
|
|
/* 1: Get the con to focus next */
|
|
|
|
|
Con *next = focused;
|
2016-05-08 12:49:24 +02:00
|
|
|
|
|
|
|
|
|
/* 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);
|
2018-07-30 00:56:51 +02:00
|
|
|
|
tree_close_internal(current, DONT_KILL_WINDOW, false);
|
2016-05-08 12:49:24 +02:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-15 12:48:14 +01:00
|
|
|
|
/* Restore focus after con_detach / con_attach. next can be NULL, see #3523. */
|
|
|
|
|
if (next) {
|
|
|
|
|
DLOG("now focusing next = %p\n", next);
|
|
|
|
|
con_focus(next);
|
|
|
|
|
workspace_show(con_get_workspace(next));
|
|
|
|
|
}
|
2016-05-08 12:49:24 +02:00
|
|
|
|
|
|
|
|
|
/* 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 */
|
2016-05-08 12:49:24 +02:00
|
|
|
|
output->con = NULL;
|
2018-07-30 00:56:51 +02:00
|
|
|
|
tree_close_internal(con, DONT_KILL_WINDOW, true);
|
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
|
|
|
|
DLOG("Done. Should be fine now\n");
|
2016-05-08 12:49:24 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output->to_be_disabled = false;
|
|
|
|
|
output->changed = false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-28 18:20:46 +01:00
|
|
|
|
static void fallback_to_root_output(void) {
|
|
|
|
|
root_output->active = true;
|
|
|
|
|
output_init_con(root_output);
|
2018-09-12 15:53:20 +02:00
|
|
|
|
init_ws_for_output(root_output);
|
2016-11-28 18:20:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
2010-03-02 12:47:21 +01:00
|
|
|
|
/*
|
|
|
|
|
* We have just established a connection to the X server and need the initial
|
|
|
|
|
* XRandR information to setup workspaces for each screen.
|
|
|
|
|
*
|
|
|
|
|
*/
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2015-09-21 11:44:39 +02:00
|
|
|
|
root_output = create_root_output(conn);
|
|
|
|
|
TAILQ_INSERT_TAIL(&outputs, root_output, outputs);
|
2015-09-14 22:12:47 +02:00
|
|
|
|
|
2011-01-04 22:51:42 +01:00
|
|
|
|
extreply = xcb_get_extension_data(conn, &xcb_randr_id);
|
2015-09-21 11:44:39 +02:00
|
|
|
|
if (!extreply->present) {
|
|
|
|
|
DLOG("RandR is not present, activating root output.\n");
|
2016-11-28 18:20:46 +01:00
|
|
|
|
fallback_to_root_output();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-09-21 11:44:39 +02:00
|
|
|
|
|
2016-11-28 18:20:46 +01:00
|
|
|
|
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) {
|
|
|
|
|
ELOG("Could not query RandR version: X11 error code %d\n", err->error_code);
|
2017-09-17 00:14:47 +02:00
|
|
|
|
free(err);
|
2016-11-28 18:20:46 +01:00
|
|
|
|
fallback_to_root_output();
|
2011-11-25 00:49:35 +01:00
|
|
|
|
return;
|
2015-09-21 11:44:39 +02:00
|
|
|
|
}
|
2015-09-14 22:12:47 +02:00
|
|
|
|
|
2016-11-28 18:20:46 +01:00
|
|
|
|
has_randr_1_5 = (randr_version->major_version >= 1) &&
|
|
|
|
|
(randr_version->minor_version >= 5) &&
|
|
|
|
|
!disable_randr15;
|
|
|
|
|
|
|
|
|
|
free(randr_version);
|
|
|
|
|
|
2015-09-14 22:12:47 +02:00
|
|
|
|
randr_query_outputs();
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-01-04 22:51:42 +01:00
|
|
|
|
if (event_base != NULL)
|
|
|
|
|
*event_base = extreply->first_event;
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-01-04 22:51:42 +01:00
|
|
|
|
xcb_randr_select_input(conn, root,
|
2014-06-15 19:07:02 +02:00
|
|
|
|
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
|
|
|
|
|
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
|
|
|
|
|
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
|
|
|
|
|
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
|
2010-03-02 12:47:21 +01:00
|
|
|
|
|
2011-01-04 22:51:42 +01:00
|
|
|
|
xcb_flush(conn);
|
2010-03-02 12:47:21 +01:00
|
|
|
|
}
|