diff --git a/docs/userguide b/docs/userguide index 76982207..34159304 100644 --- a/docs/userguide +++ b/docs/userguide @@ -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*: diff --git a/include/data.h b/include/data.h index 69a79ade..31ef1dc1 100644 --- a/include/data.h +++ b/include/data.h @@ -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; diff --git a/include/output.h b/include/output.h index b13c9728..31084da1 100644 --- a/include/output.h +++ b/include/output.h @@ -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. * diff --git a/src/con.c b/src/con.c index b520c110..6bbe692f 100644 --- a/src/con.c +++ b/src/con.c @@ -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); } diff --git a/src/fake_outputs.c b/src/fake_outputs.c index b898ce98..6639b361 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -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; diff --git a/src/handlers.c b/src/handlers.c index c273e116..8d500fd9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -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); diff --git a/src/ipc.c b/src/ipc.c index 18d6075d..274f6010 100644 --- a/src/ipc.c +++ b/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); diff --git a/src/main.c b/src/main.c index 21265446..44e4517e 100644 --- a/src/main.c +++ b/src/main.c @@ -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 diff --git a/src/manage.c b/src/manage.c index 86a361c3..004e8038 100644 --- a/src/manage.c +++ b/src/manage.c @@ -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; } diff --git a/src/output.c b/src/output.c index e3c54a67..e7690384 100644 --- a/src/output.c +++ b/src/output.c @@ -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) { diff --git a/src/randr.c b/src/randr.c index 48bffb46..bc791696 100644 --- a/src/randr.c +++ b/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(); diff --git a/src/tree.c b/src/tree.c index 2d4647f8..82a4756c 100644 --- a/src/tree.c +++ b/src/tree.c @@ -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; diff --git a/src/workspace.c b/src/workspace.c index d7f2ce7c..4b350b82 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -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 */ diff --git a/src/xinerama.c b/src/xinerama.c index 25bfa6b1..d0651a85 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -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; diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index 5796ef05..6cccfa76 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -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); } diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 0f307eb3..5951fd26 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -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 . '"'; } diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 4046e620..683f3d39 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -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 diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t index 08fa88cc..5b81194a 100644 --- a/testcases/t/533-randr15.t +++ b/testcases/t/533-randr15.t @@ -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 = < 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}};