4 #include <X11/extensions/Xinerama.h>
6 #include "vdagentd-proto.h"
7 #include "vdagent-x11-priv.h"
9 static int caught_error
;
10 static int (*old_error_handler
)(Display
*, XErrorEvent
*);
12 static int error_handler(Display
*display
, XErrorEvent
*error
)
17 static void arm_error_handler(struct vdagent_x11
*x11
)
20 XSync(x11
->display
, True
);
21 old_error_handler
= XSetErrorHandler(error_handler
);
24 static void check_error_handler(struct vdagent_x11
*x11
)
26 XSync(x11
->display
, False
);
27 XSetErrorHandler(old_error_handler
);
29 x11
->set_crtc_config_not_functional
= 1;
33 static XRRModeInfo
*mode_from_id(struct vdagent_x11
*x11
, int id
)
37 for (i
= 0 ; i
< x11
->randr
.res
->nmode
; ++i
) {
38 if (id
== x11
->randr
.res
->modes
[i
].id
) {
39 return &x11
->randr
.res
->modes
[i
];
45 static XRRCrtcInfo
*crtc_from_id(struct vdagent_x11
*x11
, int id
)
49 for (i
= 0 ; i
< x11
->randr
.res
->ncrtc
; ++i
) {
50 if (id
== x11
->randr
.res
->crtcs
[i
]) {
51 return x11
->randr
.crtcs
[i
];
57 static void free_randr_resources(struct vdagent_x11
*x11
)
61 if (!x11
->randr
.res
) {
64 if (x11
->randr
.outputs
!= NULL
) {
65 for (i
= 0 ; i
< x11
->randr
.res
->noutput
; ++i
) {
66 XRRFreeOutputInfo(x11
->randr
.outputs
[i
]);
68 free(x11
->randr
.outputs
);
70 if (x11
->randr
.crtcs
!= NULL
) {
71 for (i
= 0 ; i
< x11
->randr
.res
->ncrtc
; ++i
) {
72 XRRFreeCrtcInfo(x11
->randr
.crtcs
[i
]);
74 free(x11
->randr
.crtcs
);
76 XRRFreeScreenResources(x11
->randr
.res
);
77 x11
->randr
.res
= NULL
;
78 x11
->randr
.outputs
= NULL
;
79 x11
->randr
.crtcs
= NULL
;
82 static int update_randr_res(struct vdagent_x11
*x11
)
86 free_randr_resources(x11
);
87 /* Note: we don't need XRRGetScreenResourcesCurrent since we cache the changes */
88 x11
->randr
.res
= XRRGetScreenResources(x11
->display
, x11
->root_window
);
89 x11
->randr
.outputs
= malloc(x11
->randr
.res
->noutput
* sizeof(*x11
->randr
.outputs
));
90 x11
->randr
.crtcs
= malloc(x11
->randr
.res
->ncrtc
* sizeof(*x11
->randr
.crtcs
));
91 for (i
= 0 ; i
< x11
->randr
.res
->noutput
; ++i
) {
92 x11
->randr
.outputs
[i
] = XRRGetOutputInfo(x11
->display
, x11
->randr
.res
,
93 x11
->randr
.res
->outputs
[i
]);
95 for (i
= 0 ; i
< x11
->randr
.res
->ncrtc
; ++i
) {
96 x11
->randr
.crtcs
[i
] = XRRGetCrtcInfo(x11
->display
, x11
->randr
.res
,
97 x11
->randr
.res
->crtcs
[i
]);
99 /* XXX is this dynamic? should it be cached? */
100 if (XRRGetScreenSizeRange(x11
->display
, x11
->root_window
,
101 &x11
->randr
.min_width
,
102 &x11
->randr
.min_height
,
103 &x11
->randr
.max_width
,
104 &x11
->randr
.max_height
) != 1) {
105 fprintf(x11
->errfile
, "RRGetScreenSizeRange failed\n");
109 void vdagent_x11_randr_init(struct vdagent_x11
*x11
)
113 if (XRRQueryExtension(x11
->display
, &i
, &i
)) {
115 if (!update_randr_res(x11
)) {
116 fprintf(x11
->errfile
, "get screen info failed\n");
118 XRRQueryVersion(x11
->display
, &x11
->xrandr_major
, &x11
->xrandr_minor
);
120 x11
->randr
.res
= NULL
;
123 if (XineramaQueryExtension(x11
->display
, &i
, &i
))
124 x11
->has_xinerama
= 1;
126 switch (x11
->has_xrandr
<< 4 | x11
->has_xinerama
) {
128 fprintf(x11
->errfile
, "Neither Xrandr nor Xinerama found, assuming single monitor setup\n");
132 fprintf(x11
->errfile
, "Found Xinerama extension without Xrandr, assuming a multi monitor setup\n");
135 fprintf(x11
->errfile
, "Found Xrandr but no Xinerama, weird! Assuming a single monitor setup\n");
138 /* Standard single monitor setup, nothing to see here */
144 find_mode_by_name (struct vdagent_x11
*x11
, char *name
)
147 XRRModeInfo
*ret
= NULL
;
149 for (m
= 0; m
< x11
->randr
.res
->nmode
; m
++) {
150 XRRModeInfo
*mode
= &x11
->randr
.res
->modes
[m
];
151 if (!strcmp (name
, mode
->name
)) {
160 find_mode_by_size (struct vdagent_x11
*x11
, int width
, int height
)
163 XRRModeInfo
*ret
= NULL
;
165 for (m
= 0; m
< x11
->randr
.res
->nmode
; m
++) {
166 XRRModeInfo
*mode
= &x11
->randr
.res
->modes
[m
];
167 if (mode
->width
== width
&& mode
->height
== height
) {
175 static int mode_in_use(struct vdagent_x11
*x11
, int xid
, const char *name
)
179 XRROutputInfo
*output_info
;
182 output_info
= XRRGetOutputInfo(x11
->display
, x11
->randr
.res
, xid
);
183 crtc
= output_info
->crtc
;
184 XRRFreeOutputInfo(output_info
);
188 for (m
= 0 ; m
< x11
->randr
.res
->nmode
; ++m
) {
189 mode
= &x11
->randr
.res
->modes
[m
];
190 if (strcmp(mode
->name
, name
) == 0) {
197 static void delete_mode(struct vdagent_x11
*x11
, int output_index
, const char *name
)
201 XRRModeInfo
*the_mode
;
202 XRROutputInfo
*output_info
;
203 XRRCrtcInfo
*crtc_info
;
205 int current_mode
= -1;
207 output_info
= x11
->randr
.outputs
[output_index
];
208 if (output_info
->ncrtc
!= 1) {
209 fprintf(x11
->errfile
,
210 "output has %d crtcs, expected exactly 1, "
211 "failed to delete mode\n",
215 crtc_info
= crtc_from_id(x11
, output_info
->crtcs
[0]);
216 current_mode
= crtc_info
->mode
;
217 crtc
= output_info
->crtc
;
219 for (m
= 0 ; m
< x11
->randr
.res
->nmode
; ++m
) {
220 mode
= &x11
->randr
.res
->modes
[m
];
221 if (strcmp(mode
->name
, name
) == 0) {
227 if (crtc
&& the_mode
->id
== current_mode
) {
228 fprintf(x11
->errfile
,
229 "delete_mode of in use mode, setting crtc to NULL mode\n");
230 XRRSetCrtcConfig(x11
->display
, x11
->randr
.res
, crtc
,
231 CurrentTime
, 0, 0, None
, RR_Rotate_0
, NULL
, 0);
233 XRRDeleteOutputMode (x11
->display
, x11
->randr
.res
->outputs
[output_index
],
235 XRRDestroyMode (x11
->display
, mode
->id
);
239 void set_reduced_cvt_mode(XRRModeInfo
*mode
, int width
, int height
)
241 /* Code taken from hw/xfree86/modes/xf86cvt.c
242 * See that file for lineage. Originated in public domain code
243 * Would be nice if xorg exported this in a library */
245 /* 1) top/bottom margin size (% of height) - default: 1.8 */
246 #define CVT_MARGIN_PERCENTAGE 1.8
248 /* 2) character cell horizontal granularity (pixels) - default 8 */
249 #define CVT_H_GRANULARITY 8
251 /* 4) Minimum vertical porch (lines) - default 3 */
252 #define CVT_MIN_V_PORCH 3
254 /* 4) Minimum number of vertical back porch lines - default 6 */
255 #define CVT_MIN_V_BPORCH 6
257 /* Pixel Clock step (kHz) */
258 #define CVT_CLOCK_STEP 250
260 /* Minimum vertical blanking interval time (µs) - default 460 */
261 #define CVT_RB_MIN_VBLANK 460.0
263 /* Fixed number of clocks for horizontal sync */
264 #define CVT_RB_H_SYNC 32.0
266 /* Fixed number of clocks for horizontal blanking */
267 #define CVT_RB_H_BLANK 160.0
269 /* Fixed number of lines for vertical front porch - default 3 */
270 #define CVT_RB_VFPORCH 3
273 float VFieldRate
= 60.0;
277 /* 2. Horizontal pixels */
278 width
= width
- (width
% CVT_H_GRANULARITY
);
281 mode
->height
= height
;
284 /* 8. Estimate Horizontal period. */
285 HPeriod
= ((float) (1000000.0 / VFieldRate
- CVT_RB_MIN_VBLANK
)) / height
;
287 /* 9. Find number of lines in vertical blanking */
288 VBILines
= ((float) CVT_RB_MIN_VBLANK
) / HPeriod
+ 1;
290 /* 10. Check if vertical blanking is sufficient */
291 if (VBILines
< (CVT_RB_VFPORCH
+ VSync
+ CVT_MIN_V_BPORCH
))
292 VBILines
= CVT_RB_VFPORCH
+ VSync
+ CVT_MIN_V_BPORCH
;
294 /* 11. Find total number of lines in vertical field */
295 mode
->vTotal
= height
+ VBILines
;
297 /* 12. Find total number of pixels in a line */
298 mode
->hTotal
= mode
->width
+ CVT_RB_H_BLANK
;
300 /* Fill in HSync values */
301 mode
->hSyncEnd
= mode
->width
+ CVT_RB_H_BLANK
/ 2;
302 mode
->hSyncStart
= mode
->hSyncEnd
- CVT_RB_H_SYNC
;
304 /* Fill in VSync values */
305 mode
->vSyncStart
= mode
->height
+ CVT_RB_VFPORCH
;
306 mode
->vSyncEnd
= mode
->vSyncStart
+ VSync
;
308 /* 15/13. Find pixel clock frequency (kHz for xf86) */
309 mode
->dotClock
= mode
->hTotal
* 1000.0 / HPeriod
;
310 mode
->dotClock
-= mode
->dotClock
% CVT_CLOCK_STEP
;
314 static XRRModeInfo
*create_new_mode(struct vdagent_x11
*x11
, int output_index
,
315 int width
, int height
)
320 /* we may be using this mode from an old invocation - check first */
321 snprintf(modename
, sizeof(modename
), "vdagent-%d", output_index
);
322 arm_error_handler(x11
);
323 delete_mode(x11
, output_index
, modename
);
324 check_error_handler(x11
);
325 if (x11
->set_crtc_config_not_functional
) {
329 mode
.name
= modename
;
330 mode
.nameLength
= strlen(mode
.name
);
331 set_reduced_cvt_mode(&mode
, width
, height
);
335 XRRCreateMode (x11
->display
, x11
->root_window
, &mode
);
336 /* silly to update everytime for more then one monitor */
337 if (!update_randr_res(x11
)) {
338 fprintf(x11
->errfile
, "get screen info failed\n");
340 return find_mode_by_name(x11
, modename
);
343 static int xrandr_add_and_set(struct vdagent_x11
*x11
, int output
, int x
, int y
,
344 int width
, int height
)
351 if (!x11
->randr
.res
|| output
>= x11
->randr
.res
->noutput
|| output
< 0) {
352 fprintf(x11
->errfile
, "%s: program error: missing RANDR or bad output\n",
356 if (x11
->set_crtc_config_not_functional
) {
357 /* succeed without doing anything. set_best_mode will
358 * find something close. */
361 xid
= x11
->randr
.res
->outputs
[output
];
362 mode
= find_mode_by_size(x11
, width
, height
);
364 mode
= create_new_mode(x11
, output
, width
, height
);
367 fprintf(x11
->errfile
, "failed to add a new mode\n");
370 XRRAddOutputMode(x11
->display
, xid
, mode
->id
); // Call this anyway?
372 s
= XRRSetCrtcConfig(x11
->display
, x11
->randr
.res
, x11
->randr
.res
->crtcs
[output
],
373 CurrentTime
, x
, y
, mode
->id
, RR_Rotate_0
, outputs
,
375 if (s
!= RRSetConfigSuccess
) {
376 fprintf(x11
->errfile
, "failed to XRRSetCrtcConfig\n");
377 // TODO - return crtc config to default
383 static int set_screen_to_best_size(struct vdagent_x11
*x11
, int width
, int height
,
384 int *out_width
, int *out_height
){
385 int i
, num_sizes
= 0;
387 unsigned int closest_diff
= -1;
388 XRRScreenSize
*sizes
;
389 XRRScreenConfiguration
*config
;
392 sizes
= XRRSizes(x11
->display
, x11
->screen
, &num_sizes
);
393 if (!sizes
|| !num_sizes
) {
394 fprintf(x11
->errfile
, "XRRSizes failed\n");
398 /* Find the closest size which will fit within the monitor */
399 for (i
= 0; i
< num_sizes
; i
++) {
400 if (sizes
[i
].width
> width
||
401 sizes
[i
].height
> height
)
402 continue; /* Too large for the monitor */
404 unsigned int wdiff
= width
- sizes
[i
].width
;
405 unsigned int hdiff
= height
- sizes
[i
].height
;
406 unsigned int diff
= wdiff
* wdiff
+ hdiff
* hdiff
;
407 if (diff
< closest_diff
) {
414 fprintf(x11
->errfile
, "no suitable resolution found for monitor\n");
418 config
= XRRGetScreenInfo(x11
->display
, x11
->root_window
);
420 fprintf(x11
->errfile
, "get screen info failed\n");
423 XRRConfigCurrentConfiguration(config
, &rotation
);
424 XRRSetScreenConfig(x11
->display
, config
, x11
->root_window
, best
,
425 rotation
, CurrentTime
);
426 XRRFreeScreenConfigInfo(config
);
428 *out_width
= sizes
[best
].width
;
429 *out_height
= sizes
[best
].height
;
433 void vdagent_x11_randr_handle_root_size_change(struct vdagent_x11
*x11
,
434 int width
, int height
)
436 if (width
== x11
->width
&& height
== x11
->height
) {
441 x11
->height
= height
;
442 if (!x11
->dont_send_guest_xorg_res
) {
443 vdagent_x11_send_daemon_guest_xorg_res(x11
);
447 static int min_int(int x
, int y
)
449 return x
> y
? y
: x
;
452 static int max_int(int x
, int y
)
454 return x
> y
? x
: y
;
457 int constrain_to_range(int low
, int *val
, int high
)
459 if (low
<= *val
&& *val
<= high
) {
471 void constrain_to_screen(struct vdagent_x11
*x11
, int *w
, int *h
)
477 lx
= x11
->randr
.min_width
;
478 hx
= x11
->randr
.max_width
;
479 ly
= x11
->randr
.min_height
;
480 hy
= x11
->randr
.max_height
;
481 if (constrain_to_range(lx
, w
, hx
)) {
482 fprintf(x11
->errfile
,
483 "width not in driver range: ! %d < %d < %d\n",
486 if (constrain_to_range(ly
, h
, hy
)) {
487 fprintf(x11
->errfile
,
488 "height not in driver range: ! %d < %d < %d\n",
494 * The agent config doesn't contain a primary size, just the monitors, but
495 * we need to total size as well, to make sure we have enough memory and
496 * because X needs it.
498 * At the same pass constrain any provided size to what the server accepts.
501 * x >= 0, y >= 0 for all x, y in mon_config
502 * max_width >= width >= min_width,
503 * max_height >= height >= min_height for all monitors in mon_config
505 static void zero_base_monitors(struct vdagent_x11
*x11
,
506 VDAgentMonitorsConfig
*mon_config
,
507 int *width
, int *height
)
517 mon_width
= &mon_config
->monitors
[i
].width
;
518 mon_height
= &mon_config
->monitors
[i
].height
;
519 constrain_to_screen(x11
, mon_width
, mon_height
);
520 min_x
= mon_config
->monitors
[0].x
;
521 min_y
= mon_config
->monitors
[0].y
;
522 max_x
= mon_config
->monitors
[0].width
+ min_x
;
523 max_y
= mon_config
->monitors
[0].height
+ min_y
;
524 for (++i
; i
< mon_config
->num_of_monitors
; ++i
) {
525 mon_width
= &mon_config
->monitors
[i
].width
;
526 mon_height
= &mon_config
->monitors
[i
].height
;
527 constrain_to_screen(x11
, mon_width
, mon_height
);
528 min_x
= min_int(mon_config
->monitors
[i
].x
, min_x
);
529 min_y
= min_int(mon_config
->monitors
[i
].y
, min_y
);
530 max_x
= max_int(mon_config
->monitors
[i
].x
+ *mon_width
, max_x
);
531 max_y
= max_int(mon_config
->monitors
[i
].y
+ *mon_height
, max_y
);
533 if (min_x
!= 0 || min_y
!= 0) {
534 fprintf(x11
->errfile
,
535 "%s: agent config %d,%d rooted, adjusting to 0,0.\n",
536 __FUNCTION__
, min_x
, min_y
);
537 for (i
= 0 ; i
< mon_config
->num_of_monitors
; ++i
) {
538 mon_config
->monitors
[i
].x
-= min_x
;
539 mon_config
->monitors
[i
].y
-= min_y
;
548 int same_monitor_configs(struct vdagent_x11
*x11
, VDAgentMonitorsConfig
*mon
)
553 VDAgentMonConfig
*client_mode
;
554 XRRScreenResources
*res
= x11
->randr
.res
;
556 if (res
->noutput
> res
->ncrtc
) {
557 fprintf(x11
->errfile
, "error: unexpected noutput > ncrtc in driver\n");
561 if (mon
->num_of_monitors
> res
->noutput
) {
562 fprintf(x11
->errfile
, "error: unexpected client request: "
563 "#mon %d > driver output %d\n",
564 mon
->num_of_monitors
, res
->noutput
);
568 for (i
= 0 ; i
< mon
->num_of_monitors
; ++i
) {
569 if (x11
->randr
.outputs
[i
]->ncrtc
== 0) {
572 if (x11
->randr
.outputs
[i
]->ncrtc
!= 1) {
573 fprintf(x11
->errfile
,
574 "error: unexpected driver config, ncrtc %d != 1",
575 x11
->randr
.outputs
[i
]->ncrtc
);
578 crtc
= crtc_from_id(x11
, x11
->randr
.outputs
[i
]->crtcs
[0]);
580 fprintf(x11
->errfile
, "error: inconsistent or stale data from X\n");
583 client_mode
= &mon
->monitors
[i
];
584 mode
= mode_from_id(x11
, crtc
->mode
);
588 /* NOTE: we don't compare depth. */
589 /* NOTE 2: width set by X is a multiple of 8, so ignore lower 3 bits */
590 if ((mode
->width
& ~7) != (client_mode
->width
& ~7) ||
591 mode
->height
!= client_mode
->height
||
592 (crtc
->x
& ~7) != (client_mode
->x
& ~7) ||
593 crtc
->y
!= client_mode
->y
) {
600 static void dump_monitors_config(struct vdagent_x11
*x11
,
601 VDAgentMonitorsConfig
*mon_config
,
607 fprintf(x11
->errfile
, "%s: %d, %x\n", prefix
, mon_config
->num_of_monitors
,
609 for (i
= 0 ; i
< mon_config
->num_of_monitors
; ++i
) {
610 m
= &mon_config
->monitors
[i
];
611 fprintf(x11
->errfile
, "received monitor %d config %dx%d+%d+%d\n", i
,
612 m
->width
, m
->height
, m
->x
, m
->y
);
617 * Set monitor configuration according to client request.
619 * On exit send current configuration to client, regardless of error.
622 * screen size too large for driver to handle. (we set the largest/smallest possible)
623 * no randr support in X server.
624 * invalid configuration request from client.
626 void vdagent_x11_set_monitor_config(struct vdagent_x11
*x11
,
627 VDAgentMonitorsConfig
*mon_config
)
632 int primary_w
, primary_h
;
635 if (!x11
->has_xrandr
)
638 if (mon_config
->num_of_monitors
< 1) {
639 fprintf(x11
->errfile
, "client sent invalid monitor config number %d\n",
640 mon_config
->num_of_monitors
);
644 if (same_monitor_configs(x11
, mon_config
)) {
649 dump_monitors_config(x11
, mon_config
, "from guest");
652 zero_base_monitors(x11
, mon_config
, &primary_w
, &primary_h
);
654 constrain_to_screen(x11
, &primary_w
, &primary_h
);
657 dump_monitors_config(x11
, mon_config
, "after zeroing");
660 for (i
= 0; i
< mon_config
->num_of_monitors
; ++i
) {
661 /* Try to create the requested resolution */
662 width
= mon_config
->monitors
[i
].width
;
663 height
= mon_config
->monitors
[i
].height
;
664 x
= mon_config
->monitors
[i
].x
;
665 y
= mon_config
->monitors
[i
].y
;
666 if (!xrandr_add_and_set(x11
, i
, x
, y
, width
, height
) &&
667 mon_config
->num_of_monitors
== 1) {
668 set_screen_to_best_size(x11
, width
, height
, &width
, &height
);
672 if (primary_w
!= x11
->width
|| primary_h
!= x11
->height
) {
673 arm_error_handler(x11
);
674 XRRSetScreenSize(x11
->display
, x11
->root_window
, primary_w
, primary_h
,
675 DisplayWidthMM(x11
->display
, x11
->screen
),
676 DisplayHeightMM(x11
->display
, x11
->screen
));
677 check_error_handler(x11
);
680 if (!update_randr_res(x11
)) {
681 fprintf(x11
->errfile
, "get screen info failed\n");
683 x11
->width
= primary_w
;
684 x11
->height
= primary_h
;
686 /* Flush output buffers and consume any pending events (ConfigureNotify) */
687 x11
->dont_send_guest_xorg_res
= 1;
688 vdagent_x11_do_read(x11
);
689 x11
->dont_send_guest_xorg_res
= 0;
692 vdagent_x11_send_daemon_guest_xorg_res(x11
);
694 /* Flush output buffers and consume any pending events */
695 vdagent_x11_do_read(x11
);
698 void vdagent_x11_send_daemon_guest_xorg_res(struct vdagent_x11
*x11
)
700 struct vdagentd_guest_xorg_resolution
*res
= NULL
;
701 XineramaScreenInfo
*screen_info
= NULL
;
702 int i
, screen_count
= 0;
704 if (x11
->has_xinerama
) {
705 /* Xinerama reports the same information RANDR reports, so stay
706 * with Xinerama for support of Xinerama only setups */
707 screen_info
= XineramaQueryScreens(x11
->display
, &screen_count
);
710 if (screen_count
== 0)
713 res
= malloc(screen_count
* sizeof(*res
));
715 fprintf(x11
->errfile
, "out of memory while trying to send resolutions, not sending resolutions.\n");
722 for (i
= 0; i
< screen_count
; i
++) {
723 if (screen_info
[i
].screen_number
>= screen_count
) {
724 fprintf(x11
->errfile
, "Invalid screen number in xinerama screen info (%d >= %d)\n",
725 screen_info
[i
].screen_number
, screen_count
);
730 res
[screen_info
[i
].screen_number
].width
= screen_info
[i
].width
;
731 res
[screen_info
[i
].screen_number
].height
= screen_info
[i
].height
;
732 res
[screen_info
[i
].screen_number
].x
= screen_info
[i
].x_org
;
733 res
[screen_info
[i
].screen_number
].y
= screen_info
[i
].y_org
;
737 res
[0].width
= x11
->width
;
738 res
[0].height
= x11
->height
;
743 udscs_write(x11
->vdagentd
, VDAGENTD_GUEST_XORG_RESOLUTION
, x11
->width
,
744 x11
->height
, (uint8_t *)res
, screen_count
* sizeof(*res
));