Merge pull request #2920 from CyberShadow/monitor-output-names
Consider RandR 1.5's monitors' output names in addition to monitor names
This commit is contained in:
commit
44e4ad52f6
|
@ -869,6 +869,18 @@ The 'output' is the name of the RandR output you attach your screen to. On a
|
||||||
laptop, you might have VGA1 and LVDS1 as output names. You can see the
|
laptop, you might have VGA1 and LVDS1 as output names. You can see the
|
||||||
available outputs by running +xrandr --current+.
|
available outputs by running +xrandr --current+.
|
||||||
|
|
||||||
|
If your X server supports RandR 1.5 or newer, i3 will use RandR monitor objects
|
||||||
|
instead of output objects. Run +xrandr --listmonitors+ to see a list. Usually,
|
||||||
|
a monitor object contains exactly one output, and has the same name as the
|
||||||
|
output; but should that not be the case, you may specify the name of either the
|
||||||
|
monitor or the output in i3's configuration. For example, the Dell UP2414Q uses
|
||||||
|
two scalers internally, so its output names might be “DP1” and “DP2”, but the
|
||||||
|
monitor name is “Dell UP2414Q”.
|
||||||
|
|
||||||
|
(Note that even if you specify the name of an output which doesn't span the
|
||||||
|
entire monitor, i3 will still use the entire area of the containing monitor
|
||||||
|
rather than that of just the output's.)
|
||||||
|
|
||||||
If you use named workspaces, they must be quoted:
|
If you use named workspaces, they must be quoted:
|
||||||
|
|
||||||
*Examples*:
|
*Examples*:
|
||||||
|
|
|
@ -349,6 +349,13 @@ struct Autostart {
|
||||||
autostarts_always;
|
autostarts_always;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct output_name {
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
SLIST_ENTRY(output_name)
|
||||||
|
names;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Output is a physical output on your graphics driver. Outputs which
|
* An Output is a physical output on your graphics driver. Outputs which
|
||||||
* are currently in use have (output->active == true). Each output has a
|
* are currently in use have (output->active == true). Each output has a
|
||||||
|
@ -370,8 +377,11 @@ struct xoutput {
|
||||||
bool to_be_disabled;
|
bool to_be_disabled;
|
||||||
bool primary;
|
bool primary;
|
||||||
|
|
||||||
/** Name of the output */
|
/** List of names for the output.
|
||||||
char *name;
|
* An output always has at least one name; the first name is
|
||||||
|
* considered the primary one. */
|
||||||
|
SLIST_HEAD(names_head, output_name)
|
||||||
|
names_head;
|
||||||
|
|
||||||
/** Pointer to the Con which represents this output */
|
/** Pointer to the Con which represents this output */
|
||||||
Con *con;
|
Con *con;
|
||||||
|
|
|
@ -24,6 +24,12 @@ Con *output_get_content(Con *output);
|
||||||
*/
|
*/
|
||||||
Output *get_output_from_string(Output *current_output, const char *output_str);
|
Output *get_output_from_string(Output *current_output, const char *output_str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the primary name of an output.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
char *output_primary_name(Output *output);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the output for the given con.
|
* Returns the output for the given con.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1264,7 +1264,7 @@ void con_move_to_output(Con *con, Output *output) {
|
||||||
Con *ws = NULL;
|
Con *ws = NULL;
|
||||||
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
|
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
|
||||||
assert(ws != NULL);
|
assert(ws != NULL);
|
||||||
DLOG("Moving con %p to output %s\n", con, output->name);
|
DLOG("Moving con %p to output %s\n", con, output_primary_name(output));
|
||||||
con_move_to_workspace(con, ws, false, false, false);
|
con_move_to_workspace(con, ws, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,12 @@ void fake_outputs_init(const char *output_spec) {
|
||||||
new_output->rect.width = min(new_output->rect.width, width);
|
new_output->rect.width = min(new_output->rect.width, width);
|
||||||
new_output->rect.height = min(new_output->rect.height, height);
|
new_output->rect.height = min(new_output->rect.height, height);
|
||||||
} else {
|
} else {
|
||||||
|
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
||||||
new_output = scalloc(1, sizeof(Output));
|
new_output = scalloc(1, sizeof(Output));
|
||||||
sasprintf(&(new_output->name), "fake-%d", num_screens);
|
sasprintf(&(output_name->name), "fake-%d", num_screens);
|
||||||
DLOG("Created new fake output %s (%p)\n", new_output->name, new_output);
|
SLIST_INIT(&(new_output->names_head));
|
||||||
|
SLIST_INSERT_HEAD(&(new_output->names_head), output_name, names);
|
||||||
|
DLOG("Created new fake output %s (%p)\n", output_primary_name(new_output), new_output);
|
||||||
new_output->active = true;
|
new_output->active = true;
|
||||||
new_output->rect.x = x;
|
new_output->rect.x = x;
|
||||||
new_output->rect.y = y;
|
new_output->rect.y = y;
|
||||||
|
|
|
@ -388,7 +388,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
|
||||||
Con *current_output = con_get_output(con);
|
Con *current_output = con_get_output(con);
|
||||||
Output *target = get_output_containing(x, y);
|
Output *target = get_output_containing(x, y);
|
||||||
if (target != NULL && current_output != target->con) {
|
if (target != NULL && current_output != target->con) {
|
||||||
DLOG("Dock client is requested to be moved to output %s, moving it there.\n", target->name);
|
DLOG("Dock client is requested to be moved to output %s, moving it there.\n", output_primary_name(target));
|
||||||
Match *match;
|
Match *match;
|
||||||
Con *nc = con_for_window(target->con, con->window, &match);
|
Con *nc = con_for_window(target->con, con->window, &match);
|
||||||
DLOG("Dock client will be moved to container %p.\n", nc);
|
DLOG("Dock client will be moved to container %p.\n", nc);
|
||||||
|
|
18
src/ipc.c
18
src/ipc.c
|
@ -579,6 +579,11 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
|
||||||
y(array_close);
|
y(array_close);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *canonicalize_output_name(char *name) {
|
||||||
|
Output *output = get_output_by_name(name, false);
|
||||||
|
return output ? output_primary_name(output) : name;
|
||||||
|
}
|
||||||
|
|
||||||
static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
||||||
y(map_open);
|
y(map_open);
|
||||||
|
|
||||||
|
@ -588,8 +593,13 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
||||||
if (config->num_outputs > 0) {
|
if (config->num_outputs > 0) {
|
||||||
ystr("outputs");
|
ystr("outputs");
|
||||||
y(array_open);
|
y(array_open);
|
||||||
for (int c = 0; c < config->num_outputs; c++)
|
for (int c = 0; c < config->num_outputs; c++) {
|
||||||
ystr(config->outputs[c]);
|
/* Convert monitor names (RandR ≥ 1.5) or output names
|
||||||
|
* (RandR < 1.5) into monitor names. This way, existing
|
||||||
|
* configs which use output names transparently keep
|
||||||
|
* working. */
|
||||||
|
ystr(canonicalize_output_name(config->outputs[c]));
|
||||||
|
}
|
||||||
y(array_close);
|
y(array_close);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,7 +609,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
||||||
|
|
||||||
struct tray_output_t *tray_output;
|
struct tray_output_t *tray_output;
|
||||||
TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) {
|
TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) {
|
||||||
ystr(tray_output->output);
|
ystr(canonicalize_output_name(tray_output->output));
|
||||||
}
|
}
|
||||||
|
|
||||||
y(array_close);
|
y(array_close);
|
||||||
|
@ -829,7 +839,7 @@ IPC_HANDLER(get_outputs) {
|
||||||
y(map_open);
|
y(map_open);
|
||||||
|
|
||||||
ystr("name");
|
ystr("name");
|
||||||
ystr(output->name);
|
ystr(output_primary_name(output));
|
||||||
|
|
||||||
ystr("active");
|
ystr("active");
|
||||||
y(bool, output->active);
|
y(bool, output->active);
|
||||||
|
|
|
@ -687,7 +687,7 @@ int main(int argc, char *argv[]) {
|
||||||
TAILQ_FOREACH(con, &(croot->nodes_head), nodes) {
|
TAILQ_FOREACH(con, &(croot->nodes_head), nodes) {
|
||||||
Output *output;
|
Output *output;
|
||||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||||
if (output->active || strcmp(con->name, output->name) != 0)
|
if (output->active || strcmp(con->name, output_primary_name(output)) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* This will correctly correlate the output with its content
|
/* This will correctly correlate the output with its content
|
||||||
|
|
|
@ -219,7 +219,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||||
LOG("This window is of type dock\n");
|
LOG("This window is of type dock\n");
|
||||||
Output *output = get_output_containing(geom->x, geom->y);
|
Output *output = get_output_containing(geom->x, geom->y);
|
||||||
if (output != NULL) {
|
if (output != NULL) {
|
||||||
DLOG("Starting search at output %s\n", output->name);
|
DLOG("Starting search at output %s\n", output_primary_name(output));
|
||||||
search_at = output->con;
|
search_at = output->con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,14 @@ Output *get_output_from_string(Output *current_output, const char *output_str) {
|
||||||
return get_output_by_name(output_str, true);
|
return get_output_by_name(output_str, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieves the primary name of an output.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
char *output_primary_name(Output *output) {
|
||||||
|
return SLIST_FIRST(&output->names_head)->name;
|
||||||
|
}
|
||||||
|
|
||||||
Output *get_output_for_con(Con *con) {
|
Output *get_output_for_con(Con *con) {
|
||||||
Con *output_con = con_get_output(con);
|
Con *output_con = con_get_output(con);
|
||||||
if (output_con == NULL) {
|
if (output_con == NULL) {
|
||||||
|
|
106
src/randr.c
106
src/randr.c
|
@ -48,10 +48,18 @@ Output *get_output_by_name(const char *name, const bool require_active) {
|
||||||
Output *output;
|
Output *output;
|
||||||
bool get_primary = (strcasecmp("primary", name) == 0);
|
bool get_primary = (strcasecmp("primary", name) == 0);
|
||||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||||
if ((output->primary && get_primary) ||
|
if (output->primary && get_primary) {
|
||||||
((!require_active || output->active) && strcasecmp(output->name, name) == 0)) {
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -178,7 +186,7 @@ Output *get_output_next_wrap(direction_t direction, Output *current) {
|
||||||
}
|
}
|
||||||
if (!best)
|
if (!best)
|
||||||
best = current;
|
best = current;
|
||||||
DLOG("current = %s, best = %s\n", current->name, best->name);
|
DLOG("current = %s, best = %s\n", output_primary_name(current), output_primary_name(best));
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +258,7 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL"));
|
DLOG("current = %s, best = %s\n", output_primary_name(current), (best ? output_primary_name(best) : "NULL"));
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +274,11 @@ Output *create_root_output(xcb_connection_t *conn) {
|
||||||
s->rect.y = 0;
|
s->rect.y = 0;
|
||||||
s->rect.width = root_screen->width_in_pixels;
|
s->rect.width = root_screen->width_in_pixels;
|
||||||
s->rect.height = root_screen->height_in_pixels;
|
s->rect.height = root_screen->height_in_pixels;
|
||||||
s->name = "xroot-0";
|
|
||||||
|
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);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -280,12 +292,12 @@ void output_init_con(Output *output) {
|
||||||
Con *con = NULL, *current;
|
Con *con = NULL, *current;
|
||||||
bool reused = false;
|
bool reused = false;
|
||||||
|
|
||||||
DLOG("init_con for output %s\n", output->name);
|
DLOG("init_con for output %s\n", output_primary_name(output));
|
||||||
|
|
||||||
/* Search for a Con with that name directly below the root node. There
|
/* Search for a Con with that name directly below the root node. There
|
||||||
* might be one from a restored layout. */
|
* might be one from a restored layout. */
|
||||||
TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
|
TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
|
||||||
if (strcmp(current->name, output->name) != 0)
|
if (strcmp(current->name, output_primary_name(output)) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
con = current;
|
con = current;
|
||||||
|
@ -297,7 +309,7 @@ void output_init_con(Output *output) {
|
||||||
if (con == NULL) {
|
if (con == NULL) {
|
||||||
con = con_new(croot, NULL);
|
con = con_new(croot, NULL);
|
||||||
FREE(con->name);
|
FREE(con->name);
|
||||||
con->name = sstrdup(output->name);
|
con->name = sstrdup(output_primary_name(output));
|
||||||
con->type = CT_OUTPUT;
|
con->type = CT_OUTPUT;
|
||||||
con->layout = L_OUTPUT;
|
con->layout = L_OUTPUT;
|
||||||
con_fix_percent(croot);
|
con_fix_percent(croot);
|
||||||
|
@ -384,7 +396,7 @@ void init_ws_for_output(Output *output, Con *content) {
|
||||||
/* go through all assignments and move the existing workspaces to this output */
|
/* go through all assignments and move the existing workspaces to this output */
|
||||||
struct Workspace_Assignment *assignment;
|
struct Workspace_Assignment *assignment;
|
||||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||||
if (strcmp(assignment->output, output->name) != 0)
|
if (strcmp(assignment->output, output_primary_name(output)) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* check if this workspace actually exists */
|
/* check if this workspace actually exists */
|
||||||
|
@ -402,13 +414,13 @@ void init_ws_for_output(Output *output, Con *content) {
|
||||||
LOG("Workspace \"%s\" assigned to output \"%s\", but it is already "
|
LOG("Workspace \"%s\" assigned to output \"%s\", but it is already "
|
||||||
"there. Do you have two assignment directives for the same "
|
"there. Do you have two assignment directives for the same "
|
||||||
"workspace in your configuration file?\n",
|
"workspace in your configuration file?\n",
|
||||||
workspace->name, output->name);
|
workspace->name, output_primary_name(output));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if so, move it over */
|
/* if so, move it over */
|
||||||
LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
|
LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
|
||||||
workspace->name, workspace_out->name, output->name);
|
workspace->name, workspace_out->name, output_primary_name(output));
|
||||||
|
|
||||||
/* if the workspace is currently visible on that output, we need to
|
/* if the workspace is currently visible on that output, we need to
|
||||||
* switch to a different workspace - otherwise the output would end up
|
* switch to a different workspace - otherwise the output would end up
|
||||||
|
@ -445,7 +457,7 @@ void init_ws_for_output(Output *output, Con *content) {
|
||||||
workspace_out->name);
|
workspace_out->name);
|
||||||
init_ws_for_output(get_output_by_name(workspace_out->name, true),
|
init_ws_for_output(get_output_by_name(workspace_out->name, true),
|
||||||
output_get_content(workspace_out));
|
output_get_content(workspace_out));
|
||||||
DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
|
DLOG("Done re-initializing, continuing with \"%s\"\n", output_primary_name(output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,7 +477,7 @@ void init_ws_for_output(Output *output, Con *content) {
|
||||||
|
|
||||||
/* otherwise, we create the first assigned ws for this output */
|
/* otherwise, we create the first assigned ws for this output */
|
||||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||||
if (strcmp(assignment->output, output->name) != 0)
|
if (strcmp(assignment->output, output_primary_name(output)) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
|
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
|
||||||
|
@ -594,7 +606,42 @@ static bool randr_query_outputs_15(void) {
|
||||||
Output *new = get_output_by_name(name, false);
|
Output *new = get_output_by_name(name, false);
|
||||||
if (new == NULL) {
|
if (new == NULL) {
|
||||||
new = scalloc(1, sizeof(Output));
|
new = scalloc(1, sizeof(Output));
|
||||||
new->name = sstrdup(name);
|
|
||||||
|
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 */
|
||||||
|
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);
|
||||||
|
|
||||||
if (monitor_info->primary) {
|
if (monitor_info->primary) {
|
||||||
TAILQ_INSERT_HEAD(&outputs, new, outputs);
|
TAILQ_INSERT_HEAD(&outputs, new, outputs);
|
||||||
} else {
|
} else {
|
||||||
|
@ -643,16 +690,25 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
||||||
|
|
||||||
Output *new = get_output_by_id(id);
|
Output *new = get_output_by_id(id);
|
||||||
bool existing = (new != NULL);
|
bool existing = (new != NULL);
|
||||||
if (!existing)
|
if (!existing) {
|
||||||
new = scalloc(1, sizeof(Output));
|
new = scalloc(1, sizeof(Output));
|
||||||
|
SLIST_INIT(&new->names_head);
|
||||||
|
}
|
||||||
new->id = id;
|
new->id = id;
|
||||||
new->primary = (primary && primary->output == id);
|
new->primary = (primary && primary->output == id);
|
||||||
FREE(new->name);
|
while (!SLIST_EMPTY(&new->names_head)) {
|
||||||
sasprintf(&new->name, "%.*s",
|
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",
|
||||||
xcb_randr_get_output_info_name_length(output),
|
xcb_randr_get_output_info_name_length(output),
|
||||||
xcb_randr_get_output_info_name(output));
|
xcb_randr_get_output_info_name(output));
|
||||||
|
SLIST_INSERT_HEAD(&new->names_head, output_name, names);
|
||||||
|
|
||||||
DLOG("found output with name %s\n", new->name);
|
DLOG("found output with name %s\n", output_primary_name(new));
|
||||||
|
|
||||||
/* Even if no CRTC is used at the moment, we store the output so that
|
/* 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
|
* we do not need to change the list ever again (we only update the
|
||||||
|
@ -672,7 +728,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
||||||
icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
|
icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
|
||||||
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
|
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
|
||||||
DLOG("Skipping output %s: could not get CRTC (%p)\n",
|
DLOG("Skipping output %s: could not get CRTC (%p)\n",
|
||||||
new->name, crtc);
|
output_primary_name(new), crtc);
|
||||||
free(new);
|
free(new);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -789,7 +845,7 @@ void randr_query_outputs(void) {
|
||||||
if (!output->active || output->to_be_disabled)
|
if (!output->active || output->to_be_disabled)
|
||||||
continue;
|
continue;
|
||||||
DLOG("output %p / %s, position (%d, %d), checking for clones\n",
|
DLOG("output %p / %s, position (%d, %d), checking for clones\n",
|
||||||
output, output->name, output->rect.x, output->rect.y);
|
output, output_primary_name(output), output->rect.x, output->rect.y);
|
||||||
|
|
||||||
for (other = output;
|
for (other = output;
|
||||||
other != TAILQ_END(&outputs);
|
other != TAILQ_END(&outputs);
|
||||||
|
@ -813,7 +869,7 @@ void randr_query_outputs(void) {
|
||||||
update_if_necessary(&(other->rect.width), width);
|
update_if_necessary(&(other->rect.width), width);
|
||||||
update_if_necessary(&(other->rect.height), height);
|
update_if_necessary(&(other->rect.height), height);
|
||||||
|
|
||||||
DLOG("disabling output %p (%s)\n", other, other->name);
|
DLOG("disabling output %p (%s)\n", other, output_primary_name(other));
|
||||||
other->to_be_disabled = true;
|
other->to_be_disabled = true;
|
||||||
|
|
||||||
DLOG("new output mode %d x %d, other mode %d x %d\n",
|
DLOG("new output mode %d x %d, other mode %d x %d\n",
|
||||||
|
@ -828,7 +884,7 @@ void randr_query_outputs(void) {
|
||||||
* LVDS1 gets disabled. */
|
* LVDS1 gets disabled. */
|
||||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||||
if (output->active && output->con == NULL) {
|
if (output->active && output->con == NULL) {
|
||||||
DLOG("Need to initialize a Con for output %s\n", output->name);
|
DLOG("Need to initialize a Con for output %s\n", output_primary_name(output));
|
||||||
output_init_con(output);
|
output_init_con(output);
|
||||||
output->changed = false;
|
output->changed = false;
|
||||||
}
|
}
|
||||||
|
@ -854,7 +910,7 @@ void randr_query_outputs(void) {
|
||||||
Con *content = output_get_content(output->con);
|
Con *content = output_get_content(output->con);
|
||||||
if (!TAILQ_EMPTY(&(content->nodes_head)))
|
if (!TAILQ_EMPTY(&(content->nodes_head)))
|
||||||
continue;
|
continue;
|
||||||
DLOG("Should add ws for output %s\n", output->name);
|
DLOG("Should add ws for output %s\n", output_primary_name(output));
|
||||||
init_ws_for_output(output, content);
|
init_ws_for_output(output, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -863,7 +919,7 @@ void randr_query_outputs(void) {
|
||||||
if (!output->primary || !output->con)
|
if (!output->primary || !output->con)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
DLOG("Focusing primary output %s\n", output->name);
|
DLOG("Focusing primary output %s\n", output_primary_name(output));
|
||||||
con_focus(con_descend_focused(output->con));
|
con_focus(con_descend_focused(output->con));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,7 +937,7 @@ void randr_disable_output(Output *output) {
|
||||||
assert(output->to_be_disabled);
|
assert(output->to_be_disabled);
|
||||||
|
|
||||||
output->active = false;
|
output->active = false;
|
||||||
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
|
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output_primary_name(output));
|
||||||
|
|
||||||
Output *first = get_first_output();
|
Output *first = get_first_output();
|
||||||
|
|
||||||
|
|
|
@ -537,7 +537,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
|
||||||
|
|
||||||
if (!current_output)
|
if (!current_output)
|
||||||
return false;
|
return false;
|
||||||
DLOG("Current output is %s\n", current_output->name);
|
DLOG("Current output is %s\n", output_primary_name(current_output));
|
||||||
|
|
||||||
/* Try to find next output */
|
/* Try to find next output */
|
||||||
direction_t direction;
|
direction_t direction;
|
||||||
|
@ -555,7 +555,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
|
||||||
next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
||||||
if (!next_output)
|
if (!next_output)
|
||||||
return false;
|
return false;
|
||||||
DLOG("Next output is %s\n", next_output->name);
|
DLOG("Next output is %s\n", output_primary_name(next_output));
|
||||||
|
|
||||||
/* Find visible workspace on next output */
|
/* Find visible workspace on next output */
|
||||||
Con *workspace = NULL;
|
Con *workspace = NULL;
|
||||||
|
|
|
@ -188,7 +188,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
||||||
struct Workspace_Assignment *assignment;
|
struct Workspace_Assignment *assignment;
|
||||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||||
if (strcmp(assignment->name, target_name) != 0 ||
|
if (strcmp(assignment->name, target_name) != 0 ||
|
||||||
strcmp(assignment->output, output->name) == 0)
|
strcmp(assignment->output, output_primary_name(output)) == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
assigned = true;
|
assigned = true;
|
||||||
|
@ -935,7 +935,7 @@ bool workspace_move_to_output(Con *ws, const char *name) {
|
||||||
bool used_assignment = false;
|
bool used_assignment = false;
|
||||||
struct Workspace_Assignment *assignment;
|
struct Workspace_Assignment *assignment;
|
||||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||||
if (assignment->output == NULL || strcmp(assignment->output, current_output->name) != 0)
|
if (assignment->output == NULL || strcmp(assignment->output, output_primary_name(current_output)) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* check if this workspace is already attached to the tree */
|
/* check if this workspace is already attached to the tree */
|
||||||
|
|
|
@ -55,8 +55,11 @@ static void query_screens(xcb_connection_t *conn) {
|
||||||
s->rect.height = min(s->rect.height, screen_info[screen].height);
|
s->rect.height = min(s->rect.height, screen_info[screen].height);
|
||||||
} else {
|
} else {
|
||||||
s = scalloc(1, sizeof(Output));
|
s = scalloc(1, sizeof(Output));
|
||||||
sasprintf(&(s->name), "xinerama-%d", num_screens);
|
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
||||||
DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
|
sasprintf(&output_name->name, "xinerama-%d", num_screens);
|
||||||
|
SLIST_INIT(&s->names_head);
|
||||||
|
SLIST_INSERT_HEAD(&s->names_head, output_name, names);
|
||||||
|
DLOG("Created new Xinerama screen %s (%p)\n", output_primary_name(s), s);
|
||||||
s->active = true;
|
s->active = true;
|
||||||
s->rect.x = screen_info[screen].x_org;
|
s->rect.x = screen_info[screen].x_org;
|
||||||
s->rect.y = screen_info[screen].y_org;
|
s->rect.y = screen_info[screen].y_org;
|
||||||
|
|
|
@ -41,9 +41,14 @@ void cleanup_socket(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct injected_reply {
|
||||||
|
void *buf;
|
||||||
|
off_t len;
|
||||||
|
};
|
||||||
|
|
||||||
/* BEGIN RandR 1.5 specific */
|
/* BEGIN RandR 1.5 specific */
|
||||||
static void *injected_reply = NULL;
|
static struct injected_reply getmonitors_reply = {NULL, 0};
|
||||||
static off_t injected_reply_len = 0;
|
static struct injected_reply getoutputinfo_reply = {NULL, 0};
|
||||||
/* END RandR 1.5 specific */
|
/* END RandR 1.5 specific */
|
||||||
|
|
||||||
#define XCB_PAD(i) (-(i)&3)
|
#define XCB_PAD(i) (-(i)&3)
|
||||||
|
@ -66,6 +71,8 @@ struct connstate {
|
||||||
int getext_randr;
|
int getext_randr;
|
||||||
/* sequence number of the most recent RRGetMonitors request */
|
/* sequence number of the most recent RRGetMonitors request */
|
||||||
int getmonitors;
|
int getmonitors;
|
||||||
|
/* sequence number of the most recent RRGetOutputInfo request */
|
||||||
|
int getoutputinfo;
|
||||||
|
|
||||||
int randr_major_opcode;
|
int randr_major_opcode;
|
||||||
/* END RandR 1.5 specific */
|
/* END RandR 1.5 specific */
|
||||||
|
@ -259,6 +266,8 @@ static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) {
|
||||||
const uint8_t randr_opcode = ((generic_x11_request_t *)request)->pad0;
|
const uint8_t randr_opcode = ((generic_x11_request_t *)request)->pad0;
|
||||||
if (randr_opcode == XCB_RANDR_GET_MONITORS) {
|
if (randr_opcode == XCB_RANDR_GET_MONITORS) {
|
||||||
connstate->getmonitors = connstate->sequence;
|
connstate->getmonitors = connstate->sequence;
|
||||||
|
} else if (randr_opcode == XCB_RANDR_GET_OUTPUT_INFO) {
|
||||||
|
connstate->getoutputinfo = connstate->sequence;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* END RandR 1.5 specific */
|
/* END RandR 1.5 specific */
|
||||||
|
@ -267,6 +276,32 @@ static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) {
|
||||||
free(request);
|
free(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool handle_sequence(struct connstate *connstate, uint16_t sequence) {
|
||||||
|
/* BEGIN RandR 1.5 specific */
|
||||||
|
if (sequence == connstate->getmonitors) {
|
||||||
|
printf("RRGetMonitors reply!\n");
|
||||||
|
if (getmonitors_reply.buf != NULL) {
|
||||||
|
printf("injecting reply\n");
|
||||||
|
((generic_x11_reply_t *)getmonitors_reply.buf)->sequence = sequence;
|
||||||
|
must_write(writeall(connstate->clientw->fd, getmonitors_reply.buf, getmonitors_reply.len));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence == connstate->getoutputinfo) {
|
||||||
|
printf("RRGetOutputInfo reply!\n");
|
||||||
|
if (getoutputinfo_reply.buf != NULL) {
|
||||||
|
printf("injecting reply\n");
|
||||||
|
((generic_x11_reply_t *)getoutputinfo_reply.buf)->sequence = sequence;
|
||||||
|
must_write(writeall(connstate->clientw->fd, getoutputinfo_reply.buf, getoutputinfo_reply.len));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* END RandR 1.5 specific */
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) {
|
static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) {
|
||||||
struct connstate *connstate = (struct connstate *)w->data;
|
struct connstate *connstate = (struct connstate *)w->data;
|
||||||
// all packets from the server are at least 32 bytes in length
|
// all packets from the server are at least 32 bytes in length
|
||||||
|
@ -274,9 +309,14 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) {
|
||||||
void *packet = smalloc(len);
|
void *packet = smalloc(len);
|
||||||
must_read(readall_into(packet, len, connstate->serverw->fd));
|
must_read(readall_into(packet, len, connstate->serverw->fd));
|
||||||
switch (((generic_x11_reply_t *)packet)->code) {
|
switch (((generic_x11_reply_t *)packet)->code) {
|
||||||
case 0: // error
|
case 0: { // error
|
||||||
|
const uint16_t sequence = ((xcb_request_error_t *)packet)->sequence;
|
||||||
|
if (handle_sequence(connstate, sequence)) {
|
||||||
|
free(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 1: // reply
|
case 1: // reply
|
||||||
len += ((generic_x11_reply_t *)packet)->length * 4;
|
len += ((generic_x11_reply_t *)packet)->length * 4;
|
||||||
if (len > 32) {
|
if (len > 32) {
|
||||||
|
@ -291,19 +331,13 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) {
|
||||||
xcb_query_extension_reply_t *reply = packet;
|
xcb_query_extension_reply_t *reply = packet;
|
||||||
connstate->randr_major_opcode = reply->major_opcode;
|
connstate->randr_major_opcode = reply->major_opcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sequence == connstate->getmonitors) {
|
|
||||||
printf("RRGetMonitors reply!\n");
|
|
||||||
if (injected_reply != NULL) {
|
|
||||||
printf("injecting reply\n");
|
|
||||||
((generic_x11_reply_t *)injected_reply)->sequence = sequence;
|
|
||||||
must_write(writeall(connstate->clientw->fd, injected_reply, injected_reply_len));
|
|
||||||
free(packet);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* END RandR 1.5 specific */
|
/* END RandR 1.5 specific */
|
||||||
|
|
||||||
|
if (handle_sequence(connstate, sequence)) {
|
||||||
|
free(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // event
|
default: // event
|
||||||
|
@ -322,7 +356,7 @@ static void child_cb(EV_P_ ev_child *w, int revents) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void must_read_reply(const char *filename) {
|
static void must_read_reply(const char *filename, struct injected_reply *reply) {
|
||||||
FILE *f;
|
FILE *f;
|
||||||
if ((f = fopen(filename, "r")) == NULL) {
|
if ((f = fopen(filename, "r")) == NULL) {
|
||||||
err(EXIT_FAILURE, "fopen(%s)", filename);
|
err(EXIT_FAILURE, "fopen(%s)", filename);
|
||||||
|
@ -331,11 +365,9 @@ static void must_read_reply(const char *filename) {
|
||||||
if (fstat(fileno(f), &stbuf) != 0) {
|
if (fstat(fileno(f), &stbuf) != 0) {
|
||||||
err(EXIT_FAILURE, "fstat(%s)", filename);
|
err(EXIT_FAILURE, "fstat(%s)", filename);
|
||||||
}
|
}
|
||||||
/* BEGIN RandR 1.5 specific */
|
reply->len = stbuf.st_size;
|
||||||
injected_reply_len = stbuf.st_size;
|
reply->buf = smalloc(stbuf.st_size);
|
||||||
injected_reply = smalloc(stbuf.st_size);
|
int n = fread(reply->buf, 1, stbuf.st_size, f);
|
||||||
int n = fread(injected_reply, 1, stbuf.st_size, f);
|
|
||||||
/* END RandR 1.5 specific */
|
|
||||||
if (n != stbuf.st_size) {
|
if (n != stbuf.st_size) {
|
||||||
err(EXIT_FAILURE, "fread(%s)", filename);
|
err(EXIT_FAILURE, "fread(%s)", filename);
|
||||||
}
|
}
|
||||||
|
@ -345,6 +377,7 @@ static void must_read_reply(const char *filename) {
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{"getmonitors_reply", required_argument, 0, 0},
|
{"getmonitors_reply", required_argument, 0, 0},
|
||||||
|
{"getoutputinfo_reply", required_argument, 0, 0},
|
||||||
{0, 0, 0, 0},
|
{0, 0, 0, 0},
|
||||||
};
|
};
|
||||||
char *options_string = "";
|
char *options_string = "";
|
||||||
|
@ -353,11 +386,15 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
while ((opt = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
while ((opt = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 0:
|
case 0: {
|
||||||
if (strcmp(long_options[option_index].name, "getmonitors_reply") == 0) {
|
const char *option_name = long_options[option_index].name;
|
||||||
must_read_reply(optarg);
|
if (strcmp(option_name, "getmonitors_reply") == 0) {
|
||||||
|
must_read_reply(optarg, &getmonitors_reply);
|
||||||
|
} else if (strcmp(option_name, "getoutputinfo_reply") == 0) {
|
||||||
|
must_read_reply(optarg, &getoutputinfo_reply);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,12 @@ sub activate_i3 {
|
||||||
if ($args{inject_randr15}) {
|
if ($args{inject_randr15}) {
|
||||||
# See comment in $args{strace} branch.
|
# See comment in $args{strace} branch.
|
||||||
$cmd = 'test.inject_randr15 --getmonitors_reply="' .
|
$cmd = 'test.inject_randr15 --getmonitors_reply="' .
|
||||||
$args{inject_randr15} . '" -- ' .
|
$args{inject_randr15} . '" ' .
|
||||||
|
($args{inject_randr15_outputinfo}
|
||||||
|
? '--getoutputinfo_reply="' .
|
||||||
|
$args{inject_randr15_outputinfo} . '" '
|
||||||
|
: '') .
|
||||||
|
'-- ' .
|
||||||
'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
|
'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -864,6 +864,7 @@ sub launch_with_config {
|
||||||
dont_create_temp_dir => $args{dont_create_temp_dir},
|
dont_create_temp_dir => $args{dont_create_temp_dir},
|
||||||
validate_config => $args{validate_config},
|
validate_config => $args{validate_config},
|
||||||
inject_randr15 => $args{inject_randr15},
|
inject_randr15 => $args{inject_randr15},
|
||||||
|
inject_randr15_outputinfo => $args{inject_randr15_outputinfo},
|
||||||
);
|
);
|
||||||
|
|
||||||
# If we called i3 with -C, we wait for it to exit and then return as
|
# If we called i3 with -C, we wait for it to exit and then return as
|
||||||
|
|
|
@ -20,10 +20,16 @@
|
||||||
use File::Temp qw(tempfile);
|
use File::Temp qw(tempfile);
|
||||||
use i3test i3_autostart => 0;
|
use i3test i3_autostart => 0;
|
||||||
|
|
||||||
|
my $monitor_name = 'i3-fake-monitor';
|
||||||
|
my $output_name = 'i3-fake-output';
|
||||||
|
|
||||||
my $config = <<EOT;
|
my $config = <<EOT;
|
||||||
# i3 config file (v4)
|
# i3 config file (v4)
|
||||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||||
|
|
||||||
|
bar {
|
||||||
|
output $output_name
|
||||||
|
}
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
my ($outfh, $outname) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1);
|
my ($outfh, $outname) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1);
|
||||||
|
@ -33,50 +39,81 @@ my ($outfh, $outname) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1);
|
||||||
my $reply = pack('cxSLLLLx[LLL]',
|
my $reply = pack('cxSLLLLx[LLL]',
|
||||||
1, # reply
|
1, # reply
|
||||||
0, # sequence (will be filled in by inject_randr15)
|
0, # sequence (will be filled in by inject_randr15)
|
||||||
# 56 = length($reply) + length($monitor1)
|
# 60 = length($reply) + length($monitor1)
|
||||||
# 32 = minimum X11 reply length
|
# 32 = minimum X11 reply length
|
||||||
(56-32) / 4, # length in words
|
(60-32) / 4, # length in words
|
||||||
0, # timestamp TODO
|
0, # timestamp TODO
|
||||||
1, # nmonitors
|
1, # nmonitors
|
||||||
0); # noutputs
|
1); # noutputs
|
||||||
|
|
||||||
# Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if
|
# Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if
|
||||||
# they are not yet interned.
|
# they are not yet interned.
|
||||||
my $atom_cookie = $x->intern_atom(0, length("DP3"), "DP3");
|
my $atom_cookie = $x->intern_atom(0, length($monitor_name), $monitor_name);
|
||||||
my $DP3 = $x->intern_atom_reply($atom_cookie->{sequence})->{atom};
|
my $monitor_name_atom = $x->intern_atom_reply($atom_cookie->{sequence})->{atom};
|
||||||
|
|
||||||
# MONITORINFO is defined in A.1.1 in
|
# MONITORINFO is defined in A.1.1 in
|
||||||
# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
|
# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
|
||||||
my $monitor1 = pack('LccSssSSLL',
|
my $monitor1 = pack('LccSssSSLLL',
|
||||||
$DP3, # name (ATOM)
|
$monitor_name_atom, # name (ATOM)
|
||||||
1, # primary
|
1, # primary
|
||||||
1, # automatic
|
1, # automatic
|
||||||
0, # ncrtcs
|
1, # ncrtcs
|
||||||
0, # x
|
0, # x
|
||||||
0, # y
|
0, # y
|
||||||
3840, # width in pixels
|
3840, # width in pixels
|
||||||
2160, # height in pixels
|
2160, # height in pixels
|
||||||
520, # width in millimeters
|
520, # width in millimeters
|
||||||
290); # height in millimeters
|
290, # height in millimeters
|
||||||
|
12345); # output ID #0
|
||||||
|
|
||||||
print $outfh $reply;
|
print $outfh $reply;
|
||||||
print $outfh $monitor1;
|
print $outfh $monitor1;
|
||||||
|
|
||||||
close($outfh);
|
close($outfh);
|
||||||
|
|
||||||
my $pid = launch_with_config($config, inject_randr15 => $outname);
|
# Prepare a RRGetOutputInfo reply as well; see RRGetOutputInfo in
|
||||||
|
# https://www.x.org/releases/current/doc/randrproto/randrproto.txt
|
||||||
|
($outfh, my $outname_moninfo) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1);
|
||||||
|
my $moninfo = pack('cxSLLLx[LLccSSSS]S a* x!4',
|
||||||
|
1, # reply
|
||||||
|
0, # sequence (will be filled in by inject_randr15)
|
||||||
|
# 36 = length($moninfo) (without name and padding)
|
||||||
|
# 32 = minimum X11 reply length
|
||||||
|
((36 + length($output_name) - 32) + 3) / 4, # length in words
|
||||||
|
0, # timestamp TODO
|
||||||
|
12345, # CRTC
|
||||||
|
length($output_name), # length of name
|
||||||
|
$output_name); # name
|
||||||
|
|
||||||
|
print $outfh $moninfo;
|
||||||
|
close($outfh);
|
||||||
|
|
||||||
|
my $pid = launch_with_config($config,
|
||||||
|
inject_randr15 => $outname,
|
||||||
|
inject_randr15_outputinfo => $outname_moninfo);
|
||||||
|
|
||||||
my $tree = i3->get_tree->recv;
|
my $tree = i3->get_tree->recv;
|
||||||
my @outputs = map { $_->{name} } @{$tree->{nodes}};
|
my @outputs = map { $_->{name} } @{$tree->{nodes}};
|
||||||
is_deeply(\@outputs, [ '__i3', 'DP3' ], 'outputs are __i3 and DP3');
|
is_deeply(\@outputs, [ '__i3', $monitor_name ], 'outputs are __i3 and the fake monitor');
|
||||||
|
|
||||||
my ($dp3) = grep { $_->{name} eq 'DP3' } @{$tree->{nodes}};
|
my ($output_data) = grep { $_->{name} eq $monitor_name } @{$tree->{nodes}};
|
||||||
is_deeply($dp3->{rect}, {
|
is_deeply($output_data->{rect}, {
|
||||||
width => 3840,
|
width => 3840,
|
||||||
height => 2160,
|
height => 2160,
|
||||||
x => 0,
|
x => 0,
|
||||||
y => 0,
|
y => 0,
|
||||||
}, 'Output DP3 at 3840x2160+0+0');
|
}, "Fake output at 3840x2160+0+0");
|
||||||
|
|
||||||
|
# Verify that i3 canonicalizes RandR output names to i3 output names
|
||||||
|
# (RandR monitor names) for bar configs
|
||||||
|
|
||||||
|
my $bars = i3->get_bar_config()->recv;
|
||||||
|
is(@$bars, 1, 'one bar configured');
|
||||||
|
|
||||||
|
my $bar_id = shift @$bars;
|
||||||
|
|
||||||
|
my $bar_config = i3->get_bar_config($bar_id)->recv;
|
||||||
|
is_deeply($bar_config->{outputs}, [ $monitor_name ], 'bar_config output name is normalized');
|
||||||
|
|
||||||
exit_gracefully($pid);
|
exit_gracefully($pid);
|
||||||
|
|
||||||
|
@ -86,7 +123,7 @@ exit_gracefully($pid);
|
||||||
|
|
||||||
# When inject_randr15 is defined but false, fake-xinerama will be turned off,
|
# When inject_randr15 is defined but false, fake-xinerama will be turned off,
|
||||||
# but inject_randr15 will not actually be used.
|
# but inject_randr15 will not actually be used.
|
||||||
my $pid = launch_with_config($config, inject_randr15 => '');
|
$pid = launch_with_config($config, inject_randr15 => '');
|
||||||
|
|
||||||
$tree = i3->get_tree->recv;
|
$tree = i3->get_tree->recv;
|
||||||
@outputs = map { $_->{name} } @{$tree->{nodes}};
|
@outputs = map { $_->{name} } @{$tree->{nodes}};
|
||||||
|
|
Loading…
Reference in New Issue