/* * Copyright (C) 2007, 2008 Red Hat, Inc. * Copyright (C) 2013 Intel, 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-panel.h" #include #include #include #include #include #include #include "shell/cc-object-storage.h" #include "list-box-helper.h" #include #include "cc-display-config-manager-dbus.h" #include "cc-display-config.h" #include "cc-display-arrangement.h" #include "cc-night-light-page.h" #include "cc-display-resources.h" #include "cc-display-settings.h" /* The minimum supported size for the panel * Note that WIDTH is assumed to be the larger size and we accept portrait * mode too effectively (in principle we should probably restrict the rotation * setting in that case). */ #define MINIMUM_WIDTH 740 #define MINIMUM_HEIGHT 530 #define PANEL_PADDING 32 #define SECTION_PADDING 32 #define HEADING_PADDING 12 typedef enum { CC_DISPLAY_CONFIG_SINGLE, CC_DISPLAY_CONFIG_JOIN, CC_DISPLAY_CONFIG_CLONE, CC_DISPLAY_CONFIG_INVALID_NONE, } CcDisplayConfigType; #define CC_DISPLAY_CONFIG_LAST_VALID CC_DISPLAY_CONFIG_CLONE struct _CcDisplayPanel { CcPanel parent_instance; CcDisplayConfigManager *manager; CcDisplayConfig *current_config; CcDisplayMonitor *current_output; gint rebuilding_counter; CcDisplayArrangement *arrangement; CcDisplaySettings *settings; guint focus_id; CcNightLightPage *night_light_page; GtkDialog *night_light_dialog; UpClient *up_client; gboolean lid_is_closed; GDBusProxy *shell_proxy; gchar *main_title; GtkWidget *main_titlebar; GtkWidget *apply_titlebar; GtkWidget *apply_titlebar_apply; GtkWidget *apply_titlebar_warning; GListStore *primary_display_list; GtkListStore *output_selection_list; GtkWidget *arrangement_frame; GtkAlignment *arrangement_bin; GtkRadioButton *config_type_join; GtkRadioButton *config_type_mirror; GtkRadioButton *config_type_single; GtkWidget *config_type_switcher_frame; GtkLabel *current_output_label; GtkWidget *display_settings_frame; GtkBox *multi_selection_box; GtkSwitch *output_enabled_switch; GtkComboBox *output_selection_combo; GtkStack *output_selection_stack; GtkButtonBox *output_selection_two_buttonbox; GtkRadioButton *output_selection_two_first; GtkRadioButton *output_selection_two_second; HdyComboRow *primary_display_row; GtkWidget *stack_switcher; }; CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel) static void update_apply_button (CcDisplayPanel *panel); static void apply_current_configuration (CcDisplayPanel *self); static void reset_current_config (CcDisplayPanel *panel); static void rebuild_ui (CcDisplayPanel *panel); static void set_current_output (CcDisplayPanel *panel, CcDisplayMonitor *output, gboolean force); static CcDisplayConfigType config_get_current_type (CcDisplayPanel *panel) { guint n_active_outputs; GList *outputs, *l; outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); n_active_outputs = 0; for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; if (cc_display_monitor_is_useful (output)) n_active_outputs += 1; } if (n_active_outputs == 0) return CC_DISPLAY_CONFIG_INVALID_NONE; if (n_active_outputs == 1) return CC_DISPLAY_CONFIG_SINGLE; if (cc_display_config_is_cloning (panel->current_config)) return CC_DISPLAY_CONFIG_CLONE; return CC_DISPLAY_CONFIG_JOIN; } static CcDisplayConfigType cc_panel_get_selected_type (CcDisplayPanel *panel) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_join))) return CC_DISPLAY_CONFIG_JOIN; else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror))) return CC_DISPLAY_CONFIG_CLONE; else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_single))) return CC_DISPLAY_CONFIG_SINGLE; else g_assert_not_reached (); } static void config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type) { CcDisplayConfigType current_type = config_get_current_type (panel); GList *outputs, *l; /* Do not do anything if the current detected configuration type is * identitcal to what we expect. */ if (type == current_type) return; reset_current_config (panel); outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); switch (type) { case CC_DISPLAY_CONFIG_SINGLE: g_debug ("Creating new single config"); /* Disable all but the current primary output */ cc_display_config_set_cloning (panel->current_config, FALSE); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; /* Select the current primary output as the active one */ if (cc_display_monitor_is_primary (output)) { cc_display_monitor_set_active (output, TRUE); cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output)); set_current_output (panel, output, FALSE); } else { cc_display_monitor_set_active (output, FALSE); cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output)); } } break; case CC_DISPLAY_CONFIG_JOIN: g_debug ("Creating new join config"); /* Enable all usable outputs * Note that this might result in invalid configurations as we * we might not be able to drive all attached monitors. */ cc_display_config_set_cloning (panel->current_config, FALSE); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; cc_display_monitor_set_active (output, cc_display_monitor_is_usable (output)); cc_display_monitor_set_mode (output, cc_display_monitor_get_preferred_mode (output)); } break; case CC_DISPLAY_CONFIG_CLONE: { g_debug ("Creating new clone config"); GList *modes = cc_display_config_get_cloning_modes (panel->current_config); gint bw, bh; CcDisplayMode *best = NULL; /* 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; } cc_display_config_set_mode_on_all_outputs (panel->current_config, best); } break; default: g_assert_not_reached (); } rebuild_ui (panel); } static void cc_panel_set_selected_type (CcDisplayPanel *panel, CcDisplayConfigType type) { switch (type) { case CC_DISPLAY_CONFIG_JOIN: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_join), TRUE); break; case CC_DISPLAY_CONFIG_CLONE: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror), TRUE); break; case CC_DISPLAY_CONFIG_SINGLE: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_single), TRUE); break; default: g_assert_not_reached (); } config_ensure_of_type (panel, type); } static void monitor_labeler_hide (CcDisplayPanel *self) { if (!self->shell_proxy) return; g_dbus_proxy_call (self->shell_proxy, "HideMonitorLabels", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } static void monitor_labeler_show (CcDisplayPanel *self) { GList *outputs, *l; GVariantBuilder builder; gint number = 0; if (!self->shell_proxy || !self->current_config) return; outputs = cc_display_config_get_ui_sorted_monitors (self->current_config); if (!outputs) return; if (cc_display_config_is_cloning (self->current_config)) return monitor_labeler_hide (self); g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY); for (l = outputs; l != NULL; l = l->next) { CcDisplayMonitor *output = l->data; number = cc_display_monitor_get_ui_number (output); if (number == 0) continue; g_variant_builder_add (&builder, "{sv}", cc_display_monitor_get_connector_name (output), g_variant_new_int32 (number)); } g_variant_builder_close (&builder); if (number < 2) return monitor_labeler_hide (self); g_dbus_proxy_call (self->shell_proxy, "ShowMonitorLabels", g_variant_builder_end (&builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } static void ensure_monitor_labels (CcDisplayPanel *self) { g_autoptr(GList) windows = NULL; GList *w; windows = gtk_window_list_toplevels (); for (w = windows; w; w = w->next) { if (gtk_window_has_toplevel_focus (GTK_WINDOW (w->data))) { monitor_labeler_show (self); break; } } if (!w) monitor_labeler_hide (self); } static void dialog_toplevel_focus_changed (CcDisplayPanel *self) { ensure_monitor_labels (self); } static void reset_titlebar (CcDisplayPanel *self) { GtkWidget *toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self))); if (self->main_titlebar) { gtk_window_set_titlebar (GTK_WINDOW (toplevel), self->main_titlebar); g_clear_object (&self->main_titlebar); /* The split header bar will not reset the window title, so do that here. */ gtk_window_set_title (GTK_WINDOW (toplevel), self->main_title); g_clear_pointer (&self->main_title, g_free); } g_clear_object (&self->apply_titlebar); g_clear_object (&self->apply_titlebar_apply); g_clear_object (&self->apply_titlebar_warning); } static void active_panel_changed (CcPanel *self) { CcShell *shell; g_autoptr(CcPanel) panel = NULL; shell = cc_panel_get_shell (CC_PANEL (self)); g_object_get (shell, "active-panel", &panel, NULL); if (panel != self) reset_titlebar (CC_DISPLAY_PANEL (self)); } static void cc_display_panel_dispose (GObject *object) { CcDisplayPanel *self = CC_DISPLAY_PANEL (object); reset_titlebar (CC_DISPLAY_PANEL (object)); if (self->focus_id) { self->focus_id = 0; monitor_labeler_hide (CC_DISPLAY_PANEL (object)); } g_clear_object (&self->manager); g_clear_object (&self->current_config); g_clear_object (&self->up_client); g_clear_object (&self->shell_proxy); g_clear_pointer ((GtkWidget **) &self->night_light_dialog, gtk_widget_destroy); G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object); } static void on_arrangement_selected_ouptut_changed_cb (CcDisplayPanel *panel) { set_current_output (panel, cc_display_arrangement_get_selected_output (panel->arrangement), FALSE); } static void on_monitor_settings_updated_cb (CcDisplayPanel *panel, CcDisplayMonitor *monitor, CcDisplaySettings *settings) { if (monitor) cc_display_config_snap_output (panel->current_config, monitor); update_apply_button (panel); } static void on_config_type_toggled_cb (CcDisplayPanel *panel, GtkRadioButton *btn) { CcDisplayConfigType type; if (panel->rebuilding_counter > 0) return; if (!panel->current_config) return; if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn))) return; type = cc_panel_get_selected_type (panel); config_ensure_of_type (panel, type); } static void on_night_light_list_box_row_activated_cb (CcDisplayPanel *panel) { GtkWindow *toplevel; toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))); if (!panel->night_light_dialog) { GtkWidget *content_area; panel->night_light_dialog = (GtkDialog *)gtk_dialog_new (); content_area = gtk_dialog_get_content_area (panel->night_light_dialog); gtk_container_add (GTK_CONTAINER (content_area), GTK_WIDGET (panel->night_light_page)); gtk_widget_show (GTK_WIDGET (panel->night_light_page)); } gtk_window_set_transient_for (GTK_WINDOW (panel->night_light_dialog), toplevel); gtk_window_present (GTK_WINDOW (panel->night_light_dialog)); } static void on_output_enabled_active_changed_cb (CcDisplayPanel *panel) { gboolean active; if (!panel->current_output) return; active = gtk_switch_get_active (panel->output_enabled_switch); if (cc_display_monitor_is_active (panel->current_output) == active) return; cc_display_monitor_set_active (panel->current_output, active); /* Prevent the invalid configuration of disabling the last monitor * by switching on a different one. */ if (config_get_current_type (panel) == CC_DISPLAY_CONFIG_INVALID_NONE) { GList *outputs, *l; outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = CC_DISPLAY_MONITOR (l->data); if (output == panel->current_output) continue; if (!cc_display_monitor_is_usable (output)) continue; cc_display_monitor_set_active (output, TRUE); cc_display_monitor_set_primary (output, TRUE); break; } } /* Changing the active state requires a UI rebuild. */ rebuild_ui (panel); } static void on_output_selection_combo_changed_cb (CcDisplayPanel *panel) { GtkTreeIter iter; g_autoptr(CcDisplayMonitor) output = NULL; if (!panel->current_config) return; if (!gtk_combo_box_get_active_iter (panel->output_selection_combo, &iter)) return; gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter, 1, &output, -1); set_current_output (panel, output, FALSE); } static void on_output_selection_two_toggled_cb (CcDisplayPanel *panel, GtkRadioButton *btn) { CcDisplayMonitor *output; if (panel->rebuilding_counter > 0) return; if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn))) return; output = g_object_get_data (G_OBJECT (btn), "display"); /* Stay in single mode when we are in single mode. * This UI must never cause a switch between the configuration type. * this is in contrast to the combobox monitor selection, which may * switch to a disabled output both in SINGLE/MULTI mode without * anything changing. */ if (cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_SINGLE) { if (panel->current_output) cc_display_monitor_set_active (panel->current_output, FALSE); if (output) cc_display_monitor_set_active (output, TRUE); update_apply_button (panel); } set_current_output (panel, g_object_get_data (G_OBJECT (btn), "display"), FALSE); } static void on_primary_display_selected_index_changed_cb (CcDisplayPanel *panel) { gint idx = hdy_combo_row_get_selected_index (panel->primary_display_row); g_autoptr(CcDisplayMonitor) output = NULL; if (idx < 0 || panel->rebuilding_counter > 0) return; output = g_list_model_get_item (G_LIST_MODEL (panel->primary_display_list), idx); if (cc_display_monitor_is_primary (output)) return; cc_display_monitor_set_primary (output, TRUE); update_apply_button (panel); } static void cc_display_panel_constructed (GObject *object) { g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel", G_CALLBACK (active_panel_changed), object, G_CONNECT_SWAPPED); G_OBJECT_CLASS (cc_display_panel_parent_class)->constructed (object); } static const char * cc_display_panel_get_help_uri (CcPanel *panel) { return "help:gnome-help/prefs-display"; } static GtkWidget * cc_display_panel_get_title_widget (CcPanel *panel) { CcDisplayPanel *self = CC_DISPLAY_PANEL (panel); return self->stack_switcher; } static void cc_display_panel_class_init (CcDisplayPanelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); CcPanelClass *panel_class = CC_PANEL_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); g_type_ensure (CC_TYPE_NIGHT_LIGHT_PAGE); panel_class->get_help_uri = cc_display_panel_get_help_uri; panel_class->get_title_widget = cc_display_panel_get_title_widget; object_class->constructed = cc_display_panel_constructed; object_class->dispose = cc_display_panel_dispose; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-panel.ui"); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_frame); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_bin); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_switcher_frame); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_join); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_mirror); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_single); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, current_output_label); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_frame); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, multi_selection_box); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_page); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_enabled_switch); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_combo); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_stack); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_buttonbox); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_first); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_second); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, primary_display_row); gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, stack_switcher); gtk_widget_class_bind_template_callback (widget_class, on_config_type_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, on_night_light_list_box_row_activated_cb); gtk_widget_class_bind_template_callback (widget_class, on_output_enabled_active_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_output_selection_combo_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_output_selection_two_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, on_primary_display_selected_index_changed_cb); } static void set_current_output (CcDisplayPanel *panel, CcDisplayMonitor *output, gboolean force) { GtkTreeIter iter; gboolean changed; /* Note, this function is also called if the internal UI needs updating after a rebuild. */ changed = (output != panel->current_output); if (!changed && !force) return; panel->rebuilding_counter++; panel->current_output = output; if (panel->current_output) { gtk_label_set_text (panel->current_output_label, cc_display_monitor_get_ui_name (panel->current_output)); gtk_switch_set_active (panel->output_enabled_switch, cc_display_monitor_is_active (panel->current_output)); gtk_widget_set_sensitive (GTK_WIDGET (panel->output_enabled_switch), cc_display_monitor_is_usable (panel->current_output)); } else { gtk_label_set_text (panel->current_output_label, ""); gtk_switch_set_active (panel->output_enabled_switch, FALSE); gtk_widget_set_sensitive (GTK_WIDGET (panel->output_enabled_switch), FALSE); } if (g_object_get_data (G_OBJECT (panel->output_selection_two_first), "display") == output) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_first), TRUE); if (g_object_get_data (G_OBJECT (panel->output_selection_two_second), "display") == output) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_second), TRUE); gtk_tree_model_get_iter_first (GTK_TREE_MODEL (panel->output_selection_list), &iter); while (gtk_list_store_iter_is_valid (panel->output_selection_list, &iter)) { g_autoptr(CcDisplayMonitor) o = NULL; gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter, 1, &o, -1); if (o == panel->current_output) { gtk_combo_box_set_active_iter (panel->output_selection_combo, &iter); break; } gtk_tree_model_iter_next (GTK_TREE_MODEL (panel->output_selection_list), &iter); } if (changed) { cc_display_settings_set_selected_output (panel->settings, panel->current_output); cc_display_arrangement_set_selected_output (panel->arrangement, panel->current_output); } panel->rebuilding_counter--; } static void rebuild_ui (CcDisplayPanel *panel) { guint n_outputs, n_active_outputs, n_usable_outputs; GList *outputs, *l; CcDisplayConfigType type; panel->rebuilding_counter++; g_list_store_remove_all (panel->primary_display_list); gtk_list_store_clear (panel->output_selection_list); if (!panel->current_config) { panel->rebuilding_counter--; return; } n_active_outputs = 0; n_usable_outputs = 0; outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); for (l = outputs; l; l = l->next) { GtkTreeIter iter; CcDisplayMonitor *output = l->data; gtk_list_store_append (panel->output_selection_list, &iter); gtk_list_store_set (panel->output_selection_list, &iter, 0, cc_display_monitor_get_ui_number_name (output), 1, output, -1); if (!cc_display_monitor_is_usable (output)) continue; n_usable_outputs += 1; if (n_usable_outputs == 1) { gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_first), cc_display_monitor_get_ui_name (output)); g_object_set_data (G_OBJECT (panel->output_selection_two_first), "display", output); } else if (n_usable_outputs == 2) { gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_second), cc_display_monitor_get_ui_name (output)); g_object_set_data (G_OBJECT (panel->output_selection_two_second), "display", output); } if (cc_display_monitor_is_active (output)) { n_active_outputs += 1; g_list_store_append (panel->primary_display_list, output); if (cc_display_monitor_is_primary (output)) hdy_combo_row_set_selected_index (panel->primary_display_row, g_list_model_get_n_items (G_LIST_MODEL (panel->primary_display_list)) - 1); /* Ensure that an output is selected; note that this doesn't ensure * the selected output is any useful (i.e. when switching types). */ if (!panel->current_output) set_current_output (panel, output, FALSE); } } /* Sync the rebuild lists/buttons */ set_current_output (panel, panel->current_output, TRUE); n_outputs = g_list_length (outputs); type = config_get_current_type (panel); if (n_outputs == 2 && n_usable_outputs == 2) { /* We only show the top chooser with two monitors that are * both usable (i.e. two monitors incl. internal and lid not closed). * In this case, the arrangement widget is shown if we are in JOIN mode. */ if (type > CC_DISPLAY_CONFIG_LAST_VALID) type = CC_DISPLAY_CONFIG_JOIN; gtk_widget_set_visible (panel->config_type_switcher_frame, TRUE); gtk_widget_set_visible (panel->arrangement_frame, type == CC_DISPLAY_CONFIG_JOIN); /* We need a switcher except in CLONE mode */ if (type == CC_DISPLAY_CONFIG_CLONE) gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->current_output_label)); else gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->output_selection_two_buttonbox)); } else if (n_usable_outputs > 1) { /* We have more than one usable monitor. In this case there is no chooser, * and we always show the arrangement widget even if we are in SINGLE mode. */ gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE); gtk_widget_set_visible (panel->arrangement_frame, TRUE); /* Mirror is also invalid as it cannot be configured using this UI. */ if (type == CC_DISPLAY_CONFIG_CLONE || type > CC_DISPLAY_CONFIG_LAST_VALID) type = CC_DISPLAY_CONFIG_JOIN; gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->multi_selection_box)); } else { /* We only have a single usable monitor, show neither configuration type * switcher nor arrangement widget and ensure we really are in SINGLE * mode (and not e.g. mirroring across one display) */ type = CC_DISPLAY_CONFIG_SINGLE; gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE); gtk_widget_set_visible (panel->arrangement_frame, FALSE); gtk_stack_set_visible_child (panel->output_selection_stack, GTK_WIDGET (panel->current_output_label)); } cc_panel_set_selected_type (panel, type); panel->rebuilding_counter--; update_apply_button (panel); } static void update_panel_orientation_managed (CcDisplayPanel *panel, gboolean managed) { cc_display_settings_set_has_accelerometer (panel->settings, managed); } static void reset_current_config (CcDisplayPanel *panel) { CcDisplayConfig *current; CcDisplayConfig *old; GList *outputs, *l; g_debug ("Resetting current config!"); /* We need to hold on to the config until all display references are dropped. */ old = panel->current_config; panel->current_output = NULL; current = cc_display_config_manager_get_current (panel->manager); if (!current) return; cc_display_config_set_minimum_size (current, MINIMUM_WIDTH, MINIMUM_HEIGHT); panel->current_config = current; g_signal_connect_object (current, "panel-orientation-managed", G_CALLBACK (update_panel_orientation_managed), panel, G_CONNECT_SWAPPED); update_panel_orientation_managed (panel, cc_display_config_get_panel_orientation_managed (current)); g_list_store_remove_all (panel->primary_display_list); gtk_list_store_clear (panel->output_selection_list); if (panel->current_config) { outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; /* Mark any builtin monitor as unusable if the lid is closed. */ if (cc_display_monitor_is_builtin (output) && panel->lid_is_closed) cc_display_monitor_set_usable (output, FALSE); } } cc_display_arrangement_set_config (panel->arrangement, panel->current_config); cc_display_settings_set_config (panel->settings, panel->current_config); set_current_output (panel, NULL, FALSE); g_clear_object (&old); update_apply_button (panel); } static void on_screen_changed (CcDisplayPanel *panel) { if (!panel->manager) return; reset_titlebar (panel); reset_current_config (panel); rebuild_ui (panel); if (!panel->current_config) return; ensure_monitor_labels (panel); } static gboolean on_toplevel_key_press (GtkWidget *button, GdkEventKey *event) { if (event->keyval != GDK_KEY_Escape) return GDK_EVENT_PROPAGATE; g_signal_emit_by_name (button, "activate"); return GDK_EVENT_STOP; } static void show_apply_titlebar (CcDisplayPanel *panel, gboolean is_applicable) { if (!panel->apply_titlebar) { g_autoptr(GtkSizeGroup) size_group = NULL; GtkWidget *header, *button, *toplevel; panel->apply_titlebar = header = gtk_header_bar_new (); gtk_widget_show (header); size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); button = gtk_button_new_with_mnemonic (_("_Cancel")); gtk_widget_show (button); gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button); gtk_size_group_add_widget (size_group, button); g_signal_connect_object (button, "clicked", G_CALLBACK (on_screen_changed), panel, G_CONNECT_SWAPPED); toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel))); g_signal_connect_object (toplevel, "key-press-event", G_CALLBACK (on_toplevel_key_press), button, G_CONNECT_SWAPPED); panel->apply_titlebar_apply = button = gtk_button_new_with_mnemonic (_("_Apply")); gtk_widget_show (button); gtk_header_bar_pack_end (GTK_HEADER_BAR (header), button); gtk_size_group_add_widget (size_group, button); g_signal_connect_object (button, "clicked", G_CALLBACK (apply_current_configuration), panel, G_CONNECT_SWAPPED); gtk_style_context_add_class (gtk_widget_get_style_context (button), GTK_STYLE_CLASS_SUGGESTED_ACTION); header = gtk_window_get_titlebar (GTK_WINDOW (toplevel)); if (header) panel->main_titlebar = g_object_ref (header); panel->main_title = g_strdup (gtk_window_get_title (GTK_WINDOW (toplevel))); gtk_window_set_titlebar (GTK_WINDOW (toplevel), panel->apply_titlebar); g_object_ref (panel->apply_titlebar); g_object_ref (panel->apply_titlebar_apply); } if (is_applicable) { gtk_header_bar_set_title (GTK_HEADER_BAR (panel->apply_titlebar), _("Apply Changes?")); gtk_header_bar_set_subtitle (GTK_HEADER_BAR (panel->apply_titlebar), NULL); } else { gtk_header_bar_set_title (GTK_HEADER_BAR (panel->apply_titlebar), _("Changes Cannot be Applied")); gtk_header_bar_set_subtitle (GTK_HEADER_BAR (panel->apply_titlebar), _("This could be due to hardware limitations.")); } gtk_widget_set_sensitive (panel->apply_titlebar_apply, is_applicable); } static void update_apply_button (CcDisplayPanel *panel) { gboolean config_equal; g_autoptr(CcDisplayConfig) applied_config = NULL; if (!panel->current_config) { reset_titlebar (panel); return; } applied_config = cc_display_config_manager_get_current (panel->manager); config_equal = cc_display_config_equal (panel->current_config, applied_config); if (config_equal) reset_titlebar (panel); else show_apply_titlebar (panel, cc_display_config_is_applicable (panel->current_config)); } static void apply_current_configuration (CcDisplayPanel *self) { g_autoptr(GError) error = NULL; cc_display_config_apply (self->current_config, &error); /* re-read the configuration */ on_screen_changed (self); if (error) g_warning ("Error applying configuration: %s", error->message); } static void mapped_cb (CcDisplayPanel *panel) { CcShell *shell; GtkWidget *toplevel; shell = cc_panel_get_shell (CC_PANEL (panel)); toplevel = cc_shell_get_toplevel (shell); if (toplevel && !panel->focus_id) panel->focus_id = g_signal_connect_object (toplevel, "notify::has-toplevel-focus", G_CALLBACK (dialog_toplevel_focus_changed), panel, G_CONNECT_SWAPPED); } static void cc_display_panel_up_client_changed (CcDisplayPanel *self) { gboolean lid_is_closed; lid_is_closed = up_client_get_lid_is_closed (self->up_client); if (lid_is_closed != self->lid_is_closed) { self->lid_is_closed = lid_is_closed; on_screen_changed (self); } } static void shell_proxy_ready (GObject *source, GAsyncResult *res, CcDisplayPanel *self) { GDBusProxy *proxy; g_autoptr(GError) error = NULL; proxy = cc_object_storage_create_dbus_proxy_finish (res, &error); if (!proxy) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to contact gnome-shell: %s", error->message); return; } self->shell_proxy = proxy; ensure_monitor_labels (self); } static void session_bus_ready (GObject *source, GAsyncResult *res, CcDisplayPanel *self) { GDBusConnection *bus; g_autoptr(GError) error = NULL; bus = g_bus_get_finish (res, &error); if (!bus) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Failed to get session bus: %s", error->message); } return; } self->manager = cc_display_config_manager_dbus_new (); g_signal_connect_object (self->manager, "changed", G_CALLBACK (on_screen_changed), self, G_CONNECT_SWAPPED); } static void cc_display_panel_init (CcDisplayPanel *self) { g_autoptr(GtkCssProvider) provider = NULL; GtkCellRenderer *renderer; g_resources_register (cc_display_get_resource ()); gtk_widget_init_template (GTK_WIDGET (self)); self->arrangement = cc_display_arrangement_new (NULL); gtk_widget_show (GTK_WIDGET (self->arrangement)); gtk_widget_set_size_request (GTK_WIDGET (self->arrangement), 400, 175); gtk_container_add (GTK_CONTAINER (self->arrangement_bin), GTK_WIDGET (self->arrangement)); g_signal_connect_object (self->arrangement, "updated", G_CALLBACK (update_apply_button), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->arrangement, "notify::selected-output", G_CALLBACK (on_arrangement_selected_ouptut_changed_cb), self, G_CONNECT_SWAPPED); self->settings = cc_display_settings_new (); gtk_widget_show (GTK_WIDGET (self->settings)); gtk_container_add (GTK_CONTAINER (self->display_settings_frame), GTK_WIDGET (self->settings)); g_signal_connect_object (self->settings, "updated", G_CALLBACK (on_monitor_settings_updated_cb), self, G_CONNECT_SWAPPED); self->primary_display_list = g_list_store_new (CC_TYPE_DISPLAY_MONITOR); hdy_combo_row_bind_name_model (self->primary_display_row, G_LIST_MODEL (self->primary_display_list), (HdyComboRowGetNameFunc) cc_display_monitor_dup_ui_number_name, NULL, NULL); self->output_selection_list = gtk_list_store_new (2, G_TYPE_STRING, CC_TYPE_DISPLAY_MONITOR); gtk_combo_box_set_model (self->output_selection_combo, GTK_TREE_MODEL (self->output_selection_list)); gtk_cell_layout_clear (GTK_CELL_LAYOUT (self->output_selection_combo)); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->output_selection_combo), renderer, TRUE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (self->output_selection_combo), renderer, "text", 0); gtk_cell_renderer_set_visible (renderer, TRUE); self->up_client = up_client_new (); if (up_client_get_lid_is_present (self->up_client)) { g_signal_connect_object (self->up_client, "notify::lid-is-closed", G_CALLBACK (cc_display_panel_up_client_changed), self, G_CONNECT_SWAPPED); cc_display_panel_up_client_changed (self); } else g_clear_object (&self->up_client); g_signal_connect (self, "map", G_CALLBACK (mapped_cb), NULL); cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, "org.gnome.Shell", "/org/gnome/Shell", "org.gnome.Shell", cc_panel_get_cancellable (CC_PANEL (self)), (GAsyncReadyCallback) shell_proxy_ready, self); g_bus_get (G_BUS_TYPE_SESSION, cc_panel_get_cancellable (CC_PANEL (self)), (GAsyncReadyCallback) session_bus_ready, self); provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/display/display-arrangement.css"); gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); }