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