Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/backends/meta-monitor-config-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,9 @@ void
meta_monitor_config_manager_set_current (MetaMonitorConfigManager *config_manager,
MetaMonitorsConfig *config)
{
if (config_manager->current_config == config)
return;

if (config_manager->current_config)
{
g_queue_push_head (&config_manager->config_history,
Expand Down
33 changes: 33 additions & 0 deletions src/backends/meta-monitor-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,39 @@ meta_monitor_manager_ensure_configured (MetaMonitorManager *manager)
else
method = META_MONITORS_CONFIG_METHOD_TEMPORARY;

/* If a configuration has already been applied this session and is still
* valid for the detected monitor set, re-apply it. This preserves user
* intent across hotplug events that do not actually change the connected
* monitors — e.g. spurious link-state pings caused by a monitor's input
* auto-scan, or short DP/HDMI re-trains. Without this, the fallbacks
* below would compute a fresh "suggested" layout and silently re-enable
* a monitor the user has explicitly turned off via xrandr, Super+P, or
* the Display GUI. The stored on-disk configuration is consulted only
* during initial setup (current_config is NULL until the first apply).
*/
if (!manager->in_init)
{
MetaMonitorsConfig *current_config =
meta_monitor_config_manager_get_current (manager->config_manager);

if (current_config &&
meta_monitor_manager_is_config_complete (manager, current_config))
{
if (meta_monitor_manager_apply_monitors_config (manager,
current_config,
method,
&error))
{
config = g_object_ref (current_config);
goto done;
}

g_warning ("Failed to re-apply current monitor configuration: %s",
error->message);
g_clear_error (&error);
}
}

if (use_stored_config)
{
g_autoptr(MetaMonitorsConfig) new_config = NULL;
Expand Down
81 changes: 81 additions & 0 deletions src/tests/monitor-unit-tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -6297,6 +6297,84 @@ meta_test_monitor_migrated_wiggle (void)
g_error ("Failed to remove test data output file: %s", error->message);
}

/*
* Regression test for issue #819: a hotplug event on an unchanged
* connected-monitor set must not discard a user-applied configuration.
* Concretely, a monitor that the user has explicitly disabled (e.g.
* via Super+P, xrandr --off, or the Display GUI) must remain disabled
* after the kernel emits a DRM hotplug — most often caused by the
* monitor itself performing an auto input-scan and re-toggling its
* link state.
*
* Without the fix, ensure_configured() walked past current_config and
* fell through to create_suggested(), which silently re-enabled every
* detected output.
*/
static void
meta_test_monitor_hotplug_preserves_user_disabled_monitor (void)
{
MetaBackend *backend = meta_get_backend ();
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaMonitorConfigManager *config_manager = monitor_manager->config_manager;
MonitorTestCase test_case = initial_test_case;
MetaMonitorTestSetup *test_setup;
MetaMonitorsConfig *linear;
MetaMonitorsConfig *disabled_config;
MetaMonitorsConfig *post_hotplug_config;
MetaLogicalMonitorLayoutMode layout_mode;
GList *first_link;

/* Initial hotplug: both monitors detected, both enabled. */
test_setup = create_monitor_test_setup (&test_case,
MONITOR_TEST_FLAG_NO_STORED);
emulate_hotplug (test_setup);

/* Build a "second monitor disabled" config by stealing the first
* logical_monitor_config out of a freshly-created linear config and
* handing it to meta_monitors_config_new(), which auto-populates
* disabled_monitor_specs from the remaining detected monitors. The
* remaining list elements are released when the linear config is
* dropped. */
linear = meta_monitor_config_manager_create_linear (config_manager);
layout_mode = linear->layout_mode;
first_link = linear->logical_monitor_configs;
linear->logical_monitor_configs = first_link->next;
if (first_link->next)
first_link->next->prev = NULL;
first_link->next = NULL;
g_object_unref (linear);

disabled_config = meta_monitors_config_new (monitor_manager,
first_link,
layout_mode,
META_MONITORS_CONFIG_FLAG_NONE);
g_assert_cmpuint (g_list_length (disabled_config->logical_monitor_configs),
==, 1);
g_assert_cmpuint (g_list_length (disabled_config->disabled_monitor_specs),
==, 1);

/* Mark it as current — this is what apply_monitors_config() does on
* a successful TEMPORARY apply (the path taken by xrandr --off and
* Super+P, neither of which writes to the on-disk config store). */
meta_monitor_config_manager_set_current (config_manager, disabled_config);
g_object_unref (disabled_config);

/* Hotplug with the same monitor topology — simulates a monitor's
* auto input-scan triggering a DRM link-state ping. */
test_setup = create_monitor_test_setup (&test_case,
MONITOR_TEST_FLAG_NO_STORED);
emulate_hotplug (test_setup);

post_hotplug_config =
meta_monitor_config_manager_get_current (config_manager);
g_assert_nonnull (post_hotplug_config);
g_assert_cmpuint (g_list_length (post_hotplug_config->logical_monitor_configs),
==, 1);
g_assert_cmpuint (g_list_length (post_hotplug_config->disabled_monitor_specs),
==, 1);
}

static void
test_case_setup (void **fixture,
const void *data)
Expand Down Expand Up @@ -6418,6 +6496,9 @@ init_monitor_tests (void)

add_monitor_test ("/backends/monitor/wm/tiling",
meta_test_monitor_wm_tiling);

add_monitor_test ("/backends/monitor/hotplug-preserves-user-disabled-monitor",
meta_test_monitor_hotplug_preserves_user_disabled_monitor);
}

void
Expand Down