Merge pull request #2920 from CyberShadow/monitor-output-names

Consider RandR 1.5's monitors' output names in addition to monitor names
next
Michael Stapelberg 2017-09-13 10:58:38 +02:00 committed by GitHub
commit 44e4ad52f6
18 changed files with 271 additions and 83 deletions

View File

@ -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*:

View File

@ -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;

View File

@ -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.
*

View File

@ -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);
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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();

View File

@ -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;

View File

@ -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 */

View File

@ -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;

View File

@ -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);
}

View File

@ -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 . '"';
}

View File

@ -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

View File

@ -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}};