Signed-off-by: Zhiyi Zhang zzhang@codeweavers.com --- dlls/user32/tests/monitor.c | 36 +--- dlls/winex11.drv/settings.c | 382 +++++++++++++++++++++++++++++++----- dlls/winex11.drv/xrandr.c | 7 +- 3 files changed, 337 insertions(+), 88 deletions(-)
diff --git a/dlls/user32/tests/monitor.c b/dlls/user32/tests/monitor.c index 005627f28b0..0d32dbfeef9 100644 --- a/dlls/user32/tests/monitor.c +++ b/dlls/user32/tests/monitor.c @@ -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 diff --git a/dlls/winex11.drv/settings.c b/dlls/winex11.drv/settings.c index 02495756179..56f55e78b42 100644 --- a/dlls/winex11.drv/settings.c +++ b/dlls/winex11.drv/settings.c @@ -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, ®istry_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, ¤t_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, ¤t_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: diff --git a/dlls/winex11.drv/xrandr.c b/dlls/winex11.drv/xrandr.c index d111b960fa6..4fbb2e540dc 100644 --- a/dlls/winex11.drv/xrandr.c +++ b/dlls/winex11.drv/xrandr.c @@ -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;