1 /* Monitor Settings. A preference panel for configuring monitors
2 *
3 * Copyright (C) 2007, 2008 Red Hat, 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 St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Author: Soren Sandmann <sandmann@redhat.com>
20 */
21
22 #include <config.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <sys/wait.h>
26
27 #include <gtk/gtk.h>
28 #include "scrollarea.h"
29 #define MATE_DESKTOP_USE_UNSTABLE_API
30 #include <libmate-desktop/mate-desktop-utils.h>
31 #include <libmate-desktop/mate-rr.h>
32 #include <libmate-desktop/mate-rr-config.h>
33 #include <libmate-desktop/mate-rr-labeler.h>
34 #include <gdk/gdkx.h>
35 #include <X11/Xlib.h>
36 #include <glib/gi18n.h>
37 #include <gio/gio.h>
38
39 #include "capplet-util.h"
40
41 #define MATE_INTERFACE_SCHEMA "org.mate.interface"
42 #define WINDOW_SCALE_KEY "window-scaling-factor"
43
44 typedef struct App App;
45 typedef struct GrabInfo GrabInfo;
46
47 struct App
48 {
49 MateRRScreen *screen;
50 MateRRConfig *current_configuration;
51 MateRRLabeler *labeler;
52 MateRROutputInfo *current_output;
53
54 GtkWidget *dialog;
55 GtkWidget *current_monitor_event_box;
56 GtkWidget *current_monitor_label;
57 GtkWidget *monitor_on_radio;
58 GtkWidget *monitor_off_radio;
59 GtkListStore *resolution_store;
60 GtkWidget *resolution_combo;
61 GtkWidget *refresh_combo;
62 GtkWidget *rotation_combo;
63 GtkWidget *panel_checkbox;
64 GtkWidget *scale_vbox;
65 GtkWidget *scale_bbox;
66 GtkWidget *clone_checkbox;
67 GtkWidget *show_icon_checkbox;
68 GtkWidget *primary_button;
69
70 /* We store the event timestamp when the Apply button is clicked */
71 GtkWidget *apply_button;
72 guint32 apply_button_clicked_timestamp;
73
74 GtkWidget *area;
75 gboolean ignore_gui_changes;
76 GSettings *settings;
77 GSettings *scale_settings;
78
79 /* These are used while we are waiting for the ApplyConfiguration method to be executed over D-bus */
80 GDBusConnection *connection;
81 GDBusProxy *proxy;
82
83 enum {
84 APPLYING_VERSION_1,
85 APPLYING_VERSION_2
86 } apply_configuration_state;
87 };
88
89 /* Response codes for custom buttons in the main dialog */
90 enum {
91 RESPONSE_MAKE_DEFAULT = 1
92 };
93
94 static void rebuild_gui (App *app);
95 static void on_clone_changed (GtkWidget *box, gpointer data);
96 static void on_rate_changed (GtkComboBox *box, gpointer data);
97 static gboolean output_overlaps (MateRROutputInfo *output, MateRRConfig *config);
98 static void select_current_output_from_dialog_position (App *app);
99 static void monitor_on_off_toggled_cb (GtkToggleButton *toggle, gpointer data);
100 static void apply_configuration_returned_cb (GObject *source_object, GAsyncResult *res, gpointer data);
101
102 static void
error_message(App * app,const char * primary_text,const char * secondary_text)103 error_message (App *app, const char *primary_text, const char *secondary_text)
104 {
105 GtkWidget *dialog;
106
107 dialog = gtk_message_dialog_new ((app && app->dialog) ? GTK_WINDOW (app->dialog) : NULL,
108 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
109 GTK_MESSAGE_ERROR,
110 GTK_BUTTONS_CLOSE,
111 "%s", primary_text);
112
113 if (secondary_text)
114 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", secondary_text);
115
116 gtk_dialog_run (GTK_DIALOG (dialog));
117 gtk_widget_destroy (dialog);
118 }
119
120 static gboolean
do_free(gpointer data)121 do_free (gpointer data)
122 {
123 g_free (data);
124 return FALSE;
125 }
126
127 static gchar *
idle_free(gchar * s)128 idle_free (gchar *s)
129 {
130 g_idle_add (do_free, s);
131
132 return s;
133 }
134
135 static void
on_screen_changed(MateRRScreen * scr,gpointer data)136 on_screen_changed (MateRRScreen *scr,
137 gpointer data)
138 {
139 MateRRConfig *current;
140 App *app = data;
141
142 current = mate_rr_config_new_current (app->screen, NULL);
143
144 if (app->current_configuration)
145 g_object_unref (app->current_configuration);
146
147 app->current_configuration = current;
148 app->current_output = NULL;
149
150 if (app->labeler) {
151 mate_rr_labeler_hide (app->labeler);
152 g_object_unref (app->labeler);
153 }
154
155 app->labeler = mate_rr_labeler_new (app->current_configuration);
156
157 select_current_output_from_dialog_position (app);
158 }
159
160 static void
on_viewport_changed(FooScrollArea * scroll_area,GdkRectangle * old_viewport,GdkRectangle * new_viewport)161 on_viewport_changed (FooScrollArea *scroll_area,
162 GdkRectangle *old_viewport,
163 GdkRectangle *new_viewport)
164 {
165 foo_scroll_area_set_size (scroll_area,
166 new_viewport->width,
167 new_viewport->height);
168
169 foo_scroll_area_invalidate (scroll_area);
170 }
171
172 static void
layout_set_font(PangoLayout * layout,const char * font)173 layout_set_font (PangoLayout *layout, const char *font)
174 {
175 PangoFontDescription *desc =
176 pango_font_description_from_string (font);
177
178 if (desc)
179 {
180 pango_layout_set_font_description (layout, desc);
181
182 pango_font_description_free (desc);
183 }
184 }
185
186 static void
clear_combo(GtkWidget * widget)187 clear_combo (GtkWidget *widget)
188 {
189 GtkComboBox *box = GTK_COMBO_BOX (widget);
190 GtkTreeModel *model = gtk_combo_box_get_model (box);
191 GtkListStore *store = GTK_LIST_STORE (model);
192
193 gtk_list_store_clear (store);
194 }
195
196 typedef struct
197 {
198 const char *text;
199 gboolean found;
200 GtkTreeIter iter;
201 } ForeachInfo;
202
203 static gboolean
foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)204 foreach (GtkTreeModel *model,
205 GtkTreePath *path,
206 GtkTreeIter *iter,
207 gpointer data)
208 {
209 ForeachInfo *info = data;
210 char *text = NULL;
211
212 gtk_tree_model_get (model, iter, 0, &text, -1);
213
214 g_assert (text != NULL);
215
216 if (strcmp (info->text, text) == 0)
217 {
218 info->found = TRUE;
219 info->iter = *iter;
220 return TRUE;
221 }
222
223 return FALSE;
224 }
225
226 static void
add_key(GtkWidget * widget,const char * text,guint width,guint height,int rate,MateRRRotation rotation)227 add_key (GtkWidget *widget,
228 const char *text,
229 guint width,
230 guint height,
231 int rate,
232 MateRRRotation rotation)
233 {
234 ForeachInfo info;
235 GtkComboBox *box = GTK_COMBO_BOX (widget);
236 GtkTreeModel *model = gtk_combo_box_get_model (box);
237 GtkListStore *store = GTK_LIST_STORE (model);
238
239 info.text = text;
240 info.found = FALSE;
241
242 gtk_tree_model_foreach (model, foreach, &info);
243
244 if (!info.found)
245 {
246 GtkTreeIter iter;
247 gtk_list_store_insert_with_values (store, &iter, -1,
248 0, text,
249 1, width,
250 2, height,
251 3, rate,
252 4, width * height,
253 5, rotation,
254 -1);
255 }
256 }
257
258 static gboolean
combo_select(GtkWidget * widget,const char * text)259 combo_select (GtkWidget *widget, const char *text)
260 {
261 GtkComboBox *box = GTK_COMBO_BOX (widget);
262 GtkTreeModel *model = gtk_combo_box_get_model (box);
263 ForeachInfo info;
264
265 info.text = text;
266 info.found = FALSE;
267
268 gtk_tree_model_foreach (model, foreach, &info);
269
270 if (!info.found)
271 return FALSE;
272
273 gtk_combo_box_set_active_iter (box, &info.iter);
274 return TRUE;
275 }
276
277 static MateRRMode **
get_current_modes(App * app)278 get_current_modes (App *app)
279 {
280 MateRROutput *output;
281
282 if (mate_rr_config_get_clone (app->current_configuration))
283 {
284 return mate_rr_screen_list_clone_modes (app->screen);
285 }
286 else
287 {
288 if (!app->current_output)
289 return NULL;
290
291 output = mate_rr_screen_get_output_by_name (app->screen,
292 mate_rr_output_info_get_name (app->current_output));
293
294 if (!output)
295 return NULL;
296
297 return mate_rr_output_list_modes (output);
298 }
299 }
300
301 static void
rebuild_rotation_combo(App * app)302 rebuild_rotation_combo (App *app)
303 {
304 typedef struct
305 {
306 MateRRRotation rotation;
307 const char * name;
308 } RotationInfo;
309 static const RotationInfo rotations[] = {
310 { MATE_RR_ROTATION_0, N_("Normal") },
311 { MATE_RR_ROTATION_90, N_("Left") },
312 { MATE_RR_ROTATION_270, N_("Right") },
313 { MATE_RR_ROTATION_180, N_("Upside Down") },
314 };
315 const char *selection;
316 MateRRRotation current;
317 unsigned int i;
318
319 clear_combo (app->rotation_combo);
320
321 gtk_widget_set_sensitive (app->rotation_combo,
322 app->current_output && mate_rr_output_info_is_active (app->current_output));
323
324 if (!app->current_output)
325 return;
326
327 current = mate_rr_output_info_get_rotation (app->current_output);
328
329 selection = NULL;
330 for (i = 0; i < G_N_ELEMENTS (rotations); ++i)
331 {
332 const RotationInfo *info = &(rotations[i]);
333
334 mate_rr_output_info_set_rotation (app->current_output, info->rotation);
335
336 /* NULL-GError --- FIXME: we should say why this rotation is not available! */
337 if (mate_rr_config_applicable (app->current_configuration, app->screen, NULL))
338 {
339 add_key (app->rotation_combo, _(info->name), 0, 0, 0, info->rotation);
340
341 if (info->rotation == current)
342 selection = _(info->name);
343 }
344 }
345
346 mate_rr_output_info_set_rotation (app->current_output, current);
347
348 if (!(selection && combo_select (app->rotation_combo, selection)))
349 combo_select (app->rotation_combo, _("Normal"));
350 }
351
352 static char *
make_rate_string(int hz)353 make_rate_string (int hz)
354 {
355 return g_strdup_printf (_("%d Hz"), hz);
356 }
357
358 static void
rebuild_rate_combo(App * app)359 rebuild_rate_combo (App *app)
360 {
361 MateRRMode **modes;
362 int best;
363 int i;
364
365 clear_combo (app->refresh_combo);
366
367 gtk_widget_set_sensitive (
368 app->refresh_combo, app->current_output && mate_rr_output_info_is_active (app->current_output));
369
370 if (!app->current_output
371 || !(modes = get_current_modes (app)))
372 return;
373
374 best = -1;
375 for (i = 0; modes[i] != NULL; ++i)
376 {
377 MateRRMode *mode = modes[i];
378 guint width;
379 guint height;
380 int rate;
381 int output_width;
382 int output_height;
383
384 mate_rr_output_info_get_geometry (app->current_output, NULL, NULL,
385 &output_width,
386 &output_height);
387
388 width = mate_rr_mode_get_width (mode);
389 height = mate_rr_mode_get_height (mode);
390 rate = mate_rr_mode_get_freq (mode);
391
392 if ((width == (guint) output_width) &&
393 (height == (guint) output_height))
394 {
395 add_key (app->refresh_combo,
396 idle_free (make_rate_string (rate)),
397 0, 0, rate, -1);
398
399 if (rate > best)
400 best = rate;
401 }
402 }
403
404 if (!combo_select (app->refresh_combo, idle_free (make_rate_string (mate_rr_output_info_get_refresh_rate (app->current_output)))))
405 combo_select (app->refresh_combo, idle_free (make_rate_string (best)));
406 }
407
408 static int
count_active_outputs(App * app)409 count_active_outputs (App *app)
410 {
411 int i, count = 0;
412 MateRROutputInfo **outputs = mate_rr_config_get_outputs (app->current_configuration);
413
414 for (i = 0; outputs[i] != NULL; ++i)
415 {
416 if (mate_rr_output_info_is_active (outputs[i]))
417 count++;
418 }
419
420 return count;
421 }
422
423 /* FIXME: this function is copied from mate-settings-daemon/plugins/xrandr/gsd-xrandr-manager.c.
424 * Do we need to put this function in mate-desktop for public use?
425 */
426 static gboolean
get_clone_size(MateRRScreen * screen,guint * width,guint * height)427 get_clone_size (MateRRScreen *screen,
428 guint *width,
429 guint *height)
430 {
431 MateRRMode **modes = mate_rr_screen_list_clone_modes (screen);
432 guint best_w = 0;
433 guint best_h = 0;
434 guint i;
435
436 for (i = 0; modes[i] != NULL; ++i) {
437 MateRRMode *mode = modes[i];
438 guint w, h;
439
440 w = mate_rr_mode_get_width (mode);
441 h = mate_rr_mode_get_height (mode);
442
443 if (w * h > best_w * best_h) {
444 best_w = w;
445 best_h = h;
446 }
447 }
448
449 if (best_w > 0 && best_h > 0) {
450 if (width)
451 *width = best_w;
452 if (height)
453 *height = best_h;
454
455 return TRUE;
456 }
457
458 return FALSE;
459 }
460
461 static gboolean
output_info_supports_mode(App * app,MateRROutputInfo * info,guint width,guint height)462 output_info_supports_mode (App *app,
463 MateRROutputInfo *info,
464 guint width,
465 guint height)
466 {
467 MateRROutput *output;
468 MateRRMode **modes;
469 guint i;
470
471 if (!mate_rr_output_info_is_connected (info))
472 return FALSE;
473
474 output = mate_rr_screen_get_output_by_name (app->screen, mate_rr_output_info_get_name (info));
475 if (!output)
476 return FALSE;
477
478 modes = mate_rr_output_list_modes (output);
479
480 for (i = 0; modes[i]; i++) {
481 if ((mate_rr_mode_get_width (modes[i]) == width) &&
482 (mate_rr_mode_get_height (modes[i]) == height))
483 {
484 return TRUE;
485 }
486 }
487
488 return FALSE;
489 }
490
491 /* Computes whether "Mirror Screens" (clone mode) is supported based on these criteria:
492 *
493 * 1. There is an available size for cloning.
494 *
495 * 2. There are 2 or more connected outputs that support that size.
496 */
497 static gboolean
mirror_screens_is_supported(App * app)498 mirror_screens_is_supported (App *app)
499 {
500 guint clone_width;
501 guint clone_height;
502 gboolean have_clone_size;
503 gboolean mirror_is_supported;
504
505 mirror_is_supported = FALSE;
506
507 have_clone_size = get_clone_size (app->screen, &clone_width, &clone_height);
508
509 if (have_clone_size) {
510 guint i;
511 guint num_outputs_with_clone_size = 0;
512 MateRROutputInfo **outputs = mate_rr_config_get_outputs (app->current_configuration);
513
514 for (i = 0; outputs[i] != NULL; i++)
515 {
516 /* We count the connected outputs that support the clone size. It
517 * doesn't matter if those outputs aren't actually On currently; we
518 * will turn them on in on_clone_changed().
519 */
520 if (mate_rr_output_info_is_connected (outputs[i]) &&
521 output_info_supports_mode (app, outputs[i], clone_width, clone_height))
522 {
523 num_outputs_with_clone_size++;
524 }
525 }
526
527 if (num_outputs_with_clone_size >= 2)
528 mirror_is_supported = TRUE;
529 }
530
531 return mirror_is_supported;
532 }
533
534 static void
on_scale_btn_active_changed_cb(GtkWidget * widget,App * app)535 on_scale_btn_active_changed_cb (GtkWidget *widget,
536 App *app)
537 {
538 gint scale;
539
540 if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
541 return;
542
543 scale = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "scale"));
544 g_settings_set_int (app->scale_settings, WINDOW_SCALE_KEY, scale);
545 }
546
547 static void
rebuild_scale_window(App * app)548 rebuild_scale_window (App *app)
549 {
550 GtkRadioButton *group = NULL;
551 gint32 scale;
552 int i;
553 const char *button_label[] = {_("auto detect"), _("100%"), _("200%"), NULL};
554
555 if (app->scale_bbox != NULL)
556 return;
557
558 app->scale_bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
559 gtk_container_set_border_width (GTK_CONTAINER (app->scale_bbox), 6);
560 gtk_container_add (GTK_CONTAINER (app->scale_vbox), app->scale_bbox);
561
562 scale = g_settings_get_int (app->scale_settings, WINDOW_SCALE_KEY);
563
564 for (i = 0; button_label[i] != NULL; i++)
565 {
566 GtkWidget *scale_btn;
567 scale_btn = gtk_radio_button_new_with_label_from_widget (group, button_label[i]);
568 if (!group)
569 group = GTK_RADIO_BUTTON (scale_btn);
570 if (i == scale)
571 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scale_btn), TRUE);
572
573 g_object_set_data (G_OBJECT (scale_btn),
574 "scale",
575 GINT_TO_POINTER (i));
576
577 g_signal_connect (scale_btn,
578 "toggled",
579 G_CALLBACK (on_scale_btn_active_changed_cb),
580 app);
581 gtk_container_add (GTK_CONTAINER (app->scale_bbox), scale_btn);
582 }
583 gtk_widget_show_all (app->scale_bbox);
584 }
585
586 static void
rebuild_mirror_screens(App * app)587 rebuild_mirror_screens (App *app)
588 {
589 gboolean mirror_is_active;
590 gboolean mirror_is_supported;
591
592 g_signal_handlers_block_by_func (app->clone_checkbox, G_CALLBACK (on_clone_changed), app);
593
594 mirror_is_active = app->current_configuration && mate_rr_config_get_clone (app->current_configuration);
595
596 /* If mirror_is_active, then it *must* be possible to turn mirroring off */
597 mirror_is_supported = mirror_is_active || mirror_screens_is_supported (app);
598
599 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->clone_checkbox), mirror_is_active);
600 gtk_widget_set_sensitive (app->clone_checkbox, mirror_is_supported);
601
602 g_signal_handlers_unblock_by_func (app->clone_checkbox, G_CALLBACK (on_clone_changed), app);
603 }
604
605 static void
rebuild_current_monitor_label(App * app)606 rebuild_current_monitor_label (App *app)
607 {
608 char *str, *tmp;
609 GdkRGBA color;
610 gboolean use_color;
611
612 if (app->current_output)
613 {
614 if (mate_rr_config_get_clone (app->current_configuration))
615 tmp = g_strdup (_("Mirror Screens"));
616 else
617 tmp = g_strdup_printf (_("Monitor: %s"), mate_rr_output_info_get_display_name (app->current_output));
618
619 str = g_strdup_printf ("<b>%s</b>", tmp);
620 mate_rr_labeler_get_rgba_for_output (app->labeler, app->current_output, &color);
621 use_color = TRUE;
622 g_free (tmp);
623 }
624 else
625 {
626 str = g_strdup_printf ("<b>%s</b>", _("Monitor"));
627 use_color = FALSE;
628 }
629
630 gtk_label_set_markup (GTK_LABEL (app->current_monitor_label), str);
631 g_free (str);
632
633 if (use_color)
634 {
635 GdkRGBA black = { 0, 0, 0, 1.0 };
636
637 gtk_widget_override_background_color (app->current_monitor_event_box, gtk_widget_get_state_flags (app->current_monitor_event_box), &color);
638
639 /* Make the label explicitly black. We don't want it to follow the
640 * theme's colors, since the label is always shown against a light
641 * pastel background. See bgo#556050
642 */
643 gtk_widget_override_color (app->current_monitor_label, gtk_widget_get_state_flags (app->current_monitor_label), &black);
644 }
645
646 gtk_event_box_set_visible_window (GTK_EVENT_BOX (app->current_monitor_event_box), use_color);
647 }
648
649 static void
rebuild_on_off_radios(App * app)650 rebuild_on_off_radios (App *app)
651 {
652 gboolean sensitive;
653 gboolean on_active;
654 gboolean off_active;
655
656 g_signal_handlers_block_by_func (app->monitor_on_radio, G_CALLBACK (monitor_on_off_toggled_cb), app);
657 g_signal_handlers_block_by_func (app->monitor_off_radio, G_CALLBACK (monitor_on_off_toggled_cb), app);
658
659 sensitive = FALSE;
660 on_active = FALSE;
661 off_active = FALSE;
662
663 if (!mate_rr_config_get_clone (app->current_configuration) && app->current_output)
664 {
665 if (count_active_outputs (app) > 1 || !mate_rr_output_info_is_active (app->current_output))
666 sensitive = TRUE;
667 else
668 sensitive = FALSE;
669
670 on_active = mate_rr_output_info_is_active (app->current_output);
671 off_active = !on_active;
672 }
673
674 gtk_widget_set_sensitive (app->monitor_on_radio, sensitive);
675 gtk_widget_set_sensitive (app->monitor_off_radio, sensitive);
676
677 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->monitor_on_radio), on_active);
678 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->monitor_off_radio), off_active);
679
680 g_signal_handlers_unblock_by_func (app->monitor_on_radio, G_CALLBACK (monitor_on_off_toggled_cb), app);
681 g_signal_handlers_unblock_by_func (app->monitor_off_radio, G_CALLBACK (monitor_on_off_toggled_cb), app);
682 }
683
684 static char *
make_resolution_string(guint width,guint height)685 make_resolution_string (guint width,
686 guint height)
687 {
688 return g_strdup_printf (_("%u x %u"), width, height);
689 }
690
691 static void
find_best_mode(MateRRMode ** modes,guint * out_width,guint * out_height)692 find_best_mode (MateRRMode **modes,
693 guint *out_width,
694 guint *out_height)
695 {
696 guint i;
697
698 *out_width = 0;
699 *out_height = 0;
700
701 for (i = 0; modes[i] != NULL; i++)
702 {
703 guint w, h;
704
705 w = mate_rr_mode_get_width (modes[i]);
706 h = mate_rr_mode_get_height (modes[i]);
707
708 if ((w * h) > ((*out_width) * (*out_height)))
709 {
710 *out_width = w;
711 *out_height = h;
712 }
713 }
714 }
715
716 static void
rebuild_resolution_combo(App * app)717 rebuild_resolution_combo (App *app)
718 {
719 guint i;
720 MateRRMode **modes;
721 const char *current;
722 int output_width, output_height;
723
724 clear_combo (app->resolution_combo);
725
726 if (!(modes = get_current_modes (app))
727 || !app->current_output
728 || !mate_rr_output_info_is_active (app->current_output))
729 {
730 gtk_widget_set_sensitive (app->resolution_combo, FALSE);
731 return;
732 }
733
734 g_assert (app->current_output != NULL);
735
736 mate_rr_output_info_get_geometry (app->current_output, NULL, NULL, &output_width, &output_height);
737 g_assert (output_width != 0 && output_height != 0);
738
739 gtk_widget_set_sensitive (app->resolution_combo, TRUE);
740
741 for (i = 0; modes[i] != NULL; ++i)
742 {
743 guint width, height;
744
745 width = mate_rr_mode_get_width (modes[i]);
746 height = mate_rr_mode_get_height (modes[i]);
747
748 add_key (app->resolution_combo,
749 idle_free (make_resolution_string (width, height)),
750 width, height, 0, -1);
751 }
752
753 current = idle_free (make_resolution_string ((guint) output_width, (guint) output_height));
754
755 if (!combo_select (app->resolution_combo, current))
756 {
757 guint best_w, best_h;
758
759 find_best_mode (modes, &best_w, &best_h);
760 combo_select (app->resolution_combo, idle_free (make_resolution_string (best_w, best_h)));
761 }
762 }
763
764 static void
rebuild_gui(App * app)765 rebuild_gui (App *app)
766 {
767 gboolean sensitive;
768
769 /* We would break spectacularly if we recursed, so
770 * just assert if that happens
771 */
772 g_assert (app->ignore_gui_changes == FALSE);
773
774 app->ignore_gui_changes = TRUE;
775
776 sensitive = app->current_output ? TRUE : FALSE;
777
778 #if 0
779 g_debug ("rebuild gui, is on: %d", mate_rr_output_info_is_active (app->current_output));
780 #endif
781
782 rebuild_mirror_screens (app);
783 rebuild_scale_window (app);
784 rebuild_current_monitor_label (app);
785 rebuild_on_off_radios (app);
786 rebuild_resolution_combo (app);
787 rebuild_rate_combo (app);
788 rebuild_rotation_combo (app);
789
790 #if 0
791 g_debug ("sensitive: %d, on: %d", sensitive, mate_rr_output_info_is_active (app->current_output));
792 #endif
793 gtk_widget_set_sensitive (app->panel_checkbox, sensitive);
794
795 gtk_widget_set_sensitive (app->primary_button, app->current_output && !mate_rr_output_info_get_primary(app->current_output));
796
797 app->ignore_gui_changes = FALSE;
798 }
799
800 static gboolean
get_mode(GtkWidget * widget,int * width,int * height,int * freq,MateRRRotation * rot)801 get_mode (GtkWidget *widget, int *width, int *height, int *freq, MateRRRotation *rot)
802 {
803 GtkTreeIter iter;
804 GtkTreeModel *model;
805 GtkComboBox *box = GTK_COMBO_BOX (widget);
806 int dummy;
807
808 if (!gtk_combo_box_get_active_iter (box, &iter))
809 return FALSE;
810
811 if (!width)
812 width = &dummy;
813
814 if (!height)
815 height = &dummy;
816
817 if (!freq)
818 freq = &dummy;
819
820 if (!rot)
821 rot = (MateRRRotation *)&dummy;
822
823 model = gtk_combo_box_get_model (box);
824 gtk_tree_model_get (model, &iter,
825 1, width,
826 2, height,
827 3, freq,
828 5, rot,
829 -1);
830
831 return TRUE;
832
833 }
834
835 static void
on_rotation_changed(GtkComboBox * box,gpointer data)836 on_rotation_changed (GtkComboBox *box, gpointer data)
837 {
838 App *app = data;
839 MateRRRotation rotation;
840
841 if (!app->current_output)
842 return;
843
844 if (get_mode (app->rotation_combo, NULL, NULL, NULL, &rotation))
845 mate_rr_output_info_set_rotation (app->current_output, rotation);
846
847 foo_scroll_area_invalidate (FOO_SCROLL_AREA (app->area));
848 }
849
850 static void
on_rate_changed(GtkComboBox * box,gpointer data)851 on_rate_changed (GtkComboBox *box, gpointer data)
852 {
853 App *app = data;
854 int rate;
855
856 if (!app->current_output)
857 return;
858
859 if (get_mode (app->refresh_combo, NULL, NULL, &rate, NULL))
860 mate_rr_output_info_set_refresh_rate (app->current_output, rate);
861
862 foo_scroll_area_invalidate (FOO_SCROLL_AREA (app->area));
863 }
864
865 static void
select_resolution_for_current_output(App * app)866 select_resolution_for_current_output (App *app)
867 {
868 MateRRMode **modes;
869 int width, height;
870 guint best_w, best_h;
871 int x, y;
872
873 mate_rr_output_info_get_geometry (app->current_output, &x, &y, NULL, NULL);
874
875 width = mate_rr_output_info_get_preferred_width (app->current_output);
876 height = mate_rr_output_info_get_preferred_height (app->current_output);
877
878 if (width != 0 && height != 0)
879 {
880 mate_rr_output_info_set_geometry (app->current_output, x, y, width, height);
881 return;
882 }
883
884 modes = get_current_modes (app);
885 if (!modes)
886 return;
887
888 find_best_mode (modes, &best_w, &best_h);
889 mate_rr_output_info_set_geometry (app->current_output, x, y, (int) best_w, (int) best_w);
890 }
891
892 static void
monitor_on_off_toggled_cb(GtkToggleButton * toggle,gpointer data)893 monitor_on_off_toggled_cb (GtkToggleButton *toggle, gpointer data)
894 {
895 App *app = data;
896 gboolean is_on;
897
898 if (!app->current_output)
899 return;
900
901 if (!gtk_toggle_button_get_active (toggle))
902 return;
903
904 if (GTK_WIDGET (toggle) == app->monitor_on_radio)
905 is_on = TRUE;
906 else if (GTK_WIDGET (toggle) == app->monitor_off_radio)
907 is_on = FALSE;
908 else
909 {
910 g_assert_not_reached ();
911 return;
912 }
913
914 mate_rr_output_info_set_active (app->current_output, is_on);
915
916 if (is_on)
917 select_resolution_for_current_output (app); /* The refresh rate will be picked in rebuild_rate_combo() */
918
919 rebuild_gui (app);
920 foo_scroll_area_invalidate (FOO_SCROLL_AREA (app->area));
921 }
922
923 static void
realign_outputs_after_resolution_change(App * app,MateRROutputInfo * output_that_changed,int old_width,int old_height)924 realign_outputs_after_resolution_change (App *app, MateRROutputInfo *output_that_changed, int old_width, int old_height)
925 {
926 /* We find the outputs that were below or to the right of the output that
927 * changed, and realign them; we also do that for outputs that shared the
928 * right/bottom edges with the output that changed. The outputs that are
929 * above or to the left of that output don't need to change.
930 */
931
932 int i;
933 int old_right_edge, old_bottom_edge;
934 int dx, dy;
935 int x, y, width, height;
936 MateRROutputInfo **outputs;
937
938 g_assert (app->current_configuration != NULL);
939
940 mate_rr_output_info_get_geometry (output_that_changed, &x, &y, &width, &height);
941 if (width == old_width && height == old_height)
942 return;
943
944 old_right_edge = x + old_width;
945 old_bottom_edge = y + old_height;
946
947 dx = width - old_width;
948 dy = height - old_height;
949
950 outputs = mate_rr_config_get_outputs (app->current_configuration);
951
952 for (i = 0; outputs[i] != NULL; i++)
953 {
954 int output_x, output_y;
955 int output_width, output_height;
956
957 if (outputs[i] == output_that_changed || mate_rr_output_info_is_connected (outputs[i]))
958 continue;
959
960 mate_rr_output_info_get_geometry (outputs[i], &output_x, &output_y, &output_width, &output_height);
961
962 if (output_x >= old_right_edge)
963 output_x += dx;
964 else if (output_x + output_width == old_right_edge)
965 output_x = x + width - output_width;
966
967
968 if (output_y >= old_bottom_edge)
969 output_y += dy;
970 else if (output_y + output_height == old_bottom_edge)
971 output_y = y + height - output_height;
972
973 mate_rr_output_info_set_geometry (outputs[i], output_x, output_y, output_width, output_height);
974 }
975 }
976
977 static void
on_resolution_changed(GtkComboBox * box,gpointer data)978 on_resolution_changed (GtkComboBox *box, gpointer data)
979 {
980 App *app = data;
981 int old_width, old_height;
982 int x, y;
983 int width;
984 int height;
985
986 if (!app->current_output)
987 return;
988
989 mate_rr_output_info_get_geometry (app->current_output, &x, &y, &old_width, &old_height);
990
991 if (get_mode (app->resolution_combo, &width, &height, NULL, NULL))
992 {
993 mate_rr_output_info_set_geometry (app->current_output, x, y, width, height);
994
995 if (width == 0 || height == 0)
996 mate_rr_output_info_set_active (app->current_output, FALSE);
997 else
998 mate_rr_output_info_set_active (app->current_output, TRUE);
999 }
1000
1001 realign_outputs_after_resolution_change (app, app->current_output, old_width, old_height);
1002
1003 rebuild_rate_combo (app);
1004 rebuild_rotation_combo (app);
1005
1006 foo_scroll_area_invalidate (FOO_SCROLL_AREA (app->area));
1007 }
1008
1009 static void
lay_out_outputs_horizontally(App * app)1010 lay_out_outputs_horizontally (App *app)
1011 {
1012 int i;
1013 int x;
1014 MateRROutputInfo **outputs;
1015
1016 /* Lay out all the monitors horizontally when "mirror screens" is turned
1017 * off, to avoid having all of them overlapped initially. We put the
1018 * outputs turned off on the right-hand side.
1019 */
1020
1021 x = 0;
1022
1023 /* First pass, all "on" outputs */
1024 outputs = mate_rr_config_get_outputs (app->current_configuration);
1025
1026 for (i = 0; outputs[i]; ++i)
1027 {
1028 int width, height;
1029 if (mate_rr_output_info_is_connected (outputs[i]) &&mate_rr_output_info_is_active (outputs[i]))
1030 {
1031 mate_rr_output_info_get_geometry (outputs[i], NULL, NULL, &width, &height);
1032 mate_rr_output_info_set_geometry (outputs[i], x, 0, width, height);
1033 x += width;
1034 }
1035 }
1036
1037 /* Second pass, all the black screens */
1038
1039 for (i = 0; outputs[i]; ++i)
1040 {
1041 int width, height;
1042 if (!(mate_rr_output_info_is_connected (outputs[i]) && mate_rr_output_info_is_active (outputs[i])))
1043 {
1044 mate_rr_output_info_get_geometry (outputs[i], NULL, NULL, &width, &height);
1045 mate_rr_output_info_set_geometry (outputs[i], x, 0, width, height);
1046 x += width;
1047 }
1048 }
1049
1050 }
1051
1052 static void
on_clone_changed(GtkWidget * box,gpointer data)1053 on_clone_changed (GtkWidget *box, gpointer data)
1054 {
1055 App *app = data;
1056
1057 mate_rr_config_set_clone (app->current_configuration, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (app->clone_checkbox)));
1058
1059 if (mate_rr_config_get_clone (app->current_configuration)) {
1060 guint i;
1061 guint width;
1062 guint height;
1063 MateRROutputInfo **outputs = mate_rr_config_get_outputs (app->current_configuration);
1064
1065 for (i = 0; outputs[i]; ++i) {
1066 if (mate_rr_output_info_is_connected(outputs[i])) {
1067 app->current_output = outputs[i];
1068 break;
1069 }
1070 }
1071
1072 /* Turn on all the connected screens that support the best clone mode.
1073 * The user may hit "Mirror Screens", but he shouldn't have to turn on
1074 * all the required outputs as well.
1075 */
1076
1077 get_clone_size (app->screen, &width, &height);
1078
1079 for (i = 0; outputs[i]; i++) {
1080 int x, y;
1081
1082 if (output_info_supports_mode (app, outputs[i], width, height)) {
1083 mate_rr_output_info_set_active (outputs[i], TRUE);
1084 mate_rr_output_info_get_geometry (outputs[i], &x, &y, NULL, NULL);
1085 mate_rr_output_info_set_geometry (outputs[i], x, y, (int) width, (int) height);
1086 }
1087 }
1088 } else {
1089 if (output_overlaps (app->current_output, app->current_configuration))
1090 lay_out_outputs_horizontally (app);
1091 }
1092
1093 rebuild_gui (app);
1094 }
1095
1096 #define SPACE 15
1097 #define MARGIN 15
1098
1099 static void
get_geometry(MateRROutputInfo * output,int * w,int * h)1100 get_geometry (MateRROutputInfo *output,
1101 int *w,
1102 int *h)
1103 {
1104 MateRRRotation rotation;
1105
1106 if (mate_rr_output_info_is_active (output)) {
1107 mate_rr_output_info_get_geometry (output, NULL, NULL, w, h);
1108 } else {
1109 *h = mate_rr_output_info_get_preferred_height (output);
1110 *w = mate_rr_output_info_get_preferred_width (output);
1111 }
1112 rotation = mate_rr_output_info_get_rotation (output);
1113 if ((rotation & MATE_RR_ROTATION_90) ||
1114 (rotation & MATE_RR_ROTATION_270)) {
1115 int tmp;
1116 tmp = *h;
1117 *h = *w;
1118 *w = tmp;
1119 }
1120 }
1121
1122 static GList *
list_connected_outputs(App * app,int * total_w,int * total_h)1123 list_connected_outputs (App *app,
1124 int *total_w,
1125 int *total_h)
1126 {
1127 guint i;
1128 int dummy;
1129 GList *result = NULL;
1130 MateRROutputInfo **outputs;
1131
1132 if (!total_w)
1133 total_w = &dummy;
1134 if (!total_h)
1135 total_h = &dummy;
1136
1137 *total_w = 0;
1138 *total_h = 0;
1139
1140 outputs = mate_rr_config_get_outputs(app->current_configuration);
1141 for (i = 0; outputs[i] != NULL; ++i) {
1142 if (mate_rr_output_info_is_connected (outputs[i])) {
1143 int w, h;
1144
1145 result = g_list_prepend (result, outputs[i]);
1146
1147 get_geometry (outputs[i], &w, &h);
1148
1149 *total_w += w;
1150 *total_h += h;
1151 }
1152 }
1153
1154 return g_list_reverse (result);
1155 }
1156
1157 static guint
get_n_connected(App * app)1158 get_n_connected (App *app)
1159 {
1160 GList *connected_outputs = list_connected_outputs (app, NULL, NULL);
1161 guint n = g_list_length (connected_outputs);
1162
1163 g_list_free (connected_outputs);
1164
1165 return n;
1166 }
1167
1168 static double
compute_scale(App * app)1169 compute_scale (App *app)
1170 {
1171 int available_w, available_h;
1172 int total_w, total_h;
1173 guint n_monitors;
1174 GdkRectangle viewport;
1175 GList *connected_outputs;
1176
1177 foo_scroll_area_get_viewport (FOO_SCROLL_AREA (app->area), &viewport);
1178
1179 connected_outputs = list_connected_outputs (app, &total_w, &total_h);
1180
1181 n_monitors = g_list_length (connected_outputs);
1182
1183 g_list_free (connected_outputs);
1184
1185 available_w = viewport.width - 2 * MARGIN - ((int) n_monitors - 1) * SPACE;
1186 available_h = viewport.height - 2 * MARGIN - ((int) n_monitors - 1) * SPACE;
1187
1188 return MIN ((double)available_w / total_w, (double)available_h / total_h);
1189 }
1190
1191 typedef struct Edge
1192 {
1193 MateRROutputInfo *output;
1194 int x1, y1;
1195 int x2, y2;
1196 } Edge;
1197
1198 typedef struct Snap
1199 {
1200 Edge *snapper; /* Edge that should be snapped */
1201 Edge *snappee;
1202 int dy, dx;
1203 } Snap;
1204
1205 static void
add_edge(MateRROutputInfo * output,int x1,int y1,int x2,int y2,GArray * edges)1206 add_edge (MateRROutputInfo *output, int x1, int y1, int x2, int y2, GArray *edges)
1207 {
1208 Edge e;
1209
1210 e.x1 = x1;
1211 e.x2 = x2;
1212 e.y1 = y1;
1213 e.y2 = y2;
1214 e.output = output;
1215
1216 g_array_append_val (edges, e);
1217 }
1218
1219 static void
list_edges_for_output(MateRROutputInfo * output,GArray * edges)1220 list_edges_for_output (MateRROutputInfo *output, GArray *edges)
1221 {
1222 int x, y, w, h;
1223
1224 mate_rr_output_info_get_geometry (output, &x, &y, &w, &h);
1225 get_geometry (output, &w, &h); /* accounts for rotation */
1226
1227 /* Top, Bottom, Left, Right */
1228 add_edge (output, x, y, x + w, y, edges);
1229 add_edge (output, x, y + h, x + w, y + h, edges);
1230 add_edge (output, x, y, x, y + h, edges);
1231 add_edge (output, x + w, y, x + w, y + h, edges);
1232 }
1233
1234 static void
list_edges(MateRRConfig * config,GArray * edges)1235 list_edges (MateRRConfig *config, GArray *edges)
1236 {
1237 int i;
1238 MateRROutputInfo **outputs = mate_rr_config_get_outputs (config);
1239
1240 for (i = 0; outputs[i]; ++i)
1241 {
1242 if (mate_rr_output_info_is_connected (outputs[i]))
1243 list_edges_for_output (outputs[i], edges);
1244 }
1245 }
1246
1247 static gboolean
overlap(int s1,int e1,int s2,int e2)1248 overlap (int s1, int e1, int s2, int e2)
1249 {
1250 return (!(e1 < s2 || s1 >= e2));
1251 }
1252
1253 static gboolean
horizontal_overlap(Edge * snapper,Edge * snappee)1254 horizontal_overlap (Edge *snapper, Edge *snappee)
1255 {
1256 if (snapper->y1 != snapper->y2 || snappee->y1 != snappee->y2)
1257 return FALSE;
1258
1259 return overlap (snapper->x1, snapper->x2, snappee->x1, snappee->x2);
1260 }
1261
1262 static gboolean
vertical_overlap(Edge * snapper,Edge * snappee)1263 vertical_overlap (Edge *snapper, Edge *snappee)
1264 {
1265 if (snapper->x1 != snapper->x2 || snappee->x1 != snappee->x2)
1266 return FALSE;
1267
1268 return overlap (snapper->y1, snapper->y2, snappee->y1, snappee->y2);
1269 }
1270
1271 static void
add_snap(GArray * snaps,Snap snap)1272 add_snap (GArray *snaps, Snap snap)
1273 {
1274 if (ABS (snap.dx) <= 200 || ABS (snap.dy) <= 200)
1275 g_array_append_val (snaps, snap);
1276 }
1277
1278 static void
add_edge_snaps(Edge * snapper,Edge * snappee,GArray * snaps)1279 add_edge_snaps (Edge *snapper, Edge *snappee, GArray *snaps)
1280 {
1281 Snap snap;
1282
1283 snap.snapper = snapper;
1284 snap.snappee = snappee;
1285
1286 if (horizontal_overlap (snapper, snappee))
1287 {
1288 snap.dx = 0;
1289 snap.dy = snappee->y1 - snapper->y1;
1290
1291 add_snap (snaps, snap);
1292 }
1293 else if (vertical_overlap (snapper, snappee))
1294 {
1295 snap.dy = 0;
1296 snap.dx = snappee->x1 - snapper->x1;
1297
1298 add_snap (snaps, snap);
1299 }
1300
1301 /* Corner snaps */
1302 /* 1->1 */
1303 snap.dx = snappee->x1 - snapper->x1;
1304 snap.dy = snappee->y1 - snapper->y1;
1305
1306 add_snap (snaps, snap);
1307
1308 /* 1->2 */
1309 snap.dx = snappee->x2 - snapper->x1;
1310 snap.dy = snappee->y2 - snapper->y1;
1311
1312 add_snap (snaps, snap);
1313
1314 /* 2->2 */
1315 snap.dx = snappee->x2 - snapper->x2;
1316 snap.dy = snappee->y2 - snapper->y2;
1317
1318 add_snap (snaps, snap);
1319
1320 /* 2->1 */
1321 snap.dx = snappee->x1 - snapper->x2;
1322 snap.dy = snappee->y1 - snapper->y2;
1323
1324 add_snap (snaps, snap);
1325 }
1326
1327 static void
list_snaps(MateRROutputInfo * output,GArray * edges,GArray * snaps)1328 list_snaps (MateRROutputInfo *output, GArray *edges, GArray *snaps)
1329 {
1330 guint i;
1331
1332 for (i = 0; i < edges->len; ++i) {
1333 Edge *output_edge = &(g_array_index (edges, Edge, i));
1334
1335 if (output_edge->output == output) {
1336 guint j;
1337
1338 for (j = 0; j < edges->len; ++j) {
1339 Edge *edge = &(g_array_index (edges, Edge, j));
1340
1341 if (edge->output != output)
1342 add_edge_snaps (output_edge, edge, snaps);
1343 }
1344 }
1345 }
1346 }
1347
1348 #if 0
1349 static void
1350 print_edge (Edge *edge)
1351 {
1352 g_debug ("(%d %d %d %d)", edge->x1, edge->y1, edge->x2, edge->y2);
1353 }
1354 #endif
1355
1356 static gboolean
corner_on_edge(int x,int y,Edge * e)1357 corner_on_edge (int x, int y, Edge *e)
1358 {
1359 if (x == e->x1 && x == e->x2 && y >= e->y1 && y <= e->y2)
1360 return TRUE;
1361
1362 if (y == e->y1 && y == e->y2 && x >= e->x1 && x <= e->x2)
1363 return TRUE;
1364
1365 return FALSE;
1366 }
1367
1368 static gboolean
edges_align(Edge * e1,Edge * e2)1369 edges_align (Edge *e1, Edge *e2)
1370 {
1371 if (corner_on_edge (e1->x1, e1->y1, e2))
1372 return TRUE;
1373
1374 if (corner_on_edge (e2->x1, e2->y1, e1))
1375 return TRUE;
1376
1377 return FALSE;
1378 }
1379
1380 static gboolean
output_is_aligned(MateRROutputInfo * output,GArray * edges)1381 output_is_aligned (MateRROutputInfo *output, GArray *edges)
1382 {
1383 gboolean result = FALSE;
1384 guint i;
1385
1386 for (i = 0; i < edges->len; ++i) {
1387 Edge *output_edge = &(g_array_index (edges, Edge, i));
1388
1389 if (output_edge->output == output) {
1390 guint j;
1391
1392 for (j = 0; j < edges->len; ++j) {
1393 Edge *edge = &(g_array_index (edges, Edge, j));
1394
1395 /* We are aligned if an output edge matches
1396 * an edge of another output
1397 */
1398 if ((edge->output != output_edge->output) &&
1399 edges_align (output_edge, edge)) {
1400 result = TRUE;
1401 goto done;
1402 }
1403 }
1404 }
1405 }
1406 done:
1407
1408 return result;
1409 }
1410
1411 static void
get_output_rect(MateRROutputInfo * output,GdkRectangle * rect)1412 get_output_rect (MateRROutputInfo *output, GdkRectangle *rect)
1413 {
1414 mate_rr_output_info_get_geometry (output, &rect->x, &rect->y, &rect->width, &rect->height);
1415 get_geometry (output, &rect->width, &rect->height); /* accounts for rotation */
1416 }
1417
1418 static gboolean
output_overlaps(MateRROutputInfo * output,MateRRConfig * config)1419 output_overlaps (MateRROutputInfo *output, MateRRConfig *config)
1420 {
1421 int i;
1422 GdkRectangle output_rect;
1423 MateRROutputInfo **outputs;
1424
1425 get_output_rect (output, &output_rect);
1426
1427 outputs = mate_rr_config_get_outputs (config);
1428 for (i = 0; outputs[i]; ++i)
1429 {
1430 if (outputs[i] != output && mate_rr_output_info_is_connected (outputs[i]))
1431 {
1432 GdkRectangle other_rect;
1433
1434 get_output_rect (outputs[i], &other_rect);
1435 if (gdk_rectangle_intersect (&output_rect, &other_rect, NULL))
1436 return TRUE;
1437 }
1438 }
1439
1440 return FALSE;
1441 }
1442
1443 static gboolean
mate_rr_config_is_aligned(MateRRConfig * config,GArray * edges)1444 mate_rr_config_is_aligned (MateRRConfig *config, GArray *edges)
1445 {
1446 int i;
1447 gboolean result = TRUE;
1448 MateRROutputInfo **outputs;
1449
1450 outputs = mate_rr_config_get_outputs(config);
1451 for (i = 0; outputs[i]; ++i)
1452 {
1453 if (mate_rr_output_info_is_connected (outputs[i]))
1454 {
1455 if (!output_is_aligned (outputs[i], edges))
1456 return FALSE;
1457
1458 if (output_overlaps (outputs[i], config))
1459 return FALSE;
1460 }
1461 }
1462
1463 return result;
1464 }
1465
1466 struct GrabInfo
1467 {
1468 int grab_x;
1469 int grab_y;
1470 int output_x;
1471 int output_y;
1472 };
1473
1474 static gboolean
is_corner_snap(const Snap * s)1475 is_corner_snap (const Snap *s)
1476 {
1477 return s->dx != 0 && s->dy != 0;
1478 }
1479
1480 static int
compare_snaps(gconstpointer v1,gconstpointer v2)1481 compare_snaps (gconstpointer v1, gconstpointer v2)
1482 {
1483 const Snap *s1 = v1;
1484 const Snap *s2 = v2;
1485 int sv1 = MAX (ABS (s1->dx), ABS (s1->dy));
1486 int sv2 = MAX (ABS (s2->dx), ABS (s2->dy));
1487 int d;
1488
1489 d = sv1 - sv2;
1490
1491 /* This snapping algorithm is good enough for rock'n'roll, but
1492 * this is probably a better:
1493 *
1494 * First do a horizontal/vertical snap, then
1495 * with the new coordinates from that snap,
1496 * do a corner snap.
1497 *
1498 * Right now, it's confusing that corner snapping
1499 * depends on the distance in an axis that you can't actually see.
1500 *
1501 */
1502 if (d == 0)
1503 {
1504 if (is_corner_snap (s1) && !is_corner_snap (s2))
1505 return -1;
1506 else if (is_corner_snap (s2) && !is_corner_snap (s1))
1507 return 1;
1508 else
1509 return 0;
1510 }
1511 else
1512 {
1513 return d;
1514 }
1515 }
1516
1517 /* Sets a mouse cursor for a widget's window. As a hack, you can pass
1518 * GDK_BLANK_CURSOR to mean "set the cursor to NULL" (i.e. reset the widget's
1519 * window's cursor to its default).
1520 */
1521 static void
set_cursor(GtkWidget * widget,GdkCursorType type)1522 set_cursor (GtkWidget *widget, GdkCursorType type)
1523 {
1524 GdkCursor *cursor;
1525 GdkWindow *window;
1526
1527 if (type == GDK_BLANK_CURSOR)
1528 cursor = NULL;
1529 else
1530 cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), type);
1531
1532 window = gtk_widget_get_window (widget);
1533
1534 if (window)
1535 gdk_window_set_cursor (window, cursor);
1536
1537 if (cursor)
1538 g_object_unref (cursor);
1539 }
1540
1541 static void
set_monitors_tooltip(App * app,gboolean is_dragging)1542 set_monitors_tooltip (App *app, gboolean is_dragging)
1543 {
1544 const char *text;
1545
1546 if (is_dragging)
1547 text = NULL;
1548 else
1549 text = _("Select a monitor to change its properties; drag it to rearrange its placement.");
1550
1551 gtk_widget_set_tooltip_text (app->area, text);
1552 }
1553
1554 static void
on_output_event(FooScrollArea * area,FooScrollAreaEvent * event,gpointer data)1555 on_output_event (FooScrollArea *area,
1556 FooScrollAreaEvent *event,
1557 gpointer data)
1558 {
1559 MateRROutputInfo *output = data;
1560 App *app = g_object_get_data (G_OBJECT (area), "app");
1561
1562 /* If the mouse is inside the outputs, set the cursor to "you can move me". See
1563 * on_canvas_event() for where we reset the cursor to the default if it
1564 * exits the outputs' area.
1565 */
1566 if (!mate_rr_config_get_clone (app->current_configuration) && get_n_connected (app) > 1)
1567 set_cursor (GTK_WIDGET (area), GDK_FLEUR);
1568
1569 if (event->type == FOO_BUTTON_PRESS)
1570 {
1571 GrabInfo *info;
1572
1573 app->current_output = output;
1574
1575 rebuild_gui (app);
1576 set_monitors_tooltip (app, TRUE);
1577
1578 if (!mate_rr_config_get_clone (app->current_configuration) && get_n_connected (app) > 1)
1579 {
1580 int output_x, output_y;
1581 mate_rr_output_info_get_geometry (output, &output_x, &output_y, NULL, NULL);
1582
1583 foo_scroll_area_begin_grab (area, on_output_event, data);
1584
1585 info = g_new0 (GrabInfo, 1);
1586 info->grab_x = event->x;
1587 info->grab_y = event->y;
1588 info->output_x = output_x;
1589 info->output_y = output_y;
1590
1591 g_object_set_data (G_OBJECT (output), "grab-info", info);
1592 }
1593
1594 foo_scroll_area_invalidate (area);
1595 }
1596 else
1597 {
1598 if (foo_scroll_area_is_grabbed (area))
1599 {
1600 GrabInfo *info = g_object_get_data (G_OBJECT (output), "grab-info");
1601 double scale = compute_scale (app);
1602 int old_x, old_y;
1603 int width, height;
1604 int new_x, new_y;
1605 guint i;
1606 GArray *edges, *snaps, *new_edges;
1607
1608 mate_rr_output_info_get_geometry (output, &old_x, &old_y, &width, &height);
1609 new_x = info->output_x + (int) ((double)(event->x - info->grab_x) / scale);
1610 new_y = info->output_y + (int) ((double)(event->y - info->grab_y) / scale);
1611
1612 mate_rr_output_info_set_geometry (output, new_x, new_y, width, height);
1613
1614 edges = g_array_new (TRUE, TRUE, sizeof (Edge));
1615 snaps = g_array_new (TRUE, TRUE, sizeof (Snap));
1616 new_edges = g_array_new (TRUE, TRUE, sizeof (Edge));
1617
1618 list_edges (app->current_configuration, edges);
1619 list_snaps (output, edges, snaps);
1620
1621 g_array_sort (snaps, compare_snaps);
1622
1623 mate_rr_output_info_set_geometry (output, new_x, new_y, width, height);
1624
1625 for (i = 0; i < snaps->len; ++i)
1626 {
1627 Snap *snap = &(g_array_index (snaps, Snap, i));
1628 GArray *new_edges = g_array_new (TRUE, TRUE, sizeof (Edge));
1629
1630 mate_rr_output_info_set_geometry (output, new_x + snap->dx, new_y + snap->dy, width, height);
1631
1632 g_array_set_size (new_edges, 0);
1633 list_edges (app->current_configuration, new_edges);
1634
1635 if (mate_rr_config_is_aligned (app->current_configuration, new_edges))
1636 {
1637 g_array_free (new_edges, TRUE);
1638 break;
1639 }
1640 else
1641 {
1642 mate_rr_output_info_set_geometry (output, info->output_x, info->output_y, width, height);
1643 }
1644 }
1645
1646 g_array_free (new_edges, TRUE);
1647 g_array_free (snaps, TRUE);
1648 g_array_free (edges, TRUE);
1649
1650 if (event->type == FOO_BUTTON_RELEASE)
1651 {
1652 foo_scroll_area_end_grab (area);
1653 set_monitors_tooltip (app, FALSE);
1654
1655 g_free (g_object_get_data (G_OBJECT (output), "grab-info"));
1656 g_object_set_data (G_OBJECT (output), "grab-info", NULL);
1657
1658 #if 0
1659 g_debug ("new position: %d %d %d %d", output->x, output->y, output->width, output->height);
1660 #endif
1661 }
1662
1663 foo_scroll_area_invalidate (area);
1664 }
1665 }
1666 }
1667
1668 static void
on_canvas_event(FooScrollArea * area,FooScrollAreaEvent * event,gpointer data)1669 on_canvas_event (FooScrollArea *area,
1670 FooScrollAreaEvent *event,
1671 gpointer data)
1672 {
1673 /* If the mouse exits the outputs, reset the cursor to the default. See
1674 * on_output_event() for where we set the cursor to the movement cursor if
1675 * it is over one of the outputs.
1676 */
1677 set_cursor (GTK_WIDGET (area), GDK_BLANK_CURSOR);
1678 }
1679
1680 static PangoLayout *
get_display_name(App * app,MateRROutputInfo * output)1681 get_display_name (App *app,
1682 MateRROutputInfo *output)
1683 {
1684 char *text;
1685 PangoLayout * layout;
1686
1687 if (mate_rr_config_get_clone (app->current_configuration)) {
1688 /* Translators: this is the feature where what you see on your laptop's
1689 * screen is the same as your external monitor. Here, "Mirror" is being
1690 * used as an adjective, not as a verb. For example, the Spanish
1691 * translation could be "Pantallas en Espejo", *not* "Espejar Pantallas".
1692 */
1693 text = g_strdup_printf (_("Mirror Screens"));
1694 }
1695 else {
1696 text = g_strdup_printf ("<b>%s</b>\n<small>%s</small>", mate_rr_output_info_get_display_name (output), mate_rr_output_info_get_name (output));
1697 }
1698 layout = gtk_widget_create_pango_layout (GTK_WIDGET (app->area), text);
1699 pango_layout_set_markup (layout, text, -1);
1700 g_free (text);
1701 pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
1702 return layout;
1703 }
1704
1705 static void
paint_background(FooScrollArea * area,cairo_t * cr)1706 paint_background (FooScrollArea *area,
1707 cairo_t *cr)
1708 {
1709 GdkRectangle viewport;
1710 GtkWidget *widget;
1711 GtkStyleContext *widget_style;
1712 GdkRGBA *base_color = NULL;
1713 GdkRGBA dark_color;
1714
1715 widget = GTK_WIDGET (area);
1716
1717 foo_scroll_area_get_viewport (area, &viewport);
1718
1719 widget_style = gtk_widget_get_style_context (widget);
1720
1721 gtk_style_context_save (widget_style);
1722 gtk_style_context_set_state (widget_style, GTK_STATE_FLAG_SELECTED);
1723 gtk_style_context_get (widget_style,
1724 gtk_style_context_get_state (widget_style),
1725 GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &base_color,
1726 NULL);
1727 gtk_style_context_restore (widget_style);
1728 gdk_cairo_set_source_rgba(cr, base_color);
1729 gdk_rgba_free (base_color);
1730
1731 cairo_rectangle (cr,
1732 viewport.x, viewport.y,
1733 viewport.width, viewport.height);
1734
1735 cairo_fill_preserve (cr);
1736
1737 foo_scroll_area_add_input_from_fill (area, cr, on_canvas_event, NULL);
1738
1739 gtk_style_context_save (widget_style);
1740 gtk_style_context_set_state (widget_style, GTK_STATE_FLAG_SELECTED);
1741 mate_desktop_gtk_style_get_dark_color (widget_style,
1742 gtk_style_context_get_state (widget_style),
1743 &dark_color);
1744 gtk_style_context_restore (widget_style);
1745 gdk_cairo_set_source_rgba (cr, &dark_color);
1746
1747 cairo_stroke (cr);
1748 }
1749
1750 static void
paint_output(App * app,cairo_t * cr,guint i)1751 paint_output (App *app,
1752 cairo_t *cr,
1753 guint i)
1754 {
1755 int w, h;
1756 double scale = compute_scale (app);
1757 double x, y;
1758 int output_x, output_y;
1759 MateRRRotation rotation;
1760 int total_w, total_h;
1761 GList *connected_outputs = list_connected_outputs (app, &total_w, &total_h);
1762 MateRROutputInfo *output = g_list_nth_data (connected_outputs, i);
1763 PangoLayout *layout = get_display_name (app, output);
1764 PangoRectangle ink_extent, log_extent;
1765 GdkRectangle viewport;
1766 GdkRGBA output_color;
1767 double r, g, b;
1768 double available_w;
1769 double factor;
1770
1771 cairo_save (cr);
1772
1773 foo_scroll_area_get_viewport (FOO_SCROLL_AREA (app->area), &viewport);
1774
1775 get_geometry (output, &w, &h);
1776
1777 #if 0
1778 g_debug ("%s (%p) geometry %d %d %d", output->name, output,
1779 w, h, output->rate);
1780 #endif
1781
1782 viewport.height -= 2 * MARGIN;
1783 viewport.width -= 2 * MARGIN;
1784
1785 mate_rr_output_info_get_geometry (output, &output_x, &output_y, NULL, NULL);
1786 x = output_x * scale + MARGIN + (viewport.width - total_w * scale) / 2.0;
1787 y = output_y * scale + MARGIN + (viewport.height - total_h * scale) / 2.0;
1788
1789 #if 0
1790 g_debug ("scaled: %f %f", x, y);
1791
1792 g_debug ("scale: %f", scale);
1793
1794 g_debug ("%f %f %f %f", x, y, w * scale + 0.5, h * scale + 0.5);
1795 #endif
1796
1797 cairo_translate (cr,
1798 x + (w * scale + 0.5) / 2,
1799 y + (h * scale + 0.5) / 2);
1800
1801 /* rotation is already applied in get_geometry */
1802
1803 rotation = mate_rr_output_info_get_rotation (output);
1804 if (rotation & MATE_RR_REFLECT_X)
1805 cairo_scale (cr, -1, 1);
1806
1807 if (rotation & MATE_RR_REFLECT_Y)
1808 cairo_scale (cr, 1, -1);
1809
1810 cairo_translate (cr,
1811 - x - (w * scale + 0.5) / 2,
1812 - y - (h * scale + 0.5) / 2);
1813
1814
1815 cairo_rectangle (cr, x, y, w * scale + 0.5, h * scale + 0.5);
1816 cairo_clip_preserve (cr);
1817
1818 mate_rr_labeler_get_rgba_for_output (app->labeler, output, &output_color);
1819 r = output_color.red;
1820 g = output_color.green;
1821 b = output_color.blue;
1822
1823 if (!mate_rr_output_info_is_active (output))
1824 {
1825 /* If the output is turned off, just darken the selected color */
1826 r *= 0.2;
1827 g *= 0.2;
1828 b *= 0.2;
1829 }
1830
1831 cairo_set_source_rgba (cr, r, g, b, 1.0);
1832
1833 foo_scroll_area_add_input_from_fill (FOO_SCROLL_AREA (app->area),
1834 cr, on_output_event, output);
1835 cairo_fill (cr);
1836
1837 if (output == app->current_output)
1838 {
1839 cairo_rectangle (cr, x + 2, y + 2, w * scale + 0.5 - 4, h * scale + 0.5 - 4);
1840
1841 cairo_set_line_width (cr, 4);
1842 cairo_set_source_rgba (cr, 0.33, 0.43, 0.57, 1.0);
1843 cairo_stroke (cr);
1844 }
1845
1846 cairo_rectangle (cr, x + 0.5, y + 0.5, w * scale + 0.5 - 1, h * scale + 0.5 - 1);
1847
1848 cairo_set_line_width (cr, 1);
1849 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
1850
1851 cairo_stroke (cr);
1852 cairo_set_line_width (cr, 2);
1853
1854 layout_set_font (layout, "Sans 12");
1855 pango_layout_get_pixel_extents (layout, &ink_extent, &log_extent);
1856
1857 available_w = w * scale + 0.5 - 6; /* Same as the inner rectangle's width, minus 1 pixel of padding on each side */
1858 if (available_w < ink_extent.width)
1859 factor = available_w / ink_extent.width;
1860 else
1861 factor = 1.0;
1862
1863 cairo_move_to (cr,
1864 x + ((w * scale + 0.5) - factor * log_extent.width) / 2,
1865 y + ((h * scale + 0.5) - factor * log_extent.height) / 2);
1866
1867 cairo_scale (cr, factor, factor);
1868
1869 if (mate_rr_output_info_is_active (output))
1870 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
1871 else
1872 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
1873
1874 pango_cairo_show_layout (cr, layout);
1875
1876 cairo_restore (cr);
1877
1878 g_object_unref (layout);
1879 }
1880
1881 static void
on_area_paint(FooScrollArea * area,cairo_t * cr,gpointer data)1882 on_area_paint (FooScrollArea *area,
1883 cairo_t *cr,
1884 gpointer data)
1885 {
1886 App *app = data;
1887 GList *connected_outputs = NULL;
1888 GList *list;
1889
1890 paint_background (area, cr);
1891
1892 if (!app->current_configuration)
1893 return;
1894
1895 connected_outputs = list_connected_outputs (app, NULL, NULL);
1896
1897 #if 0
1898 double scale;
1899 scale = compute_scale (app);
1900 g_debug ("scale: %f", scale);
1901 #endif
1902
1903 for (list = connected_outputs; list != NULL; list = list->next)
1904 {
1905 int pos;
1906
1907 if ((pos = g_list_position (connected_outputs, list)) != -1)
1908 paint_output (app, cr, (guint) pos);
1909
1910 if (mate_rr_config_get_clone (app->current_configuration))
1911 break;
1912 }
1913 }
1914
1915 static void
make_text_combo(GtkWidget * widget,int sort_column)1916 make_text_combo (GtkWidget *widget, int sort_column)
1917 {
1918 GtkComboBox *box = GTK_COMBO_BOX (widget);
1919 GtkListStore *store = gtk_list_store_new (
1920 6,
1921 G_TYPE_STRING, /* Text */
1922 G_TYPE_INT, /* Width */
1923 G_TYPE_INT, /* Height */
1924 G_TYPE_INT, /* Frequency */
1925 G_TYPE_INT, /* Width * Height */
1926 G_TYPE_INT); /* Rotation */
1927
1928 GtkCellRenderer *cell;
1929
1930 gtk_cell_layout_clear (GTK_CELL_LAYOUT (widget));
1931
1932 gtk_combo_box_set_model (box, GTK_TREE_MODEL (store));
1933
1934 cell = gtk_cell_renderer_text_new ();
1935 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (box), cell, TRUE);
1936 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (box), cell,
1937 "text", 0,
1938 NULL);
1939
1940 if (sort_column != -1)
1941 {
1942 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
1943 sort_column,
1944 GTK_SORT_DESCENDING);
1945 }
1946 }
1947
1948 static void
compute_virtual_size_for_configuration(MateRRConfig * config,int * ret_width,int * ret_height)1949 compute_virtual_size_for_configuration (MateRRConfig *config, int *ret_width, int *ret_height)
1950 {
1951 int i;
1952 int width, height;
1953 int output_x, output_y, output_width, output_height;
1954 MateRROutputInfo **outputs;
1955
1956 width = height = 0;
1957
1958 outputs = mate_rr_config_get_outputs (config);
1959 for (i = 0; outputs[i] != NULL; i++)
1960 {
1961 if (mate_rr_output_info_is_active (outputs[i]))
1962 {
1963 mate_rr_output_info_get_geometry (outputs[i], &output_x, &output_y, &output_width, &output_height);
1964 width = MAX (width, output_x + output_width);
1965 height = MAX (height, output_y + output_height);
1966 }
1967 }
1968
1969 *ret_width = width;
1970 *ret_height = height;
1971 }
1972
1973 static void
check_required_virtual_size(App * app)1974 check_required_virtual_size (App *app)
1975 {
1976 int req_width, req_height;
1977 int min_width, max_width;
1978 int min_height, max_height;
1979
1980 compute_virtual_size_for_configuration (app->current_configuration, &req_width, &req_height);
1981
1982 mate_rr_screen_get_ranges (app->screen, &min_width, &max_width, &min_height, &max_height);
1983
1984 #if 0
1985 g_debug ("X Server supports:");
1986 g_debug ("min_width = %d, max_width = %d", min_width, max_width);
1987 g_debug ("min_height = %d, max_height = %d", min_height, max_height);
1988
1989 g_debug ("Requesting size of %dx%d", req_width, req_height);
1990 #endif
1991
1992 if (!(min_width <= req_width && req_width <= max_width
1993 && min_height <= req_height && req_height <= max_height))
1994 {
1995 /* FIXME: present a useful dialog, maybe even before the user tries to Apply */
1996 #if 0
1997 g_debug ("Your X server needs a larger Virtual size!");
1998 #endif
1999 }
2000 }
2001
2002 static void
begin_version2_apply_configuration(App * app,GdkWindow * parent_window,guint32 timestamp)2003 begin_version2_apply_configuration (App *app, GdkWindow *parent_window, guint32 timestamp)
2004 {
2005 XID parent_window_xid;
2006 GError *error = NULL;
2007
2008 parent_window_xid = GDK_WINDOW_XID (parent_window);
2009
2010 app->proxy = g_dbus_proxy_new_sync (app->connection,
2011 G_DBUS_PROXY_FLAGS_NONE,
2012 NULL,
2013 "org.mate.SettingsDaemon",
2014 "/org/mate/SettingsDaemon/XRANDR",
2015 "org.mate.SettingsDaemon.XRANDR_2",
2016 NULL,
2017 &error);
2018 if (app->proxy == NULL) {
2019 g_warning ("Failed to get dbus connection: %s", error->message);
2020 g_error_free (error);
2021 } else {
2022 app->apply_configuration_state = APPLYING_VERSION_2;
2023 g_dbus_proxy_call (app->proxy,
2024 "ApplyConfiguration",
2025 g_variant_new ("(xx)", parent_window_xid, timestamp),
2026 G_DBUS_CALL_FLAGS_NONE,
2027 -1,
2028 NULL,
2029 (GAsyncReadyCallback) apply_configuration_returned_cb,
2030 app);
2031 }
2032 }
2033
2034 static void
begin_version1_apply_configuration(App * app)2035 begin_version1_apply_configuration (App *app)
2036 {
2037 GError *error = NULL;
2038
2039 app->proxy = g_dbus_proxy_new_sync (app->connection,
2040 G_DBUS_PROXY_FLAGS_NONE,
2041 NULL,
2042 "org.mate.SettingsDaemon",
2043 "/org/mate/SettingsDaemon/XRANDR",
2044 "org.mate.SettingsDaemon.XRANDR",
2045 NULL,
2046 &error);
2047 if (app->proxy == NULL) {
2048 g_warning ("Failed to get dbus connection: %s", error->message);
2049 g_error_free (error);
2050 } else {
2051 app->apply_configuration_state = APPLYING_VERSION_1;
2052 g_dbus_proxy_call (app->proxy,
2053 "ApplyConfiguration",
2054 g_variant_new ("()"),
2055 G_DBUS_CALL_FLAGS_NONE,
2056 -1,
2057 NULL,
2058 (GAsyncReadyCallback) apply_configuration_returned_cb,
2059 app);
2060 }
2061 }
2062
2063 static void
ensure_current_configuration_is_saved(void)2064 ensure_current_configuration_is_saved (void)
2065 {
2066 MateRRScreen *rr_screen;
2067 MateRRConfig *rr_config;
2068
2069 /* Normally, mate_rr_config_save() creates a backup file based on the
2070 * old monitors.xml. However, if *that* file didn't exist, there is
2071 * nothing from which to create a backup. So, here we'll save the
2072 * current/unchanged configuration and then let our caller call
2073 * mate_rr_config_save() again with the new/changed configuration, so
2074 * that there *will* be a backup file in the end.
2075 */
2076
2077 rr_screen = mate_rr_screen_new (gdk_screen_get_default (), NULL); /* NULL-GError */
2078 if (!rr_screen)
2079 return;
2080
2081 rr_config = mate_rr_config_new_current (rr_screen, NULL);
2082 mate_rr_config_save (rr_config, NULL); /* NULL-GError */
2083
2084 g_object_unref (rr_config);
2085 g_object_unref (rr_screen);
2086 }
2087
2088 /* Callback for g_dbus_proxy_call() */
2089 static void
apply_configuration_returned_cb(GObject * source_object,GAsyncResult * res,gpointer data)2090 apply_configuration_returned_cb (GObject *source_object,
2091 GAsyncResult *res,
2092 gpointer data)
2093 {
2094 GVariant *variant;
2095 GError *error;
2096 App *app = data;
2097
2098 error = NULL;
2099
2100 if (app->proxy == NULL)
2101 return;
2102
2103 variant = g_dbus_proxy_call_finish (app->proxy, res, &error);
2104 if (variant == NULL) {
2105 if (app->apply_configuration_state == APPLYING_VERSION_2
2106 && g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
2107 g_error_free (error);
2108
2109 g_object_unref (app->proxy);
2110 app->proxy = NULL;
2111
2112 begin_version1_apply_configuration (app);
2113 return;
2114 } else {
2115 /* We don't pop up an error message; mate-settings-daemon already does that
2116 * in case the selected RANDR configuration could not be applied.
2117 */
2118 g_error_free (error);
2119 }
2120 }
2121
2122 g_object_unref (app->proxy);
2123 app->proxy = NULL;
2124
2125 g_object_unref (app->connection);
2126 app->connection = NULL;
2127
2128 gtk_widget_set_sensitive (app->dialog, TRUE);
2129 }
2130
2131 static gboolean
sanitize_and_save_configuration(App * app)2132 sanitize_and_save_configuration (App *app)
2133 {
2134 GError *error;
2135
2136 mate_rr_config_sanitize (app->current_configuration);
2137
2138 check_required_virtual_size (app);
2139
2140 foo_scroll_area_invalidate (FOO_SCROLL_AREA (app->area));
2141
2142 ensure_current_configuration_is_saved ();
2143
2144 error = NULL;
2145 if (!mate_rr_config_save (app->current_configuration, &error))
2146 {
2147 error_message (app, _("Could not save the monitor configuration"), error->message);
2148 g_error_free (error);
2149 return FALSE;
2150 }
2151
2152 return TRUE;
2153 }
2154
2155 static void
apply(App * app)2156 apply (App *app)
2157 {
2158 GError *error = NULL;
2159
2160 if (!sanitize_and_save_configuration (app))
2161 return;
2162
2163 g_assert (app->connection == NULL);
2164 g_assert (app->proxy == NULL);
2165
2166 app->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
2167 if (app->connection == NULL) {
2168 error_message (app, _("Could not get session bus while applying display configuration"), error->message);
2169 g_error_free (error);
2170 return;
2171 }
2172
2173 gtk_widget_set_sensitive (app->dialog, FALSE);
2174
2175 begin_version2_apply_configuration (app, gtk_widget_get_window (app->dialog), app->apply_button_clicked_timestamp);
2176 }
2177
2178 static void
on_detect_displays(GtkWidget * widget,gpointer data)2179 on_detect_displays (GtkWidget *widget, gpointer data)
2180 {
2181 App *app = data;
2182 GError *error;
2183
2184 error = NULL;
2185 if (!mate_rr_screen_refresh (app->screen, &error)) {
2186 if (error) {
2187 error_message (app, _("Could not detect displays"), error->message);
2188 g_error_free (error);
2189 }
2190 }
2191 }
2192
2193 static void
set_primary(GtkWidget * widget,gpointer data)2194 set_primary (GtkWidget *widget, gpointer data)
2195 {
2196 App *app = data;
2197 int i;
2198 MateRROutputInfo **outputs;
2199
2200 if (!app->current_output)
2201 return;
2202
2203 outputs = mate_rr_config_get_outputs (app->current_configuration);
2204 for (i=0; outputs[i]!=NULL; i++) {
2205 mate_rr_output_info_set_primary (outputs[i], outputs[i] == app->current_output);
2206 }
2207
2208 gtk_widget_set_sensitive (app->primary_button, !mate_rr_output_info_get_primary(app->current_output));
2209 }
2210
2211 #define MSD_XRANDR_SCHEMA "org.mate.SettingsDaemon.plugins.xrandr"
2212 #define SHOW_ICON_KEY "show-notification-icon"
2213 #define DEFAULT_CONFIGURATION_FILE_KEY "default-configuration-file"
2214
2215 static void
on_show_icon_toggled(GtkWidget * widget,gpointer data)2216 on_show_icon_toggled (GtkWidget *widget, gpointer data)
2217 {
2218 GtkToggleButton *tb = GTK_TOGGLE_BUTTON (widget);
2219 App *app = data;
2220
2221 g_settings_set_boolean (app->settings, SHOW_ICON_KEY,
2222 gtk_toggle_button_get_active (tb));
2223 }
2224
2225 static MateRROutputInfo *
get_nearest_output(MateRRConfig * configuration,int x,int y)2226 get_nearest_output (MateRRConfig *configuration, int x, int y)
2227 {
2228 int i;
2229 int nearest_index;
2230 int nearest_dist;
2231 MateRROutputInfo **outputs;
2232
2233 nearest_index = -1;
2234 nearest_dist = G_MAXINT;
2235
2236 outputs = mate_rr_config_get_outputs (configuration);
2237 for (i = 0; outputs[i] != NULL; i++)
2238 {
2239 int dist_x, dist_y;
2240 int output_x, output_y, output_width, output_height;
2241
2242 if (!(mate_rr_output_info_is_connected(outputs[i]) && mate_rr_output_info_is_active (outputs[i])))
2243 continue;
2244
2245 mate_rr_output_info_get_geometry (outputs[i], &output_x, &output_y, &output_width, &output_height);
2246 if (x < output_x)
2247 dist_x = output_x - x;
2248 else if (x >= output_x + output_width)
2249 dist_x = x - (output_x + output_width) + 1;
2250 else
2251 dist_x = 0;
2252
2253 if (y < output_y)
2254 dist_y = output_y - y;
2255 else if (y >= output_y + output_height)
2256 dist_y = y - (output_y + output_height) + 1;
2257 else
2258 dist_y = 0;
2259
2260 if (MIN (dist_x, dist_y) < nearest_dist)
2261 {
2262 nearest_dist = MIN (dist_x, dist_y);
2263 nearest_index = i;
2264 }
2265 }
2266
2267 if (nearest_index != -1)
2268 return outputs[nearest_index];
2269 else
2270 return NULL;
2271
2272 }
2273
2274 /* Gets the output that contains the largest intersection with the window.
2275 * Logic stolen from gdk_screen_get_monitor_at_window().
2276 */
2277 static MateRROutputInfo *
get_output_for_window(MateRRConfig * configuration,GdkWindow * window)2278 get_output_for_window (MateRRConfig *configuration, GdkWindow *window)
2279 {
2280 GdkRectangle win_rect;
2281 int i;
2282 int largest_area;
2283 int largest_index;
2284 MateRROutputInfo **outputs;
2285
2286 gdk_window_get_geometry (window, &win_rect.x, &win_rect.y, &win_rect.width, &win_rect.height);
2287 gdk_window_get_origin (window, &win_rect.x, &win_rect.y);
2288
2289 largest_area = 0;
2290 largest_index = -1;
2291
2292 outputs = mate_rr_config_get_outputs (configuration);
2293 for (i = 0; outputs[i] != NULL; i++)
2294 {
2295 GdkRectangle output_rect, intersection;
2296
2297 mate_rr_output_info_get_geometry (outputs[i], &output_rect.x, &output_rect.y, &output_rect.width, &output_rect.height);
2298
2299 if (mate_rr_output_info_is_connected (outputs[i]) && gdk_rectangle_intersect (&win_rect, &output_rect, &intersection))
2300 {
2301 int area;
2302
2303 area = intersection.width * intersection.height;
2304 if (area > largest_area)
2305 {
2306 largest_area = area;
2307 largest_index = i;
2308 }
2309 }
2310 }
2311
2312 if (largest_index != -1)
2313 return outputs[largest_index];
2314 else
2315 return get_nearest_output (configuration,
2316 win_rect.x + win_rect.width / 2,
2317 win_rect.y + win_rect.height / 2);
2318 }
2319
2320 /* We select the current output, i.e. select the one being edited, based on
2321 * which output is showing the configuration dialog.
2322 */
2323 static void
select_current_output_from_dialog_position(App * app)2324 select_current_output_from_dialog_position (App *app)
2325 {
2326 if (gtk_widget_get_realized (app->dialog))
2327 app->current_output = get_output_for_window (app->current_configuration, gtk_widget_get_window (app->dialog));
2328 else
2329 app->current_output = NULL;
2330
2331 rebuild_gui (app);
2332 }
2333
2334 /* This is a GtkWidget::map-event handler. We wait for the display-properties
2335 * dialog to be mapped, and then we select the output which corresponds to the
2336 * monitor on which the dialog is being shown.
2337 */
2338 static gboolean
dialog_map_event_cb(GtkWidget * widget,GdkEventAny * event,gpointer data)2339 dialog_map_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
2340 {
2341 App *app = data;
2342
2343 select_current_output_from_dialog_position (app);
2344 return FALSE;
2345 }
2346
2347 static void
apply_button_clicked_cb(GtkButton * button,gpointer data)2348 apply_button_clicked_cb (GtkButton *button, gpointer data)
2349 {
2350 App *app = data;
2351
2352 /* We simply store the timestamp at which the Apply button was clicked.
2353 * We'll just wait for the dialog to return from gtk_dialog_run(), and
2354 * *then* use the timestamp when applying the RANDR configuration.
2355 */
2356
2357 app->apply_button_clicked_timestamp = gtk_get_current_event_time ();
2358 }
2359
2360 static void
success_dialog_for_make_default(App * app)2361 success_dialog_for_make_default (App *app)
2362 {
2363 GtkWidget *dialog;
2364
2365 dialog = gtk_message_dialog_new (GTK_WINDOW (app->dialog),
2366 GTK_DIALOG_MODAL,
2367 GTK_MESSAGE_INFO,
2368 GTK_BUTTONS_OK,
2369 _("The monitor configuration has been saved"));
2370 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2371 _("This configuration will be used the next time someone logs in."));
2372
2373 gtk_dialog_run (GTK_DIALOG (dialog));
2374 gtk_widget_destroy (dialog);
2375 }
2376
2377 static void
error_dialog_for_make_default(App * app,const char * error_text)2378 error_dialog_for_make_default (App *app, const char *error_text)
2379 {
2380 error_message (app, _("Could not set the default configuration for monitors"), error_text);
2381 }
2382
2383 static void
make_default(App * app)2384 make_default (App *app)
2385 {
2386 char *command_line;
2387 char *source_filename;
2388 char *dest_filename;
2389 char *dest_basename;
2390 char *std_error;
2391 gint exit_status;
2392 GError *error;
2393
2394 if (!sanitize_and_save_configuration (app))
2395 return;
2396
2397 dest_filename = g_settings_get_string (app->settings, DEFAULT_CONFIGURATION_FILE_KEY);
2398 if (!dest_filename)
2399 return; /* FIXME: present an error? */
2400
2401 dest_basename = g_path_get_basename (dest_filename);
2402
2403 source_filename = mate_rr_config_get_intended_filename ();
2404
2405 command_line = g_strdup_printf ("pkexec %s/mate-display-properties-install-systemwide %s %s",
2406 SBINDIR,
2407 source_filename,
2408 dest_basename);
2409
2410 error = NULL;
2411 if (!g_spawn_command_line_sync (command_line, NULL, &std_error, &exit_status, &error))
2412 {
2413 error_dialog_for_make_default (app, error->message);
2414 g_error_free (error);
2415 }
2416 else if (!WIFEXITED (exit_status) || WEXITSTATUS (exit_status) != 0)
2417 {
2418 error_dialog_for_make_default (app, std_error);
2419 }
2420 else
2421 {
2422 success_dialog_for_make_default (app);
2423 }
2424
2425 g_free (std_error);
2426 g_free (dest_filename);
2427 g_free (dest_basename);
2428 g_free (source_filename);
2429 g_free (command_line);
2430 }
2431
2432 static GtkWidget*
_gtk_builder_get_widget(GtkBuilder * builder,const gchar * name)2433 _gtk_builder_get_widget (GtkBuilder *builder, const gchar *name)
2434 {
2435 return GTK_WIDGET (gtk_builder_get_object (builder, name));
2436 }
2437
2438 static void
run_application(App * app)2439 run_application (App *app)
2440 {
2441 GtkBuilder *builder;
2442 GtkWidget *align;
2443 GError *error = NULL;
2444
2445 builder = gtk_builder_new_from_resource ("/org/mate/mcc/display/display-capplet.ui");
2446
2447 app->screen = mate_rr_screen_new (gdk_screen_get_default (), &error);
2448 g_signal_connect (app->screen, "changed", G_CALLBACK (on_screen_changed), app);
2449 if (!app->screen)
2450 {
2451 error_message (NULL, _("Could not get screen information"), error->message);
2452 g_error_free (error);
2453 g_object_unref (builder);
2454 return;
2455 }
2456
2457 app->settings = g_settings_new (MSD_XRANDR_SCHEMA);
2458 app->scale_settings = g_settings_new (MATE_INTERFACE_SCHEMA);
2459
2460 app->dialog = _gtk_builder_get_widget (builder, "dialog");
2461 g_signal_connect_after (app->dialog, "map-event",
2462 G_CALLBACK (dialog_map_event_cb), app);
2463
2464 gtk_window_set_default_icon_name ("preferences-desktop-display");
2465 gtk_window_set_icon_name (GTK_WINDOW (app->dialog),
2466 "preferences-desktop-display");
2467
2468 app->current_monitor_event_box = _gtk_builder_get_widget (builder,
2469 "current_monitor_event_box");
2470 app->current_monitor_label = _gtk_builder_get_widget (builder,
2471 "current_monitor_label");
2472
2473 app->monitor_on_radio = _gtk_builder_get_widget (builder,
2474 "monitor_on_radio");
2475 app->monitor_off_radio = _gtk_builder_get_widget (builder,
2476 "monitor_off_radio");
2477 g_signal_connect (app->monitor_on_radio, "toggled",
2478 G_CALLBACK (monitor_on_off_toggled_cb), app);
2479 g_signal_connect (app->monitor_off_radio, "toggled",
2480 G_CALLBACK (monitor_on_off_toggled_cb), app);
2481
2482 app->resolution_combo = _gtk_builder_get_widget (builder,
2483 "resolution_combo");
2484 g_signal_connect (app->resolution_combo, "changed",
2485 G_CALLBACK (on_resolution_changed), app);
2486
2487 app->refresh_combo = _gtk_builder_get_widget (builder, "refresh_combo");
2488 g_signal_connect (app->refresh_combo, "changed",
2489 G_CALLBACK (on_rate_changed), app);
2490
2491 app->rotation_combo = _gtk_builder_get_widget (builder, "rotation_combo");
2492 g_signal_connect (app->rotation_combo, "changed",
2493 G_CALLBACK (on_rotation_changed), app);
2494
2495 app->scale_vbox = _gtk_builder_get_widget (builder, "scale_vbox");
2496
2497 app->clone_checkbox = _gtk_builder_get_widget (builder, "clone_checkbox");
2498 g_signal_connect (app->clone_checkbox, "toggled",
2499 G_CALLBACK (on_clone_changed), app);
2500
2501 g_signal_connect (_gtk_builder_get_widget (builder, "detect_displays_button"),
2502 "clicked", G_CALLBACK (on_detect_displays), app);
2503
2504 app->primary_button = _gtk_builder_get_widget (builder, "primary_button");
2505
2506 g_signal_connect (app->primary_button, "clicked", G_CALLBACK (set_primary), app);
2507
2508 app->show_icon_checkbox = _gtk_builder_get_widget (builder,
2509 "show_notification_icon");
2510
2511 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (app->show_icon_checkbox),
2512 g_settings_get_boolean (app->settings, SHOW_ICON_KEY));
2513
2514 g_signal_connect (app->show_icon_checkbox, "toggled", G_CALLBACK (on_show_icon_toggled), app);
2515
2516 app->panel_checkbox = _gtk_builder_get_widget (builder, "panel_checkbox");
2517
2518 make_text_combo (app->resolution_combo, 4);
2519 make_text_combo (app->refresh_combo, 3);
2520 make_text_combo (app->rotation_combo, -1);
2521
2522 g_assert (app->panel_checkbox);
2523
2524 /* Scroll Area */
2525 app->area = (GtkWidget *)foo_scroll_area_new ();
2526
2527 g_object_set_data (G_OBJECT (app->area), "app", app);
2528
2529 set_monitors_tooltip (app, FALSE);
2530
2531 /* FIXME: this should be computed dynamically */
2532 foo_scroll_area_set_min_size (FOO_SCROLL_AREA (app->area), -1, 200);
2533 gtk_widget_show (app->area);
2534 g_signal_connect (app->area, "paint",
2535 G_CALLBACK (on_area_paint), app);
2536 g_signal_connect (app->area, "viewport_changed",
2537 G_CALLBACK (on_viewport_changed), app);
2538
2539 align = _gtk_builder_get_widget (builder, "align");
2540
2541 gtk_container_add (GTK_CONTAINER (align), app->area);
2542
2543 app->apply_button = _gtk_builder_get_widget (builder, "apply_button");
2544 g_signal_connect (app->apply_button, "clicked",
2545 G_CALLBACK (apply_button_clicked_cb), app);
2546
2547 on_screen_changed (app->screen, app);
2548
2549 g_object_unref (builder);
2550
2551 restart:
2552 switch (gtk_dialog_run (GTK_DIALOG (app->dialog)))
2553 {
2554 default:
2555 /* Fall Through */
2556 case GTK_RESPONSE_DELETE_EVENT:
2557 case GTK_RESPONSE_CLOSE:
2558 #if 0
2559 g_debug ("Close");
2560 #endif
2561 break;
2562
2563 case GTK_RESPONSE_HELP:
2564 gtk_show_uri_on_window (GTK_WINDOW (app->dialog),
2565 "help:mate-user-guide/goscustdesk-70",
2566 gtk_get_current_event_time (),
2567 &error);
2568 if (error)
2569 {
2570 error_message (app, _("Could not open help content"), error->message);
2571 g_error_free (error);
2572 }
2573 goto restart;
2574 break;
2575
2576 case GTK_RESPONSE_APPLY:
2577 apply (app);
2578 goto restart;
2579 break;
2580
2581 case RESPONSE_MAKE_DEFAULT:
2582 make_default (app);
2583 goto restart;
2584 break;
2585 }
2586
2587 gtk_widget_destroy (app->dialog);
2588 g_object_unref (app->screen);
2589 g_object_unref (app->settings);
2590 g_object_unref (app->scale_settings);
2591 }
2592
2593 int
main(int argc,char ** argv)2594 main (int argc, char **argv)
2595 {
2596 App *app;
2597
2598 capplet_init (NULL, &argc, &argv);
2599
2600 app = g_new0 (App, 1);
2601
2602 run_application (app);
2603
2604 g_free (app);
2605
2606 return 0;
2607 }
2608