From: Robert Ancell Date: Sun, 15 Mar 2020 09:07:51 +0100 Subject: display: Allow fractional scaling to be enabled --- panels/display/cc-display-config-dbus.c | 60 ++++++++ panels/display/cc-display-config.c | 263 ++++++++++++++++++++++++++++++++ panels/display/cc-display-config.h | 11 ++ panels/display/cc-display-settings.c | 26 ++++ panels/display/cc-display-settings.ui | 7 + 5 files changed, 367 insertions(+) diff --git a/panels/display/cc-display-config-dbus.c b/panels/display/cc-display-config-dbus.c index aea0c65..0ab118c 100644 --- a/panels/display/cc-display-config-dbus.c +++ b/panels/display/cc-display-config-dbus.c @@ -1101,6 +1101,8 @@ struct _CcDisplayConfigDBus gboolean supports_changing_layout_mode; gboolean global_scale_required; CcDisplayLayoutMode layout_mode; + gint legacy_ui_scale; + char *renderer; GList *monitors; CcDisplayMonitorDBus *primary; @@ -1267,6 +1269,9 @@ cc_display_config_dbus_equal (CcDisplayConfig *pself, g_return_val_if_fail (pself, FALSE); g_return_val_if_fail (pother, FALSE); + if (self->layout_mode != other->layout_mode) + return FALSE; + cc_display_config_dbus_ensure_non_offset_coords (self); cc_display_config_dbus_ensure_non_offset_coords (other); @@ -1570,6 +1575,29 @@ cc_display_config_dbus_is_layout_logical (CcDisplayConfig *pself) self->layout_mode == CC_DISPLAY_LAYOUT_MODE_GLOBAL_UI_LOGICAL; } +static void +cc_display_config_dbus_set_layout_logical (CcDisplayConfig *pself, + gboolean logical) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + if (!self->supports_changing_layout_mode) + return; + + if (!logical) + { + self->layout_mode = CC_DISPLAY_LAYOUT_MODE_PHYSICAL; + return; + } + + if (g_str_equal (self->renderer, "native") || g_str_equal (self->renderer, "kms")) + self->layout_mode = CC_DISPLAY_LAYOUT_MODE_LOGICAL; + else if (g_str_equal (self->renderer, "xrandr")) + self->layout_mode = CC_DISPLAY_LAYOUT_MODE_GLOBAL_UI_LOGICAL; + else + g_return_if_reached (); +} + static gboolean cc_display_config_dbus_layout_use_ui_scale (CcDisplayConfig *pself) { @@ -1577,6 +1605,20 @@ cc_display_config_dbus_layout_use_ui_scale (CcDisplayConfig *pself) return self->layout_mode == CC_DISPLAY_LAYOUT_MODE_GLOBAL_UI_LOGICAL; } +static gint +cc_display_config_dbus_get_legacy_ui_scale (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + return self->legacy_ui_scale; +} + +static const char * +cc_display_config_dbus_get_renderer (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + return self->renderer; +} + static gboolean is_scale_allowed_by_active_monitors (CcDisplayConfigDBus *self, CcDisplayMode *mode, @@ -1744,6 +1786,11 @@ cc_display_config_dbus_init (CcDisplayConfigDBus *self) self->global_scale_required = FALSE; self->layout_mode = CC_DISPLAY_LAYOUT_MODE_LOGICAL; self->logical_monitors = g_hash_table_new (NULL, NULL); + + if (g_getenv ("WAYLAND_DISPLAY")) + self->renderer = g_strdup ("native"); + else if (g_getenv ("DISPLAY")) + self->renderer = g_strdup ("xrandr"); } static void @@ -1929,6 +1976,15 @@ cc_display_config_dbus_constructed (GObject *object) { g_variant_get (v, "b", &self->global_scale_required); } + else if (g_str_equal (s, "legacy-ui-scaling-factor")) + { + g_variant_get (v, "i", &self->legacy_ui_scale); + } + else if (g_str_equal (s, "renderer")) + { + g_clear_pointer (&self->renderer, g_free); + g_variant_get (v, "s", &self->renderer); + } else if (g_str_equal (s, "layout-mode")) { guint32 u = 0; @@ -2032,6 +2088,7 @@ cc_display_config_dbus_finalize (GObject *object) g_clear_list (&self->monitors, g_object_unref); g_clear_pointer (&self->logical_monitors, g_hash_table_destroy); + g_clear_pointer (&self->renderer, g_free); G_OBJECT_CLASS (cc_display_config_dbus_parent_class)->finalize (object); } @@ -2057,11 +2114,14 @@ cc_display_config_dbus_class_init (CcDisplayConfigDBusClass *klass) parent_class->set_cloning = cc_display_config_dbus_set_cloning; parent_class->generate_cloning_modes = cc_display_config_dbus_generate_cloning_modes; parent_class->is_layout_logical = cc_display_config_dbus_is_layout_logical; + parent_class->set_layout_logical = cc_display_config_dbus_set_layout_logical; parent_class->is_scaled_mode_valid = cc_display_config_dbus_is_scaled_mode_valid; parent_class->set_minimum_size = cc_display_config_dbus_set_minimum_size; parent_class->get_panel_orientation_managed = cc_display_config_dbus_get_panel_orientation_managed; parent_class->layout_use_ui_scale = cc_display_config_dbus_layout_use_ui_scale; + parent_class->get_legacy_ui_scale = cc_display_config_dbus_get_legacy_ui_scale; + parent_class->get_renderer = cc_display_config_dbus_get_renderer; pspec = g_param_spec_variant ("state", "GVariant", diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c index ce7d358..e8dc6c4 100644 --- a/panels/display/cc-display-config.c +++ b/panels/display/cc-display-config.c @@ -17,6 +17,12 @@ * */ +#define MUTTER_SCHEMA "org.gnome.mutter" +#define MUTTER_EXPERIMENTAL_FEATURES_KEY "experimental-features" +#define MUTTER_FEATURE_FRACTIONAL_SCALING_X11 "x11-randr-fractional-scaling" +#define MUTTER_FEATURE_FRACTIONAL_SCALING_WAYLAND "scale-monitor-framebuffer" +#define MUTTER_FEATURE_FRACTIONAL_SCALING_XWAYLAND "xwayland-native-scaling" + #include #include #include "cc-display-config.h" @@ -486,6 +492,10 @@ cc_display_monitor_set_ui_info (CcDisplayMonitor *self, gint ui_number, gchar *u struct _CcDisplayConfigPrivate { GList *ui_sorted_monitors; + + GSettings *mutter_settings; + gboolean fractional_scaling; + gboolean fractional_scaling_pending_disable; }; typedef struct _CcDisplayConfigPrivate CcDisplayConfigPrivate; @@ -493,6 +503,86 @@ G_DEFINE_TYPE_WITH_PRIVATE (CcDisplayConfig, cc_display_config, G_TYPE_OBJECT) +static const char * +get_fractional_scaling_key (CcDisplayConfig *self) +{ + const char *renderer = cc_display_config_get_renderer (self); + + if (!renderer) + g_return_val_if_reached (MUTTER_FEATURE_FRACTIONAL_SCALING_WAYLAND); + + if (g_str_equal (renderer, "xrandr")) + return MUTTER_FEATURE_FRACTIONAL_SCALING_X11; + + if (g_str_equal (renderer, "native") || + g_str_equal (renderer, "kms") || + g_str_equal (renderer, "dummy")) + return MUTTER_FEATURE_FRACTIONAL_SCALING_WAYLAND; + + g_return_val_if_reached (NULL); +} + +static gboolean +get_fractional_scaling_active (CcDisplayConfig *self) +{ + const char *renderer = cc_display_config_get_renderer (self); + + if (renderer && g_str_equal (renderer, "xrandr") && + !cc_display_config_layout_use_ui_scale (self)) + return FALSE; + + return cc_display_config_is_layout_logical (self); +} + +static void +set_fractional_scaling_active (CcDisplayConfig *self, + gboolean enable) +{ + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + g_auto(GStrv) existing_features = NULL; + gboolean have_fractional_scaling = FALSE; + gboolean have_xwayland_scaling = FALSE; + g_autoptr(GVariantBuilder) builder = NULL; + const char *key = get_fractional_scaling_key (self); + gboolean is_wayland; + + is_wayland = g_strcmp0 (key, MUTTER_FEATURE_FRACTIONAL_SCALING_WAYLAND) == 0; + + /* Add or remove the fractional scaling feature from mutter */ + existing_features = g_settings_get_strv (priv->mutter_settings, + MUTTER_EXPERIMENTAL_FEATURES_KEY); + builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + for (int i = 0; existing_features[i] != NULL; i++) + { + if (g_strcmp0 (existing_features[i], key) == 0) + { + if (enable) + have_fractional_scaling = TRUE; + else + continue; + } + + if (g_strcmp0 (existing_features[i], MUTTER_FEATURE_FRACTIONAL_SCALING_XWAYLAND) == 0) + { + if (enable && is_wayland) + have_xwayland_scaling = TRUE; + else + continue; + } + + g_variant_builder_add (builder, "s", existing_features[i]); + } + + if (enable && !have_fractional_scaling && key) + g_variant_builder_add (builder, "s", key); + + if (enable && !have_xwayland_scaling && is_wayland) + g_variant_builder_add (builder, "s", MUTTER_FEATURE_FRACTIONAL_SCALING_XWAYLAND); + + g_settings_set_value (priv->mutter_settings, MUTTER_EXPERIMENTAL_FEATURES_KEY, + g_variant_builder_end (builder)); +} + static void cc_display_config_init (CcDisplayConfig *self) { @@ -520,6 +610,10 @@ cc_display_config_constructed (GObject *object) } cc_display_config_update_ui_numbers_names(self); + + /* No need to connect to the setting, as we'll get notified by mutter */ + priv->mutter_settings = g_settings_new (MUTTER_SCHEMA); + priv->fractional_scaling = get_fractional_scaling_active (self); } static void @@ -529,6 +623,7 @@ cc_display_config_finalize (GObject *object) CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); g_list_free (priv->ui_sorted_monitors); + g_clear_object (&priv->mutter_settings); G_OBJECT_CLASS (cc_display_config_parent_class)->finalize (object); } @@ -620,9 +715,16 @@ gboolean cc_display_config_equal (CcDisplayConfig *self, CcDisplayConfig *other) { + CcDisplayConfigPrivate *spriv = cc_display_config_get_instance_private (self); + CcDisplayConfigPrivate *opriv = cc_display_config_get_instance_private (other); + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), FALSE); g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (other), FALSE); + if (spriv->fractional_scaling_pending_disable != + opriv->fractional_scaling_pending_disable) + return FALSE; + return CC_DISPLAY_CONFIG_GET_CLASS (self)->equal (self, other); } @@ -630,6 +732,8 @@ gboolean cc_display_config_apply (CcDisplayConfig *self, GError **error) { + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + if (!CC_IS_DISPLAY_CONFIG (self)) { g_warning ("Cannot apply invalid configuration"); @@ -640,6 +744,12 @@ cc_display_config_apply (CcDisplayConfig *self, return FALSE; } + if (priv->fractional_scaling_pending_disable) + { + set_fractional_scaling_active (self, FALSE); + priv->fractional_scaling_pending_disable = FALSE; + } + return CC_DISPLAY_CONFIG_GET_CLASS (self)->apply (self, error); } @@ -672,6 +782,14 @@ cc_display_config_is_layout_logical (CcDisplayConfig *self) return CC_DISPLAY_CONFIG_GET_CLASS (self)->is_layout_logical (self); } +void +cc_display_config_set_layout_logical (CcDisplayConfig *self, + gboolean logical) +{ + g_return_if_fail (CC_IS_DISPLAY_CONFIG (self)); + return CC_DISPLAY_CONFIG_GET_CLASS (self)->set_layout_logical (self, logical); +} + void cc_display_config_set_minimum_size (CcDisplayConfig *self, int width, @@ -681,13 +799,37 @@ cc_display_config_set_minimum_size (CcDisplayConfig *self, CC_DISPLAY_CONFIG_GET_CLASS (self)->set_minimum_size (self, width, height); } +gint +cc_display_config_get_legacy_ui_scale (CcDisplayConfig *self) +{ + return CC_DISPLAY_CONFIG_GET_CLASS (self)->get_legacy_ui_scale (self); +} + +const char * +cc_display_config_get_renderer (CcDisplayConfig *self) +{ + return CC_DISPLAY_CONFIG_GET_CLASS (self)->get_renderer (self); +} + +static gboolean +scale_value_is_fractional (double scale) +{ + return (int) scale != scale; +} + gboolean cc_display_config_is_scaled_mode_valid (CcDisplayConfig *self, CcDisplayMode *mode, double scale) { + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), FALSE); g_return_val_if_fail (CC_IS_DISPLAY_MODE (mode), FALSE); + + if (priv->fractional_scaling_pending_disable && scale_value_is_fractional (scale)) + return FALSE; + return CC_DISPLAY_CONFIG_GET_CLASS (self)->is_scaled_mode_valid (self, mode, scale); } @@ -749,3 +891,124 @@ cc_display_config_get_maximum_scaling (CcDisplayConfig *self) return max_scale; } + +static gboolean +set_monitors_scaling_to_preferred_integers (CcDisplayConfig *self) +{ + GList *l; + gboolean any_changed = FALSE; + + for (l = cc_display_config_get_monitors (self); l; l = l->next) + { + CcDisplayMonitor *monitor = l->data; + gdouble monitor_scale = cc_display_monitor_get_scale (monitor); + + if (scale_value_is_fractional (monitor_scale)) + { + CcDisplayMode *preferred_mode; + double preferred_scale; + double *saved_scale; + + preferred_mode = cc_display_monitor_get_preferred_mode (monitor); + preferred_scale = cc_display_mode_get_preferred_scale (preferred_mode); + cc_display_monitor_set_scale (monitor, preferred_scale); + any_changed = TRUE; + + saved_scale = g_new (double, 1); + *saved_scale = monitor_scale; + g_object_set_data_full (G_OBJECT (monitor), + "previous-fractional-scale", + saved_scale, g_free); + } + else + { + g_signal_emit_by_name (monitor, "scale"); + } + } + + return any_changed; +} + +static void +reset_monitors_scaling_to_selected_values (CcDisplayConfig *self) +{ + GList *l; + + for (l = cc_display_config_get_monitors (self); l; l = l->next) + { + CcDisplayMonitor *monitor = l->data; + gdouble *saved_scale; + + saved_scale = g_object_get_data (G_OBJECT (monitor), + "previous-fractional-scale"); + + if (saved_scale) + { + cc_display_monitor_set_scale (monitor, *saved_scale); + g_object_set_data (G_OBJECT (monitor), "previous-fractional-scale", NULL); + } + else + { + g_signal_emit_by_name (monitor, "scale"); + } + } +} + +void +cc_display_config_set_fractional_scaling (CcDisplayConfig *self, + gboolean enabled) +{ + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + + if (priv->fractional_scaling == enabled) + return; + + priv->fractional_scaling = enabled; + + if (!cc_display_config_layout_use_ui_scale (self)) + cc_display_config_set_layout_logical (self, enabled); + + if (priv->fractional_scaling) + { + if (priv->fractional_scaling_pending_disable) + { + priv->fractional_scaling_pending_disable = FALSE; + reset_monitors_scaling_to_selected_values (self); + } + + if (!get_fractional_scaling_active (self)) + set_fractional_scaling_active (self, enabled); + } + else + { + priv->fractional_scaling_pending_disable = TRUE; + + if (!set_monitors_scaling_to_preferred_integers (self)) + { + gboolean disable_now = FALSE; + + if (cc_display_config_layout_use_ui_scale (self)) + { + disable_now = + G_APPROX_VALUE (cc_display_config_get_legacy_ui_scale (self), + cc_display_config_get_maximum_scaling (self), + DBL_EPSILON); + } + + if (disable_now) + { + priv->fractional_scaling_pending_disable = FALSE; + reset_monitors_scaling_to_selected_values (self); + set_fractional_scaling_active (self, enabled); + } + } + } +} + +gboolean +cc_display_config_get_fractional_scaling (CcDisplayConfig *self) +{ + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + + return priv->fractional_scaling; +} diff --git a/panels/display/cc-display-config.h b/panels/display/cc-display-config.h index 4fdc2dd..ebe8423 100644 --- a/panels/display/cc-display-config.h +++ b/panels/display/cc-display-config.h @@ -180,6 +180,8 @@ struct _CcDisplayConfigClass gboolean clone); GList* (*generate_cloning_modes) (CcDisplayConfig *self); gboolean (*is_layout_logical) (CcDisplayConfig *self); + void (*set_layout_logical) (CcDisplayConfig *self, + gboolean enabled); void (*set_minimum_size) (CcDisplayConfig *self, int width, int height); @@ -188,6 +190,8 @@ struct _CcDisplayConfigClass double scale); gboolean (* get_panel_orientation_managed) (CcDisplayConfig *self); gboolean (*layout_use_ui_scale) (CcDisplayConfig *self); + gint (*get_legacy_ui_scale) (CcDisplayConfig *self); + const char * (*get_renderer) (CcDisplayConfig *self); }; @@ -208,6 +212,8 @@ void cc_display_config_set_mode_on_all_outputs (CcDisplayConfig *co CcDisplayMode *mode); gboolean cc_display_config_is_layout_logical (CcDisplayConfig *self); +void cc_display_config_set_layout_logical (CcDisplayConfig *self, + gboolean logical); void cc_display_config_set_minimum_size (CcDisplayConfig *self, int width, int height); @@ -218,8 +224,13 @@ gboolean cc_display_config_get_panel_orientation_managed (CcDisplayConfig *self); void cc_display_config_update_ui_numbers_names (CcDisplayConfig *self); gboolean cc_display_config_layout_use_ui_scale (CcDisplayConfig *self); +gint cc_display_config_get_legacy_ui_scale (CcDisplayConfig *self); +const char* cc_display_config_get_renderer (CcDisplayConfig *self); double cc_display_config_get_maximum_scaling (CcDisplayConfig *self); +void cc_display_config_set_fractional_scaling (CcDisplayConfig *self, + gboolean enabled); +gboolean cc_display_config_get_fractional_scaling (CcDisplayConfig *self); const char* cc_display_monitor_get_display_name (CcDisplayMonitor *monitor); gboolean cc_display_monitor_is_active (CcDisplayMonitor *monitor); diff --git a/panels/display/cc-display-settings.c b/panels/display/cc-display-settings.c index 5b6eb01..9ce2c87 100644 --- a/panels/display/cc-display-settings.c +++ b/panels/display/cc-display-settings.c @@ -58,6 +58,7 @@ struct _CcDisplaySettings GtkWidget *scale_bbox; GtkWidget *scale_buttons_row; GtkWidget *scale_combo_row; + GtkWidget *scale_fractional_row; AdwSwitchRow *underscanning_row; }; @@ -394,6 +395,7 @@ cc_display_settings_rebuild_ui (CcDisplaySettings *self) gtk_widget_set_visible (self->resolution_row, FALSE); gtk_widget_set_visible (self->scale_combo_row, FALSE); gtk_widget_set_visible (self->scale_buttons_row, FALSE); + gtk_widget_set_visible (self->scale_fractional_row, FALSE); gtk_widget_set_visible (GTK_WIDGET (self->underscanning_row), FALSE); return G_SOURCE_REMOVE; @@ -650,6 +652,11 @@ cc_display_settings_rebuild_ui (CcDisplaySettings *self) } cc_display_settings_refresh_layout (self, self->collapsed); + gtk_widget_set_visible (self->scale_fractional_row, TRUE); + g_object_set (G_OBJECT (self->scale_fractional_row), "active", + cc_display_config_get_fractional_scaling (self->config), + NULL); + gtk_widget_set_visible (GTK_WIDGET (self->underscanning_row), cc_display_monitor_supports_underscanning (self->selected_output) && !cc_display_config_is_cloning (self->config)); @@ -957,6 +964,7 @@ cc_display_settings_class_init (CcDisplaySettingsClass *klass) gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_bbox); gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_buttons_row); gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_combo_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_fractional_row); gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_row); gtk_widget_class_bind_template_callback (widget_class, on_enabled_row_active_changed_cb); @@ -968,6 +976,19 @@ cc_display_settings_class_init (CcDisplaySettingsClass *klass) gtk_widget_class_bind_template_callback (widget_class, on_underscanning_row_active_changed_cb); } +static void +on_scale_fractional_toggled (CcDisplaySettings *self) +{ + gboolean active; + + active = adw_switch_row_get_active (ADW_SWITCH_ROW (self->scale_fractional_row)); + + if (self->config) + cc_display_config_set_fractional_scaling (self->config, active); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + static void cc_display_settings_init (CcDisplaySettings *self) { @@ -1015,6 +1036,11 @@ cc_display_settings_init (CcDisplaySettings *self) adw_combo_row_set_model (ADW_COMBO_ROW (self->resolution_row), G_LIST_MODEL (self->resolution_list)); + g_signal_connect_object (self->scale_fractional_row, + "notify::active", + G_CALLBACK (on_scale_fractional_toggled), + self, G_CONNECT_SWAPPED); + self->updating = FALSE; } diff --git a/panels/display/cc-display-settings.ui b/panels/display/cc-display-settings.ui index 5cffb04..550d5fb 100644 --- a/panels/display/cc-display-settings.ui +++ b/panels/display/cc-display-settings.ui @@ -110,6 +110,13 @@ + + + _Fractional Scaling + May increase power usage, lower speed, or reduce display sharpness. + True + +