From: Michael Stapelberg Date: Mon, 28 Nov 2016 17:20:46 +0000 (+0100) Subject: Implement RandR 1.5 support (#2580) X-Git-Tag: 4.14~87 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=633a9f7b1475ffd5c72489bc6bd2d224c441c9a9;p=i3%2Fi3 Implement RandR 1.5 support (#2580) This comes with the intentionally undocumented --disable-randr15 command line flag and disable-randr15 configuration directive. We will add documentation before the release if and only if it turns out that users actually need to use this flag in their setups. Ideally, nobody would need to use the flag and everything would just keep working, but it’s better to be safe than sorry. fixes #1799 --- diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 6208945d..a7687c7b 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -65,7 +65,7 @@ for my $line (@raw_lines) { my $current_state; for my $line (@lines) { - if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) { + if (my ($state) = ($line =~ /^state ([A-Z0-9_]+):$/)) { #say "got a new state: $state"; $current_state = $state; } else { @@ -155,12 +155,20 @@ for my $state (@keys) { # to generate a format string. The format uses %d for s, # literal numbers or state IDs and %s for NULL, s and literal # strings. + + # remove the function name temporarily, so that the following + # replacements only apply to the arguments. + my ($funcname) = ($fmt =~ /^(.+)\(/); + $fmt =~ s/^$funcname//; + $fmt =~ s/$_/%d/g for @keys; $fmt =~ s/\$([a-z_]+)/%s/g; $fmt =~ s/\&([a-z_]+)/%ld/g; $fmt =~ s/"([a-z0-9_]+)"/%s/g; $fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g; + $fmt = $funcname . $fmt; + say $callfh " case $call_id:"; say $callfh " result->next_state = $next_state;"; say $callfh '#ifndef TEST_PARSER'; diff --git a/include/config_directives.h b/include/config_directives.h index 7f1bfe4a..0bf52168 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -51,6 +51,7 @@ CFGFUN(focus_follows_mouse, const char *value); CFGFUN(mouse_warping, const char *value); CFGFUN(force_focus_wrapping, const char *value); CFGFUN(force_xinerama, const char *value); +CFGFUN(disable_randr15, const char *value); CFGFUN(fake_outputs, const char *outputs); CFGFUN(force_display_urgency_hint, const long duration_ms); CFGFUN(focus_on_window_activation, const char *mode); diff --git a/include/configuration.h b/include/configuration.h index f93afde7..66628eeb 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -156,6 +156,9 @@ struct Config { * is fetched once and never updated. */ bool force_xinerama; + /** Don’t use RandR 1.5 for querying outputs. */ + bool disable_randr15; + /** Overwrites output detection (for testing), see src/fake_outputs.c */ char *fake_outputs; diff --git a/include/randr.h b/include/randr.h index 55068316..8cbfc842 100644 --- a/include/randr.h +++ b/include/randr.h @@ -29,7 +29,7 @@ typedef enum { * XRandR information to setup workspaces for each screen. * */ -void randr_init(int *event_base); +void randr_init(int *event_base, const bool disable_randr15); /** * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 90296819..19e2d21a 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -37,6 +37,7 @@ state INITIAL: 'mouse_warping' -> MOUSE_WARPING 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA + 'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15 'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH 'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS 'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT @@ -205,6 +206,11 @@ state FORCE_XINERAMA: value = word -> call cfg_force_xinerama($value) +# disable_randr15 +state DISABLE_RANDR15: + value = word + -> call cfg_disable_randr15($value) + # workspace_back_and_forth state WORKSPACE_BACK_AND_FORTH: value = word diff --git a/src/config_directives.c b/src/config_directives.c index 6b5464f1..a260518c 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -252,6 +252,10 @@ CFGFUN(force_xinerama, const char *value) { config.force_xinerama = eval_boolstr(value); } +CFGFUN(disable_randr15, const char *value) { + config.disable_randr15 = eval_boolstr(value); +} + CFGFUN(force_focus_wrapping, const char *value) { config.force_focus_wrapping = eval_boolstr(value); } diff --git a/src/handlers.c b/src/handlers.c index 7dfacef7..5e589e9c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1150,6 +1150,21 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { return; } +/* + * Handles ConfigureNotify events for the root window, which are generated when + * the monitor configuration changed. + * + */ +static void handle_configure_notify(xcb_configure_notify_event_t *event) { + if (event->event != root) { + DLOG("ConfigureNotify for non-root window 0x%08x, ignoring\n", event->event); + return; + } + DLOG("ConfigureNotify for root window 0x%08x\n", event->event); + + randr_query_outputs(); +} + /* * Handles the WM_CLASS property for assignments and criteria selection. * @@ -1476,6 +1491,10 @@ void handle_event(int type, xcb_generic_event_t *event) { break; } + case XCB_CONFIGURE_NOTIFY: + handle_configure_notify((xcb_configure_notify_event_t *)event); + break; + default: //DLOG("Unhandled event of type %d\n", type); break; diff --git a/src/main.c b/src/main.c index 4737175b..43efb3c2 100644 --- a/src/main.c +++ b/src/main.c @@ -194,6 +194,7 @@ int main(int argc, char *argv[]) { char *layout_path = NULL; bool delete_layout_path = false; bool force_xinerama = false; + bool disable_randr15 = false; char *fake_outputs = NULL; bool disable_signalhandler = false; bool only_check_config = false; @@ -209,6 +210,8 @@ int main(int argc, char *argv[]) { {"restart", required_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0}, {"force_xinerama", no_argument, 0, 0}, + {"disable-randr15", no_argument, 0, 0}, + {"disable_randr15", no_argument, 0, 0}, {"disable-signalhandler", no_argument, 0, 0}, {"shmlog-size", required_argument, 0, 0}, {"shmlog_size", required_argument, 0, 0}, @@ -289,6 +292,10 @@ int main(int argc, char *argv[]) { "Please check if your driver really does not support RandR " "and disable this option as soon as you can.\n"); break; + } else if (strcmp(long_options[option_index].name, "disable-randr15") == 0 || + strcmp(long_options[option_index].name, "disable_randr15") == 0) { + disable_randr15 = true; + break; } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) { disable_signalhandler = true; break; @@ -661,7 +668,7 @@ int main(int argc, char *argv[]) { xinerama_init(); } else { DLOG("Checking for XRandR...\n"); - randr_init(&randr_base); + randr_init(&randr_base, disable_randr15 || config.disable_randr15); } /* We need to force disabling outputs which have been loaded from the diff --git a/src/randr.c b/src/randr.c index e5dcddfc..16ef62b8 100644 --- a/src/randr.c +++ b/src/randr.c @@ -14,11 +14,6 @@ #include #include -/* While a clean namespace is usually a pretty good thing, we really need - * to use shorter names than the whole xcb_randr_* default names. */ -typedef xcb_randr_get_crtc_info_reply_t crtc_info; -typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; - /* Pointer to the result of the query for primary output */ xcb_randr_get_output_primary_reply_t *primary; @@ -27,6 +22,7 @@ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); /* This is the output covering the root window */ static Output *root_output; +static bool has_randr_1_5 = false; /* * Get a specific output by its internal X11 id. Used by randr_query_outputs @@ -534,18 +530,112 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { } /* - * Gets called by randr_query_outputs() for each output. The function adds new - * outputs to the list of outputs, checks if the mode of existing outputs has - * been changed or if an existing output has been disabled. It will then change - * either the "changed" or the "to_be_deleted" flag of the output, if + * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs. + * + */ +static bool randr_query_outputs_15(void) { +#if XCB_RANDR_MINOR_VERSION < 5 + return false; +#else + /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */ + if (!has_randr_1_5) { + return false; + } + /* RandR 1.5 available at run-time (supported by the server and not + * disabled by the user) */ + DLOG("Querying outputs using RandR 1.5\n"); + xcb_generic_error_t *err; + xcb_randr_get_monitors_reply_t *monitors = + xcb_randr_get_monitors_reply( + conn, xcb_randr_get_monitors(conn, root, true), &err); + if (err != NULL) { + ELOG("Could not get RandR monitors: X11 error code %d\n", err->error_code); + free(err); + /* Fall back to RandR ≤ 1.4 */ + return false; + } + + /* Mark all outputs as to_be_disabled, since xcb_randr_get_monitors() will + * only return active outputs. */ + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (output != root_output) { + output->to_be_disabled = true; + } + } + + DLOG("%d RandR monitors found (timestamp %d)\n", + xcb_randr_get_monitors_monitors_length(monitors), + monitors->timestamp); + + xcb_randr_monitor_info_iterator_t iter; + for (iter = xcb_randr_get_monitors_monitors_iterator(monitors); + iter.rem; + xcb_randr_monitor_info_next(&iter)) { + const xcb_randr_monitor_info_t *monitor_info = iter.data; + xcb_get_atom_name_reply_t *atom_reply = + xcb_get_atom_name_reply( + conn, xcb_get_atom_name(conn, monitor_info->name), &err); + if (err != NULL) { + ELOG("Could not get RandR monitor name: X11 error code %d\n", err->error_code); + free(err); + continue; + } + char *name; + sasprintf(&name, "%.*s", + xcb_get_atom_name_name_length(atom_reply), + xcb_get_atom_name_name(atom_reply)); + free(atom_reply); + + Output *new = get_output_by_name(name); + if (new == NULL) { + new = scalloc(1, sizeof(Output)); + new->name = sstrdup(name); + if (monitor_info->primary) { + TAILQ_INSERT_HEAD(&outputs, new, outputs); + } else { + TAILQ_INSERT_TAIL(&outputs, new, outputs); + } + } + /* We specified get_active == true in xcb_randr_get_monitors(), so we + * will only receive active outputs. */ + new->active = true; + new->to_be_disabled = false; + + new->primary = monitor_info->primary; + + new->changed = + update_if_necessary(&(new->rect.x), monitor_info->x) | + update_if_necessary(&(new->rect.y), monitor_info->y) | + update_if_necessary(&(new->rect.width), monitor_info->width) | + update_if_necessary(&(new->rect.height), monitor_info->height); + + DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n", + name, + monitor_info->x, monitor_info->y, monitor_info->width, monitor_info->height, + monitor_info->width_in_millimeters, monitor_info->height_in_millimeters, + monitor_info->primary, monitor_info->automatic); + free(name); + } + free(monitors); + return true; +#endif +} + +/* + * Gets called by randr_query_outputs_14() for each output. The function adds + * new outputs to the list of outputs, checks if the mode of existing outputs + * has been changed or if an existing output has been disabled. It will then + * change either the "changed" or the "to_be_deleted" flag of the output, if * appropriate. * */ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, xcb_randr_get_output_info_reply_t *output, - xcb_timestamp_t cts, resources_reply *res) { + xcb_timestamp_t cts, + xcb_randr_get_screen_resources_current_reply_t *res) { /* each CRT controller has a position in which we are interested in */ - crtc_info *crtc; + xcb_randr_get_crtc_info_reply_t *crtc; Output *new = get_output_by_id(id); bool existing = (new != NULL); @@ -614,25 +704,16 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, } /* - * (Re-)queries the outputs via RandR and stores them in the list of outputs. - * - * If no outputs are found use the root window. + * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs. * */ -void randr_query_outputs(void) { - Output *output, *other; - xcb_randr_get_output_primary_cookie_t pcookie; - xcb_randr_get_screen_resources_current_cookie_t rcookie; - - /* timestamp of the configuration so that we get consistent replies to all - * requests (if the configuration changes between our different calls) */ - xcb_timestamp_t cts; - - /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ - xcb_randr_output_t *randr_outputs; +static void randr_query_outputs_14(void) { + DLOG("Querying outputs using RandR ≤ 1.4\n"); /* Get screen resources (primary output, crtcs, outputs, modes) */ + xcb_randr_get_screen_resources_current_cookie_t rcookie; rcookie = xcb_randr_get_screen_resources_current(conn, root); + xcb_randr_get_output_primary_cookie_t pcookie; pcookie = xcb_randr_get_output_primary(conn, root); if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) @@ -640,30 +721,52 @@ void randr_query_outputs(void) { else DLOG("primary output is %08x\n", primary->output); - resources_reply *res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); + xcb_randr_get_screen_resources_current_reply_t *res = + xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); if (res == NULL) { ELOG("Could not query screen resources.\n"); - } else { - cts = res->config_timestamp; + return; + } - int len = xcb_randr_get_screen_resources_current_outputs_length(res); - randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + /* timestamp of the configuration so that we get consistent replies to all + * requests (if the configuration changes between our different calls) */ + const xcb_timestamp_t cts = res->config_timestamp; - /* Request information for each output */ - xcb_randr_get_output_info_cookie_t ocookie[len]; - for (int i = 0; i < len; i++) - ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + const int len = xcb_randr_get_screen_resources_current_outputs_length(res); - /* Loop through all outputs available for this X11 screen */ - for (int i = 0; i < len; i++) { - xcb_randr_get_output_info_reply_t *output; + /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ + xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); - if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) - continue; + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); - handle_output(conn, randr_outputs[i], output, cts, res); - free(output); - } + /* Loop through all outputs available for this X11 screen */ + for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; + + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; + + handle_output(conn, randr_outputs[i], output, cts, res); + free(output); + } + + FREE(res); +} + +/* + * (Re-)queries the outputs via RandR and stores them in the list of outputs. + * + * If no outputs are found use the root window. + * + */ +void randr_query_outputs(void) { + Output *output, *other; + + if (!randr_query_outputs_15()) { + randr_query_outputs_14(); } /* If there's no randr output, enable the output covering the root window. */ @@ -763,7 +866,6 @@ void randr_query_outputs(void) { /* render_layout flushes */ tree_render(); - FREE(res); FREE(primary); } @@ -857,12 +959,18 @@ void randr_disable_output(Output *output) { output->changed = false; } +static void fallback_to_root_output(void) { + root_output->active = true; + output_init_con(root_output); + init_ws_for_output(root_output, output_get_content(root_output->con)); +} + /* * We have just established a connection to the X server and need the initial * XRandR information to setup workspaces for each screen. * */ -void randr_init(int *event_base) { +void randr_init(int *event_base, const bool disable_randr15) { const xcb_query_extension_reply_t *extreply; root_output = create_root_output(conn); @@ -871,13 +979,27 @@ void randr_init(int *event_base) { extreply = xcb_get_extension_data(conn, &xcb_randr_id); if (!extreply->present) { DLOG("RandR is not present, activating root output.\n"); - root_output->active = true; - output_init_con(root_output); - init_ws_for_output(root_output, output_get_content(root_output->con)); + fallback_to_root_output(); + return; + } + xcb_generic_error_t *err; + xcb_randr_query_version_reply_t *randr_version = + xcb_randr_query_version_reply( + conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err); + if (err != NULL) { + free(err); + ELOG("Could not query RandR version: X11 error code %d\n", err->error_code); + fallback_to_root_output(); return; } + has_randr_1_5 = (randr_version->major_version >= 1) && + (randr_version->minor_version >= 5) && + !disable_randr15; + + free(randr_version); + randr_query_outputs(); if (event_base != NULL) diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index bd0df399..5796ef05 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -294,7 +294,6 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { if (sequence == connstate->getmonitors) { printf("RRGetMonitors reply!\n"); - xcb_randr_get_monitors_reply_t *reply = packet; if (injected_reply != NULL) { printf("injecting reply\n"); ((generic_x11_reply_t *)injected_reply)->sequence = sequence; diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 6cd84b6f..1de86c65 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -467,6 +467,8 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , ' force_focus_wrapping force_xinerama force-xinerama + disable_randr15 + disable-randr15 workspace_auto_back_and_forth fake_outputs fake-outputs diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t index f520806c..08fa88cc 100644 --- a/testcases/t/533-randr15.t +++ b/testcases/t/533-randr15.t @@ -66,7 +66,42 @@ close($outfh); my $pid = launch_with_config($config, inject_randr15 => $outname); -cmd 'nop'; +my $tree = i3->get_tree->recv; +my @outputs = map { $_->{name} } @{$tree->{nodes}}; +is_deeply(\@outputs, [ '__i3', 'DP3' ], 'outputs are __i3 and DP3'); + +my ($dp3) = grep { $_->{name} eq 'DP3' } @{$tree->{nodes}}; +is_deeply($dp3->{rect}, { + width => 3840, + height => 2160, + x => 0, + y => 0, + }, 'Output DP3 at 3840x2160+0+0'); + +exit_gracefully($pid); + +################################################################################ +# Verify that adding monitors with RandR 1.5 results in i3 outputs. +################################################################################ + +# 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 => ''); + +$tree = i3->get_tree->recv; +@outputs = map { $_->{name} } @{$tree->{nodes}}; +is_deeply(\@outputs, [ '__i3', 'default' ], 'outputs are __i3 and default'); + +SKIP: { + skip 'xrandr --setmonitor failed (xrandr too old?)', 1 unless + system(q|xrandr --setmonitor up2414q 3840/527x2160/296+1280+0 none|) == 0; + + sync_with_i3; + + $tree = i3->get_tree->recv; + @outputs = map { $_->{name} } @{$tree->{nodes}}; + is_deeply(\@outputs, [ '__i3', 'default', 'up2414q' ], 'outputs are __i3, default and up2414q'); +} exit_gracefully($pid);