From 670de085a3560c9919fef507ddc9974db8c81785 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 2 Feb 2019 13:48:26 +0530 Subject: [PATCH] Add API to GLFW for user notifications using DBus --- glfw/dbus_glfw.c | 70 ++++++++++++++++++++++++++++++++------------ glfw/dbus_glfw.h | 3 ++ glfw/glfw.py | 3 ++ glfw/linux_notify.c | 66 +++++++++++++++++++++++++++++++++++++++++ glfw/linux_notify.h | 15 ++++++++++ glfw/wl_window.c | 5 ++++ glfw/x11_window.c | 5 ++++ kitty/glfw-wrapper.c | 2 ++ kitty/glfw-wrapper.h | 15 ++++++++-- 9 files changed, 163 insertions(+), 21 deletions(-) create mode 100644 glfw/linux_notify.c create mode 100644 glfw/linux_notify.h diff --git a/glfw/dbus_glfw.c b/glfw/dbus_glfw.c index dfa77a932..9b9f55549 100644 --- a/glfw/dbus_glfw.c +++ b/glfw/dbus_glfw.c @@ -43,6 +43,7 @@ report_error(DBusError *err, const char *fmt, ...) { } static _GLFWDBUSData *dbus_data = NULL; +static DBusConnection *session_bus = NULL; GLFWbool glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) { @@ -177,6 +178,10 @@ glfw_dbus_terminate(_GLFWDBUSData *dbus) { dbus_data->eld = NULL; dbus_data = NULL; } + if (session_bus) { + dbus_connection_unref(session_bus); + session_bus = NULL; + } } void @@ -223,34 +228,43 @@ method_reply_received(DBusPendingCall *pending, void *user_data) { } } +GLFWbool +call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data) { + GLFWbool retval = GLFW_FALSE; +#define REPORT(errs) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: node=%s path=%s interface=%s method=%s, with error: %s", dbus_message_get_destination(msg), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), errs) + if (callback) { + DBusPendingCall *pending = NULL; + if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) { + MethodResponse *res = malloc(sizeof(MethodResponse)); + if (!res) return GLFW_FALSE; + res->callback = callback; + res->user_data = user_data; + dbus_pending_call_set_notify(pending, method_reply_received, res, free); + retval = GLFW_TRUE; + } else { + REPORT("out of memory"); + } + } else { + if (dbus_connection_send(conn, msg, NULL)) { + retval = GLFW_TRUE; + } else { + REPORT("out of memory"); + } + } + return retval; +#undef REPORT +} + static GLFWbool call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, va_list ap) { if (!conn) return GLFW_FALSE; DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method); if (!msg) return GLFW_FALSE; GLFWbool retval = GLFW_FALSE; - MethodResponse *res = malloc(sizeof(MethodResponse)); - if (!res) { dbus_message_unref(msg); return GLFW_FALSE; } - res->callback = callback; - res->user_data = user_data; int firstarg = va_arg(ap, int); if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) { - if (callback) { - DBusPendingCall *pending = NULL; - if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) { - dbus_pending_call_set_notify(pending, method_reply_received, res, free); - retval = GLFW_TRUE; - } else { - _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface); - } - } else { - if (dbus_connection_send(conn, msg, NULL)) { - retval = GLFW_TRUE; - } else { - _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface); - } - } + retval = call_method_with_msg(conn, msg, timeout, callback, user_data); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface); } @@ -293,3 +307,21 @@ glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) { va_end(ap); return ans; } + +static void +glfw_dbus_connect_to_session_bus() { + DBusError error; + dbus_error_init(&error); + if (session_bus) dbus_connection_unref(session_bus); + session_bus = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (dbus_error_is_set(&error)) { + report_error(&error, "Failed to connect to DBUS session bus"); + session_bus = NULL; + } +} + +DBusConnection * +glfw_dbus_session_bus() { + if (!session_bus) glfw_dbus_connect_to_session_bus(); + return session_bus; +} diff --git a/glfw/dbus_glfw.h b/glfw/dbus_glfw.h index 3cd0fb41b..b758c0dc9 100644 --- a/glfw/dbus_glfw.h +++ b/glfw/dbus_glfw.h @@ -42,9 +42,12 @@ void glfw_dbus_terminate(_GLFWDBUSData *dbus); DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, GLFWbool register_on_bus); void glfw_dbus_close_connection(DBusConnection *conn); GLFWbool +call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data); +GLFWbool glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...); GLFWbool glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...); void glfw_dbus_dispatch(DBusConnection *); GLFWbool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...); int glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...); +DBusConnection* glfw_dbus_session_bus(); diff --git a/glfw/glfw.py b/glfw/glfw.py index c0a798dee..28ff4a373 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -212,6 +212,8 @@ def generate_wrappers(glfw_header): const char* glfwGetPrimarySelectionString(GLFWwindow* window, void) int glfwGetXKBScancode(const char* key_name, int case_sensitive) void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback) + unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \ +int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) '''.splitlines(): if line: functions.append(Function(line.strip(), check_fail=False)) @@ -231,6 +233,7 @@ typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int); typedef int (* GLFWapplicationshouldhandlereopenfun)(int); typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); +typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); {} const char* load_glfw(const char* path); diff --git a/glfw/linux_notify.c b/glfw/linux_notify.c new file mode 100644 index 000000000..bd53e866a --- /dev/null +++ b/glfw/linux_notify.c @@ -0,0 +1,66 @@ +/* + * linux_notify.c + * Copyright (C) 2019 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "internal.h" +#include "linux_notify.h" +#include + +#define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications" +#define NOTIFICATIONS_PATH "/org/freedesktop/Notifications" +#define NOTIFICATIONS_IFACE "org.freedesktop.Notifications" + +static notification_id_type notification_id = 0; + +typedef struct { + notification_id_type next_id; + GLFWDBusnotificationcreatedfun callback; + void *data; +} NotificationCreatedData; + +void +notification_created(DBusMessage *msg, const char* errmsg, void *data) { + if (errmsg) { + _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s", errmsg); + return; + } + uint32_t notification_id; + if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, ¬ification_id, DBUS_TYPE_INVALID)) return; + NotificationCreatedData *ncd = (NotificationCreatedData*)data; + if (ncd->callback) ncd->callback(ncd->next_id, notification_id, ncd->data); +} + +notification_id_type +glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *user_data) { + DBusConnection *session_bus = glfw_dbus_session_bus(); + if (!session_bus) return 0; + NotificationCreatedData *data = malloc(sizeof(NotificationCreatedData)); + 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) return 0; + DBusMessageIter args, array; + dbus_message_iter_init_append(msg, &args); +#define OOMMSG { 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; + 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 +#undef APPEND + if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data)) return 0; + return data->next_id; +} diff --git a/glfw/linux_notify.h b/glfw/linux_notify.h new file mode 100644 index 000000000..808e7c903 --- /dev/null +++ b/glfw/linux_notify.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2019 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + + +#include "dbus_glfw.h" + +typedef unsigned long long notification_id_type; +typedef void (*GLFWDBusnotificationcreatedfun)(notification_id_type, uint32_t, void*); +notification_id_type +glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, int32_t timeout, GLFWDBusnotificationcreatedfun, void*); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index e4990577e..23349a1c6 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -29,6 +29,7 @@ #include "internal.h" #include "backend_utils.h" #include "memfd.h" +#include "linux_notify.h" #include #include @@ -2130,3 +2131,7 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long wl_surface_commit(window->wl.surface); } } + +GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) { + return glfw_dbus_send_user_notification(app_name, icon, summary, body, timeout, callback, data); +} diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 6269482c2..8ce73899d 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -28,6 +28,7 @@ #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" +#include "linux_notify.h" #include #include @@ -2893,3 +2894,7 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle) GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool 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, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) { + return glfw_dbus_send_user_notification(app_name, icon, summary, body, timeout, callback, data); +} diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 0d52870be..f2399489b 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -385,6 +385,8 @@ load_glfw(const char* path) { *(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent"); + *(void **) (&glfwDBusUserNotify_impl) = dlsym(handle, "glfwDBusUserNotify"); + return NULL; } diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index cbd6f6096..025f0f30c 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -166,7 +166,7 @@ #define GLFW_KEY_GRAVE_ACCENT 96 /* ` */ #define GLFW_KEY_WORLD_1 161 /* non-US #1 */ #define GLFW_KEY_WORLD_2 162 /* non-US #2 */ -#define GLFW_KEY_PLUS 163 /* non-US #2 */ +#define GLFW_KEY_PLUS 163 /* Function keys */ #define GLFW_KEY_ESCAPE 256 @@ -1139,7 +1139,13 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); * @param[in] window The window that received the event. * @param[in] xoffset The scroll offset along the x-axis. * @param[in] yoffset The scroll offset along the y-axis. - * @param[in] flags A bit-mask providing extra data about the event. flags & 1 will be true if and only if the offset values are "high-precision". Typically pixel values. Otherwise the offset values are number of lines. + * @param[in] flags A bit-mask providing extra data about the event. + * flags & 1 will be true if and only if the offset values are "high-precision". + * Typically pixel values. Otherwise the offset values are number of lines. + * (flags >> 1) & 7 will have value 1 for the start of momentum scrolling, + * value 2 for stationary momentum scrolling, value 3 for momentum scrolling + * in progress, value 4 for momentum scrolling ended, value 5 for momentum + * scrolling cancelled and value 6 if scrolling may begin soon. * * @sa @ref scrolling * @sa @ref glfwSetScrollCallback @@ -1389,6 +1395,7 @@ typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int); typedef int (* GLFWapplicationshouldhandlereopenfun)(int); typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); +typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); typedef int (*glfwInit_func)(); glfwInit_func glfwInit_impl; #define glfwInit glfwInit_impl @@ -1901,4 +1908,8 @@ typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long lon glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl; #define glfwRequestWaylandFrameEvent glfwRequestWaylandFrameEvent_impl +typedef unsigned long long (*glfwDBusUserNotify_func)(const char*, const char*, const char*, const char*, int32_t, GLFWDBusnotificationcreatedfun, void*); +glfwDBusUserNotify_func glfwDBusUserNotify_impl; +#define glfwDBusUserNotify glfwDBusUserNotify_impl + const char* load_glfw(const char* path);