winex11.drv: Support automatic display position adjustment.

Signed-off-by: Zhiyi Zhang <zzhang@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Zhiyi Zhang 2020-07-27 16:00:43 +08:00 committed by Alexandre Julliard
parent 00b64430e8
commit 4a24816313
3 changed files with 337 additions and 88 deletions

View file

@ -283,29 +283,9 @@ struct device_info
DEVMODEA original_mode;
};
static BOOL get_primary_adapter(CHAR *name)
{
DISPLAY_DEVICEA dd;
DWORD i;
dd.cb = sizeof(dd);
for (i = 0; EnumDisplayDevicesA(NULL, i, &dd, 0); ++i)
{
if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
{
lstrcpyA(name, dd.DeviceName);
return TRUE;
}
}
return FALSE;
}
#define expect_dm(a, b, c) _expect_dm(__LINE__, a, b, c)
static void _expect_dm(INT line, DEVMODEA expected, const CHAR *device, DWORD test)
{
CHAR primary_adapter[CCHDEVICENAME];
BOOL is_primary;
DEVMODEA dm;
BOOL ret;
@ -315,33 +295,23 @@ static void _expect_dm(INT line, DEVMODEA expected, const CHAR *device, DWORD te
ret = EnumDisplaySettingsA(device, ENUM_CURRENT_SETTINGS, &dm);
ok_(__FILE__, line)(ret, "Device %s test %d EnumDisplaySettingsA failed, error %#x\n", device, test, GetLastError());
ok(get_primary_adapter(primary_adapter), "Failed to get primary adapter name.\n");
is_primary = !lstrcmpA(primary_adapter, device);
ok_(__FILE__, line)((dm.dmFields & expected.dmFields) == expected.dmFields,
"Device %s test %d expect dmFields to contain %#x, got %#x\n", device, test, expected.dmFields, dm.dmFields);
/* Wine doesn't support changing color depth yet */
todo_wine_if(expected.dmBitsPerPel != 32 && expected.dmBitsPerPel != 24)
ok_(__FILE__, line)(dm.dmBitsPerPel == expected.dmBitsPerPel, "Device %s test %d expect dmBitsPerPel %d, got %d\n",
device, test, expected.dmBitsPerPel, dm.dmBitsPerPel);
/* Wine currently reports primary monitor settings for non-primary monitors */
todo_wine_if(!is_primary && dm.dmPelsWidth != expected.dmPelsWidth)
ok_(__FILE__, line)(dm.dmPelsWidth == expected.dmPelsWidth, "Device %s test %d expect dmPelsWidth %d, got %d\n",
device, test, expected.dmPelsWidth, dm.dmPelsWidth);
todo_wine_if(!is_primary && dm.dmPelsHeight != expected.dmPelsHeight)
ok_(__FILE__, line)(dm.dmPelsHeight == expected.dmPelsHeight, "Device %s test %d expect dmPelsHeight %d, got %d\n",
device, test, expected.dmPelsHeight, dm.dmPelsHeight);
todo_wine_if(!is_primary && dm.dmPosition.x != expected.dmPosition.x)
ok_(__FILE__, line)(dm.dmPosition.x == expected.dmPosition.x, "Device %s test %d expect dmPosition.x %d, got %d\n",
device, test, expected.dmPosition.x, dm.dmPosition.x);
todo_wine_if(!is_primary && dm.dmPosition.y != expected.dmPosition.y)
ok_(__FILE__, line)(dm.dmPosition.y == expected.dmPosition.y, "Device %s test %d expect dmPosition.y %d, got %d\n",
device, test, expected.dmPosition.y, dm.dmPosition.y);
todo_wine_if(!is_primary && dm.dmDisplayFrequency != expected.dmDisplayFrequency)
ok_(__FILE__, line)(dm.dmDisplayFrequency == expected.dmDisplayFrequency,
"Device %s test %d expect dmDisplayFrequency %d, got %d\n", device, test, expected.dmDisplayFrequency,
dm.dmDisplayFrequency);
todo_wine_if(!is_primary && dm.dmDisplayOrientation != expected.dmDisplayOrientation)
ok_(__FILE__, line)(dm.dmDisplayOrientation == expected.dmDisplayOrientation,
"Device %s test %d expect dmDisplayOrientation %d, got %d\n", device, test, expected.dmDisplayOrientation,
dm.dmDisplayOrientation);
@ -778,7 +748,7 @@ static void test_ChangeDisplaySettingsEx(void)
if (res)
{
/* The secondary adapter should be to the right of the primary adapter */
todo_wine ok(dm2.dmPosition.x == dm.dmPosition.x + dm.dmPelsWidth,
ok(dm2.dmPosition.x == dm.dmPosition.x + dm.dmPelsWidth,
"Expected dm2.dmPosition.x %d, got %d.\n", dm.dmPosition.x + dm.dmPelsWidth,
dm2.dmPosition.x);
ok(dm2.dmPosition.y == dm.dmPosition.y, "Expected dm2.dmPosition.y %d, got %d.\n",
@ -795,7 +765,7 @@ static void test_ChangeDisplaySettingsEx(void)
dm2.dmSize = sizeof(dm2);
res = EnumDisplaySettingsA(devices[1].name, ENUM_CURRENT_SETTINGS, &dm2);
ok(res, "EnumDisplaySettingsA %s failed, error %#x\n", devices[1].name, GetLastError());
todo_wine ok((dm2.dmPosition.x == dm.dmPosition.x - dm2.dmPelsWidth),
ok(dm2.dmPosition.x == dm.dmPosition.x - dm2.dmPelsWidth,
"Expected dmPosition.x %d, got %d.\n", dm.dmPosition.x - dm2.dmPelsWidth,
dm2.dmPosition.x);
@ -889,7 +859,7 @@ static void test_ChangeDisplaySettingsEx(void)
dm2.dmSize = sizeof(dm2);
res = EnumDisplaySettingsA(devices[1].name, ENUM_CURRENT_SETTINGS, &dm2);
ok(res, "EnumDisplaySettingsA %s failed, error %#x\n", devices[1].name, GetLastError());
todo_wine ok(dm2.dmPosition.x == dm.dmPelsWidth, "Expect dmPosition.x %d, got %d\n",
ok(dm2.dmPosition.x == dm.dmPelsWidth, "Expect dmPosition.x %d, got %d\n",
dm.dmPelsWidth, dm2.dmPosition.x);
}
else

View file

@ -36,6 +36,15 @@
WINE_DEFAULT_DEBUG_CHANNEL(x11settings);
struct x11drv_display_setting
{
ULONG_PTR id;
BOOL placed;
RECT new_rect;
RECT desired_rect;
DEVMODEW desired_mode;
};
static struct x11drv_mode_info *dd_modes = NULL;
static unsigned int dd_mode_count = 0;
static unsigned int dd_max_modes = 0;
@ -534,6 +543,305 @@ static DEVMODEW *get_full_mode(ULONG_PTR id, const DEVMODEW *dev_mode)
return full_mode;
}
static LONG get_display_settings(struct x11drv_display_setting **new_displays,
INT *new_display_count, const WCHAR *dev_name, DEVMODEW *dev_mode)
{
struct x11drv_display_setting *displays;
DEVMODEW registry_mode, current_mode;
INT display_idx, display_count = 0;
DISPLAY_DEVICEW display_device;
LONG ret = DISP_CHANGE_FAILED;
display_device.cb = sizeof(display_device);
for (display_idx = 0; EnumDisplayDevicesW(NULL, display_idx, &display_device, 0); ++display_idx)
++display_count;
displays = heap_calloc(display_count, sizeof(*displays));
if (!displays)
goto done;
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
if (!EnumDisplayDevicesW(NULL, display_idx, &display_device, 0))
goto done;
if (!handler.get_id(display_device.DeviceName, &displays[display_idx].id))
{
ret = DISP_CHANGE_BADPARAM;
goto done;
}
if (!dev_mode)
{
registry_mode.dmSize = sizeof(registry_mode);
if (!EnumDisplaySettingsExW(display_device.DeviceName, ENUM_REGISTRY_SETTINGS, &registry_mode, 0))
goto done;
displays[display_idx].desired_mode = registry_mode;
}
else if (!lstrcmpiW(dev_name, display_device.DeviceName))
{
displays[display_idx].desired_mode = *dev_mode;
if (!(dev_mode->dmFields & DM_POSITION))
{
current_mode.dmSize = sizeof(current_mode);
if (!EnumDisplaySettingsExW(display_device.DeviceName, ENUM_CURRENT_SETTINGS, &current_mode, 0))
goto done;
displays[display_idx].desired_mode.dmFields |= DM_POSITION;
displays[display_idx].desired_mode.u1.s2.dmPosition = current_mode.u1.s2.dmPosition;
}
}
else
{
current_mode.dmSize = sizeof(current_mode);
if (!EnumDisplaySettingsExW(display_device.DeviceName, ENUM_CURRENT_SETTINGS, &current_mode, 0))
goto done;
displays[display_idx].desired_mode = current_mode;
}
SetRect(&displays[display_idx].desired_rect,
displays[display_idx].desired_mode.u1.s2.dmPosition.x,
displays[display_idx].desired_mode.u1.s2.dmPosition.y,
displays[display_idx].desired_mode.u1.s2.dmPosition.x + displays[display_idx].desired_mode.dmPelsWidth,
displays[display_idx].desired_mode.u1.s2.dmPosition.y + displays[display_idx].desired_mode.dmPelsHeight);
lstrcpyW(displays[display_idx].desired_mode.dmDeviceName, display_device.DeviceName);
}
*new_displays = displays;
*new_display_count = display_count;
return DISP_CHANGE_SUCCESSFUL;
done:
heap_free(displays);
return ret;
}
static INT offset_length(POINT offset)
{
return offset.x * offset.x + offset.y * offset.y;
}
/* Check if a rect overlaps with placed display rects */
static BOOL overlap_placed_displays(const RECT *rect, const struct x11drv_display_setting *displays, INT display_count)
{
INT display_idx;
RECT intersect;
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
if (displays[display_idx].placed &&
IntersectRect(&intersect, &displays[display_idx].new_rect, rect))
return TRUE;
}
return FALSE;
}
/* Get the offset with minimum length to place a display next to the placed displays with no spacing and overlaps */
static POINT get_placement_offset(const struct x11drv_display_setting *displays, INT display_count, INT placing_idx)
{
POINT points[8], left_top, offset, min_offset = {0, 0};
INT display_idx, point_idx, point_count, vertex_idx;
BOOL has_placed = FALSE, first = TRUE;
INT width, height;
RECT rect;
/* If the display to be placed is detached, no offset is needed to place it */
if (IsRectEmpty(&displays[placing_idx].desired_rect))
return min_offset;
/* If there is no placed and attached display, place this display as it is */
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
if (displays[display_idx].placed && !IsRectEmpty(&displays[display_idx].new_rect))
{
has_placed = TRUE;
break;
}
}
if (!has_placed)
return min_offset;
/* Try to place this display with each of its four vertices at every vertex of the placed
* displays and see which combination has the minimum offset length */
width = displays[placing_idx].desired_rect.right - displays[placing_idx].desired_rect.left;
height = displays[placing_idx].desired_rect.bottom - displays[placing_idx].desired_rect.top;
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
if (!displays[display_idx].placed || IsRectEmpty(&displays[display_idx].new_rect))
continue;
/* Get four vertices of the placed display rectangle */
points[0].x = displays[display_idx].new_rect.left;
points[0].y = displays[display_idx].new_rect.top;
points[1].x = displays[display_idx].new_rect.left;
points[1].y = displays[display_idx].new_rect.bottom;
points[2].x = displays[display_idx].new_rect.right;
points[2].y = displays[display_idx].new_rect.top;
points[3].x = displays[display_idx].new_rect.right;
points[3].y = displays[display_idx].new_rect.bottom;
point_count = 4;
/* Intersected points when moving the display to be placed horizontally */
if (displays[placing_idx].desired_rect.bottom >= displays[display_idx].new_rect.top &&
displays[placing_idx].desired_rect.top <= displays[display_idx].new_rect.bottom)
{
points[point_count].x = displays[display_idx].new_rect.left;
points[point_count++].y = displays[placing_idx].desired_rect.top;
points[point_count].x = displays[display_idx].new_rect.right;
points[point_count++].y = displays[placing_idx].desired_rect.top;
}
/* Intersected points when moving the display to be placed vertically */
if (displays[placing_idx].desired_rect.left <= displays[display_idx].new_rect.right &&
displays[placing_idx].desired_rect.right >= displays[display_idx].new_rect.left)
{
points[point_count].x = displays[placing_idx].desired_rect.left;
points[point_count++].y = displays[display_idx].new_rect.top;
points[point_count].x = displays[placing_idx].desired_rect.left;
points[point_count++].y = displays[display_idx].new_rect.bottom;
}
/* Try moving each vertex of the display rectangle to each points */
for (point_idx = 0; point_idx < point_count; ++point_idx)
{
for (vertex_idx = 0; vertex_idx < 4; ++vertex_idx)
{
switch (vertex_idx)
{
/* Move the bottom right vertex to the point */
case 0:
left_top.x = points[point_idx].x - width;
left_top.y = points[point_idx].y - height;
break;
/* Move the bottom left vertex to the point */
case 1:
left_top.x = points[point_idx].x;
left_top.y = points[point_idx].y - height;
break;
/* Move the top right vertex to the point */
case 2:
left_top.x = points[point_idx].x - width;
left_top.y = points[point_idx].y;
break;
/* Move the top left vertex to the point */
case 3:
left_top.x = points[point_idx].x;
left_top.y = points[point_idx].y;
break;
}
offset.x = left_top.x - displays[placing_idx].desired_rect.left;
offset.y = left_top.y - displays[placing_idx].desired_rect.top;
rect = displays[placing_idx].desired_rect;
OffsetRect(&rect, offset.x, offset.y);
if (!overlap_placed_displays(&rect, displays, display_count))
{
if (first)
{
min_offset = offset;
first = FALSE;
continue;
}
if (offset_length(offset) < offset_length(min_offset))
min_offset = offset;
}
}
}
}
return min_offset;
}
static void place_all_displays(struct x11drv_display_setting *displays, INT display_count)
{
INT left_most = INT_MAX, top_most = INT_MAX;
INT placing_idx, display_idx;
POINT min_offset, offset;
/* Place all displays with no extra space between them and no overlapping */
while (1)
{
/* Place the unplaced display with the minimum offset length first */
placing_idx = -1;
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
if (displays[display_idx].placed)
continue;
offset = get_placement_offset(displays, display_count, display_idx);
if (placing_idx == -1 || offset_length(offset) < offset_length(min_offset))
{
min_offset = offset;
placing_idx = display_idx;
}
}
/* If all displays are placed */
if (placing_idx == -1)
break;
displays[placing_idx].new_rect = displays[placing_idx].desired_rect;
OffsetRect(&displays[placing_idx].new_rect, min_offset.x, min_offset.y);
displays[placing_idx].placed = TRUE;
}
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
displays[display_idx].desired_mode.u1.s2.dmPosition.x = displays[display_idx].new_rect.left;
displays[display_idx].desired_mode.u1.s2.dmPosition.y = displays[display_idx].new_rect.top;
left_most = min(left_most, displays[display_idx].new_rect.left);
top_most = min(top_most, displays[display_idx].new_rect.top);
}
/* Convert virtual screen coordinates to root coordinates */
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
displays[display_idx].desired_mode.u1.s2.dmPosition.x -= left_most;
displays[display_idx].desired_mode.u1.s2.dmPosition.y -= top_most;
}
}
static LONG apply_display_settings(struct x11drv_display_setting *displays, INT display_count)
{
DEVMODEW *full_mode;
INT display_idx;
LONG ret;
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
if (is_detached_mode(&displays[display_idx].desired_mode))
{
FIXME("Detaching %s is currently unsupported.\n",
wine_dbgstr_w(displays[display_idx].desired_mode.dmDeviceName));
continue;
}
full_mode = get_full_mode(displays[display_idx].id, &displays[display_idx].desired_mode);
if (!full_mode)
return DISP_CHANGE_BADMODE;
full_mode->dmFields |= DM_POSITION;
full_mode->u1.s2.dmPosition = displays[display_idx].desired_mode.u1.s2.dmPosition;
TRACE("handler:%s changing %s to position:(%d,%d) resolution:%ux%u frequency:%uHz "
"depth:%ubits orientation:%#x.\n", handler.name,
wine_dbgstr_w(displays[display_idx].desired_mode.dmDeviceName),
full_mode->u1.s2.dmPosition.x, full_mode->u1.s2.dmPosition.y, full_mode->dmPelsWidth,
full_mode->dmPelsHeight, full_mode->dmDisplayFrequency, full_mode->dmBitsPerPel,
full_mode->u1.s2.dmDisplayOrientation);
ret = handler.set_current_mode(displays[display_idx].id, full_mode);
heap_free(full_mode);
if (ret != DISP_CHANGE_SUCCESSFUL)
return ret;
}
return DISP_CHANGE_SUCCESSFUL;
}
/***********************************************************************
* ChangeDisplaySettingsEx (X11DRV.@)
*
@ -541,10 +849,11 @@ static DEVMODEW *get_full_mode(ULONG_PTR id, const DEVMODEW *dev_mode)
LONG CDECL X11DRV_ChangeDisplaySettingsEx( LPCWSTR devname, LPDEVMODEW devmode,
HWND hwnd, DWORD flags, LPVOID lpvoid )
{
struct x11drv_display_setting *displays;
WCHAR primary_adapter[CCHDEVICENAME];
char bpp_buffer[16], freq_buffer[18];
DEVMODEW default_mode, *full_mode;
ULONG_PTR id;
INT display_idx, display_count;
DEVMODEW default_mode;
LONG ret;
DWORD i;
@ -552,70 +861,39 @@ LONG CDECL X11DRV_ChangeDisplaySettingsEx( LPCWSTR devname, LPDEVMODEW devmode,
if (!handler.name)
goto old_interface;
if (!get_primary_adapter(primary_adapter))
return DISP_CHANGE_FAILED;
ret = get_display_settings(&displays, &display_count, devname, devmode);
if (ret != DISP_CHANGE_SUCCESSFUL)
return ret;
if (!devname && !devmode)
if (flags & CDS_UPDATEREGISTRY && devname && devmode)
{
default_mode.dmSize = sizeof(default_mode);
if (!EnumDisplaySettingsExW(primary_adapter, ENUM_REGISTRY_SETTINGS, &default_mode, 0))
for (display_idx = 0; display_idx < display_count; ++display_idx)
{
ERR("Default mode not found for %s!\n", wine_dbgstr_w(primary_adapter));
return DISP_CHANGE_BADMODE;
if (!lstrcmpiW(displays[display_idx].desired_mode.dmDeviceName, devname))
{
if (!write_registry_settings(devname, &displays[display_idx].desired_mode))
{
ERR("Failed to write %s display settings to registry.\n", wine_dbgstr_w(devname));
heap_free(displays);
return DISP_CHANGE_NOTUPDATED;
}
break;
}
}
devname = primary_adapter;
devmode = &default_mode;
}
if (!handler.get_id(devname, &id))
{
ERR("Failed to get %s device id.\n", wine_dbgstr_w(devname));
return DISP_CHANGE_BADPARAM;
}
if (is_detached_mode(devmode))
{
FIXME("Detaching adapters is currently unsupported.\n");
return DISP_CHANGE_SUCCESSFUL;
}
if (!(full_mode = get_full_mode(id, devmode)))
{
ERR("Failed to find a valid mode.\n");
return DISP_CHANGE_BADMODE;
}
if (flags & CDS_UPDATEREGISTRY && !write_registry_settings(devname, full_mode))
{
ERR("Failed to write %s display settings to registry.\n", wine_dbgstr_w(devname));
heap_free(full_mode);
return DISP_CHANGE_NOTUPDATED;
}
if (lstrcmpiW(primary_adapter, devname))
{
FIXME("Changing non-primary adapter %s settings is currently unsupported.\n",
wine_dbgstr_w(devname));
heap_free(full_mode);
return DISP_CHANGE_SUCCESSFUL;
}
if (flags & (CDS_TEST | CDS_NORESET))
{
heap_free(full_mode);
heap_free(displays);
return DISP_CHANGE_SUCCESSFUL;
}
TRACE("handler:%s device:%s position:(%d,%d) resolution:%ux%u frequency:%uHz depth:%ubits "
"orientation:%#x.\n", handler.name, wine_dbgstr_w(devname), full_mode->u1.s2.dmPosition.x,
full_mode->u1.s2.dmPosition.y, full_mode->dmPelsWidth, full_mode->dmPelsHeight,
full_mode->dmDisplayFrequency, full_mode->dmBitsPerPel, full_mode->u1.s2.dmDisplayOrientation);
place_all_displays(displays, display_count);
ret = handler.set_current_mode(id, full_mode);
ret = apply_display_settings(displays, display_count);
if (ret == DISP_CHANGE_SUCCESSFUL)
X11DRV_DisplayDevices_Update(TRUE);
heap_free(full_mode);
heap_free(displays);
return ret;
old_interface:

View file

@ -1367,12 +1367,13 @@ static LONG xrandr14_set_current_mode( ULONG_PTR id, DEVMODEW *mode )
goto done;
get_screen_size( screen_resources, &screen_width, &screen_height );
screen_width = max( screen_width, crtc_info->x + mode->dmPelsWidth );
screen_height = max( screen_height, crtc_info->y + mode->dmPelsHeight );
screen_width = max( screen_width, mode->u1.s2.dmPosition.x + mode->dmPelsWidth );
screen_height = max( screen_height, mode->u1.s2.dmPosition.y + mode->dmPelsHeight );
set_screen_size( screen_width, screen_height );
status = pXRRSetCrtcConfig( gdi_display, screen_resources, crtc, CurrentTime,
crtc_info->x, crtc_info->y, rrmode, rotation, outputs, output_count );
mode->u1.s2.dmPosition.x, mode->u1.s2.dmPosition.y, rrmode,
rotation, outputs, output_count );
if (status == RRSetConfigSuccess)
ret = DISP_CHANGE_SUCCESSFUL;