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
|
||||
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:
|
||||
|
||||
*Examples*:
|
||||
|
|
|
@ -349,6 +349,13 @@ struct Autostart {
|
|||
autostarts_always;
|
||||
};
|
||||
|
||||
struct output_name {
|
||||
char *name;
|
||||
|
||||
SLIST_ENTRY(output_name)
|
||||
names;
|
||||
};
|
||||
|
||||
/**
|
||||
* An Output is a physical output on your graphics driver. Outputs which
|
||||
* are currently in use have (output->active == true). Each output has a
|
||||
|
@ -370,8 +377,11 @@ struct xoutput {
|
|||
bool to_be_disabled;
|
||||
bool primary;
|
||||
|
||||
/** Name of the output */
|
||||
char *name;
|
||||
/** List of names for the output.
|
||||
* 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 */
|
||||
Con *con;
|
||||
|
|
|
@ -24,6 +24,12 @@ Con *output_get_content(Con *output);
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -1264,7 +1264,7 @@ void con_move_to_output(Con *con, Output *output) {
|
|||
Con *ws = NULL;
|
||||
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.height = min(new_output->rect.height, height);
|
||||
} else {
|
||||
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
||||
new_output = scalloc(1, sizeof(Output));
|
||||
sasprintf(&(new_output->name), "fake-%d", num_screens);
|
||||
DLOG("Created new fake output %s (%p)\n", new_output->name, new_output);
|
||||
sasprintf(&(output_name->name), "fake-%d", num_screens);
|
||||
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->rect.x = x;
|
||||
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);
|
||||
Output *target = get_output_containing(x, y);
|
||||
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;
|
||||
Con *nc = con_for_window(target->con, con->window, &match);
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
y(map_open);
|
||||
|
||||
|
@ -588,8 +593,13 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
|||
if (config->num_outputs > 0) {
|
||||
ystr("outputs");
|
||||
y(array_open);
|
||||
for (int c = 0; c < config->num_outputs; c++)
|
||||
ystr(config->outputs[c]);
|
||||
for (int c = 0; c < config->num_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);
|
||||
}
|
||||
|
||||
|
@ -599,7 +609,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
|||
|
||||
struct tray_output_t *tray_output;
|
||||
TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) {
|
||||
ystr(tray_output->output);
|
||||
ystr(canonicalize_output_name(tray_output->output));
|
||||
}
|
||||
|
||||
y(array_close);
|
||||
|
@ -829,7 +839,7 @@ IPC_HANDLER(get_outputs) {
|
|||
y(map_open);
|
||||
|
||||
ystr("name");
|
||||
ystr(output->name);
|
||||
ystr(output_primary_name(output));
|
||||
|
||||
ystr("active");
|
||||
y(bool, output->active);
|
||||
|
|
|
@ -687,7 +687,7 @@ int main(int argc, char *argv[]) {
|
|||
TAILQ_FOREACH(con, &(croot->nodes_head), nodes) {
|
||||
Output *output;
|
||||
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;
|
||||
|
||||
/* 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");
|
||||
Output *output = get_output_containing(geom->x, geom->y);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,14 @@ Output *get_output_from_string(Output *current_output, const char *output_str) {
|
|||
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) {
|
||||
Con *output_con = con_get_output(con);
|
||||
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;
|
||||
bool get_primary = (strcasecmp("primary", name) == 0);
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if ((output->primary && get_primary) ||
|
||||
((!require_active || output->active) && strcasecmp(output->name, name) == 0)) {
|
||||
if (output->primary && get_primary) {
|
||||
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;
|
||||
|
@ -178,7 +186,7 @@ Output *get_output_next_wrap(direction_t direction, Output *current) {
|
|||
}
|
||||
if (!best)
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -266,7 +274,11 @@ Output *create_root_output(xcb_connection_t *conn) {
|
|||
s->rect.y = 0;
|
||||
s->rect.width = root_screen->width_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;
|
||||
}
|
||||
|
@ -280,12 +292,12 @@ void output_init_con(Output *output) {
|
|||
Con *con = NULL, *current;
|
||||
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
|
||||
* might be one from a restored layout. */
|
||||
TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
|
||||
if (strcmp(current->name, output->name) != 0)
|
||||
if (strcmp(current->name, output_primary_name(output)) != 0)
|
||||
continue;
|
||||
|
||||
con = current;
|
||||
|
@ -297,7 +309,7 @@ void output_init_con(Output *output) {
|
|||
if (con == NULL) {
|
||||
con = con_new(croot, NULL);
|
||||
FREE(con->name);
|
||||
con->name = sstrdup(output->name);
|
||||
con->name = sstrdup(output_primary_name(output));
|
||||
con->type = CT_OUTPUT;
|
||||
con->layout = L_OUTPUT;
|
||||
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 */
|
||||
struct Workspace_Assignment *assignment;
|
||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||
if (strcmp(assignment->output, output->name) != 0)
|
||||
if (strcmp(assignment->output, output_primary_name(output)) != 0)
|
||||
continue;
|
||||
|
||||
/* 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 "
|
||||
"there. Do you have two assignment directives for the same "
|
||||
"workspace in your configuration file?\n",
|
||||
workspace->name, output->name);
|
||||
workspace->name, output_primary_name(output));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if so, move it over */
|
||||
LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
|
||||
workspace->name, workspace_out->name, output->name);
|
||||
workspace->name, workspace_out->name, output_primary_name(output));
|
||||
|
||||
/* if the workspace is currently visible on that output, we need to
|
||||
* 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);
|
||||
init_ws_for_output(get_output_by_name(workspace_out->name, true),
|
||||
output_get_content(workspace_out));
|
||||
DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
|
||||
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 */
|
||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||
if (strcmp(assignment->output, output->name) != 0)
|
||||
if (strcmp(assignment->output, output_primary_name(output)) != 0)
|
||||
continue;
|
||||
|
||||
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);
|
||||
if (new == NULL) {
|
||||
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) {
|
||||
TAILQ_INSERT_HEAD(&outputs, new, outputs);
|
||||
} 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);
|
||||
bool existing = (new != NULL);
|
||||
if (!existing)
|
||||
if (!existing) {
|
||||
new = scalloc(1, sizeof(Output));
|
||||
SLIST_INIT(&new->names_head);
|
||||
}
|
||||
new->id = id;
|
||||
new->primary = (primary && primary->output == id);
|
||||
FREE(new->name);
|
||||
sasprintf(&new->name, "%.*s",
|
||||
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",
|
||||
xcb_randr_get_output_info_name_length(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
|
||||
* 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);
|
||||
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
|
||||
DLOG("Skipping output %s: could not get CRTC (%p)\n",
|
||||
new->name, crtc);
|
||||
output_primary_name(new), crtc);
|
||||
free(new);
|
||||
return;
|
||||
}
|
||||
|
@ -789,7 +845,7 @@ void randr_query_outputs(void) {
|
|||
if (!output->active || output->to_be_disabled)
|
||||
continue;
|
||||
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;
|
||||
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.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;
|
||||
|
||||
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. */
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
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->changed = false;
|
||||
}
|
||||
|
@ -854,7 +910,7 @@ void randr_query_outputs(void) {
|
|||
Con *content = output_get_content(output->con);
|
||||
if (!TAILQ_EMPTY(&(content->nodes_head)))
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -863,7 +919,7 @@ void randr_query_outputs(void) {
|
|||
if (!output->primary || !output->con)
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -881,7 +937,7 @@ void randr_disable_output(Output *output) {
|
|||
assert(output->to_be_disabled);
|
||||
|
||||
output->active = false;
|
||||
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
|
||||
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output_primary_name(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)
|
||||
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 */
|
||||
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);
|
||||
if (!next_output)
|
||||
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 */
|
||||
Con *workspace = NULL;
|
||||
|
|
|
@ -188,7 +188,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
|||
struct Workspace_Assignment *assignment;
|
||||
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
|
||||
if (strcmp(assignment->name, target_name) != 0 ||
|
||||
strcmp(assignment->output, output->name) == 0)
|
||||
strcmp(assignment->output, output_primary_name(output)) == 0)
|
||||
continue;
|
||||
|
||||
assigned = true;
|
||||
|
@ -935,7 +935,7 @@ bool workspace_move_to_output(Con *ws, const char *name) {
|
|||
bool used_assignment = false;
|
||||
struct Workspace_Assignment *assignment;
|
||||
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;
|
||||
|
||||
/* 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);
|
||||
} else {
|
||||
s = scalloc(1, sizeof(Output));
|
||||
sasprintf(&(s->name), "xinerama-%d", num_screens);
|
||||
DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
|
||||
struct output_name *output_name = scalloc(1, sizeof(struct output_name));
|
||||
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->rect.x = screen_info[screen].x_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 */
|
||||
static void *injected_reply = NULL;
|
||||
static off_t injected_reply_len = 0;
|
||||
static struct injected_reply getmonitors_reply = {NULL, 0};
|
||||
static struct injected_reply getoutputinfo_reply = {NULL, 0};
|
||||
/* END RandR 1.5 specific */
|
||||
|
||||
#define XCB_PAD(i) (-(i)&3)
|
||||
|
@ -66,6 +71,8 @@ struct connstate {
|
|||
int getext_randr;
|
||||
/* sequence number of the most recent RRGetMonitors request */
|
||||
int getmonitors;
|
||||
/* sequence number of the most recent RRGetOutputInfo request */
|
||||
int getoutputinfo;
|
||||
|
||||
int randr_major_opcode;
|
||||
/* 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;
|
||||
if (randr_opcode == XCB_RANDR_GET_MONITORS) {
|
||||
connstate->getmonitors = connstate->sequence;
|
||||
} else if (randr_opcode == XCB_RANDR_GET_OUTPUT_INFO) {
|
||||
connstate->getoutputinfo = connstate->sequence;
|
||||
}
|
||||
}
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
struct connstate *connstate = (struct connstate *)w->data;
|
||||
// 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);
|
||||
must_read(readall_into(packet, len, connstate->serverw->fd));
|
||||
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;
|
||||
|
||||
}
|
||||
case 1: // reply
|
||||
len += ((generic_x11_reply_t *)packet)->length * 4;
|
||||
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;
|
||||
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 */
|
||||
|
||||
if (handle_sequence(connstate, sequence)) {
|
||||
free(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
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;
|
||||
if ((f = fopen(filename, "r")) == NULL) {
|
||||
err(EXIT_FAILURE, "fopen(%s)", filename);
|
||||
|
@ -331,11 +365,9 @@ static void must_read_reply(const char *filename) {
|
|||
if (fstat(fileno(f), &stbuf) != 0) {
|
||||
err(EXIT_FAILURE, "fstat(%s)", filename);
|
||||
}
|
||||
/* BEGIN RandR 1.5 specific */
|
||||
injected_reply_len = stbuf.st_size;
|
||||
injected_reply = smalloc(stbuf.st_size);
|
||||
int n = fread(injected_reply, 1, stbuf.st_size, f);
|
||||
/* END RandR 1.5 specific */
|
||||
reply->len = stbuf.st_size;
|
||||
reply->buf = smalloc(stbuf.st_size);
|
||||
int n = fread(reply->buf, 1, stbuf.st_size, f);
|
||||
if (n != stbuf.st_size) {
|
||||
err(EXIT_FAILURE, "fread(%s)", filename);
|
||||
}
|
||||
|
@ -345,6 +377,7 @@ static void must_read_reply(const char *filename) {
|
|||
int main(int argc, char *argv[]) {
|
||||
static struct option long_options[] = {
|
||||
{"getmonitors_reply", required_argument, 0, 0},
|
||||
{"getoutputinfo_reply", required_argument, 0, 0},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
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) {
|
||||
switch (opt) {
|
||||
case 0:
|
||||
if (strcmp(long_options[option_index].name, "getmonitors_reply") == 0) {
|
||||
must_read_reply(optarg);
|
||||
case 0: {
|
||||
const char *option_name = long_options[option_index].name;
|
||||
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;
|
||||
}
|
||||
default:
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
|
|
@ -145,7 +145,12 @@ sub activate_i3 {
|
|||
if ($args{inject_randr15}) {
|
||||
# See comment in $args{strace} branch.
|
||||
$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 . '"';
|
||||
}
|
||||
|
||||
|
|
|
@ -864,6 +864,7 @@ sub launch_with_config {
|
|||
dont_create_temp_dir => $args{dont_create_temp_dir},
|
||||
validate_config => $args{validate_config},
|
||||
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
|
||||
|
|
|
@ -20,10 +20,16 @@
|
|||
use File::Temp qw(tempfile);
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
my $monitor_name = 'i3-fake-monitor';
|
||||
my $output_name = 'i3-fake-output';
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bar {
|
||||
output $output_name
|
||||
}
|
||||
EOT
|
||||
|
||||
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]',
|
||||
1, # reply
|
||||
0, # sequence (will be filled in by inject_randr15)
|
||||
# 56 = length($reply) + length($monitor1)
|
||||
# 60 = length($reply) + length($monitor1)
|
||||
# 32 = minimum X11 reply length
|
||||
(56-32) / 4, # length in words
|
||||
(60-32) / 4, # length in words
|
||||
0, # timestamp TODO
|
||||
1, # nmonitors
|
||||
0); # noutputs
|
||||
1); # noutputs
|
||||
|
||||
# Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if
|
||||
# they are not yet interned.
|
||||
my $atom_cookie = $x->intern_atom(0, length("DP3"), "DP3");
|
||||
my $DP3 = $x->intern_atom_reply($atom_cookie->{sequence})->{atom};
|
||||
my $atom_cookie = $x->intern_atom(0, length($monitor_name), $monitor_name);
|
||||
my $monitor_name_atom = $x->intern_atom_reply($atom_cookie->{sequence})->{atom};
|
||||
|
||||
# MONITORINFO is defined in A.1.1 in
|
||||
# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
|
||||
my $monitor1 = pack('LccSssSSLL',
|
||||
$DP3, # name (ATOM)
|
||||
my $monitor1 = pack('LccSssSSLLL',
|
||||
$monitor_name_atom, # name (ATOM)
|
||||
1, # primary
|
||||
1, # automatic
|
||||
0, # ncrtcs
|
||||
1, # ncrtcs
|
||||
0, # x
|
||||
0, # y
|
||||
3840, # width in pixels
|
||||
2160, # height in pixels
|
||||
520, # width in millimeters
|
||||
290); # height in millimeters
|
||||
290, # height in millimeters
|
||||
12345); # output ID #0
|
||||
|
||||
print $outfh $reply;
|
||||
print $outfh $monitor1;
|
||||
|
||||
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 @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}};
|
||||
is_deeply($dp3->{rect}, {
|
||||
my ($output_data) = grep { $_->{name} eq $monitor_name } @{$tree->{nodes}};
|
||||
is_deeply($output_data->{rect}, {
|
||||
width => 3840,
|
||||
height => 2160,
|
||||
x => 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);
|
||||
|
||||
|
@ -86,7 +123,7 @@ exit_gracefully($pid);
|
|||
|
||||
# When inject_randr15 is defined but false, fake-xinerama will be turned off,
|
||||
# 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;
|
||||
@outputs = map { $_->{name} } @{$tree->{nodes}};
|
||||
|
|
Loading…
Reference in New Issue