wined3d: Use wined3d_uint32_compare() in compare_sig().
[wine.git] / dlls / winex11.drv / settings.c
blobcea7ebbc6b7a6c11a07529d251df42a2b1b1fa9a
1 /*
2 * Wine X11drv display settings functions
4 * Copyright 2003 Alexander James Pasadyn
5 * Copyright 2020 Zhiyi Zhang for CodeWeavers
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "config.h"
23 #include <stdlib.h>
25 #define NONAMELESSUNION
26 #define NONAMELESSSTRUCT
28 #include "x11drv.h"
30 #include "windef.h"
31 #include "winreg.h"
32 #include "wingdi.h"
33 #include "wine/debug.h"
34 #include "wine/heap.h"
35 #include "wine/unicode.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(x11settings);
39 struct x11drv_display_setting
41 ULONG_PTR id;
42 BOOL placed;
43 RECT new_rect;
44 RECT desired_rect;
45 DEVMODEW desired_mode;
48 struct x11drv_display_depth
50 struct list entry;
51 ULONG_PTR display_id;
52 DWORD depth;
55 /* Display device emulated depth list, protected by modes_section */
56 static struct list x11drv_display_depth_list = LIST_INIT(x11drv_display_depth_list);
58 /* All Windows drivers seen so far either support 32 bit depths, or 24 bit depths, but never both. So if we have
59 * a 32 bit framebuffer, report 32 bit bpps, otherwise 24 bit ones.
61 static const unsigned int depths_24[] = {8, 16, 24};
62 static const unsigned int depths_32[] = {8, 16, 32};
63 const unsigned int *depths;
65 static struct x11drv_settings_handler handler;
67 /* Cached display modes for a device, protected by modes_section */
68 static WCHAR cached_device_name[CCHDEVICENAME];
69 static DWORD cached_flags;
70 static DEVMODEW *cached_modes;
71 static UINT cached_mode_count;
73 static CRITICAL_SECTION modes_section;
74 static CRITICAL_SECTION_DEBUG modes_critsect_debug =
76 0, 0, &modes_section,
77 {&modes_critsect_debug.ProcessLocksList, &modes_critsect_debug.ProcessLocksList},
78 0, 0, {(DWORD_PTR)(__FILE__ ": modes_section")}
80 static CRITICAL_SECTION modes_section = {&modes_critsect_debug, -1, 0, 0, 0, 0};
82 void X11DRV_Settings_SetHandler(const struct x11drv_settings_handler *new_handler)
84 if (new_handler->priority > handler.priority)
86 handler = *new_handler;
87 TRACE("Display settings are now handled by: %s.\n", handler.name);
91 /***********************************************************************
92 * Default handlers if resolution switching is not enabled
95 static BOOL nores_get_id(const WCHAR *device_name, ULONG_PTR *id)
97 WCHAR primary_adapter[CCHDEVICENAME];
99 if (!get_primary_adapter( primary_adapter ))
100 return FALSE;
102 *id = !lstrcmpiW( device_name, primary_adapter ) ? 1 : 0;
103 return TRUE;
106 static BOOL nores_get_modes(ULONG_PTR id, DWORD flags, DEVMODEW **new_modes, UINT *mode_count)
108 RECT primary = get_host_primary_monitor_rect();
109 DEVMODEW *modes;
111 modes = heap_calloc(1, sizeof(*modes));
112 if (!modes)
114 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
115 return FALSE;
118 modes[0].dmSize = sizeof(*modes);
119 modes[0].dmDriverExtra = 0;
120 modes[0].dmFields = DM_DISPLAYORIENTATION | DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT |
121 DM_DISPLAYFLAGS | DM_DISPLAYFREQUENCY;
122 modes[0].u1.s2.dmDisplayOrientation = DMDO_DEFAULT;
123 modes[0].dmBitsPerPel = screen_bpp;
124 modes[0].dmPelsWidth = primary.right;
125 modes[0].dmPelsHeight = primary.bottom;
126 modes[0].u2.dmDisplayFlags = 0;
127 modes[0].dmDisplayFrequency = 60;
129 *new_modes = modes;
130 *mode_count = 1;
131 return TRUE;
134 static void nores_free_modes(DEVMODEW *modes)
136 heap_free(modes);
139 static BOOL nores_get_current_mode(ULONG_PTR id, DEVMODEW *mode)
141 RECT primary = get_host_primary_monitor_rect();
143 mode->dmFields = DM_DISPLAYORIENTATION | DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT |
144 DM_DISPLAYFLAGS | DM_DISPLAYFREQUENCY | DM_POSITION;
145 mode->u1.s2.dmDisplayOrientation = DMDO_DEFAULT;
146 mode->u2.dmDisplayFlags = 0;
147 mode->u1.s2.dmPosition.x = 0;
148 mode->u1.s2.dmPosition.y = 0;
150 if (id != 1)
152 FIXME("Non-primary adapters are unsupported.\n");
153 mode->dmBitsPerPel = 0;
154 mode->dmPelsWidth = 0;
155 mode->dmPelsHeight = 0;
156 mode->dmDisplayFrequency = 0;
157 return TRUE;
160 mode->dmBitsPerPel = screen_bpp;
161 mode->dmPelsWidth = primary.right;
162 mode->dmPelsHeight = primary.bottom;
163 mode->dmDisplayFrequency = 60;
164 return TRUE;
167 static LONG nores_set_current_mode(ULONG_PTR id, DEVMODEW *mode)
169 WARN("NoRes settings handler, ignoring mode change request.\n");
170 return DISP_CHANGE_SUCCESSFUL;
173 /* default handler only gets the current X desktop resolution */
174 void X11DRV_Settings_Init(void)
176 struct x11drv_settings_handler nores_handler;
178 depths = screen_bpp == 32 ? depths_32 : depths_24;
180 nores_handler.name = "NoRes";
181 nores_handler.priority = 1;
182 nores_handler.get_id = nores_get_id;
183 nores_handler.get_modes = nores_get_modes;
184 nores_handler.free_modes = nores_free_modes;
185 nores_handler.get_current_mode = nores_get_current_mode;
186 nores_handler.set_current_mode = nores_set_current_mode;
187 X11DRV_Settings_SetHandler(&nores_handler);
190 /* Initialize registry display settings when new display devices are added */
191 void init_registry_display_settings(void)
193 DEVMODEW dm = {.dmSize = sizeof(dm)};
194 DISPLAY_DEVICEW dd = {sizeof(dd)};
195 DWORD i = 0;
196 LONG ret;
198 while (EnumDisplayDevicesW(NULL, i++, &dd, 0))
200 /* Skip if the device already has registry display settings */
201 if (EnumDisplaySettingsExW(dd.DeviceName, ENUM_REGISTRY_SETTINGS, &dm, 0))
202 continue;
204 if (!EnumDisplaySettingsExW(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm, 0))
206 ERR("Failed to query current display settings for %s.\n", wine_dbgstr_w(dd.DeviceName));
207 continue;
210 TRACE("Device %s current display mode %ux%u %ubits %uHz at %d,%d.\n",
211 wine_dbgstr_w(dd.DeviceName), dm.dmPelsWidth, dm.dmPelsHeight, dm.dmBitsPerPel,
212 dm.dmDisplayFrequency, dm.u1.s2.dmPosition.x, dm.u1.s2.dmPosition.y);
214 ret = ChangeDisplaySettingsExW(dd.DeviceName, &dm, NULL,
215 CDS_GLOBAL | CDS_NORESET | CDS_UPDATEREGISTRY, NULL);
216 if (ret != DISP_CHANGE_SUCCESSFUL)
217 ERR("Failed to save registry display settings for %s, returned %d.\n",
218 wine_dbgstr_w(dd.DeviceName), ret);
222 static BOOL get_display_device_reg_key(const WCHAR *device_name, WCHAR *key, unsigned len)
224 static const WCHAR display[] = {'\\','\\','.','\\','D','I','S','P','L','A','Y'};
225 static const WCHAR video_value_fmt[] = {'\\','D','e','v','i','c','e','\\',
226 'V','i','d','e','o','%','d',0};
227 static const WCHAR video_key[] = {'H','A','R','D','W','A','R','E','\\',
228 'D','E','V','I','C','E','M','A','P','\\',
229 'V','I','D','E','O','\\',0};
230 WCHAR value_name[MAX_PATH], buffer[MAX_PATH], *end_ptr;
231 DWORD adapter_index, size;
233 /* Device name has to be \\.\DISPLAY%d */
234 if (strncmpiW(device_name, display, ARRAY_SIZE(display)))
235 return FALSE;
237 /* Parse \\.\DISPLAY* */
238 adapter_index = strtolW(device_name + ARRAY_SIZE(display), &end_ptr, 10) - 1;
239 if (*end_ptr)
240 return FALSE;
242 /* Open \Device\Video* in HKLM\HARDWARE\DEVICEMAP\VIDEO\ */
243 sprintfW(value_name, video_value_fmt, adapter_index);
244 size = sizeof(buffer);
245 if (RegGetValueW(HKEY_LOCAL_MACHINE, video_key, value_name, RRF_RT_REG_SZ, NULL, buffer, &size))
246 return FALSE;
248 if (len < lstrlenW(buffer + 18) + 1)
249 return FALSE;
251 /* Skip \Registry\Machine\ prefix */
252 lstrcpyW(key, buffer + 18);
253 TRACE("display device %s registry settings key %s.\n", wine_dbgstr_w(device_name), wine_dbgstr_w(key));
254 return TRUE;
257 static BOOL read_registry_settings(const WCHAR *device_name, DEVMODEW *dm)
259 WCHAR wine_x11_reg_key[MAX_PATH];
260 HANDLE mutex;
261 HKEY hkey;
262 DWORD type, size;
263 BOOL ret = TRUE;
265 dm->dmFields = 0;
267 mutex = get_display_device_init_mutex();
268 if (!get_display_device_reg_key(device_name, wine_x11_reg_key, ARRAY_SIZE(wine_x11_reg_key)))
270 release_display_device_init_mutex(mutex);
271 return FALSE;
274 if (RegOpenKeyExW(HKEY_CURRENT_CONFIG, wine_x11_reg_key, 0, KEY_READ, &hkey))
276 release_display_device_init_mutex(mutex);
277 return FALSE;
280 #define query_value(name, data) \
281 size = sizeof(DWORD); \
282 if (RegQueryValueExA(hkey, name, 0, &type, (LPBYTE)(data), &size) || \
283 type != REG_DWORD || size != sizeof(DWORD)) \
284 ret = FALSE
286 query_value("DefaultSettings.BitsPerPel", &dm->dmBitsPerPel);
287 dm->dmFields |= DM_BITSPERPEL;
288 query_value("DefaultSettings.XResolution", &dm->dmPelsWidth);
289 dm->dmFields |= DM_PELSWIDTH;
290 query_value("DefaultSettings.YResolution", &dm->dmPelsHeight);
291 dm->dmFields |= DM_PELSHEIGHT;
292 query_value("DefaultSettings.VRefresh", &dm->dmDisplayFrequency);
293 dm->dmFields |= DM_DISPLAYFREQUENCY;
294 query_value("DefaultSettings.Flags", &dm->u2.dmDisplayFlags);
295 dm->dmFields |= DM_DISPLAYFLAGS;
296 query_value("DefaultSettings.XPanning", &dm->u1.s2.dmPosition.x);
297 query_value("DefaultSettings.YPanning", &dm->u1.s2.dmPosition.y);
298 dm->dmFields |= DM_POSITION;
299 query_value("DefaultSettings.Orientation", &dm->u1.s2.dmDisplayOrientation);
300 dm->dmFields |= DM_DISPLAYORIENTATION;
301 query_value("DefaultSettings.FixedOutput", &dm->u1.s2.dmDisplayFixedOutput);
303 #undef query_value
305 RegCloseKey(hkey);
306 release_display_device_init_mutex(mutex);
307 return ret;
310 static BOOL write_registry_settings(const WCHAR *device_name, const DEVMODEW *dm)
312 WCHAR wine_x11_reg_key[MAX_PATH];
313 HANDLE mutex;
314 HKEY hkey;
315 BOOL ret = TRUE;
317 mutex = get_display_device_init_mutex();
318 if (!get_display_device_reg_key(device_name, wine_x11_reg_key, ARRAY_SIZE(wine_x11_reg_key)))
320 release_display_device_init_mutex(mutex);
321 return FALSE;
324 if (RegCreateKeyExW(HKEY_CURRENT_CONFIG, wine_x11_reg_key, 0, NULL,
325 REG_OPTION_VOLATILE, KEY_WRITE, NULL, &hkey, NULL))
327 release_display_device_init_mutex(mutex);
328 return FALSE;
331 #define set_value(name, data) \
332 if (RegSetValueExA(hkey, name, 0, REG_DWORD, (const BYTE*)(data), sizeof(DWORD))) \
333 ret = FALSE
335 set_value("DefaultSettings.BitsPerPel", &dm->dmBitsPerPel);
336 set_value("DefaultSettings.XResolution", &dm->dmPelsWidth);
337 set_value("DefaultSettings.YResolution", &dm->dmPelsHeight);
338 set_value("DefaultSettings.VRefresh", &dm->dmDisplayFrequency);
339 set_value("DefaultSettings.Flags", &dm->u2.dmDisplayFlags);
340 set_value("DefaultSettings.XPanning", &dm->u1.s2.dmPosition.x);
341 set_value("DefaultSettings.YPanning", &dm->u1.s2.dmPosition.y);
342 set_value("DefaultSettings.Orientation", &dm->u1.s2.dmDisplayOrientation);
343 set_value("DefaultSettings.FixedOutput", &dm->u1.s2.dmDisplayFixedOutput);
345 #undef set_value
347 RegCloseKey(hkey);
348 release_display_device_init_mutex(mutex);
349 return ret;
352 BOOL get_primary_adapter(WCHAR *name)
354 DISPLAY_DEVICEW dd;
355 DWORD i;
357 dd.cb = sizeof(dd);
358 for (i = 0; EnumDisplayDevicesW(NULL, i, &dd, 0); ++i)
360 if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
362 lstrcpyW(name, dd.DeviceName);
363 return TRUE;
367 return FALSE;
370 static int mode_compare(const void *p1, const void *p2)
372 DWORD a_width, a_height, b_width, b_height;
373 const DEVMODEW *a = p1, *b = p2;
375 /* Use the width and height in landscape mode for comparison */
376 if (a->u1.s2.dmDisplayOrientation == DMDO_DEFAULT || a->u1.s2.dmDisplayOrientation == DMDO_180)
378 a_width = a->dmPelsWidth;
379 a_height = a->dmPelsHeight;
381 else
383 a_width = a->dmPelsHeight;
384 a_height = a->dmPelsWidth;
387 if (b->u1.s2.dmDisplayOrientation == DMDO_DEFAULT || b->u1.s2.dmDisplayOrientation == DMDO_180)
389 b_width = b->dmPelsWidth;
390 b_height = b->dmPelsHeight;
392 else
394 b_width = b->dmPelsHeight;
395 b_height = b->dmPelsWidth;
398 /* Depth in descending order */
399 if (a->dmBitsPerPel != b->dmBitsPerPel)
400 return b->dmBitsPerPel - a->dmBitsPerPel;
402 /* Width in ascending order */
403 if (a_width != b_width)
404 return a_width - b_width;
406 /* Height in ascending order */
407 if (a_height != b_height)
408 return a_height - b_height;
410 /* Frequency in descending order */
411 if (a->dmDisplayFrequency != b->dmDisplayFrequency)
412 return b->dmDisplayFrequency - a->dmDisplayFrequency;
414 /* Orientation in ascending order */
415 return a->u1.s2.dmDisplayOrientation - b->u1.s2.dmDisplayOrientation;
418 static void set_display_depth(ULONG_PTR display_id, DWORD depth)
420 struct x11drv_display_depth *display_depth;
422 EnterCriticalSection(&modes_section);
423 LIST_FOR_EACH_ENTRY(display_depth, &x11drv_display_depth_list, struct x11drv_display_depth, entry)
425 if (display_depth->display_id == display_id)
427 display_depth->depth = depth;
428 LeaveCriticalSection(&modes_section);
429 return;
433 display_depth = heap_alloc(sizeof(*display_depth));
434 if (!display_depth)
436 ERR("Failed to allocate memory.\n");
437 LeaveCriticalSection(&modes_section);
438 return;
441 display_depth->display_id = display_id;
442 display_depth->depth = depth;
443 list_add_head(&x11drv_display_depth_list, &display_depth->entry);
444 LeaveCriticalSection(&modes_section);
447 static DWORD get_display_depth(ULONG_PTR display_id)
449 struct x11drv_display_depth *display_depth;
450 DWORD depth;
452 EnterCriticalSection(&modes_section);
453 LIST_FOR_EACH_ENTRY(display_depth, &x11drv_display_depth_list, struct x11drv_display_depth, entry)
455 if (display_depth->display_id == display_id)
457 depth = display_depth->depth;
458 LeaveCriticalSection(&modes_section);
459 return depth;
462 LeaveCriticalSection(&modes_section);
463 return screen_bpp;
466 /***********************************************************************
467 * EnumDisplaySettingsEx (X11DRV.@)
470 BOOL CDECL X11DRV_EnumDisplaySettingsEx( LPCWSTR name, DWORD n, LPDEVMODEW devmode, DWORD flags)
472 static const WCHAR dev_name[CCHDEVICENAME] =
473 { 'W','i','n','e',' ','X','1','1',' ','d','r','i','v','e','r',0 };
474 DEVMODEW *modes;
475 UINT mode_count;
476 ULONG_PTR id;
478 if (n == ENUM_REGISTRY_SETTINGS)
480 if (!read_registry_settings(name, devmode))
482 ERR("Failed to get %s registry display settings.\n", wine_dbgstr_w(name));
483 return FALSE;
485 goto done;
488 if (n == ENUM_CURRENT_SETTINGS)
490 if (!handler.get_id(name, &id) || !handler.get_current_mode(id, devmode))
492 ERR("Failed to get %s current display settings.\n", wine_dbgstr_w(name));
493 return FALSE;
496 if (!is_detached_mode(devmode))
497 devmode->dmBitsPerPel = get_display_depth(id);
499 goto done;
502 EnterCriticalSection(&modes_section);
503 if (n == 0 || lstrcmpiW(cached_device_name, name) || cached_flags != flags)
505 if (!handler.get_id(name, &id) || !handler.get_modes(id, flags, &modes, &mode_count))
507 ERR("Failed to get %s supported display modes.\n", wine_dbgstr_w(name));
508 LeaveCriticalSection(&modes_section);
509 return FALSE;
512 qsort(modes, mode_count, sizeof(*modes) + modes[0].dmDriverExtra, mode_compare);
514 if (cached_modes)
515 handler.free_modes(cached_modes);
516 lstrcpyW(cached_device_name, name);
517 cached_flags = flags;
518 cached_modes = modes;
519 cached_mode_count = mode_count;
522 if (n >= cached_mode_count)
524 LeaveCriticalSection(&modes_section);
525 WARN("handler:%s device:%s mode index:%#x not found.\n", handler.name, wine_dbgstr_w(name), n);
526 SetLastError(ERROR_NO_MORE_FILES);
527 return FALSE;
530 memcpy(devmode, (BYTE *)cached_modes + (sizeof(*cached_modes) + cached_modes[0].dmDriverExtra) * n, sizeof(*devmode));
531 LeaveCriticalSection(&modes_section);
533 done:
534 /* Set generic fields */
535 devmode->dmSize = FIELD_OFFSET(DEVMODEW, dmICMMethod);
536 devmode->dmDriverExtra = 0;
537 devmode->dmSpecVersion = DM_SPECVERSION;
538 devmode->dmDriverVersion = DM_SPECVERSION;
539 lstrcpyW(devmode->dmDeviceName, dev_name);
540 return TRUE;
543 BOOL is_detached_mode(const DEVMODEW *mode)
545 return mode->dmFields & DM_POSITION &&
546 mode->dmFields & DM_PELSWIDTH &&
547 mode->dmFields & DM_PELSHEIGHT &&
548 mode->dmPelsWidth == 0 &&
549 mode->dmPelsHeight == 0;
552 /* Get the full display mode with all the necessary fields set.
553 * Return NULL on failure. Caller should call free_full_mode() to free the returned mode. */
554 static DEVMODEW *get_full_mode(ULONG_PTR id, DEVMODEW *dev_mode)
556 DEVMODEW *modes, *full_mode, *found_mode = NULL;
557 UINT mode_count, mode_idx;
559 if (is_detached_mode(dev_mode))
560 return dev_mode;
562 if (!handler.get_modes(id, EDS_ROTATEDMODE, &modes, &mode_count))
563 return NULL;
565 qsort(modes, mode_count, sizeof(*modes) + modes[0].dmDriverExtra, mode_compare);
566 for (mode_idx = 0; mode_idx < mode_count; ++mode_idx)
568 found_mode = (DEVMODEW *)((BYTE *)modes + (sizeof(*modes) + modes[0].dmDriverExtra) * mode_idx);
570 if (dev_mode->dmFields & DM_BITSPERPEL &&
571 dev_mode->dmBitsPerPel &&
572 found_mode->dmBitsPerPel != dev_mode->dmBitsPerPel)
573 continue;
574 if (dev_mode->dmFields & DM_PELSWIDTH && found_mode->dmPelsWidth != dev_mode->dmPelsWidth)
575 continue;
576 if (dev_mode->dmFields & DM_PELSHEIGHT && found_mode->dmPelsHeight != dev_mode->dmPelsHeight)
577 continue;
578 if (dev_mode->dmFields & DM_DISPLAYFREQUENCY &&
579 dev_mode->dmDisplayFrequency &&
580 found_mode->dmDisplayFrequency &&
581 dev_mode->dmDisplayFrequency != 1 &&
582 dev_mode->dmDisplayFrequency != found_mode->dmDisplayFrequency)
583 continue;
584 if (dev_mode->dmFields & DM_DISPLAYORIENTATION &&
585 found_mode->u1.s2.dmDisplayOrientation != dev_mode->u1.s2.dmDisplayOrientation)
586 continue;
588 break;
591 if (!found_mode || mode_idx == mode_count)
593 handler.free_modes(modes);
594 return NULL;
597 if (!(full_mode = heap_alloc(sizeof(*found_mode) + found_mode->dmDriverExtra)))
599 handler.free_modes(modes);
600 return NULL;
603 memcpy(full_mode, found_mode, sizeof(*found_mode) + found_mode->dmDriverExtra);
604 handler.free_modes(modes);
606 full_mode->dmFields |= DM_POSITION;
607 full_mode->u1.s2.dmPosition = dev_mode->u1.s2.dmPosition;
608 return full_mode;
611 static void free_full_mode(DEVMODEW *mode)
613 if (!is_detached_mode(mode))
614 heap_free(mode);
617 static LONG get_display_settings(struct x11drv_display_setting **new_displays,
618 INT *new_display_count, const WCHAR *dev_name, DEVMODEW *dev_mode)
620 struct x11drv_display_setting *displays;
621 DEVMODEW registry_mode, current_mode;
622 INT display_idx, display_count = 0;
623 DISPLAY_DEVICEW display_device;
624 LONG ret = DISP_CHANGE_FAILED;
626 display_device.cb = sizeof(display_device);
627 for (display_idx = 0; EnumDisplayDevicesW(NULL, display_idx, &display_device, 0); ++display_idx)
628 ++display_count;
630 displays = heap_calloc(display_count, sizeof(*displays));
631 if (!displays)
632 goto done;
634 for (display_idx = 0; display_idx < display_count; ++display_idx)
636 if (!EnumDisplayDevicesW(NULL, display_idx, &display_device, 0))
637 goto done;
639 if (!handler.get_id(display_device.DeviceName, &displays[display_idx].id))
641 ret = DISP_CHANGE_BADPARAM;
642 goto done;
645 if (!dev_mode)
647 memset(&registry_mode, 0, sizeof(registry_mode));
648 registry_mode.dmSize = sizeof(registry_mode);
649 if (!EnumDisplaySettingsExW(display_device.DeviceName, ENUM_REGISTRY_SETTINGS, &registry_mode, 0))
650 goto done;
652 displays[display_idx].desired_mode = registry_mode;
654 else if (!lstrcmpiW(dev_name, display_device.DeviceName))
656 displays[display_idx].desired_mode = *dev_mode;
657 if (!(dev_mode->dmFields & DM_POSITION))
659 memset(&current_mode, 0, sizeof(current_mode));
660 current_mode.dmSize = sizeof(current_mode);
661 if (!EnumDisplaySettingsExW(display_device.DeviceName, ENUM_CURRENT_SETTINGS, &current_mode, 0))
662 goto done;
664 displays[display_idx].desired_mode.dmFields |= DM_POSITION;
665 displays[display_idx].desired_mode.u1.s2.dmPosition = current_mode.u1.s2.dmPosition;
668 else
670 memset(&current_mode, 0, sizeof(current_mode));
671 current_mode.dmSize = sizeof(current_mode);
672 if (!EnumDisplaySettingsExW(display_device.DeviceName, ENUM_CURRENT_SETTINGS, &current_mode, 0))
673 goto done;
675 displays[display_idx].desired_mode = current_mode;
678 SetRect(&displays[display_idx].desired_rect,
679 displays[display_idx].desired_mode.u1.s2.dmPosition.x,
680 displays[display_idx].desired_mode.u1.s2.dmPosition.y,
681 displays[display_idx].desired_mode.u1.s2.dmPosition.x + displays[display_idx].desired_mode.dmPelsWidth,
682 displays[display_idx].desired_mode.u1.s2.dmPosition.y + displays[display_idx].desired_mode.dmPelsHeight);
683 lstrcpyW(displays[display_idx].desired_mode.dmDeviceName, display_device.DeviceName);
686 *new_displays = displays;
687 *new_display_count = display_count;
688 return DISP_CHANGE_SUCCESSFUL;
690 done:
691 heap_free(displays);
692 return ret;
695 static INT offset_length(POINT offset)
697 return offset.x * offset.x + offset.y * offset.y;
700 /* Check if a rect overlaps with placed display rects */
701 static BOOL overlap_placed_displays(const RECT *rect, const struct x11drv_display_setting *displays, INT display_count)
703 INT display_idx;
704 RECT intersect;
706 for (display_idx = 0; display_idx < display_count; ++display_idx)
708 if (displays[display_idx].placed &&
709 IntersectRect(&intersect, &displays[display_idx].new_rect, rect))
710 return TRUE;
712 return FALSE;
715 /* Get the offset with minimum length to place a display next to the placed displays with no spacing and overlaps */
716 static POINT get_placement_offset(const struct x11drv_display_setting *displays, INT display_count, INT placing_idx)
718 POINT points[8], left_top, offset, min_offset = {0, 0};
719 INT display_idx, point_idx, point_count, vertex_idx;
720 BOOL has_placed = FALSE, first = TRUE;
721 INT width, height;
722 RECT rect;
724 /* If the display to be placed is detached, no offset is needed to place it */
725 if (IsRectEmpty(&displays[placing_idx].desired_rect))
726 return min_offset;
728 /* If there is no placed and attached display, place this display as it is */
729 for (display_idx = 0; display_idx < display_count; ++display_idx)
731 if (displays[display_idx].placed && !IsRectEmpty(&displays[display_idx].new_rect))
733 has_placed = TRUE;
734 break;
738 if (!has_placed)
739 return min_offset;
741 /* Try to place this display with each of its four vertices at every vertex of the placed
742 * displays and see which combination has the minimum offset length */
743 width = displays[placing_idx].desired_rect.right - displays[placing_idx].desired_rect.left;
744 height = displays[placing_idx].desired_rect.bottom - displays[placing_idx].desired_rect.top;
746 for (display_idx = 0; display_idx < display_count; ++display_idx)
748 if (!displays[display_idx].placed || IsRectEmpty(&displays[display_idx].new_rect))
749 continue;
751 /* Get four vertices of the placed display rectangle */
752 points[0].x = displays[display_idx].new_rect.left;
753 points[0].y = displays[display_idx].new_rect.top;
754 points[1].x = displays[display_idx].new_rect.left;
755 points[1].y = displays[display_idx].new_rect.bottom;
756 points[2].x = displays[display_idx].new_rect.right;
757 points[2].y = displays[display_idx].new_rect.top;
758 points[3].x = displays[display_idx].new_rect.right;
759 points[3].y = displays[display_idx].new_rect.bottom;
760 point_count = 4;
762 /* Intersected points when moving the display to be placed horizontally */
763 if (displays[placing_idx].desired_rect.bottom >= displays[display_idx].new_rect.top &&
764 displays[placing_idx].desired_rect.top <= displays[display_idx].new_rect.bottom)
766 points[point_count].x = displays[display_idx].new_rect.left;
767 points[point_count++].y = displays[placing_idx].desired_rect.top;
768 points[point_count].x = displays[display_idx].new_rect.right;
769 points[point_count++].y = displays[placing_idx].desired_rect.top;
771 /* Intersected points when moving the display to be placed vertically */
772 if (displays[placing_idx].desired_rect.left <= displays[display_idx].new_rect.right &&
773 displays[placing_idx].desired_rect.right >= displays[display_idx].new_rect.left)
775 points[point_count].x = displays[placing_idx].desired_rect.left;
776 points[point_count++].y = displays[display_idx].new_rect.top;
777 points[point_count].x = displays[placing_idx].desired_rect.left;
778 points[point_count++].y = displays[display_idx].new_rect.bottom;
781 /* Try moving each vertex of the display rectangle to each points */
782 for (point_idx = 0; point_idx < point_count; ++point_idx)
784 for (vertex_idx = 0; vertex_idx < 4; ++vertex_idx)
786 switch (vertex_idx)
788 /* Move the bottom right vertex to the point */
789 case 0:
790 left_top.x = points[point_idx].x - width;
791 left_top.y = points[point_idx].y - height;
792 break;
793 /* Move the bottom left vertex to the point */
794 case 1:
795 left_top.x = points[point_idx].x;
796 left_top.y = points[point_idx].y - height;
797 break;
798 /* Move the top right vertex to the point */
799 case 2:
800 left_top.x = points[point_idx].x - width;
801 left_top.y = points[point_idx].y;
802 break;
803 /* Move the top left vertex to the point */
804 case 3:
805 left_top.x = points[point_idx].x;
806 left_top.y = points[point_idx].y;
807 break;
810 offset.x = left_top.x - displays[placing_idx].desired_rect.left;
811 offset.y = left_top.y - displays[placing_idx].desired_rect.top;
812 rect = displays[placing_idx].desired_rect;
813 OffsetRect(&rect, offset.x, offset.y);
814 if (!overlap_placed_displays(&rect, displays, display_count))
816 if (first)
818 min_offset = offset;
819 first = FALSE;
820 continue;
823 if (offset_length(offset) < offset_length(min_offset))
824 min_offset = offset;
830 return min_offset;
833 static void place_all_displays(struct x11drv_display_setting *displays, INT display_count)
835 INT left_most = INT_MAX, top_most = INT_MAX;
836 INT placing_idx, display_idx;
837 POINT min_offset, offset;
839 /* Place all displays with no extra space between them and no overlapping */
840 while (1)
842 /* Place the unplaced display with the minimum offset length first */
843 placing_idx = -1;
844 for (display_idx = 0; display_idx < display_count; ++display_idx)
846 if (displays[display_idx].placed)
847 continue;
849 offset = get_placement_offset(displays, display_count, display_idx);
850 if (placing_idx == -1 || offset_length(offset) < offset_length(min_offset))
852 min_offset = offset;
853 placing_idx = display_idx;
857 /* If all displays are placed */
858 if (placing_idx == -1)
859 break;
861 displays[placing_idx].new_rect = displays[placing_idx].desired_rect;
862 OffsetRect(&displays[placing_idx].new_rect, min_offset.x, min_offset.y);
863 displays[placing_idx].placed = TRUE;
866 for (display_idx = 0; display_idx < display_count; ++display_idx)
868 displays[display_idx].desired_mode.u1.s2.dmPosition.x = displays[display_idx].new_rect.left;
869 displays[display_idx].desired_mode.u1.s2.dmPosition.y = displays[display_idx].new_rect.top;
870 left_most = min(left_most, displays[display_idx].new_rect.left);
871 top_most = min(top_most, displays[display_idx].new_rect.top);
874 /* Convert virtual screen coordinates to root coordinates */
875 for (display_idx = 0; display_idx < display_count; ++display_idx)
877 displays[display_idx].desired_mode.u1.s2.dmPosition.x -= left_most;
878 displays[display_idx].desired_mode.u1.s2.dmPosition.y -= top_most;
882 static LONG apply_display_settings(struct x11drv_display_setting *displays, INT display_count, BOOL do_attach)
884 DEVMODEW *full_mode;
885 BOOL attached_mode;
886 INT display_idx;
887 LONG ret;
889 for (display_idx = 0; display_idx < display_count; ++display_idx)
891 attached_mode = !is_detached_mode(&displays[display_idx].desired_mode);
892 if ((attached_mode && !do_attach) || (!attached_mode && do_attach))
893 continue;
895 full_mode = get_full_mode(displays[display_idx].id, &displays[display_idx].desired_mode);
896 if (!full_mode)
897 return DISP_CHANGE_BADMODE;
899 TRACE("handler:%s changing %s to position:(%d,%d) resolution:%ux%u frequency:%uHz "
900 "depth:%ubits orientation:%#x.\n", handler.name,
901 wine_dbgstr_w(displays[display_idx].desired_mode.dmDeviceName),
902 full_mode->u1.s2.dmPosition.x, full_mode->u1.s2.dmPosition.y, full_mode->dmPelsWidth,
903 full_mode->dmPelsHeight, full_mode->dmDisplayFrequency, full_mode->dmBitsPerPel,
904 full_mode->u1.s2.dmDisplayOrientation);
906 ret = handler.set_current_mode(displays[display_idx].id, full_mode);
907 if (attached_mode && ret == DISP_CHANGE_SUCCESSFUL)
908 set_display_depth(displays[display_idx].id, full_mode->dmBitsPerPel);
909 free_full_mode(full_mode);
910 if (ret != DISP_CHANGE_SUCCESSFUL)
911 return ret;
914 return DISP_CHANGE_SUCCESSFUL;
917 static BOOL all_detached_settings(const struct x11drv_display_setting *displays, INT display_count)
919 INT display_idx;
921 for (display_idx = 0; display_idx < display_count; ++display_idx)
923 if (!is_detached_mode(&displays[display_idx].desired_mode))
924 return FALSE;
927 return TRUE;
930 /***********************************************************************
931 * ChangeDisplaySettingsEx (X11DRV.@)
934 LONG CDECL X11DRV_ChangeDisplaySettingsEx( LPCWSTR devname, LPDEVMODEW devmode,
935 HWND hwnd, DWORD flags, LPVOID lpvoid )
937 struct x11drv_display_setting *displays;
938 INT display_idx, display_count;
939 DEVMODEW *full_mode;
940 LONG ret;
942 ret = get_display_settings(&displays, &display_count, devname, devmode);
943 if (ret != DISP_CHANGE_SUCCESSFUL)
944 return ret;
946 if (flags & CDS_UPDATEREGISTRY && devname && devmode)
948 for (display_idx = 0; display_idx < display_count; ++display_idx)
950 if (!lstrcmpiW(displays[display_idx].desired_mode.dmDeviceName, devname))
952 full_mode = get_full_mode(displays[display_idx].id, &displays[display_idx].desired_mode);
953 if (!full_mode)
955 heap_free(displays);
956 return DISP_CHANGE_BADMODE;
959 if (!write_registry_settings(devname, full_mode))
961 ERR("Failed to write %s display settings to registry.\n", wine_dbgstr_w(devname));
962 free_full_mode(full_mode);
963 heap_free(displays);
964 return DISP_CHANGE_NOTUPDATED;
967 free_full_mode(full_mode);
968 break;
973 if (flags & (CDS_TEST | CDS_NORESET))
975 heap_free(displays);
976 return DISP_CHANGE_SUCCESSFUL;
979 if (all_detached_settings(displays, display_count))
981 WARN("Detaching all displays is not permitted.\n");
982 heap_free(displays);
983 return DISP_CHANGE_SUCCESSFUL;
986 place_all_displays(displays, display_count);
988 /* Detach displays first to free up CRTCs */
989 ret = apply_display_settings(displays, display_count, FALSE);
990 if (ret == DISP_CHANGE_SUCCESSFUL)
991 ret = apply_display_settings(displays, display_count, TRUE);
992 if (ret == DISP_CHANGE_SUCCESSFUL)
993 X11DRV_DisplayDevices_Update(TRUE);
994 heap_free(displays);
995 return ret;