]> git.sur5r.net Git - i3/i3/commitdiff
Implement RandR 1.5 support (#2580)
authorMichael Stapelberg <stapelberg@users.noreply.github.com>
Mon, 28 Nov 2016 17:20:46 +0000 (18:20 +0100)
committerGitHub <noreply@github.com>
Mon, 28 Nov 2016 17:20:46 +0000 (18:20 +0100)
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

12 files changed:
generate-command-parser.pl
include/config_directives.h
include/configuration.h
include/randr.h
parser-specs/config.spec
src/config_directives.c
src/handlers.c
src/main.c
src/randr.c
testcases/inject_randr1.5.c
testcases/t/201-config-parser.t
testcases/t/533-randr15.t

index 6208945dc87558f0ee88cd5cfdbfcd9a9bd68e69..a7687c7bc480ab91d9e63c0cc13c2c94bec434ae 100755 (executable)
@@ -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 <number>s,
         # literal numbers or state IDs and %s for NULL, <string>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';
index 7f1bfe4a091e9613d5d21f0fa186da759107e87e..0bf521686855fed4b3dd4540017e96979e600e33 100644 (file)
@@ -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);
index f93afde757b45a261da85e678b7a29cb5d335f43..66628eebf15c5317eca677021ad98b0f28e3af07 100644 (file)
@@ -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;
 
index 55068316607e5e660b4b9e209280608f6a00ca5c..8cbfc8424dedf87fbad57b2461d3a1cb0da0560a 100644 (file)
@@ -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
index 9029681983791be93000e7566ec0c5c6763c3d03..19e2d21a09166290b1251422a3ec01bcc30680f9 100644 (file)
@@ -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
index 6b5464f19575985365d0e577f131f5664b9e64c4..a260518c3d533bb949ac08aaf989d5a5ffe4d54f 100644 (file)
@@ -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);
 }
index 7dfacef78f5b56a6c17e5481dbc81a5e6f117956..5e589e9c91b51697d8e8c347dbebf9daf6c54217 100644 (file)
@@ -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;
index 4737175b09a18efa810ae0533560ef8133e1359e..43efb3c2669f81a81d8b198a4eb398cc5122a1e1 100644 (file)
@@ -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
index e5dcddfc38146763b1c8e21043d73e1b09bcdef1..16ef62b8f767b6a027da5426964f4315e19a4cf7 100644 (file)
 #include <time.h>
 #include <xcb/randr.h>
 
-/* 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)
index bd0df399ce98e3e6ba96e66a6f8f42384f628e6c..5796ef05519edf5b6ed34b751e6fbdbb4eccfdaf 100644 (file)
@@ -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;
index 6cd84b6f1dfc39d918048accb8ce06b65afa0d7e..1de86c653c2fde44e78e4608167fdda3dd18cd17 100644 (file)
@@ -467,6 +467,8 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
         force_focus_wrapping
         force_xinerama
         force-xinerama
+        disable_randr15
+        disable-randr15
         workspace_auto_back_and_forth
         fake_outputs
         fake-outputs
index f520806c80682bc5135b8629d94acb3d01d301ec..08fa88cc20b72f09e39d9233888fb66ef7ae6b67 100644 (file)
@@ -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);