1 /* mate-rr-config.c
2  * -*- c-basic-offset: 4 -*-
3  *
4  * Copyright 2007, 2008, Red Hat, Inc.
5  * Copyright 2010 Giovanni Campagna
6  *
7  * This file is part of the Mate Library.
8  *
9  * The Mate Library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public License as
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * The Mate Library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with the Mate Library; see the file COPYING.LIB.  If not,
21  * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  *
24  * Author: Soren Sandmann <sandmann@redhat.com>
25  */
26 
27 #define MATE_DESKTOP_USE_UNSTABLE_API
28 
29 #include <config.h>
30 #include <glib/gi18n-lib.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <glib.h>
34 #include <glib/gstdio.h>
35 
36 #include <X11/Xlib.h>
37 #include <gdk/gdkx.h>
38 
39 #include "mate-rr-config.h"
40 
41 #include "edid.h"
42 #include "mate-rr-private.h"
43 
44 #define CONFIG_INTENDED_BASENAME "monitors.xml"
45 #define CONFIG_BACKUP_BASENAME "monitors.xml.backup"
46 
47 /* In version 0 of the config file format, we had several <configuration>
48  * toplevel elements and no explicit version number.  So, the filed looked
49  * like
50  *
51  *   <configuration>
52  *     ...
53  *   </configuration>
54  *   <configuration>
55  *     ...
56  *   </configuration>
57  *
58  * Since version 1 of the config file, the file has a toplevel <monitors>
59  * element to group all the configurations.  That element has a "version"
60  * attribute which is an integer. So, the file looks like this:
61  *
62  *   <monitors version="1">
63  *     <configuration>
64  *       ...
65  *     </configuration>
66  *     <configuration>
67  *       ...
68  *     </configuration>
69  *   </monitors>
70  */
71 
72 /* A helper wrapper around the GMarkup parser stuff */
73 static gboolean parse_file_gmarkup (const gchar *file,
74 				    const GMarkupParser *parser,
75 				    gpointer data,
76 				    GError **err);
77 
78 typedef struct CrtcAssignment CrtcAssignment;
79 
80 static gboolean         crtc_assignment_apply (CrtcAssignment   *assign,
81 					       guint32           timestamp,
82 					       GError          **error);
83 static CrtcAssignment  *crtc_assignment_new   (MateRRScreen      *screen,
84 					       MateRROutputInfo **outputs,
85 					       GError            **error);
86 static void             crtc_assignment_free  (CrtcAssignment   *assign);
87 
88 enum {
89   PROP_0,
90   PROP_SCREEN,
91   PROP_LAST
92 };
93 
94 G_DEFINE_TYPE_WITH_PRIVATE (MateRRConfig, mate_rr_config, G_TYPE_OBJECT)
95 
96 typedef struct Parser Parser;
97 
98 /* Parser for monitor configurations */
99 struct Parser
100 {
101     int			config_file_version;
102     MateRROutputInfo *	output;
103     MateRRConfig *	configuration;
104     GPtrArray *		outputs;
105     GPtrArray *		configurations;
106     GQueue *		stack;
107 };
108 
109 static int
parse_int(const char * text)110 parse_int (const char *text)
111 {
112     return strtol (text, NULL, 0);
113 }
114 
115 static guint
parse_uint(const char * text)116 parse_uint (const char *text)
117 {
118     return strtoul (text, NULL, 0);
119 }
120 
121 static gboolean
stack_is(Parser * parser,const char * s1,...)122 stack_is (Parser *parser,
123 	  const char *s1,
124 	  ...)
125 {
126     GList *stack = NULL;
127     const char *s;
128     GList *l1, *l2;
129     va_list args;
130 
131     stack = g_list_prepend (stack, (gpointer)s1);
132 
133     va_start (args, s1);
134 
135     s = va_arg (args, const char *);
136     while (s)
137     {
138 	stack = g_list_prepend (stack, (gpointer)s);
139 	s = va_arg (args, const char *);
140     }
141 
142     va_end (args);
143 
144     l1 = stack;
145     l2 = parser->stack->head;
146 
147     while (l1 && l2)
148     {
149 	if (strcmp (l1->data, l2->data) != 0)
150 	{
151 	    g_list_free (stack);
152 	    return FALSE;
153 	}
154 
155 	l1 = l1->next;
156 	l2 = l2->next;
157     }
158 
159     g_list_free (stack);
160 
161     return (!l1 && !l2);
162 }
163 
164 static void
handle_start_element(GMarkupParseContext * context,const gchar * name,const gchar ** attr_names,const gchar ** attr_values,gpointer user_data,GError ** err)165 handle_start_element (GMarkupParseContext *context,
166 		      const gchar         *name,
167 		      const gchar        **attr_names,
168 		      const gchar        **attr_values,
169 		      gpointer             user_data,
170 		      GError             **err)
171 {
172     Parser *parser = user_data;
173 
174     if (strcmp (name, "output") == 0)
175     {
176 	int i;
177 	g_assert (parser->output == NULL);
178 
179 	parser->output = g_object_new (MATE_TYPE_RR_OUTPUT_INFO, NULL);
180 	parser->output->priv->rotation = 0;
181 
182 	for (i = 0; attr_names[i] != NULL; ++i)
183 	{
184 	    if (strcmp (attr_names[i], "name") == 0)
185 	    {
186 		parser->output->priv->name = g_strdup (attr_values[i]);
187 		break;
188 	    }
189 	}
190 
191 	if (!parser->output->priv->name)
192 	{
193 	    /* This really shouldn't happen, but it's better to make
194 	     * something up than to crash later.
195 	     */
196 	    g_warning ("Malformed monitor configuration file");
197 
198 	    parser->output->priv->name = g_strdup ("default");
199 	}
200 	parser->output->priv->connected = FALSE;
201 	parser->output->priv->on = FALSE;
202 	parser->output->priv->primary = FALSE;
203     }
204     else if (strcmp (name, "configuration") == 0)
205     {
206 	g_assert (parser->configuration == NULL);
207 
208 	parser->configuration = g_object_new (MATE_TYPE_RR_CONFIG, NULL);
209 	parser->configuration->priv->clone = FALSE;
210 	parser->configuration->priv->outputs = NULL;
211     }
212     else if (strcmp (name, "monitors") == 0)
213     {
214 	int i;
215 
216 	for (i = 0; attr_names[i] != NULL; i++)
217 	{
218 	    if (strcmp (attr_names[i], "version") == 0)
219 	    {
220 		parser->config_file_version = parse_int (attr_values[i]);
221 		break;
222 	    }
223 	}
224     }
225 
226     g_queue_push_tail (parser->stack, g_strdup (name));
227 }
228 
229 static void
handle_end_element(GMarkupParseContext * context,const gchar * name,gpointer user_data,GError ** err)230 handle_end_element (GMarkupParseContext *context,
231 		    const gchar         *name,
232 		    gpointer             user_data,
233 		    GError             **err)
234 {
235     Parser *parser = user_data;
236 
237     if (strcmp (name, "output") == 0)
238     {
239 	/* If no rotation properties were set, just use MATE_RR_ROTATION_0 */
240 	if (parser->output->priv->rotation == 0)
241 	    parser->output->priv->rotation = MATE_RR_ROTATION_0;
242 
243 	g_ptr_array_add (parser->outputs, parser->output);
244 
245 	parser->output = NULL;
246     }
247     else if (strcmp (name, "configuration") == 0)
248     {
249 	g_ptr_array_add (parser->outputs, NULL);
250 	parser->configuration->priv->outputs =
251 	    (MateRROutputInfo **)g_ptr_array_free (parser->outputs, FALSE);
252 	parser->outputs = g_ptr_array_new ();
253 	g_ptr_array_add (parser->configurations, parser->configuration);
254 	parser->configuration = NULL;
255     }
256 
257     g_free (g_queue_pop_tail (parser->stack));
258 }
259 
260 #define TOPLEVEL_ELEMENT (parser->config_file_version > 0 ? "monitors" : NULL)
261 
262 static void
handle_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** err)263 handle_text (GMarkupParseContext *context,
264 	     const gchar         *text,
265 	     gsize                text_len,
266 	     gpointer             user_data,
267 	     GError             **err)
268 {
269     Parser *parser = user_data;
270 
271     if (stack_is (parser, "vendor", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
272     {
273 	parser->output->priv->connected = TRUE;
274 
275 	strncpy ((gchar*) parser->output->priv->vendor, text, 3);
276 	parser->output->priv->vendor[3] = 0;
277     }
278     else if (stack_is (parser, "clone", "configuration", TOPLEVEL_ELEMENT, NULL))
279     {
280 	if (strcmp (text, "yes") == 0)
281 	    parser->configuration->priv->clone = TRUE;
282     }
283     else if (stack_is (parser, "product", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
284     {
285 	parser->output->priv->connected = TRUE;
286 
287 	parser->output->priv->product = parse_int (text);
288     }
289     else if (stack_is (parser, "serial", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
290     {
291 	parser->output->priv->connected = TRUE;
292 
293 	parser->output->priv->serial = parse_uint (text);
294     }
295     else if (stack_is (parser, "width", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
296     {
297 	parser->output->priv->on = TRUE;
298 
299 	parser->output->priv->width = parse_int (text);
300     }
301     else if (stack_is (parser, "x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
302     {
303 	parser->output->priv->on = TRUE;
304 
305 	parser->output->priv->x = parse_int (text);
306     }
307     else if (stack_is (parser, "y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
308     {
309 	parser->output->priv->on = TRUE;
310 
311 	parser->output->priv->y = parse_int (text);
312     }
313     else if (stack_is (parser, "height", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
314     {
315 	parser->output->priv->on = TRUE;
316 
317 	parser->output->priv->height = parse_int (text);
318     }
319     else if (stack_is (parser, "rate", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
320     {
321 	parser->output->priv->on = TRUE;
322 
323 	parser->output->priv->rate = parse_int (text);
324     }
325     else if (stack_is (parser, "rotation", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
326     {
327 	if (strcmp (text, "normal") == 0)
328 	{
329 	    parser->output->priv->rotation |= MATE_RR_ROTATION_0;
330 	}
331 	else if (strcmp (text, "left") == 0)
332 	{
333 	    parser->output->priv->rotation |= MATE_RR_ROTATION_90;
334 	}
335 	else if (strcmp (text, "upside_down") == 0)
336 	{
337 	    parser->output->priv->rotation |= MATE_RR_ROTATION_180;
338 	}
339 	else if (strcmp (text, "right") == 0)
340 	{
341 	    parser->output->priv->rotation |= MATE_RR_ROTATION_270;
342 	}
343     }
344     else if (stack_is (parser, "reflect_x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
345     {
346 	if (strcmp (text, "yes") == 0)
347 	{
348 	    parser->output->priv->rotation |= MATE_RR_REFLECT_X;
349 	}
350     }
351     else if (stack_is (parser, "reflect_y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
352     {
353 	if (strcmp (text, "yes") == 0)
354 	{
355 	    parser->output->priv->rotation |= MATE_RR_REFLECT_Y;
356 	}
357     }
358     else if (stack_is (parser, "primary", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
359     {
360 	if (strcmp (text, "yes") == 0)
361 	{
362 	    parser->output->priv->primary = TRUE;
363 	}
364     }
365     else
366     {
367 	/* Ignore other properties so we can expand the format in the future */
368     }
369 }
370 
371 static void
parser_free(Parser * parser)372 parser_free (Parser *parser)
373 {
374     int i;
375     GList *list;
376 
377     g_assert (parser != NULL);
378 
379     if (parser->output)
380 	g_object_unref (parser->output);
381 
382     if (parser->configuration)
383 	g_object_unref (parser->configuration);
384 
385     for (i = 0; i < parser->outputs->len; ++i)
386     {
387 	MateRROutputInfo *output = parser->outputs->pdata[i];
388 
389 	g_object_unref (output);
390     }
391 
392     g_ptr_array_free (parser->outputs, TRUE);
393 
394     for (i = 0; i < parser->configurations->len; ++i)
395     {
396 	MateRRConfig *config = parser->configurations->pdata[i];
397 
398 	g_object_unref (config);
399     }
400 
401     g_ptr_array_free (parser->configurations, TRUE);
402 
403     for (list = parser->stack->head; list; list = list->next)
404 	g_free (list->data);
405     g_queue_free (parser->stack);
406 
407     g_free (parser);
408 }
409 
410 static MateRRConfig **
configurations_read_from_file(const gchar * filename,GError ** error)411 configurations_read_from_file (const gchar *filename, GError **error)
412 {
413     Parser *parser = g_new0 (Parser, 1);
414     MateRRConfig **result;
415     GMarkupParser callbacks = {
416 	handle_start_element,
417 	handle_end_element,
418 	handle_text,
419 	NULL, /* passthrough */
420 	NULL, /* error */
421     };
422 
423     parser->config_file_version = 0;
424     parser->configurations = g_ptr_array_new ();
425     parser->outputs = g_ptr_array_new ();
426     parser->stack = g_queue_new ();
427 
428     if (!parse_file_gmarkup (filename, &callbacks, parser, error))
429     {
430 	result = NULL;
431 
432 	g_assert (parser->outputs);
433 	goto out;
434     }
435 
436     g_assert (parser->outputs);
437 
438     g_ptr_array_add (parser->configurations, NULL);
439     result = (MateRRConfig **)g_ptr_array_free (parser->configurations, FALSE);
440     parser->configurations = g_ptr_array_new ();
441 
442     g_assert (parser->outputs);
443 out:
444     parser_free (parser);
445 
446     return result;
447 }
448 
449 static void
mate_rr_config_init(MateRRConfig * self)450 mate_rr_config_init (MateRRConfig *self)
451 {
452     self->priv = mate_rr_config_get_instance_private (self);
453 
454     self->priv->clone = FALSE;
455     self->priv->screen = NULL;
456     self->priv->outputs = NULL;
457 }
458 
459 static void
mate_rr_config_set_property(GObject * gobject,guint property_id,const GValue * value,GParamSpec * property)460 mate_rr_config_set_property (GObject *gobject, guint property_id, const GValue *value, GParamSpec *property)
461 {
462     MateRRConfig *self = MATE_RR_CONFIG (gobject);
463 
464     switch (property_id) {
465 	case PROP_SCREEN:
466 	    self->priv->screen = g_value_dup_object (value);
467 	    return;
468 	default:
469 	    G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
470     }
471 }
472 
473 static void
mate_rr_config_finalize(GObject * gobject)474 mate_rr_config_finalize (GObject *gobject)
475 {
476     MateRRConfig *self = MATE_RR_CONFIG (gobject);
477 
478     if (self->priv->screen)
479 	g_object_unref (self->priv->screen);
480 
481     if (self->priv->outputs) {
482 	int i;
483 
484         for (i = 0; self->priv->outputs[i] != NULL; i++) {
485 	    MateRROutputInfo *output = self->priv->outputs[i];
486 	    g_object_unref (output);
487 	}
488 	g_free (self->priv->outputs);
489     }
490 
491     G_OBJECT_CLASS (mate_rr_config_parent_class)->finalize (gobject);
492 }
493 
494 gboolean
mate_rr_config_load_current(MateRRConfig * config,GError ** error)495 mate_rr_config_load_current (MateRRConfig *config, GError **error)
496 {
497     GPtrArray *a;
498     MateRROutput **rr_outputs;
499     int i;
500     int clone_width = -1;
501     int clone_height = -1;
502     int last_x;
503 
504     g_return_val_if_fail (MATE_IS_RR_CONFIG (config), FALSE);
505 
506     a = g_ptr_array_new ();
507     rr_outputs = mate_rr_screen_list_outputs (config->priv->screen);
508 
509     config->priv->clone = FALSE;
510 
511     for (i = 0; rr_outputs[i] != NULL; ++i)
512     {
513 	MateRROutput *rr_output = rr_outputs[i];
514 	MateRROutputInfo *output = g_object_new (MATE_TYPE_RR_OUTPUT_INFO, NULL);
515 	MateRRMode *mode = NULL;
516 	const guint8 *edid_data = mate_rr_output_get_edid_data (rr_output);
517 	MateRRCrtc *crtc;
518 
519 	output->priv->name = g_strdup (mate_rr_output_get_name (rr_output));
520 	output->priv->connected = mate_rr_output_is_connected (rr_output);
521 
522 	if (!output->priv->connected)
523 	{
524 	    output->priv->x = -1;
525 	    output->priv->y = -1;
526 	    output->priv->width = -1;
527 	    output->priv->height = -1;
528 	    output->priv->rate = -1;
529 	    output->priv->rotation = MATE_RR_ROTATION_0;
530 	}
531 	else
532 	{
533 	    MonitorInfo *info = NULL;
534 
535 	    if (edid_data)
536 		info = decode_edid (edid_data);
537 
538 	    if (info)
539 	    {
540 		memcpy (output->priv->vendor, info->manufacturer_code,
541 			sizeof (output->priv->vendor));
542 
543 		output->priv->product = info->product_code;
544 		output->priv->serial = info->serial_number;
545 		output->priv->aspect = info->aspect_ratio;
546 	    }
547 	    else
548 	    {
549 		g_strlcpy (output->priv->vendor, "???", sizeof (output->priv->vendor));
550 		output->priv->product = 0;
551 		output->priv->serial = 0;
552 	    }
553 
554 	    if (mate_rr_output_is_laptop (rr_output))
555 		output->priv->display_name = g_strdup (_("Laptop"));
556 	    else
557 		output->priv->display_name = make_display_name (info);
558 
559 	    g_free (info);
560 
561 	    crtc = mate_rr_output_get_crtc (rr_output);
562 	    mode = crtc? mate_rr_crtc_get_current_mode (crtc) : NULL;
563 
564 	    if (crtc && mode)
565 	    {
566 		output->priv->on = TRUE;
567 
568 		mate_rr_crtc_get_position (crtc, &output->priv->x, &output->priv->y);
569 		output->priv->width = mate_rr_mode_get_width (mode);
570 		output->priv->height = mate_rr_mode_get_height (mode);
571 		output->priv->rate = mate_rr_mode_get_freq (mode);
572 		output->priv->rotation = mate_rr_crtc_get_current_rotation (crtc);
573 
574 		if (output->priv->x == 0 && output->priv->y == 0) {
575 			if (clone_width == -1) {
576 				clone_width = output->priv->width;
577 				clone_height = output->priv->height;
578 			} else if (clone_width == output->priv->width &&
579 				   clone_height == output->priv->height) {
580 				config->priv->clone = TRUE;
581 			}
582 		}
583 	    }
584 	    else
585 	    {
586 		output->priv->on = FALSE;
587 		config->priv->clone = FALSE;
588 	    }
589 
590 	    /* Get preferred size for the monitor */
591 	    mode = mate_rr_output_get_preferred_mode (rr_output);
592 
593 	    if (!mode)
594 	    {
595 		MateRRMode **modes = mate_rr_output_list_modes (rr_output);
596 
597 		/* FIXME: we should pick the "best" mode here, where best is
598 		 * sorted wrt
599 		 *
600 		 * - closest aspect ratio
601 		 * - mode area
602 		 * - refresh rate
603 		 * - We may want to extend randrwrap so that get_preferred
604 		 *   returns that - although that could also depend on
605 		 *   the crtc.
606 		 */
607 		if (modes[0])
608 		    mode = modes[0];
609 	    }
610 
611 	    if (mode)
612 	    {
613 		output->priv->pref_width = mate_rr_mode_get_width (mode);
614 		output->priv->pref_height = mate_rr_mode_get_height (mode);
615 	    }
616 	    else
617 	    {
618 		/* Pick some random numbers. This should basically never happen */
619 		output->priv->pref_width = 1024;
620 		output->priv->pref_height = 768;
621 	    }
622 	}
623 
624         output->priv->primary = mate_rr_output_get_is_primary (rr_output);
625 
626 	g_ptr_array_add (a, output);
627     }
628 
629     g_ptr_array_add (a, NULL);
630 
631     config->priv->outputs = (MateRROutputInfo **)g_ptr_array_free (a, FALSE);
632 
633     /* Walk the outputs computing the right-most edge of all
634      * lit-up displays
635      */
636     last_x = 0;
637     for (i = 0; config->priv->outputs[i] != NULL; ++i)
638     {
639 	MateRROutputInfo *output = config->priv->outputs[i];
640 
641 	if (output->priv->on)
642 	{
643 	    last_x = MAX (last_x, output->priv->x + output->priv->width);
644 	}
645     }
646 
647     /* Now position all off displays to the right of the
648      * on displays
649      */
650     for (i = 0; config->priv->outputs[i] != NULL; ++i)
651     {
652 	MateRROutputInfo *output = config->priv->outputs[i];
653 
654 	if (output->priv->connected && !output->priv->on)
655 	{
656 	    output->priv->x = last_x;
657 	    last_x = output->priv->x + output->priv->width;
658 	}
659     }
660 
661     g_assert (mate_rr_config_match (config, config));
662 
663     return TRUE;
664 }
665 
666 gboolean
mate_rr_config_load_filename(MateRRConfig * result,const char * filename,GError ** error)667 mate_rr_config_load_filename (MateRRConfig *result, const char *filename, GError **error)
668 {
669     MateRRConfig *current;
670     MateRRConfig **configs;
671     gboolean found = FALSE;
672 
673     g_return_val_if_fail (MATE_IS_RR_CONFIG (result), FALSE);
674     g_return_val_if_fail (filename != NULL, FALSE);
675     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
676 
677     if (filename == NULL)
678       filename = mate_rr_config_get_intended_filename ();
679 
680     current = mate_rr_config_new_current (result->priv->screen, error);
681 
682     configs = configurations_read_from_file (filename, error);
683 
684     if (configs)
685     {
686 	int i;
687 
688 	for (i = 0; configs[i] != NULL; ++i)
689 	{
690 	    if (mate_rr_config_match (configs[i], current))
691 	    {
692 		int j;
693 		GPtrArray *array;
694 		result->priv->clone = configs[i]->priv->clone;
695 
696 		array = g_ptr_array_new ();
697 		for (j = 0; configs[i]->priv->outputs[j] != NULL; j++) {
698 		    g_object_ref (configs[i]->priv->outputs[j]);
699 		    g_ptr_array_add (array, configs[i]->priv->outputs[j]);
700 		}
701 		g_ptr_array_add (array, NULL);
702 		result->priv->outputs = (MateRROutputInfo **) g_ptr_array_free (array, FALSE);
703 
704 		found = TRUE;
705 		break;
706 	    }
707 	    g_object_unref (configs[i]);
708 	}
709 	g_free (configs);
710 
711 	if (!found)
712 	    g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_NO_MATCHING_CONFIG,
713 			 _("none of the saved display configurations matched the active configuration"));
714     }
715 
716     g_object_unref (current);
717     return found;
718 }
719 
720 static void
mate_rr_config_class_init(MateRRConfigClass * klass)721 mate_rr_config_class_init (MateRRConfigClass *klass)
722 {
723     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
724 
725     gobject_class->set_property = mate_rr_config_set_property;
726     gobject_class->finalize = mate_rr_config_finalize;
727 
728     g_object_class_install_property (gobject_class, PROP_SCREEN,
729 				     g_param_spec_object ("screen", "Screen", "The MateRRScreen this config applies to", MATE_TYPE_RR_SCREEN,
730 							  G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
731 }
732 
733 MateRRConfig *
mate_rr_config_new_current(MateRRScreen * screen,GError ** error)734 mate_rr_config_new_current (MateRRScreen *screen, GError **error)
735 {
736     MateRRConfig *self = g_object_new (MATE_TYPE_RR_CONFIG, "screen", screen, NULL);
737 
738     if (mate_rr_config_load_current (self, error))
739       return self;
740     else
741       {
742 	g_object_unref (self);
743 	return NULL;
744       }
745 }
746 
747 MateRRConfig *
mate_rr_config_new_stored(MateRRScreen * screen,GError ** error)748 mate_rr_config_new_stored (MateRRScreen *screen, GError **error)
749 {
750     MateRRConfig *self = g_object_new (MATE_TYPE_RR_CONFIG, "screen", screen, NULL);
751     char *filename;
752     gboolean success;
753 
754     filename = mate_rr_config_get_intended_filename ();
755 
756     success = mate_rr_config_load_filename (self, filename, error);
757 
758     g_free (filename);
759 
760     if (success)
761       return self;
762     else
763       {
764 	g_object_unref (self);
765 	return NULL;
766       }
767 }
768 
769 static gboolean
parse_file_gmarkup(const gchar * filename,const GMarkupParser * parser,gpointer data,GError ** err)770 parse_file_gmarkup (const gchar          *filename,
771 		    const GMarkupParser  *parser,
772 		    gpointer             data,
773 		    GError              **err)
774 {
775     GMarkupParseContext *context = NULL;
776     gchar *contents = NULL;
777     gboolean result = TRUE;
778     gsize len;
779 
780     if (!g_file_get_contents (filename, &contents, &len, err))
781     {
782 	result = FALSE;
783 	goto out;
784     }
785 
786     context = g_markup_parse_context_new (parser, 0, data, NULL);
787 
788     if (!g_markup_parse_context_parse (context, contents, len, err))
789     {
790 	result = FALSE;
791 	goto out;
792     }
793 
794     if (!g_markup_parse_context_end_parse (context, err))
795     {
796 	result = FALSE;
797 	goto out;
798     }
799 
800 out:
801     if (contents)
802 	g_free (contents);
803 
804     if (context)
805 	g_markup_parse_context_free (context);
806 
807     return result;
808 }
809 
810 static gboolean
output_match(MateRROutputInfo * output1,MateRROutputInfo * output2)811 output_match (MateRROutputInfo *output1, MateRROutputInfo *output2)
812 {
813     g_assert (MATE_IS_RR_OUTPUT_INFO (output1));
814     g_assert (MATE_IS_RR_OUTPUT_INFO (output2));
815 
816     if (strcmp (output1->priv->name, output2->priv->name) != 0)
817 	return FALSE;
818 
819     if (strcmp (output1->priv->vendor, output2->priv->vendor) != 0)
820 	return FALSE;
821 
822     if (output1->priv->product != output2->priv->product)
823 	return FALSE;
824 
825     if (output1->priv->serial != output2->priv->serial)
826 	return FALSE;
827 
828     if (output1->priv->connected != output2->priv->connected)
829 	return FALSE;
830 
831     return TRUE;
832 }
833 
834 static gboolean
output_equal(MateRROutputInfo * output1,MateRROutputInfo * output2)835 output_equal (MateRROutputInfo *output1, MateRROutputInfo *output2)
836 {
837     g_assert (MATE_IS_RR_OUTPUT_INFO (output1));
838     g_assert (MATE_IS_RR_OUTPUT_INFO (output2));
839 
840     if (!output_match (output1, output2))
841 	return FALSE;
842 
843     if (output1->priv->on != output2->priv->on)
844 	return FALSE;
845 
846     if (output1->priv->on)
847     {
848 	if (output1->priv->width != output2->priv->width)
849 	    return FALSE;
850 
851 	if (output1->priv->height != output2->priv->height)
852 	    return FALSE;
853 
854 	if (output1->priv->rate != output2->priv->rate)
855 	    return FALSE;
856 
857 	if (output1->priv->x != output2->priv->x)
858 	    return FALSE;
859 
860 	if (output1->priv->y != output2->priv->y)
861 	    return FALSE;
862 
863 	if (output1->priv->rotation != output2->priv->rotation)
864 	    return FALSE;
865     }
866 
867     return TRUE;
868 }
869 
870 static MateRROutputInfo *
find_output(MateRRConfig * config,const char * name)871 find_output (MateRRConfig *config, const char *name)
872 {
873     int i;
874 
875     for (i = 0; config->priv->outputs[i] != NULL; ++i)
876     {
877 	MateRROutputInfo *output = config->priv->outputs[i];
878 
879 	if (strcmp (name, output->priv->name) == 0)
880 	    return output;
881     }
882 
883     return NULL;
884 }
885 
886 /* Match means "these configurations apply to the same hardware
887  * setups"
888  */
889 gboolean
mate_rr_config_match(MateRRConfig * c1,MateRRConfig * c2)890 mate_rr_config_match (MateRRConfig *c1, MateRRConfig *c2)
891 {
892     int i;
893     g_return_val_if_fail (MATE_IS_RR_CONFIG (c1), FALSE);
894     g_return_val_if_fail (MATE_IS_RR_CONFIG (c2), FALSE);
895 
896     for (i = 0; c1->priv->outputs[i] != NULL; ++i)
897     {
898 	MateRROutputInfo *output1 = c1->priv->outputs[i];
899 	MateRROutputInfo *output2;
900 
901 	output2 = find_output (c2, output1->priv->name);
902 	if (!output2 || !output_match (output1, output2))
903 	    return FALSE;
904     }
905 
906     return TRUE;
907 }
908 
909 /* Equal means "the configurations will result in the same
910  * modes being set on the outputs"
911  */
912 gboolean
mate_rr_config_equal(MateRRConfig * c1,MateRRConfig * c2)913 mate_rr_config_equal (MateRRConfig  *c1,
914 		       MateRRConfig  *c2)
915 {
916     int i;
917     g_return_val_if_fail (MATE_IS_RR_CONFIG (c1), FALSE);
918     g_return_val_if_fail (MATE_IS_RR_CONFIG (c2), FALSE);
919 
920     for (i = 0; c1->priv->outputs[i] != NULL; ++i)
921     {
922 	MateRROutputInfo *output1 = c1->priv->outputs[i];
923 	MateRROutputInfo *output2;
924 
925 	output2 = find_output (c2, output1->priv->name);
926 	if (!output2 || !output_equal (output1, output2))
927 	    return FALSE;
928     }
929 
930     return TRUE;
931 }
932 
933 static MateRROutputInfo **
make_outputs(MateRRConfig * config)934 make_outputs (MateRRConfig *config)
935 {
936     GPtrArray *outputs;
937     MateRROutputInfo *first_on;
938     int i;
939 
940     outputs = g_ptr_array_new ();
941 
942     first_on = NULL;
943 
944     for (i = 0; config->priv->outputs[i] != NULL; ++i)
945     {
946 	MateRROutputInfo *old = config->priv->outputs[i];
947 	MateRROutputInfo *new = g_object_new (MATE_TYPE_RR_OUTPUT_INFO, NULL);
948 	*(new->priv) = *(old->priv);
949 	if (old->priv->name)
950 	    new->priv->name = g_strdup (old->priv->name);
951 	if (old->priv->display_name)
952 	    new->priv->display_name = g_strdup (old->priv->display_name);
953 
954 	if (old->priv->on && !first_on)
955 	    first_on = old;
956 
957 	if (config->priv->clone && new->priv->on)
958 	{
959 	    g_assert (first_on);
960 
961 	    new->priv->width = first_on->priv->width;
962 	    new->priv->height = first_on->priv->height;
963 	    new->priv->rotation = first_on->priv->rotation;
964 	    new->priv->x = 0;
965 	    new->priv->y = 0;
966 	}
967 
968 	g_ptr_array_add (outputs, new);
969     }
970 
971     g_ptr_array_add (outputs, NULL);
972 
973     return (MateRROutputInfo **)g_ptr_array_free (outputs, FALSE);
974 }
975 
976 gboolean
mate_rr_config_applicable(MateRRConfig * configuration,MateRRScreen * screen,GError ** error)977 mate_rr_config_applicable (MateRRConfig  *configuration,
978 			    MateRRScreen  *screen,
979 			    GError        **error)
980 {
981     MateRROutputInfo **outputs;
982     CrtcAssignment *assign;
983     gboolean result;
984     int i;
985 
986     g_return_val_if_fail (MATE_IS_RR_CONFIG (configuration), FALSE);
987     g_return_val_if_fail (MATE_IS_RR_SCREEN (screen), FALSE);
988     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
989 
990     outputs = make_outputs (configuration);
991     assign = crtc_assignment_new (screen, outputs, error);
992 
993     if (assign)
994     {
995 	result = TRUE;
996 	crtc_assignment_free (assign);
997     }
998     else
999     {
1000 	result = FALSE;
1001     }
1002 
1003     for (i = 0; outputs[i] != NULL; i++) {
1004 	 g_object_unref (outputs[i]);
1005     }
1006 
1007     return result;
1008 }
1009 
1010 /* Database management */
1011 
1012 static void
ensure_config_directory(void)1013 ensure_config_directory (void)
1014 {
1015     g_mkdir_with_parents (g_get_user_config_dir (), 0700);
1016 }
1017 
1018 char *
mate_rr_config_get_backup_filename(void)1019 mate_rr_config_get_backup_filename (void)
1020 {
1021     ensure_config_directory ();
1022     return g_build_filename (g_get_user_config_dir (), CONFIG_BACKUP_BASENAME, NULL);
1023 }
1024 
1025 char *
mate_rr_config_get_intended_filename(void)1026 mate_rr_config_get_intended_filename (void)
1027 {
1028     ensure_config_directory ();
1029     return g_build_filename (g_get_user_config_dir (), CONFIG_INTENDED_BASENAME, NULL);
1030 }
1031 
1032 static const char *
get_rotation_name(MateRRRotation r)1033 get_rotation_name (MateRRRotation r)
1034 {
1035     if (r & MATE_RR_ROTATION_0)
1036 	return "normal";
1037     if (r & MATE_RR_ROTATION_90)
1038 	return "left";
1039     if (r & MATE_RR_ROTATION_180)
1040 	return "upside_down";
1041     if (r & MATE_RR_ROTATION_270)
1042 	return "right";
1043 
1044     return "normal";
1045 }
1046 
1047 static const char *
yes_no(int x)1048 yes_no (int x)
1049 {
1050     return x? "yes" : "no";
1051 }
1052 
1053 static const char *
get_reflect_x(MateRRRotation r)1054 get_reflect_x (MateRRRotation r)
1055 {
1056     return yes_no (r & MATE_RR_REFLECT_X);
1057 }
1058 
1059 static const char *
get_reflect_y(MateRRRotation r)1060 get_reflect_y (MateRRRotation r)
1061 {
1062     return yes_no (r & MATE_RR_REFLECT_Y);
1063 }
1064 
1065 static void
emit_configuration(MateRRConfig * config,GString * string)1066 emit_configuration (MateRRConfig *config,
1067 		    GString *string)
1068 {
1069     int j;
1070 
1071     g_string_append_printf (string, "  <configuration>\n");
1072 
1073     g_string_append_printf (string, "      <clone>%s</clone>\n", yes_no (config->priv->clone));
1074 
1075     for (j = 0; config->priv->outputs[j] != NULL; ++j)
1076     {
1077 	MateRROutputInfo *output = config->priv->outputs[j];
1078 
1079 	g_string_append_printf (
1080 	    string, "      <output name=\"%s\">\n", output->priv->name);
1081 
1082 	if (output->priv->connected && *output->priv->vendor != '\0')
1083 	{
1084 	    g_string_append_printf (
1085 		string, "          <vendor>%s</vendor>\n", output->priv->vendor);
1086 	    g_string_append_printf (
1087 		string, "          <product>0x%04x</product>\n", output->priv->product);
1088 	    g_string_append_printf (
1089 		string, "          <serial>0x%08x</serial>\n", output->priv->serial);
1090 	}
1091 
1092 	/* An unconnected output which is on does not make sense */
1093 	if (output->priv->connected && output->priv->on)
1094 	{
1095 	    g_string_append_printf (
1096 		string, "          <width>%d</width>\n", output->priv->width);
1097 	    g_string_append_printf (
1098 		string, "          <height>%d</height>\n", output->priv->height);
1099 	    g_string_append_printf (
1100 		string, "          <rate>%d</rate>\n", output->priv->rate);
1101 	    g_string_append_printf (
1102 		string, "          <x>%d</x>\n", output->priv->x);
1103 	    g_string_append_printf (
1104 		string, "          <y>%d</y>\n", output->priv->y);
1105 	    g_string_append_printf (
1106 		string, "          <rotation>%s</rotation>\n", get_rotation_name (output->priv->rotation));
1107 	    g_string_append_printf (
1108 		string, "          <reflect_x>%s</reflect_x>\n", get_reflect_x (output->priv->rotation));
1109 	    g_string_append_printf (
1110 		string, "          <reflect_y>%s</reflect_y>\n", get_reflect_y (output->priv->rotation));
1111             g_string_append_printf (
1112                 string, "          <primary>%s</primary>\n", yes_no (output->priv->primary));
1113 	}
1114 
1115 	g_string_append_printf (string, "      </output>\n");
1116     }
1117 
1118     g_string_append_printf (string, "  </configuration>\n");
1119 }
1120 
1121 void
mate_rr_config_sanitize(MateRRConfig * config)1122 mate_rr_config_sanitize (MateRRConfig *config)
1123 {
1124     int i;
1125     int x_offset, y_offset;
1126     gboolean found;
1127 
1128     /* Offset everything by the top/left-most coordinate to
1129      * make sure the configuration starts at (0, 0)
1130      */
1131     x_offset = y_offset = G_MAXINT;
1132     for (i = 0; config->priv->outputs[i]; ++i)
1133     {
1134 	MateRROutputInfo *output = config->priv->outputs[i];
1135 
1136 	if (output->priv->on)
1137 	{
1138 	    x_offset = MIN (x_offset, output->priv->x);
1139 	    y_offset = MIN (y_offset, output->priv->y);
1140 	}
1141     }
1142 
1143     for (i = 0; config->priv->outputs[i]; ++i)
1144     {
1145 	MateRROutputInfo *output = config->priv->outputs[i];
1146 
1147 	if (output->priv->on)
1148 	{
1149 	    output->priv->x -= x_offset;
1150 	    output->priv->y -= y_offset;
1151 	}
1152     }
1153 
1154     /* Only one primary, please */
1155     found = FALSE;
1156     for (i = 0; config->priv->outputs[i]; ++i)
1157     {
1158         if (config->priv->outputs[i]->priv->primary)
1159         {
1160             if (found)
1161             {
1162                 config->priv->outputs[i]->priv->primary = FALSE;
1163             }
1164             else
1165             {
1166                 found = TRUE;
1167             }
1168         }
1169     }
1170 }
1171 
1172 gboolean
mate_rr_config_ensure_primary(MateRRConfig * configuration)1173 mate_rr_config_ensure_primary (MateRRConfig *configuration)
1174 {
1175         int              i;
1176         MateRROutputInfo  *laptop;
1177         MateRROutputInfo  *top_left;
1178         gboolean        found;
1179         MateRRConfigPrivate *priv;
1180 
1181         g_return_val_if_fail (MATE_IS_RR_CONFIG (configuration), FALSE);
1182 
1183         laptop = NULL;
1184         top_left = NULL;
1185         found = FALSE;
1186         priv = configuration->priv;
1187 
1188         for (i = 0; priv->outputs[i] != NULL; ++i) {
1189                 MateRROutputInfo *info = priv->outputs[i];
1190 
1191                 if (! info->priv->on) {
1192                         info->priv->primary = FALSE;
1193                         continue;
1194 		}
1195 
1196                 /* ensure only one */
1197                 if (info->priv->primary) {
1198                         if (found) {
1199                                 info->priv->primary = FALSE;
1200                         } else {
1201                                 found = TRUE;
1202                         }
1203                 }
1204 
1205                 if (top_left == NULL
1206                     || (info->priv->x < top_left->priv->x
1207                         && info->priv->y < top_left->priv->y)) {
1208                         top_left = info;
1209                 }
1210                 if (laptop == NULL
1211                     && _mate_rr_output_name_is_laptop (info->priv->name)) {
1212                         laptop = info;
1213                 }
1214         }
1215 
1216         if (!found) {
1217                 if (laptop != NULL) {
1218                         laptop->priv->primary = TRUE;
1219                 } else if (top_left != NULL) {
1220 		        /* Note: top_left can be NULL if all outputs are off */
1221                         top_left->priv->primary = TRUE;
1222                 }
1223         }
1224 
1225         return !found;
1226 }
1227 
1228 gboolean
mate_rr_config_save(MateRRConfig * configuration,GError ** error)1229 mate_rr_config_save (MateRRConfig *configuration, GError **error)
1230 {
1231     MateRRConfig **configurations;
1232     GString *output;
1233     int i;
1234     gchar *intended_filename;
1235     gchar *backup_filename;
1236     gboolean result;
1237 
1238     g_return_val_if_fail (MATE_IS_RR_CONFIG (configuration), FALSE);
1239     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1240 
1241     output = g_string_new ("");
1242 
1243     backup_filename = mate_rr_config_get_backup_filename ();
1244     intended_filename = mate_rr_config_get_intended_filename ();
1245 
1246     configurations = configurations_read_from_file (intended_filename, NULL); /* NULL-GError */
1247 
1248     g_string_append_printf (output, "<monitors version=\"1\">\n");
1249 
1250     if (configurations)
1251     {
1252 	for (i = 0; configurations[i] != NULL; ++i)
1253 	{
1254 	    if (!mate_rr_config_match (configurations[i], configuration))
1255 		emit_configuration (configurations[i], output);
1256 	    g_object_unref (configurations[i]);
1257 	}
1258 
1259 	g_free (configurations);
1260     }
1261 
1262     emit_configuration (configuration, output);
1263 
1264     g_string_append_printf (output, "</monitors>\n");
1265 
1266     /* backup the file first */
1267     rename (intended_filename, backup_filename); /* no error checking because the intended file may not even exist */
1268 
1269     result = g_file_set_contents (intended_filename, output->str, -1, error);
1270 
1271     if (!result)
1272 	rename (backup_filename, intended_filename); /* no error checking because the backup may not even exist */
1273 
1274     g_free (backup_filename);
1275     g_free (intended_filename);
1276     g_string_free (output, TRUE);
1277 
1278     return result;
1279 }
1280 
1281 gboolean
mate_rr_config_apply_with_time(MateRRConfig * config,MateRRScreen * screen,guint32 timestamp,GError ** error)1282 mate_rr_config_apply_with_time (MateRRConfig *config,
1283 				 MateRRScreen *screen,
1284 				 guint32        timestamp,
1285 				 GError       **error)
1286 {
1287     CrtcAssignment *assignment;
1288     MateRROutputInfo **outputs;
1289     gboolean result = FALSE;
1290     int i;
1291     GdkDisplay *display;
1292 
1293     g_return_val_if_fail (MATE_IS_RR_CONFIG (config), FALSE);
1294     g_return_val_if_fail (MATE_IS_RR_SCREEN (screen), FALSE);
1295 
1296     outputs = make_outputs (config);
1297 
1298     assignment = crtc_assignment_new (screen, outputs, error);
1299 
1300     for (i = 0; outputs[i] != NULL; i++)
1301 	g_object_unref (outputs[i]);
1302     g_free (outputs);
1303 
1304     if (assignment)
1305     {
1306 	if (crtc_assignment_apply (assignment, timestamp, error))
1307 	    result = TRUE;
1308 
1309 	crtc_assignment_free (assignment);
1310 
1311 	display = gdk_display_get_default ();
1312 	gdk_display_flush (display);
1313     }
1314 
1315     return result;
1316 }
1317 
1318 /* mate_rr_config_apply_from_filename_with_time:
1319  * @screen: A #MateRRScreen
1320  * @filename: Path of the file to look in for stored RANDR configurations.
1321  * @timestamp: X server timestamp from the event that causes the screen configuration to change (a user's button press, for example)
1322  * @error: Location to store error, or %NULL
1323  *
1324  * First, this function refreshes the @screen to match the current RANDR
1325  * configuration from the X server.  Then, it tries to load the file in
1326  * @filename and looks for suitable matching RANDR configurations in the file;
1327  * if one is found, that configuration will be applied to the current set of
1328  * RANDR outputs.
1329  *
1330  * Typically, @filename is the result of mate_rr_config_get_intended_filename() or
1331  * mate_rr_config_get_backup_filename().
1332  *
1333  * Returns: TRUE if the RANDR configuration was loaded and applied from
1334  * $(XDG_CONFIG_HOME)/monitors.xml, or FALSE otherwise:
1335  *
1336  * If the current RANDR configuration could not be refreshed, the @error will
1337  * have a domain of #MATE_RR_ERROR and a corresponding error code.
1338  *
1339  * If the file in question is loaded successfully but the configuration cannot
1340  * be applied, the @error will have a domain of #MATE_RR_ERROR.  Note that an
1341  * error code of #MATE_RR_ERROR_NO_MATCHING_CONFIG is not a real error; it
1342  * simply means that there were no stored configurations that match the current
1343  * set of RANDR outputs.
1344  *
1345  * If the file in question cannot be loaded, the @error will have a domain of
1346  * #G_FILE_ERROR.  Note that an error code of G_FILE_ERROR_NOENT is not really
1347  * an error, either; it means that there was no stored configuration file and so
1348  * nothing is changed.
1349  */
1350 gboolean
mate_rr_config_apply_from_filename_with_time(MateRRScreen * screen,const char * filename,guint32 timestamp,GError ** error)1351 mate_rr_config_apply_from_filename_with_time (MateRRScreen *screen, const char *filename, guint32 timestamp, GError **error)
1352 {
1353     MateRRConfig *stored;
1354     GError *my_error;
1355 
1356     g_return_val_if_fail (MATE_IS_RR_SCREEN (screen), FALSE);
1357     g_return_val_if_fail (filename != NULL, FALSE);
1358     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1359 
1360     my_error = NULL;
1361     if (!mate_rr_screen_refresh (screen, &my_error)) {
1362 	    if (my_error) {
1363 		    g_propagate_error (error, my_error);
1364 		    return FALSE; /* This is a genuine error */
1365 	    }
1366 
1367 	    /* This means the screen didn't change, so just proceed */
1368     }
1369 
1370     stored = g_object_new (MATE_TYPE_RR_CONFIG, "screen", screen, NULL);
1371 
1372     if (mate_rr_config_load_filename (stored, filename, error))
1373     {
1374 	gboolean result;
1375 
1376 	mate_rr_config_ensure_primary (stored);
1377 	result = mate_rr_config_apply_with_time (stored, screen, timestamp, error);
1378 
1379 	g_object_unref (stored);
1380 
1381 	return result;
1382     }
1383     else
1384     {
1385         g_object_unref (stored);
1386 	return FALSE;
1387     }
1388 }
1389 
1390 /**
1391  * mate_rr_config_get_outputs:
1392  *
1393  * Returns: (array zero-terminated=1) (element-type MateDesktop.RROutputInfo) (transfer none): the output configuration for this #MateRRConfig
1394  */
1395 MateRROutputInfo **
mate_rr_config_get_outputs(MateRRConfig * self)1396 mate_rr_config_get_outputs (MateRRConfig *self)
1397 {
1398     g_return_val_if_fail (MATE_IS_RR_CONFIG (self), NULL);
1399 
1400     return self->priv->outputs;
1401 }
1402 
1403 /**
1404  * mate_rr_config_get_clone:
1405  *
1406  * Returns: whether at least two outputs are at (0, 0) offset and they
1407  * have the same width/height.  Those outputs are of course connected and on
1408  * (i.e. they have a CRTC assigned).
1409  */
1410 gboolean
mate_rr_config_get_clone(MateRRConfig * self)1411 mate_rr_config_get_clone (MateRRConfig *self)
1412 {
1413     g_return_val_if_fail (MATE_IS_RR_CONFIG (self), FALSE);
1414 
1415     return self->priv->clone;
1416 }
1417 
1418 void
mate_rr_config_set_clone(MateRRConfig * self,gboolean clone)1419 mate_rr_config_set_clone (MateRRConfig *self, gboolean clone)
1420 {
1421     g_return_if_fail (MATE_IS_RR_CONFIG (self));
1422 
1423     self->priv->clone = clone;
1424 }
1425 
1426 
1427 /*
1428  * CRTC assignment
1429  */
1430 typedef struct CrtcInfo CrtcInfo;
1431 
1432 struct CrtcInfo
1433 {
1434     MateRRMode    *mode;
1435     int        x;
1436     int        y;
1437     MateRRRotation rotation;
1438     GPtrArray *outputs;
1439 };
1440 
1441 struct CrtcAssignment
1442 {
1443     MateRRScreen *screen;
1444     GHashTable *info;
1445     MateRROutput *primary;
1446 };
1447 
1448 static gboolean
can_clone(CrtcInfo * info,MateRROutput * output)1449 can_clone (CrtcInfo *info,
1450 	   MateRROutput *output)
1451 {
1452     int i;
1453 
1454     for (i = 0; i < info->outputs->len; ++i)
1455     {
1456 	MateRROutput *clone = info->outputs->pdata[i];
1457 
1458 	if (!mate_rr_output_can_clone (clone, output))
1459 	    return FALSE;
1460     }
1461 
1462     return TRUE;
1463 }
1464 
1465 static gboolean
crtc_assignment_assign(CrtcAssignment * assign,MateRRCrtc * crtc,MateRRMode * mode,int x,int y,MateRRRotation rotation,gboolean primary,MateRROutput * output,GError ** error)1466 crtc_assignment_assign (CrtcAssignment   *assign,
1467 			MateRRCrtc      *crtc,
1468 			MateRRMode      *mode,
1469 			int               x,
1470 			int               y,
1471 			MateRRRotation   rotation,
1472                         gboolean          primary,
1473 			MateRROutput    *output,
1474 			GError          **error)
1475 {
1476     CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
1477     guint32 crtc_id;
1478     const char *output_name;
1479 
1480     crtc_id = mate_rr_crtc_get_id (crtc);
1481     output_name = mate_rr_output_get_name (output);
1482 
1483     if (!mate_rr_crtc_can_drive_output (crtc, output))
1484     {
1485 	g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
1486 		     _("CRTC %d cannot drive output %s"), crtc_id, output_name);
1487 	return FALSE;
1488     }
1489 
1490     if (!mate_rr_output_supports_mode (output, mode))
1491     {
1492 	g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
1493 		     _("output %s does not support mode %dx%d@%dHz"),
1494 		     output_name,
1495 		     mate_rr_mode_get_width (mode),
1496 		     mate_rr_mode_get_height (mode),
1497 		     mate_rr_mode_get_freq (mode));
1498 	return FALSE;
1499     }
1500 
1501     if (!mate_rr_crtc_supports_rotation (crtc, rotation))
1502     {
1503 	g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
1504 		     _("CRTC %d does not support rotation=%s"),
1505 		     crtc_id,
1506 		     get_rotation_name (rotation));
1507 	return FALSE;
1508     }
1509 
1510     if (info)
1511     {
1512 	if (!(info->mode == mode	&&
1513 	      info->x == x		&&
1514 	      info->y == y		&&
1515 	      info->rotation == rotation))
1516 	{
1517 	    g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
1518 			 _("output %s does not have the same parameters as another cloned output:\n"
1519 			   "existing mode = %d, new mode = %d\n"
1520 			   "existing coordinates = (%d, %d), new coordinates = (%d, %d)\n"
1521 			   "existing rotation = %s, new rotation = %s"),
1522 			 output_name,
1523 			 mate_rr_mode_get_id (info->mode), mate_rr_mode_get_id (mode),
1524 			 info->x, info->y,
1525 			 x, y,
1526 			 get_rotation_name (info->rotation), get_rotation_name (rotation));
1527 	    return FALSE;
1528 	}
1529 
1530 	if (!can_clone (info, output))
1531 	{
1532 	    g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
1533 			 _("cannot clone to output %s"),
1534 			 output_name);
1535 	    return FALSE;
1536 	}
1537 
1538 	g_ptr_array_add (info->outputs, output);
1539 
1540 	if (primary && !assign->primary)
1541 	{
1542 	    assign->primary = output;
1543 	}
1544 
1545 	return TRUE;
1546     }
1547     else
1548     {
1549 	info = g_new0 (CrtcInfo, 1);
1550 
1551 	info->mode = mode;
1552 	info->x = x;
1553 	info->y = y;
1554 	info->rotation = rotation;
1555 	info->outputs = g_ptr_array_new ();
1556 
1557 	g_ptr_array_add (info->outputs, output);
1558 
1559 	g_hash_table_insert (assign->info, crtc, info);
1560 
1561         if (primary && !assign->primary)
1562         {
1563             assign->primary = output;
1564         }
1565 
1566 	return TRUE;
1567     }
1568 }
1569 
1570 static void
crtc_assignment_unassign(CrtcAssignment * assign,MateRRCrtc * crtc,MateRROutput * output)1571 crtc_assignment_unassign (CrtcAssignment *assign,
1572 			  MateRRCrtc         *crtc,
1573 			  MateRROutput       *output)
1574 {
1575     CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
1576 
1577     if (info)
1578     {
1579 	g_ptr_array_remove (info->outputs, output);
1580 
1581         if (assign->primary == output)
1582         {
1583             assign->primary = NULL;
1584         }
1585 
1586 	if (info->outputs->len == 0)
1587 	    g_hash_table_remove (assign->info, crtc);
1588     }
1589 }
1590 
1591 static void
crtc_assignment_free(CrtcAssignment * assign)1592 crtc_assignment_free (CrtcAssignment *assign)
1593 {
1594     g_hash_table_destroy (assign->info);
1595 
1596     g_free (assign);
1597 }
1598 
1599 typedef struct {
1600     guint32 timestamp;
1601     gboolean has_error;
1602     GError **error;
1603 } ConfigureCrtcState;
1604 
1605 static void
configure_crtc(gpointer key,gpointer value,gpointer data)1606 configure_crtc (gpointer key,
1607 		gpointer value,
1608 		gpointer data)
1609 {
1610     MateRRCrtc *crtc = key;
1611     CrtcInfo *info = value;
1612     ConfigureCrtcState *state = data;
1613 
1614     if (state->has_error)
1615 	return;
1616 
1617     if (!mate_rr_crtc_set_config_with_time (crtc,
1618 					     state->timestamp,
1619 					     info->x, info->y,
1620 					     info->mode,
1621 					     info->rotation,
1622 					     (MateRROutput **)info->outputs->pdata,
1623 					     info->outputs->len,
1624 					     state->error))
1625 	state->has_error = TRUE;
1626 }
1627 
1628 static gboolean
mode_is_rotated(CrtcInfo * info)1629 mode_is_rotated (CrtcInfo *info)
1630 {
1631     if ((info->rotation & MATE_RR_ROTATION_270)		||
1632 	(info->rotation & MATE_RR_ROTATION_90))
1633     {
1634 	return TRUE;
1635     }
1636     return FALSE;
1637 }
1638 
1639 static gboolean
crtc_is_rotated(MateRRCrtc * crtc)1640 crtc_is_rotated (MateRRCrtc *crtc)
1641 {
1642     MateRRRotation r = mate_rr_crtc_get_current_rotation (crtc);
1643 
1644     if ((r & MATE_RR_ROTATION_270)		||
1645 	(r & MATE_RR_ROTATION_90))
1646     {
1647 	return TRUE;
1648     }
1649 
1650     return FALSE;
1651 }
1652 
1653 static void
accumulate_error(GString * accumulated_error,GError * error)1654 accumulate_error (GString *accumulated_error, GError *error)
1655 {
1656     g_string_append_printf (accumulated_error, "    %s\n", error->message);
1657     g_error_free (error);
1658 }
1659 
1660 /* Check whether the given set of settings can be used
1661  * at the same time -- ie. whether there is an assignment
1662  * of CRTC's to outputs.
1663  *
1664  * Brute force - the number of objects involved is small
1665  * enough that it doesn't matter.
1666  */
1667 static gboolean
real_assign_crtcs(MateRRScreen * screen,MateRROutputInfo ** outputs,CrtcAssignment * assignment,GError ** error)1668 real_assign_crtcs (MateRRScreen *screen,
1669 		   MateRROutputInfo **outputs,
1670 		   CrtcAssignment *assignment,
1671 		   GError **error)
1672 {
1673     MateRRCrtc **crtcs = mate_rr_screen_list_crtcs (screen);
1674     MateRROutputInfo *output;
1675     int i;
1676     gboolean tried_mode;
1677     GError *my_error;
1678     GString *accumulated_error;
1679     gboolean success;
1680 
1681     output = *outputs;
1682     if (!output)
1683 	return TRUE;
1684 
1685     /* It is always allowed for an output to be turned off */
1686     if (!output->priv->on)
1687     {
1688 	return real_assign_crtcs (screen, outputs + 1, assignment, error);
1689     }
1690 
1691     success = FALSE;
1692     tried_mode = FALSE;
1693     accumulated_error = g_string_new (NULL);
1694 
1695     for (i = 0; crtcs[i] != NULL; ++i)
1696     {
1697 	MateRRCrtc *crtc = crtcs[i];
1698 	int crtc_id = mate_rr_crtc_get_id (crtc);
1699 	int pass;
1700 
1701 	g_string_append_printf (accumulated_error,
1702 				_("Trying modes for CRTC %d\n"),
1703 				crtc_id);
1704 
1705 	/* Make two passes, one where frequencies must match, then
1706 	 * one where they don't have to
1707 	 */
1708 	for (pass = 0; pass < 2; ++pass)
1709 	{
1710 	    MateRROutput *mate_rr_output = mate_rr_screen_get_output_by_name (screen, output->priv->name);
1711 	    MateRRMode **modes = mate_rr_output_list_modes (mate_rr_output);
1712 	    int j;
1713 
1714 	    for (j = 0; modes[j] != NULL; ++j)
1715 	    {
1716 		MateRRMode *mode = modes[j];
1717 		int mode_width;
1718 		int mode_height;
1719 		int mode_freq;
1720 
1721 		mode_width = mate_rr_mode_get_width (mode);
1722 		mode_height = mate_rr_mode_get_height (mode);
1723 		mode_freq = mate_rr_mode_get_freq (mode);
1724 
1725 		g_string_append_printf (accumulated_error,
1726 					_("CRTC %d: trying mode %dx%d@%dHz with output at %dx%d@%dHz (pass %d)\n"),
1727 					crtc_id,
1728 					mode_width, mode_height, mode_freq,
1729 					output->priv->width, output->priv->height, output->priv->rate,
1730 					pass);
1731 
1732 		if (mode_width == output->priv->width	&&
1733 		    mode_height == output->priv->height &&
1734 		    (pass == 1 || mode_freq == output->priv->rate))
1735 		{
1736 		    tried_mode = TRUE;
1737 
1738 		    my_error = NULL;
1739 		    if (crtc_assignment_assign (
1740 			    assignment, crtc, modes[j],
1741 			    output->priv->x, output->priv->y,
1742 			    output->priv->rotation,
1743                             output->priv->primary,
1744 			    mate_rr_output,
1745 			    &my_error))
1746 		    {
1747 			my_error = NULL;
1748 			if (real_assign_crtcs (screen, outputs + 1, assignment, &my_error)) {
1749 			    success = TRUE;
1750 			    goto out;
1751 			} else
1752 			    accumulate_error (accumulated_error, my_error);
1753 
1754 			crtc_assignment_unassign (assignment, crtc, mate_rr_output);
1755 		    } else
1756 			accumulate_error (accumulated_error, my_error);
1757 		}
1758 	    }
1759 	}
1760     }
1761 
1762 out:
1763 
1764     if (success)
1765 	g_string_free (accumulated_error, TRUE);
1766     else {
1767 	char *str;
1768 
1769 	str = g_string_free (accumulated_error, FALSE);
1770 
1771 	if (tried_mode)
1772 	    g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
1773 			 _("could not assign CRTCs to outputs:\n%s"),
1774 			 str);
1775 	else
1776 	    g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
1777 			 _("none of the selected modes were compatible with the possible modes:\n%s"),
1778 			 str);
1779 
1780 	g_free (str);
1781     }
1782 
1783     return success;
1784 }
1785 
1786 static void
crtc_info_free(CrtcInfo * info)1787 crtc_info_free (CrtcInfo *info)
1788 {
1789     g_ptr_array_free (info->outputs, TRUE);
1790     g_free (info);
1791 }
1792 
1793 static void
get_required_virtual_size(CrtcAssignment * assign,int * width,int * height)1794 get_required_virtual_size (CrtcAssignment *assign, int *width, int *height)
1795 {
1796     GList *active_crtcs = g_hash_table_get_keys (assign->info);
1797     GList *list;
1798     int d;
1799 
1800     if (!width)
1801 	width = &d;
1802     if (!height)
1803 	height = &d;
1804 
1805     /* Compute size of the screen */
1806     *width = *height = 1;
1807     for (list = active_crtcs; list != NULL; list = list->next)
1808     {
1809 	MateRRCrtc *crtc = list->data;
1810 	CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
1811 	int w, h;
1812 
1813 	w = mate_rr_mode_get_width (info->mode);
1814 	h = mate_rr_mode_get_height (info->mode);
1815 
1816 	if (mode_is_rotated (info))
1817 	{
1818 	    int tmp = h;
1819 	    h = w;
1820 	    w = tmp;
1821 	}
1822 
1823 	*width = MAX (*width, info->x + w);
1824 	*height = MAX (*height, info->y + h);
1825     }
1826 
1827     g_list_free (active_crtcs);
1828 }
1829 
1830 static CrtcAssignment *
crtc_assignment_new(MateRRScreen * screen,MateRROutputInfo ** outputs,GError ** error)1831 crtc_assignment_new (MateRRScreen *screen, MateRROutputInfo **outputs, GError **error)
1832 {
1833     CrtcAssignment *assignment = g_new0 (CrtcAssignment, 1);
1834 
1835     assignment->info = g_hash_table_new_full (
1836 	g_direct_hash, g_direct_equal, NULL, (GFreeFunc)crtc_info_free);
1837 
1838     if (real_assign_crtcs (screen, outputs, assignment, error))
1839     {
1840 	int width, height;
1841 	int min_width, max_width, min_height, max_height;
1842 	int required_pixels, min_pixels, max_pixels;
1843 
1844 	get_required_virtual_size (assignment, &width, &height);
1845 
1846 	mate_rr_screen_get_ranges (
1847 	    screen, &min_width, &max_width, &min_height, &max_height);
1848 
1849 	required_pixels = width * height;
1850 	min_pixels = min_width * min_height;
1851 	max_pixels = max_width * max_height;
1852 
1853 	if (required_pixels < min_pixels || required_pixels > max_pixels)
1854 	{
1855 	    g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_BOUNDS_ERROR,
1856 			 /* Translators: the "requested", "minimum", and
1857 			  * "maximum" words here are not keywords; please
1858 			  * translate them as usual. */
1859 			 _("required virtual size does not fit available size: "
1860 			   "requested=(%d, %d), minimum=(%d, %d), maximum=(%d, %d)"),
1861 			 width, height,
1862 			 min_width, min_height,
1863 			 max_width, max_height);
1864 	    goto fail;
1865 	}
1866 
1867 	assignment->screen = screen;
1868 
1869 	return assignment;
1870     }
1871 
1872 fail:
1873     crtc_assignment_free (assignment);
1874 
1875     return NULL;
1876 }
1877 
1878 static gboolean
crtc_assignment_apply(CrtcAssignment * assign,guint32 timestamp,GError ** error)1879 crtc_assignment_apply (CrtcAssignment *assign, guint32 timestamp, GError **error)
1880 {
1881     MateRRCrtc **all_crtcs = mate_rr_screen_list_crtcs (assign->screen);
1882     int width, height;
1883     int i;
1884     int min_width, max_width, min_height, max_height;
1885     int width_mm, height_mm;
1886     gboolean success = TRUE;
1887 
1888     /* Compute size of the screen */
1889     get_required_virtual_size (assign, &width, &height);
1890 
1891     mate_rr_screen_get_ranges (
1892 	assign->screen, &min_width, &max_width, &min_height, &max_height);
1893 
1894     /* We should never get here if the dimensions don't fit in the virtual size,
1895      * but just in case we do, fix it up.
1896      */
1897     width = MAX (min_width, width);
1898     width = MIN (max_width, width);
1899     height = MAX (min_height, height);
1900     height = MIN (max_height, height);
1901 
1902     /* FMQ: do we need to check the sizes instead of clamping them? */
1903 
1904     /* Grab the server while we fiddle with the CRTCs and the screen, so that
1905      * apps that listen for RANDR notifications will only receive the final
1906      * status.
1907      */
1908 
1909     gdk_x11_display_grab (gdk_screen_get_display (assign->screen->priv->gdk_screen));
1910 
1911     /* Turn off all crtcs that are currently displaying outside the new screen,
1912      * or are not used in the new setup
1913      */
1914     for (i = 0; all_crtcs[i] != NULL; ++i)
1915     {
1916 	MateRRCrtc *crtc = all_crtcs[i];
1917 	MateRRMode *mode = mate_rr_crtc_get_current_mode (crtc);
1918 	int x, y;
1919 
1920 	if (mode)
1921 	{
1922 	    int w, h;
1923 	    mate_rr_crtc_get_position (crtc, &x, &y);
1924 
1925 	    w = mate_rr_mode_get_width (mode);
1926 	    h = mate_rr_mode_get_height (mode);
1927 
1928 	    if (crtc_is_rotated (crtc))
1929 	    {
1930 		int tmp = h;
1931 		h = w;
1932 		w = tmp;
1933 	    }
1934 
1935 	    if (x + w > width || y + h > height || !g_hash_table_lookup (assign->info, crtc))
1936 	    {
1937 		if (!mate_rr_crtc_set_config_with_time (crtc, timestamp, 0, 0, NULL, MATE_RR_ROTATION_0, NULL, 0, error))
1938 		{
1939 		    success = FALSE;
1940 		    break;
1941 		}
1942 
1943 	    }
1944 	}
1945     }
1946 
1947     /* The 'physical size' of an X screen is meaningless if that screen
1948      * can consist of many monitors. So just pick a size that make the
1949      * dpi 96.
1950      *
1951      * Firefox and Evince apparently believe what X tells them.
1952      */
1953     width_mm = (width / 96.0) * 25.4 + 0.5;
1954     height_mm = (height / 96.0) * 25.4 + 0.5;
1955 
1956     if (success)
1957     {
1958 	ConfigureCrtcState state;
1959 
1960 	mate_rr_screen_set_size (assign->screen, width, height, width_mm, height_mm);
1961 
1962 	state.timestamp = timestamp;
1963 	state.has_error = FALSE;
1964 	state.error = error;
1965 
1966 	g_hash_table_foreach (assign->info, configure_crtc, &state);
1967 
1968 	success = !state.has_error;
1969     }
1970 
1971     mate_rr_screen_set_primary_output (assign->screen, assign->primary);
1972 
1973     gdk_x11_display_ungrab (gdk_screen_get_display (assign->screen->priv->gdk_screen));
1974 
1975     return success;
1976 }
1977