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
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 4ee1b07..ba2a628 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -1057,6 +1057,13 @@
         }
         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 412f967..d88dcab 100644
--- a/src/video/windows/SDL_windowsmodes.c
+++ b/src/video/windows/SDL_windowsmodes.c
@@ -140,8 +140,9 @@
 }
 
 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 @@
         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 @@
     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 @@
     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 @@
                         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 @@
 }
 
 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 @@
 int
 WIN_InitModes(_THIS)
 {
-    WIN_AddDisplays(_this);
+    WIN_AddDisplays(_this, SDL_FALSE);
 
     if (_this->num_displays == 0) {
         return SDL_SetError("No displays available");
@@ -396,6 +412,32 @@
 }
 
 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)
 {
     /* All fullscreen windows should have restored modes by now */
diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h
index a89159a..4317242 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_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_ */