GCC Code Coverage Report


Directory: ./
File: panels/display/cc-display-settings.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 472 0.0%
Functions: 0 39 0.0%
Branches: 0 211 0.0%

Line Branch Exec Source
1 /* cc-display-settings.c
2 *
3 * Copyright (C) 2007, 2008, 2018, 2019 Red Hat, Inc.
4 * Copyright (C) 2013 Intel, Inc.
5 *
6 * Written by: Benjamin Berg <bberg@redhat.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2, or (at your option)
11 * any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <float.h>
23 #include <glib/gi18n.h>
24 #include <float.h>
25 #include <math.h>
26 #include "cc-display-settings.h"
27 #include "cc-display-config.h"
28
29 #define MAX_SCALE_BUTTONS 5
30
31 struct _CcDisplaySettings
32 {
33 GtkBox object;
34
35 gboolean updating;
36 gboolean num_scales;
37 gboolean collapsed;
38 guint idle_udpate_id;
39
40 gboolean has_accelerometer;
41 CcDisplayConfig *config;
42 CcDisplayMonitor *selected_output;
43
44 GListModel *orientation_list;
45 GListStore *refresh_rate_list;
46 GListStore *resolution_list;
47 GListModel *scale_list;
48
49 GtkWidget *enabled_listbox;
50 AdwSwitchRow *enabled_row;
51 GtkWidget *orientation_row;
52 GtkWidget *refresh_rate_row;
53 AdwExpanderRow *refresh_rate_expander_row;
54 GtkLabel *refresh_rate_expander_suffix_label;
55 AdwSwitchRow *variable_refresh_rate_row;
56 AdwComboRow *preferred_refresh_rate_row;
57 GtkWidget *resolution_row;
58 GtkWidget *scale_bbox;
59 GtkWidget *scale_buttons_row;
60 GtkWidget *scale_combo_row;
61 AdwSwitchRow *underscanning_row;
62 };
63
64 typedef struct _CcDisplaySettings CcDisplaySettings;
65
66 enum {
67 PROP_0,
68 PROP_HAS_ACCELEROMETER,
69 PROP_CONFIG,
70 PROP_SELECTED_OUTPUT,
71 PROP_LAST
72 };
73
74 G_DEFINE_TYPE (CcDisplaySettings, cc_display_settings, GTK_TYPE_BOX)
75
76 static GParamSpec *props[PROP_LAST];
77
78 static void on_scale_btn_active_changed_cb (CcDisplaySettings *self,
79 GParamSpec *pspec,
80 GtkWidget *widget);
81
82 static gboolean
83 should_show_rotation (CcDisplaySettings *self)
84 {
85 gboolean supports_rotation;
86
87 supports_rotation = cc_display_monitor_supports_rotation (self->selected_output,
88 CC_DISPLAY_ROTATION_90 |
89 CC_DISPLAY_ROTATION_180 |
90 CC_DISPLAY_ROTATION_270);
91
92 /* Doesn't support rotation at all */
93 if (!supports_rotation)
94 return FALSE;
95
96 /* We can always rotate displays that aren't builtin */
97 if (!cc_display_monitor_is_builtin (self->selected_output))
98 return TRUE;
99
100 /* Only offer rotation if there's no accelerometer */
101 return !self->has_accelerometer;
102 }
103
104 static const gchar *
105 string_for_rotation (CcDisplayRotation rotation)
106 {
107 switch (rotation)
108 {
109 case CC_DISPLAY_ROTATION_NONE:
110 case CC_DISPLAY_ROTATION_180_FLIPPED:
111 return C_("Display rotation", "Landscape");
112 case CC_DISPLAY_ROTATION_90:
113 case CC_DISPLAY_ROTATION_270_FLIPPED:
114 return C_("Display rotation", "Portrait Right");
115 case CC_DISPLAY_ROTATION_270:
116 case CC_DISPLAY_ROTATION_90_FLIPPED:
117 return C_("Display rotation", "Portrait Left");
118 case CC_DISPLAY_ROTATION_180:
119 case CC_DISPLAY_ROTATION_FLIPPED:
120 return C_("Display rotation", "Landscape (flipped)");
121 }
122 return "";
123 }
124
125 static const gchar *
126 make_aspect_string (gint width,
127 gint height)
128 {
129 int ratio;
130 const gchar *aspect = NULL;
131
132 /* We use a number of Unicode characters below:
133 * ∶ is U+2236 RATIO
134 *   is U+2009 THIN SPACE,
135 * × is U+00D7 MULTIPLICATION SIGN
136 */
137 if (width && height) {
138 if (width > height)
139 ratio = width * 10 / height;
140 else
141 ratio = height * 10 / width;
142
143 switch (ratio) {
144 case 13:
145 aspect = "4∶3";
146 break;
147 case 16:
148 aspect = "16∶10";
149 break;
150 case 17:
151 aspect = "16∶9";
152 break;
153 case 23:
154 aspect = "21∶9";
155 break;
156 case 35:
157 aspect = "32∶9";
158 break;
159 case 12:
160 aspect = "5∶4";
161 break;
162 /* This catches 1.5625 as well (1600x1024) when maybe it shouldn't. */
163 case 15:
164 aspect = "3∶2";
165 break;
166 case 18:
167 aspect = "9∶5";
168 break;
169 case 10:
170 aspect = "1∶1";
171 break;
172 }
173 }
174
175 return aspect;
176 }
177
178 static gchar *
179 make_refresh_rate_string (CcDisplayMode *mode)
180 {
181 return g_strdup_printf (_("%.2lf Hz"), cc_display_mode_get_freq_f (mode));
182 }
183
184 static gchar *
185 make_variable_refresh_rate_string (CcDisplayMonitor *output,
186 CcDisplayMode *mode)
187 {
188 int min_freq;
189
190 min_freq = cc_display_monitor_get_min_freq (output);
191 if (min_freq > 0)
192 {
193 /* Translators:
194 * 1. "Variable" is an adjective that refers to the refresh rate
195 * 2. The formatting sequence is a range separated by an en-dash
196 * (unicode "\u2013"). For example: "Variable (48–144.97 Hz)"
197 */
198 return g_strdup_printf (_("Variable (%d\u2013%.2lf Hz)"),
199 min_freq,
200 cc_display_mode_get_freq_f (mode));
201 }
202 else
203 {
204 /* Translators: "Variable" is an adjective that refers to the refresh rate */
205 return g_strdup_printf (_("Variable (up to %.2lf Hz)"),
206 cc_display_mode_get_freq_f (mode));
207 }
208 }
209
210 static gchar *
211 make_expander_refresh_rate_string (CcDisplayMonitor *output,
212 CcDisplayMode *mode)
213 {
214 switch (cc_display_mode_get_refresh_rate_mode (mode))
215 {
216 case MODE_REFRESH_RATE_MODE_FIXED:
217 return make_refresh_rate_string (mode);
218 case MODE_REFRESH_RATE_MODE_VARIABLE:
219 return make_variable_refresh_rate_string (output, mode);
220 default:
221 g_assert_not_reached();
222 }
223
224 return NULL;
225 }
226
227 static gboolean
228 mode_to_refresh_rate_transform_func (GBinding *binding,
229 const GValue *source_value,
230 GValue *target_value,
231 CcDisplaySettings *self)
232 {
233 CcDisplayMode *mode;
234 gchar *refresh_rate_string;
235
236 if (!G_VALUE_HOLDS_OBJECT (source_value))
237 return FALSE;
238
239 if (!G_VALUE_HOLDS_STRING (target_value))
240 return FALSE;
241
242 mode = CC_DISPLAY_MODE (g_value_get_object (source_value));
243 g_return_val_if_fail (mode != NULL, FALSE);
244
245 refresh_rate_string =
246 make_expander_refresh_rate_string (self->selected_output, mode);
247
248 g_value_take_string (target_value, refresh_rate_string);
249
250 return TRUE;
251 }
252
253 static gchar *
254 make_resolution_string (CcDisplayMode *mode)
255 {
256 const char *interlaced;
257 const char *aspect;
258 int width, height;
259
260 cc_display_mode_get_resolution (mode, &width, &height);
261 aspect = make_aspect_string (width, height);
262 interlaced = cc_display_mode_is_interlaced (mode) ? "i" : "";
263
264 if (aspect != NULL)
265 return g_strdup_printf ("%d × %d%s (%s)", width, height, interlaced, aspect);
266 else
267 return g_strdup_printf ("%d × %d%s", width, height, interlaced);
268 }
269
270 static double
271 round_scale_for_ui (double scale)
272 {
273 /* Keep in sync with mutter */
274 return round (scale*4)/4;
275 }
276
277 static gchar *
278 make_scale_string (gdouble scale)
279 {
280 return g_strdup_printf ("%d %%", (int) (round_scale_for_ui (scale)*100));
281 }
282
283 static gint
284 sort_modes_by_area_desc (CcDisplayMode *a, CcDisplayMode *b)
285 {
286 gint wa, ha, wb, hb;
287 gint res;
288
289 cc_display_mode_get_resolution (a, &wa, &ha);
290 cc_display_mode_get_resolution (b, &wb, &hb);
291
292 /* Sort first by width, then height.
293 * We used to sort by area, but that can be confusing. */
294 res = wb - wa;
295 if (res)
296 return res;
297 return hb - ha;
298 }
299
300 static gint
301 sort_modes_by_refresh_rate_desc (CcDisplayMode *a, CcDisplayMode *b)
302 {
303 if (cc_display_mode_get_refresh_rate_mode (a) != cc_display_mode_get_refresh_rate_mode (b))
304 {
305 if (cc_display_mode_get_refresh_rate_mode (a) == MODE_REFRESH_RATE_MODE_VARIABLE)
306 return -1;
307 else
308 return 1;
309 }
310
311 double delta = (cc_display_mode_get_freq_f (b) - cc_display_mode_get_freq_f (a))*1000.;
312
313 return delta;
314 }
315
316 static gboolean
317 cc_display_settings_rebuild_ui (CcDisplaySettings *self)
318 {
319 GtkWidget *child;
320 g_autolist(CcDisplayMode) clone_modes = NULL;
321 GList *modes;
322 GList *item;
323 gint width, height;
324 CcDisplayMode *current_mode;
325 GtkToggleButton *group = NULL;
326 g_autoptr(GArray) scales = NULL;
327 gint i;
328
329 self->idle_udpate_id = 0;
330
331 if (!self->config || !self->selected_output)
332 {
333 gtk_widget_set_visible (self->enabled_listbox, FALSE);
334 gtk_widget_set_visible (self->orientation_row, FALSE);
335 gtk_widget_set_visible (self->refresh_rate_row, FALSE);
336 gtk_widget_set_visible (GTK_WIDGET (self->refresh_rate_expander_row), FALSE);
337 gtk_widget_set_visible (self->resolution_row, FALSE);
338 gtk_widget_set_visible (self->scale_combo_row, FALSE);
339 gtk_widget_set_visible (self->scale_buttons_row, FALSE);
340 gtk_widget_set_visible (GTK_WIDGET (self->underscanning_row), FALSE);
341
342 return G_SOURCE_REMOVE;
343 }
344
345 g_object_freeze_notify ((GObject*) self->enabled_row);
346 g_object_freeze_notify ((GObject*) self->orientation_row);
347 g_object_freeze_notify ((GObject*) self->refresh_rate_row);
348 g_object_freeze_notify ((GObject*) self->refresh_rate_expander_row);
349 g_object_freeze_notify ((GObject*) self->variable_refresh_rate_row);
350 g_object_freeze_notify ((GObject*) self->preferred_refresh_rate_row);
351 g_object_freeze_notify ((GObject*) self->resolution_row);
352 g_object_freeze_notify ((GObject*) self->scale_combo_row);
353 g_object_freeze_notify ((GObject*) self->underscanning_row);
354
355 cc_display_monitor_get_geometry (self->selected_output, NULL, NULL, &width, &height);
356
357 /* Selecte the first mode we can find if the monitor is disabled. */
358 current_mode = cc_display_monitor_get_mode (self->selected_output);
359 if (current_mode == NULL)
360 current_mode = cc_display_monitor_get_preferred_mode (self->selected_output);
361 if (current_mode == NULL) {
362 modes = cc_display_monitor_get_modes (self->selected_output);
363 /* Lets assume that a monitor always has at least one mode. */
364 g_assert (modes);
365 current_mode = CC_DISPLAY_MODE (modes->data);
366 }
367
368 /* Enabled Switch */
369 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->enabled_row),
370 cc_display_monitor_get_ui_name (self->selected_output));
371 adw_switch_row_set_active (self->enabled_row,
372 cc_display_monitor_is_active (self->selected_output));
373
374 if (should_show_rotation (self))
375 {
376 guint i;
377 CcDisplayRotation rotations[] = { CC_DISPLAY_ROTATION_NONE,
378 CC_DISPLAY_ROTATION_90,
379 CC_DISPLAY_ROTATION_270,
380 CC_DISPLAY_ROTATION_180 };
381
382 gtk_widget_set_visible (self->orientation_row, TRUE);
383
384 gtk_string_list_splice (GTK_STRING_LIST (self->orientation_list),
385 0,
386 g_list_model_get_n_items (self->orientation_list),
387 NULL);
388 for (i = 0; i < G_N_ELEMENTS (rotations); i++)
389 {
390 g_autoptr(GObject) obj = NULL;
391
392 if (!cc_display_monitor_supports_rotation (self->selected_output, rotations[i]))
393 continue;
394
395 gtk_string_list_append (GTK_STRING_LIST (self->orientation_list),
396 string_for_rotation (rotations[i]));
397 obj = g_list_model_get_item (self->orientation_list, i);
398 g_object_set_data (G_OBJECT (obj), "rotation-value", GINT_TO_POINTER (rotations[i]));
399
400 if (cc_display_monitor_get_rotation (self->selected_output) == rotations[i])
401 adw_combo_row_set_selected (ADW_COMBO_ROW (self->orientation_row),
402 g_list_model_get_n_items (G_LIST_MODEL (self->orientation_list)) - 1);
403 }
404 }
405 else
406 {
407 gtk_widget_set_visible (self->orientation_row, FALSE);
408 }
409
410 /* Only show refresh rate if we are not in cloning mode. */
411 if (!cc_display_config_is_cloning (self->config))
412 {
413 GList *item;
414 gdouble current_freq;
415 CcDisplayModeRefreshRateMode current_refresh_rate_mode;
416 gboolean has_variable_refresh_rate_modes = FALSE;
417
418 current_freq = cc_display_mode_get_freq_f (current_mode);
419 current_refresh_rate_mode = cc_display_mode_get_refresh_rate_mode (current_mode);
420
421 modes = cc_display_monitor_get_modes (self->selected_output);
422
423 g_list_store_remove_all (self->refresh_rate_list);
424
425 for (item = modes; item != NULL; item = item->next)
426 {
427 gint w, h;
428 guint new;
429 CcDisplayMode *mode = CC_DISPLAY_MODE (item->data);
430 CcDisplayModeRefreshRateMode refresh_rate_mode;
431
432 cc_display_mode_get_resolution (mode, &w, &h);
433 if (w != width || h != height)
434 continue;
435
436 refresh_rate_mode = cc_display_mode_get_refresh_rate_mode (mode);
437
438 if (refresh_rate_mode == MODE_REFRESH_RATE_MODE_VARIABLE)
439 has_variable_refresh_rate_modes = TRUE;
440
441 if (current_refresh_rate_mode != refresh_rate_mode)
442 continue;
443
444 /* At some point we used to filter very close resolutions,
445 * but we don't anymore these days.
446 */
447 new = g_list_store_insert_sorted (self->refresh_rate_list,
448 mode,
449 (GCompareDataFunc) sort_modes_by_refresh_rate_desc,
450 NULL);
451
452 if (current_freq != cc_display_mode_get_freq_f (mode))
453 continue;
454
455 adw_combo_row_set_selected (ADW_COMBO_ROW (self->refresh_rate_row), new);
456 adw_combo_row_set_selected (self->preferred_refresh_rate_row, new);
457 }
458
459 adw_switch_row_set_active (self->variable_refresh_rate_row,
460 current_refresh_rate_mode == MODE_REFRESH_RATE_MODE_VARIABLE);
461 gtk_widget_set_sensitive (GTK_WIDGET (self->variable_refresh_rate_row),
462 has_variable_refresh_rate_modes);
463
464 if (cc_display_monitor_supports_variable_refresh_rate (self->selected_output))
465 {
466 gtk_widget_set_visible (self->refresh_rate_row, FALSE);
467 gtk_widget_set_visible (GTK_WIDGET (self->refresh_rate_expander_row), TRUE);
468 }
469 else
470 {
471 gtk_widget_set_visible (self->refresh_rate_row, TRUE);
472 gtk_widget_set_visible (GTK_WIDGET (self->refresh_rate_expander_row), FALSE);
473 }
474 }
475 else
476 {
477 gtk_widget_set_visible (self->refresh_rate_row, FALSE);
478 gtk_widget_set_visible (GTK_WIDGET (self->refresh_rate_expander_row), FALSE);
479 }
480
481
482 /* Resolutions are always shown. */
483 gtk_widget_set_visible (self->resolution_row, TRUE);
484 if (cc_display_config_is_cloning (self->config))
485 {
486 clone_modes = cc_display_config_generate_cloning_modes (self->config);
487 modes = clone_modes;
488 }
489 else
490 {
491 modes = cc_display_monitor_get_modes (self->selected_output);
492 }
493
494 g_list_store_remove_all (self->resolution_list);
495 g_list_store_append (self->resolution_list, current_mode);
496 adw_combo_row_set_selected (ADW_COMBO_ROW (self->resolution_row), 0);
497 for (item = modes; item != NULL; item = item->next)
498 {
499 gint ins;
500 gint w, h;
501 CcDisplayMode *mode = CC_DISPLAY_MODE (item->data);
502
503 cc_display_mode_get_resolution (mode, &w, &h);
504
505 /* Find the appropriate insertion point. */
506 for (ins = 0; ins < g_list_model_get_n_items (G_LIST_MODEL (self->resolution_list)); ins++)
507 {
508 g_autoptr(CcDisplayMode) m = NULL;
509 gint cmp;
510
511 m = g_list_model_get_item (G_LIST_MODEL (self->resolution_list), ins);
512
513 cmp = sort_modes_by_area_desc (mode, m);
514 /* Next item is smaller, insert at this point. */
515 if (cmp < 0)
516 break;
517
518 /* Don't insert if it is already in the list */
519 if (cmp == 0)
520 {
521 ins = -1;
522 break;
523 }
524 }
525
526 if (ins >= 0)
527 g_list_store_insert (self->resolution_list, ins, mode);
528 }
529
530
531 /* Scale row is usually shown. */
532 while ((child = gtk_widget_get_first_child (self->scale_bbox)) != NULL)
533 gtk_box_remove (GTK_BOX (self->scale_bbox), child);
534
535 gtk_string_list_splice (GTK_STRING_LIST (self->scale_list),
536 0,
537 g_list_model_get_n_items (self->scale_list),
538 NULL);
539 scales = cc_display_mode_get_supported_scales (current_mode);
540 self->num_scales = scales->len;
541 for (i = 0; i < scales->len; i++)
542 {
543 g_autofree gchar *scale_str = NULL;
544 g_autoptr(GObject) value_object = NULL;
545 double scale = g_array_index (scales, double, i);
546 GtkWidget *scale_btn;
547 gboolean is_selected;
548
549 /* ComboRow */
550 scale_str = make_scale_string (scale);
551 is_selected = G_APPROX_VALUE (cc_display_monitor_get_scale (self->selected_output),
552 scale, DBL_EPSILON);
553
554 gtk_string_list_append (GTK_STRING_LIST (self->scale_list), scale_str);
555 value_object = g_list_model_get_item (self->scale_list, i);
556 g_object_set_data_full (G_OBJECT (value_object), "scale",
557 g_memdup2 (&scale, sizeof (double)), g_free);
558 if (is_selected)
559 adw_combo_row_set_selected (ADW_COMBO_ROW (self->scale_combo_row),
560 g_list_model_get_n_items (G_LIST_MODEL (self->scale_list)) - 1);
561
562 /* ButtonBox */
563 scale_btn = gtk_toggle_button_new_with_label (scale_str);
564 gtk_toggle_button_set_group (GTK_TOGGLE_BUTTON (scale_btn), group);
565 g_object_set_data_full (G_OBJECT (scale_btn), "scale",
566 g_memdup2 (&scale, sizeof (double)), g_free);
567
568 if (!group)
569 group = GTK_TOGGLE_BUTTON (scale_btn);
570 gtk_box_append (GTK_BOX (self->scale_bbox), scale_btn);
571 /* Set active before connecting the signal */
572 if (is_selected)
573 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scale_btn), TRUE);
574
575 g_signal_connect_object (scale_btn,
576 "notify::active",
577 G_CALLBACK (on_scale_btn_active_changed_cb),
578 self, G_CONNECT_SWAPPED);
579 }
580 cc_display_settings_refresh_layout (self, self->collapsed);
581
582 gtk_widget_set_visible (GTK_WIDGET (self->underscanning_row),
583 cc_display_monitor_supports_underscanning (self->selected_output) &&
584 !cc_display_config_is_cloning (self->config));
585 adw_switch_row_set_active (self->underscanning_row,
586 cc_display_monitor_get_underscanning (self->selected_output));
587
588 self->updating = TRUE;
589 g_object_thaw_notify ((GObject*) self->enabled_row);
590 g_object_thaw_notify ((GObject*) self->orientation_row);
591 g_object_thaw_notify ((GObject*) self->refresh_rate_row);
592 g_object_thaw_notify ((GObject*) self->refresh_rate_expander_row);
593 g_object_thaw_notify ((GObject*) self->variable_refresh_rate_row);
594 g_object_thaw_notify ((GObject*) self->preferred_refresh_rate_row);
595 g_object_thaw_notify ((GObject*) self->resolution_row);
596 g_object_thaw_notify ((GObject*) self->scale_combo_row);
597 g_object_thaw_notify ((GObject*) self->underscanning_row);
598 self->updating = FALSE;
599
600 return G_SOURCE_REMOVE;
601 }
602
603 static void
604 on_output_changed_cb (CcDisplaySettings *self,
605 GParamSpec *pspec,
606 CcDisplayMonitor *output)
607 {
608 /* Do this frmo an idle handler, because otherwise we may create an
609 * infinite loop triggering the notify::selected-index from the
610 * combo rows. */
611 if (self->idle_udpate_id)
612 return;
613
614 self->idle_udpate_id = g_idle_add ((GSourceFunc) cc_display_settings_rebuild_ui, self);
615 }
616
617 static void
618 on_enabled_row_active_changed_cb (CcDisplaySettings *self)
619 {
620 if (self->updating)
621 return;
622
623 cc_display_monitor_set_active (self->selected_output,
624 adw_switch_row_get_active (self->enabled_row));
625
626 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
627 }
628
629 static void
630 on_orientation_selection_changed_cb (CcDisplaySettings *self)
631 {
632 gint idx;
633 g_autoptr(GObject) obj = NULL;
634
635 if (self->updating)
636 return;
637
638 idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->orientation_row));
639 obj = g_list_model_get_item (G_LIST_MODEL (self->orientation_list), idx);
640
641 if (!obj)
642 return;
643
644 cc_display_monitor_set_rotation (self->selected_output,
645 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (obj), "rotation-value")));
646
647 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
648 }
649
650 static void
651 on_refresh_rate_selection_changed_cb (CcDisplaySettings *self)
652 {
653 gint idx;
654 g_autoptr(CcDisplayMode) mode = NULL;
655
656 if (self->updating)
657 return;
658
659 idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->refresh_rate_row));
660 mode = g_list_model_get_item (G_LIST_MODEL (self->refresh_rate_list), idx);
661
662 if (!mode)
663 return;
664
665 cc_display_monitor_set_mode (self->selected_output, mode);
666
667 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
668 }
669
670 static void
671 on_variable_refresh_rate_active_changed_cb (CcDisplaySettings *self)
672 {
673 if (self->updating)
674 return;
675
676 if (!adw_switch_row_get_active (self->variable_refresh_rate_row))
677 {
678 cc_display_monitor_set_refresh_rate_mode (self->selected_output,
679 MODE_REFRESH_RATE_MODE_FIXED);
680 }
681 else
682 {
683 cc_display_monitor_set_refresh_rate_mode (self->selected_output,
684 MODE_REFRESH_RATE_MODE_VARIABLE);
685 }
686
687 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
688 }
689
690 static void
691 on_resolution_selection_changed_cb (CcDisplaySettings *self)
692 {
693 gint idx;
694 g_autoptr(CcDisplayMode) mode = NULL;
695
696 if (self->updating)
697 return;
698
699 idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->resolution_row));
700 mode = g_list_model_get_item (G_LIST_MODEL (self->resolution_list), idx);
701
702 if (!mode)
703 return;
704
705 /* This is the only row that can be changed when in cloning mode. */
706 if (!cc_display_config_is_cloning (self->config))
707 cc_display_monitor_set_mode (self->selected_output, mode);
708 else
709 cc_display_config_set_mode_on_all_outputs (self->config, mode);
710
711 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
712 }
713
714 static void
715 on_scale_btn_active_changed_cb (CcDisplaySettings *self,
716 GParamSpec *pspec,
717 GtkWidget *widget)
718 {
719 gdouble scale;
720 if (self->updating)
721 return;
722
723 if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
724 return;
725
726 scale = *(gdouble*) g_object_get_data (G_OBJECT (widget), "scale");
727 cc_display_monitor_set_scale (self->selected_output,
728 scale);
729
730 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
731 }
732
733 static void
734 on_scale_selection_changed_cb (CcDisplaySettings *self)
735 {
736 int idx;
737 double scale;
738 g_autoptr(GObject) obj = NULL;
739
740 if (self->updating)
741 return;
742
743 idx = adw_combo_row_get_selected (ADW_COMBO_ROW (self->scale_combo_row));
744 obj = g_list_model_get_item (G_LIST_MODEL (self->scale_list), idx);
745 if (!obj)
746 return;
747 scale = *(gdouble*) g_object_get_data (G_OBJECT (obj), "scale");
748
749 cc_display_monitor_set_scale (self->selected_output, scale);
750
751 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
752 }
753
754 static void
755 on_underscanning_row_active_changed_cb (CcDisplaySettings *self)
756 {
757 if (self->updating)
758 return;
759
760 cc_display_monitor_set_underscanning (self->selected_output,
761 adw_switch_row_get_active (self->underscanning_row));
762
763 g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output);
764 }
765
766 static void
767 cc_display_settings_get_property (GObject *object,
768 guint prop_id,
769 GValue *value,
770 GParamSpec *pspec)
771 {
772 CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object);
773
774 switch (prop_id)
775 {
776 case PROP_HAS_ACCELEROMETER:
777 g_value_set_boolean (value, cc_display_settings_get_has_accelerometer (self));
778 break;
779
780 case PROP_CONFIG:
781 g_value_set_object (value, self->config);
782 break;
783
784 case PROP_SELECTED_OUTPUT:
785 g_value_set_object (value, self->selected_output);
786 break;
787
788 default:
789 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
790 }
791 }
792
793 static void
794 cc_display_settings_set_property (GObject *object,
795 guint prop_id,
796 const GValue *value,
797 GParamSpec *pspec)
798 {
799 CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object);
800
801 switch (prop_id)
802 {
803 case PROP_HAS_ACCELEROMETER:
804 cc_display_settings_set_has_accelerometer (self, g_value_get_boolean (value));
805 break;
806
807 case PROP_CONFIG:
808 cc_display_settings_set_config (self, g_value_get_object (value));
809 break;
810
811 case PROP_SELECTED_OUTPUT:
812 cc_display_settings_set_selected_output (self, g_value_get_object (value));
813 break;
814
815 default:
816 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
817 }
818 }
819
820 static void
821 cc_display_settings_finalize (GObject *object)
822 {
823 CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object);
824
825 g_clear_object (&self->config);
826
827 g_clear_object (&self->orientation_list);
828 g_clear_object (&self->refresh_rate_list);
829 g_clear_object (&self->resolution_list);
830 g_clear_object (&self->scale_list);
831
832 g_clear_handle_id (&self->idle_udpate_id, g_source_remove);
833
834 G_OBJECT_CLASS (cc_display_settings_parent_class)->finalize (object);
835 }
836
837 static void
838 cc_display_settings_class_init (CcDisplaySettingsClass *klass)
839 {
840 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
841 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
842
843 gobject_class->finalize = cc_display_settings_finalize;
844 gobject_class->get_property = cc_display_settings_get_property;
845 gobject_class->set_property = cc_display_settings_set_property;
846
847 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-settings.ui");
848
849 props[PROP_HAS_ACCELEROMETER] =
850 g_param_spec_boolean ("has-accelerometer", "Has Accelerometer",
851 "If an accelerometre is available for the builtin display",
852 FALSE,
853 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
854
855 props[PROP_CONFIG] =
856 g_param_spec_object ("config", "Display Config",
857 "The display configuration to work with",
858 CC_TYPE_DISPLAY_CONFIG,
859 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
860
861 props[PROP_SELECTED_OUTPUT] =
862 g_param_spec_object ("selected-output", "Selected Output",
863 "The output that is currently selected on the configuration",
864 CC_TYPE_DISPLAY_MONITOR,
865 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
866
867 g_object_class_install_properties (gobject_class,
868 PROP_LAST,
869 props);
870
871 g_signal_new ("updated",
872 CC_TYPE_DISPLAY_SETTINGS,
873 G_SIGNAL_RUN_LAST,
874 0, NULL, NULL, NULL,
875 G_TYPE_NONE, 1, CC_TYPE_DISPLAY_MONITOR);
876
877 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, enabled_listbox);
878 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, enabled_row);
879 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, orientation_row);
880 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, refresh_rate_row);
881 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, refresh_rate_expander_row);
882 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, refresh_rate_expander_suffix_label);
883 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, variable_refresh_rate_row);
884 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, preferred_refresh_rate_row);
885 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, resolution_row);
886 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_bbox);
887 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_buttons_row);
888 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_combo_row);
889 gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_row);
890
891 gtk_widget_class_bind_template_callback (widget_class, on_enabled_row_active_changed_cb);
892 gtk_widget_class_bind_template_callback (widget_class, on_orientation_selection_changed_cb);
893 gtk_widget_class_bind_template_callback (widget_class, on_refresh_rate_selection_changed_cb);
894 gtk_widget_class_bind_template_callback (widget_class, on_variable_refresh_rate_active_changed_cb);
895 gtk_widget_class_bind_template_callback (widget_class, on_resolution_selection_changed_cb);
896 gtk_widget_class_bind_template_callback (widget_class, on_scale_selection_changed_cb);
897 gtk_widget_class_bind_template_callback (widget_class, on_underscanning_row_active_changed_cb);
898 }
899
900 static void
901 cc_display_settings_init (CcDisplaySettings *self)
902 {
903 GtkExpression *expression;
904
905 gtk_widget_init_template (GTK_WIDGET (self));
906
907 self->orientation_list = G_LIST_MODEL (gtk_string_list_new (NULL));
908 self->refresh_rate_list = g_list_store_new (CC_TYPE_DISPLAY_MODE);
909 self->resolution_list = g_list_store_new (CC_TYPE_DISPLAY_MODE);
910 self->scale_list = G_LIST_MODEL (gtk_string_list_new (NULL));
911
912 self->updating = TRUE;
913
914 adw_combo_row_set_model (ADW_COMBO_ROW (self->orientation_row),
915 G_LIST_MODEL (self->orientation_list));
916 adw_combo_row_set_model (ADW_COMBO_ROW (self->scale_combo_row),
917 G_LIST_MODEL (self->scale_list));
918
919 expression = gtk_cclosure_expression_new (G_TYPE_STRING,
920 NULL, 0, NULL,
921 G_CALLBACK (make_refresh_rate_string),
922 self, NULL);
923 adw_combo_row_set_expression (ADW_COMBO_ROW (self->refresh_rate_row), expression);
924 adw_combo_row_set_model (ADW_COMBO_ROW (self->refresh_rate_row),
925 G_LIST_MODEL (self->refresh_rate_list));
926
927 adw_combo_row_set_expression (self->preferred_refresh_rate_row, expression);
928 adw_combo_row_set_model (self->preferred_refresh_rate_row,
929 G_LIST_MODEL (self->refresh_rate_list));
930
931 g_object_bind_property_full (self->preferred_refresh_rate_row,
932 "selected-item",
933 self->refresh_rate_expander_suffix_label,
934 "label",
935 G_BINDING_DEFAULT,
936 (GBindingTransformFunc) mode_to_refresh_rate_transform_func,
937 NULL, self, NULL);
938
939 expression = gtk_cclosure_expression_new (G_TYPE_STRING,
940 NULL, 0, NULL,
941 G_CALLBACK (make_resolution_string),
942 self, NULL);
943 adw_combo_row_set_expression (ADW_COMBO_ROW (self->resolution_row), expression);
944 adw_combo_row_set_model (ADW_COMBO_ROW (self->resolution_row),
945 G_LIST_MODEL (self->resolution_list));
946
947 self->updating = FALSE;
948 }
949
950 CcDisplaySettings*
951 cc_display_settings_new (void)
952 {
953 return g_object_new (CC_TYPE_DISPLAY_SETTINGS,
954 NULL);
955 }
956
957 gboolean
958 cc_display_settings_get_has_accelerometer (CcDisplaySettings *settings)
959 {
960 return settings->has_accelerometer;
961 }
962
963 void
964 cc_display_settings_set_has_accelerometer (CcDisplaySettings *self,
965 gboolean has_accelerometer)
966 {
967 self->has_accelerometer = has_accelerometer;
968
969 cc_display_settings_rebuild_ui (self);
970 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]);
971 }
972
973 CcDisplayConfig*
974 cc_display_settings_get_config (CcDisplaySettings *self)
975 {
976 return self->config;
977 }
978
979 void
980 cc_display_settings_set_config (CcDisplaySettings *self,
981 CcDisplayConfig *config)
982 {
983 const gchar *signals[] = { "rotation", "mode", "scale", "is-usable", "active" };
984 GList *outputs, *l;
985 guint i;
986
987 if (self->config)
988 {
989 outputs = cc_display_config_get_monitors (self->config);
990 for (l = outputs; l; l = l->next)
991 {
992 CcDisplayMonitor *output = l->data;
993
994 g_signal_handlers_disconnect_by_data (output, self);
995 }
996 }
997 g_clear_object (&self->config);
998
999 self->config = g_object_ref (config);
1000
1001 /* Listen to all the signals */
1002 if (self->config)
1003 {
1004 outputs = cc_display_config_get_monitors (self->config);
1005 for (l = outputs; l; l = l->next)
1006 {
1007 CcDisplayMonitor *output = l->data;
1008
1009 for (i = 0; i < G_N_ELEMENTS (signals); ++i)
1010 g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED);
1011 }
1012 }
1013
1014 cc_display_settings_set_selected_output (self, NULL);
1015
1016 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]);
1017 }
1018
1019 CcDisplayMonitor*
1020 cc_display_settings_get_selected_output (CcDisplaySettings *self)
1021 {
1022 return self->selected_output;
1023 }
1024
1025 void
1026 cc_display_settings_set_selected_output (CcDisplaySettings *self,
1027 CcDisplayMonitor *output)
1028 {
1029 self->selected_output = output;
1030
1031 adw_expander_row_set_expanded (self->refresh_rate_expander_row, FALSE);
1032
1033 cc_display_settings_rebuild_ui (self);
1034
1035 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_OUTPUT]);
1036 }
1037
1038 void
1039 cc_display_settings_refresh_layout (CcDisplaySettings *self,
1040 gboolean collapsed)
1041 {
1042 gboolean use_combo;
1043
1044 self->collapsed = collapsed;
1045 use_combo = self->num_scales > MAX_SCALE_BUTTONS || (self->num_scales > 2 && collapsed);
1046
1047 gtk_widget_set_visible (self->scale_combo_row, use_combo);
1048 gtk_widget_set_visible (self->scale_buttons_row, self->num_scales > 1 && !use_combo);
1049 }
1050
1051 void
1052 cc_display_settings_set_multimonitor (CcDisplaySettings *self,
1053 gboolean multimonitor)
1054 {
1055 gtk_widget_set_visible (self->enabled_listbox, multimonitor);
1056
1057 if (!multimonitor)
1058 adw_switch_row_set_active (self->enabled_row, TRUE);
1059 }
1060