From b9cbea354ff05c072626a3ac912336ff4e2cffa3 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 13 Oct 2020 21:08:20 -0700 Subject: [PATCH] video: Refresh Windows display list on WM_DISPLAYCHANGE - Displays may have been added, removed or changed and all cached monitor handles are invalidated as a result. - Display events are handled in three steps: 1. Mark all currently know displays as invalid 2. Enumerate all displays, adding new ones and marking known displays as valid 3. Remove all displays still invalid after enumeration - Display connect/disconnect events are sent when displays are added or removed after initial setup --- src/video/windows/SDL_windowsevents.c | 7 ++++ src/video/windows/SDL_windowsmodes.c | 56 +++++++++++++++++++++++---- src/video/windows/SDL_windowsmodes.h | 4 +- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 4ee1b07de..ba2a628d1 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -1057,6 +1057,13 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } break; + case WM_DISPLAYCHANGE: + { + // Reacquire displays if any were added or removed + WIN_RefreshDisplays(SDL_GetVideoDevice()); + } + break; + case WM_NCCALCSIZE: { Uint32 window_flags = SDL_GetWindowFlags(data->window); diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c index 412f967e4..d88dcab11 100644 --- a/src/video/windows/SDL_windowsmodes.c +++ b/src/video/windows/SDL_windowsmodes.c @@ -140,8 +140,9 @@ WIN_GetDisplayMode(_THIS, LPCTSTR deviceName, DWORD index, SDL_DisplayMode * mod } static SDL_bool -WIN_AddDisplay(_THIS, HMONITOR hMonitor, const MONITORINFOEX *info) +WIN_AddDisplay(_THIS, HMONITOR hMonitor, const MONITORINFOEXW *info, SDL_bool send_event) { + int i; SDL_VideoDisplay display; SDL_DisplayData *displaydata; SDL_DisplayMode mode; @@ -155,6 +156,18 @@ WIN_AddDisplay(_THIS, HMONITOR hMonitor, const MONITORINFOEX *info) return SDL_FALSE; } + // Prevent adding duplicate displays. Do this after we know the display is + // ready to be added to allow any displays that we can't fully query to be + // removed + for(i = 0; i < _this->num_displays; ++i) { + SDL_DisplayData *driverdata = (SDL_DisplayData *)_this->displays[i].driverdata; + if (SDL_wcscmp(driverdata->DeviceName, info->szDevice) == 0) { + driverdata->MonitorHandle = hMonitor; + driverdata->IsValid = SDL_TRUE; + return SDL_FALSE; + } + } + displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata)); if (!displaydata) { return SDL_FALSE; @@ -162,6 +175,7 @@ WIN_AddDisplay(_THIS, HMONITOR hMonitor, const MONITORINFOEX *info) SDL_memcpy(displaydata->DeviceName, info->szDevice, sizeof(displaydata->DeviceName)); displaydata->MonitorHandle = hMonitor; + displaydata->IsValid = SDL_TRUE; SDL_zero(display); device.cb = sizeof(device); @@ -171,13 +185,14 @@ WIN_AddDisplay(_THIS, HMONITOR hMonitor, const MONITORINFOEX *info) display.desktop_mode = mode; display.current_mode = mode; display.driverdata = displaydata; - SDL_AddVideoDisplay(&display, SDL_FALSE); + SDL_AddVideoDisplay(&display, send_event); SDL_free(display.name); return SDL_TRUE; } typedef struct _WIN_AddDisplaysData { SDL_VideoDevice *video_device; + SDL_bool send_event; SDL_bool want_primary; } WIN_AddDisplaysData; @@ -188,16 +203,16 @@ WIN_AddDisplaysCallback(HMONITOR hMonitor, LPARAM dwData) { WIN_AddDisplaysData *data = (WIN_AddDisplaysData*)dwData; - MONITORINFOEX info; + MONITORINFOEXW info; SDL_zero(info); info.cbSize = sizeof(info); - if (GetMonitorInfo(hMonitor, (LPMONITORINFO)&info) != 0) { + if (GetMonitorInfoW(hMonitor, (LPMONITORINFO)&info) != 0) { const SDL_bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY); if (is_primary == data->want_primary) { - WIN_AddDisplay(data->video_device, hMonitor, &info); + WIN_AddDisplay(data->video_device, hMonitor, &info, data->send_event); } } @@ -206,10 +221,11 @@ WIN_AddDisplaysCallback(HMONITOR hMonitor, } static void -WIN_AddDisplays(_THIS) +WIN_AddDisplays(_THIS, SDL_bool send_event) { WIN_AddDisplaysData callback_data; callback_data.video_device = _this; + callback_data.send_event = send_event; callback_data.want_primary = SDL_TRUE; EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data); @@ -221,7 +237,7 @@ WIN_AddDisplays(_THIS) int WIN_InitModes(_THIS) { - WIN_AddDisplays(_this); + WIN_AddDisplays(_this, SDL_FALSE); if (_this->num_displays == 0) { return SDL_SetError("No displays available"); @@ -395,6 +411,32 @@ WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode) return 0; } +void +WIN_RefreshDisplays(_THIS) +{ + int i; + + // Mark all displays as potentially invalid to detect + // entries that have actually been removed + for (i = 0; i < _this->num_displays; ++i) { + SDL_DisplayData *driverdata = (SDL_DisplayData *)_this->displays[i].driverdata; + driverdata->IsValid = SDL_FALSE; + } + + // Enumerate displays to add any new ones and mark still + // connected entries as valid + WIN_AddDisplays(_this, SDL_TRUE); + + // 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_DisplayData *driverdata = (SDL_DisplayData *)_this->displays[i].driverdata; + if (driverdata->IsValid == SDL_FALSE) { + SDL_DelVideoDisplay(i); + } + } +} + void WIN_QuitModes(_THIS) { diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h index a89159a61..431724221 100644 --- a/src/video/windows/SDL_windowsmodes.h +++ b/src/video/windows/SDL_windowsmodes.h @@ -25,8 +25,9 @@ typedef struct { - TCHAR DeviceName[32]; + WCHAR DeviceName[32]; HMONITOR MonitorHandle; + SDL_bool IsValid; } SDL_DisplayData; typedef struct @@ -40,6 +41,7 @@ extern int WIN_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rec extern int WIN_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi); extern void WIN_GetDisplayModes(_THIS, SDL_VideoDisplay * display); extern int WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode); +extern void WIN_RefreshDisplays(_THIS); extern void WIN_QuitModes(_THIS); #endif /* SDL_windowsmodes_h_ */