SDL: Add notification framework with test and dummy driver

From 068e4059124d8bf5fa478d404e66f73527eeab52 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Mon, 6 Apr 2026 12:42:57 -0400
Subject: [PATCH] Add notification framework with test and dummy driver

---
 Android.mk                                    |   2 +
 CMakeLists.txt                                |  41 ++-
 VisualC-GDK/SDL/SDL.vcxproj                   |   5 +
 include/SDL3/SDL.h                            |   1 +
 include/SDL3/SDL_events.h                     |  22 ++
 include/SDL3/SDL_notification.h               | 254 ++++++++++++++++++
 src/SDL.c                                     |   6 +-
 src/dynapi/SDL_dynapi.exports                 |   4 +
 src/dynapi/SDL_dynapi.sym                     |   4 +
 src/dynapi/SDL_dynapi_overrides.h             |   4 +
 src/dynapi/SDL_dynapi_procs.h                 |   4 +
 src/events/SDL_categories.c                   |   3 +
 src/events/SDL_categories_c.h                 |   1 +
 src/events/SDL_events.c                       |   5 +
 src/events/SDL_notificationevents.c           |  41 +++
 src/events/SDL_notificationevents_c.h         |  30 +++
 src/notification/SDL_notification.c           |  85 ++++++
 src/notification/SDL_notification_c.h         |  34 +++
 .../dummy/SDL_dummynotification.c             |  51 ++++
 src/test/SDL_test_common.c                    |   4 +
 test/CMakeLists.txt                           |   1 +
 test/testnotification.c                       | 160 +++++++++++
 22 files changed, 759 insertions(+), 3 deletions(-)
 create mode 100644 include/SDL3/SDL_notification.h
 create mode 100644 src/events/SDL_notificationevents.c
 create mode 100644 src/events/SDL_notificationevents_c.h
 create mode 100644 src/notification/SDL_notification.c
 create mode 100644 src/notification/SDL_notification_c.h
 create mode 100644 src/notification/dummy/SDL_dummynotification.c
 create mode 100644 test/testnotification.c

diff --git a/Android.mk b/Android.mk
index d53bf403b1611..6959288468943 100644
--- a/Android.mk
+++ b/Android.mk
@@ -59,6 +59,8 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/main/generic/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/misc/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/misc/android/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/notification/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/notification/dummy/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/power/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/power/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/process/*.c) \
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 45892a81f745e..5c61b5383259a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -268,6 +268,7 @@ define_sdl_subsystem(Power)
 define_sdl_subsystem(Sensor)
 define_sdl_subsystem(Dialog)
 define_sdl_subsystem(Tray)
+define_sdl_subsystem(Notification)
 
 cmake_dependent_option(SDL_FRAMEWORK "Build SDL libraries as Apple Framework" OFF "APPLE" OFF)
 if(SDL_FRAMEWORK)
@@ -460,6 +461,7 @@ if (NGAGE)
   set(SDL_DUMMYAUDIO       OFF)
   set(SDL_DUMMYCAMERA      OFF)
   set(SDL_DUMMYVIDEO       OFF)
+  set(SDL_NOTIFICATION     OFF)
   set(SDL_OFFSCREEN        OFF)
   set(SDL_RENDER_GPU       OFF)
   set(SDL_TRAY             OFF)
@@ -1327,6 +1329,8 @@ sdl_glob_sources(
   "${SDL3_SOURCE_DIR}/src/main/*.h"
   "${SDL3_SOURCE_DIR}/src/misc/*.c"
   "${SDL3_SOURCE_DIR}/src/misc/*.h"
+  "${SDL3_SOURCE_DIR}/src/notification/*.c"
+  "${SDL3_SOURCE_DIR}/src/notification/*.h"
   "${SDL3_SOURCE_DIR}/src/power/*.c"
   "${SDL3_SOURCE_DIR}/src/power/*.h"
   "${SDL3_SOURCE_DIR}/src/render/*.c"
@@ -2055,6 +2059,13 @@ elseif(UNIX AND NOT (APPLE OR RISCOS OR HAIKU OR CYGWIN))
         "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.c"
         "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.h"
       )
+
+      if(SDL_NOTIFICATION)
+        sdl_sources(
+          "${SDL3_SOURCE_DIR}/src/notification/unix/SDL_dbusnotification.c"
+        )
+        set(HAVE_SDL_NOTIFICATION TRUE)
+      endif()
     endif()
 
     if(SDL_USE_IME)
@@ -2316,6 +2327,7 @@ elseif(WINDOWS OR CYGWIN)
   check_include_file(audioclient.h HAVE_AUDIOCLIENT_H)
   check_include_file(sensorsapi.h HAVE_SENSORSAPI_H)
   check_include_file(shellscalingapi.h HAVE_SHELLSCALINGAPI_H)
+  check_include_file(Windows.ui.notifications.h HAVE_WINDOWS_UI_NOTIFICATIONS_H)
   check_c_source_compiles("
     #include <windows.h>
     #include <mfapi.h>
@@ -2425,7 +2437,7 @@ elseif(WINDOWS OR CYGWIN)
   set(HAVE_SDL_STORAGE 1)
 
   # Libraries for Win32 native and MinGW
-  sdl_link_dependency(base LIBS kernel32 user32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32 hid)
+  sdl_link_dependency(base LIBS kernel32 user32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32 hid mincore)
 
   set(SDL_TIME_WINDOWS 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/windows/*.c")
@@ -2474,6 +2486,11 @@ elseif(WINDOWS OR CYGWIN)
     set(HAVE_SDL_TRAY TRUE)
   endif()
 
+  if(SDL_NOTIFICATION AND HAVE_WINDOWS_UI_NOTIFICATIONS_H)
+    sdl_glob_sources("${SDL3_SOURCE_DIR}/src/notification/windows/*.c")
+    set(HAVE_SDL_NOTIFICATION TRUE)
+  endif()
+
   if(SDL_HIDAPI)
     CheckHIDAPI()
   endif()
@@ -2650,6 +2667,11 @@ elseif(APPLE)
     set(HAVE_SDL_HAPTIC TRUE)
   endif()
 
+  if(SDL_NOTIFICATION)
+    set(SDL_FRAMEWORK_SECURITY 1)
+    set(SDL_FRAMEWORK_USERNOTIFICATIONS 1)
+  endif()
+
   if(SDL_POWER)
     if (IOS OR TVOS OR VISIONOS OR WATCHOS)
       sdl_glob_sources(
@@ -2680,6 +2702,10 @@ elseif(APPLE)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/cocoa/*.m")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  set(SDL_NOTIFICATION_COCOA 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/notification/cocoa/*.m")
+  set(HAVE_SDL_NOTIFICATION TRUE)
+
   # TODO: SDL_STORAGE_ICLOUD
   set(SDL_STORAGE_GENERIC 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c")
@@ -2842,12 +2868,19 @@ elseif(APPLE)
   if(SDL_FRAMEWORK_METAL)
     sdl_link_dependency(metal LIBS "$<LINK_LIBRARY:FRAMEWORK,Metal>" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,Metal")
   endif()
+  if(SDL_FRAMEWORK_USERNOTIFICATIONS)
+    find_library(USERNOTIFICATIONS UserNotifications)
+    sdl_link_dependency(usernotifications LIBS "$<LINK_LIBRARY:FRAMEWORK,UserNotifications>" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,UserNotifications")
+  endif()
   if(SDL_FRAMEWORK_OPENGLES)
     sdl_link_dependency(opengles LIBS "$<LINK_LIBRARY:FRAMEWORK,OpenGLES>" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,OpenGLES")
   endif()
   if(SDL_FRAMEWORK_QUARTZCORE)
     sdl_link_dependency(quartz_core LIBS "$<LINK_LIBRARY:FRAMEWORK,QuartzCore>" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,QuartzCore")
   endif()
+  if(SDL_FRAMEWORK_SECURITY)
+    sdl_link_dependency(quartz_core LIBS "$<LINK_LIBRARY:FRAMEWORK,Security>" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,Security")
+  endif()
   if(SDL_FRAMEWORK_UIKIT)
     sdl_link_dependency(ui_kit LIBS "$<LINK_LIBRARY:FRAMEWORK,UIKit>" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,UIKit")
   endif()
@@ -3754,6 +3787,12 @@ if(NOT HAVE_CAMERA)
     "${SDL3_SOURCE_DIR}/src/camera/dummy/*.h"
   )
 endif()
+if(NOT HAVE_SDL_NOTIFICATION)
+  sdl_glob_sources(
+     "${SDL3_SOURCE_DIR}/src/notification/dummy/*.c"
+     "${SDL3_SOURCE_DIR}/src/notification/dummy/*.h"
+  )
+endif()
 
 # We always need to have threads and timers around
 if(NOT HAVE_SDL_THREADS)
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 4ecdb0fb457f2..d09e24c5d07a6 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -452,6 +452,7 @@
     <ClInclude Include="..\..\src\events\SDL_keyboard_c.h" />
     <ClInclude Include="..\..\src\events\SDL_keymap_c.h" />
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_notificationevents_c.h" />
     <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
     <ClInclude Include="..\..\src\filesystem\SDL_sysfilesystem.h" />
@@ -486,6 +487,7 @@
     <ClInclude Include="..\..\src\locale\SDL_syslocale.h" />
     <ClInclude Include="..\..\src\main\SDL_main_callbacks.h" />
     <ClInclude Include="..\..\src\misc\SDL_sysurl.h" />
+    <ClInclude Include="..\..\src\notification\SDL_notification_c.h" />
     <ClInclude Include="..\..\src\power\SDL_syspower.h" />
     <ClInclude Include="..\..\src\render\direct3d11\SDL_shaders_d3d11.h" />
     <ClInclude Include="..\..\src\render\direct3d12\SDL_render_d3d12_xbox.h" />
@@ -544,6 +546,7 @@
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
     <ClCompile Include="..\..\src\dialog\SDL_dialog.c" />
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c" />
+    <ClCompile Include="..\..\src\events\SDL_notificationevents.c" />
     <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
     <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />
     <ClCompile Include="..\..\src\io\generic\SDL_asyncio_generic.c" />
@@ -553,6 +556,8 @@
     <ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
     <ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />
     <ClCompile Include="..\..\src\main\SDL_runapp.c" />
+    <ClCompile Include="..\..\src\notification\dummy\SDL_dummynotification.c" />
+    <ClCompile Include="..\..\src\notification\SDL_notification.c" />
     <ClCompile Include="..\..\src\SDL_guid.c" />
     <ClInclude Include="..\..\src\SDL_hashtable.h" />
     <ClInclude Include="..\..\src\SDL_hints_c.h" />
diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h
index 7442f0bf5e266..8fc6d5ca87b3a 100644
--- a/include/SDL3/SDL.h
+++ b/include/SDL3/SDL.h
@@ -66,6 +66,7 @@
 #include <SDL3/SDL_misc.h>
 #include <SDL3/SDL_mouse.h>
 #include <SDL3/SDL_mutex.h>
+#include <SDL3/SDL_notification.h>
 #include <SDL3/SDL_pen.h>
 #include <SDL3/SDL_pixels.h>
 #include <SDL3/SDL_platform.h>
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 0a3827571f342..bdb1128dedf79 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -61,6 +61,7 @@
 #include <SDL3/SDL_keyboard.h>
 #include <SDL3/SDL_keycode.h>
 #include <SDL3/SDL_mouse.h>
+#include <SDL3/SDL_notification.h>
 #include <SDL3/SDL_pen.h>
 #include <SDL3/SDL_power.h>
 #include <SDL3/SDL_sensor.h>
@@ -262,6 +263,9 @@ typedef enum SDL_EventType
     SDL_EVENT_CAMERA_DEVICE_APPROVED,        /**< A camera device has been approved for use by the user. */
     SDL_EVENT_CAMERA_DEVICE_DENIED,          /**< A camera device has been denied for use by the user. */
 
+    /* Notification events */
+    SDL_EVENT_NOTIFICATION_ACTION_INVOKED = 0x1500, /**< A user response to a system notification was received. */
+
     /* Render events */
     SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
     SDL_EVENT_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
@@ -764,6 +768,23 @@ typedef struct SDL_CameraDeviceEvent
     SDL_CameraID which;       /**< SDL_CameraID for the device being added or removed or changing */
 } SDL_CameraDeviceEvent;
 
+/**
+ * Notification dialog event structure (event.notification.*)
+ *
+ * An `action_id` value of 'default' for an SDL_EVENT_NOTIFICATION_ACTION_INVOKED
+ * event indicates that the notification was interacted with without selecting a
+ * specific action (e.g. the body of the notification was clicked on).
+ *
+ * \since This struct is available since SDL 3.6.0.
+ */
+typedef struct SDL_NotificationEvent
+{
+    SDL_EventType type; /**< SDL_EVENT_NOTIFICATION_ACTION_INVOKED */
+    Uint32 reserved;
+    Uint64 timestamp;         /**< In nanoseconds, populated using SDL_GetTicksNS() */
+    SDL_NotificationID which; /**< The ID of the notification that generated this event. */
+    const char *action_id;    /**< The identifier string of the action invoked in the notification dialog. */
+} SDL_NotificationEvent;
 
 /**
  * Renderer event structure (event.render.*)
@@ -1075,6 +1096,7 @@ typedef union SDL_Event
     SDL_RenderEvent render;                 /**< Render event data */
     SDL_DropEvent drop;                     /**< Drag and drop event data */
     SDL_ClipboardEvent clipboard;           /**< Clipboard event data */
+    SDL_NotificationEvent notification;     /**< Notification event data */
 
     /* This is necessary for ABI compatibility between Visual C++ and GCC.
        Visual C++ will respect the push pack pragma and use 52 bytes (size of
diff --git a/include/SDL3/SDL_notification.h b/include/SDL3/SDL_notification.h
new file mode 100644
index 0000000000000..a18e44fbc0fa3
--- /dev/null
+++ b/include/SDL3/SDL_notification.h
@@ -0,0 +1,254 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ * # CategoryNotifications
+ *
+ * Notifications are temporary popup dialogs that passively present
+ * information to the user, or prompt user action. They are managed
+ * and presented by the system, and can present simple options for
+ * user feedback, usually in the form of buttons.
+ *
+ * The capabilities of notifications, and how they are displayed,
+ * vary between systems, but they generally allow for a title,
+ * message body, an associated image, and buttons to allow the user
+ * to provide feedback.
+ *
+ * How notifications are presented and handled are subject to system
+ * policy, and it should not be assumed that showing a notification
+ * means that the user will see it immediately, if at all. The
+ * user may disable notifications for certain applications, they may
+ * be suppressed based on the current activity, and most systems
+ * provide a "do not disturb" mode that universally silences
+ * notifications when activated.
+ *
+ * There is both a customizable function `SDL_ShowNotificationWithProperties()`
+ * that offers many options for what is displayed, and also a much-simplified
+ * version `SDL_ShowSimpleNotification()`, which simply takes a header (required),
+ * body (optional), and image (optional).
+ */
+
+#ifndef SDL_notification_h_
+#define SDL_notification_h_
+
+#include <SDL3/SDL_properties.h>
+#include <SDL3/SDL_stdinc.h>
+#include <SDL3/SDL_surface.h>
+
+#include <SDL3/SDL_begin_code.h>
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The path to an image to be used as the header icon for system notifications on
+ * some platforms. This is required on:
+ *  - Windows
+ *  - *nix when not running in a container, and no .desktop entry is available
+ *
+ * Image types supported depend on the platform, but .png generally offers the best
+ * compatability.
+ *
+ * On *nix platforms, this can also be the name of a system icon, as specified by
+ * the Icon Naming Specification.
+ *
+ * Can be set before calling SDL_ShowNotification() or SDL_ShowSimpleNotification()
+ * for the first time.
+ *
+ * \since This macro is available since SDL 3.6.0.
+ */
+#define SDL_PROP_GLOBAL_NOTIFICATION_HEADER_ICON_STRING "SDL.notification.header_icon"
+
+typedef Uint32 SDL_NotificationID; /**< The identifier for a system notification. */
+
+typedef enum SDL_NotificationPriority
+{
+    SDL_NOTIFICATION_PRIORITY_LOW = -1,    /**< Lowest priority. */
+    SDL_NOTIFICATION_PRIORITY_NORMAL = 0,  /**< Normal/medium priority. */
+    SDL_NOTIFICATION_PRIORITY_HIGH = 1,    /**< High/important priority. */
+    SDL_NOTIFICATION_PRIORITY_CRITICAL = 2 /**< Highest/critical priority. Note that this may override any "Do Not Disturb" settings and wake the screen. */
+} SDL_NotificationPriority;
+
+typedef enum SDL_NotificationActionType
+{
+    SDL_NOTIFICATION_ACTION_TYPE_BUTTON = 1 /**< Adds a button to the notification that generates feedback when activated. */
+} SDL_NotificationActionType;
+
+/**
+ * Notification structure describing actions that can be used to allow users
+ * to interact with notification dialogs. Exactly How they are presented depends
+ * on the platform and implementation.
+ *
+ * User interactions with a notification are reported via events with the type
+ * SDL_EVENT_NOTIFICATION_ACTION_INVOKED.
+ *
+ * Action types:
+ * - button: A button with a localized text label, which generates feedback when activated.
+ *
+ * \sa SDL_NotificationEvent
+ * \sa SDL_NotificationActionType
+ */
+typedef union SDL_NotificationAction
+{
+    SDL_NotificationActionType type;
+
+    struct
+    {
+        SDL_NotificationActionType type; /**< SDL_NOTIFICATION_ACTION_TYPE_BUTTON */
+        const char *action_id;           /**< The identifier string for the button. 'default' is a reserved identifier and must not be used. */
+        const char *action_label;        /**< The localized label for the button associated with the action, in UTF-8 encoding. */
+    } button;
+
+    Uint8 padding[128];
+} SDL_NotificationAction;
+
+#define SDL_PROP_NOTIFICATION_ACTIONS_POINTER     "SDL.notification.actions"
+#define SDL_PROP_NOTIFICATION_ACTION_COUNT_NUMBER "SDL.notification.action_count"
+#define SDL_PROP_NOTIFICATION_IMAGE_POINTER       "SDL.notification.image"
+#define SDL_PROP_NOTIFICATION_MESSAGE_STRING      "SDL.notification.message"
+#define SDL_PROP_NOTIFICATION_PRIORITY_NUMBER     "SDL.notification.priority"
+#define SDL_PROP_NOTIFICATION_REPLACES_NUMBER     "SDL.notification.replaces"
+#define SDL_PROP_NOTIFICATION_SOUND_STRING        "SDL.notification.sound"
+#define SDL_PROP_NOTIFICATION_TRANSIENT_BOOLEAN   "SDL.notification.transient"
+#define SDL_PROP_NOTIFICATION_TITLE_STRING        "SDL.notification.title"
+
+/**
+ *  Requests permission from the system to display notifications. A return value of `true`
+ *  only means that the system supports notifications, and that the request for permission
+ *  was successfully issued. It does not reflect any user settings to allow or deny
+ *  notifications.
+ *
+ *  \returns True on success or false on failure; call
+ *           SDL_GetError() for more information.
+ *
+ *  \since This function is available since SDL 3.6.0
+ *
+ *  \sa SDL_ShowNotification
+ *  \sa SDL_ShowNotificationWithProperties
+ *  \sa SDL_NotificationAction
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_RequestNotificationPermission(void);
+
+/**
+ *  Show a system notification.
+ *
+ *  System notifications are small, asynchronous popup windows that notify the user
+ *  of some information. How they are displayed is system dependent.
+ *
+ *  These are the supported properties:
+ *
+ * - `SDL_PROP_NOTIFICATION_TITLE_STRING`: the title of the notification, in
+ *   UTF-8 encoding. This property is required.
+ * - `SDL_PROP_NOTIFICATION_ACTIONS_POINTER`: An array of pointers to `SDL_NotificationAction`
+ *   structs that will add actions to the notification, usually in the form of buttons or menu
+ *   items. Note that systems may have a limit on the maximum number of actions that a
+ *   notification can have.
+ * - `SDL_PROP_NOTIFICATIONS_ACTION_COUNT_NUMBER`: the number of actions in the array of actions,
+ *   if it exists.
+ * - `SDL_PROP_NOTIFICATION_IMAGE_POINTER`: a pointer to an `SDL_Surface` containing
+ *   an image that will be attached to the notification. In most cases, the image is displayed
+ *   in the form of a large icon or thumbnail alongside the message body. Notifications on Apple
+ *   platforms can be expanded to show a larger format image.
+ * - `SDL_PROP_NOTIFICATION_MESSAGE_STRING`: the message body of the notification,
+ *   in UTF-8 encoding.
+ * - `SDL_PROP_NOTIFICATION_PRIORITY_NUMBER`: an `SDL_NotificationPriority` value representing
+ *   the notification priority.
+ * - `SDL_PROP_NOTIFICATION_REPLACES_NUMBER`: the `SDL_NotificationID` of a previously
+ *   shown notification that this notification should replace.
+ * - `SDL_PROP_NOTIFICATION_SOUND_STRING`: sets a sound to play when the notification is shown.
+ *   This can have the value "default", to play the system default notification sound, "silent",
+ *   to play no sound, or contain the path to a file with a custom sound. The paths and formats
+ *   that can be used for custom sounds are system-specific, and can have some restrictions,
+ *   depending on the platform:
+ *    - Apple platforms require that the sound file is contained within the app bundle. Supported
+ *      formats are: Linear PCM, MA4 (IMA/ADPCM), uLaw, or aLaw, in an .aiff, .wav, or .caf file.
+ *    - Windows can only play custom notification sounds when the app is packaged inside an MSIX
+ *      installer. Playback from arbitrary file paths is not supported. Supported formats are:
+ *      .aac, .flac, .m4a, .mp3, .wav, and .wma.
+ *    - Unix platforms can generally load sounds from any arbitrary path, as long as the read
+ *      permissions are correct. Supported formats are: ogg/opus, ogg/vorbis, and wav/pcm.
+ *    If this property is not set, the system default sound will be used.
+ * - `SDL_PROP_NOTIFICATION_TRANSIENT_BOOLEAN`: true if the notification should not persist
+ *   in the system notification center after initially being shown.
+ *
+ * Not all properties are supported by all platforms.
+ *
+ * Notifications are available on:
+ *  - Windows 10 or higher
+ *  - macOS 10.14 or higher
+ *  - iOS 11 or higher
+ *  - *nix platforms that support the org.freedesktop.Notifications, or
+ *    org.freedesktop.portal.Notification interfaces
+ *
+ *  \param props the properties to be used when creating this notification.
+ *  \returns A non-zero SDL_NotificationID on success or 0 on failure; call
+ *           SDL_GetError() for more information.
+ *
+ *  \since This function is available since SDL 3.6.0
+ *
+ *  \sa SDL_ShowNotification
+ *  \sa SDL_NotificationAction
+ *  \sa SDL_NotificationPriority
+ *  \sa SDL_NotificationEvent
+ */
+extern SDL_DECLSPEC SDL_NotificationID SDLCALL SDL_ShowNotificationWithProperties(SDL_PropertiesID props);
+
+/**
+ *  Show a system notification with normal priority.
+ *
+ *  \param title    UTF-8 title text, required.
+ *  \param message  UTF-8 message text, may be NULL.
+ *  \param image The image associated with this notification, may be NULL.
+ *  \param actions An array of actions to attach to the notification, may be NULL.
+ *  \param num_actions The number of actions in the actions array.
+ *  \returns A non-zero SDL_NotificationID on success or 0 on failure; call
+ *           SDL_GetError() for more information.
+ *
+ *  \since This function is available since SDL 3.6.0
+ *
+ *  \sa SDL_ShowNotificationWithProperties
+ *  \sa SDL_NotificationAction
+ *  \sa SDL_NotificationEvent
+ */
+extern SDL_DECLSPEC SDL_NotificationID SDLCALL SDL_ShowNotification(const char *title, const char *message, SDL_Surface *image, SDL_NotificationAction *actions, int num_actions);
+
+/**
+ *  Remove a notification.
+ *
+ *  \param notification the ID of the notification to remove.
+ *  \returns True on success or false on failure; call
+ *           SDL_GetError() for more information.
+ *
+ *  \since This function is available since SDL 3.6.0
+ *
+ *  \sa SDL_ShowNotificationWithProperties
+ *  \sa SDL_ShowNotification
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_RemoveNotification(SDL_NotificationID notification);
+
+// Ends C function definitions when using C++
+#ifdef __cplusplus
+}
+#endif
+#include <SDL3/SDL_close_code.h>
+
+#endif // SDL_notification_h_
diff --git a/src/SDL.c b/src/SDL.c
index 61d14bbff7b7b..ab814bd318169 100644
--- a/src/SDL.c
+++ b/src/SDL.c
@@ -44,9 +44,12 @@
 #include "camera/SDL_camera_c.h"
 #include "cpuinfo/SDL_cpuinfo_c.h"
 #include "events/SDL_events_c.h"
+#include "filesystem/SDL_filesystem_c.h"
 #include "haptic/SDL_haptic_c.h"
+#include "io/SDL_asyncio_c.h"
 #include "joystick/SDL_gamepad_c.h"
 #include "joystick/SDL_joystick_c.h"
+#include "notification/SDL_notification_c.h"
 #include "render/SDL_sysrender.h"
 #include "sensor/SDL_sensor_c.h"
 #include "stdlib/SDL_getenv_c.h"
@@ -55,8 +58,6 @@
 #include "video/SDL_pixels_c.h"
 #include "video/SDL_surface_c.h"
 #include "video/SDL_video_c.h"
-#include "filesystem/SDL_filesystem_c.h"
-#include "io/SDL_asyncio_c.h"
 #ifdef SDL_PLATFORM_ANDROID
 #include "core/android/SDL_android.h"
 #endif
@@ -710,6 +711,7 @@ void SDL_Quit(void)
 #endif
     SDL_QuitSubSystem(SDL_ALL_SUBSYSTEM_FLAGS);
     SDL_CleanupTrays();
+    SDL_CleanupNotifications();
 
 #ifdef SDL_USE_LIBDBUS
     SDL_DBus_Quit();
diff --git a/src/dynapi/SDL_dynapi.exports b/src/dynapi/SDL_dynapi.exports
index b8bf43232587b..05af5c7b89ccd 100644
--- a/src/dynapi/SDL_dynapi.exports
+++ b/src/dynapi/SDL_dynapi.exports
@@ -1294,3 +1294,7 @@ _SDL_aligned_alloc_zero
 _SDL_wcstoul
 _SDL_wcstoll
 _SDL_wcstoull
+_SDL_RequestNotificationPermission
+_SDL_ShowNotificationWithProperties
+_SDL_ShowNotification
+_SDL_RemoveNotification
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 92ccf55be5a17..725f62128234c 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1295,6 +1295,10 @@ SDL3_0.0.0 {
     SDL_wcstoul;
     SDL_wcstoll;
     SDL_wcstoull;
+    SDL_RequestNotificationPermission;
+    SDL_ShowNotificationWithProperties;
+    SDL_ShowNotification;
+    SDL_RemoveNotification;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 47841e3862c40..0c25bff621b6d 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1321,3 +1321,7 @@
 #define SDL_wcstoul SDL_wcstoul_REAL
 #define SDL_wcstoll SDL_wcstoll_REAL
 #define SDL_wcstoull SDL_wcstoull_REAL
+#define SDL_RequestNotificationPermission SDL_RequestNotificationPermission_REAL
+#define SDL_ShowNotificationWithProperties SDL_ShowNotificationWithProperties_REAL
+#define SDL_ShowNotification SDL_ShowNotification_REAL
+#define SDL_RemoveNotification SDL_RemoveNotification_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 1ad817ec0db4c..69c229401ca80 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1329,3 +1329,7 @@ SDL_DYNAPI_PROC(void*,SDL_aligned_alloc_zero,(size_t a,size_t b),(a,b),return)
 SDL_DYNAPI_PROC(unsigned long,SDL_wcstoul,(const wchar_t *a,wchar_t **b,int c),(a,b,c),return)
 SDL_DYNAPI_PROC(long long,SDL_wcstoll,(const wchar_t *a,wchar_t **b,int c),(a,b,c),return)
 SDL_DYNAPI_PROC(unsigned long long,SDL_wcstoull,(const wchar_t *a,wchar_t **b,int c),(a,b,c),return)
+SDL_DYNAPI_PROC(bool,SDL_RequestNotificationPermission,(void),(),return)
+SDL_DYNAPI_PROC(SDL_NotificationID,SDL_ShowNotificationWithProperties,(SDL_PropertiesID a),(a),return)
+SDL_DYNAPI_PROC(SDL_NotificationID,SDL_ShowNotification,(const char *a,const char *b,SDL_Surface *c,SDL_NotificationAction *d,int e),(a,b,c,d,e),return)
+SDL_DYNAPI_PROC(bool,SDL_RemoveNotification,(SDL_NotificationID a),(a),return)
diff --git a/src/events/SDL_categories.c b/src/events/SDL_categories.c
index 9d7722923b761..9be9ec8cc1e81 100644
--- a/src/events/SDL_categories.c
+++ b/src/events/SDL_categories.c
@@ -185,6 +185,9 @@ SDL_EventCategory SDL_GetEventCategory(Uint32 type)
     case SDL_EVENT_CAMERA_DEVICE_APPROVED:
     case SDL_EVENT_CAMERA_DEVICE_DENIED:
         return SDL_EVENTCATEGORY_CDEVICE;
+
+    case SDL_EVENT_NOTIFICATION_ACTION_INVOKED:
+        return SDL_EVENTCATEGORY_NOTIFICATION;
     }
 }
 
diff --git a/src/events/SDL_categories_c.h b/src/events/SDL_categories_c.h
index a3762746d53b8..59cef81eb5ad8 100644
--- a/src/events/SDL_categories_c.h
+++ b/src/events/SDL_categories_c.h
@@ -64,6 +64,7 @@ typedef enum SDL_EventCategory
     SDL_EVENTCATEGORY_DROP,
     SDL_EVENTCATEGORY_CLIPBOARD,
     SDL_EVENTCATEGORY_RENDER,
+    SDL_EVENTCATEGORY_NOTIFICATION,
 } SDL_EventCategory;
 
 extern SDL_EventCategory SDL_GetEventCategory(Uint32 type);
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index a0ed2fc3bfb36..f682775d5e911 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -911,6 +911,11 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen)
         break;
 #undef PRINT_CAMERADEV_EVENT
 
+        SDL_EVENT_CASE(SDL_EVENT_NOTIFICATION_ACTION_INVOKED)
+        (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d button_id='%s')",
+                           event->notification.timestamp, (uint)event->notification.which, event->notification.action_id);
+        break;
+
         SDL_EVENT_CASE(SDL_EVENT_SENSOR_UPDATE)
         (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d data[0]=%f data[1]=%f data[2]=%f data[3]=%f data[4]=%f data[5]=%f)",
                            event->sensor.timestamp, (int)event->sensor.which,
diff --git a/src/events/SDL_notificationevents.c b/src/events/SDL_notificationevents.c
new file mode 100644
index 0000000000000..1eee1c32b1e14
--- /dev/null
+++ b/src/events/SDL_notificationevents.c
@@ -0,0 +1,41 @@
+
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#include "SDL_events_c.h"
+#include "SDL_notificationevents_c.h"
+
+bool SDL_SendNotificationAction(SDL_NotificationID notification_id, const char *action_id)
+{
+    if (SDL_EventEnabled(SDL_EVENT_NOTIFICATION_ACTION_INVOKED)) {
+        SDL_Event event;
+        event.type = SDL_EVENT_NOTIFICATION_ACTION_INVOKED;
+
+        SDL_NotificationEvent *nevent = &event.notification;
+        nevent->timestamp = 0;
+        nevent->which = notification_id;
+        nevent->action_id = SDL_CreateTemporaryString(action_id);
+        return SDL_PushEvent(&event);
+    }
+
+    return false;
+}
diff --git a/src/events/SDL_notificationevents_c.h b/src/events/SDL_notificationevents_c.h
new file mode 100644
index 0000000000000..7528809cce45e
--- /dev/null
+++ b/src/events/SDL_notificationevents_c.h
@@ -0,0 +1,30 @@
+
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', with

(Patch may be truncated, please check the link at the top of this post.)