diff --git a/Android.mk b/Android.mk index d53bf403b1..6959288468 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 45892a81f7..5c61b53832 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 #include @@ -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 "$" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,Metal") endif() + if(SDL_FRAMEWORK_USERNOTIFICATIONS) + find_library(USERNOTIFICATIONS UserNotifications) + sdl_link_dependency(usernotifications LIBS "$" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,UserNotifications") + endif() if(SDL_FRAMEWORK_OPENGLES) sdl_link_dependency(opengles LIBS "$" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,OpenGLES") endif() if(SDL_FRAMEWORK_QUARTZCORE) sdl_link_dependency(quartz_core LIBS "$" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,QuartzCore") endif() + if(SDL_FRAMEWORK_SECURITY) + sdl_link_dependency(quartz_core LIBS "$" PKG_CONFIG_LINK_OPTIONS "-Wl,-framework,Security") + endif() if(SDL_FRAMEWORK_UIKIT) sdl_link_dependency(ui_kit LIBS "$" 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 4ecdb0fb45..d09e24c5d0 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -452,6 +452,7 @@ + @@ -486,6 +487,7 @@ + @@ -544,6 +546,7 @@ + @@ -553,6 +556,8 @@ + + diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h index 7442f0bf5e..8fc6d5ca87 100644 --- a/include/SDL3/SDL.h +++ b/include/SDL3/SDL.h @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 0a3827571f..bdb1128ded 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -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 0000000000..a18e44fbc0 --- /dev/null +++ b/include/SDL3/SDL_notification.h @@ -0,0 +1,254 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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 +#include +#include + +#include +/* 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 + +#endif // SDL_notification_h_ diff --git a/src/SDL.c b/src/SDL.c index 61d14bbff7..ab814bd318 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 b8bf432325..05af5c7b89 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 92ccf55be5..725f621282 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 47841e3862..0c25bff621 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 1ad817ec0d..69c229401c 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 9d7722923b..9be9ec8cc1 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 a3762746d5..59cef81eb5 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 a0ed2fc3bf..f682775d5e 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 0000000000..1eee1c32b1 --- /dev/null +++ b/src/events/SDL_notificationevents.c @@ -0,0 +1,41 @@ + +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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 0000000000..7528809cce --- /dev/null +++ b/src/events/SDL_notificationevents_c.h @@ -0,0 +1,30 @@ + +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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_ + diff --git a/src/notification/SDL_notification.c b/src/notification/SDL_notification.c new file mode 100644 index 0000000000..8e97b25c62 --- /dev/null +++ b/src/notification/SDL_notification.c @@ -0,0 +1,85 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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 + +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; +} diff --git a/src/notification/SDL_notification_c.h b/src/notification/SDL_notification_c.h new file mode 100644 index 0000000000..d4b72aa348 --- /dev/null +++ b/src/notification/SDL_notification_c.h @@ -0,0 +1,34 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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 + +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 diff --git a/src/notification/dummy/SDL_dummynotification.c b/src/notification/dummy/SDL_dummynotification.c new file mode 100644 index 0000000000..973207914b --- /dev/null +++ b/src/notification/dummy/SDL_dummynotification.c @@ -0,0 +1,51 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + 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 \ No newline at end of file diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index ee324a7a35..6e7b12f048 100644 --- a/src/test/SDL_test_common.c +++ b/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); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 42f1d58aa3..074c21035d 100644 --- a/test/CMakeLists.txt +++ b/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) diff --git a/test/testnotification.c b/test/testnotification.c new file mode 100644 index 0000000000..e1a75391a6 --- /dev/null +++ b/test/testnotification.c @@ -0,0 +1,160 @@ +/* + Copyright (C) 1997-2026 Sam Lantinga + + 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 +#include +#include + +/* 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); +}