diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h index cb51cd7ac2f8a4..337af08909d62a 100644 --- a/include/SDL3/SDL.h +++ b/include/SDL3/SDL.h @@ -58,6 +58,7 @@ #include <SDL3/SDL_misc.h> #include <SDL3/SDL_mouse.h> #include <SDL3/SDL_mutex.h> +#include <SDL3/SDL_notification.h> #include <SDL3/SDL_pixels.h> #include <SDL3/SDL_platform.h> #include <SDL3/SDL_power.h> diff --git a/include/SDL3/SDL_notification.h b/include/SDL3/SDL_notification.h new file mode 100644 index 00000000000000..4aeea8d4f17bee --- /dev/null +++ b/include/SDL3/SDL_notification.h @@ -0,0 +1,113 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 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. +*/ + +/** + * \file SDL_notification.h + * + * \brief Header file for notification API + */ + +#ifndef SDL_notification_h_ +#define SDL_notification_h_ + +#include <SDL3/SDL_stdinc.h> + +#include <SDL3/SDL_begin_code.h> +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief SDL_Notification flags. + */ +typedef enum +{ + SDL_NOTIFICATION_PRIORITY_LOW = 0x00000010, /**< lowest */ + SDL_NOTIFICATION_PRIORITY_NORMAL = 0x00000020, /**< normal/medium */ + SDL_NOTIFICATION_PRIORITY_HIGH = 0x00000040 /**< high/important/critical */ +} SDL_NotificationFlags; + +/** + * \brief SDL_Icon flags. + */ +typedef enum +{ + SDL_ICON_TYPE_SINGLE_FILE = 0x00000010, /**< A single icon file. */ + SDL_ICON_TYPE_SURFACE = 0x00000020, /**< Icon inside an SDL surface. */ + SDL_ICON_TYPE_WINDOW = 0x00000040 /**< Icon is same as SDL window. */ +} SDL_IconFlags; + +/** + * Notification structure containing title, text, window, etc. + */ +typedef struct +{ + Uint32 flags; /**< ::SDL_NotificationFlags */ + const char *title; /**< UTF-8 title */ + const char *message; /**< UTF-8 message text */ + + struct + { + Uint32 flags; /**< ::SDL_IconFlags */ + union { + const char *path; + SDL_Surface *surface; + SDL_Window *window; + } data; + } icon; + +} SDL_NotificationData; + +/** + * \brief Create a system notification. + * + * \param notificationdata the SDL_NotificationData structure with title, text and other options + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \sa SDL_ShowSimpleNotification + * \sa SDL_NotificationData + */ +extern DECLSPEC int SDLCALL SDL_ShowNotification(const SDL_NotificationData *notificationdata); + +/** + * \brief Create a simple system notification. + * + * \param title UTF-8 title text + * \param message UTF-8 message text + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \sa SDL_ShowNotification + * \sa SDL_NotificationData + */ +extern DECLSPEC int SDLCALL SDL_ShowSimpleNotification(const char *title, const char *message); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include <SDL3/SDL_close_code.h> + + +#endif /* SDL_notification_h_ */ + diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 16a7c13f94e886..8e755e12145a18 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -498,4 +498,63 @@ SDL_DBus_ScreensaverInhibit(SDL_bool inhibit) return SDL_TRUE; } + +int SDL_DBus_ShowNotification(const SDL_NotificationData *notificationdata) +{ + dbus_bool_t status = 0; + DBusConnection *conn = dbus.session_conn; + DBusMessage *msg = NULL; + Uint32 replace_id = 0; + Sint32 timeout = -1; + const char *icon = ""; + DBusMessageIter iter; + DBusMessageIter sub; + + const char *app; + const char *title; + const char *body; + + + app = notificationdata->title; + title = notificationdata->title; + body = notificationdata->message; + + if (conn == NULL) { + return SDL_FALSE; + } + + msg = dbus.message_new_method_call("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "Notify"); + if (msg != NULL) { + dbus.message_iter_init_append(msg, &iter); + status = dbus.message_iter_append_basic(&iter, DBUS_TYPE_STRING, &app); + status = dbus.message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &replace_id); + status = dbus.message_iter_append_basic(&iter, DBUS_TYPE_STRING, &icon); + status = dbus.message_iter_append_basic(&iter, DBUS_TYPE_STRING, &title); + status = dbus.message_iter_append_basic(&iter, DBUS_TYPE_STRING, &body); + status = dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub); + status = dbus.message_iter_close_container(&iter, &sub); + status = dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub); + status = dbus.message_iter_close_container(&iter, &sub); + status = dbus.message_iter_append_basic(&iter, DBUS_TYPE_INT32, &timeout); + if (!status) { + SDL_SetError("Error appending to dbus parameters (notification)"); + } else { + status = dbus.connection_send(conn, msg, NULL); + if (!status) { + SDL_SetError("Error sending to dbus (notification)"); + } else { + dbus.connection_flush(conn); + } + } + + dbus.message_unref(msg); + } + + return (status) ? SDL_TRUE : SDL_FALSE; +} + + #endif diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 2a90e0a21c0445..02a0476dd00fa6 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -93,6 +93,7 @@ extern SDL_bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const c extern void SDL_DBus_ScreensaverTickle(void); extern SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit); +extern int SDL_DBus_ShowNotification(const SDL_NotificationData *notificationdata); #endif /* HAVE_DBUS_DBUS_H */ diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 4b334c254cb3d5..be7bb2aebff31f 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -841,6 +841,8 @@ SDL3_0.0.0 { SDL_GetSystemTheme; SDL_CreatePopupWindow; SDL_GetWindowParent; + SDL_ShowNotification; + SDL_ShowSimpleNotification; # 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 0d4b3c9c7954f2..37364545ade079 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -868,3 +868,5 @@ #define SDL_GetSystemTheme SDL_GetSystemTheme_REAL #define SDL_CreatePopupWindow SDL_CreatePopupWindow_REAL #define SDL_GetWindowParent SDL_GetWindowParent_REAL +#define SDL_ShowNotification SDL_ShowNotification_REAL +#define SDL_ShowSimpleNotification SDL_ShowSimpleNotification_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 8945a153515b48..1980ce4a14eaf0 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -913,3 +913,5 @@ SDL_DYNAPI_PROC(int,SDL_GetRenderWindowSize,(SDL_Renderer *a, int *b, int *c),(a SDL_DYNAPI_PROC(SDL_SystemTheme,SDL_GetSystemTheme,(void),(),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreatePopupWindow,(SDL_Window *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_GetWindowParent,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_ShowNotification,(const SDL_NotificationData *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_ShowSimpleNotification,(const char *a, const char *b),(a,b),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index b256f2744728af..52841dde6ce3e0 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -344,6 +344,9 @@ struct SDL_VideoDevice /* Hit-testing */ int (*SetWindowHitTest)(SDL_Window *window, SDL_bool enabled); + /* Notification */ + int (*ShowNotification)(_THIS, const SDL_NotificationData *notificationdata); + /* Tell window that app enabled drag'n'drop events */ void (*AcceptDragAndDrop)(SDL_Window *window, SDL_bool accept); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 705ce074f1e5dd..33017a595c6b05 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -5045,3 +5045,46 @@ void *SDL_Metal_GetLayer(SDL_MetalView view) return NULL; } } + +int SDL_ShowNotification(const SDL_NotificationData *notificationdata) +{ + if (notificationdata == NULL) { + return SDL_InvalidParamError("notificationdata"); + } + + if (notificationdata->title == NULL) { + return SDL_InvalidParamError("notificationdata->title"); + } + + if (notificationdata->message == NULL) { + return SDL_InvalidParamError("notificationdata->message"); + } + + if (notificationdata->icon.flags == SDL_ICON_TYPE_SINGLE_FILE && notificationdata->icon.data.path == NULL) { + return SDL_InvalidParamError("notificationdata->icon.dta.path"); + } + + if (notificationdata->icon.flags == SDL_ICON_TYPE_SINGLE_FILE && notificationdata->icon.data.surface == NULL) { + return SDL_InvalidParamError("notificationdata->icon.data.surface"); + } + + if (notificationdata->icon.flags == SDL_ICON_TYPE_WINDOW && notificationdata->icon.data.window == NULL) { + return SDL_InvalidParamError("notificationdata->icon.data.window"); + } + + if (_this && _this->ShowNotification) { + return _this->ShowNotification(_this, notificationdata); + } + + return SDL_Unsupported(); +} + + +int SDL_ShowSimpleNotification(const char *title, const char *message) +{ + SDL_NotificationData notificationdata; + SDL_zero(notificationdata); + notificationdata.title = title; + notificationdata.message = message; + return SDL_ShowNotification(¬ificationdata); +} diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 1f42324e0a56a7..441da0abdd35cc 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -162,6 +162,12 @@ static void Wayland_DeleteDevice(SDL_VideoDevice *device) SDL_WAYLAND_UnloadSymbols(); } +#ifdef SDL_USE_LIBDBUS +static int Wayland_ShowNotification(_THIS, const SDL_NotificationData *notificationdata) { + return SDL_DBus_ShowNotification(notificationdata); +} +#endif + static SDL_VideoDevice *Wayland_CreateDevice(void) { SDL_VideoDevice *device; @@ -266,6 +272,10 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->StopTextInput = Wayland_StopTextInput; device->SetTextInputRect = Wayland_SetTextInputRect; +#ifdef SDL_USE_LIBDBUS + device->ShowNotification = Wayland_ShowNotification; +#endif + #if SDL_VIDEO_VULKAN device->Vulkan_LoadLibrary = Wayland_Vulkan_LoadLibrary; device->Vulkan_UnloadLibrary = Wayland_Vulkan_UnloadLibrary; diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 3d182b2ba57f1d..a464efc0e0a9cf 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -140,6 +140,12 @@ static int X11_SafetyNetErrHandler(Display *d, XErrorEvent *e) return 0; } +#ifdef SDL_USE_LIBDBUS +static int X11_ShowNotification(_THIS, const SDL_NotificationData *notificationdata) { + return SDL_DBus_ShowNotification(notificationdata); +} +#endif + static SDL_VideoDevice *X11_CreateDevice(void) { SDL_VideoDevice *device; @@ -307,6 +313,10 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->HideScreenKeyboard = X11_HideScreenKeyboard; device->IsScreenKeyboardShown = X11_IsScreenKeyboardShown; +#ifdef SDL_USE_LIBDBUS + device->ShowNotification = X11_ShowNotification; +#endif + device->free = X11_DeleteDevice; #if SDL_VIDEO_VULKAN diff --git a/test/testmessage.c b/test/testmessage.c index f94e2a63ccb14a..6d695f97c384fe 100644 --- a/test/testmessage.c +++ b/test/testmessage.c @@ -200,11 +200,22 @@ int main(int argc, char *argv[]) quit(1); } + + /* Test showing a system notification message with a parent window */ + success = SDL_ShowSimpleNotification("Simple Notification", "Hey this window needs attention!"); + if (success == -1) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting Notification: %s\n", SDL_GetError()); + quit(1); + } + while (SDL_WaitEvent(&event)) { if (event.type == SDL_EVENT_QUIT || event.type == SDL_EVENT_KEY_UP) { break; } } + + + SDL_DestroyWindow(window); } SDL_Quit();