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