SDL: events: Emit an event when a window is destroyed

From bee60993721f90eed2a6c1b7d3152645cb16ba4e Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Thu, 27 Apr 2023 09:20:40 -0400
Subject: [PATCH] events: Emit an event when a window is destroyed

As child windows can be recursively destroyed when their parents are destroyed, emit an event to notify the application when a window is being or has been implicitly destroyed so that it can appropriately clean up any associated resources.

If the application has registered an event watch, the destroy message will be received when the window handle is still valid, so the application can retrieve and release any userdata associated with the window. If the message is processed at any time after that, the window handle is already invalid and the ID is only useful for application-side bookkeeping purposes.
---
 include/SDL3/SDL_events.h     | 6 +++++-
 src/events/SDL_windowevents.c | 2 +-
 src/video/SDL_video.c         | 2 ++
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 8059da3522b8..a28cced2d451 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -120,8 +120,12 @@ typedef enum
     SDL_EVENT_WINDOW_HIT_TEST,          /**< Window had a hit test that wasn't SDL_HITTEST_NORMAL */
     SDL_EVENT_WINDOW_ICCPROF_CHANGED,   /**< The ICC profile of the window's display has changed */
     SDL_EVENT_WINDOW_DISPLAY_CHANGED,   /**< Window has been moved to display data1 */
+    SDL_EVENT_WINDOW_DESTROYED,         /**< The window with the associated ID is being or has been destroyed. If this message is being handled
+                                             in an event watcher, the window handle is still valid and can still be used to retrieve any userdata
+                                             associated with the window. Otherwise, the handle has already been destroyed and all resources
+                                             associated with it are invalid */
     SDL_EVENT_WINDOW_FIRST = SDL_EVENT_WINDOW_SHOWN,
-    SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_DISPLAY_CHANGED,
+    SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_DESTROYED,
 
     /* Keyboard events */
     SDL_EVENT_KEY_DOWN        = 0x300, /**< Key pressed */
diff --git a/src/events/SDL_windowevents.c b/src/events/SDL_windowevents.c
index 3079f54731e5..49d282c7cd3f 100644
--- a/src/events/SDL_windowevents.c
+++ b/src/events/SDL_windowevents.c
@@ -46,7 +46,7 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent,
     if (window == NULL) {
         return 0;
     }
-    if (window->is_destroying) {
+    if (window->is_destroying && windowevent != SDL_EVENT_WINDOW_DESTROYED) {
         return 0;
     }
     switch (windowevent) {
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 7ffcff11d41b..a888e73bf105 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -3410,6 +3410,8 @@ void SDL_DestroyWindow(SDL_Window *window)
         SDL_DestroyWindow(window->first_child);
     }
 
+    SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DESTROYED, 0, 0);
+
     /* If this is a child window, unlink it from its siblings */
     if (window->parent) {
         if (window->next_sibling) {