gnome-control-center/panels/display/cc-display-panel.c
2019-01-29 12:05:43 +01:00

1224 lines
39 KiB
C

/*
* 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>
#define HANDY_USE_UNSTABLE_API 1
#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-dialog.h"
#include "cc-display-resources.h"
#include "cc-display-settings.h"
/* The minimum supported size for the panel */
#define MINIMUM_WIDTH 740
#define MINIMUM_HEIGHT 530
#define PANEL_PADDING 32
#define SECTION_PADDING 32
#define HEADING_PADDING 12
typedef enum {
/*< flags >*/
CC_DISPLAY_CONFIG_SINGLE = 0x1,
CC_DISPLAY_CONFIG_JOIN = 0x2,
CC_DISPLAY_CONFIG_CLONE = 0x4,
} CcDisplayConfigType;
struct _CcDisplayPanel
{
CcPanel parent_instance;
CcDisplayConfigManager *manager;
CcDisplayConfig *current_config;
CcDisplayMonitor *current_output;
gboolean rebuilding;
CcDisplayArrangement *arrangement;
CcDisplaySettings *settings;
guint focus_id;
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;
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;
GtkLabel *night_light_status_label;
GtkSwitch *output_enabled_switch;
GtkComboBox *output_selection_combo;
GtkStack *output_selection_stack;
GtkButtonBox *output_selection_two_first;
GtkButtonBox *output_selection_two_second;
HdyComboRow *primary_display_row;
};
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_find_types (CcDisplayPanel *panel)
{
CcDisplayConfigType types = 0;
guint n_outputs, n_active_outputs;
GList *outputs, *l;
outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config);
n_outputs = g_list_length (outputs);
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_outputs > (panel->lid_is_closed ? 2 : 1))
{
if (cc_display_config_is_cloning (panel->current_config))
types |= CC_DISPLAY_CONFIG_CLONE;
else
{
types |= CC_DISPLAY_CONFIG_JOIN;
if (n_active_outputs == 1)
types |= CC_DISPLAY_CONFIG_SINGLE;
}
}
else
{
types |= CC_DISPLAY_CONFIG_SINGLE;
}
return types;
}
static CcDisplayConfigType
config_select_type (CcDisplayPanel *panel)
{
CcDisplayConfigType types = config_find_types (panel);
if (types & CC_DISPLAY_CONFIG_CLONE)
return CC_DISPLAY_CONFIG_CLONE;
if (types & CC_DISPLAY_CONFIG_SINGLE)
return CC_DISPLAY_CONFIG_SINGLE;
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
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 ();
}
}
static void
config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type)
{
CcDisplayConfigType types = config_find_types (panel);
GList *outputs, *l;
if (type == CC_DISPLAY_CONFIG_SINGLE && (types & CC_DISPLAY_CONFIG_SINGLE))
return;
/* Already compatible and not just "single" mode. */
if (!(types & CC_DISPLAY_CONFIG_SINGLE) && (types & 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);
set_current_output (panel, output, FALSE);
}
else
{
cc_display_monitor_set_active (output, FALSE);
}
}
break;
case CC_DISPLAY_CONFIG_JOIN:
g_debug ("Creating new join config");
/* Enable all usable outputs */
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));
break;
}
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
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_cancellable_cancel (self->shell_cancellable);
g_clear_object (&self->shell_cancellable);
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)
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))));
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;
/* Changing the active state requires a UI rebuild. */
cc_display_monitor_set_active (panel->current_output, active);
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)
{
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn)))
return;
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)
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, 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);
GtkWidgetClass *widget_class = GTK_WIDGET_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;
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, night_light_status_label);
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_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_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;
if (changed && cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_SINGLE)
{
if (output)
cc_display_monitor_set_active (output, TRUE);
if (panel->current_output)
cc_display_monitor_set_active (panel->current_output, FALSE);
}
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));
}
else
{
gtk_label_set_text (panel->current_output_label, "");
gtk_switch_set_active (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);
}
}
static void
rebuild_ui (CcDisplayPanel *panel)
{
guint n_outputs, n_active_outputs, n_usable_outputs;
GList *outputs, *l;
panel->rebuilding = TRUE;
g_list_store_remove_all (panel->primary_display_list);
gtk_list_store_clear (panel->output_selection_list);
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;
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);
}
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_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);
/* We only show the top chooser with two outputs (and never if the lid is closed).
* And only in that mode do we allow mirroring. */
if (n_outputs == 2 && !panel->lid_is_closed)
{
CcDisplayConfigType types, type;
gtk_widget_set_visible (panel->config_type_switcher_frame, TRUE);
type = cc_panel_get_selected_type (panel);
types = config_find_types (panel);
if (!(type & types))
cc_panel_set_selected_type (panel, config_select_type (panel));
}
else
{
gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE);
if (n_outputs == (panel->lid_is_closed ? 2 : 1))
cc_panel_set_selected_type (panel, CC_DISPLAY_CONFIG_SINGLE);
else
cc_panel_set_selected_type (panel, CC_DISPLAY_CONFIG_JOIN);
}
gtk_widget_set_visible (panel->arrangement_frame, cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_JOIN);
if (panel->lid_is_closed)
{
if (n_outputs <= 2 || cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_CLONE)
gtk_stack_set_visible_child_name (panel->output_selection_stack, "no-selection");
else
gtk_stack_set_visible_child_name (panel->output_selection_stack, "multi-selection");
}
else
{
if (n_outputs == 1 || cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_CLONE)
gtk_stack_set_visible_child_name (panel->output_selection_stack, "no-selection");
else if (n_outputs == 2)
gtk_stack_set_visible_child_name (panel->output_selection_stack, "two-selection");
else
gtk_stack_set_visible_child_name (panel->output_selection_stack, "multi-selection");
}
panel->rebuilding = FALSE;
update_apply_button (panel);
}
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);
panel->current_config = 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;
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_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;
cc_display_settings_set_has_accelerometer (self->settings, self->has_accelerometer);
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;
}
cc_display_settings_set_has_accelerometer (self->settings, self->has_accelerometer);
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 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->night_light_dialog = cc_night_light_dialog_new ();
self->settings_color = g_settings_new ("org.gnome.settings-daemon.plugins.color");
g_signal_connect_object (self->settings_color, "changed",
G_CALLBACK (settings_color_changed_cb), self->night_light_status_label, 0);
night_light_sync_label (GTK_WIDGET (self->night_light_status_label), self->settings_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);
}