From 32f3d43aa004f44076395a050e8ac1a0ee928462 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 May 2024 20:10:26 +0530 Subject: [PATCH] Support setting urgency for DBUS notifications --- glfw/glfw.py | 2 +- glfw/linux_notify.c | 58 +++++++++++++++++++++++++-------------- glfw/linux_notify.h | 2 +- glfw/wl_window.c | 4 +-- glfw/x11_window.c | 4 +-- kitty/fast_data_types.pyi | 3 +- kitty/glfw-wrapper.h | 2 +- kitty/glfw.c | 6 ++-- kitty/notify.py | 17 +++++++++--- kitty/window.py | 22 +++++++++++---- 10 files changed, 78 insertions(+), 42 deletions(-) diff --git a/glfw/glfw.py b/glfw/glfw.py index 5e236760a..db3651266 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -328,7 +328,7 @@ def generate_wrappers(glfw_header: str) -> None: void glfwWaylandSetupLayerShellForNextWindow(GLFWLayerShellConfig c) pid_t glfwWaylandCompositorPID(void) unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \ -const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) +const char *action_text, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun callback, void *data) void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc) void glfwSetX11WindowAsDock(int32_t x11_window_id) diff --git a/glfw/linux_notify.c b/glfw/linux_notify.c index d140bc57b..a87044e80 100644 --- a/glfw/linux_notify.c +++ b/glfw/linux_notify.c @@ -13,6 +13,11 @@ #define NOTIFICATIONS_PATH "/org/freedesktop/Notifications" #define NOTIFICATIONS_IFACE "org.freedesktop.Notifications" +static inline void cleanup_free(void *p) { free(*(void**)p); } +#define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer +static inline void cleanup_msg(void *p) { dbus_message_unref(*(DBusMessage**)p); *(DBusMessage**)p = NULL; } +#define RAII_MSG(name, initializer) __attribute__((cleanup(cleanup_msg))) DBusMessage *name = initializer + static notification_id_type notification_id = 0; typedef struct { @@ -61,7 +66,7 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data U } notification_id_type -glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char* action_name, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *user_data) { +glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char* action_name, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun callback, void *user_data) { DBusConnection *session_bus = glfw_dbus_session_bus(); static DBusConnection *added_signal_match = NULL; if (!session_bus) return 0; @@ -70,36 +75,47 @@ glfw_dbus_send_user_notification(const char *app_name, const char* icon, const c dbus_connection_add_filter(session_bus, message_handler, NULL, NULL); added_signal_match = session_bus; } - NotificationCreatedData *data = malloc(sizeof(NotificationCreatedData)); + RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData))); if (!data) return 0; data->next_id = ++notification_id; data->callback = callback; data->data = user_data; if (!data->next_id) data->next_id = ++notification_id; uint32_t replaces_id = 0; - DBusMessage *msg = dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"); - if (!msg) { free(data); return 0; } - DBusMessageIter args, array; + RAII_MSG(msg, dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify")); + if (!msg) { return 0; } + DBusMessageIter args, array, variant, dict; dbus_message_iter_init_append(msg, &args); -#define OOMMSG { free(data); data = NULL; dbus_message_unref(msg); _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; } -#define APPEND(type, val) { if (!dbus_message_iter_append_basic(&args, type, val)) OOMMSG } - APPEND(DBUS_TYPE_STRING, &app_name) - APPEND(DBUS_TYPE_UINT32, &replaces_id) - APPEND(DBUS_TYPE_STRING, &icon) - APPEND(DBUS_TYPE_STRING, &summary) - APPEND(DBUS_TYPE_STRING, &body) - if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "s", &array)) OOMMSG; +#define check_call(func, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; } +#define APPEND(to, type, val) check_call(dbus_message_iter_append_basic, &to, type, &val); + APPEND(args, DBUS_TYPE_STRING, app_name) + APPEND(args, DBUS_TYPE_UINT32, replaces_id) + APPEND(args, DBUS_TYPE_STRING, icon) + APPEND(args, DBUS_TYPE_STRING, summary) + APPEND(args, DBUS_TYPE_STRING, body) + check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "s", &array); if (action_name) { static const char* default_action = "default"; - dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &default_action); - dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &action_name); + APPEND(array, DBUS_TYPE_STRING, default_action); + APPEND(array, DBUS_TYPE_STRING, action_name); } - if (!dbus_message_iter_close_container(&args, &array)) OOMMSG; - if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &array)) OOMMSG; - if (!dbus_message_iter_close_container(&args, &array)) OOMMSG; - APPEND(DBUS_TYPE_INT32, &timeout) -#undef OOMMSG + check_call(dbus_message_iter_close_container, &args, &array); + check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "{sv}", &array); + + check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); + static const char* urgency_key = "urgency"; + APPEND(dict, DBUS_TYPE_STRING, urgency_key); + check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant); + uint8_t urgencyb = urgency & 3; + APPEND(variant, DBUS_TYPE_BYTE, urgencyb); + check_call(dbus_message_iter_close_container, &dict, &variant); + check_call(dbus_message_iter_close_container, &array, &dict); + check_call(dbus_message_iter_close_container, &args, &array); + APPEND(args, DBUS_TYPE_INT32, timeout) +#undef check_call #undef APPEND if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data)) return 0; - return data->next_id; + notification_id_type ans = data->next_id; + data = NULL; + return ans; } diff --git a/glfw/linux_notify.h b/glfw/linux_notify.h index 90d2f53a4..7369a3e43 100644 --- a/glfw/linux_notify.h +++ b/glfw/linux_notify.h @@ -14,6 +14,6 @@ typedef unsigned long long notification_id_type; typedef void (*GLFWDBusnotificationcreatedfun)(notification_id_type, uint32_t, void*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*); notification_id_type -glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, GLFWDBusnotificationcreatedfun, void*); +glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun, void*); void glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index ec9ce7608..fc83b8ac2 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2722,8 +2722,8 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long } } -GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) { - return glfw_dbus_send_user_notification(app_name, icon, summary, body, action_name, timeout, callback, data); +GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun callback, void *data) { + return glfw_dbus_send_user_notification(app_name, icon, summary, body, action_name, timeout, urgency, callback, data); } GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 90ecd08de..2ac4f083b 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -3253,8 +3253,8 @@ GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) { return glfw_xkb_keysym_from_name(keyName, caseSensitive); } -GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) { - return glfw_dbus_send_user_notification(app_name, icon, summary, body, action_name, timeout, callback, data); +GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, int urgency,GLFWDBusnotificationcreatedfun callback, void *data) { + return glfw_dbus_send_user_notification(app_name, icon, summary, body, action_name, timeout, urgency, callback, data); } GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) { diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index fe639497e..35af3b129 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -493,7 +493,8 @@ def dbus_send_notification( summary: str, body: str, action_name: str, - timeout: int = -1 + timeout: int = -1, + urgency: int = 1, ) -> int: pass diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 62b3f7bf5..3e9b34dff 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -2328,7 +2328,7 @@ typedef pid_t (*glfwWaylandCompositorPID_func)(void); GFW_EXTERN glfwWaylandCompositorPID_func glfwWaylandCompositorPID_impl; #define glfwWaylandCompositorPID glfwWaylandCompositorPID_impl -typedef unsigned long long (*glfwDBusUserNotify_func)(const char*, const char*, const char*, const char*, const char*, int32_t, GLFWDBusnotificationcreatedfun, void*); +typedef unsigned long long (*glfwDBusUserNotify_func)(const char*, const char*, const char*, const char*, const char*, int32_t, int, GLFWDBusnotificationcreatedfun, void*); GFW_EXTERN glfwDBusUserNotify_func glfwDBusUserNotify_impl; #define glfwDBusUserNotify glfwDBusUserNotify_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 040e3d2f2..3870ad22f 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -2064,13 +2064,13 @@ dbus_notification_created_callback(unsigned long long notification_id, uint32_t static PyObject* dbus_send_notification(PyObject *self UNUSED, PyObject *args) { char *app_name, *icon, *summary, *body, *action_name; - int timeout = -1; - if (!PyArg_ParseTuple(args, "sssss|i", &app_name, &icon, &summary, &body, &action_name, &timeout)) return NULL; + int timeout = -1, urgency = 1; + if (!PyArg_ParseTuple(args, "sssss|ii", &app_name, &icon, &summary, &body, &action_name, &timeout, &urgency)) return NULL; if (!glfwDBusUserNotify) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwDBusUserNotify, did you call glfw_init?"); return NULL; } - unsigned long long notification_id = glfwDBusUserNotify(app_name, icon, summary, body, action_name, timeout, dbus_notification_created_callback, NULL); + unsigned long long notification_id = glfwDBusUserNotify(app_name, icon, summary, body, action_name, timeout, urgency, dbus_notification_created_callback, NULL); return PyLong_FromUnsignedLongLong(notification_id); } diff --git a/kitty/notify.py b/kitty/notify.py index 9f0488c1b..1df0931a9 100644 --- a/kitty/notify.py +++ b/kitty/notify.py @@ -14,7 +14,12 @@ from .fast_data_types import current_focused_os_window_id, get_boss from .types import run_once from .utils import get_custom_window_icon, log_error -NotifyImplementation = Callable[[str, str, str], None] + +class Urgency(Enum): + Low: int = 0 + Normal: int = 1 + Critical: int = 2 + if is_macos: from .fast_data_types import cocoa_send_notification @@ -27,6 +32,7 @@ if is_macos: icon: bool = True, identifier: Optional[str] = None, subtitle: Optional[str] = None, + urgency: Urgency = Urgency.Normal, ) -> None: cocoa_send_notification(identifier, title, body, subtitle) @@ -56,17 +62,20 @@ else: icon: bool = True, identifier: Optional[str] = None, subtitle: Optional[str] = None, + urgency: Urgency = Urgency.Normal, ) -> None: icf = '' if icon is True: icf = get_custom_window_icon()[1] or logo_png_file - alloc_id = dbus_send_notification(application, icf, title, body, 'Click to see changes', timeout) + alloc_id = dbus_send_notification(application, icf, title, body, 'Click to see changes', timeout, urgency.value) if alloc_id and identifier is not None: alloc_map[alloc_id] = identifier +class NotifyImplementation: + def __call__(self, title: str, body: str, identifier: str, urgency: Urgency = Urgency.Normal) -> None: + notify(title, body, identifier=identifier, urgency=urgency) -def notify_implementation(title: str, body: str, identifier: str) -> None: - notify(title, body, identifier=identifier) +notify_implementation = NotifyImplementation() class OnlyWhen(Enum): diff --git a/kitty/window.py b/kitty/window.py index 3d0def19d..9f8c89e22 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -90,7 +90,9 @@ from .fast_data_types import ( from .keys import keyboard_mode_name, mod_mask from .notify import ( NotificationCommand, + NotifyImplementation, OnlyWhen, + Urgency, handle_notification_cmd, notify_with_command, sanitize_identifier_pat, @@ -1430,13 +1432,21 @@ class Window: if action == 'notify': notify_with_command(cmd, self.id) elif action == 'bell': - def bell(title: str, body: str, identifier: str) -> None: - self.screen.bell() - notify_with_command(cmd, self.id, notify_implementation=bell) + class Bell(NotifyImplementation): + def __init__(self, window_id: int): + self.window_id = window_id + def __call__(self, title: str, body: str, identifier: str, urgency: Urgency = Urgency.Normal) -> None: + w = get_boss().window_id_map.get(self.window_id) + if w: + w.screen.bell() + notify_with_command(cmd, self.id, notify_implementation=Bell(self.id)) elif action == 'command': - def run_command(title: str, body: str, identifier: str) -> None: - open_cmd([x.replace('%c', self.last_cmd_cmdline).replace('%s', exit_status) for x in notify_cmdline]) - notify_with_command(cmd, self.id, notify_implementation=run_command) + class Run(NotifyImplementation): + def __init__(self, last_cmd_cmdline: str): + self.last_cmd_cmdline = last_cmd_cmdline + def __call__(self, title: str, body: str, identifier: str, urgency: Urgency = Urgency.Normal) -> None: + open_cmd([x.replace('%c', self.last_cmd_cmdline).replace('%s', exit_status) for x in notify_cmdline]) + notify_with_command(cmd, self.id, notify_implementation=Run(self.last_cmd_cmdline)) else: raise ValueError(f'Unknown action in option `notify_on_cmd_finish`: {action}')