GCC Code Coverage Report


Directory: ./
File: panels/display/cc-display-panel.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 499 0.0%
Functions: 0 44 0.0%
Branches: 0 205 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (C) 2007, 2008 Red Hat, Inc.
3 * Copyright (C) 2013 Intel, Inc.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 */
20
21 #include "cc-display-panel.h"
22
23 #include <gtk/gtk.h>
24 #include <glib/gi18n.h>
25 #include <stdlib.h>
26 #include <gdesktop-enums.h>
27 #include <math.h>
28
29 #include "shell/cc-object-storage.h"
30 #include <libupower-glib/upower.h>
31
32 #include "cc-list-row.h"
33 #include "cc-display-config-manager-dbus.h"
34 #include "cc-display-config.h"
35 #include "cc-display-arrangement.h"
36 #include "cc-night-light-page.h"
37 #include "cc-display-resources.h"
38 #include "cc-display-settings.h"
39
40 /* The minimum supported size for the panel
41 * Note that WIDTH is assumed to be the larger size and we accept portrait
42 * mode too effectively (in principle we should probably restrict the rotation
43 * setting in that case). */
44 #define MINIMUM_WIDTH 720
45 #define MINIMUM_HEIGHT 360
46
47 #define PANEL_PADDING 32
48 #define SECTION_PADDING 32
49 #define HEADING_PADDING 12
50
51 #define DISPLAY_SCHEMA "org.gnome.settings-daemon.plugins.color"
52
53 typedef enum {
54 CC_DISPLAY_CONFIG_JOIN,
55 CC_DISPLAY_CONFIG_CLONE,
56
57 CC_DISPLAY_CONFIG_INVALID_NONE,
58 } CcDisplayConfigType;
59
60 #define CC_DISPLAY_CONFIG_LAST_VALID CC_DISPLAY_CONFIG_CLONE
61
62 struct _CcDisplayPanel
63 {
64 CcPanel parent_instance;
65
66 CcDisplayConfigManager *manager;
67 CcDisplayConfig *current_config;
68 CcDisplayMonitor *current_output;
69
70 gint rebuilding_counter;
71
72 CcDisplayArrangement *arrangement;
73 CcDisplaySettings *settings;
74
75 guint focus_id;
76
77 CcNightLightPage *night_light_page;
78 CcListRow *night_light_row;
79
80 UpClient *up_client;
81 gboolean lid_is_closed;
82
83 GDBusProxy *shell_proxy;
84
85 GtkWidget *apply_button;
86 GtkWidget *cancel_button;
87 AdwWindowTitle *apply_titlebar_title_widget;
88 gboolean showing_apply_titlebar;
89
90 GListStore *primary_display_list;
91 GList *monitor_rows;
92
93 GtkWidget *display_settings_disabled_group;
94
95 GtkWidget *arrangement_row;
96 AdwBin *arrangement_bin;
97 GtkToggleButton *config_type_join;
98 GtkToggleButton *config_type_mirror;
99 GtkWidget *display_multiple_displays;
100 AdwBin *display_settings_bin;
101 GtkWidget *display_settings_group;
102 AdwNavigationPage *display_settings_page;
103 AdwNavigationView *nav_view;
104 AdwComboRow *primary_display_row;
105 AdwPreferencesGroup *single_display_settings_group;
106
107 GtkShortcut *escape_shortcut;
108
109 GSettings *display_settings;
110 };
111
112 enum {
113 PROP_0,
114 PROP_SHOWING_APPLY_TITLEBAR,
115 };
116
117 CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel)
118
119 static void
120 update_apply_button (CcDisplayPanel *self);
121 static void
122 apply_current_configuration (CcDisplayPanel *self);
123 static void
124 cancel_current_configuration (CcDisplayPanel *panel);
125 static void
126 reset_current_config (CcDisplayPanel *self);
127 static void
128 rebuild_ui (CcDisplayPanel *self);
129 static void
130 set_current_output (CcDisplayPanel *self,
131 CcDisplayMonitor *output,
132 gboolean force);
133 static void
134 on_screen_changed (CcDisplayPanel *self);
135
136
137 static CcDisplayConfigType
138 config_get_current_type (CcDisplayPanel *self)
139 {
140 guint n_active_outputs;
141 GList *outputs, *l;
142
143 outputs = cc_display_config_get_ui_sorted_monitors (self->current_config);
144 n_active_outputs = 0;
145 for (l = outputs; l; l = l->next)
146 {
147 CcDisplayMonitor *output = l->data;
148
149 if (cc_display_monitor_is_useful (output))
150 n_active_outputs += 1;
151 }
152
153 if (n_active_outputs == 0)
154 return CC_DISPLAY_CONFIG_INVALID_NONE;
155
156 if (cc_display_config_is_cloning (self->current_config))
157 return CC_DISPLAY_CONFIG_CLONE;
158
159 return CC_DISPLAY_CONFIG_JOIN;
160 }
161
162 static CcDisplayConfigType
163 cc_panel_get_selected_type (CcDisplayPanel *self)
164 {
165 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->config_type_join)))
166 return CC_DISPLAY_CONFIG_JOIN;
167 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->config_type_mirror)))
168 return CC_DISPLAY_CONFIG_CLONE;
169 else
170 g_assert_not_reached ();
171 }
172
173 static CcDisplayMode *
174 find_preferred_mode (GList *modes)
175 {
176 GList *l;
177
178 for (l = modes; l; l = l->next)
179 {
180 CcDisplayMode *mode = l->data;
181
182 if (cc_display_mode_is_preferred (mode))
183 return mode;
184 }
185
186 return NULL;
187 }
188
189 static void
190 config_ensure_of_type (CcDisplayPanel *self, CcDisplayConfigType type)
191 {
192 CcDisplayConfigType current_type = config_get_current_type (self);
193 GList *outputs, *l;
194 CcDisplayMonitor *current_primary = NULL;
195 gdouble old_primary_scale = -1;
196
197 /* Do not do anything if the current detected configuration type is
198 * identitcal to what we expect. */
199 if (type == current_type)
200 return;
201
202 reset_current_config (self);
203
204 outputs = cc_display_config_get_ui_sorted_monitors (self->current_config);
205 for (l = outputs; l; l = l->next)
206 {
207 CcDisplayMonitor *output = l->data;
208
209 if (cc_display_monitor_is_primary (output))
210 {
211 current_primary = output;
212 old_primary_scale = cc_display_monitor_get_scale (current_primary);
213 break;
214 }
215 }
216
217 switch (type)
218 {
219 case CC_DISPLAY_CONFIG_JOIN:
220 g_debug ("Creating new join config");
221 /* Enable all usable outputs
222 * Note that this might result in invalid configurations as we
223 * we might not be able to drive all attached monitors. */
224 cc_display_config_set_cloning (self->current_config, FALSE);
225 for (l = outputs; l; l = l->next)
226 {
227 CcDisplayMonitor *output = l->data;
228 gdouble scale;
229 CcDisplayMode *mode;
230
231 mode = cc_display_monitor_get_preferred_mode (output);
232 /* If the monitor was active, try using the current scale, otherwise
233 * try picking the preferred scale. */
234 if (cc_display_monitor_is_active (output))
235 scale = cc_display_monitor_get_scale (output);
236 else
237 scale = cc_display_mode_get_preferred_scale (mode);
238
239 /* If we cannot use the current/preferred scale, try to fall back to
240 * the previously scale of the primary monitor instead.
241 * This is not guaranteed to result in a valid configuration! */
242 if (!cc_display_config_is_scaled_mode_valid (self->current_config,
243 mode,
244 scale))
245 {
246 if (current_primary &&
247 cc_display_config_is_scaled_mode_valid (self->current_config,
248 mode,
249 old_primary_scale))
250 scale = old_primary_scale;
251 }
252
253 cc_display_monitor_set_active (output, cc_display_monitor_is_usable (output));
254 cc_display_monitor_set_mode (output, mode);
255 cc_display_monitor_set_scale (output, scale);
256 }
257 break;
258
259 case CC_DISPLAY_CONFIG_CLONE:
260 {
261 g_debug ("Creating new clone config");
262 gdouble scale;
263 g_autolist(CcDisplayMode) modes = NULL;
264 CcDisplayMode *clone_mode;
265
266 /* Turn on cloning and select the best mode we can find by default */
267 cc_display_config_set_cloning (self->current_config, TRUE);
268
269 modes = cc_display_config_generate_cloning_modes (self->current_config);
270 clone_mode = find_preferred_mode (modes);
271 g_return_if_fail (clone_mode);
272
273 /* Take the preferred scale by default, */
274 scale = cc_display_mode_get_preferred_scale (clone_mode);
275 /* but prefer the old primary scale if that is valid. */
276 if (current_primary &&
277 cc_display_config_is_scaled_mode_valid (self->current_config,
278 clone_mode,
279 old_primary_scale))
280 scale = old_primary_scale;
281
282 for (l = outputs; l; l = l->next)
283 {
284 CcDisplayMonitor *output = l->data;
285
286 cc_display_monitor_set_compatible_clone_mode (output, clone_mode);
287 cc_display_monitor_set_scale (output, scale);
288 }
289 }
290 break;
291
292 default:
293 g_assert_not_reached ();
294 }
295
296 if (!self->rebuilding_counter)
297 rebuild_ui (self);
298 }
299
300 static void
301 cc_panel_set_selected_type (CcDisplayPanel *self, CcDisplayConfigType type)
302 {
303 switch (type)
304 {
305 case CC_DISPLAY_CONFIG_JOIN:
306 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->config_type_join), TRUE);
307 break;
308 case CC_DISPLAY_CONFIG_CLONE:
309 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->config_type_mirror), TRUE);
310 break;
311 default:
312 g_assert_not_reached ();
313 }
314
315 config_ensure_of_type (self, type);
316 }
317
318 static void
319 monitor_labeler_hide (CcDisplayPanel *self)
320 {
321 if (!self->shell_proxy)
322 return;
323
324 g_dbus_proxy_call (self->shell_proxy,
325 "HideMonitorLabels",
326 NULL, G_DBUS_CALL_FLAGS_NONE,
327 -1, NULL, NULL, NULL);
328 }
329
330 static void
331 monitor_labeler_show (CcDisplayPanel *self)
332 {
333 GList *outputs, *l;
334 GVariantBuilder builder;
335 gint number = 0;
336
337 if (!self->shell_proxy || !self->current_config)
338 return;
339
340 outputs = cc_display_config_get_ui_sorted_monitors (self->current_config);
341 if (!outputs)
342 return;
343
344 if (cc_display_config_is_cloning (self->current_config))
345 return monitor_labeler_hide (self);
346
347 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
348 g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
349
350 for (l = outputs; l != NULL; l = l->next)
351 {
352 CcDisplayMonitor *output = l->data;
353
354 number = cc_display_monitor_get_ui_number (output);
355 if (number == 0)
356 continue;
357
358 g_variant_builder_add (&builder, "{sv}",
359 cc_display_monitor_get_connector_name (output),
360 g_variant_new_int32 (number));
361 }
362
363 g_variant_builder_close (&builder);
364
365 if (number < 2)
366 {
367 g_variant_builder_clear (&builder);
368 return monitor_labeler_hide (self);
369 }
370
371 g_dbus_proxy_call (self->shell_proxy,
372 "ShowMonitorLabels",
373 g_variant_builder_end (&builder),
374 G_DBUS_CALL_FLAGS_NONE,
375 -1, NULL, NULL, NULL);
376 }
377
378 static void
379 ensure_monitor_labels (CcDisplayPanel *self)
380 {
381 g_autoptr(GList) windows = NULL;
382 GList *w;
383
384 windows = gtk_window_list_toplevels ();
385
386 for (w = windows; w; w = w->next)
387 {
388 if (gtk_window_is_active (GTK_WINDOW (w->data)))
389 {
390 monitor_labeler_show (self);
391 break;
392 }
393 }
394
395 if (!w)
396 monitor_labeler_hide (self);
397 }
398
399 static void
400 dialog_toplevel_is_active_changed (CcDisplayPanel *self)
401 {
402 ensure_monitor_labels (self);
403 }
404
405 static void
406 reset_titlebar (CcDisplayPanel *self)
407 {
408 self->showing_apply_titlebar = FALSE;
409 g_object_notify (G_OBJECT (self), "showing-apply-titlebar");
410 }
411
412 static void
413 active_panel_changed (CcPanel *self)
414 {
415 CcShell *shell;
416 g_autoptr(CcPanel) panel = NULL;
417
418 shell = cc_panel_get_shell (CC_PANEL (self));
419 g_object_get (shell, "active-panel", &panel, NULL);
420 if (panel != self)
421 reset_titlebar (CC_DISPLAY_PANEL (self));
422 }
423
424 static void
425 cc_display_panel_dispose (GObject *object)
426 {
427 CcDisplayPanel *self = CC_DISPLAY_PANEL (object);
428 GtkWidget *toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)));
429
430 reset_titlebar (CC_DISPLAY_PANEL (object));
431
432 if (self->focus_id)
433 {
434 self->focus_id = 0;
435 monitor_labeler_hide (CC_DISPLAY_PANEL (object));
436 }
437
438 g_clear_pointer (&self->monitor_rows, g_list_free);
439 g_clear_object (&self->manager);
440 g_clear_object (&self->current_config);
441 g_clear_object (&self->up_client);
442
443 g_clear_object (&self->shell_proxy);
444
445 g_signal_handlers_disconnect_by_data (toplevel, self);
446
447 G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object);
448 }
449
450 static void
451 cc_display_panel_get_property (GObject *object,
452 guint prop_id,
453 GValue *value,
454 GParamSpec *pspec)
455 {
456 CcDisplayPanel *self = CC_DISPLAY_PANEL (object);
457
458 switch (prop_id) {
459 case PROP_SHOWING_APPLY_TITLEBAR:
460 g_value_set_boolean (value, self->showing_apply_titlebar);
461 break;
462 default:
463 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
464 break;
465 }
466 }
467
468 static void
469 on_arrangement_selected_ouptut_changed_cb (CcDisplayPanel *self)
470 {
471 set_current_output (self, cc_display_arrangement_get_selected_output (self->arrangement), FALSE);
472 }
473
474 static void
475 on_monitor_settings_updated_cb (CcDisplayPanel *self,
476 CcDisplayMonitor *monitor,
477 CcDisplaySettings *settings)
478 {
479 if (monitor)
480 cc_display_config_snap_outputs (self->current_config);
481 update_apply_button (self);
482 }
483
484 static void
485 on_config_type_toggled_cb (CcDisplayPanel *self,
486 GtkCheckButton *btn)
487 {
488 CcDisplayConfigType type;
489
490 if (self->rebuilding_counter > 0)
491 return;
492
493 if (!self->current_config)
494 return;
495
496 if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn)))
497 return;
498
499 type = cc_panel_get_selected_type (self);
500 config_ensure_of_type (self, type);
501 }
502
503 static void
504 on_night_light_enabled_changed_cb (CcDisplayPanel *self)
505 {
506 if (g_settings_get_boolean (self->display_settings, "night-light-enabled"))
507 cc_list_row_set_secondary_label (self->night_light_row, _("On"));
508 else
509 cc_list_row_set_secondary_label (self->night_light_row, _("Off"));
510 }
511
512 static void
513 on_primary_display_selected_item_changed_cb (CcDisplayPanel *self)
514 {
515 gint idx = adw_combo_row_get_selected (self->primary_display_row);
516 g_autoptr(CcDisplayMonitor) output = NULL;
517
518 if (idx < 0 || self->rebuilding_counter > 0)
519 return;
520
521 output = g_list_model_get_item (G_LIST_MODEL (self->primary_display_list), idx);
522
523 if (cc_display_monitor_is_primary (output))
524 return;
525
526 cc_display_monitor_set_primary (output, TRUE);
527 update_apply_button (self);
528 }
529
530 static void
531 on_toplevel_collapsed (CcDisplayPanel *self, GParamSpec *pspec, GtkWidget *toplevel)
532 {
533 gboolean collapsed;
534
535 g_object_get (toplevel, "collapsed", &collapsed, NULL);
536 cc_display_settings_refresh_layout (self->settings, collapsed);
537 }
538
539 static gboolean
540 on_toplevel_escape_pressed_cb (GtkWidget *widget,
541 GVariant *args,
542 CcDisplayPanel *self)
543 {
544 if (self->showing_apply_titlebar)
545 {
546 gtk_widget_activate (self->cancel_button);
547 return GDK_EVENT_STOP;
548 }
549
550 return GDK_EVENT_PROPAGATE;
551 }
552
553 static void
554 cc_display_panel_constructed (GObject *object)
555 {
556 CcShell *shell = cc_panel_get_shell (CC_PANEL (object));
557 GtkWidget *toplevel = cc_shell_get_toplevel (shell);
558
559 g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel",
560 G_CALLBACK (active_panel_changed), object, G_CONNECT_SWAPPED);
561
562 g_signal_connect_swapped (toplevel, "notify::collapsed", G_CALLBACK (on_toplevel_collapsed), object);
563 on_toplevel_collapsed (CC_DISPLAY_PANEL (object), NULL, toplevel);
564
565 G_OBJECT_CLASS (cc_display_panel_parent_class)->constructed (object);
566 }
567
568 static const char *
569 cc_display_panel_get_help_uri (CcPanel *panel)
570 {
571 return "help:gnome-help/prefs-display";
572 }
573
574 static void
575 cc_display_panel_class_init (CcDisplayPanelClass *klass)
576 {
577 GObjectClass *object_class = G_OBJECT_CLASS (klass);
578 CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
579 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
580
581 g_type_ensure (CC_TYPE_LIST_ROW);
582 g_type_ensure (CC_TYPE_NIGHT_LIGHT_PAGE);
583
584 panel_class->get_help_uri = cc_display_panel_get_help_uri;
585
586 object_class->constructed = cc_display_panel_constructed;
587 object_class->dispose = cc_display_panel_dispose;
588 object_class->get_property = cc_display_panel_get_property;
589
590 g_object_class_install_property (object_class,
591 PROP_SHOWING_APPLY_TITLEBAR,
592 g_param_spec_boolean ("showing-apply-titlebar",
593 NULL,
594 NULL,
595 FALSE,
596 G_PARAM_READABLE));
597
598 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-panel.ui");
599
600 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, apply_button);
601 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, apply_titlebar_title_widget);
602 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_disabled_group);
603 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_bin);
604 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_row);
605 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, cancel_button);
606 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_multiple_displays);
607 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_join);
608 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_mirror);
609 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_bin);
610 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_group);
611 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_page);
612 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, escape_shortcut);
613 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, nav_view);
614 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_page);
615 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_row);
616 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, primary_display_row);
617 gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, single_display_settings_group);
618
619 gtk_widget_class_bind_template_callback (widget_class, apply_current_configuration);
620 gtk_widget_class_bind_template_callback (widget_class, cancel_current_configuration);
621 gtk_widget_class_bind_template_callback (widget_class, on_config_type_toggled_cb);
622 gtk_widget_class_bind_template_callback (widget_class, on_primary_display_selected_item_changed_cb);
623 gtk_widget_class_bind_template_callback (widget_class, on_screen_changed);
624 gtk_widget_class_bind_template_callback (widget_class, on_toplevel_escape_pressed_cb);
625 }
626
627 static void
628 set_current_output (CcDisplayPanel *self,
629 CcDisplayMonitor *output,
630 gboolean force)
631 {
632 gboolean changed;
633
634 /* Note, this function is also called if the internal UI needs updating after a rebuild. */
635 changed = (output != self->current_output);
636
637 if (!changed && !force)
638 return;
639
640 self->rebuilding_counter++;
641
642 self->current_output = output;
643
644 if (changed)
645 {
646 cc_display_settings_set_selected_output (self->settings, self->current_output);
647 cc_display_arrangement_set_selected_output (self->arrangement, self->current_output);
648
649 adw_navigation_page_set_title (self->display_settings_page,
650 output ? cc_display_monitor_get_ui_name (output) : "");
651 }
652
653 self->rebuilding_counter--;
654 }
655
656 static void
657 on_monitor_row_activated_cb (CcDisplayPanel *self,
658 GtkListBoxRow *row)
659 {
660 CcDisplayMonitor *monitor;
661
662 monitor = g_object_get_data (G_OBJECT (row), "monitor");
663 set_current_output (self, monitor, FALSE);
664
665 adw_navigation_view_push (self->nav_view, self->display_settings_page);
666 }
667
668 static void
669 add_display_row (CcDisplayPanel *self,
670 CcDisplayMonitor *monitor)
671 {
672 g_autofree gchar *number_string = NULL;
673 GtkWidget *number_label;
674 GtkWidget *icon;
675 GtkWidget *row;
676
677 row = adw_action_row_new ();
678 g_object_set_data (G_OBJECT (row), "monitor", monitor);
679 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row),
680 cc_display_monitor_get_ui_name (monitor));
681
682 number_string = g_strdup_printf ("%d", cc_display_monitor_get_ui_number (monitor));
683 number_label = gtk_label_new (number_string);
684 gtk_widget_set_valign (number_label, GTK_ALIGN_CENTER);
685 gtk_widget_set_halign (number_label, GTK_ALIGN_CENTER);
686 gtk_widget_add_css_class (number_label, "monitor-label");
687 adw_action_row_add_prefix (ADW_ACTION_ROW (row), number_label);
688
689 icon = gtk_image_new_from_icon_name ("go-next-symbolic");
690 adw_action_row_add_suffix (ADW_ACTION_ROW (row), icon);
691 adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), icon);
692
693 adw_preferences_group_add (ADW_PREFERENCES_GROUP (self->display_settings_group), row);
694
695 g_signal_connect_swapped (row, "activated", G_CALLBACK (on_monitor_row_activated_cb), self);
696
697 self->monitor_rows = g_list_prepend (self->monitor_rows, row);
698 }
699
700 static void
701 move_display_settings_to_main_page (CcDisplayPanel *self)
702 {
703 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self->settings));
704
705 if (parent != GTK_WIDGET (self->display_settings_bin))
706 return;
707
708 g_object_ref (self->settings);
709 adw_bin_set_child (self->display_settings_bin, NULL);
710 adw_preferences_group_add (self->single_display_settings_group,
711 GTK_WIDGET (self->settings));
712 g_object_unref (self->settings);
713
714 gtk_widget_set_visible (GTK_WIDGET (self->single_display_settings_group), TRUE);
715 }
716
717 static void
718 move_display_settings_to_separate_page (CcDisplayPanel *self)
719 {
720 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self->settings));
721
722 if (parent == GTK_WIDGET (self->display_settings_bin))
723 return;
724
725 g_object_ref (self->settings);
726 adw_preferences_group_remove (self->single_display_settings_group,
727 GTK_WIDGET (self->settings));
728 adw_bin_set_child (self->display_settings_bin,
729 GTK_WIDGET (self->settings));
730 g_object_unref (self->settings);
731
732 gtk_widget_set_visible (GTK_WIDGET (self->single_display_settings_group), FALSE);
733 }
734
735 static void
736 rebuild_ui (CcDisplayPanel *self)
737 {
738 guint n_active_outputs, n_usable_outputs;
739 GList *outputs, *l;
740 CcDisplayConfigType type;
741
742 if (!cc_display_config_manager_get_apply_allowed (self->manager))
743 {
744 gtk_widget_set_visible (self->display_settings_disabled_group, TRUE);
745 gtk_widget_set_visible (self->display_settings_group, FALSE);
746 gtk_widget_set_visible (self->arrangement_row, FALSE);
747 return;
748 }
749
750 self->rebuilding_counter++;
751
752 g_list_store_remove_all (self->primary_display_list);
753
754 /* Remove all monitor rows */
755 while (self->monitor_rows)
756 {
757 adw_preferences_group_remove (ADW_PREFERENCES_GROUP (self->display_settings_group),
758 self->monitor_rows->data);
759 self->monitor_rows = g_list_remove_link (self->monitor_rows, self->monitor_rows);
760 }
761
762 if (!self->current_config)
763 {
764 self->rebuilding_counter--;
765 return;
766 }
767
768 gtk_widget_set_visible (self->display_settings_disabled_group, FALSE);
769
770 n_active_outputs = 0;
771 n_usable_outputs = 0;
772 outputs = cc_display_config_get_ui_sorted_monitors (self->current_config);
773 for (l = outputs; l; l = l->next)
774 {
775 CcDisplayMonitor *output = l->data;
776
777 if (!cc_display_monitor_is_usable (output))
778 continue;
779
780 n_usable_outputs += 1;
781
782 if (cc_display_monitor_is_active (output))
783 {
784 n_active_outputs += 1;
785
786 g_list_store_append (self->primary_display_list, output);
787 if (cc_display_monitor_is_primary (output))
788 adw_combo_row_set_selected (self->primary_display_row,
789 g_list_model_get_n_items (G_LIST_MODEL (self->primary_display_list)) - 1);
790
791 /* Ensure that an output is selected; note that this doesn't ensure
792 * the selected output is any useful (i.e. when switching types).
793 */
794 if (!self->current_output)
795 set_current_output (self, output, FALSE);
796 }
797
798 add_display_row (self, l->data);
799 }
800
801 /* Sync the rebuild lists/buttons */
802 set_current_output (self, self->current_output, TRUE);
803
804 type = config_get_current_type (self);
805
806 if (n_usable_outputs > 1)
807 {
808 if (type > CC_DISPLAY_CONFIG_LAST_VALID)
809 type = CC_DISPLAY_CONFIG_JOIN;
810
811 gtk_widget_set_visible (self->display_settings_group, type == CC_DISPLAY_CONFIG_JOIN);
812 gtk_widget_set_visible (self->display_multiple_displays, TRUE);
813 gtk_widget_set_visible (self->arrangement_row, type == CC_DISPLAY_CONFIG_JOIN);
814
815 if (type == CC_DISPLAY_CONFIG_CLONE)
816 move_display_settings_to_main_page (self);
817 else
818 move_display_settings_to_separate_page (self);
819 }
820 else
821 {
822 type = CC_DISPLAY_CONFIG_JOIN;
823
824 gtk_widget_set_visible (self->display_settings_group, FALSE);
825 gtk_widget_set_visible (self->display_multiple_displays, FALSE);
826 gtk_widget_set_visible (self->arrangement_row, FALSE);
827
828 move_display_settings_to_main_page (self);
829 }
830
831 cc_display_settings_set_multimonitor (self->settings,
832 n_usable_outputs > 1 &&
833 type != CC_DISPLAY_CONFIG_CLONE);
834
835 cc_panel_set_selected_type (self, type);
836
837 self->rebuilding_counter--;
838 update_apply_button (self);
839 }
840
841 static void
842 update_panel_orientation_managed (CcDisplayPanel *self,
843 gboolean managed)
844 {
845 cc_display_settings_set_has_accelerometer (self->settings, managed);
846 }
847
848 static void
849 reset_current_config (CcDisplayPanel *self)
850 {
851 CcDisplayConfig *current;
852 CcDisplayConfig *old;
853 GList *outputs, *l;
854
855 g_debug ("Resetting current config!");
856
857 /* We need to hold on to the config until all display references are dropped. */
858 old = self->current_config;
859 self->current_output = NULL;
860
861 current = cc_display_config_manager_get_current (self->manager);
862
863 if (!current)
864 return;
865
866 cc_display_config_set_minimum_size (current, MINIMUM_WIDTH, MINIMUM_HEIGHT);
867 self->current_config = current;
868
869 g_signal_connect_object (current, "panel-orientation-managed",
870 G_CALLBACK (update_panel_orientation_managed), self,
871 G_CONNECT_SWAPPED);
872 update_panel_orientation_managed (self,
873 cc_display_config_get_panel_orientation_managed (current));
874
875 g_list_store_remove_all (self->primary_display_list);
876
877 if (self->current_config)
878 {
879 outputs = cc_display_config_get_ui_sorted_monitors (self->current_config);
880 for (l = outputs; l; l = l->next)
881 {
882 CcDisplayMonitor *output = l->data;
883
884 /* Mark any builtin monitor as unusable if the lid is closed. */
885 if (cc_display_monitor_is_builtin (output) && self->lid_is_closed)
886 cc_display_monitor_set_usable (output, FALSE);
887 }
888
889 /* Recalculate UI numbers after the monitor usability is determined to skip numbering gaps. */
890 cc_display_config_update_ui_numbers_names(self->current_config);
891 }
892
893 cc_display_arrangement_set_config (self->arrangement, self->current_config);
894 cc_display_settings_set_config (self->settings, self->current_config);
895 set_current_output (self, NULL, FALSE);
896
897 g_clear_object (&old);
898
899 update_apply_button (self);
900 }
901
902 static void
903 on_screen_changed (CcDisplayPanel *self)
904 {
905 if (!self->manager)
906 return;
907
908 reset_titlebar (self);
909
910 reset_current_config (self);
911 rebuild_ui (self);
912
913 if (!self->current_config)
914 return;
915
916 ensure_monitor_labels (self);
917 }
918
919 static void
920 show_apply_titlebar (CcDisplayPanel *self, gboolean is_applicable)
921 {
922 gtk_widget_set_sensitive (self->apply_button, is_applicable);
923
924 if (is_applicable)
925 {
926 adw_window_title_set_title (self->apply_titlebar_title_widget,
927 _("Apply Changes?"));
928 adw_window_title_set_subtitle (self->apply_titlebar_title_widget, "");
929 }
930 else
931 {
932 adw_window_title_set_title (self->apply_titlebar_title_widget,
933 _("Changes Cannot be Applied"));
934 adw_window_title_set_subtitle (self->apply_titlebar_title_widget,
935 _("This could be due to hardware limitations."));
936 }
937
938 self->showing_apply_titlebar = TRUE;
939 g_object_notify (G_OBJECT (self), "showing-apply-titlebar");
940 }
941
942 static void
943 update_apply_button (CcDisplayPanel *self)
944 {
945 gboolean config_equal;
946 g_autoptr(CcDisplayConfig) applied_config = NULL;
947
948 if (!self->current_config)
949 {
950 reset_titlebar (self);
951 return;
952 }
953
954 applied_config = cc_display_config_manager_get_current (self->manager);
955
956 config_equal = cc_display_config_equal (self->current_config,
957 applied_config);
958
959 if (config_equal)
960 reset_titlebar (self);
961 else
962 show_apply_titlebar (self, cc_display_config_is_applicable (self->current_config));
963 }
964
965 static void
966 apply_current_configuration (CcDisplayPanel *self)
967 {
968 g_autoptr(GError) error = NULL;
969
970 cc_display_config_apply (self->current_config, &error);
971
972 /* re-read the configuration */
973 on_screen_changed (self);
974
975 if (error)
976 g_warning ("Error applying configuration: %s", error->message);
977
978 adw_navigation_view_pop (self->nav_view);
979 }
980
981 static void
982 cancel_current_configuration (CcDisplayPanel *panel)
983 {
984 CcDisplayConfigType selected;
985 CcDisplayConfig *current;
986
987 selected = cc_panel_get_selected_type (panel);
988 current = cc_display_config_manager_get_current (panel->manager);
989
990 /* Closes the potentially open monitor page. */
991 if (selected == CC_DISPLAY_CONFIG_JOIN && cc_display_config_is_cloning (current))
992 adw_navigation_view_pop (panel->nav_view);
993
994 on_screen_changed (panel);
995 }
996
997 static void
998 mapped_cb (CcDisplayPanel *self)
999 {
1000 CcShell *shell;
1001 GtkWidget *toplevel;
1002
1003 shell = cc_panel_get_shell (CC_PANEL (self));
1004 toplevel = cc_shell_get_toplevel (shell);
1005 if (toplevel && !self->focus_id)
1006 self->focus_id = g_signal_connect_object (toplevel, "notify::is-active",
1007 G_CALLBACK (dialog_toplevel_is_active_changed), self, G_CONNECT_SWAPPED);
1008 }
1009
1010 static void
1011 cc_display_panel_up_client_changed (CcDisplayPanel *self)
1012 {
1013 gboolean lid_is_closed;
1014
1015 lid_is_closed = up_client_get_lid_is_closed (self->up_client);
1016
1017 if (lid_is_closed != self->lid_is_closed)
1018 {
1019 self->lid_is_closed = lid_is_closed;
1020
1021 on_screen_changed (self);
1022 }
1023 }
1024
1025 static void
1026 shell_proxy_ready (GObject *source,
1027 GAsyncResult *res,
1028 CcDisplayPanel *self)
1029 {
1030 GDBusProxy *proxy;
1031 g_autoptr(GError) error = NULL;
1032
1033 proxy = cc_object_storage_create_dbus_proxy_finish (res, &error);
1034 if (!proxy)
1035 {
1036 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1037 g_warning ("Failed to contact gnome-shell: %s", error->message);
1038 return;
1039 }
1040
1041 self->shell_proxy = proxy;
1042
1043 ensure_monitor_labels (self);
1044 }
1045
1046 static void
1047 session_bus_ready (GObject *source,
1048 GAsyncResult *res,
1049 CcDisplayPanel *self)
1050 {
1051 GDBusConnection *bus;
1052 g_autoptr(GError) error = NULL;
1053
1054 bus = g_bus_get_finish (res, &error);
1055 if (!bus)
1056 {
1057 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1058 {
1059 g_warning ("Failed to get session bus: %s", error->message);
1060 }
1061 return;
1062 }
1063
1064 self->manager = cc_display_config_manager_dbus_new ();
1065 g_signal_connect_object (self->manager, "changed",
1066 G_CALLBACK (on_screen_changed),
1067 self,
1068 G_CONNECT_SWAPPED);
1069 }
1070
1071 static void
1072 cc_display_panel_init (CcDisplayPanel *self)
1073 {
1074 g_autoptr(GtkCssProvider) provider = NULL;
1075 GtkExpression *expression;
1076
1077 g_resources_register (cc_display_get_resource ());
1078
1079 gtk_widget_init_template (GTK_WIDGET (self));
1080
1081 self->arrangement = cc_display_arrangement_new (NULL);
1082 gtk_widget_set_size_request (GTK_WIDGET (self->arrangement), -1, 175);
1083 adw_bin_set_child (self->arrangement_bin, GTK_WIDGET (self->arrangement));
1084
1085 g_signal_connect_object (self->arrangement, "updated",
1086 G_CALLBACK (update_apply_button), self,
1087 G_CONNECT_SWAPPED);
1088 g_signal_connect_object (self->arrangement, "notify::selected-output",
1089 G_CALLBACK (on_arrangement_selected_ouptut_changed_cb), self,
1090 G_CONNECT_SWAPPED);
1091
1092 self->settings = cc_display_settings_new ();
1093 adw_bin_set_child (self->display_settings_bin, GTK_WIDGET (self->settings));
1094 g_signal_connect_object (self->settings, "updated",
1095 G_CALLBACK (on_monitor_settings_updated_cb), self,
1096 G_CONNECT_SWAPPED);
1097
1098 self->primary_display_list = g_list_store_new (CC_TYPE_DISPLAY_MONITOR);
1099
1100 expression = gtk_cclosure_expression_new (G_TYPE_STRING,
1101 NULL, 0, NULL,
1102 G_CALLBACK (cc_display_monitor_dup_ui_number_name),
1103 self, NULL);
1104 adw_combo_row_set_expression (self->primary_display_row, expression);
1105 adw_combo_row_set_model (self->primary_display_row,
1106 G_LIST_MODEL (self->primary_display_list));
1107
1108 self->up_client = up_client_new ();
1109 if (up_client_get_lid_is_present (self->up_client))
1110 {
1111 g_signal_connect_object (self->up_client, "notify::lid-is-closed",
1112 G_CALLBACK (cc_display_panel_up_client_changed), self, G_CONNECT_SWAPPED);
1113 cc_display_panel_up_client_changed (self);
1114 }
1115 else
1116 g_clear_object (&self->up_client);
1117
1118 g_signal_connect (self, "map", G_CALLBACK (mapped_cb), NULL);
1119
1120 cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION,
1121 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
1122 G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
1123 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
1124 "org.gnome.Shell",
1125 "/org/gnome/Shell",
1126 "org.gnome.Shell",
1127 cc_panel_get_cancellable (CC_PANEL (self)),
1128 (GAsyncReadyCallback) shell_proxy_ready,
1129 self);
1130
1131 g_bus_get (G_BUS_TYPE_SESSION,
1132 cc_panel_get_cancellable (CC_PANEL (self)),
1133 (GAsyncReadyCallback) session_bus_ready,
1134 self);
1135
1136 provider = gtk_css_provider_new ();
1137 gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/display/display-arrangement.css");
1138 gtk_style_context_add_provider_for_display (gdk_display_get_default (),
1139 GTK_STYLE_PROVIDER (provider),
1140 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1141
1142 gtk_shortcut_set_action (self->escape_shortcut,
1143 gtk_callback_action_new ((GtkShortcutFunc) on_toplevel_escape_pressed_cb,
1144 self,
1145 NULL));
1146
1147 self->display_settings = g_settings_new (DISPLAY_SCHEMA);
1148 g_signal_connect_object (self->display_settings,
1149 "changed::night-light-enabled",
1150 G_CALLBACK (on_night_light_enabled_changed_cb),
1151 self,
1152 G_CONNECT_SWAPPED);
1153 on_night_light_enabled_changed_cb (self);
1154 }
1155