SDL: Implemented the window flash operations for X11

From ff1b5e1bf7971fb87c77fd9b17fe05ec5a14dbba Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 24 Jul 2021 15:10:57 -0700
Subject: [PATCH] Implemented the window flash operations for X11

---
 src/video/x11/SDL_x11events.c | 13 +++++++++
 src/video/x11/SDL_x11sym.h    |  3 ++-
 src/video/x11/SDL_x11window.c | 50 +++++++++++++++++++++++++----------
 src/video/x11/SDL_x11window.h |  2 ++
 4 files changed, 53 insertions(+), 15 deletions(-)

diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 8c654f23d3..8d19a2e6d6 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -404,6 +404,9 @@ X11_DispatchFocusIn(_THIS, SDL_WindowData *data)
 #ifdef SDL_USE_IME
     SDL_IME_SetFocus(SDL_TRUE);
 #endif
+    if (data->flashing_window) {
+        X11_FlashWindow(_this, data->window, SDL_FLASH_CANCEL);
+    }
 }
 
 static void
@@ -1548,6 +1551,7 @@ X11_PumpEvents(_THIS)
 {
     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
     XEvent xevent;
+    int i;
 
     if (data->last_mode_change_deadline) {
         if (SDL_TICKS_PASSED(SDL_GetTicks(), data->last_mode_change_deadline)) {
@@ -1586,6 +1590,15 @@ X11_PumpEvents(_THIS)
 
     /* FIXME: Only need to do this when there are pending focus changes */
     X11_HandleFocusChanges(_this);
+
+    /* FIXME: Only need to do this when there are flashing windows */
+    for (i = 0; i < data->numwindows; ++i) {
+        if (data->windowlist[i] != NULL &&
+            data->windowlist[i]->flash_cancel_time &&
+            SDL_TICKS_PASSED(SDL_GetTicks(), data->windowlist[i]->flash_cancel_time)) {
+            X11_FlashWindow(_this, data->windowlist[i]->window, SDL_FLASH_CANCEL);
+        }
+    }
 }
 
 
diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h
index 6433b93106..9a46802cd6 100644
--- a/src/video/x11/SDL_x11sym.h
+++ b/src/video/x11/SDL_x11sym.h
@@ -119,8 +119,9 @@ SDL_X11_SYM(int,XSetSelectionOwner,(Display* a,Atom b,Window c,Time d),(a,b,c,d)
 SDL_X11_SYM(int,XSetTransientForHint,(Display* a,Window b,Window c),(a,b,c),return)
 SDL_X11_SYM(void,XSetTextProperty,(Display* a,Window b,XTextProperty* c,Atom d),(a,b,c,d),)
 SDL_X11_SYM(int,XSetWindowBackground,(Display* a,Window b,unsigned long c),(a,b,c),return)
-SDL_X11_SYM(void,XSetWMProperties,(Display* a,Window b,XTextProperty* c,XTextProperty* d,char** e,int f,XSizeHints* g,XWMHints* h,XClassHint* i),(a,b,c,d,e,f,g,h,i),)
+SDL_X11_SYM(void,XSetWMHints,(Display* a,Window b,XWMHints* c),(a,b,c),)
 SDL_X11_SYM(void,XSetWMNormalHints,(Display* a,Window b,XSizeHints* c),(a,b,c),)
+SDL_X11_SYM(void,XSetWMProperties,(Display* a,Window b,XTextProperty* c,XTextProperty* d,char** e,int f,XSizeHints* g,XWMHints* h,XClassHint* i),(a,b,c,d,e,f,g,h,i),)
 SDL_X11_SYM(Status,XSetWMProtocols,(Display* a,Window b,Atom* c,int d),(a,b,c,d),return)
 SDL_X11_SYM(int,XStoreColors,(Display* a,Colormap b,XColor* c,int d),(a,b,c,d),return)
 SDL_X11_SYM(int,XStoreName,(Display* a,Window b,_Xconst char* c),(a,b,c),return)
diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c
index 9a09fee48a..4ce299f03e 100644
--- a/src/video/x11/SDL_x11window.c
+++ b/src/video/x11/SDL_x11window.c
@@ -1752,23 +1752,45 @@ int
 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;
     Display *display = data->videodata->display;
+    XWMHints *wmhints;
 
-    Atom demands_attention = X11_XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 1);
-    Atom wm_state = X11_XInternAtom(display, "_NET_WM_STATE", 1);
-
-    XEvent snd_ntfy_ev = {ClientMessage};
-    snd_ntfy_ev.xclient.window = data->xwindow;
-    snd_ntfy_ev.xclient.message_type = wm_state;
-    snd_ntfy_ev.xclient.format = 32;
-    snd_ntfy_ev.xclient.data.l[0] = 1; /* _NET_WM_STATE_ADD */
-    snd_ntfy_ev.xclient.data.l[1] = demands_attention;
-    snd_ntfy_ev.xclient.data.l[2] = 0;
-    snd_ntfy_ev.xclient.data.l[3] = 1; /* normal application */
-    snd_ntfy_ev.xclient.data.l[4] = 0;
-    X11_XSendEvent(display, RootWindow(display, displaydata->screen), False, SubstructureNotifyMask | SubstructureRedirectMask, &snd_ntfy_ev);
+    wmhints = X11_XGetWMHints(display, data->xwindow);
+    if (!wmhints) {
+        return SDL_SetError("Couldn't get WM hints");
+    }
+
+    wmhints->flags &= ~XUrgencyHint;
+    data->flashing_window = SDL_FALSE;
+    data->flash_cancel_time = 0;
+
+    switch (operation) {
+    case SDL_FLASH_CANCEL:
+        /* Taken care of above */
+        break;
+    case SDL_FLASH_BRIEFLY:
+        if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
+            wmhints->flags |= XUrgencyHint;
+            data->flashing_window = SDL_TRUE;
+            /* On Ubuntu 21.04 this causes a dialog to pop up, so leave it up for a full second so users can see it */
+            data->flash_cancel_time = SDL_GetTicks() + 1000;
+            if (!data->flash_cancel_time) {
+                data->flash_cancel_time = 1;
+            }
+        }
+        break;
+    case SDL_FLASH_UNTIL_FOCUSED:
+        if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
+            wmhints->flags |= XUrgencyHint;
+            data->flashing_window = SDL_TRUE;
+        }
+        break;
+    default:
+        break;
+    }
 
+    X11_XSetWMHints(display, data->xwindow, wmhints);
+    X11_XFree(wmhints);
     return 0;
 }
 
diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h
index 25d12dd8d2..3b2da5d2b1 100644
--- a/src/video/x11/SDL_x11window.h
+++ b/src/video/x11/SDL_x11window.h
@@ -68,6 +68,8 @@ typedef struct
     unsigned long user_time;
     Atom xdnd_req;
     Window xdnd_source;
+    SDL_bool flashing_window;
+    Uint32 flash_cancel_time;
 #if SDL_VIDEO_OPENGL_EGL  
     EGLSurface egl_surface;
 #endif