gnome-control-center/panels/display/cc-display-panel.c
Robert Ancell 93b14a4339 panel: Move shared GCancellable code into panel class
Make the panel class provide a cancellable that will be cancelled when the panel
is destroyed. Panel implementations can use this and not have to mangage the
cancellable themselves. Consolidate cases where panels had multiple cancellables
that were all being used for this behaviour.
2020-02-03 09:36:24 +13:00

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