From 89ad36b1d96fdd64cb2e59bd5743716b5874dac9 Mon Sep 17 00:00:00 2001
From: Anton Baskanov <baskanov@gmail.com>
Date: Tue, 6 Feb 2024 20:59:57 +0700
Subject: [PATCH] explorer: Restore display settings on process exit.

Restore display settings to the ones in the registry when CDS_FULLSCREEN
is used in ChangeDisplaySettings().

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49674
---
 dlls/user32/tests/monitor.c |  50 ++++++++---------
 dlls/win32u/sysparams.c     |  13 +++++
 programs/explorer/desktop.c | 105 +++++++++++++++++++++++++++++++++---
 3 files changed, 136 insertions(+), 32 deletions(-)

diff --git a/dlls/user32/tests/monitor.c b/dlls/user32/tests/monitor.c
index c638e1cbfef..e4fe79d6882 100644
--- a/dlls/user32/tests/monitor.c
+++ b/dlls/user32/tests/monitor.c
@@ -291,8 +291,8 @@ struct device_info
     DEVMODEA original_mode;
 };
 
-#define expect_dm(a, b, c, d) _expect_dm(__LINE__, a, b, c, d)
-static void _expect_dm(INT line, const DEVMODEA *expected, const CHAR *device, DWORD test, BOOL todo)
+#define expect_dm(a, b, c) _expect_dm(__LINE__, a, b, c)
+static void _expect_dm(INT line, const DEVMODEA *expected, const CHAR *device, DWORD test)
 {
     DEVMODEA dm;
     BOOL ret;
@@ -307,9 +307,9 @@ static void _expect_dm(INT line, const DEVMODEA *expected, const CHAR *device, D
             "Device %s test %ld expect dmFields to contain %#lx, got %#lx\n", device, test, expected->dmFields, dm.dmFields);
     ok_(__FILE__, line)(!(expected->dmFields & DM_BITSPERPEL) || dm.dmBitsPerPel == expected->dmBitsPerPel,
             "Device %s test %ld expect dmBitsPerPel %lu, got %lu\n", device, test, expected->dmBitsPerPel, dm.dmBitsPerPel);
-    todo_wine_if(todo) ok_(__FILE__, line)(!(expected->dmFields & DM_PELSWIDTH) || dm.dmPelsWidth == expected->dmPelsWidth,
+    ok_(__FILE__, line)(!(expected->dmFields & DM_PELSWIDTH) || dm.dmPelsWidth == expected->dmPelsWidth,
             "Device %s test %ld expect dmPelsWidth %lu, got %lu\n", device, test, expected->dmPelsWidth, dm.dmPelsWidth);
-    todo_wine_if(todo) ok_(__FILE__, line)(!(expected->dmFields & DM_PELSHEIGHT) || dm.dmPelsHeight == expected->dmPelsHeight,
+    ok_(__FILE__, line)(!(expected->dmFields & DM_PELSHEIGHT) || dm.dmPelsHeight == expected->dmPelsHeight,
             "Device %s test %ld expect dmPelsHeight %lu, got %lu\n", device, test, expected->dmPelsHeight, dm.dmPelsHeight);
     ok_(__FILE__, line)(!(expected->dmFields & DM_POSITION) || dm.dmPosition.x == expected->dmPosition.x,
             "Device %s test %ld expect dmPosition.x %ld, got %ld\n", device, test, expected->dmPosition.x, dm.dmPosition.x);
@@ -325,8 +325,8 @@ static void _expect_dm(INT line, const DEVMODEA *expected, const CHAR *device, D
             dm.dmDisplayOrientation);
 }
 
-#define wait_for_dm(a, b, c, d) wait_for_dm_(__LINE__, a, b, c, d)
-static void wait_for_dm_(int line, const char *device, DWORD expected_width, DWORD expected_height, BOOL todo)
+#define wait_for_dm(a, b, c) wait_for_dm_(__LINE__, a, b, c)
+static void wait_for_dm_(int line, const char *device, DWORD expected_width, DWORD expected_height)
 {
     DEVMODEA dm;
     BOOL ret;
@@ -346,9 +346,9 @@ static void wait_for_dm_(int line, const char *device, DWORD expected_width, DWO
         Sleep(100);
     }
 
-    todo_wine_if(todo) ok_(__FILE__, line)(dm.dmPelsWidth == expected_width,
+    ok_(__FILE__, line)(dm.dmPelsWidth == expected_width,
             "Device %s expect dmPelsWidth %lu, got %lu\n", device, expected_width, dm.dmPelsWidth);
-    todo_wine_if(todo) ok_(__FILE__, line)(dm.dmPelsHeight == expected_height,
+    ok_(__FILE__, line)(dm.dmPelsHeight == expected_height,
             "Device %s expect dmPelsHeight %lu, got %lu\n", device, expected_height, dm.dmPelsHeight);
 }
 
@@ -729,7 +729,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
                 continue;
             }
             flush_events();
-            expect_dm(&dm3, devices[device].name, test, FALSE);
+            expect_dm(&dm3, devices[device].name, test);
 
             /* Change the registry mode to the second mode */
             res = ChangeDisplaySettingsExA(devices[device].name, &dm2, NULL, CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
@@ -863,7 +863,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
             }
 
             flush_events();
-            expect_dm(&dm, devices[device].name, mode, FALSE);
+            expect_dm(&dm, devices[device].name, mode);
         }
 
         /* Restore settings */
@@ -936,7 +936,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
         }
 
         flush_events();
-        expect_dm(&dm, devices[device].name, 0, FALSE);
+        expect_dm(&dm, devices[device].name, 0);
 
         /* Test specifying only position, width and height */
         memset(&dm, 0, sizeof(dm));
@@ -981,7 +981,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
         ok(dm.dmBitsPerPel, "Expected dmBitsPerPel not zero.\n");
         ok(dm.dmDisplayFrequency, "Expected dmDisplayFrequency not zero.\n");
 
-        expect_dm(&dm, devices[device].name, 0, FALSE);
+        expect_dm(&dm, devices[device].name, 0);
     }
 
     /* Test dmPosition */
@@ -1053,7 +1053,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
             ok(res == DISP_CHANGE_SUCCESSFUL, "ChangeDisplaySettingsExA %s returned unexpected %ld\n", devices[1].name, res);
 
             dm2.dmPosition.x = dm.dmPosition.x + dm.dmPelsWidth;
-            expect_dm(&dm2, devices[1].name, 0, FALSE);
+            expect_dm(&dm2, devices[1].name, 0);
 
             /* Test placing the secondary adapter to all sides of the primary adapter */
             for (test = 0; test < 8; ++test)
@@ -1112,7 +1112,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
                 }
 
                 flush_events();
-                expect_dm(&dm2, devices[1].name, test, FALSE);
+                expect_dm(&dm2, devices[1].name, test);
             }
 
             /* Test automatic position update when other adapters change resolution */
@@ -1177,7 +1177,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
             ok(res == DISP_CHANGE_SUCCESSFUL, "ChangeDisplaySettingsExA %s mode %d returned unexpected %ld.\n",
                     devices[device].name, mode, res);
             flush_events();
-            expect_dm(&dm2, devices[device].name, mode, FALSE);
+            expect_dm(&dm2, devices[device].name, mode);
 
             /* EnumDisplaySettingsEx without EDS_ROTATEDMODE reports modes with current orientation */
             memset(&dm3, 0, sizeof(dm3));
@@ -1258,13 +1258,13 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
 
         dm.dmPelsWidth = 640;
         dm.dmPelsHeight = 480;
-        expect_dm(&dm, devices[0].name, 0, FALSE);
+        expect_dm(&dm, devices[0].name, 0);
 
         SetEvent(exit_event1);
         wait_result = WaitForSingleObject(process1, 5000);
         ok(wait_result == WAIT_OBJECT_0, "WaitForSingleObject returned %lx.\n", wait_result);
 
-        wait_for_dm(devices[0].name, 800, 600, TRUE);
+        wait_for_dm(devices[0].name, 800, 600);
 
         CloseHandle(process1);
         CloseHandle(process0);
@@ -1278,7 +1278,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
         wait_result = WaitForSingleObject(process1, 5000);
         ok(wait_result == WAIT_OBJECT_0, "WaitForSingleObject returned %lx.\n", wait_result);
 
-        wait_for_dm(devices[0].name, 800, 600, TRUE);
+        wait_for_dm(devices[0].name, 800, 600);
 
         SetEvent(exit_event0);
         wait_result = WaitForSingleObject(process0, 5000);
@@ -1300,7 +1300,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
 
         dm.dmPelsWidth = 640;
         dm.dmPelsHeight = 480;
-        expect_dm(&dm, devices[0].name, 0, FALSE);
+        expect_dm(&dm, devices[0].name, 0);
 
         SetEvent(exit_event1);
         wait_result = WaitForSingleObject(process1, 5000);
@@ -1310,7 +1310,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
 
         dm.dmPelsWidth = 640;
         dm.dmPelsHeight = 480;
-        expect_dm(&dm, devices[0].name, 0, FALSE);
+        expect_dm(&dm, devices[0].name, 0);
 
         CloseHandle(process1);
         CloseHandle(process0);
@@ -1328,7 +1328,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
 
         dm.dmPelsWidth = 640;
         dm.dmPelsHeight = 480;
-        expect_dm(&dm, devices[0].name, 0, FALSE);
+        expect_dm(&dm, devices[0].name, 0);
 
         SetEvent(exit_event0);
         wait_result = WaitForSingleObject(process0, 5000);
@@ -1338,7 +1338,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
 
         dm.dmPelsWidth = 640;
         dm.dmPelsHeight = 480;
-        expect_dm(&dm, devices[0].name, 0, FALSE);
+        expect_dm(&dm, devices[0].name, 0);
 
         CloseHandle(process1);
         CloseHandle(process0);
@@ -1363,8 +1363,8 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
             wait_result = WaitForSingleObject(process0, 5000);
             ok(wait_result == WAIT_OBJECT_0, "WaitForSingleObject returned %lx.\n", wait_result);
 
-            wait_for_dm(devices[0].name, 800, 600, TRUE);
-            wait_for_dm(devices[1].name, 800, 600, TRUE);
+            wait_for_dm(devices[0].name, 800, 600);
+            wait_for_dm(devices[1].name, 800, 600);
 
             CloseHandle(process0);
         }
@@ -1387,7 +1387,7 @@ static void test_ChangeDisplaySettingsEx(int argc, char **argv)
             broken(res == DISP_CHANGE_FAILED), /* win8 TestBot */
             "ChangeDisplaySettingsExA returned unexpected %ld\n", res);
     for (device = 0; device < device_count; ++device)
-        expect_dm(&devices[device].original_mode, devices[device].name, 0, FALSE);
+        expect_dm(&devices[device].original_mode, devices[device].name, 0);
 
     free(devices);
 }
diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c
index 6993373ca54..e66ad488486 100644
--- a/dlls/win32u/sysparams.c
+++ b/dlls/win32u/sysparams.c
@@ -3149,9 +3149,13 @@ static BOOL all_detached_settings( const DEVMODEW *displays )
 static LONG apply_display_settings( struct source *target, const DEVMODEW *devmode,
                                     HWND hwnd, DWORD flags, void *lparam )
 {
+    static const WCHAR restorerW[] = {'_','_','w','i','n','e','_','d','i','s','p','l','a','y','_',
+                                      's','e','t','t','i','n','g','s','_','r','e','s','t','o','r','e','r',0};
+    UNICODE_STRING restoter_str = RTL_CONSTANT_STRING( restorerW );
     WCHAR primary_name[CCHDEVICENAME];
     struct source *primary, *source;
     DEVMODEW *mode, *displays;
+    HWND restorer_window;
     LONG ret;
 
     if (!lock_display_devices()) return DISP_CHANGE_FAILED;
@@ -3200,6 +3204,15 @@ static LONG apply_display_settings( struct source *target, const DEVMODEW *devmo
     free( displays );
     if (ret) return ret;
 
+    if ((restorer_window = NtUserFindWindowEx( NULL, NULL, &restoter_str, NULL, 0 )))
+    {
+        if (NtUserGetWindowThread( restorer_window, NULL ) != GetCurrentThreadId())
+        {
+            DWORD fullscreen_process_id = (flags & CDS_FULLSCREEN) ? GetCurrentProcessId() : 0;
+            send_message( restorer_window, WM_USER + 0, 0, fullscreen_process_id );
+        }
+    }
+
     if (!update_display_cache( TRUE ))
         WARN( "Failed to update display cache after mode change.\n" );
 
diff --git a/programs/explorer/desktop.c b/programs/explorer/desktop.c
index a18c9806d42..fb59258ad36 100644
--- a/programs/explorer/desktop.c
+++ b/programs/explorer/desktop.c
@@ -613,6 +613,18 @@ static void initialize_launchers( HWND hwnd )
     }
 }
 
+static void wait_named_mutex( const WCHAR *name )
+{
+    HANDLE mutex;
+
+    mutex = CreateMutexW( NULL, TRUE, name );
+    if (GetLastError() == ERROR_ALREADY_EXISTS)
+    {
+        TRACE( "waiting for mutex %s\n", debugstr_w( name ));
+        WaitForSingleObject( mutex, INFINITE );
+    }
+}
+
 /**************************************************************************
  *		wait_clipboard_mutex
  *
@@ -622,7 +634,6 @@ static BOOL wait_clipboard_mutex(void)
 {
     static const WCHAR prefix[] = L"__wine_clipboard_";
     WCHAR buffer[MAX_PATH + ARRAY_SIZE( prefix )];
-    HANDLE mutex;
 
     memcpy( buffer, prefix, sizeof(prefix) );
     if (!GetUserObjectInformationW( GetProcessWindowStation(), UOI_NAME,
@@ -632,12 +643,7 @@ static BOOL wait_clipboard_mutex(void)
         ERR( "failed to get winstation name\n" );
         return FALSE;
     }
-    mutex = CreateMutexW( NULL, TRUE, buffer );
-    if (GetLastError() == ERROR_ALREADY_EXISTS)
-    {
-        TRACE( "waiting for mutex %s\n", debugstr_w( buffer ));
-        WaitForSingleObject( mutex, INFINITE );
-    }
+    wait_named_mutex( buffer );
     return TRUE;
 }
 
@@ -698,6 +704,87 @@ static DWORD WINAPI clipboard_thread( void *arg )
     return 0;
 }
 
+static HANDLE fullscreen_process;
+
+static LRESULT WINAPI display_settings_restorer_wndproc( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
+{
+    TRACE( "got msg %04x wp %Ix lp %Ix\n", message, wp, lp );
+
+    switch(message)
+    {
+    case WM_USER + 0:
+        TRACE( "fullscreen process id %Iu.\n", lp );
+
+        if (fullscreen_process)
+        {
+            CloseHandle( fullscreen_process );
+            fullscreen_process = NULL;
+        }
+
+        if (lp)
+            fullscreen_process = OpenProcess( SYNCHRONIZE, FALSE, lp );
+
+        return 0;
+    }
+
+    return DefWindowProcW( hwnd, message, wp, lp );
+}
+
+static DWORD WINAPI display_settings_restorer_thread( void *param )
+{
+    static const WCHAR *display_settings_restorer_classname = L"__wine_display_settings_restorer";
+    DWORD wait_result;
+    WNDCLASSW class;
+    MSG msg;
+
+    SetThreadDescription( GetCurrentThread(), L"wine_explorer_display_settings_restorer" );
+
+    wait_named_mutex( L"__wine_display_settings_restorer_mutex" );
+
+    memset( &class, 0, sizeof(class) );
+    class.lpfnWndProc   = display_settings_restorer_wndproc;
+    class.lpszClassName = display_settings_restorer_classname;
+
+    if (!RegisterClassW( &class ) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
+    {
+        ERR( "could not register display settings restorer window class err %lu\n", GetLastError() );
+        return 0;
+    }
+    if (!CreateWindowW( display_settings_restorer_classname, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, NULL ))
+    {
+        WARN( "failed to create display settings restorer window err %lu\n", GetLastError() );
+        UnregisterClassW( display_settings_restorer_classname, NULL );
+        return 0;
+    }
+
+    for (;;)
+    {
+        if (PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE ))
+        {
+            if (msg.message == WM_QUIT)
+                break;
+            DispatchMessageW( &msg );
+            continue;
+        }
+
+        wait_result = MsgWaitForMultipleObjects( fullscreen_process ? 1 : 0, &fullscreen_process,
+                                                 FALSE, INFINITE, QS_ALLINPUT );
+        if (wait_result == WAIT_FAILED)
+            break;
+        if (!fullscreen_process || wait_result != WAIT_OBJECT_0)
+            continue;
+
+        TRACE( "restoring display settings on process exit\n" );
+
+        ChangeDisplaySettingsExW( NULL, NULL, NULL, 0, NULL );
+
+        CloseHandle( fullscreen_process );
+        fullscreen_process = NULL;
+    }
+
+    return 0;
+}
+
 static WNDPROC desktop_orig_wndproc;
 
 /* window procedure for the desktop window */
@@ -970,6 +1057,7 @@ static void initialize_display_settings( unsigned int width, unsigned int height
 {
     DISPLAY_DEVICEW device = {.cb = sizeof(DISPLAY_DEVICEW)};
     DWORD i = 0, flags = CDS_GLOBAL | CDS_UPDATEREGISTRY;
+    HANDLE thread;
 
     /* Store current display mode in the registry */
     while (EnumDisplayDevicesW( NULL, i++, &device, 0 ))
@@ -1002,6 +1090,9 @@ static void initialize_display_settings( unsigned int width, unsigned int height
         if (ChangeDisplaySettingsExW( NULL, &devmode, 0, flags, NULL ))
             ERR( "Failed to set primary display settings.\n" );
     }
+
+    thread = CreateThread( NULL, 0, display_settings_restorer_thread, NULL, 0, NULL );
+    if (thread) CloseHandle( thread );
 }
 
 static void set_desktop_window_title( HWND hwnd, const WCHAR *name )
-- 
GitLab