/* * 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 "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-dialog.h" #include "cc-display-resources.h" #define PANEL_PADDING 32 #define SECTION_PADDING 32 #define HEADING_PADDING 12 enum { DISPLAY_MODE_PRIMARY, DISPLAY_MODE_SECONDARY, /* DISPLAY_MODE_PRESENTATION, */ DISPLAY_MODE_MIRROR, DISPLAY_MODE_OFF }; struct _CcDisplayPanel { CcPanel parent_instance; CcDisplayConfigManager *manager; CcDisplayConfig *current_config; CcDisplayMonitor *current_output; CcDisplayArrangement *arrangement; guint focus_id; GtkSizeGroup *main_size_group; GtkSizeGroup *rows_size_group; GtkWidget *stack; CcNightLightDialog *night_light_dialog; GSettings *settings_color; UpClient *up_client; gboolean lid_is_closed; GDBusProxy *shell_proxy; GCancellable *shell_cancellable; guint sensor_watch_id; GDBusProxy *iio_sensor_proxy; gboolean has_accelerometer; gchar *main_title; GtkWidget *main_titlebar; GtkWidget *apply_titlebar; GtkWidget *apply_titlebar_apply; GtkWidget *apply_titlebar_warning; }; CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel) typedef struct { int grab_x; int grab_y; int output_x; int output_y; } GrabInfo; enum { CURRENT_OUTPUT, LAST_PANEL_SIGNAL }; static guint panel_signals[LAST_PANEL_SIGNAL] = { 0 }; static const gchar * get_resolution_string (CcDisplayMode *mode); static const gchar * get_frequency_string (CcDisplayMode *mode); static GtkWidget * make_night_light_widget (CcDisplayPanel *panel); static gboolean should_show_rotation (CcDisplayPanel *panel, CcDisplayMonitor *output); static void update_apply_button (CcDisplayPanel *panel); static void apply_current_configuration (CcDisplayPanel *self); static void reset_current_config (CcDisplayPanel *panel); 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, "ShowMonitorLabels2", 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; 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 (CcShell *shell, GParamSpec *pspec, CcPanel *self) { g_autoptr(CcPanel) panel = NULL; 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); CcShell *shell; GtkWidget *toplevel; reset_titlebar (CC_DISPLAY_PANEL (object)); if (self->sensor_watch_id > 0) { g_bus_unwatch_name (self->sensor_watch_id); self->sensor_watch_id = 0; } g_clear_object (&self->iio_sensor_proxy); if (self->focus_id) { shell = cc_panel_get_shell (CC_PANEL (object)); toplevel = cc_shell_get_toplevel (shell); if (toplevel != NULL) g_signal_handler_disconnect (G_OBJECT (toplevel), 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->settings_color); g_clear_object (&self->main_size_group); g_cancellable_cancel (self->shell_cancellable); g_clear_object (&self->shell_cancellable); g_clear_object (&self->shell_proxy); g_clear_pointer (&self->night_light_dialog, gtk_widget_destroy); G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object); } 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, 0); 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 void cc_display_panel_class_init (CcDisplayPanelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); CcPanelClass *panel_class = CC_PANEL_CLASS (klass); panel_class->get_help_uri = cc_display_panel_get_help_uri; object_class->constructed = cc_display_panel_constructed; object_class->dispose = cc_display_panel_dispose; panel_signals[CURRENT_OUTPUT] = g_signal_new ("current-output", CC_TYPE_DISPLAY_PANEL, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void set_current_output (CcDisplayPanel *panel, CcDisplayMonitor *output) { panel->current_output = output; g_signal_emit (panel, panel_signals[CURRENT_OUTPUT], 0); } static GtkWidget * make_bin (void) { return g_object_new (GTK_TYPE_FRAME, "shadow-type", GTK_SHADOW_NONE, NULL); } static GtkWidget * wrap_in_boxes (GtkWidget *widget) { GtkWidget *box, *bin; box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, PANEL_PADDING); bin = make_bin (); gtk_widget_show (bin); gtk_box_pack_start (GTK_BOX (box), bin, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 0); bin = make_bin (); gtk_widget_show (bin); gtk_box_pack_start (GTK_BOX (box), bin, TRUE, TRUE, 0); return box; } static GtkWidget * make_scrollable (GtkWidget *widget) { GtkWidget *sw, *box; sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, "hscrollbar-policy", GTK_POLICY_NEVER, "min-content-height", 450, "propagate-natural-height", TRUE, NULL); box = wrap_in_boxes (widget); gtk_widget_show (box); gtk_container_add (GTK_CONTAINER (sw), box); return sw; } static GtkWidget * make_bold_label (const gchar *text) { g_autoptr(GtkCssProvider) provider = NULL; GtkWidget *label = gtk_label_new (text); provider = gtk_css_provider_new (); gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), "label { font-weight: bold; }", -1, NULL); gtk_style_context_add_provider (gtk_widget_get_style_context (label), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); return label; } static GtkWidget * make_main_vbox (GtkSizeGroup *size_group) { GtkWidget *vbox; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_set_margin_top (vbox, PANEL_PADDING); gtk_widget_set_margin_bottom (vbox, PANEL_PADDING); if (size_group) gtk_size_group_add_widget (size_group, vbox); return vbox; } static GtkWidget * make_row (GtkSizeGroup *size_group, GtkWidget *start_widget, GtkWidget *end_widget) { GtkWidget *row, *box; row = g_object_new (CC_TYPE_LIST_BOX_ROW, NULL); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 50); gtk_widget_show (box); gtk_widget_set_margin_start (box, 20); gtk_widget_set_margin_end (box, 20); gtk_widget_set_margin_top (box, 20); gtk_widget_set_margin_bottom (box, 20); if (start_widget) { gtk_widget_set_halign (start_widget, GTK_ALIGN_START); gtk_box_pack_start (GTK_BOX (box), start_widget, FALSE, FALSE, 0); } if (end_widget) { gtk_widget_set_halign (end_widget, GTK_ALIGN_END); gtk_box_pack_end (GTK_BOX (box), end_widget, FALSE, FALSE, 0); } gtk_container_add (GTK_CONTAINER (row), box); if (size_group) gtk_size_group_add_widget (size_group, row); return row; } static GtkWidget * make_frame (const gchar *title, const gchar *subtitle) { GtkWidget *frame; frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); if (title) { GtkWidget *vbox, *label; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, HEADING_PADDING/2); gtk_widget_show (vbox); gtk_widget_set_margin_bottom (vbox, HEADING_PADDING); label = make_bold_label (title); gtk_widget_show (label); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_container_add (GTK_CONTAINER (vbox), label); if (subtitle) { label = gtk_label_new (subtitle); gtk_widget_show (label); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_container_add (GTK_CONTAINER (vbox), label); gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL); } gtk_frame_set_label_widget (GTK_FRAME (frame), vbox); gtk_frame_set_label_align (GTK_FRAME (frame), 0.0, 1.0); } return frame; } static GtkWidget * make_list_box (void) { GtkWidget *listbox; listbox = g_object_new (CC_TYPE_LIST_BOX, NULL); gtk_list_box_set_selection_mode (GTK_LIST_BOX (listbox), GTK_SELECTION_NONE); gtk_list_box_set_header_func (GTK_LIST_BOX (listbox), cc_list_box_update_header_func, NULL, NULL); return listbox; } static void make_list_transparent (GtkWidget *listbox) { g_autoptr(GtkCssProvider) provider = NULL; provider = gtk_css_provider_new (); gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), "list { border-style: none; background-color: transparent; }", -1, NULL); gtk_style_context_add_provider (gtk_widget_get_style_context (listbox), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } static GtkWidget * make_list_popover (GtkWidget *listbox) { GtkWidget *popover = g_object_new (GTK_TYPE_POPOVER, "position", GTK_POS_BOTTOM, NULL); GtkWidget *sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, "hscrollbar-policy", GTK_POLICY_NEVER, "max-content-height", 400, "propagate-natural-height", TRUE, NULL); gtk_widget_show (sw); make_list_transparent (listbox); gtk_container_add (GTK_CONTAINER (sw), listbox); gtk_container_add (GTK_CONTAINER (popover), sw); g_signal_connect_object (listbox, "row-activated", G_CALLBACK (gtk_widget_hide), popover, G_CONNECT_SWAPPED); return popover; } static GtkWidget * make_popover_label (const gchar *text) { return g_object_new (GTK_TYPE_LABEL, "label", text, "margin", 12, "xalign", 0.0, "width-chars", 20, "max-width-chars", 20, NULL); } static const gchar * string_for_rotation (CcDisplayRotation rotation) { switch (rotation) { case CC_DISPLAY_ROTATION_NONE: case CC_DISPLAY_ROTATION_180_FLIPPED: return C_("Display rotation", "Landscape"); case CC_DISPLAY_ROTATION_90: case CC_DISPLAY_ROTATION_270_FLIPPED: return C_("Display rotation", "Portrait Right"); case CC_DISPLAY_ROTATION_270: case CC_DISPLAY_ROTATION_90_FLIPPED: return C_("Display rotation", "Portrait Left"); case CC_DISPLAY_ROTATION_180: case CC_DISPLAY_ROTATION_FLIPPED: return C_("Display rotation", "Landscape (flipped)"); } return ""; } static void orientation_row_activated (CcDisplayPanel *panel, GtkListBoxRow *row) { CcDisplayRotation rotation = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "rotation")); cc_display_monitor_set_rotation (panel->current_output, rotation); update_apply_button (panel); } static GtkWidget * make_orientation_popover (CcDisplayPanel *panel) { GtkWidget *listbox; CcDisplayRotation rotations[] = { CC_DISPLAY_ROTATION_NONE, CC_DISPLAY_ROTATION_90, CC_DISPLAY_ROTATION_270, CC_DISPLAY_ROTATION_180 }; guint i = 0; listbox = make_list_box (); gtk_widget_show (listbox); g_object_set (listbox, "margin", 12, NULL); for (i = 0; i < G_N_ELEMENTS (rotations); ++i) { CcDisplayRotation rotation = rotations[i]; if (cc_display_monitor_supports_rotation (panel->current_output, rotation)) { GtkWidget *label, *row; label = make_popover_label (string_for_rotation (rotation)); gtk_widget_show (label); row = g_object_new (CC_TYPE_LIST_BOX_ROW, "child", label, NULL); gtk_widget_show (row); g_object_set_data (G_OBJECT (row), "rotation", GUINT_TO_POINTER (rotation)); g_signal_connect_object (row, "activated", G_CALLBACK (orientation_row_activated), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (listbox), row); } } return make_list_popover (listbox); } static void orientation_row_sync (GtkPopover *popover, CcDisplayMonitor *output) { gtk_label_set_text (GTK_LABEL (gtk_popover_get_relative_to (popover)), string_for_rotation (cc_display_monitor_get_rotation (output))); } static GtkWidget * make_orientation_row (CcDisplayPanel *panel, CcDisplayMonitor *output) { GtkWidget *row, *label, *heading_label, *popover; label = gtk_label_new (string_for_rotation (cc_display_monitor_get_rotation (output))); gtk_widget_show (label); popover = make_orientation_popover (panel); gtk_popover_set_relative_to (GTK_POPOVER (popover), label); heading_label = gtk_label_new (_("Orientation")); gtk_widget_show (heading_label); row = make_row (panel->rows_size_group, heading_label, label); g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), popover, G_CONNECT_SWAPPED); g_signal_connect_object (output, "rotation", G_CALLBACK (orientation_row_sync), popover, G_CONNECT_SWAPPED); return row; } static void resolution_row_activated (CcDisplayPanel *panel, GtkListBoxRow *row) { CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); cc_display_monitor_set_mode (panel->current_output, mode); update_apply_button (panel); } static GtkWidget * make_resolution_popover (CcDisplayPanel *panel) { GtkWidget *listbox; GList *resolutions, *l; resolutions = g_object_get_data (G_OBJECT (panel->current_output), "res-list"); listbox = make_list_box (); gtk_widget_show (listbox); g_object_set (listbox, "margin", 12, NULL); for (l = resolutions; l; l = l->next) { CcDisplayMode *mode = l->data; GtkWidget *row; GtkWidget *child; child = make_popover_label (get_resolution_string (mode)); gtk_widget_show (child); row = g_object_new (CC_TYPE_LIST_BOX_ROW, "child", child, NULL); gtk_widget_show (row); g_object_set_data (G_OBJECT (row), "mode", mode); g_signal_connect_object (row, "activated", G_CALLBACK (resolution_row_activated), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (listbox), row); } return make_list_popover (listbox); } static void resolution_row_sync (GtkPopover *popover, CcDisplayMonitor *output) { gtk_label_set_text (GTK_LABEL (gtk_popover_get_relative_to (popover)), get_resolution_string (cc_display_monitor_get_mode (output))); } static GtkWidget * make_resolution_row (CcDisplayPanel *panel, CcDisplayMonitor *output) { GtkWidget *row, *label, *heading_label, *popover; label = gtk_label_new (get_resolution_string (cc_display_monitor_get_mode (output))); gtk_widget_show (label); popover = make_resolution_popover (panel); gtk_popover_set_relative_to (GTK_POPOVER (popover), label); heading_label = gtk_label_new (_("Resolution")); gtk_widget_show (heading_label); row = make_row (panel->rows_size_group, heading_label, label); g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), popover, G_CONNECT_SWAPPED); g_signal_connect_object (output, "mode", G_CALLBACK (resolution_row_sync), popover, G_CONNECT_SWAPPED); return row; } static void refresh_rate_row_activated (CcDisplayPanel *panel, GtkListBoxRow *row) { CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); cc_display_monitor_set_mode (panel->current_output, mode); update_apply_button (panel); } static GtkWidget * make_refresh_rate_popover (CcDisplayPanel *panel) { GtkWidget *listbox; GHashTable *res_freqs; GList *freqs, *l; res_freqs = g_object_get_data (G_OBJECT (panel->current_output), "res-freqs"); freqs = g_hash_table_lookup (res_freqs, get_resolution_string (cc_display_monitor_get_mode (panel->current_output))); listbox = make_list_box (); gtk_widget_show (listbox); g_object_set (listbox, "margin", 12, NULL); for (l = freqs; l; l = l->next) { CcDisplayMode *mode = l->data; GtkWidget *label, *row; label = make_popover_label (get_frequency_string (mode)); gtk_widget_show (label); row = g_object_new (CC_TYPE_LIST_BOX_ROW, "child", label, NULL); gtk_widget_show (row); g_object_set_data (G_OBJECT (row), "mode", mode); g_signal_connect_object (row, "activated", G_CALLBACK (refresh_rate_row_activated), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (listbox), row); } return make_list_popover (listbox); } static void refresh_rate_row_sync (GtkPopover *popover, CcDisplayMonitor *output) { gtk_label_set_text (GTK_LABEL (gtk_popover_get_relative_to (popover)), get_frequency_string (cc_display_monitor_get_mode (output))); } static gboolean should_show_refresh_rate (CcDisplayMonitor *output) { GHashTable *res_freqs = g_object_get_data (G_OBJECT (output), "res-freqs"); const gchar *resolution = get_resolution_string (cc_display_monitor_get_mode (output)); GList *freqs = g_hash_table_lookup (res_freqs, resolution); return g_list_length (freqs) > 1; } static void refresh_rate_row_sync_visibility (GtkWidget *row, CcDisplayMonitor *output) { if (!should_show_refresh_rate (output)) gtk_widget_hide (row); else gtk_widget_show (row); } static GtkWidget * make_refresh_rate_row (CcDisplayPanel *panel, CcDisplayMonitor *output) { GtkWidget *row, *label, *heading_label, *popover; label = gtk_label_new (get_frequency_string (cc_display_monitor_get_mode (output))); gtk_widget_show (label); popover = make_refresh_rate_popover (panel); gtk_popover_set_relative_to (GTK_POPOVER (popover), label); heading_label = gtk_label_new (_("Refresh Rate")); gtk_widget_show (heading_label); row = make_row (panel->rows_size_group, heading_label, label); g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), popover, G_CONNECT_SWAPPED); g_signal_connect_object (output, "mode", G_CALLBACK (refresh_rate_row_sync), popover, G_CONNECT_SWAPPED); g_signal_connect_object (output, "mode", G_CALLBACK (refresh_rate_row_sync_visibility), row, G_CONNECT_SWAPPED); refresh_rate_row_sync_visibility (row, output); return row; } static guint n_supported_scales (CcDisplayMode *mode) { const double *scales = cc_display_mode_get_supported_scales (mode); guint n = 0; while (scales[n] != 0.0) n++; return n; } static gboolean should_show_scale_row (CcDisplayMonitor *output) { CcDisplayMode *mode = cc_display_monitor_get_mode (output); return mode ? n_supported_scales (mode) > 1 : FALSE; } static void scale_row_sync_visibility (GtkWidget *row, CcDisplayMonitor *output) { if (!should_show_scale_row (output)) gtk_widget_hide (row); else gtk_widget_show (row); } static void scale_buttons_active (CcDisplayPanel *panel, GParamSpec *pspec, GtkWidget *button) { double scale = *(double*) g_object_get_data (G_OBJECT (button), "scale"); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { cc_display_monitor_set_scale (panel->current_output, scale); update_apply_button (panel); } } static double round_scale_for_ui (double scale) { /* Keep in sync with mutter */ return round (scale*4)/4; } static GtkWidget * make_label_for_scale (double scale) { g_autofree gchar *text = g_strdup_printf (" %d %% ", (int) (round_scale_for_ui (scale)*100)); return gtk_label_new (text); } #define MAX_N_SCALES 5 static void setup_scale_buttons (GtkWidget *bbox, CcDisplayMonitor *output) { CcDisplayPanel *panel; GtkRadioButton *group; CcDisplayMode *mode; const double *scales, *scale; guint i; panel = g_object_get_data (G_OBJECT (bbox), "panel"); gtk_container_foreach (GTK_CONTAINER (bbox), (GtkCallback) gtk_widget_destroy, NULL); mode = cc_display_monitor_get_mode (output); if (!mode) return; scales = cc_display_mode_get_supported_scales (mode); group = NULL; for (scale = scales, i = 0; *scale != 0.0 && i < MAX_N_SCALES; scale++, i++) { GtkWidget *button, *label; button = gtk_radio_button_new_from_widget (group); gtk_widget_show (button); label = make_label_for_scale (*scale); gtk_widget_show (label); gtk_button_set_image (GTK_BUTTON (button), label); gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); g_object_set_data_full (G_OBJECT (button), "scale", g_memdup (scale, sizeof (double)), g_free); g_signal_connect_object (button, "notify::active", G_CALLBACK (scale_buttons_active), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (bbox), button); group = GTK_RADIO_BUTTON (button); } gtk_widget_show (bbox); } #undef MAX_N_SCALES static void scale_buttons_sync (GtkWidget *bbox, CcDisplayMonitor *output) { g_autoptr(GList) children; GList *l; children = gtk_container_get_children (GTK_CONTAINER (bbox)); for (l = children; l; l = l->next) { GtkWidget *button = l->data; double scale = *(double*) g_object_get_data (G_OBJECT (button), "scale"); if (scale == cc_display_monitor_get_scale (output)) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); } } static GtkWidget * make_scale_row (CcDisplayPanel *panel, CcDisplayMonitor *output) { GtkWidget *row, *bbox, *label; label = gtk_label_new (_("Scale")); gtk_widget_show (label); bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); gtk_widget_show (bbox); gtk_widget_set_valign (bbox, GTK_ALIGN_CENTER); gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_EXPAND); row = make_row (panel->rows_size_group, label, bbox); gtk_widget_set_margin_top (gtk_bin_get_child (GTK_BIN (row)), 0); gtk_widget_set_margin_bottom (gtk_bin_get_child (GTK_BIN (row)), 0); gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); g_object_set_data (G_OBJECT (bbox), "panel", panel); g_signal_connect_object (output, "mode", G_CALLBACK (setup_scale_buttons), bbox, G_CONNECT_SWAPPED); setup_scale_buttons (bbox, output); g_signal_connect_object (output, "scale", G_CALLBACK (scale_buttons_sync), bbox, G_CONNECT_SWAPPED); scale_buttons_sync (bbox, output); g_signal_connect_object (output, "mode", G_CALLBACK (scale_row_sync_visibility), row, G_CONNECT_SWAPPED); scale_row_sync_visibility (row, output); return row; } static void underscanning_switch_active (CcDisplayPanel *panel, GParamSpec *pspec, GtkWidget *button) { cc_display_monitor_set_underscanning (panel->current_output, gtk_switch_get_active (GTK_SWITCH (button))); update_apply_button (panel); } static GtkWidget * make_underscanning_row (CcDisplayPanel *panel, CcDisplayMonitor *output) { GtkWidget *button, *label; button = gtk_switch_new (); gtk_widget_show (button); gtk_switch_set_active (GTK_SWITCH (button), cc_display_monitor_get_underscanning (output)); g_signal_connect_object (button, "notify::active", G_CALLBACK (underscanning_switch_active), panel, G_CONNECT_SWAPPED); label = gtk_label_new (_("Adjust for TV")); gtk_widget_show (label); return make_row (panel->rows_size_group, label, button); } static gint sort_modes_by_area_desc (CcDisplayMode *a, CcDisplayMode *b) { int wa, ha, wb, hb; cc_display_mode_get_resolution (a, &wa, &ha); cc_display_mode_get_resolution (b, &wb, &hb); return wb*hb - wa*ha; } static gint sort_modes_by_freq_desc (CcDisplayMode *a, CcDisplayMode *b) { double delta = (cc_display_mode_get_freq_f (b) - cc_display_mode_get_freq_f (a))*1000.; return delta; } static void ensure_res_freqs (CcDisplayMonitor *output) { GHashTable *res_freqs; GHashTableIter iter; GList *resolutions, *modes, *m; if (g_object_get_data (G_OBJECT (output), "res-freqs")) return; res_freqs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_list_free); resolutions = NULL; modes = cc_display_monitor_get_modes (output); for (m = modes; m; m = m->next) { CcDisplayMode *mode = m->data; const gchar *resolution = get_resolution_string (mode); GList *l, *exist; exist = l = g_hash_table_lookup (res_freqs, resolution); l = g_list_append (l, mode); if (!exist) g_hash_table_insert (res_freqs, (gpointer) resolution, l); } g_hash_table_iter_init (&iter, res_freqs); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &modes)) { modes = g_list_copy (modes); modes = g_list_sort (modes, (GCompareFunc) sort_modes_by_freq_desc); g_hash_table_iter_replace (&iter, modes); resolutions = g_list_prepend (resolutions, g_list_nth_data (modes, 0)); } resolutions = g_list_sort (resolutions, (GCompareFunc) sort_modes_by_area_desc); g_object_set_data_full (G_OBJECT (output), "res-freqs", res_freqs, (GDestroyNotify) g_hash_table_destroy); g_object_set_data_full (G_OBJECT (output), "res-list", resolutions, (GDestroyNotify) g_list_free); } static GtkWidget * make_output_ui (CcDisplayPanel *panel) { GtkWidget *listbox, *row; ensure_res_freqs (panel->current_output); listbox = make_list_box (); if (should_show_rotation (panel, panel->current_output)) { row = make_orientation_row (panel, panel->current_output); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); } row = make_resolution_row (panel, panel->current_output); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); row = make_scale_row (panel, panel->current_output); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); row = make_refresh_rate_row (panel, panel->current_output); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); if (cc_display_monitor_supports_underscanning (panel->current_output)) { row = make_underscanning_row (panel, panel->current_output); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); } return listbox; } static GtkWidget * make_single_output_ui (CcDisplayPanel *panel) { GtkWidget *vbox, *frame, *ui; panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); vbox = make_main_vbox (panel->main_size_group); gtk_widget_show (vbox); frame = make_frame (cc_display_monitor_get_ui_name (panel->current_output), NULL); gtk_widget_show (frame); gtk_container_add (GTK_CONTAINER (vbox), frame); ui = make_output_ui (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (frame), ui); ui = make_night_light_widget (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (vbox), ui); g_clear_object (&panel->rows_size_group); return make_scrollable (vbox); } static void arrangement_notify_selected_ouptut_cb (CcDisplayPanel *panel, GParamSpec *pspec, CcDisplayArrangement *arr) { CcDisplayMonitor *output = cc_display_arrangement_get_selected_output (arr); if (output && output != panel->current_output) set_current_output (panel, output); } static void arrangement_update_selected_output (CcDisplayArrangement *arr, CcDisplayPanel *panel) { cc_display_arrangement_set_selected_output (arr, panel->current_output); } static GtkWidget * make_arrangement_row (CcDisplayPanel *panel) { GtkWidget *row; CcDisplayArrangement *arr; arr = cc_display_arrangement_new (panel->current_config); gtk_widget_show (GTK_WIDGET (arr)); cc_display_arrangement_set_selected_output (arr, panel->current_output); g_signal_connect_object (arr, "updated", G_CALLBACK (update_apply_button), panel, G_CONNECT_SWAPPED); g_signal_connect_object (arr, "notify::selected-output", G_CALLBACK (arrangement_notify_selected_ouptut_cb), panel, G_CONNECT_SWAPPED); g_signal_connect_object (panel, "current-output", G_CALLBACK (arrangement_update_selected_output), arr, G_CONNECT_SWAPPED); gtk_widget_set_size_request (GTK_WIDGET (arr), 400, 175); row = g_object_new (CC_TYPE_LIST_BOX_ROW, NULL); gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (arr)); return row; } static void primary_chooser_sync (GtkPopover *popover, CcDisplayConfig *config) { GtkWidget *label; GList *outputs, *l; label = gtk_popover_get_relative_to (popover); outputs = cc_display_config_get_monitors (config); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; if (cc_display_monitor_is_primary (output)) { const gchar *text = cc_display_monitor_get_ui_number_name (output); gtk_label_set_text (GTK_LABEL (label), text); return; } } } static void primary_chooser_row_activated (CcDisplayPanel *panel, GtkListBoxRow *row) { CcDisplayMonitor *output = g_object_get_data (G_OBJECT (row), "output"); cc_display_monitor_set_primary (output, TRUE); update_apply_button (panel); } static GtkWidget * make_primary_chooser_popover (CcDisplayPanel *panel) { GtkWidget *listbox; GList *outputs, *l; outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); listbox = make_list_box (); gtk_widget_show (listbox); g_object_set (listbox, "margin", 12, NULL); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; GtkWidget *label, *row; const gchar *text; if (!cc_display_monitor_is_useful (output)) continue; text = cc_display_monitor_get_ui_number_name (output); label = make_popover_label (text); gtk_widget_show (label); row = g_object_new (CC_TYPE_LIST_BOX_ROW, "child", label, NULL); gtk_widget_show (row); g_object_set_data (G_OBJECT (row), "output", output); g_signal_connect_object (row, "activated", G_CALLBACK (primary_chooser_row_activated), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (listbox), row); } return make_list_popover (listbox); } static GtkWidget * make_primary_chooser_row (CcDisplayPanel *panel) { GtkWidget *row, *label, *heading_label, *popover; label = gtk_label_new (NULL); gtk_widget_show (label); popover = make_primary_chooser_popover (panel); gtk_popover_set_relative_to (GTK_POPOVER (popover), label); heading_label = gtk_label_new (_("Primary Display")); gtk_widget_show (heading_label); row = make_row (panel->rows_size_group, heading_label, label); g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), popover, G_CONNECT_SWAPPED); g_signal_connect_object (panel->current_config, "primary", G_CALLBACK (primary_chooser_sync), popover, G_CONNECT_SWAPPED); primary_chooser_sync (GTK_POPOVER (popover), panel->current_config); return row; } static void replace_current_output_ui (GtkWidget *frame, CcDisplayPanel *panel) { GtkWidget *ui; panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (frame))); ui = make_output_ui (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (frame), ui); gtk_widget_show (frame); g_clear_object (&panel->rows_size_group); } static GtkWidget * make_arrangement_ui (CcDisplayPanel *panel) { GtkWidget *frame, *listbox, *row; frame = make_frame (_("Display Arrangement"), _("Drag displays to match your setup. The top bar is placed on the primary display.")); listbox = make_list_box (); gtk_widget_show (listbox); gtk_container_add (GTK_CONTAINER (frame), listbox); row = make_arrangement_row (panel); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); row = make_primary_chooser_row (panel); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); return frame; } static void two_output_chooser_active (CcDisplayPanel *panel, GParamSpec *pspec, GtkWidget *button) { CcDisplayMonitor *output = g_object_get_data (G_OBJECT (button), "output"); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) set_current_output (panel, output); } static void two_output_chooser_sync (GtkWidget *box, CcDisplayPanel *panel) { g_autoptr(GList) children = NULL; GList *l; children = gtk_container_get_children (GTK_CONTAINER (box)); for (l = children; l; l = l->next) { GtkWidget *button = l->data; CcDisplayMonitor *output = g_object_get_data (G_OBJECT (button), "output"); if (panel->current_output == output) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); } } static GtkWidget * make_two_output_chooser (CcDisplayPanel *panel) { GtkWidget *box; GtkRadioButton *group; GList *outputs, *l; outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_EXPAND); gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_LINKED); group = NULL; for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; GtkWidget *button = gtk_radio_button_new_from_widget (group); gtk_widget_show (button); gtk_button_set_label (GTK_BUTTON (button), cc_display_monitor_get_ui_name (output)); gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); g_object_set_data (G_OBJECT (button), "output", output); g_signal_connect_object (button, "notify::active", G_CALLBACK (two_output_chooser_active), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (box), button); group = GTK_RADIO_BUTTON (button); } g_signal_connect_object (panel, "current-output", G_CALLBACK (two_output_chooser_sync), box, G_CONNECT_SWAPPED); two_output_chooser_sync (box, panel); return box; } static GtkWidget * make_two_join_ui (CcDisplayPanel *panel) { GtkWidget *vbox, *ui, *frame, *box; panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); ui = make_arrangement_ui (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (vbox), ui); box = make_two_output_chooser (panel); gtk_widget_show (box); gtk_widget_set_margin_top (box, SECTION_PADDING); gtk_container_add (GTK_CONTAINER (vbox), box); frame = make_frame (NULL, NULL); gtk_widget_show (frame); gtk_widget_set_margin_top (frame, HEADING_PADDING); gtk_container_add (GTK_CONTAINER (vbox), frame); ui = make_output_ui (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (frame), ui); g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_current_output_ui), frame, G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (vbox), ui); g_clear_object (&panel->rows_size_group); return vbox; } static void two_output_chooser_activate_output (CcDisplayPanel *panel, GParamSpec *pspec, GtkWidget *button) { CcDisplayMonitor *output = g_object_get_data (G_OBJECT (button), "output"); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { GList *outputs, *l; cc_display_monitor_set_active (output, TRUE); outputs = cc_display_config_get_monitors (panel->current_config); for (l = outputs; l; l = l->next) { CcDisplayMonitor *other = l->data; if (other != output) cc_display_monitor_set_active (other, FALSE); } update_apply_button (panel); } } static void connect_activate_output (GtkWidget *button, gpointer panel) { g_signal_connect_object (button, "notify::active", G_CALLBACK (two_output_chooser_activate_output), panel, G_CONNECT_SWAPPED); } static GtkWidget * make_two_single_ui (CcDisplayPanel *panel) { GtkWidget *vbox, *frame, *ui, *box; panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); box = make_two_output_chooser (panel); gtk_widget_show (box); gtk_container_foreach (GTK_CONTAINER (box), connect_activate_output, panel); gtk_container_add (GTK_CONTAINER (vbox), box); frame = make_frame (NULL, NULL); gtk_widget_show (frame); gtk_widget_set_margin_top (frame, HEADING_PADDING); gtk_container_add (GTK_CONTAINER (vbox), frame); ui = make_output_ui (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (frame), ui); g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_current_output_ui), frame, G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (vbox), ui); g_clear_object (&panel->rows_size_group); return vbox; } static void set_mode_on_all_outputs (CcDisplayConfig *config, CcDisplayMode *mode) { GList *outputs, *l; outputs = cc_display_config_get_monitors (config); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; cc_display_monitor_set_mode (output, mode); cc_display_monitor_set_position (output, 0, 0); } } static void mirror_resolution_row_activated (CcDisplayPanel *panel, GtkListBoxRow *row) { CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); set_mode_on_all_outputs (panel->current_config, mode); update_apply_button (panel); } static GtkWidget * make_mirror_resolution_popover (CcDisplayPanel *panel) { GtkWidget *listbox; GList *resolutions, *l; resolutions = g_object_get_data (G_OBJECT (panel->current_config), "mirror-res-list"); listbox = make_list_box (); gtk_widget_show (listbox); g_object_set (listbox, "margin", 12, NULL); for (l = resolutions; l; l = l->next) { CcDisplayMode *mode = l->data; GtkWidget *label, *row; label = make_popover_label (get_resolution_string (mode)); gtk_widget_show (label); row = g_object_new (CC_TYPE_LIST_BOX_ROW, "child", label, NULL); gtk_widget_show (row); g_object_set_data (G_OBJECT (row), "mode", mode); g_signal_connect_object (row, "activated", G_CALLBACK (mirror_resolution_row_activated), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (listbox), row); } return make_list_popover (listbox); } static GtkWidget * make_mirror_resolution_row (CcDisplayPanel *panel, CcDisplayMonitor *output) { GtkWidget *row, *label, *heading_label, *popover; label = gtk_label_new (get_resolution_string (cc_display_monitor_get_mode (output))); gtk_widget_show (label); popover = make_mirror_resolution_popover (panel); gtk_popover_set_relative_to (GTK_POPOVER (popover), label); heading_label = gtk_label_new (_("Resolution")); gtk_widget_show (heading_label); row = make_row (panel->rows_size_group, heading_label, label); g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), popover, G_CONNECT_SWAPPED); g_signal_connect_object (output, "mode", G_CALLBACK (resolution_row_sync), popover, G_CONNECT_SWAPPED); return row; } static void ensure_mirror_res_list (CcDisplayConfig *config) { GHashTable *res_set; GList *resolutions, *l; if (g_object_get_data (G_OBJECT (config), "mirror-res-list")) return; res_set = g_hash_table_new (g_str_hash, g_str_equal); resolutions = cc_display_config_get_cloning_modes (config); for (l = resolutions; l; l = l->next) { CcDisplayMode *mode = l->data; const gchar *resolution = get_resolution_string (mode); if (!g_hash_table_contains (res_set, resolution)) g_hash_table_insert (res_set, (gpointer) resolution, mode); } resolutions = g_hash_table_get_values (res_set); g_hash_table_destroy (res_set); resolutions = g_list_sort (resolutions, (GCompareFunc) sort_modes_by_area_desc); g_object_set_data_full (G_OBJECT (config), "mirror-res-list", resolutions, (GDestroyNotify) g_list_free); } static GtkWidget * make_two_mirror_ui (CcDisplayPanel *panel) { GtkWidget *vbox, *listbox, *frame, *row, *ui; ensure_mirror_res_list (panel->current_config); if (!cc_display_config_is_cloning (panel->current_config)) { GList *modes; cc_display_config_set_cloning (panel->current_config, TRUE); modes = g_object_get_data (G_OBJECT (panel->current_config), "mirror-res-list"); set_mode_on_all_outputs (panel->current_config, CC_DISPLAY_MODE (g_list_nth_data (modes, 0))); } panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); frame = make_frame (NULL, NULL); gtk_widget_show (frame); gtk_container_add (GTK_CONTAINER (vbox), frame); listbox = make_list_box (); gtk_widget_show (listbox); gtk_container_add (GTK_CONTAINER (frame), listbox); if (should_show_rotation (panel, panel->current_output)) { ui = make_orientation_row (panel, panel->current_output); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (listbox), ui); } row = make_mirror_resolution_row (panel, panel->current_output); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (listbox), row); ui = make_night_light_widget (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (vbox), ui); g_clear_object (&panel->rows_size_group); return vbox; } static void two_output_visible_child_changed (CcDisplayPanel *panel, GParamSpec *pspec, GtkWidget *stack) { GtkWidget *bin, *ui; g_autoptr(GList) children = NULL; GList *l; reset_current_config (panel); children = gtk_container_get_children (GTK_CONTAINER (stack)); for (l = children; l; l = l->next) { GtkWidget *ui = gtk_bin_get_child (GTK_BIN (l->data)); if (ui) gtk_widget_destroy (ui); } bin = gtk_stack_get_visible_child (GTK_STACK (stack)); if (g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (stack)), "mirror")) { ui = make_two_mirror_ui (panel); } else { gboolean single; GList *outputs, *l; if (cc_display_config_is_cloning (panel->current_config)) { cc_display_config_set_cloning (panel->current_config, FALSE); } single = g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (stack)), "single"); outputs = cc_display_config_get_monitors (panel->current_config); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; cc_display_monitor_set_active (output, (!single || output == panel->current_output)); } if (single) ui = make_two_single_ui (panel); else ui = make_two_join_ui (panel); } gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (bin), ui); ensure_monitor_labels (panel); update_apply_button (panel); } static gboolean transform_stack_to_button (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data) { GtkWidget *visible_child = g_value_get_object (from_value); GtkWidget *button_child = user_data; g_value_set_boolean (to_value, visible_child == button_child); return TRUE; } static gboolean transform_button_to_stack (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data) { GtkWidget *button_child = user_data; if (g_value_get_boolean (from_value)) g_value_set_object (to_value, button_child); return TRUE; } static void add_two_output_page (GtkWidget *switcher, GtkWidget *stack, const gchar *name, const gchar *title, const gchar *icon) { GtkWidget *button, *bin, *image; bin = make_bin (); gtk_widget_show (bin); gtk_stack_add_named (GTK_STACK (stack), bin, name); image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_LARGE_TOOLBAR); gtk_widget_show (image); g_object_set (G_OBJECT (image), "margin", HEADING_PADDING, NULL); button = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "image", image, "image-position", GTK_POS_LEFT, "always-show-image", TRUE, "label", title, NULL); gtk_widget_show (button); gtk_container_add (GTK_CONTAINER (switcher), button); g_object_bind_property_full (stack, "visible-child", button, "active", G_BINDING_BIDIRECTIONAL, transform_stack_to_button, transform_button_to_stack, bin, NULL); } static GtkWidget * make_two_output_ui (CcDisplayPanel *panel) { GtkWidget *vbox, *switcher, *stack, *bin, *label; gboolean show_mirror; show_mirror = g_list_length (cc_display_config_get_cloning_modes (panel->current_config)) > 0; vbox = make_main_vbox (panel->main_size_group); gtk_widget_show (vbox); label = make_bold_label (_("Display Mode")); gtk_widget_show (label); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_widget_set_margin_bottom (label, HEADING_PADDING); gtk_container_add (GTK_CONTAINER (vbox), label); switcher = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); gtk_widget_show (switcher); gtk_widget_set_margin_bottom (switcher, SECTION_PADDING); gtk_button_box_set_layout (GTK_BUTTON_BOX (switcher), GTK_BUTTONBOX_EXPAND); gtk_container_add (GTK_CONTAINER (vbox), switcher); stack = gtk_stack_new (); gtk_widget_show (stack); gtk_container_add (GTK_CONTAINER (vbox), stack); /* Add a dummy first stack page so that setting the visible child * below triggers a visible-child-name notification. */ bin = make_bin (); gtk_widget_show (bin); gtk_stack_add_named (GTK_STACK (stack), bin, "dummy"); add_two_output_page (switcher, stack, "join", _("Join Displays"), "video-joined-displays-symbolic"); if (show_mirror) add_two_output_page (switcher, stack, "mirror", _("Mirror"), "view-mirror-symbolic"); add_two_output_page (switcher, stack, "single", _("Single Display"), "video-single-display-symbolic"); g_signal_connect_object (stack, "notify::visible-child-name", G_CALLBACK (two_output_visible_child_changed), panel, G_CONNECT_SWAPPED); if (cc_display_config_is_cloning (panel->current_config) && show_mirror) gtk_stack_set_visible_child_name (GTK_STACK (stack), "mirror"); else if (cc_display_config_count_useful_monitors (panel->current_config) > 1) gtk_stack_set_visible_child_name (GTK_STACK (stack), "join"); else gtk_stack_set_visible_child_name (GTK_STACK (stack), "single"); return make_scrollable (vbox); } static void output_switch_active (CcDisplayPanel *panel, GParamSpec *pspec, GtkWidget *button) { cc_display_monitor_set_active (panel->current_output, gtk_switch_get_active (GTK_SWITCH (button))); update_apply_button (panel); } static void output_switch_sync (GtkWidget *button, CcDisplayMonitor *output) { gtk_switch_set_active (GTK_SWITCH (button), cc_display_monitor_is_active (output)); } static GtkWidget * make_output_switch (CcDisplayPanel *panel) { GtkWidget *button = gtk_switch_new (); g_signal_connect_object (button, "notify::active", G_CALLBACK (output_switch_active), panel, G_CONNECT_SWAPPED); g_signal_connect_object (panel->current_output, "active", G_CALLBACK (output_switch_sync), button, G_CONNECT_SWAPPED); output_switch_sync (button, panel->current_output); if ((cc_display_config_count_useful_monitors (panel->current_config) < 2 && cc_display_monitor_is_active (panel->current_output)) || !cc_display_monitor_is_usable (panel->current_output)) gtk_widget_set_sensitive (button, FALSE); return button; } static void replace_output_switch (GtkWidget *frame, CcDisplayPanel *panel) { GtkWidget *sw; gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (frame))); sw = make_output_switch (panel); gtk_widget_show (sw); gtk_container_add (GTK_CONTAINER (frame), sw); } static void output_chooser_row_activated (CcDisplayPanel *panel, GtkWidget *row) { CcDisplayMonitor *output = g_object_get_data (G_OBJECT (row), "output"); set_current_output (panel, output); } static void output_chooser_sync (GtkWidget *button, CcDisplayPanel *panel) { const gchar *text = cc_display_monitor_get_ui_number_name (panel->current_output); GtkWidget *label = gtk_bin_get_child (GTK_BIN (button)); gtk_label_set_text (GTK_LABEL (label), text); } static GtkWidget * make_output_chooser_button (CcDisplayPanel *panel) { GtkWidget *listbox, *button, *popover, *label; GList *outputs, *l; outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); listbox = make_list_box (); gtk_widget_show (listbox); for (l = outputs; l; l = l->next) { CcDisplayMonitor *output = l->data; GtkWidget *label, *row; const gchar *text; text = cc_display_monitor_get_ui_number_name (output); label = make_popover_label (text); gtk_widget_show (label); row = g_object_new (CC_TYPE_LIST_BOX_ROW, "child", label, NULL); gtk_widget_show (row); g_object_set_data (G_OBJECT (row), "output", output); g_signal_connect_object (row, "activated", G_CALLBACK (output_chooser_row_activated), panel, G_CONNECT_SWAPPED); gtk_container_add (GTK_CONTAINER (listbox), row); } popover = make_list_popover (listbox); button = gtk_menu_button_new (); label = make_bold_label (NULL); gtk_widget_show (label); gtk_container_add (GTK_CONTAINER (button), label); gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), popover); g_signal_connect_object (panel, "current-output", G_CALLBACK (output_chooser_sync), button, G_CONNECT_SWAPPED); output_chooser_sync (button, panel); return button; } static GtkWidget * make_multi_output_ui (CcDisplayPanel *panel) { GtkWidget *vbox, *ui, *frame, *sw, *hbox, *button; panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); vbox = make_main_vbox (panel->main_size_group); gtk_widget_show (vbox); ui = make_arrangement_ui (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (vbox), ui); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show (hbox); gtk_widget_set_margin_top (hbox, SECTION_PADDING); gtk_container_add (GTK_CONTAINER (vbox), hbox); button = make_output_chooser_button (panel); gtk_widget_show (button); gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); frame = make_bin (); gtk_widget_show (frame); gtk_box_pack_end (GTK_BOX (hbox), frame, FALSE, FALSE, 0); sw = make_output_switch (panel); gtk_widget_show (sw); gtk_container_add (GTK_CONTAINER (frame), sw); g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_output_switch), frame, G_CONNECT_SWAPPED); frame = make_frame (NULL, NULL); gtk_widget_show (frame); gtk_widget_set_margin_top (frame, HEADING_PADDING); gtk_container_add (GTK_CONTAINER (vbox), frame); ui = make_output_ui (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (frame), ui); g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_current_output_ui), frame, G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); gtk_container_add (GTK_CONTAINER (vbox), ui); g_clear_object (&panel->rows_size_group); return make_scrollable (vbox); } static void reset_current_config (CcDisplayPanel *panel) { GList *outputs, *l; CcDisplayConfig *current; g_clear_object (&panel->current_config); panel->current_output = NULL; current = cc_display_config_manager_get_current (panel->manager); if (!current) return; panel->current_config = current; 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); if (!cc_display_monitor_is_useful (output)) continue; panel->current_output = output; break; } } static void on_screen_changed (CcDisplayPanel *panel) { GtkWidget *main_widget; GList *outputs; guint n_outputs; if (!panel->manager) return; reset_titlebar (panel); main_widget = gtk_stack_get_child_by_name (GTK_STACK (panel->stack), "main"); if (main_widget) gtk_widget_destroy (main_widget); reset_current_config (panel); if (!panel->current_config) goto show_error; ensure_monitor_labels (panel); if (!panel->current_output) goto show_error; outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); n_outputs = g_list_length (outputs); if (panel->lid_is_closed) { if (n_outputs <= 2) main_widget = make_single_output_ui (panel); else main_widget = make_multi_output_ui (panel); } else { if (n_outputs == 1) main_widget = make_single_output_ui (panel); else if (n_outputs == 2) main_widget = make_two_output_ui (panel); else main_widget = make_multi_output_ui (panel); } gtk_widget_show (main_widget); gtk_stack_add_named (GTK_STACK (panel->stack), main_widget, "main"); gtk_stack_set_visible_child (GTK_STACK (panel->stack), main_widget); return; show_error: gtk_stack_set_visible_child_name (GTK_STACK (panel->stack), "error"); } 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; 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 const gchar * make_aspect_string (gint width, gint height) { int ratio; const gchar *aspect = NULL; /* We use a number of Unicode characters below: * ∶ is U+2236 RATIO *   is U+2009 THIN SPACE, * × is U+00D7 MULTIPLICATION SIGN */ if (width && height) { if (width > height) ratio = width * 10 / height; else ratio = height * 10 / width; switch (ratio) { case 13: aspect = "4∶3"; break; case 16: aspect = "16∶10"; break; case 17: aspect = "16∶9"; break; case 23: aspect = "21∶9"; break; case 12: aspect = "5∶4"; break; /* This catches 1.5625 as well (1600x1024) when maybe it shouldn't. */ case 15: aspect = "3∶2"; break; case 18: aspect = "9∶5"; break; case 10: aspect = "1∶1"; break; } } return aspect; } static char * make_resolution_string (CcDisplayMode *mode) { const char *interlaced = cc_display_mode_is_interlaced (mode) ? "i" : ""; const char *aspect; int width, height; cc_display_mode_get_resolution (mode, &width, &height); aspect = make_aspect_string (width, height); if (aspect != NULL) return g_strdup_printf ("%d × %d%s (%s)", width, height, interlaced, aspect); else return g_strdup_printf ("%d × %d%s", width, height, interlaced); } static const gchar * get_resolution_string (CcDisplayMode *mode) { char *resolution; if (!mode) return ""; resolution = g_object_get_data (G_OBJECT (mode), "resolution"); if (resolution) return resolution; resolution = make_resolution_string (mode); g_object_set_data_full (G_OBJECT (mode), "resolution", resolution, g_free); return resolution; } static const gchar * get_frequency_string (CcDisplayMode *mode) { char *frequency; if (!mode) return ""; frequency = g_object_get_data (G_OBJECT (mode), "frequency"); if (frequency) return frequency; frequency = g_strdup_printf (_("%.2lf Hz"), cc_display_mode_get_freq_f (mode)); g_object_set_data_full (G_OBJECT (mode), "frequency", frequency, g_free); return frequency; } static gboolean should_show_rotation (CcDisplayPanel *panel, CcDisplayMonitor *output) { gboolean supports_rotation; supports_rotation = cc_display_monitor_supports_rotation (output, CC_DISPLAY_ROTATION_90 | CC_DISPLAY_ROTATION_180 | CC_DISPLAY_ROTATION_270); /* Doesn't support rotation at all */ if (!supports_rotation) return FALSE; /* We can always rotate displays that aren't builtin */ if (!cc_display_monitor_is_builtin (output)) return TRUE; /* Only offer rotation if there's no accelerometer */ return !panel->has_accelerometer; } static void cc_display_panel_night_light_activated (CcDisplayPanel *panel) { GtkWindow *toplevel; toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))); gtk_window_set_transient_for (GTK_WINDOW (panel->night_light_dialog), toplevel); gtk_window_present (GTK_WINDOW (panel->night_light_dialog)); } 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_swapped (toplevel, "notify::has-toplevel-focus", G_CALLBACK (dialog_toplevel_focus_changed), panel); } static void cc_display_panel_up_client_changed (UpClient *client, GParamSpec *pspec, CcDisplayPanel *self) { gboolean lid_is_closed; lid_is_closed = up_client_get_lid_is_closed (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 update_has_accel (CcDisplayPanel *self) { g_autoptr(GVariant) v = NULL; if (self->iio_sensor_proxy == NULL) { g_debug ("Has no accelerometer"); self->has_accelerometer = FALSE; return; } v = g_dbus_proxy_get_cached_property (self->iio_sensor_proxy, "HasAccelerometer"); if (v) { self->has_accelerometer = g_variant_get_boolean (v); } else { self->has_accelerometer = FALSE; } g_debug ("Has %saccelerometer", self->has_accelerometer ? "" : "no "); } static void sensor_proxy_properties_changed_cb (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, CcDisplayPanel *self) { GVariantDict dict; g_variant_dict_init (&dict, changed_properties); if (g_variant_dict_contains (&dict, "HasAccelerometer")) update_has_accel (self); } static void sensor_proxy_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { CcDisplayPanel *self = user_data; g_debug ("SensorProxy appeared"); self->iio_sensor_proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, "net.hadess.SensorProxy", "/net/hadess/SensorProxy", "net.hadess.SensorProxy", NULL, NULL); g_return_if_fail (self->iio_sensor_proxy); g_signal_connect (self->iio_sensor_proxy, "g-properties-changed", G_CALLBACK (sensor_proxy_properties_changed_cb), self); update_has_accel (self); } static void sensor_proxy_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { CcDisplayPanel *self = user_data; g_debug ("SensorProxy vanished"); g_clear_object (&self->iio_sensor_proxy); update_has_accel (self); } static void night_light_sync_label (GtkWidget *label, GSettings *settings) { gboolean ret = g_settings_get_boolean (settings, "night-light-enabled"); gtk_label_set_label (GTK_LABEL (label), /* TRANSLATORS: the state of the night light setting */ ret ? _("On") : _("Off")); } static void settings_color_changed_cb (GSettings *settings, gchar *key, GtkWidget *label) { if (g_strcmp0 (key, "night-light-enabled") == 0) night_light_sync_label (label, settings); } static GtkWidget * make_night_light_widget (CcDisplayPanel *self) { GtkWidget *frame, *row, *label, *state_label; GtkWidget *night_light_listbox; frame = make_frame (NULL, NULL); night_light_listbox = make_list_box (); gtk_widget_show (night_light_listbox); gtk_container_add (GTK_CONTAINER (frame), night_light_listbox); label = gtk_label_new (_("_Night Light")); gtk_widget_show (label); gtk_label_set_use_underline (GTK_LABEL (label), TRUE); state_label = gtk_label_new (""); gtk_widget_show (state_label); g_signal_connect_object (self->settings_color, "changed", G_CALLBACK (settings_color_changed_cb), state_label, 0); night_light_sync_label (state_label, self->settings_color); row = make_row (self->rows_size_group, label, state_label); gtk_widget_show (row); gtk_container_add (GTK_CONTAINER (night_light_listbox), row); g_signal_connect_object (row, "activated", G_CALLBACK (cc_display_panel_night_light_activated), self, G_CONNECT_SWAPPED); gtk_widget_set_margin_top (frame, SECTION_PADDING); return frame; } 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); gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "error"); } 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; GtkWidget *bin, *label; g_resources_register (cc_display_get_resource ()); self->stack = gtk_stack_new (); gtk_widget_show (self->stack); bin = make_bin (); gtk_widget_show (bin); gtk_widget_set_size_request (bin, 500, -1); gtk_stack_add_named (GTK_STACK (self->stack), bin, "main-size-group"); self->main_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); gtk_size_group_add_widget (self->main_size_group, bin); label = gtk_label_new (_("Could not get screen information")); gtk_widget_show (label); gtk_stack_add_named (GTK_STACK (self->stack), label, "error"); gtk_container_add (GTK_CONTAINER (self), self->stack); self->night_light_dialog = cc_night_light_dialog_new (); self->settings_color = g_settings_new ("org.gnome.settings-daemon.plugins.color"); self->up_client = up_client_new (); if (up_client_get_lid_is_present (self->up_client)) { g_signal_connect (self->up_client, "notify::lid-is-closed", G_CALLBACK (cc_display_panel_up_client_changed), self); cc_display_panel_up_client_changed (self->up_client, NULL, self); } else g_clear_object (&self->up_client); g_signal_connect (self, "map", G_CALLBACK (mapped_cb), NULL); self->shell_cancellable = g_cancellable_new (); 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", self->shell_cancellable, (GAsyncReadyCallback) shell_proxy_ready, self); g_bus_get (G_BUS_TYPE_SESSION, self->shell_cancellable, (GAsyncReadyCallback) session_bus_ready, self); self->sensor_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, "net.hadess.SensorProxy", G_BUS_NAME_WATCHER_FLAGS_NONE, sensor_proxy_appeared_cb, sensor_proxy_vanished_cb, self, NULL); 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); }