From: Robert Ancell <robert.ancell@canonical.com> Date: Sun, 15 Mar 2020 09:07:51 +0100 Subject: [PATCH 24/26] display: Allow fractional scaling to be enabled --- panels/display/cc-display-config-dbus.c | 16 +++ panels/display/cc-display-config.c | 237 ++++++++++++++++++++++++++++++++ panels/display/cc-display-config.h | 5 + panels/display/cc-display-settings.c | 28 +++- panels/display/cc-display-settings.ui | 16 +++ 5 files changed, 301 insertions(+), 1 deletion(-) diff --git a/panels/display/cc-display-config-dbus.c b/panels/display/cc-display-config-dbus.c index fc050a3..7eda67a 100644 --- a/panels/display/cc-display-config-dbus.c +++ b/panels/display/cc-display-config-dbus.c @@ -887,6 +887,7 @@ struct _CcDisplayConfigDBus gboolean supports_changing_layout_mode; gboolean global_scale_required; CcDisplayLayoutMode layout_mode; + gint legacy_ui_scale; GList *monitors; CcDisplayMonitorDBus *primary; @@ -1059,6 +1060,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); @@ -1215,6 +1219,13 @@ 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 gboolean is_scaled_mode_allowed (CcDisplayConfigDBus *self, CcDisplayMode *pmode, @@ -1469,6 +1480,10 @@ 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, "layout-mode")) { guint32 u = 0; @@ -1565,6 +1580,7 @@ cc_display_config_dbus_class_init (CcDisplayConfigDBusClass *klass) 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->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; pspec = g_param_spec_variant ("state", "GVariant", diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c index 552a1a6..2ff5a06 100644 --- a/panels/display/cc-display-config.c +++ b/panels/display/cc-display-config.c @@ -17,10 +17,23 @@ * */ +#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" + +#include <gdk/gdk.h> #include <gio/gio.h> #include <math.h> #include "cc-display-config.h" +#ifdef GDK_WINDOWING_WAYLAND +#include <gdk/gdkwayland.h> +#endif +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif + static const double known_diagonals[] = { 12.1, 13.3, @@ -419,6 +432,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; @@ -426,12 +443,80 @@ G_DEFINE_TYPE_WITH_PRIVATE (CcDisplayConfig, cc_display_config, G_TYPE_OBJECT) +static const char * +get_fractional_scaling_key (void) +{ + GdkDisplay *display; + + display = gdk_display_get_default (); + +#if defined(GDK_WINDOWING_X11) + if (GDK_IS_X11_DISPLAY (display)) + return MUTTER_FEATURE_FRACTIONAL_SCALING_X11; +#endif /* GDK_WINDOWING_X11 */ +#if defined(GDK_WINDOWING_WAYLAND) + if (GDK_IS_WAYLAND_DISPLAY (display)) + return MUTTER_FEATURE_FRACTIONAL_SCALING_WAYLAND; +#endif /* GDK_WINDOWING_WAYLAND */ + g_return_val_if_reached (NULL); +} + +static gboolean +get_fractional_scaling_active (CcDisplayConfig *self) +{ + CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); + g_auto(GStrv) features = NULL; + const char *key = get_fractional_scaling_key (); + + g_return_val_if_fail (key, FALSE); + + features = g_settings_get_strv (priv->mutter_settings, MUTTER_EXPERIMENTAL_FEATURES_KEY); + return g_strv_contains ((const gchar **) features, key); +} + +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; + g_autoptr(GVariantBuilder) builder = NULL; + const char *key = get_fractional_scaling_key (); + + /* 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; + } + + g_variant_builder_add (builder, "s", existing_features[i]); + } + if (enable && !have_fractional_scaling && key) + g_variant_builder_add (builder, "s", key); + + g_settings_set_value (priv->mutter_settings, MUTTER_EXPERIMENTAL_FEATURES_KEY, + g_variant_builder_end (builder)); +} + static void cc_display_config_init (CcDisplayConfig *self) { CcDisplayConfigPrivate *priv = cc_display_config_get_instance_private (self); priv->ui_sorted_monitors = NULL; + + /* 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 @@ -472,6 +557,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); } @@ -557,9 +643,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); } @@ -567,6 +660,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"); @@ -577,6 +672,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); } @@ -618,13 +719,31 @@ 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); +} + +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); } @@ -653,3 +772,121 @@ 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 (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 1361f16..4a1548e 100644 --- a/panels/display/cc-display-config.h +++ b/panels/display/cc-display-config.h @@ -161,6 +161,7 @@ struct _CcDisplayConfigClass CcDisplayMode *mode, double scale); gboolean (*layout_use_ui_scale) (CcDisplayConfig *self); + gint (*get_legacy_ui_scale) (CcDisplayConfig *self); }; @@ -188,8 +189,12 @@ gboolean cc_display_config_is_scaled_mode_valid (CcDisplayConfig CcDisplayMode *mode, double scale); gboolean cc_display_config_layout_use_ui_scale (CcDisplayConfig *self); +gint cc_display_config_get_legacy_ui_scale (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 d793fcc..947335a 100644 --- a/panels/display/cc-display-settings.c +++ b/panels/display/cc-display-settings.c @@ -49,6 +49,8 @@ struct _CcDisplaySettings GtkWidget *resolution_row; GtkWidget *scale_bbox; GtkWidget *scale_row; + GtkWidget *scale_fractional_row; + GtkWidget *scale_fractional_switch; GtkWidget *underscanning_row; GtkWidget *underscanning_switch; }; @@ -71,7 +73,6 @@ static void on_scale_btn_active_changed_cb (GtkWidget *widget, GParamSpec *pspec, CcDisplaySettings *self); - static gboolean should_show_rotation (CcDisplaySettings *self) { @@ -242,6 +243,7 @@ cc_display_settings_rebuild_ui (CcDisplaySettings *self) gtk_widget_set_visible (self->refresh_rate_row, FALSE); gtk_widget_set_visible (self->resolution_row, FALSE); gtk_widget_set_visible (self->scale_row, FALSE); + gtk_widget_set_visible (self->scale_fractional_row, FALSE); gtk_widget_set_visible (self->underscanning_row, FALSE); return G_SOURCE_REMOVE; @@ -429,6 +431,10 @@ cc_display_settings_rebuild_ui (CcDisplaySettings *self) gtk_widget_set_visible (self->scale_row, buttons > 1); + gtk_widget_set_visible (self->scale_fractional_row, TRUE); + gtk_switch_set_active (GTK_SWITCH (self->scale_fractional_switch), + cc_display_config_get_fractional_scaling (self->config)); + gtk_widget_set_visible (self->underscanning_row, cc_display_monitor_supports_underscanning (self->selected_output) && !cc_display_config_is_cloning (self->config)); @@ -671,6 +677,8 @@ cc_display_settings_class_init (CcDisplaySettingsClass *klass) gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, resolution_row); gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_bbox); gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_fractional_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_fractional_switch); gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_row); gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_switch); @@ -680,11 +688,29 @@ cc_display_settings_class_init (CcDisplaySettingsClass *klass) gtk_widget_class_bind_template_callback (widget_class, on_underscanning_switch_active_changed_cb); } +static void +on_scale_fractional_toggled (CcDisplaySettings *self) +{ + gboolean active; + + active = gtk_switch_get_active (GTK_SWITCH (self->scale_fractional_switch)); + + 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) { gtk_widget_init_template (GTK_WIDGET (self)); + g_signal_connect_object (self->scale_fractional_switch, + "notify::active", + G_CALLBACK (on_scale_fractional_toggled), + self, G_CONNECT_SWAPPED); + gtk_list_box_set_header_func (GTK_LIST_BOX (self), cc_list_box_update_header_func, NULL, NULL); diff --git a/panels/display/cc-display-settings.ui b/panels/display/cc-display-settings.ui index 50ef951..cf8ca0a 100644 --- a/panels/display/cc-display-settings.ui +++ b/panels/display/cc-display-settings.ui @@ -68,5 +68,21 @@ </child> </object> </child> + <child> + <object class="HdyActionRow" id="scale_fractional_row"> + <property name="width_request">100</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="title" translatable="yes" context="display setting">Fractional Scaling</property> + <property name="subtitle" translatable="yes" context="display setting">May increase power usage, lower speed, or reduce display sharpness.</property> + <child type="action"> + <object class="GtkSwitch" id="scale_fractional_switch"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + </object> + </child> + </object> + </child> </template> </interface>