SDL: Fixed event sequence when Remote Desktop connects and disconnects

From 2e27812d39d17831da0a0d8afa1add6c8795af5a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 2 Jun 2023 08:51:47 -0700
Subject: [PATCH] Fixed event sequence when Remote Desktop connects and
 disconnects

1. Send display disconnected events for all displays no longer connected
2. Send display connected events for all displays that have been connected
3. Send window display events for any windows that have changed displays
---
 src/events/SDL_displayevents.c       |  8 +++++++
 src/video/SDL_sysvideo.h             |  1 +
 src/video/SDL_video.c                | 11 ++++++++++
 src/video/windows/SDL_windowsmodes.c | 32 +++++++++++++++++-----------
 src/video/windows/SDL_windowsmodes.h | 10 ++++++++-
 5 files changed, 48 insertions(+), 14 deletions(-)

diff --git a/src/events/SDL_displayevents.c b/src/events/SDL_displayevents.c
index cf3694466ea0..803d24d4318c 100644
--- a/src/events/SDL_displayevents.c
+++ b/src/events/SDL_displayevents.c
@@ -53,5 +53,13 @@ int SDL_SendDisplayEvent(SDL_VideoDisplay *display, SDL_EventType displayevent,
         posted = (SDL_PushEvent(&event) > 0);
     }
 
+    switch (displayevent) {
+    case SDL_EVENT_DISPLAY_CONNECTED:
+        SDL_OnDisplayConnected(display);
+        break;
+    default:
+        break;
+    }
+
     return posted;
 }
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index 5590e941cd08..9da28a1c3526 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -517,6 +517,7 @@ extern SDL_bool SDL_HasWindows(void);
 extern void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y);
 extern void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y);
 
+extern void SDL_OnDisplayConnected(SDL_VideoDisplay *display);
 extern void SDL_OnWindowShown(SDL_Window *window);
 extern void SDL_OnWindowHidden(SDL_Window *window);
 extern void SDL_OnWindowMoved(SDL_Window *window);
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index af6c9194ec8c..83936dda9aaa 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -160,6 +160,7 @@ extern SDL_bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window);
 extern SDL_bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, SDL_bool state);
 #endif
 
+static void SDL_CheckWindowDisplayChanged(SDL_Window *window);
 static void SDL_CheckWindowDisplayScaleChanged(SDL_Window *window);
 
 /* Convenience functions for reading driver flags */
@@ -694,6 +695,16 @@ SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send
     return id;
 }
 
+void SDL_OnDisplayConnected(SDL_VideoDisplay *display)
+{
+    SDL_Window *window;
+
+    /* See if any windows have changed to the new display */
+    for (window = _this->windows; window; window = window->next) {
+        SDL_CheckWindowDisplayChanged(window);
+    }
+}
+
 void SDL_DelVideoDisplay(SDL_DisplayID displayID, SDL_bool send_event)
 {
     SDL_VideoDisplay *display;
diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c
index 79a72daa1999..50fa93e0bf11 100644
--- a/src/video/windows/SDL_windowsmodes.c
+++ b/src/video/windows/SDL_windowsmodes.c
@@ -318,7 +318,7 @@ static char *WIN_GetDisplayNameVista(const WCHAR *deviceName)
     return NULL;
 }
 
-static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONITORINFOEXW *info, int *display_index, SDL_bool send_event)
+static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONITORINFOEXW *info, int *display_index)
 {
     int i, index = *display_index;
     SDL_DisplayID displayID;
@@ -356,7 +356,7 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI
             }
 
             driverdata->MonitorHandle = hMonitor;
-            driverdata->IsValid = SDL_TRUE;
+            driverdata->state = DisplayUnchanged;
 
             if (!_this->setting_display_mode) {
                 SDL_VideoDisplay *existing_display = &_this->displays[i];
@@ -385,7 +385,7 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI
     }
     SDL_memcpy(displaydata->DeviceName, info->szDevice, sizeof(displaydata->DeviceName));
     displaydata->MonitorHandle = hMonitor;
-    displaydata->IsValid = SDL_TRUE;
+    displaydata->state = DisplayAdded;
 
     SDL_zero(display);
     display.name = WIN_GetDisplayNameVista(info->szDevice);
@@ -404,8 +404,7 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI
     display.device = _this;
     display.driverdata = displaydata;
     WIN_GetDisplayBounds(_this, &display, &displaydata->bounds);
-    displayID = SDL_AddVideoDisplay(&display, send_event);
-    SDL_assert(SDL_GetDisplayIndex(displayID) == *display_index);
+    displayID = SDL_AddVideoDisplay(&display, SDL_FALSE);
     SDL_free(display.name);
 
 done:
@@ -416,7 +415,6 @@ typedef struct _WIN_AddDisplaysData
 {
     SDL_VideoDevice *video_device;
     int display_index;
-    SDL_bool send_event;
     SDL_bool want_primary;
 } WIN_AddDisplaysData;
 
@@ -435,7 +433,7 @@ static BOOL CALLBACK WIN_AddDisplaysCallback(HMONITOR hMonitor,
         const SDL_bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY);
 
         if (is_primary == data->want_primary) {
-            WIN_AddDisplay(data->video_device, hMonitor, &info, &data->display_index, data->send_event);
+            WIN_AddDisplay(data->video_device, hMonitor, &info, &data->display_index);
         }
     }
 
@@ -443,12 +441,11 @@ static BOOL CALLBACK WIN_AddDisplaysCallback(HMONITOR hMonitor,
     return TRUE;
 }
 
-static void WIN_AddDisplays(SDL_VideoDevice *_this, SDL_bool send_event)
+static void WIN_AddDisplays(SDL_VideoDevice *_this)
 {
     WIN_AddDisplaysData callback_data;
     callback_data.video_device = _this;
     callback_data.display_index = 0;
-    callback_data.send_event = send_event;
 
     callback_data.want_primary = SDL_TRUE;
     EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
@@ -459,7 +456,7 @@ static void WIN_AddDisplays(SDL_VideoDevice *_this, SDL_bool send_event)
 
 int WIN_InitModes(SDL_VideoDevice *_this)
 {
-    WIN_AddDisplays(_this, SDL_FALSE);
+    WIN_AddDisplays(_this);
 
     if (_this->num_displays == 0) {
         return SDL_SetError("No displays available");
@@ -636,22 +633,31 @@ void WIN_RefreshDisplays(SDL_VideoDevice *_this)
     // entries that have actually been removed
     for (i = 0; i < _this->num_displays; ++i) {
         SDL_DisplayData *driverdata = _this->displays[i].driverdata;
-        driverdata->IsValid = SDL_FALSE;
+        driverdata->state = DisplayRemoved;
     }
 
     // Enumerate displays to add any new ones and mark still
     // connected entries as valid
-    WIN_AddDisplays(_this, SDL_TRUE);
+    WIN_AddDisplays(_this);
 
     // Delete any entries still marked as invalid, iterate
     // in reverse as each delete takes effect immediately
     for (i = _this->num_displays - 1; i >= 0; --i) {
         SDL_VideoDisplay *display = &_this->displays[i];
         SDL_DisplayData *driverdata = display->driverdata;
-        if (driverdata->IsValid == SDL_FALSE) {
+        if (driverdata->state == DisplayRemoved) {
             SDL_DelVideoDisplay(display->id, SDL_TRUE);
         }
     }
+
+    // Send events for any newly added displays
+    for (i = 0; i < _this->num_displays; ++i) {
+        SDL_VideoDisplay *display = &_this->displays[i];
+        SDL_DisplayData *driverdata = display->driverdata;
+        if (driverdata->state == DisplayAdded) {
+            SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_CONNECTED, 0);
+        }
+    }
 }
 
 void WIN_QuitModes(SDL_VideoDevice *_this)
diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h
index 37ea30426a54..31a76026cb85 100644
--- a/src/video/windows/SDL_windowsmodes.h
+++ b/src/video/windows/SDL_windowsmodes.h
@@ -23,11 +23,19 @@
 #ifndef SDL_windowsmodes_h_
 #define SDL_windowsmodes_h_
 
+typedef enum
+{
+    DisplayUnchanged,
+    DisplayAdded,
+    DisplayRemoved,
+
+} WIN_DisplayState;
+
 struct SDL_DisplayData
 {
     WCHAR DeviceName[32];
     HMONITOR MonitorHandle;
-    SDL_bool IsValid;
+    WIN_DisplayState state;
     SDL_Rect bounds;
 };