SDL: Added a window flash operation to be explicit about window flash behavior

From f1633127d1c88f33179a5ddc8fae3eed7157b4b1 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 24 Jul 2021 13:41:55 -0700
Subject: [PATCH] Added a window flash operation to be explicit about window
 flash behavior

---
 include/SDL_hints.h                   |  9 ---------
 include/SDL_test_common.h             |  1 +
 include/SDL_video.h                   | 16 +++++++++++++++-
 src/dynapi/SDL_dynapi_procs.h         |  2 +-
 src/test/SDL_test_common.c            | 18 ++++++++++++++++--
 src/video/SDL_sysvideo.h              |  2 +-
 src/video/SDL_video.c                 |  4 ++--
 src/video/cocoa/SDL_cocoawindow.h     |  3 ++-
 src/video/cocoa/SDL_cocoawindow.m     | 23 +++++++++++++++++++++--
 src/video/uikit/SDL_uikitevents.m     |  2 +-
 src/video/wayland/SDL_waylandwindow.c |  2 +-
 src/video/wayland/SDL_waylandwindow.h |  2 +-
 src/video/windows/SDL_windowswindow.c | 21 ++++++++++++++-------
 src/video/windows/SDL_windowswindow.h |  2 +-
 src/video/x11/SDL_x11window.c         |  2 +-
 src/video/x11/SDL_x11window.h         |  2 +-
 16 files changed, 79 insertions(+), 32 deletions(-)

diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index eaed22a00..549ed6626 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1440,15 +1440,6 @@ extern "C" {
  */
 #define SDL_HINT_WAVE_TRUNCATION   "SDL_WAVE_TRUNCATION"
 
-/**
- *  \brief  Controls the number of times a window flashes with SDL_FlashWindow()
- *
- *  On Windows, if this variable is set, the SDL_FlashWindow() call will flash
- *  the specified number of times. Otherwise the window will flash until it
- *  becomes the foreground window.
- */
-#define SDL_HINT_WINDOW_FLASH_COUNT "SDL_WINDOW_FLASH_COUNT"
-
 /**
  * \brief Tell SDL not to name threads on Windows with the 0x406D1388 Exception.
  *        The 0x406D1388 Exception is a trick used to inform Visual Studio of a
diff --git a/include/SDL_test_common.h b/include/SDL_test_common.h
index ff6c97574..97f036d23 100644
--- a/include/SDL_test_common.h
+++ b/include/SDL_test_common.h
@@ -64,6 +64,7 @@ typedef struct
     const char *window_title;
     const char *window_icon;
     Uint32 window_flags;
+    SDL_bool flash_on_focus_loss;
     int window_x;
     int window_y;
     int window_w;
diff --git a/include/SDL_video.h b/include/SDL_video.h
index afced02c7..73bc660cc 100644
--- a/include/SDL_video.h
+++ b/include/SDL_video.h
@@ -188,6 +188,9 @@ typedef enum
     SDL_DISPLAYEVENT_DISCONNECTED   /**< Display has been removed from the system */
 } SDL_DisplayEventID;
 
+/**
+ *  \brief Display orientation
+ */
 typedef enum
 {
     SDL_ORIENTATION_UNKNOWN,            /**< The display orientation can't be determined */
@@ -197,6 +200,16 @@ typedef enum
     SDL_ORIENTATION_PORTRAIT_FLIPPED    /**< The display is in portrait mode, upside down */
 } SDL_DisplayOrientation;
 
+/**
+ *  \brief Window flash operation
+ */
+typedef enum
+{
+    SDL_FLASH_CANCEL,                   /**< Cancel any window flash state */
+    SDL_FLASH_BRIEFLY,                  /**< Flash the window briefly to get attention */
+    SDL_FLASH_UNTIL_FOCUSED,            /**< Flash the window until it gets focus */
+} SDL_FlashOperation;
+
 /**
  *  \brief An opaque handle to an OpenGL context.
  */
@@ -1511,10 +1524,11 @@ extern DECLSPEC int SDLCALL SDL_SetWindowHitTest(SDL_Window * window,
  * Request a window to demand attention from the user.
  *
  * \param window the window to be flashed
+ * \param the flash operation
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
  */
-extern DECLSPEC int SDLCALL SDL_FlashWindow(SDL_Window * window);
+extern DECLSPEC int SDLCALL SDL_FlashWindow(SDL_Window * window, SDL_FlashOperation operation);
 
 /**
  * Destroy a window.
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index d6ff46353..f878b5e77 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -876,6 +876,6 @@ SDL_DYNAPI_PROC(int,SDL_AndroidShowToast,(const char *a, int b, int c, int d, in
 SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceSpec,(int a, int b, SDL_AudioSpec *c),(a,b,c),return)
 SDL_DYNAPI_PROC(void,SDL_TLSCleanup,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_SetWindowAlwaysOnTop,(SDL_Window *a, SDL_bool b),(a,b),)
-SDL_DYNAPI_PROC(int,SDL_FlashWindow,(SDL_Window *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_FlashWindow,(SDL_Window *a, SDL_FlashOperation b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_GameControllerSendEffect,(SDL_GameController *a, const void *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_JoystickSendEffect,(SDL_Joystick *a, const void *b, int c),(a,b,c),return)
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index 94ec47c3e..5fea48ff0 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -37,7 +37,7 @@ static const char *video_usage[] = {
     "[--scale N]", "[--depth N]", "[--refresh R]", "[--vsync]", "[--noframe]",
     "[--resizable]", "[--minimize]", "[--maximize]", "[--grab]", "[--keyboard-grab]",
     "[--shown]", "[--hidden]", "[--input-focus]", "[--mouse-focus]",
-    "[--allow-highdpi]", "[--usable-bounds]"
+    "[--flash-on-focus-loss]", "[--allow-highdpi]", "[--usable-bounds]"
 };
 
 static const char *audio_usage[] = {
@@ -441,6 +441,10 @@ SDLTest_CommonArg(SDLTest_CommonState * state, int index)
         state->window_flags |= SDL_WINDOW_MOUSE_FOCUS;
         return 1;
     }
+    if (SDL_strcasecmp(argv[index], "--flash-on-focus-loss") == 0) {
+        state->flash_on_focus_loss = SDL_TRUE;
+        return 1;
+    }
     if (SDL_strcasecmp(argv[index], "--grab") == 0) {
         state->window_flags |= SDL_WINDOW_MOUSE_GRABBED;
         return 1;
@@ -1808,6 +1812,16 @@ SDLTest_CommonEvent(SDLTest_CommonState * state, SDL_Event * event, int *done)
                 }
             }
             break;
+        case SDL_WINDOWEVENT_FOCUS_LOST:
+            if (state->flash_on_focus_loss) {
+                SDL_Window *window = SDL_GetWindowFromID(event->window.windowID);
+                if (window) {
+                    SDL_FlashWindow(window, SDL_FLASH_UNTIL_FOCUSED);
+                }
+            }
+            break;
+        default:
+            break;
         }
         break;
     case SDL_KEYDOWN: {
@@ -1963,7 +1977,7 @@ SDLTest_CommonEvent(SDLTest_CommonState * state, SDL_Event * event, int *done)
                 /* Ctrl-F flash the window */
                 SDL_Window *window = SDL_GetWindowFromID(event->key.windowID);
                 if (window) {
-                    SDL_FlashWindow(window);
+                    SDL_FlashWindow(window, SDL_FLASH_BRIEFLY);
                 }
             }
             break;
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index 50010a23f..0be19c128 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -240,7 +240,7 @@ struct SDL_VideoDevice
     int (*UpdateWindowFramebuffer) (_THIS, SDL_Window * window, const SDL_Rect * rects, int numrects);
     void (*DestroyWindowFramebuffer) (_THIS, SDL_Window * window);
     void (*OnWindowEnter) (_THIS, SDL_Window * window);
-    int (*FlashWindow) (_THIS, SDL_Window * window);
+    int (*FlashWindow) (_THIS, SDL_Window * window, SDL_FlashOperation operation);
 
     /* * * */
     /*
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 49ebbc231..47f19b188 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -2793,12 +2793,12 @@ SDL_GetGrabbedWindow(void)
 }
 
 int
-SDL_FlashWindow(SDL_Window * window)
+SDL_FlashWindow(SDL_Window * window, SDL_FlashOperation operation)
 {
     CHECK_WINDOW_MAGIC(window, -1);
 
     if (_this->FlashWindow) {
-        return _this->FlashWindow(_this, window);
+        return _this->FlashWindow(_this, window, operation);
     }
 
     return SDL_Unsupported();
diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h
index ed53fecc2..e14bb1d47 100644
--- a/src/video/cocoa/SDL_cocoawindow.h
+++ b/src/video/cocoa/SDL_cocoawindow.h
@@ -117,6 +117,7 @@ struct SDL_WindowData
     NSMutableArray *nscontexts;
     SDL_bool created;
     SDL_bool inWindowFullscreenTransition;
+    NSInteger flash_request;
     Cocoa_WindowListener *listener;
     struct SDL_VideoData *videodata;
 #if SDL_VIDEO_OPENGL_EGL
@@ -151,7 +152,7 @@ extern void Cocoa_DestroyWindow(_THIS, SDL_Window * window);
 extern SDL_bool Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info);
 extern int Cocoa_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 extern void Cocoa_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept);
-extern int Cocoa_FlashWindow(_THIS, SDL_Window * window);
+extern int Cocoa_FlashWindow(_THIS, SDL_Window * window, SDL_FlashOperation operation);
 
 #endif /* SDL_cocoawindow_h_ */
 
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index 43aad092f..4586073b5 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -2117,11 +2117,30 @@ take effect properly (e.g. setting the window size, etc.)
 }
 
 int
-Cocoa_FlashWindow(_THIS, SDL_Window *window)
+Cocoa_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation)
 { @autoreleasepool
 {
     /* Note that this is app-wide and not window-specific! */
-    [NSApp requestUserAttention:NSInformationalRequest];
+    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+
+    if (data->flash_request) {
+        [NSApp cancelUserAttentionRequest:data->flash_request];
+        data->flash_request = 0;
+    }
+
+    switch (operation) {
+    case SDL_FLASH_CANCEL:
+        /* Canceled above */
+        break;
+    case SDL_FLASH_BRIEFLY:
+        data->flash_request = [NSApp requestUserAttention:NSInformationalRequest];
+        break;
+    case SDL_FLASH_UNTIL_FOCUSED:
+        data->flash_request = [NSApp requestUserAttention:NSCriticalRequest];
+        break;
+    default:
+        return SDL_Unsupported();
+    }
     return 0;
 }}
 
diff --git a/src/video/uikit/SDL_uikitevents.m b/src/video/uikit/SDL_uikitevents.m
index 54f88879c..6d78685d2 100644
--- a/src/video/uikit/SDL_uikitevents.m
+++ b/src/video/uikit/SDL_uikitevents.m
@@ -196,7 +196,7 @@ static void UpdateMouseGrab()
 
 static int SetGCMouseRelativeMode(SDL_bool enabled)
 {
-	mouse_relative_mode = enabled;
+    mouse_relative_mode = enabled;
     UpdateMouseGrab();
     return 0;
 }
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 773cc461e..960e3c927 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -920,7 +920,7 @@ Wayland_RaiseWindow(_THIS, SDL_Window *window)
 }
 
 int
-Wayland_FlashWindow(_THIS, SDL_Window *window)
+Wayland_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation)
 {
     Wayland_activate_window(_this->driverdata,
                             window->driverdata,
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 873ea0270..5956cf352 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -113,7 +113,7 @@ extern void Wayland_SuspendScreenSaver(_THIS);
 extern SDL_bool
 Wayland_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info);
 extern int Wayland_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
-extern int Wayland_FlashWindow(_THIS, SDL_Window * window);
+extern int Wayland_FlashWindow(_THIS, SDL_Window * window, SDL_FlashOperation operation);
 
 extern void Wayland_HandlePendingResize(SDL_Window *window);
 
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index 96eb67d0c..93d1903a7 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -1064,19 +1064,26 @@ WIN_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept)
 }
 
 int
-WIN_FlashWindow(_THIS, SDL_Window * window)
+WIN_FlashWindow(_THIS, SDL_Window * window, SDL_FlashOperation operation)
 {
     FLASHWINFO desc;
-    const char *hint = SDL_GetHint(SDL_HINT_WINDOW_FLASH_COUNT);
 
     SDL_zero(desc);
     desc.cbSize = sizeof(desc);
     desc.hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
-    desc.dwFlags = FLASHW_TRAY;
-    if (hint && *hint) {
-        desc.uCount = SDL_atoi(hint);
-    } else {
-        desc.dwFlags |= FLASHW_TIMERNOFG;
+    switch (operation) {
+    case SDL_FLASH_CANCEL:
+        desc.dwFlags = FLASHW_STOP;
+        break;
+    case SDL_FLASH_BRIEFLY:
+        desc.dwFlags = FLASHW_TRAY;
+        desc.uCount = 1;
+        break;
+    case SDL_FLASH_UNTIL_FOCUSED:
+        desc.dwFlags = (FLASHW_TRAY | FLASHW_TIMERNOFG);
+        break;
+    default:
+        return SDL_Unsupported();
     }
 
     FlashWindowEx(&desc);
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index 335326a76..700566e9c 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -86,7 +86,7 @@ extern void WIN_OnWindowEnter(_THIS, SDL_Window * window);
 extern void WIN_UpdateClipCursor(SDL_Window *window);
 extern int WIN_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 extern void WIN_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept);
-extern int WIN_FlashWindow(_THIS, SDL_Window * window);
+extern int WIN_FlashWindow(_THIS, SDL_Window * window, SDL_FlashOperation operation);
 
 #endif /* SDL_windowswindow_h_ */
 
diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c
index d041dd991..9a09fee48 100644
--- a/src/video/x11/SDL_x11window.c
+++ b/src/video/x11/SDL_x11window.c
@@ -1749,7 +1749,7 @@ X11_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept)
 }
 
 int
-X11_FlashWindow(_THIS, SDL_Window * window)
+X11_FlashWindow(_THIS, SDL_Window * window, SDL_FlashOperation operation)
 {
     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
     SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h
index ad465636d..25d12dd8d 100644
--- a/src/video/x11/SDL_x11window.h
+++ b/src/video/x11/SDL_x11window.h
@@ -107,7 +107,7 @@ extern SDL_bool X11_GetWindowWMInfo(_THIS, SDL_Window * window,
                                     struct SDL_SysWMinfo *info);
 extern int X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 extern void X11_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept);
-extern int X11_FlashWindow(_THIS, SDL_Window * window);
+extern int X11_FlashWindow(_THIS, SDL_Window * window, SDL_FlashOperation operation);
 
 #endif /* SDL_x11window_h_ */