diff --git a/panels/display/Makefile.am b/panels/display/Makefile.am index 840bb55a9..1866e3f49 100644 --- a/panels/display/Makefile.am +++ b/panels/display/Makefile.am @@ -13,10 +13,14 @@ libdisplay_la_SOURCES = \ cc-display-config.h \ cc-display-config-rr.c \ cc-display-config-rr.h \ + cc-display-config-dbus.c \ + cc-display-config-dbus.h \ cc-display-config-manager.c \ cc-display-config-manager.h \ cc-display-config-manager-rr.c \ cc-display-config-manager-rr.h \ + cc-display-config-manager-dbus.c \ + cc-display-config-manager-dbus.h \ cc-display-panel.c \ cc-display-panel.h \ cc-night-light-dialog.c \ diff --git a/panels/display/cc-display-config-dbus.c b/panels/display/cc-display-config-dbus.c new file mode 100644 index 000000000..29114ffce --- /dev/null +++ b/panels/display/cc-display-config-dbus.c @@ -0,0 +1,1645 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include + +#include "cc-display-config-dbus.h" + +#define MODE_FORMAT "(iiddu)" +#define MODES_FORMAT "a" MODE_FORMAT + +#define MONITOR_SPEC_FORMAT "(ssss)" +#define MONITOR_FORMAT "(" MONITOR_SPEC_FORMAT MODES_FORMAT "a{sv})" +#define MONITORS_FORMAT "a" MONITOR_FORMAT + +#define LOGICAL_MONITOR_FORMAT "(iiduba" MONITOR_SPEC_FORMAT "a{sv})" +#define LOGICAL_MONITORS_FORMAT "a" LOGICAL_MONITOR_FORMAT + +#define CURRENT_STATE_FORMAT "(u" MONITORS_FORMAT LOGICAL_MONITORS_FORMAT "ad" "a{sv})" + +typedef enum _CcDisplayModeFlags +{ + MODE_PREFERRED = 1 << 0, + MODE_CURRENT = 1 << 1 +} CcDisplayModeFlags; + +struct _CcDisplayModeDBus +{ + CcDisplayMode parent_instance; + + int width; + int height; + double refresh_rate; + double preferred_scale; + guint32 flags; +}; + +G_DEFINE_TYPE (CcDisplayModeDBus, + cc_display_mode_dbus, + CC_TYPE_DISPLAY_MODE) + +static gboolean +cc_display_mode_dbus_equal (const CcDisplayModeDBus *m1, + const CcDisplayModeDBus *m2) +{ + if (!m1 && !m2) + return TRUE; + else if (!m1 || !m2) + return FALSE; + + return m1->width == m2->width && + m1->height == m2->height && + m1->refresh_rate == m2->refresh_rate; +} + +static void +cc_display_mode_dbus_get_resolution (CcDisplayMode *pself, + int *w, int *h) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + if (w) + *w = self->width; + if (h) + *h = self->height; +} + +static gboolean +cc_display_mode_dbus_is_interlaced (CcDisplayMode *pself) +{ + /* XXX: add to the dbus api ? */ + return FALSE; +} + +static int +cc_display_mode_dbus_get_freq (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return self->refresh_rate; +} + +static double +cc_display_mode_dbus_get_freq_f (CcDisplayMode *pself) +{ + CcDisplayModeDBus *self = CC_DISPLAY_MODE_DBUS (pself); + + return self->refresh_rate; +} + +static void +cc_display_mode_dbus_init (CcDisplayModeDBus *self) +{ +} + +static void +cc_display_mode_dbus_finalize (GObject *object) +{ + G_OBJECT_CLASS (cc_display_mode_dbus_parent_class)->finalize (object); +} + +static void +cc_display_mode_dbus_class_init (CcDisplayModeDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayModeClass *parent_class = CC_DISPLAY_MODE_CLASS (klass); + + gobject_class->finalize = cc_display_mode_dbus_finalize; + + parent_class->get_resolution = cc_display_mode_dbus_get_resolution; + parent_class->is_interlaced = cc_display_mode_dbus_is_interlaced; + parent_class->get_freq = cc_display_mode_dbus_get_freq; + parent_class->get_freq_f = cc_display_mode_dbus_get_freq_f; +} + +static CcDisplayModeDBus * +cc_display_mode_dbus_new (GVariant *variant) +{ + CcDisplayModeDBus *self = g_object_new (CC_TYPE_DISPLAY_MODE_DBUS, NULL); + + g_variant_get (variant, MODE_FORMAT, + &self->width, + &self->height, + &self->refresh_rate, + &self->preferred_scale, + &self->flags); + + return self; +} + + +#define CC_TYPE_DISPLAY_LOGICAL_MONITOR (cc_display_logical_monitor_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayLogicalMonitor, cc_display_logical_monitor, + CC, DISPLAY_LOGICAL_MONITOR, GObject) + +struct _CcDisplayLogicalMonitor +{ + GObject parent_instance; + + int x; + int y; + double scale; + CcDisplayRotation rotation; + gboolean primary; + + GHashTable *monitors; +}; + +G_DEFINE_TYPE (CcDisplayLogicalMonitor, + cc_display_logical_monitor, + G_TYPE_OBJECT) + +static gboolean +cc_display_logical_monitor_equal (const CcDisplayLogicalMonitor *m1, + const CcDisplayLogicalMonitor *m2) +{ + if (!m1 && !m2) + return TRUE; + else if (!m1 || !m2) + return FALSE; + + return m1->x == m2->x && + m1->y == m2->y && + m1->scale == m2->scale && + m1->rotation == m2->rotation && + m1->primary == m2->primary; +} + +static void +cc_display_logical_monitor_init (CcDisplayLogicalMonitor *self) +{ + self->scale = 1.0; + self->monitors = g_hash_table_new (NULL, NULL); +} + +static void +cc_display_logical_monitor_finalize (GObject *object) +{ + CcDisplayLogicalMonitor *self = CC_DISPLAY_LOGICAL_MONITOR (object); + + g_warn_if_fail (g_hash_table_size (self->monitors) == 0); + g_clear_pointer (&self->monitors, g_hash_table_destroy); + + G_OBJECT_CLASS (cc_display_logical_monitor_parent_class)->finalize (object); +} + +static void +cc_display_logical_monitor_class_init (CcDisplayLogicalMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = cc_display_logical_monitor_finalize; +} + + +typedef enum _CcDisplayMonitorUnderscanning +{ + UNDERSCANNING_UNSUPPORTED = 0, + UNDERSCANNING_DISABLED, + UNDERSCANNING_ENABLED +} CcDisplayMonitorUnderscanning; + +struct _CcDisplayMonitorDBus +{ + CcDisplayMonitor parent_instance; + CcDisplayConfigDBus *config; + + gchar *connector_name; + gchar *vendor_name; + gchar *product_name; + gchar *product_serial; + gchar *display_name; + + int width_mm; + int height_mm; + gboolean builtin; + CcDisplayMonitorUnderscanning underscanning; + int max_width; + int max_height; + + GList *modes; + CcDisplayMode *current_mode; + CcDisplayMode *preferred_mode; + + CcDisplayLogicalMonitor *logical_monitor; +}; + +G_DEFINE_TYPE (CcDisplayMonitorDBus, + cc_display_monitor_dbus, + CC_TYPE_DISPLAY_MONITOR) + +static void +register_logical_monitor (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *logical_monitor); +static void +cc_display_config_dbus_set_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *new_primary); +static void +cc_display_config_dbus_unset_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *old_primary); +static void +cc_display_config_dbus_ensure_non_offset_coords (CcDisplayConfigDBus *self); +static void +cc_display_config_dbus_append_right (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *monitor); +static void +cc_display_config_dbus_ensure_gapless (CcDisplayConfigDBus *self); +static void +cc_display_config_dbus_make_linear (CcDisplayConfigDBus *self); +static gboolean +cc_display_config_dbus_is_supported_scale (CcDisplayConfigDBus *self, + double scale); + + +static const char * +cc_display_monitor_dbus_get_display_name (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->display_name) + return self->display_name; + + return self->connector_name; +} + +static const char * +cc_display_monitor_dbus_get_connector_name (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->connector_name; +} + +static gboolean +cc_display_monitor_dbus_is_builtin (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->builtin; +} + +static gboolean +cc_display_monitor_dbus_is_primary (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + return self->logical_monitor->primary; + + return FALSE; +} + +static void +cc_display_monitor_dbus_set_primary (CcDisplayMonitor *pself, + gboolean primary) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (primary) + cc_display_config_dbus_set_primary (self->config, self); + else + cc_display_config_dbus_unset_primary (self->config, self); +} + +static gboolean +cc_display_monitor_dbus_is_active (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->logical_monitor != NULL; +} + +static void +cc_display_monitor_dbus_set_logical_monitor (CcDisplayMonitorDBus *self, + CcDisplayLogicalMonitor *logical_monitor) +{ + gboolean was_primary = FALSE; + + if (self->logical_monitor) + { + was_primary = self->logical_monitor->primary; + if (was_primary) + cc_display_config_dbus_unset_primary (self->config, self); + g_hash_table_remove (self->logical_monitor->monitors, self); + g_object_unref (self->logical_monitor); + } + + self->logical_monitor = logical_monitor; + + if (self->logical_monitor) + { + g_hash_table_add (self->logical_monitor->monitors, self); + g_object_ref (self->logical_monitor); + if (was_primary) + cc_display_config_dbus_set_primary (self->config, self); + } +} + +static void +cc_display_monitor_dbus_set_active (CcDisplayMonitor *pself, + gboolean active) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (!self->current_mode && active) + { + if (self->preferred_mode) + self->current_mode = self->preferred_mode; + else if (self->modes) + self->current_mode = (CcDisplayMode *) self->modes->data; + else + g_warning ("Couldn't find a mode to activate monitor at %s", self->connector_name); + } + + if (!self->logical_monitor && active) + { + CcDisplayLogicalMonitor *logical_monitor; + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + cc_display_monitor_dbus_set_logical_monitor (self, logical_monitor); + cc_display_config_dbus_append_right (self->config, logical_monitor); + register_logical_monitor (self->config, logical_monitor); + } + else if (self->logical_monitor && !active) + { + cc_display_monitor_dbus_set_logical_monitor (self, NULL); + cc_display_config_dbus_ensure_gapless (self->config); + } +} + +static CcDisplayRotation +cc_display_monitor_dbus_get_rotation (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + return self->logical_monitor->rotation; + + return CC_DISPLAY_ROTATION_NONE; +} + +static void +cc_display_monitor_dbus_set_rotation (CcDisplayMonitor *pself, + CcDisplayRotation rotation) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (!self->logical_monitor) + return; + + if (self->logical_monitor->rotation != rotation) + { + self->logical_monitor->rotation = rotation; + /* See comment in ensure_gapless() for why we disregard the + existing layout here. */ + cc_display_config_dbus_make_linear (self->config); + } +} + +static gboolean +cc_display_monitor_dbus_supports_rotation (CcDisplayMonitor *pself, + CcDisplayRotation rotation) +{ + return TRUE; +} + +static void +cc_display_monitor_dbus_get_physical_size (CcDisplayMonitor *pself, + int *w, int *h) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (w) + *w = self->width_mm; + if (h) + *h = self->height_mm; +} + +static void +cc_display_monitor_dbus_get_geometry (CcDisplayMonitor *pself, + int *x, int *y, int *w, int *h) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + CcDisplayMode *mode = NULL; + + if (self->logical_monitor) + { + if (x) + *x = self->logical_monitor->x; + if (y) + *y = self->logical_monitor->y; + } + else + { + if (x) + *x = -1; + if (y) + *y = -1; + } + + if (self->current_mode) + mode = self->current_mode; + else if (self->preferred_mode) + mode = self->preferred_mode; + else if (self->modes) + mode = CC_DISPLAY_MODE (self->modes->data); + + if (mode) + cc_display_mode_get_resolution (mode, w, h); + else + { + g_warning ("Monitor at %s has no modes?", self->connector_name); + if (w) + *w = -1; + if (h) + *h = -1; + } +} + +static CcDisplayMode * +cc_display_monitor_dbus_get_mode (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->current_mode; +} + +static CcDisplayMode * +cc_display_monitor_dbus_get_preferred_mode (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->preferred_mode; +} + +static guint32 +cc_display_monitor_dbus_get_id (CcDisplayMonitor *pself) +{ + return 0; +} + +static GList * +cc_display_monitor_dbus_get_modes (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->modes; +} + +static gboolean +cc_display_monitor_dbus_supports_underscanning (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->underscanning != UNDERSCANNING_UNSUPPORTED; +} + +static gboolean +cc_display_monitor_dbus_get_underscanning (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + return self->underscanning == UNDERSCANNING_ENABLED; +} + +static void +cc_display_monitor_dbus_set_underscanning (CcDisplayMonitor *pself, + gboolean underscanning) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->underscanning == UNDERSCANNING_UNSUPPORTED) + return; + + if (underscanning) + self->underscanning = UNDERSCANNING_ENABLED; + else + self->underscanning = UNDERSCANNING_DISABLED; +} + +static CcDisplayMode * +cc_display_monitor_dbus_get_closest_mode (CcDisplayMonitorDBus *self, + CcDisplayModeDBus *mode) +{ + CcDisplayModeDBus *best = NULL; + GList *l; + + for (l = self->modes; l != NULL; l = l->next) + { + CcDisplayModeDBus *similar = l->data; + + if (similar->width != mode->width || + similar->height != mode->height) + continue; + + if (similar->refresh_rate == mode->refresh_rate) + { + best = similar; + break; + } + + /* There might be a better heuristic. */ + if (!best || best->refresh_rate < similar->refresh_rate) + { + best = similar; + continue; + } + } + + return CC_DISPLAY_MODE (best); +} + +static void +cc_display_monitor_dbus_set_mode (CcDisplayMonitor *pself, + CcDisplayMode *new_mode) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + CcDisplayMode *mode; + int w1, w2, h1, h2; + + g_return_if_fail (new_mode != NULL); + + if (self->current_mode) + cc_display_mode_get_resolution (self->current_mode, &w1, &h1); + else + w1 = h1 = 0; + + mode = cc_display_monitor_dbus_get_closest_mode (self, CC_DISPLAY_MODE_DBUS (new_mode)); + if (mode) + cc_display_mode_get_resolution (mode, &w2, &h2); + else + w2 = h2 = 0; + + self->current_mode = mode; + + /* See comment in ensure_gapless() for why we disregard the + existing layout here. */ + if (w1 != w2 || h1 != h2) + cc_display_config_dbus_make_linear (self->config); +} + +static void +cc_display_monitor_dbus_set_position (CcDisplayMonitor *pself, + int x, int y) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + { + self->logical_monitor->x = x; + self->logical_monitor->y = y; + } +} + +static double +cc_display_monitor_dbus_get_scale (CcDisplayMonitor *pself) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (self->logical_monitor) + return self->logical_monitor->scale; + + return 1.0; +} + +static void +cc_display_monitor_dbus_set_scale (CcDisplayMonitor *pself, + double scale) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (pself); + + if (!cc_display_config_dbus_is_supported_scale (self->config, scale)) + return; + + if (!self->logical_monitor) + return; + + if (self->logical_monitor->scale != scale) + { + self->logical_monitor->scale = scale; + /* See comment in ensure_gapless() for why we disregard the + existing layout here. */ + cc_display_config_dbus_make_linear (self->config); + } +} + +static void +cc_display_monitor_dbus_init (CcDisplayMonitorDBus *self) +{ + self->underscanning = UNDERSCANNING_UNSUPPORTED; + self->max_width = G_MAXINT; + self->max_height = G_MAXINT; +} + +static void +cc_display_monitor_dbus_finalize (GObject *object) +{ + CcDisplayMonitorDBus *self = CC_DISPLAY_MONITOR_DBUS (object); + + g_free (self->connector_name); + g_free (self->vendor_name); + g_free (self->product_name); + g_free (self->product_serial); + g_free (self->display_name); + + g_list_foreach (self->modes, (GFunc) g_object_unref, NULL); + g_clear_pointer (&self->modes, g_list_free); + + if (self->logical_monitor) + { + g_hash_table_remove (self->logical_monitor->monitors, self); + g_object_unref (self->logical_monitor); + } + + G_OBJECT_CLASS (cc_display_monitor_dbus_parent_class)->finalize (object); +} + +static void +cc_display_monitor_dbus_class_init (CcDisplayMonitorDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayMonitorClass *parent_class = CC_DISPLAY_MONITOR_CLASS (klass); + + gobject_class->finalize = cc_display_monitor_dbus_finalize; + + parent_class->get_display_name = cc_display_monitor_dbus_get_display_name; + parent_class->get_connector_name = cc_display_monitor_dbus_get_connector_name; + parent_class->is_builtin = cc_display_monitor_dbus_is_builtin; + parent_class->is_primary = cc_display_monitor_dbus_is_primary; + parent_class->set_primary = cc_display_monitor_dbus_set_primary; + parent_class->is_active = cc_display_monitor_dbus_is_active; + parent_class->set_active = cc_display_monitor_dbus_set_active; + parent_class->get_rotation = cc_display_monitor_dbus_get_rotation; + parent_class->set_rotation = cc_display_monitor_dbus_set_rotation; + parent_class->supports_rotation = cc_display_monitor_dbus_supports_rotation; + parent_class->get_physical_size = cc_display_monitor_dbus_get_physical_size; + parent_class->get_geometry = cc_display_monitor_dbus_get_geometry; + parent_class->get_mode = cc_display_monitor_dbus_get_mode; + parent_class->get_preferred_mode = cc_display_monitor_dbus_get_preferred_mode; + parent_class->get_id = cc_display_monitor_dbus_get_id; + parent_class->get_modes = cc_display_monitor_dbus_get_modes; + parent_class->supports_underscanning = cc_display_monitor_dbus_supports_underscanning; + 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_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; +} + +static void +construct_modes (CcDisplayMonitorDBus *self, + GVariantIter *modes) +{ + CcDisplayModeDBus *mode; + GVariant *variant; + + while (g_variant_iter_next (modes, "@"MODE_FORMAT, &variant)) + { + mode = cc_display_mode_dbus_new (variant); + self->modes = g_list_prepend (self->modes, mode); + + if (mode->flags & MODE_PREFERRED) + self->preferred_mode = CC_DISPLAY_MODE (mode); + if (mode->flags & MODE_CURRENT) + self->current_mode = CC_DISPLAY_MODE (mode); + + g_variant_unref (variant); + } +} + +static CcDisplayMonitorDBus * +cc_display_monitor_dbus_new (GVariant *variant, + CcDisplayConfigDBus *config) +{ + CcDisplayMonitorDBus *self = g_object_new (CC_TYPE_DISPLAY_MONITOR_DBUS, NULL); + gchar *s1, *s2, *s3, *s4; + GVariantIter *modes; + GVariantIter *props; + const char *s; + GVariant *v; + + self->config = config; + + g_variant_get (variant, MONITOR_FORMAT, + &s1, &s2, &s3, &s4, &modes, &props); + self->connector_name = s1; + self->vendor_name = s2; + self->product_name = s3; + self->product_serial = s4; + + construct_modes (self, modes); + + while (g_variant_iter_next (props, "{&sv}", &s, &v)) + { + if (g_str_equal (s, "width-mm")) + { + g_variant_get (v, "i", &self->width_mm); + } + else if (g_str_equal (s, "height-mm")) + { + g_variant_get (v, "i", &self->height_mm); + } + else if (g_str_equal (s, "is-underscanning")) + { + gboolean underscanning = FALSE; + g_variant_get (v, "b", &underscanning); + if (underscanning) + self->underscanning = UNDERSCANNING_ENABLED; + else + self->underscanning = UNDERSCANNING_DISABLED; + } + else if (g_str_equal (s, "max-screen-size")) + { + g_variant_get (v, "ii", &self->max_width, &self->max_height); + } + else if (g_str_equal (s, "is-builtin")) + { + g_variant_get (v, "b", &self->builtin); + } + else if (g_str_equal (s, "display-name")) + { + g_variant_get (v, "s", &self->display_name); + } + + g_variant_unref (v); + } + + g_variant_iter_free (modes); + g_variant_iter_free (props); + + return self; +} + + +typedef enum _CcDisplayLayoutMode +{ + CC_DISPLAY_LAYOUT_MODE_LOGICAL = 1, + CC_DISPLAY_LAYOUT_MODE_PHYSICAL = 2 +} CcDisplayLayoutMode; + +typedef enum _CcDisplayConfigMethod +{ + CC_DISPLAY_CONFIG_METHOD_VERIFY = 0, + CC_DISPLAY_CONFIG_METHOD_TEMPORARY = 1, + CC_DISPLAY_CONFIG_METHOD_PERSISTENT = 2 +} CcDisplayConfigMethod; + +struct _CcDisplayConfigDBus +{ + CcDisplayConfig parent_instance; + + GVariant *state; + GDBusConnection *connection; + + guint32 serial; + gboolean supports_mirroring; + gboolean supports_changing_layout_mode; + CcDisplayLayoutMode layout_mode; + + GArray *supported_scales; + + GList *monitors; + CcDisplayMonitorDBus *primary; + + GHashTable *logical_monitors; + + GList *clone_modes; +}; + +G_DEFINE_TYPE (CcDisplayConfigDBus, + cc_display_config_dbus, + CC_TYPE_DISPLAY_CONFIG) + +enum +{ + PROP_0, + PROP_STATE, + PROP_CONNECTION, +}; + +static GList * +cc_display_config_dbus_get_monitors (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return self->monitors; +} + +static GVariant * +build_monitors_variant (GHashTable *monitors) +{ + GVariantBuilder builder; + GHashTableIter iter; + CcDisplayMonitorDBus *monitor; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_hash_table_iter_init (&iter, monitors); + + while (g_hash_table_iter_next (&iter, (void **) &monitor, NULL)) + { + int w, h; + GVariantBuilder props_builder; + + if (!monitor->current_mode) + continue; + + g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&props_builder, "{sv}", + "underscanning", + g_variant_new_boolean (monitor->underscanning == UNDERSCANNING_ENABLED)); + + cc_display_mode_get_resolution (monitor->current_mode, &w, &h); + g_variant_builder_add (&builder, "(s(iid)@*)", + monitor->connector_name, + w, h, + cc_display_mode_get_freq_f (monitor->current_mode), + g_variant_builder_end (&props_builder)); + } + + return g_variant_builder_end (&builder); +} + +static GVariant * +build_logical_monitors_parameter (CcDisplayConfigDBus *self) +{ + GVariantBuilder builder; + GHashTableIter iter; + CcDisplayLogicalMonitor *logical_monitor; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_hash_table_iter_init (&iter, self->logical_monitors); + + while (g_hash_table_iter_next (&iter, (void **) &logical_monitor, NULL)) + g_variant_builder_add (&builder, "(iidub@*)", + logical_monitor->x, + logical_monitor->y, + logical_monitor->scale, + logical_monitor->rotation, + logical_monitor->primary, + build_monitors_variant (logical_monitor->monitors)); + + return g_variant_builder_end (&builder); +} + +static GVariant * +build_apply_parameters (CcDisplayConfigDBus *self, + CcDisplayConfigMethod method) +{ + GVariantBuilder props_builder; + g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}")); + + if (self->supports_changing_layout_mode) + g_variant_builder_add (&props_builder, "{sv}", + "layout-mode", g_variant_new_uint32 (self->layout_mode)); + + return g_variant_new ("(uu@*@*)", + self->serial, + method, + build_logical_monitors_parameter (self), + g_variant_builder_end (&props_builder)); +} + +static gboolean +config_apply (CcDisplayConfigDBus *self, + CcDisplayConfigMethod method, + GError **error) +{ + GVariant *retval; + + cc_display_config_dbus_ensure_non_offset_coords (self); + + retval = g_dbus_connection_call_sync (self->connection, + "org.gnome.Mutter.DisplayConfig", + "/org/gnome/Mutter/DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + "ApplyMonitorsConfig", + build_apply_parameters (self, method), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + error); + if (!retval) + return FALSE; + + g_variant_unref (retval); + return TRUE; +} + +static gboolean +cc_display_config_dbus_is_applicable (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + GError *error = NULL; + + if (!config_apply (self, CC_DISPLAY_CONFIG_METHOD_VERIFY, &error)) + { + g_warning ("Config not applicable: %s", error->message); + g_error_free (error); + return FALSE; + } + else + { + return TRUE; + } +} + +static CcDisplayMonitorDBus * +monitor_from_spec (CcDisplayConfigDBus *self, + const gchar *connector, + const gchar *vendor, + const gchar *product, + const gchar *serial) +{ + GList *l; + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitorDBus *m = l->data; + if (g_str_equal (m->connector_name, connector) && + g_str_equal (m->vendor_name, vendor) && + g_str_equal (m->product_name, product) && + g_str_equal (m->product_serial, serial)) + return m; + } + return NULL; +} + +static gboolean +cc_display_config_dbus_equal (CcDisplayConfig *pself, + CcDisplayConfig *pother) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + CcDisplayConfigDBus *other = CC_DISPLAY_CONFIG_DBUS (pother); + GList *l; + + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitorDBus *m1 = l->data; + CcDisplayMonitorDBus *m2 = monitor_from_spec (other, + m1->connector_name, + m1->vendor_name, + m1->product_name, + m1->product_serial); + if (!m2) + return FALSE; + + if (m1->underscanning != m2->underscanning) + return FALSE; + + if (!cc_display_mode_dbus_equal (CC_DISPLAY_MODE_DBUS (m1->current_mode), + CC_DISPLAY_MODE_DBUS (m2->current_mode))) + return FALSE; + + if (!cc_display_logical_monitor_equal (m1->logical_monitor, m2->logical_monitor)) + return FALSE; + } + + return TRUE; +} + +static void +cc_display_config_dbus_set_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *new_primary) +{ + if (self->primary == new_primary) + return; + + if (!new_primary->logical_monitor) + return; + + if (self->primary && self->primary->logical_monitor) + self->primary->logical_monitor->primary = FALSE; + + self->primary = new_primary; + self->primary->logical_monitor->primary = TRUE; +} + +static void +cc_display_config_dbus_unset_primary (CcDisplayConfigDBus *self, + CcDisplayMonitorDBus *old_primary) +{ + GList *l; + + if (self->primary != old_primary) + return; + + for (l = self->monitors; l != NULL; l = l->next) + { + CcDisplayMonitorDBus *monitor = l->data; + if (monitor->logical_monitor && + monitor != old_primary) + { + cc_display_config_dbus_set_primary (self, monitor); + break; + } + } + + if (self->primary == old_primary) + self->primary = NULL; +} + +static gboolean +cc_display_config_dbus_is_cloning (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + guint n_active_monitors = 0; + GList *l; + + for (l = self->monitors; l != NULL; l = l->next) + if (cc_display_monitor_is_active (CC_DISPLAY_MONITOR (l->data))) + n_active_monitors += 1; + + return n_active_monitors > 1 && g_hash_table_size (self->logical_monitors) == 1; +} + +static void +cc_display_config_dbus_set_cloning (CcDisplayConfig *pself, + gboolean clone) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + gboolean is_cloning = cc_display_config_is_cloning (pself); + CcDisplayLogicalMonitor *logical_monitor; + GList *l; + + if (clone && !is_cloning) + { + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + for (l = self->monitors; l != NULL; l = l->next) + cc_display_monitor_dbus_set_logical_monitor (CC_DISPLAY_MONITOR_DBUS (l->data), + logical_monitor); + register_logical_monitor (self, logical_monitor); + } + else if (!clone && is_cloning) + { + for (l = self->monitors; l != NULL; l = l->next) + { + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + cc_display_monitor_dbus_set_logical_monitor (CC_DISPLAY_MONITOR_DBUS (l->data), + logical_monitor); + register_logical_monitor (self, logical_monitor); + } + cc_display_config_dbus_make_linear (self); + } +} + +static GList * +cc_display_config_dbus_get_cloning_modes (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return self->clone_modes; +} + +static gboolean +cc_display_config_dbus_apply (CcDisplayConfig *pself, + GError **error) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return config_apply (self, CC_DISPLAY_CONFIG_METHOD_PERSISTENT, error); +} + +static const double * +cc_display_config_dbus_get_supported_scales (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return (const double *) self->supported_scales->data; +} + +static gboolean +cc_display_config_dbus_is_layout_logical (CcDisplayConfig *pself) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (pself); + + return self->layout_mode == CC_DISPLAY_LAYOUT_MODE_LOGICAL; +} + +static void +cc_display_config_dbus_init (CcDisplayConfigDBus *self) +{ + self->serial = 0; + self->supports_mirroring = TRUE; + self->supports_changing_layout_mode = FALSE; + self->layout_mode = CC_DISPLAY_LAYOUT_MODE_LOGICAL; + self->supported_scales = g_array_new (TRUE, TRUE, sizeof (double)); + 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) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (data); + + g_hash_table_remove (self->logical_monitors, object); +} + +static void +register_logical_monitor (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *logical_monitor) +{ + g_hash_table_add (self->logical_monitors, logical_monitor); + g_object_weak_ref (G_OBJECT (logical_monitor), remove_logical_monitor, self); + g_object_unref (logical_monitor); +} + +static void +construct_monitors (CcDisplayConfigDBus *self, + GVariantIter *monitors, + GVariantIter *logical_monitors) +{ + GVariant *variant; + + while (g_variant_iter_next (monitors, "@"MONITOR_FORMAT, &variant)) + { + CcDisplayMonitorDBus *monitor; + + monitor = cc_display_monitor_dbus_new (variant, self); + self->monitors = g_list_prepend (self->monitors, monitor); + + g_variant_unref (variant); + } + + while (g_variant_iter_next (logical_monitors, "@"LOGICAL_MONITOR_FORMAT, &variant)) + { + CcDisplayLogicalMonitor *logical_monitor; + GVariantIter *monitor_specs; + const gchar *s1, *s2, *s3, *s4; + + logical_monitor = g_object_new (CC_TYPE_DISPLAY_LOGICAL_MONITOR, NULL); + g_variant_get (variant, LOGICAL_MONITOR_FORMAT, + &logical_monitor->x, + &logical_monitor->y, + &logical_monitor->scale, + &logical_monitor->rotation, + &logical_monitor->primary, + &monitor_specs, + NULL); + + while (g_variant_iter_next (monitor_specs, "(&s&s&s&s)", &s1, &s2, &s3, &s4)) + { + CcDisplayMonitorDBus *m = monitor_from_spec (self, s1, s2, s3, s4); + if (!m) + { + g_warning ("Couldn't find monitor given spec: %s, %s, %s, %s", + s1, s2, s3, s4); + continue; + } + + cc_display_monitor_dbus_set_logical_monitor (m, logical_monitor); + } + + if (g_hash_table_size (logical_monitor->monitors) > 0) + { + if (logical_monitor->primary) + { + GHashTableIter iter; + g_hash_table_iter_init (&iter, logical_monitor->monitors); + g_hash_table_iter_next (&iter, (void **) &self->primary, NULL); + } + } + else + { + g_warning ("Got an empty logical monitor, ignoring"); + } + + register_logical_monitor (self, logical_monitor); + + g_variant_iter_free (monitor_specs); + g_variant_unref (variant); + } + + gather_clone_modes (self); +} + +static void +cc_display_config_dbus_constructed (GObject *object) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + GVariantIter *monitors; + GVariantIter *logical_monitors; + GVariantIter *scales; + GVariantIter *props; + double d; + const char *s; + GVariant *v; + + g_variant_get (self->state, + CURRENT_STATE_FORMAT, + &self->serial, + &monitors, + &logical_monitors, + &scales, + &props); + + while (g_variant_iter_next (scales, "d", &d)) + g_array_append_val (self->supported_scales, d); + + while (g_variant_iter_next (props, "{&sv}", &s, &v)) + { + if (g_str_equal (s, "supports-mirroring")) + { + g_variant_get (v, "b", &self->supports_mirroring); + } + else if (g_str_equal (s, "supports-changing-layout-mode")) + { + g_variant_get (v, "b", &self->supports_changing_layout_mode); + } + else if (g_str_equal (s, "layout-mode")) + { + guint32 u = 0; + g_variant_get (v, "u", &u); + if (u >= CC_DISPLAY_LAYOUT_MODE_LOGICAL && + u <= CC_DISPLAY_LAYOUT_MODE_PHYSICAL) + self->layout_mode = u; + } + + g_variant_unref (v); + } + + construct_monitors (self, monitors, logical_monitors); + + g_variant_iter_free (monitors); + g_variant_iter_free (logical_monitors); + g_variant_iter_free (scales); + g_variant_iter_free (props); + + G_OBJECT_CLASS (cc_display_config_dbus_parent_class)->constructed (object); +} + +static void +cc_display_config_dbus_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + + switch (prop_id) + { + case PROP_STATE: + self->state = g_value_dup_variant (value); + break; + case PROP_CONNECTION: + self->connection = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_config_dbus_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + + switch (prop_id) + { + case PROP_STATE: + g_value_set_variant (value, self->state); + break; + case PROP_CONNECTION: + g_value_set_object (value, self->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_config_dbus_finalize (GObject *object) +{ + CcDisplayConfigDBus *self = CC_DISPLAY_CONFIG_DBUS (object); + + g_clear_pointer (&self->state, g_variant_unref); + g_clear_object (&self->connection); + + g_array_free (self->supported_scales, TRUE); + g_list_foreach (self->monitors, (GFunc) g_object_unref, NULL); + g_clear_pointer (&self->monitors, g_list_free); + 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); +} + +static void +cc_display_config_dbus_class_init (CcDisplayConfigDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayConfigClass *parent_class = CC_DISPLAY_CONFIG_CLASS (klass); + GParamSpec *pspec; + + gobject_class->constructed = cc_display_config_dbus_constructed; + gobject_class->set_property = cc_display_config_dbus_set_property; + gobject_class->get_property = cc_display_config_dbus_get_property; + gobject_class->finalize = cc_display_config_dbus_finalize; + + parent_class->get_monitors = cc_display_config_dbus_get_monitors; + parent_class->is_applicable = cc_display_config_dbus_is_applicable; + parent_class->equal = cc_display_config_dbus_equal; + 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->get_supported_scales = cc_display_config_dbus_get_supported_scales; + parent_class->is_layout_logical = cc_display_config_dbus_is_layout_logical; + + pspec = g_param_spec_variant ("state", + "GVariant", + "GVariant", + G_VARIANT_TYPE (CURRENT_STATE_FORMAT), + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (gobject_class, PROP_STATE, pspec); + + pspec = g_param_spec_object ("connection", + "GDBusConnection", + "GDBusConnection", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (gobject_class, PROP_CONNECTION, pspec); +} + +static gint +sort_x_axis (gconstpointer a, gconstpointer b) +{ + const CcDisplayLogicalMonitor *ma = a; + const CcDisplayLogicalMonitor *mb = b; + return ma->x - mb->x; +} + +static gint +sort_y_axis (gconstpointer a, gconstpointer b) +{ + const CcDisplayLogicalMonitor *ma = a; + const CcDisplayLogicalMonitor *mb = b; + return ma->y - mb->y; +} + +static void +add_x_delta (gpointer d1, gpointer d2) +{ + CcDisplayLogicalMonitor *m = d1; + int delta = GPOINTER_TO_INT (d2); + m->x += delta; +} + +static gboolean +logical_monitor_is_rotated (CcDisplayLogicalMonitor *lm) +{ + switch (lm->rotation) + { + case CC_DISPLAY_ROTATION_90: + case CC_DISPLAY_ROTATION_270: + case CC_DISPLAY_ROTATION_90_FLIPPED: + case CC_DISPLAY_ROTATION_270_FLIPPED: + return TRUE; + default: + return FALSE; + } +} + +static int +logical_monitor_width (CcDisplayLogicalMonitor *lm) +{ + CcDisplayMonitorDBus *monitor; + CcDisplayModeDBus *mode; + GHashTableIter iter; + int width; + + g_hash_table_iter_init (&iter, lm->monitors); + g_hash_table_iter_next (&iter, (void **) &monitor, NULL); + mode = CC_DISPLAY_MODE_DBUS (monitor->current_mode); + if (logical_monitor_is_rotated (lm)) + width = mode ? mode->height : 0; + else + width = mode ? mode->width : 0; + + if (monitor->config->layout_mode == CC_DISPLAY_LAYOUT_MODE_LOGICAL) + return width / lm->scale; + else + return width; +} + +static void +add_y_delta (gpointer d1, gpointer d2) +{ + CcDisplayLogicalMonitor *m = d1; + int delta = GPOINTER_TO_INT (d2); + m->y += delta; +} + +static int +logical_monitor_height (CcDisplayLogicalMonitor *lm) +{ + CcDisplayMonitorDBus *monitor; + CcDisplayModeDBus *mode; + GHashTableIter iter; + int height; + + g_hash_table_iter_init (&iter, lm->monitors); + g_hash_table_iter_next (&iter, (void **) &monitor, NULL); + mode = CC_DISPLAY_MODE_DBUS (monitor->current_mode); + if (logical_monitor_is_rotated (lm)) + height = mode ? mode->width : 0; + else + height = mode ? mode->height : 0; + + if (monitor->config->layout_mode == CC_DISPLAY_LAYOUT_MODE_LOGICAL) + return height / lm->scale; + else + return height; +} + +static void +cc_display_config_dbus_ensure_non_offset_coords (CcDisplayConfigDBus *self) +{ + GList *x_axis, *y_axis; + CcDisplayLogicalMonitor *m; + + if (g_hash_table_size (self->logical_monitors) == 0) + return; + + x_axis = g_hash_table_get_keys (self->logical_monitors); + x_axis = g_list_sort (x_axis, sort_x_axis); + y_axis = g_hash_table_get_keys (self->logical_monitors); + y_axis = g_list_sort (y_axis, sort_y_axis); + + m = x_axis->data; + if (m->x != 0) + g_list_foreach (x_axis, add_x_delta, GINT_TO_POINTER (- m->x)); + + m = y_axis->data; + if (m->y != 0) + g_list_foreach (y_axis, add_y_delta, GINT_TO_POINTER (- m->y)); + + g_list_free (x_axis); + g_list_free (y_axis); +} + +static void +cc_display_config_dbus_append_right (CcDisplayConfigDBus *self, + CcDisplayLogicalMonitor *monitor) +{ + GList *x_axis; + CcDisplayLogicalMonitor *last; + + if (g_hash_table_size (self->logical_monitors) == 0) + { + monitor->x = 0; + monitor->y = 0; + return; + } + + x_axis = g_hash_table_get_keys (self->logical_monitors); + x_axis = g_list_sort (x_axis, sort_x_axis); + last = g_list_last (x_axis)->data; + monitor->x = last->x + logical_monitor_width (last); + monitor->y = last->y; + + g_list_free (x_axis); +} + +static void +cc_display_config_dbus_ensure_gapless (CcDisplayConfigDBus *self) +{ + GList *x_axis, *y_axis, *l; + + if (g_hash_table_size (self->logical_monitors) == 0) + return; + + x_axis = g_hash_table_get_keys (self->logical_monitors); + x_axis = g_list_sort (x_axis, sort_x_axis); + y_axis = g_hash_table_get_keys (self->logical_monitors); + y_axis = g_list_sort (y_axis, sort_y_axis); + + /* This might produce overlaps which will fail validation. + Unfortunately, automating this to avoid gaps and overlaps with + arbitrary rectangles is a hard problem and we'd probably not find + the layout the user wants anyway. We'll need a panel re-design + that allows manual layout adjustments after monitor operations + and before applying. */ + for (l = x_axis; l != NULL && l->next != NULL; l = l->next) + { + CcDisplayLogicalMonitor *m = l->data; + CcDisplayLogicalMonitor *n = l->next->data; + int mx2 = m->x + logical_monitor_width (m); + + if (n->x > mx2) + n->x = mx2; + } + + for (l = y_axis; l != NULL && l->next != NULL; l = l->next) + { + CcDisplayLogicalMonitor *m = l->data; + CcDisplayLogicalMonitor *n = l->next->data; + int my2 = m->y + logical_monitor_height (m); + + if (n->y > my2) + n->y = my2; + } + + g_list_free (x_axis); + g_list_free (y_axis); +} + +static void +cc_display_config_dbus_make_linear (CcDisplayConfigDBus *self) +{ + CcDisplayLogicalMonitor *primary; + GList *logical_monitors, *l; + int x; + + if (self->primary && self->primary->logical_monitor) + { + primary = self->primary->logical_monitor; + primary->x = primary->y = 0; + x = logical_monitor_width (primary); + } + else + { + primary = NULL; + x = 0; + } + + logical_monitors = g_hash_table_get_keys (self->logical_monitors); + for (l = logical_monitors; l != NULL; l = l->next) + { + CcDisplayLogicalMonitor *m = l->data; + + if (m == primary) + continue; + + m->x = x; + m->y = 0; + x += logical_monitor_width (m); + } + + g_list_free (logical_monitors); +} + +static gboolean +cc_display_config_dbus_is_supported_scale (CcDisplayConfigDBus *self, + double scale) +{ + guint i; + for (i = 0; i < self->supported_scales->len; i++) + if (g_array_index (self->supported_scales, double, i) == scale) + return TRUE; + return FALSE; +} diff --git a/panels/display/cc-display-config-dbus.h b/panels/display/cc-display-config-dbus.h new file mode 100644 index 000000000..9809652b4 --- /dev/null +++ b/panels/display/cc-display-config-dbus.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef _CC_DISPLAY_CONFIG_DBUS_H +#define _CC_DISPLAY_CONFIG_DBUS_H + +#include + +#include "cc-display-config.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_MODE_DBUS (cc_display_mode_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayModeDBus, cc_display_mode_dbus, + CC, DISPLAY_MODE_DBUS, CcDisplayMode) + +#define CC_TYPE_DISPLAY_MONITOR_DBUS (cc_display_monitor_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayMonitorDBus, cc_display_monitor_dbus, + CC, DISPLAY_MONITOR_DBUS, CcDisplayMonitor) + +#define CC_TYPE_DISPLAY_CONFIG_DBUS (cc_display_config_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayConfigDBus, cc_display_config_dbus, + CC, DISPLAY_CONFIG_DBUS, CcDisplayConfig) + +G_END_DECLS + +#endif /* _CC_DISPLAY_CONFIG_DBUS_H */ diff --git a/panels/display/cc-display-config-manager-dbus.c b/panels/display/cc-display-config-manager-dbus.c new file mode 100644 index 000000000..3ec65aab8 --- /dev/null +++ b/panels/display/cc-display-config-manager-dbus.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "cc-display-config-dbus.h" +#include "cc-display-config-manager-dbus.h" + +#include + +struct _CcDisplayConfigManagerDBus +{ + CcDisplayConfigManager parent_instance; + + GCancellable *cancellable; + GDBusConnection *connection; + guint monitors_changed_id; + + GVariant *current_state; +}; + +G_DEFINE_TYPE (CcDisplayConfigManagerDBus, + cc_display_config_manager_dbus, + CC_TYPE_DISPLAY_CONFIG_MANAGER) + +static CcDisplayConfig * +cc_display_config_manager_dbus_get_current (CcDisplayConfigManager *pself) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (pself); + + if (!self->current_state) + return NULL; + + return g_object_new (CC_TYPE_DISPLAY_CONFIG_DBUS, + "state", self->current_state, + "connection", self->connection, NULL); +} + +static void +got_current_state (GObject *object, + GAsyncResult *result, + gpointer data) +{ + CcDisplayConfigManagerDBus *self; + GVariant *variant; + GError *error = NULL; + + variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), + result, &error); + if (!variant) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + _cc_display_config_manager_emit_changed (CC_DISPLAY_CONFIG_MANAGER (data)); + g_warning ("Error calling GetCurrentState: %s", error->message); + } + g_clear_error (&error); + return; + } + + self = CC_DISPLAY_CONFIG_MANAGER_DBUS (data); + g_clear_pointer (&self->current_state, g_variant_unref); + self->current_state = variant; + + _cc_display_config_manager_emit_changed (CC_DISPLAY_CONFIG_MANAGER (self)); +} + +static void +get_current_state (CcDisplayConfigManagerDBus *self) +{ + g_dbus_connection_call (self->connection, + "org.gnome.Mutter.DisplayConfig", + "/org/gnome/Mutter/DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + "GetCurrentState", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + self->cancellable, + got_current_state, + self); +} + +static void +monitors_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer data) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (data); + get_current_state (self); +} + +static void +bus_gotten (GObject *object, + GAsyncResult *result, + gpointer data) +{ + CcDisplayConfigManagerDBus *self; + GDBusConnection *connection; + GError *error = NULL; + + connection = g_bus_get_finish (result, &error); + if (!connection) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + _cc_display_config_manager_emit_changed (CC_DISPLAY_CONFIG_MANAGER (data)); + g_warning ("Error obtaining DBus connection: %s", error->message); + } + g_clear_error (&error); + return; + } + + self = CC_DISPLAY_CONFIG_MANAGER_DBUS (data); + self->connection = connection; + self->monitors_changed_id = + g_dbus_connection_signal_subscribe (self->connection, + "org.gnome.Mutter.DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + "MonitorsChanged", + "/org/gnome/Mutter/DisplayConfig", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + monitors_changed, + self, + NULL); + get_current_state (self); +} + +static void +cc_display_config_manager_dbus_init (CcDisplayConfigManagerDBus *self) +{ + self->cancellable = g_cancellable_new (); + g_bus_get (G_BUS_TYPE_SESSION, self->cancellable, bus_gotten, self); +} + +static void +cc_display_config_manager_dbus_finalize (GObject *object) +{ + CcDisplayConfigManagerDBus *self = CC_DISPLAY_CONFIG_MANAGER_DBUS (object); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + if (self->monitors_changed_id && self->connection) + g_dbus_connection_signal_unsubscribe (self->connection, + self->monitors_changed_id); + g_clear_object (&self->connection); + g_clear_pointer (&self->current_state, g_variant_unref); + + G_OBJECT_CLASS (cc_display_config_manager_dbus_parent_class)->finalize (object); +} + +static void +cc_display_config_manager_dbus_class_init (CcDisplayConfigManagerDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcDisplayConfigManagerClass *parent_class = CC_DISPLAY_CONFIG_MANAGER_CLASS (klass); + + gobject_class->finalize = cc_display_config_manager_dbus_finalize; + + parent_class->get_current = cc_display_config_manager_dbus_get_current; +} + +CcDisplayConfigManager * +cc_display_config_manager_dbus_new (void) +{ + return g_object_new (CC_TYPE_DISPLAY_CONFIG_MANAGER_DBUS, NULL); +} diff --git a/panels/display/cc-display-config-manager-dbus.h b/panels/display/cc-display-config-manager-dbus.h new file mode 100644 index 000000000..a9b85e6be --- /dev/null +++ b/panels/display/cc-display-config-manager-dbus.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef _CC_DISPLAY_CONFIG_MANAGER_DBUS_H +#define _CC_DISPLAY_CONFIG_MANAGER_DBUS_H + +#include + +#include "cc-display-config-manager.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_CONFIG_MANAGER_DBUS (cc_display_config_manager_dbus_get_type ()) +G_DECLARE_FINAL_TYPE (CcDisplayConfigManagerDBus, cc_display_config_manager_dbus, + CC, DISPLAY_CONFIG_MANAGER_DBUS, CcDisplayConfigManager) + +CcDisplayConfigManager * cc_display_config_manager_dbus_new (void); + +G_END_DECLS + +#endif /* _CC_DISPLAY_CONFIG_MANAGER_DBUS_H */