SDL: emscripten, wayland, x11: Share the table of CSS cursor names

From b986bc8be926245fd202edd85621fbac376f03cb Mon Sep 17 00:00:00 2001
From: Simon McVittie <[EMAIL REDACTED]>
Date: Fri, 16 Feb 2024 12:20:21 +0000
Subject: [PATCH] emscripten, wayland, x11: Share the table of CSS cursor names

As suggested in #8939.

This results in some minor changes for emscripten and x11. Both
previously mapped SIZEALL to "move", but "move" is not guaranteed to be
a four-pointed arrow: according to the CSS spec, it's actually intended
to be a drag-and-drop cursor, analogous to "alias" and "copy".
Map it to "all-scroll" instead, as in Wayland: while this is *also* not
semantically guaranteed to be a four-pointed arrow, it is at least
*suggested* to make it a four-pointed arrow.

Also, emscripten was previously using two-pointed arrows for resizing
(BOTTOMLEFT mapped to "nesw-resize", and so on). This commit changes
it to use the more specific "sw-resize" and so on, which might be
single-directional.

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 src/video/SDL_video.c                      | 87 ++++++++++++++++++++++
 src/video/SDL_video_c.h                    |  4 +
 src/video/emscripten/SDL_emscriptenmouse.c | 69 +----------------
 src/video/wayland/SDL_waylandmouse.c       | 77 +------------------
 src/video/x11/SDL_x11mouse.c               | 23 +-----
 5 files changed, 97 insertions(+), 163 deletions(-)

diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 89076d6aa6fd..8fe9c9a0bef5 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -5276,3 +5276,90 @@ void *SDL_Metal_GetLayer(SDL_MetalView view)
         return NULL;
     }
 }
+
+#if defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_EMSCRIPTEN)
+const char *SDL_GetCSSCursorName(SDL_SystemCursor id, const char **fallback_name)
+{
+    /* Reference: https://www.w3.org/TR/css-ui-4/#cursor */
+    /* Also in: https://www.freedesktop.org/wiki/Specifications/cursor-spec/ */
+    switch (id) {
+    case SDL_SYSTEM_CURSOR_ARROW:
+        return "default";
+
+    case SDL_SYSTEM_CURSOR_IBEAM:
+        return "text";
+
+    case SDL_SYSTEM_CURSOR_WAIT:
+        return "wait";
+
+    case SDL_SYSTEM_CURSOR_CROSSHAIR:
+        return "crosshair";
+
+    case SDL_SYSTEM_CURSOR_WAITARROW:
+        return "progress";
+
+    case SDL_SYSTEM_CURSOR_SIZENWSE:
+        if (fallback_name) {
+            /* only a single arrow */
+            *fallback_name = "nw-resize";
+        }
+        return "nwse-resize";
+
+    case SDL_SYSTEM_CURSOR_SIZENESW:
+        if (fallback_name) {
+            /* only a single arrow */
+            *fallback_name = "ne-resize";
+        }
+        return "nesw-resize";
+
+    case SDL_SYSTEM_CURSOR_SIZEWE:
+        if (fallback_name) {
+            *fallback_name = "col-resize";
+        }
+        return "ew-resize";
+
+    case SDL_SYSTEM_CURSOR_SIZENS:
+        if (fallback_name) {
+            *fallback_name = "row-resize";
+        }
+        return "ns-resize";
+
+    case SDL_SYSTEM_CURSOR_SIZEALL:
+        return "all-scroll";
+
+    case SDL_SYSTEM_CURSOR_NO:
+        return "not-allowed";
+
+    case SDL_SYSTEM_CURSOR_HAND:
+        return "pointer";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT:
+        return "nw-resize";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_TOP:
+        return "n-resize";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT:
+        return "ne-resize";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_RIGHT:
+        return "e-resize";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT:
+        return "se-resize";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM:
+        return "s-resize";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT:
+        return "sw-resize";
+
+    case SDL_SYSTEM_CURSOR_WINDOW_LEFT:
+        return "w-resize";
+
+    default:
+        SDL_assert(0);
+        return "default";
+    }
+}
+#endif
diff --git a/src/video/SDL_video_c.h b/src/video/SDL_video_c.h
index ceacd4ccf4c3..7e06bde8d77b 100644
--- a/src/video/SDL_video_c.h
+++ b/src/video/SDL_video_c.h
@@ -59,4 +59,8 @@ extern int SDL_SetWindowTextureVSync(SDL_Window *window, int vsync);
 
 extern int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a);
 
+#if defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_EMSCRIPTEN)
+const char *SDL_GetCSSCursorName(SDL_SystemCursor id, const char **fallback_name);
+#endif
+
 #endif /* SDL_video_c_h_ */
diff --git a/src/video/emscripten/SDL_emscriptenmouse.c b/src/video/emscripten/SDL_emscriptenmouse.c
index e6beab7ecd18..0593fc636845 100644
--- a/src/video/emscripten/SDL_emscriptenmouse.c
+++ b/src/video/emscripten/SDL_emscriptenmouse.c
@@ -29,6 +29,7 @@
 #include "SDL_emscriptenmouse.h"
 #include "SDL_emscriptenvideo.h"
 
+#include "../SDL_video_c.h"
 #include "../../events/SDL_mouse_c.h"
 
 /* older Emscriptens don't have this, but we need to for wasm64 compatibility. */
@@ -117,73 +118,7 @@ static SDL_Cursor *Emscripten_CreateCursor(SDL_Surface *surface, int hot_x, int
 
 static SDL_Cursor *Emscripten_CreateSystemCursor(SDL_SystemCursor id)
 {
-    const char *cursor_name = NULL;
-
-    switch (id) {
-    case SDL_SYSTEM_CURSOR_ARROW:
-        cursor_name = "default";
-        break;
-    case SDL_SYSTEM_CURSOR_IBEAM:
-        cursor_name = "text";
-        break;
-    case SDL_SYSTEM_CURSOR_WAIT:
-        cursor_name = "wait";
-        break;
-    case SDL_SYSTEM_CURSOR_CROSSHAIR:
-        cursor_name = "crosshair";
-        break;
-    case SDL_SYSTEM_CURSOR_WAITARROW:
-        cursor_name = "progress";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZENWSE:
-        cursor_name = "nwse-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZENESW:
-        cursor_name = "nesw-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZEWE:
-        cursor_name = "ew-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZENS:
-        cursor_name = "ns-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZEALL:
-        cursor_name = "move";
-        break;
-    case SDL_SYSTEM_CURSOR_NO:
-        cursor_name = "not-allowed";
-        break;
-    case SDL_SYSTEM_CURSOR_HAND:
-        cursor_name = "pointer";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT:
-        cursor_name = "nwse-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_TOP:
-        cursor_name = "ns-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT:
-        cursor_name = "nesw-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_RIGHT:
-        cursor_name = "ew-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT:
-        cursor_name = "nwse-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM:
-        cursor_name = "ns-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT:
-        cursor_name = "nesw-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_LEFT:
-        cursor_name = "ew-resize";
-        break;
-    default:
-        SDL_assert(0);
-        return NULL;
-    }
+    const char *cursor_name = SDL_GetCSSCursorName(id, NULL);
 
     return Emscripten_CreateCursorFromString(cursor_name, SDL_FALSE);
 }
diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index 841d3da58b1c..a8416c5e0cf1 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -30,6 +30,7 @@
 #include <limits.h>
 
 #include "../SDL_sysvideo.h"
+#include "../SDL_video_c.h"
 
 #include "../../events/SDL_mouse_c.h"
 #include "SDL_waylandvideo.h"
@@ -318,81 +319,7 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa
         vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme;
     }
 
-    /* Next, find the cursor from the theme. Names taken from: */
-    /*   https://www.w3.org/TR/css-ui-4/#cursor */
-    /*   https://www.freedesktop.org/wiki/Specifications/cursor-spec/ */
-    switch (cdata->system_cursor) {
-    case SDL_SYSTEM_CURSOR_ARROW:
-        css_name = "default";
-        break;
-    case SDL_SYSTEM_CURSOR_IBEAM:
-        css_name = "text";
-        break;
-    case SDL_SYSTEM_CURSOR_WAIT:
-        css_name = "wait";
-        break;
-    case SDL_SYSTEM_CURSOR_CROSSHAIR:
-        css_name = "crosshair";
-        break;
-    case SDL_SYSTEM_CURSOR_WAITARROW:
-        css_name = "progress";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZENWSE:
-        css_name = "nwse-resize";
-        /* only a single arrow */
-        fallback_name = "nw-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZENESW:
-        css_name = "nesw-resize";
-        /* only a single arrow */
-        fallback_name = "ne-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZEWE:
-        css_name = "ew-resize";
-        fallback_name = "col-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZENS:
-        css_name = "ns-resize";
-        fallback_name = "row-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_SIZEALL:
-        css_name = "all-scroll";
-        break;
-    case SDL_SYSTEM_CURSOR_NO:
-        css_name = "not-allowed";
-        break;
-    case SDL_SYSTEM_CURSOR_HAND:
-        css_name = "pointer";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT:
-        css_name = "nw-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_TOP:
-        css_name = "n-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT:
-        css_name = "ne-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_RIGHT:
-        css_name = "e-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT:
-        css_name = "se-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM:
-        css_name = "s-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT:
-        css_name = "sw-resize";
-        break;
-    case SDL_SYSTEM_CURSOR_WINDOW_LEFT:
-        css_name = "w-resize";
-        break;
-    default:
-        SDL_assert(0);
-        return SDL_FALSE;
-    }
-
+    css_name = SDL_GetCSSCursorName(cdata->system_cursor, &fallback_name);
     cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, css_name);
     if (!cursor && fallback_name) {
         cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, fallback_name);
diff --git a/src/video/x11/SDL_x11mouse.c b/src/video/x11/SDL_x11mouse.c
index de503e09b980..d7c9e3ed4eda 100644
--- a/src/video/x11/SDL_x11mouse.c
+++ b/src/video/x11/SDL_x11mouse.c
@@ -26,6 +26,7 @@
 #include "SDL_x11video.h"
 #include "SDL_x11mouse.h"
 #include "SDL_x11xinput2.h"
+#include "../SDL_video_c.h"
 #include "../../events/SDL_mouse_c.h"
 
 /* FIXME: Find a better place to put this... */
@@ -216,7 +217,7 @@ static SDL_Cursor *X11_CreateSystemCursor(SDL_SystemCursor id)
 {
     SDL_Cursor *cursor = NULL;
     unsigned int shape = 0;
-    const char *xcursorname = NULL;
+    const char *xcursorname = SDL_GetCSSCursorName(id, NULL);
 
     switch (id) {
     default:
@@ -226,83 +227,63 @@ static SDL_Cursor *X11_CreateSystemCursor(SDL_SystemCursor id)
     /*   http://tronche.com/gui/x/xlib/appendix/b/ */
     case SDL_SYSTEM_CURSOR_ARROW:
         shape = XC_left_ptr;
-        xcursorname = "default";
         break;
     case SDL_SYSTEM_CURSOR_IBEAM:
         shape = XC_xterm;
-        xcursorname = "text";
         break;
     case SDL_SYSTEM_CURSOR_WAIT:
         shape = XC_watch;
-        xcursorname = "wait";
         break;
     case SDL_SYSTEM_CURSOR_CROSSHAIR:
         shape = XC_tcross;
-        xcursorname = "crosshair";
         break;
     case SDL_SYSTEM_CURSOR_WAITARROW:
         shape = XC_watch;
-        xcursorname = "progress";
         break;
     case SDL_SYSTEM_CURSOR_SIZENWSE:
         shape = XC_top_left_corner;
-        xcursorname = "nwse-resize";
         break;
     case SDL_SYSTEM_CURSOR_SIZENESW:
         shape = XC_top_right_corner;
-        xcursorname = "nesw-resize";
         break;
     case SDL_SYSTEM_CURSOR_SIZEWE:
         shape = XC_sb_h_double_arrow;
-        xcursorname = "ew-resize";
         break;
     case SDL_SYSTEM_CURSOR_SIZENS:
         shape = XC_sb_v_double_arrow;
-        xcursorname = "ns-resize";
         break;
     case SDL_SYSTEM_CURSOR_SIZEALL:
         shape = XC_fleur;
-        xcursorname = "move";
         break;
     case SDL_SYSTEM_CURSOR_NO:
         shape = XC_pirate;
-        xcursorname = "not-allowed";
         break;
     case SDL_SYSTEM_CURSOR_HAND:
         shape = XC_hand2;
-        xcursorname = "pointer";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_TOPLEFT:
         shape = XC_top_left_corner;
-        xcursorname = "nw-resize";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_TOP:
         shape = XC_top_side;
-        xcursorname = "n-resize";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_TOPRIGHT:
         shape = XC_top_right_corner;
-        xcursorname = "ne-resize";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_RIGHT:
         shape = XC_right_side;
-        xcursorname = "e-resize";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMRIGHT:
         shape = XC_bottom_right_corner;
-        xcursorname = "se-resize";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_BOTTOM:
         shape = XC_bottom_side;
-        xcursorname = "s-resize";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_BOTTOMLEFT:
         shape = XC_bottom_left_corner;
-        xcursorname = "sw-resize";
         break;
     case SDL_SYSTEM_CURSOR_WINDOW_LEFT:
         shape = XC_left_side;
-        xcursorname = "w-resize";
         break;
     }