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
next
Michael Stapelberg 2016-11-28 18:20:46 +01:00 committed by GitHub
parent f2ffd8d864
commit 633a9f7b14
12 changed files with 259 additions and 53 deletions

View File

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

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

View File

@ -156,6 +156,9 @@ struct Config {
* is fetched once and never updated. */
bool force_xinerama;
/** Dont use RandR 1.5 for querying outputs. */
bool disable_randr15;
/** Overwrites output detection (for testing), see src/fake_outputs.c */
char *fake_outputs;

View 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

View 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

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

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

View 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

View File

@ -14,11 +14,6 @@
#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)

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

View 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

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