build-sys: move user/system to respective dir
[vd_agent.git] / src / vdagent / vdagent-x11-randr.c
blob02089b2fbb7aae4e1ac9ae0be1ec4f6a321a065e
1 /* vdagent-x11-randr.c vdagent Xrandr integration code
3 Copyright 2012 Red Hat, Inc.
5 Red Hat Authors:
6 Alon Levy <alevy@redhat.com>
7 Hans de Goede <hdegoede@redhat.com>
8 Marc-André Lureau <mlureau@redhat.com>
10 This program is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 #include <string.h>
27 #include <syslog.h>
28 #include <stdlib.h>
29 #include <limits.h>
31 #include <X11/extensions/Xinerama.h>
33 #include "vdagentd-proto.h"
34 #include "vdagent-x11.h"
35 #include "vdagent-x11-priv.h"
37 #define MM_PER_INCH (25.4)
39 static int error_handler(Display *display, XErrorEvent *error)
41 vdagent_x11_caught_error = 1;
42 return 0;
45 static XRRModeInfo *mode_from_id(struct vdagent_x11 *x11, int id)
47 int i;
49 for (i = 0 ; i < x11->randr.res->nmode ; ++i) {
50 if (id == x11->randr.res->modes[i].id) {
51 return &x11->randr.res->modes[i];
54 return NULL;
57 static XRRCrtcInfo *crtc_from_id(struct vdagent_x11 *x11, int id)
59 int i;
61 for (i = 0 ; i < x11->randr.res->ncrtc ; ++i) {
62 if (id == x11->randr.res->crtcs[i]) {
63 return x11->randr.crtcs[i];
66 return NULL;
69 static void free_randr_resources(struct vdagent_x11 *x11)
71 int i;
73 if (!x11->randr.res) {
74 return;
76 if (x11->randr.outputs != NULL) {
77 for (i = 0 ; i < x11->randr.res->noutput; ++i) {
78 XRRFreeOutputInfo(x11->randr.outputs[i]);
80 free(x11->randr.outputs);
82 if (x11->randr.crtcs != NULL) {
83 for (i = 0 ; i < x11->randr.res->ncrtc; ++i) {
84 XRRFreeCrtcInfo(x11->randr.crtcs[i]);
86 free(x11->randr.crtcs);
88 XRRFreeScreenResources(x11->randr.res);
89 x11->randr.res = NULL;
90 x11->randr.outputs = NULL;
91 x11->randr.crtcs = NULL;
92 x11->randr.num_monitors = 0;
95 static void update_randr_res(struct vdagent_x11 *x11, int poll)
97 int i;
99 free_randr_resources(x11);
100 if (poll)
101 x11->randr.res = XRRGetScreenResources(x11->display, x11->root_window[0]);
102 else
103 x11->randr.res = XRRGetScreenResourcesCurrent(x11->display, x11->root_window[0]);
104 x11->randr.outputs = malloc(x11->randr.res->noutput * sizeof(*x11->randr.outputs));
105 x11->randr.crtcs = malloc(x11->randr.res->ncrtc * sizeof(*x11->randr.crtcs));
106 for (i = 0 ; i < x11->randr.res->noutput; ++i) {
107 x11->randr.outputs[i] = XRRGetOutputInfo(x11->display, x11->randr.res,
108 x11->randr.res->outputs[i]);
109 if (x11->randr.outputs[i]->connection == RR_Connected)
110 x11->randr.num_monitors++;
112 for (i = 0 ; i < x11->randr.res->ncrtc; ++i) {
113 x11->randr.crtcs[i] = XRRGetCrtcInfo(x11->display, x11->randr.res,
114 x11->randr.res->crtcs[i]);
116 /* XXX is this dynamic? should it be cached? */
117 if (XRRGetScreenSizeRange(x11->display, x11->root_window[0],
118 &x11->randr.min_width,
119 &x11->randr.min_height,
120 &x11->randr.max_width,
121 &x11->randr.max_height) != 1) {
122 syslog(LOG_ERR, "update_randr_res: RRGetScreenSizeRange failed");
126 void vdagent_x11_randr_init(struct vdagent_x11 *x11)
128 int i;
130 if (x11->screen_count > 1) {
131 syslog(LOG_WARNING, "X-server has more then 1 screen, "
132 "disabling client -> guest resolution syncing");
133 return;
136 if (XRRQueryExtension(x11->display, &x11->xrandr_event_base, &i)) {
137 XRRQueryVersion(x11->display, &x11->xrandr_major, &x11->xrandr_minor);
138 if (x11->xrandr_major == 1 && x11->xrandr_minor >= 3)
139 x11->has_xrandr = 1;
142 XRRSelectInput(x11->display, x11->root_window[0],
143 RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask);
145 if (x11->has_xrandr) {
146 update_randr_res(x11, 0);
147 } else {
148 x11->randr.res = NULL;
151 if (XineramaQueryExtension(x11->display, &i, &i))
152 x11->has_xinerama = 1;
154 switch (x11->has_xrandr << 4 | x11->has_xinerama) {
155 case 0x00:
156 syslog(LOG_ERR, "Neither Xrandr nor Xinerama found, assuming single monitor setup");
157 break;
158 case 0x01:
159 if (x11->debug)
160 syslog(LOG_DEBUG, "Found Xinerama extension without Xrandr, assuming Xinerama multi monitor setup");
161 break;
162 case 0x10:
163 syslog(LOG_ERR, "Found Xrandr but no Xinerama, weird!");
164 break;
165 case 0x11:
166 /* Standard xrandr setup, nothing to see here */
167 break;
171 static XRRModeInfo *
172 find_mode_by_name (struct vdagent_x11 *x11, char *name)
174 int m;
175 XRRModeInfo *ret = NULL;
177 for (m = 0; m < x11->randr.res->nmode; m++) {
178 XRRModeInfo *mode = &x11->randr.res->modes[m];
179 if (!strcmp (name, mode->name)) {
180 ret = mode;
181 break;
184 return ret;
187 static XRRModeInfo *
188 find_mode_by_size (struct vdagent_x11 *x11, int output, int width, int height)
190 int m;
191 XRRModeInfo *ret = NULL;
193 for (m = 0; m < x11->randr.outputs[output]->nmode; m++) {
194 XRRModeInfo *mode = mode_from_id(x11,
195 x11->randr.outputs[output]->modes[m]);
196 if (mode && mode->width == width && mode->height == height) {
197 ret = mode;
198 break;
201 return ret;
204 static void delete_mode(struct vdagent_x11 *x11, int output_index,
205 int width, int height)
207 int m;
208 XRRModeInfo *mode;
209 XRROutputInfo *output_info;
210 char name[20];
212 if (width == 0 || height == 0)
213 return;
215 snprintf(name, sizeof(name), "%dx%d-%d", width, height, output_index);
216 if (x11->debug)
217 syslog(LOG_DEBUG, "Deleting mode %s", name);
219 output_info = x11->randr.outputs[output_index];
220 if (output_info->ncrtc != 1) {
221 syslog(LOG_ERR, "output has %d crtcs, expected exactly 1, "
222 "failed to delete mode", output_info->ncrtc);
223 return;
225 for (m = 0 ; m < x11->randr.res->nmode; ++m) {
226 mode = &x11->randr.res->modes[m];
227 if (strcmp(mode->name, name) == 0)
228 break;
230 if (m < x11->randr.res->nmode) {
231 vdagent_x11_set_error_handler(x11, error_handler);
232 XRRDeleteOutputMode (x11->display, x11->randr.res->outputs[output_index],
233 mode->id);
234 XRRDestroyMode (x11->display, mode->id);
235 // ignore race error, if mode is created by others
236 vdagent_x11_restore_error_handler(x11);
239 /* silly to update everytime for more then one monitor */
240 update_randr_res(x11, 0);
243 static void set_reduced_cvt_mode(XRRModeInfo *mode, int width, int height)
245 /* Code taken from hw/xfree86/modes/xf86cvt.c
246 * See that file for lineage. Originated in public domain code
247 * Would be nice if xorg exported this in a library */
249 /* 1) top/bottom margin size (% of height) - default: 1.8 */
250 #define CVT_MARGIN_PERCENTAGE 1.8
252 /* 2) character cell horizontal granularity (pixels) - default 8 */
253 #define CVT_H_GRANULARITY 8
255 /* 4) Minimum vertical porch (lines) - default 3 */
256 #define CVT_MIN_V_PORCH 3
258 /* 4) Minimum number of vertical back porch lines - default 6 */
259 #define CVT_MIN_V_BPORCH 6
261 /* Pixel Clock step (kHz) */
262 #define CVT_CLOCK_STEP 250
264 /* Minimum vertical blanking interval time (µs) - default 460 */
265 #define CVT_RB_MIN_VBLANK 460.0
267 /* Fixed number of clocks for horizontal sync */
268 #define CVT_RB_H_SYNC 32.0
270 /* Fixed number of clocks for horizontal blanking */
271 #define CVT_RB_H_BLANK 160.0
273 /* Fixed number of lines for vertical front porch - default 3 */
274 #define CVT_RB_VFPORCH 3
276 int VBILines;
277 float VFieldRate = 60.0;
278 int VSync;
279 float HPeriod;
281 /* 2. Horizontal pixels */
282 width = width - (width % CVT_H_GRANULARITY);
284 mode->width = width;
285 mode->height = height;
286 VSync = 10;
288 /* 8. Estimate Horizontal period. */
289 HPeriod = ((float) (1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) / height;
291 /* 9. Find number of lines in vertical blanking */
292 VBILines = ((float) CVT_RB_MIN_VBLANK) / HPeriod + 1;
294 /* 10. Check if vertical blanking is sufficient */
295 if (VBILines < (CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH))
296 VBILines = CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH;
298 /* 11. Find total number of lines in vertical field */
299 mode->vTotal = height + VBILines;
301 /* 12. Find total number of pixels in a line */
302 mode->hTotal = mode->width + CVT_RB_H_BLANK;
304 /* Fill in HSync values */
305 mode->hSyncEnd = mode->width + CVT_RB_H_BLANK / 2;
306 mode->hSyncStart = mode->hSyncEnd - CVT_RB_H_SYNC;
308 /* Fill in VSync values */
309 mode->vSyncStart = mode->height + CVT_RB_VFPORCH;
310 mode->vSyncEnd = mode->vSyncStart + VSync;
312 /* 15/13. Find pixel clock frequency (kHz for xf86) */
313 mode->dotClock = mode->hTotal * 1000.0 / HPeriod;
314 mode->dotClock -= mode->dotClock % CVT_CLOCK_STEP;
318 static XRRModeInfo *create_new_mode(struct vdagent_x11 *x11, int output_index,
319 int width, int height)
321 char modename[20];
322 XRRModeInfo mode;
324 snprintf(modename, sizeof(modename), "%dx%d-%d", width, height, output_index);
325 mode.hSkew = 0;
326 mode.name = modename;
327 mode.nameLength = strlen(mode.name);
328 set_reduced_cvt_mode(&mode, width, height);
329 mode.modeFlags = 0;
330 mode.id = 0;
331 vdagent_x11_set_error_handler(x11, error_handler);
332 XRRCreateMode (x11->display, x11->root_window[0], &mode);
333 // ignore race error, if mode is created by others
334 vdagent_x11_restore_error_handler(x11);
336 /* silly to update everytime for more then one monitor */
337 update_randr_res(x11, 0);
339 return find_mode_by_name(x11, modename);
342 static int xrandr_add_and_set(struct vdagent_x11 *x11, int output, int x, int y,
343 int width, int height)
345 XRRModeInfo *mode;
346 int xid;
347 Status s;
348 RROutput outputs[1];
349 int old_width = x11->randr.monitor_sizes[output].width;
350 int old_height = x11->randr.monitor_sizes[output].height;
352 if (!x11->randr.res || output >= x11->randr.res->noutput || output < 0) {
353 syslog(LOG_ERR, "%s: program error: missing RANDR or bad output",
354 __FUNCTION__);
355 return 0;
357 if (x11->set_crtc_config_not_functional) {
358 /* fail, set_best_mode will find something close. */
359 return 0;
361 xid = x11->randr.res->outputs[output];
362 mode = find_mode_by_size(x11, output, width, height);
363 if (!mode) {
364 mode = create_new_mode(x11, output, width, height);
366 if (!mode) {
367 syslog(LOG_ERR, "failed to add a new mode");
368 return 0;
370 XRRAddOutputMode(x11->display, xid, mode->id);
371 x11->randr.monitor_sizes[output].width = width;
372 x11->randr.monitor_sizes[output].height = height;
373 outputs[0] = xid;
374 vdagent_x11_set_error_handler(x11, error_handler);
375 s = XRRSetCrtcConfig(x11->display, x11->randr.res, x11->randr.res->crtcs[output],
376 CurrentTime, x, y, mode->id, RR_Rotate_0, outputs,
378 if (vdagent_x11_restore_error_handler(x11) || (s != RRSetConfigSuccess)) {
379 syslog(LOG_ERR, "failed to XRRSetCrtcConfig");
380 x11->set_crtc_config_not_functional = 1;
381 return 0;
384 /* clean the previous name, if any */
385 if (width != old_width || height != old_height)
386 delete_mode(x11, output, old_width, old_height);
388 return 1;
391 static void xrandr_disable_output(struct vdagent_x11 *x11, int output)
393 Status s;
395 if (!x11->randr.res || output >= x11->randr.res->noutput || output < 0) {
396 syslog(LOG_ERR, "%s: program error: missing RANDR or bad output",
397 __FUNCTION__);
398 return;
401 s = XRRSetCrtcConfig(x11->display, x11->randr.res,
402 x11->randr.res->crtcs[output],
403 CurrentTime, 0, 0, None, RR_Rotate_0,
404 NULL, 0);
406 if (s != RRSetConfigSuccess)
407 syslog(LOG_ERR, "failed to disable monitor");
409 delete_mode(x11, output, x11->randr.monitor_sizes[output].width,
410 x11->randr.monitor_sizes[output].height);
411 x11->randr.monitor_sizes[output].width = 0;
412 x11->randr.monitor_sizes[output].height = 0;
415 static int set_screen_to_best_size(struct vdagent_x11 *x11, int width, int height,
416 int *out_width, int *out_height){
417 int i, num_sizes = 0;
418 int best = -1;
419 unsigned int closest_diff = -1;
420 XRRScreenSize *sizes;
421 XRRScreenConfiguration *config;
422 Rotation rotation;
424 sizes = XRRSizes(x11->display, 0, &num_sizes);
425 if (!sizes || !num_sizes) {
426 syslog(LOG_ERR, "XRRSizes failed");
427 return 0;
429 if (x11->debug)
430 syslog(LOG_DEBUG, "set_screen_to_best_size found %d modes\n", num_sizes);
432 /* Find the closest size which will fit within the monitor */
433 for (i = 0; i < num_sizes; i++) {
434 if (sizes[i].width > width ||
435 sizes[i].height > height)
436 continue; /* Too large for the monitor */
438 unsigned int wdiff = width - sizes[i].width;
439 unsigned int hdiff = height - sizes[i].height;
440 unsigned int diff = wdiff * wdiff + hdiff * hdiff;
441 if (diff < closest_diff) {
442 closest_diff = diff;
443 best = i;
447 if (best == -1) {
448 syslog(LOG_ERR, "no suitable resolution found for monitor");
449 return 0;
452 config = XRRGetScreenInfo(x11->display, x11->root_window[0]);
453 if(!config) {
454 syslog(LOG_ERR, "get screen info failed");
455 return 0;
457 XRRConfigCurrentConfiguration(config, &rotation);
458 XRRSetScreenConfig(x11->display, config, x11->root_window[0], best,
459 rotation, CurrentTime);
460 XRRFreeScreenConfigInfo(config);
462 if (x11->debug)
463 syslog(LOG_DEBUG, "set_screen_to_best_size set size to: %dx%d\n",
464 sizes[best].width, sizes[best].height);
465 *out_width = sizes[best].width;
466 *out_height = sizes[best].height;
467 return 1;
470 void vdagent_x11_randr_handle_root_size_change(struct vdagent_x11 *x11,
471 int screen, int width, int height)
473 update_randr_res(x11, 0);
475 if (width == x11->width[screen] && height == x11->height[screen]) {
476 return;
479 if (x11->debug)
480 syslog(LOG_DEBUG, "Root size of screen %d changed to %dx%d send %d",
481 screen, width, height, !x11->dont_send_guest_xorg_res);
483 x11->width[screen] = width;
484 x11->height[screen] = height;
485 if (!x11->dont_send_guest_xorg_res) {
486 vdagent_x11_send_daemon_guest_xorg_res(x11, 1);
490 int vdagent_x11_randr_handle_event(struct vdagent_x11 *x11,
491 XEvent event)
493 int handled = TRUE;
495 switch (event.type - x11->xrandr_event_base) {
496 case RRScreenChangeNotify: {
497 XRRScreenChangeNotifyEvent *sce =
498 (XRRScreenChangeNotifyEvent *) &event;
499 vdagent_x11_randr_handle_root_size_change(x11, 0,
500 sce->width, sce->height);
501 break;
503 case RRNotify: {
504 update_randr_res(x11, 0);
505 if (!x11->dont_send_guest_xorg_res)
506 vdagent_x11_send_daemon_guest_xorg_res(x11, 1);
507 break;
509 default:
510 handled = FALSE;
511 break;
514 return handled;
517 static int min_int(int x, int y)
519 return x > y ? y : x;
522 static int max_int(int x, int y)
524 return x > y ? x : y;
527 static int constrain_to_range(int low, int *val, int high)
529 if (low <= *val && *val <= high) {
530 return 0;
532 if (low > *val) {
533 *val = low;
535 if (*val > high) {
536 *val = high;
538 return 1;
541 static void constrain_to_screen(struct vdagent_x11 *x11, int *w, int *h)
543 int lx, ly, hx, hy;
544 int orig_h = *h;
545 int orig_w = *w;
547 lx = x11->randr.min_width;
548 hx = x11->randr.max_width;
549 ly = x11->randr.min_height;
550 hy = x11->randr.max_height;
551 if (constrain_to_range(lx, w, hx)) {
552 syslog(LOG_ERR, "width not in driver range: ! %d < %d < %d",
553 lx, orig_w, hx);
555 if (constrain_to_range(ly, h, hy)) {
556 syslog(LOG_ERR, "height not in driver range: ! %d < %d < %d",
557 ly, orig_h, hy);
561 static int monitor_enabled(VDAgentMonConfig *mon)
563 return mon->width != 0 && mon->height != 0;
567 * The agent config doesn't contain a primary size, just the monitors, but
568 * we need to total size as well, to make sure we have enough memory and
569 * because X needs it.
571 * At the same pass constrain any provided size to what the server accepts.
573 * Exit axioms:
574 * x >= 0, y >= 0 for all x, y in mon_config
575 * max_width >= width >= min_width,
576 * max_height >= height >= min_height for all monitors in mon_config
578 static void zero_base_monitors(struct vdagent_x11 *x11,
579 VDAgentMonitorsConfig *mon_config,
580 int *width, int *height)
582 int i, min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN;
583 int *mon_height, *mon_width;
585 for (i = 0; i < mon_config->num_of_monitors; i++) {
586 if (!monitor_enabled(&mon_config->monitors[i]))
587 continue;
588 mon_config->monitors[i].x &= ~7;
589 mon_config->monitors[i].width &= ~7;
590 mon_width = (int *)&mon_config->monitors[i].width;
591 mon_height = (int *)&mon_config->monitors[i].height;
592 constrain_to_screen(x11, mon_width, mon_height);
593 min_x = min_int(mon_config->monitors[i].x, min_x);
594 min_y = min_int(mon_config->monitors[i].y, min_y);
595 max_x = max_int(mon_config->monitors[i].x + *mon_width, max_x);
596 max_y = max_int(mon_config->monitors[i].y + *mon_height, max_y);
598 if (min_x != 0 || min_y != 0) {
599 syslog(LOG_ERR, "%s: agent config %d,%d rooted, adjusting to 0,0.",
600 __FUNCTION__, min_x, min_y);
601 for (i = 0 ; i < mon_config->num_of_monitors; ++i) {
602 if (!monitor_enabled(&mon_config->monitors[i]))
603 continue;
604 mon_config->monitors[i].x -= min_x;
605 mon_config->monitors[i].y -= min_y;
608 max_x -= min_x;
609 max_y -= min_y;
610 *width = max_x;
611 *height = max_y;
614 static int enabled_monitors(VDAgentMonitorsConfig *mon)
616 int i, enabled = 0;
618 for (i = 0; i < mon->num_of_monitors; i++) {
619 if (monitor_enabled(&mon->monitors[i]))
620 enabled++;
622 return enabled;
625 static int same_monitor_configs(VDAgentMonitorsConfig *conf1,
626 VDAgentMonitorsConfig *conf2)
628 int i;
630 if (conf1 == NULL || conf2 == NULL ||
631 conf1->num_of_monitors != conf2->num_of_monitors)
632 return 0;
634 for (i = 0; i < conf1->num_of_monitors; i++) {
635 VDAgentMonConfig *mon1 = &conf1->monitors[i];
636 VDAgentMonConfig *mon2 = &conf2->monitors[i];
637 /* NOTE: we don't compare depth. */
638 if (mon1->x != mon2->x || mon1->y != mon2->y ||
639 mon1->width != mon2->width || mon1->height != mon2->height)
640 return 0;
642 return 1;
645 static int config_size(int num_of_monitors)
647 return sizeof(VDAgentMonitorsConfig) +
648 num_of_monitors * sizeof(VDAgentMonConfig);
651 static VDAgentMonitorsConfig *get_current_mon_config(struct vdagent_x11 *x11)
653 int i, num_of_monitors = 0;
654 XRRModeInfo *mode;
655 XRRCrtcInfo *crtc;
656 XRRScreenResources *res = x11->randr.res;
657 VDAgentMonitorsConfig *mon_config;
659 mon_config = calloc(1, config_size(res->noutput));
660 if (!mon_config) {
661 syslog(LOG_ERR, "out of memory allocating current monitor config");
662 return NULL;
665 for (i = 0 ; i < res->noutput; i++) {
666 if (x11->randr.outputs[i]->ncrtc == 0)
667 continue; /* Monitor disabled, already zero-ed by calloc */
668 if (x11->randr.outputs[i]->ncrtc != 1)
669 goto error;
671 crtc = crtc_from_id(x11, x11->randr.outputs[i]->crtcs[0]);
672 if (!crtc)
673 goto error;
675 mode = mode_from_id(x11, crtc->mode);
676 if (!mode)
677 continue; /* Monitor disabled, already zero-ed by calloc */
679 mon_config->monitors[i].x = crtc->x;
680 mon_config->monitors[i].y = crtc->y;
681 mon_config->monitors[i].width = mode->width;
682 mon_config->monitors[i].height = mode->height;
683 num_of_monitors = i + 1;
685 mon_config->num_of_monitors = num_of_monitors;
686 mon_config->flags = VD_AGENT_CONFIG_MONITORS_FLAG_USE_POS;
687 return mon_config;
689 error:
690 syslog(LOG_ERR, "error: inconsistent or stale data from X");
691 free(mon_config);
692 return NULL;
695 static void dump_monitors_config(struct vdagent_x11 *x11,
696 VDAgentMonitorsConfig *mon_config,
697 const char *prefix)
699 int i;
700 VDAgentMonConfig *m;
702 syslog(LOG_DEBUG, "%s: %d, %x", prefix, mon_config->num_of_monitors,
703 mon_config->flags);
704 for (i = 0 ; i < mon_config->num_of_monitors; ++i) {
705 m = &mon_config->monitors[i];
706 if (!monitor_enabled(m))
707 continue;
708 syslog(LOG_DEBUG, "received monitor %d config %dx%d+%d+%d", i,
709 m->width, m->height, m->x, m->y);
714 * Set monitor configuration according to client request.
716 * On exit send current configuration to client, regardless of error.
718 * Errors:
719 * screen size too large for driver to handle. (we set the largest/smallest possible)
720 * no randr support in X server.
721 * invalid configuration request from client.
723 void vdagent_x11_set_monitor_config(struct vdagent_x11 *x11,
724 VDAgentMonitorsConfig *mon_config,
725 int fallback)
727 int primary_w, primary_h;
728 int i, real_num_of_monitors = 0;
729 VDAgentMonitorsConfig *curr = NULL;
731 if (!x11->has_xrandr)
732 goto exit;
734 if (enabled_monitors(mon_config) < 1) {
735 syslog(LOG_ERR, "client sent config with all monitors disabled");
736 goto exit;
739 if (x11->debug) {
740 dump_monitors_config(x11, mon_config, "from guest");
743 for (i = 0; i < mon_config->num_of_monitors; i++) {
744 if (monitor_enabled(&mon_config->monitors[i]))
745 real_num_of_monitors = i + 1;
747 mon_config->num_of_monitors = real_num_of_monitors;
749 update_randr_res(x11, 0);
750 if (mon_config->num_of_monitors > x11->randr.res->noutput) {
751 syslog(LOG_WARNING,
752 "warning unexpected client request: #mon %d > driver output %d",
753 mon_config->num_of_monitors, x11->randr.res->noutput);
754 mon_config->num_of_monitors = x11->randr.res->noutput;
757 if (mon_config->num_of_monitors > MONITOR_SIZE_COUNT) {
758 syslog(LOG_WARNING, "warning: client send %d monitors, capping at %d",
759 mon_config->num_of_monitors, MONITOR_SIZE_COUNT);
760 mon_config->num_of_monitors = MONITOR_SIZE_COUNT;
763 zero_base_monitors(x11, mon_config, &primary_w, &primary_h);
765 constrain_to_screen(x11, &primary_w, &primary_h);
767 if (x11->debug) {
768 dump_monitors_config(x11, mon_config, "after zeroing");
771 curr = get_current_mon_config(x11);
772 if (!curr)
773 goto exit;
774 if (same_monitor_configs(mon_config, curr) &&
775 x11->width[0] == primary_w && x11->height[0] == primary_h) {
776 goto exit;
779 if (same_monitor_configs(mon_config, x11->randr.failed_conf)) {
780 syslog(LOG_WARNING, "Ignoring previous failed client monitor config");
781 goto exit;
784 gchar *config = g_build_filename (g_get_user_config_dir (), "monitors.xml", NULL);
785 g_unlink(config);
786 g_free(config);
788 for (i = mon_config->num_of_monitors; i < x11->randr.res->noutput; i++)
789 xrandr_disable_output(x11, i);
791 /* First, disable disabled CRTCs... */
792 for (i = 0; i < mon_config->num_of_monitors; ++i) {
793 if (!monitor_enabled(&mon_config->monitors[i])) {
794 xrandr_disable_output(x11, i);
798 /* ... and disable the ones that would be bigger than
799 * the new RandR screen once it is resized. If they are enabled the
800 * XRRSetScreenSize call will fail with BadMatch. They will be
801 * reenabled after hanging the screen size.
803 for (i = 0; i < curr->num_of_monitors; ++i) {
804 int width, height;
805 int x, y;
807 width = curr->monitors[i].width;
808 height = curr->monitors[i].height;
809 x = curr->monitors[i].x;
810 y = curr->monitors[i].y;
812 if ((x + width > primary_w) || (y + height > primary_h)) {
813 if (x11->debug)
814 syslog(LOG_DEBUG, "Disabling monitor %d: "
815 "%dx%d+%d+%d > (%d,%d)",
816 i, width, height, x, y, primary_w, primary_h);
818 xrandr_disable_output(x11, i);
822 /* Then we can resize the RandR screen. */
823 if (primary_w != x11->width[0] || primary_h != x11->height[0]) {
824 const int dpi = 96; /* FIXME: read settings from desktop or get from client dpi? */
825 int width_mm = (MM_PER_INCH * primary_w) / dpi;
826 int height_mm = (MM_PER_INCH * primary_h) / dpi;
828 if (x11->debug)
829 syslog(LOG_DEBUG, "Changing screen size to %dx%d",
830 primary_w, primary_h);
831 vdagent_x11_set_error_handler(x11, error_handler);
832 XRRSetScreenSize(x11->display, x11->root_window[0], primary_w, primary_h,
833 width_mm, height_mm);
834 if (vdagent_x11_restore_error_handler(x11)) {
835 syslog(LOG_ERR, "XRRSetScreenSize failed, not enough mem?");
836 if (!fallback) {
837 syslog(LOG_WARNING, "Restoring previous config");
838 vdagent_x11_set_monitor_config(x11, curr, 1);
839 free(curr);
840 /* Remember this config failed, if the client is maximized or
841 fullscreen it will keep sending the failing config. */
842 free(x11->randr.failed_conf);
843 x11->randr.failed_conf =
844 malloc(config_size(mon_config->num_of_monitors));
845 if (x11->randr.failed_conf)
846 memcpy(x11->randr.failed_conf, mon_config,
847 config_size(mon_config->num_of_monitors));
848 return;
853 /* Finally, we set the new resolutions on RandR CRTCs now that the
854 * RandR screen is big enough to hold these. */
855 for (i = 0; i < mon_config->num_of_monitors; ++i) {
856 int width, height;
857 int x, y;
859 if (!monitor_enabled(&mon_config->monitors[i])) {
860 continue;
862 /* Try to create the requested resolution */
863 width = mon_config->monitors[i].width;
864 height = mon_config->monitors[i].height;
865 x = mon_config->monitors[i].x;
866 y = mon_config->monitors[i].y;
867 if (!xrandr_add_and_set(x11, i, x, y, width, height) &&
868 enabled_monitors(mon_config) == 1) {
869 set_screen_to_best_size(x11, width, height,
870 &primary_w, &primary_h);
871 break;
875 update_randr_res(x11,
876 x11->randr.num_monitors != enabled_monitors(mon_config));
877 x11->width[0] = primary_w;
878 x11->height[0] = primary_h;
880 /* Flush output buffers and consume any pending events (ConfigureNotify) */
881 x11->dont_send_guest_xorg_res = 1;
882 vdagent_x11_do_read(x11);
883 x11->dont_send_guest_xorg_res = 0;
885 exit:
886 vdagent_x11_send_daemon_guest_xorg_res(x11, 0);
888 /* Flush output buffers and consume any pending events */
889 vdagent_x11_do_read(x11);
890 free(curr);
893 void vdagent_x11_send_daemon_guest_xorg_res(struct vdagent_x11 *x11, int update)
895 struct vdagentd_guest_xorg_resolution *res = NULL;
896 int i, width = 0, height = 0, screen_count = 0;
898 if (x11->has_xrandr) {
899 VDAgentMonitorsConfig *curr;
901 if (update)
902 update_randr_res(x11, 0);
904 curr = get_current_mon_config(x11);
905 if (!curr)
906 goto no_info;
908 screen_count = curr->num_of_monitors;
909 res = malloc(screen_count * sizeof(*res));
910 if (!res) {
911 free(curr);
912 goto no_mem;
915 for (i = 0; i < screen_count; i++) {
916 res[i].width = curr->monitors[i].width;
917 res[i].height = curr->monitors[i].height;
918 res[i].x = curr->monitors[i].x;
919 res[i].y = curr->monitors[i].y;
921 free(curr);
922 width = x11->width[0];
923 height = x11->height[0];
924 } else if (x11->has_xinerama) {
925 XineramaScreenInfo *screen_info = NULL;
927 screen_info = XineramaQueryScreens(x11->display, &screen_count);
928 if (!screen_info)
929 goto no_info;
930 res = malloc(screen_count * sizeof(*res));
931 if (!res) {
932 XFree(screen_info);
933 goto no_mem;
935 for (i = 0; i < screen_count; i++) {
936 if (screen_info[i].screen_number >= screen_count) {
937 syslog(LOG_ERR, "Invalid screen number in xinerama screen info (%d >= %d)",
938 screen_info[i].screen_number, screen_count);
939 XFree(screen_info);
940 free(res);
941 return;
943 res[screen_info[i].screen_number].width = screen_info[i].width;
944 res[screen_info[i].screen_number].height = screen_info[i].height;
945 res[screen_info[i].screen_number].x = screen_info[i].x_org;
946 res[screen_info[i].screen_number].y = screen_info[i].y_org;
948 XFree(screen_info);
949 width = x11->width[0];
950 height = x11->height[0];
951 } else {
952 no_info:
953 screen_count = x11->screen_count;
954 res = malloc(screen_count * sizeof(*res));
955 if (!res)
956 goto no_mem;
957 for (i = 0; i < screen_count; i++) {
958 res[i].width = x11->width[i];
959 res[i].height = x11->height[i];
960 /* No way to get screen coordinates, assume rtl order */
961 res[i].x = width;
962 res[i].y = 0;
963 width += x11->width[i];
964 if (x11->height[i] > height)
965 height = x11->height[i];
969 if (x11->debug) {
970 for (i = 0; i < screen_count; i++)
971 syslog(LOG_DEBUG, "Screen %d %dx%d%+d%+d", i, res[i].width,
972 res[i].height, res[i].x, res[i].y);
975 udscs_write(x11->vdagentd, VDAGENTD_GUEST_XORG_RESOLUTION, width, height,
976 (uint8_t *)res, screen_count * sizeof(*res));
977 free(res);
978 return;
979 no_mem:
980 syslog(LOG_ERR, "out of memory while trying to send resolutions, not sending resolutions.");