win32: Fix multi-monitor virtual desktop with negative monitor coords

Under Win32, when you have a multiple-monitor setup, Windows creates a
'virtual desktop', which is a rectangle surface that extends across all
monitors.

The primary monitor always starts with the top-left corner at coordinate
(0,0).  If you have any other monitors which extend to the left or
above the primary monitor, the virtual desktop's top-left corner will
actually have coordinates which are negative.

This creates an issue in Cairo with the desktop DC, i.e. when you use a
DC from the function GetDC(NULL).  The same thing can occur with other
third party libraries like GTK, when you access the Cairo surface from
the root window.

Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=100793
Reviewed-by: Bryce Harrington <bryce@osg.samsung.com>
This commit is contained in:
Eric Hoffman 2017-04-26 04:22:25 +00:00 committed by Bryce Harrington
parent a8571a3030
commit 4d07b57c16
2 changed files with 56 additions and 4 deletions

View file

@ -325,6 +325,8 @@ _cairo_win32_display_surface_create_for_dc (HDC original_dc,
surface->win32.extents.y = 0;
surface->win32.extents.width = width;
surface->win32.extents.height = height;
surface->win32.x_ofs = 0;
surface->win32.y_ofs = 0;
surface->initial_clip_rgn = NULL;
surface->had_simple_clip = FALSE;
@ -466,7 +468,8 @@ _cairo_win32_display_surface_map_to_image (void *abstract_sur
surface->win32.extents.width,
surface->win32.extents.height,
surface->win32.dc,
surface->win32.extents.x, surface->win32.extents.y,
surface->win32.extents.x + surface->win32.x_ofs, /* Handling multi-monitor... */
surface->win32.extents.y + surface->win32.y_ofs, /* ... setup on Win32 */
SRCCOPY)) {
status = _cairo_error (CAIRO_STATUS_DEVICE_ERROR);
goto err;
@ -544,11 +547,12 @@ _cairo_win32_display_surface_flush (void *abstract_surface, unsigned flags)
if (damage->status) {
if (!BitBlt (surface->win32.dc,
0, 0,
surface->win32.extents.x + surface->win32.x_ofs, /* Handling multi-monitor... */
surface->win32.extents.y + surface->win32.y_ofs, /* ... setup on Win32 */
surface->win32.extents.width,
surface->win32.extents.height,
fallback->win32.dc,
0, 0,
surface->win32.extents.x, surface->win32.extents.y,
SRCCOPY))
status = _cairo_win32_print_gdi_error (__FUNCTION__);
} else if (damage->region) {
@ -561,7 +565,8 @@ _cairo_win32_display_surface_flush (void *abstract_surface, unsigned flags)
rect.x, rect.y,
rect.width, rect.height));
if (!BitBlt (surface->win32.dc,
rect.x, rect.y,
rect.x + surface->win32.x_ofs, /* Handling multi-monitor... */
rect.y + surface->win32.y_ofs, /* ... setup on Win32 */
rect.width, rect.height,
fallback->win32.dc,
rect.x, rect.y,
@ -628,6 +633,35 @@ _cairo_win32_save_initial_clip (HDC hdc, cairo_win32_display_surface_t *surface)
surface->win32.extents.width = rect.right - rect.left;
surface->win32.extents.height = rect.bottom - rect.top;
/* On multi-monitor setup, under Windows, the primary monitor always
* have origin (0,0). Any monitors that extends to the left or above
* will have coordinates in the negative range. Take this into
* account, by forcing our Win32 surface to start at extent (0,0) and
* using a device offset. Cairo does not handle extents with negative
* offsets.
*/
surface->win32.x_ofs = 0;
surface->win32.y_ofs = 0;
if ((surface->win32.extents.x < 0) ||
(surface->win32.extents.y < 0)) {
/* Negative offsets occurs for (and ONLY for) the desktop DC (virtual
* desktop), when a monitor extend to the left or above the primary
* monitor.
*
* More info @ https://www.microsoft.com/msj/0697/monitor/monitor.aspx
*
* Note that any other DC, including memory DC created with
* CreateCompatibleDC(<virtual desktop DC>) will have extents in the
* positive range. This will be taken into account later when we perform
* raster operations between the DC (may have to perform offset
* translation).
*/
surface->win32.x_ofs = surface->win32.extents.x;
surface->win32.y_ofs = surface->win32.extents.y;
surface->win32.extents.x = 0;
surface->win32.extents.y = 0;
}
surface->initial_clip_rgn = NULL;
surface->had_simple_clip = FALSE;

View file

@ -100,6 +100,24 @@ typedef struct _cairo_win32_surface {
* that match bounds of the clipped region.
*/
cairo_rectangle_int_t extents;
/* Offset added to extents, used when the extents start with a negative
* offset, which occur on Windows for, and only for, desktop DC. This
* occurs when you have multiple monitors, and at least one monitor
* extends to the left, or above, the primaty monitor. The primary
* monitor on Windows always start with offset (0,0), and any other points
* to the left, or above, have negative offset. So the 'desktop DC' is
* in fact a 'virtual desktop' which can start with extents in the negative
* range.
*
* Why use new variables, and not the device transform? Simply because since
* the device transform functions are exposed, a lot of 3rd party libraries
* simply overwrite those, disregarding the prior content, instead of actually
* adding the offset. GTK for example simply reset the device transform of the
* desktop cairo surface to zero. So make some private member variables for
* this, which will not be fiddled with externally.
*/
int x_ofs, y_ofs;
} cairo_win32_surface_t;
#define to_win32_surface(S) ((cairo_win32_surface_t *)(S))