[gnome-control-center/gnome-42] display: Use virtual clone modes when mirroring



commit e6cc73fd1d0a71c2e6192a13195c1e879d2ec2ec
Author: Jonas Ådahl <jadahl gmail com>
Date:   Thu Jun 23 12:59:50 2022 +0200

    display: Use virtual clone modes when mirroring
    
    These are not real modes, but just as place holders when generating
    'mirror' configurations. The clone modes will be just to match the
    flag/dimension, while the actual mode applied will be individual for
    each monitor.
    
    This allows monitors to have their own refresh rates, which is possible
    since a few mutter versions back. This also matches how mutter itself
    generates mirror modes when doing so via the key binding.

 panels/display/cc-display-config-dbus.c | 257 +++++++++++++++++++++++++++-----
 panels/display/cc-display-config.c      |  21 ++-
 panels/display/cc-display-config.h      |  10 +-
 panels/display/cc-display-panel.c       |  44 +++---
 panels/display/cc-display-settings.c    |  10 +-
 5 files changed, 276 insertions(+), 66 deletions(-)
---
diff --git a/panels/display/cc-display-config-dbus.c b/panels/display/cc-display-config-dbus.c
index fc38fd83b..be496a898 100644
--- a/panels/display/cc-display-config-dbus.c
+++ b/panels/display/cc-display-config-dbus.c
@@ -76,6 +76,14 @@ cc_display_mode_dbus_equal (const CcDisplayModeDBus *m1,
     (m1->flags & MODE_INTERLACED) == (m2->flags & MODE_INTERLACED);
 }
 
+static gboolean
+cc_display_mode_dbus_is_clone_mode (CcDisplayMode *pself)
+{
+  CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself);
+
+  return !self->id;
+}
+
 static void
 cc_display_mode_dbus_get_resolution (CcDisplayMode *pself,
                                      int *w, int *h)
@@ -173,6 +181,7 @@ cc_display_mode_dbus_class_init (CcDisplayModeDBusClass *klass)
 
   gobject_class->finalize = cc_display_mode_dbus_finalize;
 
+  parent_class->is_clone_mode = cc_display_mode_dbus_is_clone_mode;
   parent_class->get_resolution = cc_display_mode_dbus_get_resolution;
   parent_class->get_supported_scales = cc_display_mode_dbus_get_supported_scales;
   parent_class->get_preferred_scale = cc_display_mode_dbus_get_preferred_scale;
@@ -182,6 +191,25 @@ cc_display_mode_dbus_class_init (CcDisplayModeDBusClass *klass)
   parent_class->get_freq_f = cc_display_mode_dbus_get_freq_f;
 }
 
+static CcDisplayModeDBus *
+cc_display_mode_dbus_new_virtual (int     width,
+                                  int     height,
+                                  double  preferred_scale,
+                                  GArray *supported_scales)
+{
+  g_autoptr(GVariant) properties_variant = NULL;
+  CcDisplayModeDBus *self;
+
+  self = g_object_new (CC_TYPE_DISPLAY_MODE_DBUS, NULL);
+
+  self->width = width;
+  self->height = height;
+  self->preferred_scale = preferred_scale;
+  self->supported_scales = g_array_ref (supported_scales);
+
+  return self;
+}
+
 static CcDisplayModeDBus *
 cc_display_mode_dbus_new (CcDisplayMonitorDBus *monitor,
                           GVariant             *variant)
@@ -664,6 +692,44 @@ cc_display_monitor_dbus_set_mode (CcDisplayMonitor *pself,
   g_signal_emit_by_name (self, "mode");
 }
 
+static void
+cc_display_monitor_dbus_set_compatible_clone_mode (CcDisplayMonitor *pself,
+                                                   CcDisplayMode    *clone_mode)
+{
+  CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself);
+  GList *l;
+  CcDisplayMode *best_mode = NULL;
+  int clone_width, clone_height;
+
+  g_return_if_fail (cc_display_mode_is_clone_mode (clone_mode));
+
+  cc_display_mode_get_resolution (clone_mode, &clone_width, &clone_height);
+
+  for (l = self->modes; l; l = l->next)
+    {
+      CcDisplayMode *mode = l->data;
+      int width, height;
+
+      cc_display_mode_get_resolution (mode, &width, &height);
+      if (width != clone_width || height != clone_height)
+        continue;
+
+      if (!best_mode)
+        {
+          best_mode = mode;
+          continue;
+        }
+
+      if (cc_display_mode_get_freq_f (mode) >
+          cc_display_mode_get_freq_f (best_mode))
+        best_mode = mode;
+    }
+
+  g_return_if_fail (best_mode);
+
+  cc_display_monitor_set_mode (CC_DISPLAY_MONITOR (self), best_mode);
+}
+
 static void
 cc_display_monitor_dbus_set_position (CcDisplayMonitor *pself,
                                       int x, int y)
@@ -778,6 +844,7 @@ cc_display_monitor_dbus_class_init (CcDisplayMonitorDBusClass *klass)
   parent_class->get_underscanning = cc_display_monitor_dbus_get_underscanning;
   parent_class->set_underscanning = cc_display_monitor_dbus_set_underscanning;
   parent_class->set_mode = cc_display_monitor_dbus_set_mode;
+  parent_class->set_compatible_clone_mode = cc_display_monitor_dbus_set_compatible_clone_mode;
   parent_class->set_position = cc_display_monitor_dbus_set_position;
   parent_class->get_scale = cc_display_monitor_dbus_get_scale;
   parent_class->set_scale = cc_display_monitor_dbus_set_scale;
@@ -907,8 +974,6 @@ struct _CcDisplayConfigDBus
   CcDisplayMonitorDBus *primary;
 
   GHashTable *logical_monitors;
-
-  GList *clone_modes;
 };
 
 G_DEFINE_TYPE (CcDisplayConfigDBus,
@@ -1193,12 +1258,161 @@ cc_display_config_dbus_set_cloning (CcDisplayConfig *pself,
     }
 }
 
+static gboolean
+mode_supports_scale (CcDisplayMode *mode,
+                     double         scale)
+{
+  g_autoptr(GArray) scales = NULL;
+  int i;
+
+  scales = cc_display_mode_get_supported_scales (mode);
+  for (i = 0; i < scales->len; i++)
+    {
+      if (G_APPROX_VALUE (scale, g_array_index (scales, double, i),
+                          DBL_EPSILON))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+remove_unsupported_scales (CcDisplayMode *mode,
+                           GArray        *supported_scales)
+{
+  g_autoptr(GArray) mode_scales = NULL;
+  int i;
+
+  mode_scales = cc_display_mode_get_supported_scales (mode);
+  i = 0;
+  while (i < supported_scales->len)
+    {
+      double scale;
+
+      if (i == supported_scales->len)
+        break;
+
+      scale = g_array_index (supported_scales, double, i);
+
+      if (mode_supports_scale (mode, scale))
+        {
+          i++;
+          continue;
+        }
+
+      g_array_remove_range (supported_scales, i, 1);
+    }
+}
+
+static gboolean
+monitor_has_compatible_clone_mode (CcDisplayMonitorDBus *monitor,
+                                   CcDisplayModeDBus    *mode,
+                                   GArray               *supported_scales)
+{
+  GList *l;
+
+  for (l = monitor->modes; l; l = l->next)
+    {
+      CcDisplayModeDBus *other_mode = l->data;
+
+      if (other_mode->width != mode->width ||
+          other_mode->height != mode->height)
+        continue;
+
+      if ((other_mode->flags & MODE_INTERLACED) !=
+          (mode->flags & MODE_INTERLACED))
+        continue;
+
+      remove_unsupported_scales (CC_DISPLAY_MODE (other_mode), supported_scales);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+monitors_has_compatible_clone_mode (CcDisplayConfigDBus *self,
+                                    CcDisplayModeDBus   *mode,
+                                    GArray              *supported_scales)
+{
+  GList *l;
+
+  for (l = self->monitors; l; l = l->next)
+    {
+      CcDisplayMonitorDBus *monitor = l->data;
+
+      if (!monitor_has_compatible_clone_mode (monitor, mode, supported_scales))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+is_mode_better (CcDisplayModeDBus *mode,
+                CcDisplayModeDBus *other_mode)
+{
+  if (mode->width * mode->height > other_mode->width * other_mode->height)
+    return TRUE;
+  else if (mode->width * mode->height < other_mode->width * other_mode->height)
+    return FALSE;
+
+  if (!(mode->flags & MODE_INTERLACED) &&
+      (other_mode->flags & MODE_INTERLACED))
+    return TRUE;
+
+  return FALSE;
+}
+
 static GList *
-cc_display_config_dbus_get_cloning_modes (CcDisplayConfig *pself)
+cc_display_config_dbus_generate_cloning_modes (CcDisplayConfig *pself)
 {
   CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself);
+  CcDisplayMonitorDBus *base_monitor = NULL;
+  GList *l;
+  GList *clone_modes = NULL;
+  CcDisplayModeDBus *best_mode = NULL;
 
-  return self->clone_modes;
+  for (l = self->monitors; l; l = l->next)
+    {
+      CcDisplayMonitor *monitor = l->data;
+
+      if (cc_display_monitor_is_active (monitor))
+        {
+          base_monitor = CC_DISPLAY_MONITOR_DBUS (monitor);
+          break;
+        }
+    }
+
+  if (!base_monitor)
+    return NULL;
+
+  for (l = base_monitor->modes; l; l = l->next)
+    {
+      CcDisplayModeDBus *mode = l->data;
+      CcDisplayModeDBus *virtual_mode;
+      g_autoptr (GArray) supported_scales = NULL;
+
+      supported_scales =
+        cc_display_mode_get_supported_scales (CC_DISPLAY_MODE (mode));
+
+      if (!monitors_has_compatible_clone_mode (self, mode, supported_scales))
+        continue;
+
+      virtual_mode = cc_display_mode_dbus_new_virtual (mode->width,
+                                                       mode->height,
+                                                       mode->preferred_scale,
+                                                       supported_scales);
+      clone_modes = g_list_append (clone_modes, virtual_mode);
+
+      if (!best_mode || is_mode_better (virtual_mode, best_mode))
+        best_mode = virtual_mode;
+    }
+
+  best_mode->flags |= MODE_PREFERRED;
+
+  return clone_modes;
 }
 
 static gboolean
@@ -1387,36 +1601,6 @@ cc_display_config_dbus_init (CcDisplayConfigDBus *self)
   self->logical_monitors = g_hash_table_new (NULL, NULL);
 }
 
-static void
-gather_clone_modes (CcDisplayConfigDBus *self)
-{
-  guint n_monitors = g_list_length (self->monitors);
-  CcDisplayMonitorDBus *monitor;
-  GList *l;
-
-  if (n_monitors < 2)
-    return;
-
-  monitor = self->monitors->data;
-  for (l = monitor->modes; l != NULL; l = l->next)
-    {
-      CcDisplayModeDBus *mode = l->data;
-      gboolean valid = TRUE;
-      GList *ll;
-      for (ll = self->monitors->next; ll != NULL; ll = ll->next)
-        {
-          CcDisplayMonitorDBus *other_monitor = ll->data;
-          if (!cc_display_monitor_dbus_get_closest_mode (other_monitor, mode))
-            {
-              valid = FALSE;
-              break;
-            }
-        }
-      if (valid)
-        self->clone_modes = g_list_prepend (self->clone_modes, mode);
-    }
-}
-
 static void
 remove_logical_monitor (gpointer data,
                         GObject *object)
@@ -1525,8 +1709,6 @@ construct_monitors (CcDisplayConfigDBus *self,
 
       register_logical_monitor (self, logical_monitor);
     }
-
-  gather_clone_modes (self);
 }
 
 static void
@@ -1704,7 +1886,6 @@ 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->clone_modes, g_list_free);
 
   G_OBJECT_CLASS (cc_display_config_dbus_parent_class)->finalize (object);
 }
@@ -1728,7 +1909,7 @@ cc_display_config_dbus_class_init (CcDisplayConfigDBusClass *klass)
   parent_class->apply = cc_display_config_dbus_apply;
   parent_class->is_cloning = cc_display_config_dbus_is_cloning;
   parent_class->set_cloning = cc_display_config_dbus_set_cloning;
-  parent_class->get_cloning_modes = cc_display_config_dbus_get_cloning_modes;
+  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->is_scaled_mode_valid = cc_display_config_dbus_is_scaled_mode_valid;
   parent_class->set_minimum_size = cc_display_config_dbus_set_minimum_size;
diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c
index 9346dc3de..271342cbf 100644
--- a/panels/display/cc-display-config.c
+++ b/panels/display/cc-display-config.c
@@ -90,6 +90,12 @@ cc_display_mode_class_init (CcDisplayModeClass *klass)
 {
 }
 
+gboolean
+cc_display_mode_is_clone_mode (CcDisplayMode *self)
+{
+  return CC_DISPLAY_MODE_GET_CLASS (self)->is_clone_mode (self);
+}
+
 void
 cc_display_mode_get_resolution (CcDisplayMode *self, int *w, int *h)
 {
@@ -334,6 +340,12 @@ cc_display_monitor_set_mode (CcDisplayMonitor *self, CcDisplayMode *m)
   return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_mode (self, m);
 }
 
+void
+cc_display_monitor_set_compatible_clone_mode (CcDisplayMonitor *self, CcDisplayMode *m)
+{
+  return CC_DISPLAY_MONITOR_GET_CLASS (self)->set_mode (self, m);
+}
+
 void
 cc_display_monitor_set_position (CcDisplayMonitor *self, int x, int y)
 {
@@ -549,17 +561,18 @@ cc_display_config_is_applicable (CcDisplayConfig *self)
 
 void
 cc_display_config_set_mode_on_all_outputs (CcDisplayConfig *config,
-                                           CcDisplayMode   *mode)
+                                           CcDisplayMode   *clone_mode)
 {
   GList *outputs, *l;
 
   g_return_if_fail (CC_IS_DISPLAY_CONFIG (config));
+  g_return_if_fail (cc_display_mode_is_clone_mode (clone_mode));
 
   outputs = cc_display_config_get_monitors (config);
   for (l = outputs; l; l = l->next)
     {
       CcDisplayMonitor *output = l->data;
-      cc_display_monitor_set_mode (output, mode);
+      cc_display_monitor_set_compatible_clone_mode (output, clone_mode);
       cc_display_monitor_set_position (output, 0, 0);
     }
 }
@@ -607,10 +620,10 @@ cc_display_config_set_cloning (CcDisplayConfig *self,
 }
 
 GList *
-cc_display_config_get_cloning_modes (CcDisplayConfig *self)
+cc_display_config_generate_cloning_modes (CcDisplayConfig *self)
 {
   g_return_val_if_fail (CC_IS_DISPLAY_CONFIG (self), NULL);
-  return CC_DISPLAY_CONFIG_GET_CLASS (self)->get_cloning_modes (self);
+  return CC_DISPLAY_CONFIG_GET_CLASS (self)->generate_cloning_modes (self);
 }
 
 gboolean
diff --git a/panels/display/cc-display-config.h b/panels/display/cc-display-config.h
index dbaaa5cc5..852efa109 100644
--- a/panels/display/cc-display-config.h
+++ b/panels/display/cc-display-config.h
@@ -77,6 +77,7 @@ struct _CcDisplayModeClass
 {
   GObjectClass parent_class;
 
+  gboolean      (*is_clone_mode)        (CcDisplayMode *self);
   void          (*get_resolution)       (CcDisplayMode *self, int *w, int *h);
   GArray*       (*get_supported_scales) (CcDisplayMode *self);
   double        (*get_preferred_scale)  (CcDisplayMode *self);
@@ -125,6 +126,8 @@ struct _CcDisplayMonitorClass
   CcDisplayMode*    (*get_mode)               (CcDisplayMonitor  *self);
   CcDisplayMode*    (*get_preferred_mode)     (CcDisplayMonitor  *self);
   GList*            (*get_modes)              (CcDisplayMonitor  *self);
+  void              (*set_compatible_clone_mode) (CcDisplayMonitor  *self,
+                                                  CcDisplayMode     *m);
   void              (*set_mode)               (CcDisplayMonitor  *self,
                                                CcDisplayMode     *m);
   void              (*set_position)           (CcDisplayMonitor  *self,
@@ -153,7 +156,7 @@ struct _CcDisplayConfigClass
   gboolean (*is_cloning)        (CcDisplayConfig  *self);
   void     (*set_cloning)       (CcDisplayConfig  *self,
                                  gboolean          clone);
-  GList*   (*get_cloning_modes) (CcDisplayConfig  *self);
+  GList*   (*generate_cloning_modes) (CcDisplayConfig  *self);
   gboolean (*is_layout_logical) (CcDisplayConfig  *self);
   void     (*set_minimum_size)  (CcDisplayConfig  *self,
                                  int               width,
@@ -176,7 +179,7 @@ gboolean          cc_display_config_apply                   (CcDisplayConfig
 gboolean          cc_display_config_is_cloning              (CcDisplayConfig    *config);
 void              cc_display_config_set_cloning             (CcDisplayConfig    *config,
                                                              gboolean            clone);
-GList*            cc_display_config_get_cloning_modes       (CcDisplayConfig    *config);
+GList*            cc_display_config_generate_cloning_modes  (CcDisplayConfig    *config);
 
 void              cc_display_config_set_mode_on_all_outputs (CcDisplayConfig *config,
                                                              CcDisplayMode   *mode);
@@ -227,6 +230,8 @@ double            cc_display_monitor_get_scale              (CcDisplayMonitor  *
 void              cc_display_monitor_set_scale              (CcDisplayMonitor  *monitor,
                                                              double s);
 
+void              cc_display_monitor_set_compatible_clone_mode (CcDisplayMonitor  *monitor,
+                                                                CcDisplayMode     *mode);
 void              cc_display_monitor_set_mode               (CcDisplayMonitor  *monitor,
                                                              CcDisplayMode     *mode);
 void              cc_display_monitor_set_position           (CcDisplayMonitor  *monitor,
@@ -242,6 +247,7 @@ const char*       cc_display_monitor_get_ui_name            (CcDisplayMonitor  *
 const char*       cc_display_monitor_get_ui_number_name     (CcDisplayMonitor  *monitor);
 char*             cc_display_monitor_dup_ui_number_name     (CcDisplayMonitor  *monitor);
 
+gboolean          cc_display_mode_is_clone_mode             (CcDisplayMode     *mode);
 void              cc_display_mode_get_resolution            (CcDisplayMode     *mode,
                                                              int               *width,
                                                              int               *height);
diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c
index 593328acc..79d51c2c3 100644
--- a/panels/display/cc-display-panel.c
+++ b/panels/display/cc-display-panel.c
@@ -164,6 +164,22 @@ cc_panel_get_selected_type (CcDisplayPanel *panel)
     g_assert_not_reached ();
 }
 
+static CcDisplayMode *
+find_preferred_mode (GList *modes)
+{
+  GList *l;
+
+  for (l = modes; l; l = l->next)
+    {
+      CcDisplayMode *mode = l->data;
+
+      if (cc_display_mode_is_preferred (mode))
+        return mode;
+    }
+
+  return NULL;
+}
+
 static void
 config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type)
 {
@@ -238,34 +254,22 @@ config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type)
       {
         g_debug ("Creating new clone config");
         gdouble scale;
-        GList *modes = cc_display_config_get_cloning_modes (panel->current_config);
-        gint bw, bh;
-        CcDisplayMode *best = NULL;
+        g_autolist(CcDisplayMode) modes = NULL;
+        CcDisplayMode *clone_mode;
 
         /* Turn on cloning and select the best mode we can find by default */
         cc_display_config_set_cloning (panel->current_config, TRUE);
 
-        while (modes)
-          {
-            CcDisplayMode *mode = modes->data;
-            gint w, h;
-
-            cc_display_mode_get_resolution (mode, &w, &h);
-            if (best == NULL || (bw*bh < w*h))
-              {
-                best = mode;
-                cc_display_mode_get_resolution (best, &bw, &bh);
-              }
-
-            modes = modes->next;
-          }
+        modes = cc_display_config_generate_cloning_modes (panel->current_config);
+        clone_mode = find_preferred_mode (modes);
+        g_return_if_fail (clone_mode);
 
         /* Take the preferred scale by default, */
-        scale = cc_display_mode_get_preferred_scale (best);
+        scale = cc_display_mode_get_preferred_scale (clone_mode);
         /* but prefer the old primary scale if that is valid. */
         if (current_primary &&
             cc_display_config_is_scaled_mode_valid (panel->current_config,
-                                                    best,
+                                                    clone_mode,
                                                     old_primary_scale))
           scale = old_primary_scale;
 
@@ -273,7 +277,7 @@ config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type)
           {
             CcDisplayMonitor *output = l->data;
 
-            cc_display_monitor_set_mode (output, best);
+            cc_display_monitor_set_compatible_clone_mode (output, clone_mode);
             cc_display_monitor_set_scale (output, scale);
           }
       }
diff --git a/panels/display/cc-display-settings.c b/panels/display/cc-display-settings.c
index 54301b30a..21a0a1ad8 100644
--- a/panels/display/cc-display-settings.c
+++ b/panels/display/cc-display-settings.c
@@ -235,6 +235,7 @@ static gboolean
 cc_display_settings_rebuild_ui (CcDisplaySettings *self)
 {
   GtkWidget *child;
+  g_autolist(CcDisplayMode) clone_modes = NULL;
   GList *modes;
   GList *item;
   gint width, height;
@@ -365,9 +366,14 @@ cc_display_settings_rebuild_ui (CcDisplaySettings *self)
   /* Resolutions are always shown. */
   gtk_widget_set_visible (self->resolution_row, TRUE);
   if (cc_display_config_is_cloning (self->config))
-    modes = cc_display_config_get_cloning_modes (self->config);
+    {
+      clone_modes = cc_display_config_generate_cloning_modes (self->config);
+      modes = clone_modes;
+    }
   else
-    modes = cc_display_monitor_get_modes (self->selected_output);
+    {
+      modes = cc_display_monitor_get_modes (self->selected_output);
+    }
 
   g_list_store_remove_all (self->resolution_list);
   g_list_store_append (self->resolution_list, current_mode);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]