Просмотр исходного кода

Add notification framework with test and dummy driver

Frank Praznik 2 месяцев назад
Родитель
Сommit
068e405912

+ 2 - 0
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) \

+ 40 - 1
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)

+ 5 - 0
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" />

+ 1 - 0
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>

+ 22 - 0
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

+ 254 - 0
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_

+ 4 - 2
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();

+ 4 - 0
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

+ 4 - 0
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: *;
 };

+ 4 - 0
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

+ 4 - 0
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)

+ 3 - 0
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;
     }
 }
 

+ 1 - 0
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);

+ 5 - 0
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,

+ 41 - 0
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;
+}

+ 30 - 0
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', 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"
+
+#ifndef SDL_notificationevents_c_h_
+#define SDL_notificationevents_c_h_
+
+extern bool SDL_SendNotificationAction(SDL_NotificationID notification_id, const char *action_id);
+
+#endif // SDL_notificationevents_c_h_
+

+ 85 - 0
src/notification/SDL_notification.c

@@ -0,0 +1,85 @@
+/*
+  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_notification_c.h"
+#include <SDL3/SDL_properties.h>
+
+SDL_NotificationID SDL_ShowNotificationWithProperties(SDL_PropertiesID props)
+{
+    if (!props) {
+        SDL_InvalidParamError("props");
+        return 0;
+    }
+
+    // The title property is required.
+    CHECK_PARAM (true) {
+        const char *title = SDL_GetStringProperty(props, SDL_PROP_NOTIFICATION_TITLE_STRING, NULL);
+        if (!title) {
+            SDL_SetError("Notifications must have a title");
+            return 0;
+        }
+    }
+
+    return SDL_SYS_ShowNotification(props);
+}
+
+SDL_NotificationID SDL_ShowNotification(const char *title, const char *message, SDL_Surface *image, SDL_NotificationAction *actions, int num_actions)
+{
+    SDL_NotificationID id = 0;
+    SDL_PropertiesID props = SDL_CreateProperties();
+    if (!props) {
+        return 0;
+    }
+
+    if (title) {
+        if (!SDL_SetStringProperty(props, SDL_PROP_NOTIFICATION_TITLE_STRING, title)) {
+            goto cleanup;
+        }
+    } else {
+        SDL_SetError("Notifications must have a title");
+        goto cleanup;
+    }
+    if (message) {
+        if (!SDL_SetStringProperty(props, SDL_PROP_NOTIFICATION_MESSAGE_STRING, message)) {
+            goto cleanup;
+        }
+    }
+    if (image) {
+        if (!SDL_SetPointerProperty(props, SDL_PROP_NOTIFICATION_IMAGE_POINTER, image)) {
+            goto cleanup;
+        }
+    }
+    if (actions && num_actions) {
+        if (!SDL_SetPointerProperty(props, SDL_PROP_NOTIFICATION_ACTIONS_POINTER, actions)) {
+            goto cleanup;
+        }
+        if (!SDL_SetNumberProperty(props, SDL_PROP_NOTIFICATION_ACTION_COUNT_NUMBER, num_actions)) {
+            goto cleanup;
+        }
+    }
+
+    id = SDL_ShowNotificationWithProperties(props);
+
+cleanup:
+    SDL_DestroyProperties(props);
+    return id;
+}

+ 34 - 0
src/notification/SDL_notification_c.h

@@ -0,0 +1,34 @@
+/*
+  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.
+*/
+
+#ifndef SDL_NOTIFICATION_C_H
+#define SDL_NOTIFICATION_C_H
+
+#include <SDL3/SDL_notification.h>
+
+extern SDL_NotificationID SDL_SYS_ShowNotification(SDL_PropertiesID props);
+extern void SDL_CleanupNotifications();
+
+#ifdef SDL_VIDEO_DRIVER_WAYLAND
+extern const char *SDL_GetNotificationActivationToken();
+#endif
+
+#endif // SDL_NOTIFICATION_C_H

+ 51 - 0
src/notification/dummy/SDL_dummynotification.c

@@ -0,0 +1,51 @@
+/*
+  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_notification_c.h"
+
+bool SDL_RequestNotificationPermission(void)
+{
+    return SDL_Unsupported();
+}
+
+SDL_NotificationID SDL_SYS_ShowNotification(SDL_PropertiesID props)
+{
+    SDL_Unsupported();
+    return 0;
+}
+
+bool SDL_RemoveNotification(SDL_NotificationID notification)
+{
+    return SDL_Unsupported();
+}
+
+void SDL_CleanupNotifications()
+{
+    // Nothing to do.
+}
+
+#ifdef SDL_VIDEO_DRIVER_WAYLAND
+const char *SDL_GetNotificationActivationToken()
+{
+    return NULL;
+}
+#endif

+ 4 - 0
src/test/SDL_test_common.c

@@ -2012,6 +2012,10 @@ void SDLTest_PrintEvent(const SDL_Event *event)
         SDL_Log("SDL EVENT: Camera device %" SDL_PRIu32 " permission denied",
                 event->cdevice.which);
         break;
+    case SDL_EVENT_NOTIFICATION_ACTION_INVOKED:
+        SDL_Log("SDL EVENT: Notification action for %" SDL_PRIu32 " button_id=%s",
+                event->notification.which, event->notification.action_id);
+        break;
     case SDL_EVENT_SENSOR_UPDATE:
         SDL_Log("SDL EVENT: Sensor update for %" SDL_PRIu32,
                 event->sensor.which);

+ 1 - 0
test/CMakeLists.txt

@@ -441,6 +441,7 @@ add_sdl_test_executable(testlocale NONINTERACTIVE SOURCES testlocale.c NAME83 lo
 add_sdl_test_executable(testlock SOURCES testlock.c NAME83 lock)
 add_sdl_test_executable(testrwlock SOURCES testrwlock.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 20 NAME83 rwlock)
 add_sdl_test_executable(testmouse SOURCES testmouse.c NAME83 mouse)
+add_sdl_test_executable(testnotification NEEDS_RESOURCES SOURCES testnotification.c NAME83 notify)
 
 add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c NAME83 overlay)
 add_sdl_test_executable(testplatform NONINTERACTIVE SOURCES testplatform.c NAME83 platform)

+ 160 - 0
test/testnotification.c

@@ -0,0 +1,160 @@
+/*
+  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.
+*/
+
+/* Simple test of the SDL Notification API */
+#define SDL_MAIN_USE_CALLBACKS 1
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+#include <SDL3/SDL_test.h>
+
+/* This enables themed Windows dialogs when building with Visual Studio */
+#if defined(SDL_PLATFORM_WINDOWS) && defined(_MSC_VER)
+#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0'  processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
+#endif
+
+static SDLTest_CommonState *state;
+static SDL_Surface *icon;
+static SDL_NotificationID last_id;
+static SDL_PropertiesID props;
+static SDL_NotificationAction actions[] = {
+    { .button = { SDL_NOTIFICATION_ACTION_TYPE_BUTTON, "action_1", "OK" } },
+    { .button = { SDL_NOTIFICATION_ACTION_TYPE_BUTTON, "action_2", "Cancel" } }
+};
+static SDL_NotificationAction *action_array[SDL_arraysize(actions) + 1];
+
+static bool transient;
+static int sound;
+
+SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
+{
+    SDL_SetAppMetadata("SDL Notification Test", "0.0.1", "org.libsdl.testnotification");
+
+    /* Initialize test framework */
+    state = SDLTest_CommonCreateState(argv, 0);
+    if (!state) {
+        return SDL_APP_FAILURE;
+    }
+
+    /* Parse commandline */
+    if (!SDLTest_CommonDefaultArgs(state, argc, argv)) {
+        return SDL_APP_FAILURE;
+    }
+
+    state->flags |= SDL_INIT_VIDEO;
+    if (!SDLTest_CommonInit(state)) {
+        return SDL_APP_FAILURE;
+    }
+
+    SDL_SetStringProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_NOTIFICATION_HEADER_ICON_STRING, "sdl-test_round.png");
+
+    props = SDL_CreateProperties();
+    SDL_SetStringProperty(props, SDL_PROP_NOTIFICATION_TITLE_STRING, "Test Notification");
+    SDL_SetStringProperty(props, SDL_PROP_NOTIFICATION_MESSAGE_STRING, "Hey, pay attention to me!");
+
+    icon = SDL_LoadPNG("sdl-test_round.png");
+    SDL_SetPointerProperty(props, SDL_PROP_NOTIFICATION_IMAGE_POINTER, icon);
+
+    SDL_SetPointerProperty(props, SDL_PROP_NOTIFICATION_ACTIONS_POINTER, actions);
+    SDL_SetNumberProperty(props, SDL_PROP_NOTIFICATION_ACTION_COUNT_NUMBER, SDL_arraysize(actions));
+
+    SDL_RequestNotificationPermission();
+
+    return SDL_APP_CONTINUE;
+}
+
+static void ShowNotification(bool replace)
+{
+    if (replace) {
+        SDL_SetNumberProperty(props, SDL_PROP_NOTIFICATION_REPLACES_NUMBER, last_id);
+    } else {
+        SDL_SetNumberProperty(props, SDL_PROP_NOTIFICATION_REPLACES_NUMBER, 0);
+    }
+    SDL_SetBooleanProperty(props, SDL_PROP_NOTIFICATION_TRANSIENT_BOOLEAN, transient);
+
+    // Test showing a system notification message.
+    const SDL_NotificationID new_id = SDL_ShowNotificationWithProperties(props);
+    if (new_id) {
+        SDL_Log("Notification successfully dispatched. ID: %" SDL_PRIu32, new_id);
+        last_id = new_id;
+    } else {
+        SDL_Log("Notification dispatch failed: %s", SDL_GetError());
+    }
+}
+
+SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
+{
+    if (event->type == SDL_EVENT_KEY_DOWN) {
+        if (event->key.key == SDLK_SPACE) {
+            ShowNotification((event->key.mod & SDL_KMOD_CTRL) != 0);
+        } else if (event->key.key == SDLK_H) {
+            if (last_id) {
+                SDL_RemoveNotification(last_id);
+            }
+        } else if (event->key.key == SDLK_T) {
+            transient ^= true;
+        } else if (event->key.key == SDLK_S) {
+            sound = (sound + 1) % 3;
+            switch (sound) {
+            default:
+                SDL_SetStringProperty(props, SDL_PROP_NOTIFICATION_SOUND_STRING, "default");
+                break;
+            case 1:
+                SDL_SetStringProperty(props, SDL_PROP_NOTIFICATION_SOUND_STRING, "sword.wav");
+                break;
+            case 2:
+                SDL_SetStringProperty(props, SDL_PROP_NOTIFICATION_SOUND_STRING, "silent");
+            }
+        }
+    } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
+        ShowNotification(false);
+    } else if (event->type == SDL_EVENT_NOTIFICATION_ACTION_INVOKED) {
+        SDL_Log("User responded to notification %" SDL_PRIu32 " with action \"%s\"", event->notification.which, event->notification.action_id);
+
+        // Raise the window of the user clicked "OK".
+        if (SDL_strcmp(event->notification.action_id, "action_1") == 0) {
+            SDL_RaiseWindow(state->windows[0]);
+        } else if (SDL_strcmp(event->notification.action_id, "action_url") == 0) {
+            SDL_OpenURL("https://www.libsdl.org");
+        }
+    }
+
+    return SDLTest_CommonEventMainCallbacks(state, event);
+}
+
+SDL_AppResult SDL_AppIterate(void *appstate)
+{
+    for (int i = 0; i < state->num_windows; ++i) {
+        SDL_Renderer *renderer = state->renderers[i];
+        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+        SDL_RenderClear(renderer);
+        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+
+        float y = 16.0f;
+        SDL_RenderDebugText(renderer, 8.f, y, "Click or press space to show a notification");
+        y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2;
+        SDL_RenderDebugText(renderer, 8.f, y, "Press 'H' to hide the last notification");
+        y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2;
+        SDL_RenderDebugTextFormat(renderer, 8.f, y, "Press 'T' to toggle the transient property (%s)", transient ? "ON" : "OFF");
+        y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2;
+        SDL_RenderDebugTextFormat(renderer, 8.f, y, "Press 'S' to toggle the sound property (%s)", sound == 0 ? "Default" : sound == 1 ? "Custom"
+                                                                                                                                       : "Silent");
+        SDL_RenderPresent(renderer);
+    }
+
+    return SDL_APP_CONTINUE;
+}
+
+void SDL_AppQuit(void *appstate, SDL_AppResult result)
+{
+    SDL_DestroySurface(icon);
+    SDLTest_CommonQuit(state);
+}