diff options
Diffstat (limited to 'panels/display/cc-display-panel.c')
-rw-r--r-- | panels/display/cc-display-panel.c | 1183 |
1 files changed, 1183 insertions, 0 deletions
diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c new file mode 100644 index 0000000..f58aaa8 --- /dev/null +++ b/panels/display/cc-display-panel.c @@ -0,0 +1,1183 @@ +/* + * 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 <gtk/gtk.h> +#include <glib/gi18n.h> +#include <stdlib.h> +#include <gdesktop-enums.h> +#include <math.h> +#include <handy.h> + +#include "shell/cc-object-storage.h" +#include "list-box-helper.h" +#include <libupower-glib/upower.h> + +#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); +} |