From 5856ea5e4e897f4cb5cd1c3c28d14b335fe5cf54 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 17 Aug 2023 08:34:32 +0000 Subject: [PATCH] Introduce support for Desktop Notifications on Haiku * doc/lispref/os.texi (Desktop Notifications): Document Haiku desktop notifications. * etc/NEWS: Announce this change. * lisp/org/org-clock.el (haiku-notifications-notify): New declaration. (org-show-notification): Employ that function. * src/haiku_io.c (haiku_len) : Return the length for this type of event. * src/haiku_select.cc (my_team_id, be_display_notification): New functions. * src/haiku_support.cc (my_team_id, ArgvReceived): New functions. * src/haiku_support.h (enum haiku_event_type): New event type NOTIFICATION_CLICK_EVENT. (struct haiku_notification_click_event): New structure. * src/haikuselect.c (haiku_notifications_notify_1) (Fhaiku_notifications_notify): New functions. (syms_of_haikuselect): Register new defsubr. * src/haikuterm.c (haiku_read_socket): * src/haikuselect.h: * src/termhooks.h: Add new events for notification clicks on Haiku. --- doc/lispref/os.texi | 35 +++++++++- etc/NEWS | 6 ++ lisp/org/org-clock.el | 6 ++ src/haiku_io.c | 2 + src/haiku_select.cc | 146 +++++++++++++++++++++++++++++++++++++++++- src/haiku_support.cc | 54 +++++++++++++++- src/haiku_support.h | 7 ++ src/haikuselect.c | 125 ++++++++++++++++++++++++++++++++++++ src/haikuselect.h | 25 +++++--- src/haikuterm.c | 13 ++++ src/termhooks.h | 6 ++ 11 files changed, 414 insertions(+), 11 deletions(-) diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index be8624e0fde..b6d34ae0a3d 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -2850,7 +2850,9 @@ Emacs is restarted by the session manager. @cindex notifications, on desktop Emacs is able to send @dfn{notifications} on systems that support the -freedesktop.org Desktop Notifications Specification and on MS-Windows. +freedesktop.org Desktop Notifications Specification, MS-Windows, and +Haiku. + In order to use this functionality on POSIX hosts, Emacs must have been compiled with D-Bus support, and the @code{notifications} library must be loaded. @xref{Top, , D-Bus,dbus,D-Bus integration in Emacs}. @@ -3167,6 +3169,37 @@ This function removes the tray notification given by its unique @var{id}. @end defun +@cindex desktop notifications, Haiku +When Emacs runs under Haiku as a GUI program, it is also provides a +restricted pastiche of the D-Bus desktop notifications interface +previously addressed. The principle capabilities absent from the +function detailed below are call-back functions such as +@code{:actions}, @code{:on-action} and @code{:on-close}. + +@defun haiku-notifications-notify &rest params +This function sends a notification to the desktop notification server, +incorporating a number of parameters that are akin to some of those +accepted by @code{notifications-notify}. The parameters are: + +@table @code +@item :title @var{title} +@item :body @var{body} +@item :replaces-id @var{replaces-id} +@item :urgency @var{urgency} +These have the same meaning as they do when used in calls to +@code{notifications-notify}. + +@item :app-icon @var{app-icon} +This should be the file name designating an image file to use as the +icon for the notification displayed. If @code{nil}, the icon +presented will instead be Emacs's app icon. +@end table + +Its return value is a number identifying the notification, which can +be exploited as the @code{:replaces-id} parameter to a subsequent call +to this function. +@end defun + @node File Notifications @section Notifications on File Changes @cindex file notifications diff --git a/etc/NEWS b/etc/NEWS index 808b5996729..966d1f8c292 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -783,6 +783,12 @@ Use 'define-minor-mode' and 'define-globalized-minor-mode' instead. See the "(elisp) Porting Old Advice" node for help converting them to use 'advice-add' or 'define-advice' instead. ++++ +** Desktop notifications are now supported on the Haiku operating system. +The new function 'haiku-notifications-notify' provides a subset of the +capabilities of the 'notifications-notify' function in a manner +analogous to 'w32-notification-notify'. + +++ ** New value 'if-regular' for the REPLACE argument to 'insert-file-contents'. It results in 'insert-file-contents' erasing the buffer instead of diff --git a/lisp/org/org-clock.el b/lisp/org/org-clock.el index b85ce6a6368..32ef0eb4291 100644 --- a/lisp/org/org-clock.el +++ b/lisp/org/org-clock.el @@ -51,6 +51,7 @@ (declare-function org-dynamic-block-define "org" (type func)) (declare-function w32-notification-notify "w32fns.c" (&rest params)) (declare-function w32-notification-close "w32fns.c" (&rest params)) +(declare-function haiku-notifications-notify "haikufns.c") (defvar org-frame-title-format-backup nil) (defvar org-state) @@ -855,6 +856,11 @@ use libnotify if available, or fall back on a message." ((stringp org-show-notification-handler) (start-process "emacs-timer-notification" nil org-show-notification-handler notification)) + ((fboundp 'haiku-notifications-notify) + ;; N.B. timeouts are not available under Haiku. + (haiku-notifications-notify :title "Org mode message" + :body notification + :urgency 'low)) ((fboundp 'w32-notification-notify) (let ((id (w32-notification-notify :title "Org mode message" diff --git a/src/haiku_io.c b/src/haiku_io.c index 4f1b1435b4b..c6d7108bf49 100644 --- a/src/haiku_io.c +++ b/src/haiku_io.c @@ -111,6 +111,8 @@ haiku_len (enum haiku_event_type type) return sizeof (struct haiku_clipboard_changed_event); case FONT_CHANGE_EVENT: return sizeof (struct haiku_font_change_event); + case NOTIFICATION_CLICK_EVENT: + return sizeof (struct haiku_notification_click_event); } emacs_abort (); diff --git a/src/haiku_select.cc b/src/haiku_select.cc index fe46075a007..76e2d829204 100644 --- a/src/haiku_select.cc +++ b/src/haiku_select.cc @@ -17,15 +17,24 @@ You should have received a copy of the GNU General Public License along with GNU Emacs. If not, see . */ #include +#include #include +#include #include +#include #include +#include +#include #include -#include +#include + +#include #include #include +#include +#include #include "haikuselect.h" @@ -58,6 +67,10 @@ static bool owned_secondary; /* And the clipboard. */ static bool owned_clipboard; + + +/* C++ clipboard support. */ + static BClipboard * get_clipboard_object (enum haiku_clipboard clipboard) { @@ -517,3 +530,134 @@ be_get_clipboard_count (enum haiku_clipboard id) clipboard = get_clipboard_object (id); return clipboard->SystemCount (); } + + + +/* C++ notifications support. + + Desktop notifications on Haiku lack some of the features furnished + by notifications.el, specifically displaying multiple titled + actions within a single notification, sending callbacks when the + notification is dismissed, and providing a timeout after which the + notification is hidden. + + Other features, such as notification categories and identifiers, + have clean straightforward relationships with their counterparts in + notifications.el. */ + +/* The last notification ID allocated. */ +static intmax_t last_notification_id; + +/* Return the `enum notification_type' for TYPE. TYPE is the TYPE + argument to a call to `be_display_notification'. */ + +static enum notification_type +type_for_type (int type) +{ + switch (type) + { + case 0: + return B_INFORMATION_NOTIFICATION; + + case 1: + return B_IMPORTANT_NOTIFICATION; + + case 2: + return B_ERROR_NOTIFICATION; + } + + abort (); +} + +/* Return the ID of this team. */ + +static team_id +my_team_id (void) +{ + thread_id id; + thread_info info; + + id = find_thread (NULL); + get_thread_info (id, &info); + + return info.team; +} + +/* Display a desktop notification and return its identifier. + + TITLE is the title text of the notification, encoded as UTF-8 text. + + BODY is the text to be displayed within the body of the + notification. + + SUPERSEDES is the identifier of a previous notification to replace, + or -1 if a new notification should be displayed. + + TYPE states the urgency of the notification. If 0, the + notification is displayed without special decoration. If 1, the + notification is displayed with a blue band to its left, identifying + it as a notification of medium importance. If 2, the notification + is displayed with a red band to its left, marking it as one of + critical importance. + + ICON is the name of a file containing the icon of the notification, + or NULL, in which case Emacs's app icon will be displayed. */ + +intmax_t +be_display_notification (const char *title, const char *body, + intmax_t supersedes, int type, const char *icon) +{ + intmax_t id; + BNotification notification (type_for_type (type)); + char buffer[INT_STRLEN_BOUND (team_id) + + INT_STRLEN_BOUND (intmax_t) + + sizeof "."]; + BBitmap *bitmap; + + if (supersedes < 0) + { + /* SUPERSEDES hasn't been provided, so allocate a new + notification ID. */ + + INT_ADD_WRAPV (last_notification_id, 1, + &last_notification_id); + id = last_notification_id; + } + else + id = supersedes; + + /* Set the title and body text. */ + notification.SetTitle (title); + notification.SetContent (body); + + /* Derive the notification ID from the ID of this team, so as to + avoid abrogating notifications from other Emacs sessions. */ + sprintf (buffer, "%d.%jd", my_team_id (), id); + notification.SetMessageID (BString (buffer)); + + /* Now set the bitmap icon, if given. */ + + if (icon) + { + bitmap = BTranslationUtils::GetBitmap (icon); + + if (bitmap) + { + notification.SetIcon (bitmap); + delete bitmap; + } + } + + /* After this, Emacs::ArgvReceived should be called when the + notification is clicked. Lamentably, this does not come about, + probably because arguments are only passed to applications if + they are not yet running. */ +#if 0 + notification.SetOnClickApp ("application/x-vnd.GNU-emacs"); + notification.AddOnClickArg (BString ("-Notification,") += buffer); +#endif /* 0 */ + + /* Finally, send the notification. */ + notification.Send (); + return id; +} diff --git a/src/haiku_support.cc b/src/haiku_support.cc index 28d8fae39b7..d5649e4d2ce 100644 --- a/src/haiku_support.cc +++ b/src/haiku_support.cc @@ -581,6 +581,24 @@ public: } }; +#if 0 + +/* Return the ID of this team. */ + +static team_id +my_team_id (void) +{ + thread_id id; + thread_info info; + + id = find_thread (NULL); + get_thread_info (id, &info); + + return info.team; +} + +#endif /* 0 */ + class Emacs : public BApplication { public: @@ -621,7 +639,8 @@ public: { BAlert *about = new BAlert (PACKAGE_NAME, PACKAGE_STRING - "\nThe extensible, self-documenting, real-time display editor.", + "\nThe extensible, self-documenting, " + "real-time display editor.", "Close"); about->Go (); } @@ -674,6 +693,39 @@ public: else BApplication::MessageReceived (msg); } + + /* The code below doesn't function; see `be_display_notification' + for further specifics. */ + +#if 0 + void + ArgvReceived (int32 argc, char **argv) + { + struct haiku_notification_click_event rq; + intmax_t id; + team_id team; + + /* ArgvReceived is called after Emacs is first started, with each + command line argument passed to Emacs. It is, moreover, called + with ARGC set to 1 and ARGV[0] a string starting with + -Notification, after a notification is clicked. This string + both incorporates the team ID and the notification ID. */ + + if (argc == 1 + && sscanf (argv[0], "-Notification,%d.%jd", &team, &id) == 2) + { + /* Since this is a valid notification message, generate an + event if the team ID matches. */ + if (team == my_team_id ()) + { + rq.id = id; + haiku_write (NOTIFICATION_CLICK_EVENT, &rq); + } + } + + BApplication::ArgvReceived (argc, argv); + } +#endif /* 0 */ }; class EmacsWindow : public BWindow diff --git a/src/haiku_support.h b/src/haiku_support.h index 564f61f57c7..5c22eb3b0db 100644 --- a/src/haiku_support.h +++ b/src/haiku_support.h @@ -116,6 +116,7 @@ enum haiku_event_type MENU_BAR_LEFT, CLIPBOARD_CHANGED_EVENT, FONT_CHANGE_EVENT, + NOTIFICATION_CLICK_EVENT, }; struct haiku_clipboard_changed_event @@ -464,6 +465,12 @@ struct haiku_font_change_event enum haiku_what_font what; }; +struct haiku_notification_click_event +{ + /* ID uniquely designating a single notification. */ + intmax_t id; +}; + struct haiku_session_manager_reply { bool quit_reply; diff --git a/src/haikuselect.c b/src/haikuselect.c index b57c336c264..608b8e8fe30 100644 --- a/src/haikuselect.c +++ b/src/haikuselect.c @@ -1255,6 +1255,120 @@ haiku_start_watching_selections (void) be_start_watching_selection (CLIPBOARD_SECONDARY); } + + +/* Notification support. */ + +static intmax_t +haiku_notifications_notify_1 (Lisp_Object title, Lisp_Object body, + Lisp_Object replaces_id, + Lisp_Object app_icon, Lisp_Object urgency) +{ + int type; + intmax_t supersedes; + const char *icon; + + if (EQ (urgency, Qlow)) + type = 0; + else if (EQ (urgency, Qnormal)) + type = 1; + else if (EQ (urgency, Qcritical)) + type = 2; + else + signal_error ("Invalid notification type provided", urgency); + + supersedes = -1; + + if (!NILP (replaces_id)) + { + CHECK_INTEGER (replaces_id); + if (!integer_to_intmax (replaces_id, &supersedes)) + supersedes = -1; + } + + icon = NULL; + + if (!NILP (app_icon)) + icon = SSDATA (ENCODE_FILE (app_icon)); + + /* GC should not transpire from here onwards. */ + return be_display_notification (SSDATA (title), SSDATA (body), + supersedes, type, icon); +} + +DEFUN ("haiku-notifications-notify", Fhaiku_notifications_notify, + Shaiku_notifications_notify, 0, MANY, 0, doc: + /* Display a desktop notification. +ARGS must contain keywords followed by values. Each of the following +keywords is understood: + + :title The notification title. + :body The notification body. + :replaces-id The ID of a previous notification to supersede. + :app-icon The file name of the notification's icon, if any. + :urgency One of the symbols `low', `normal' or `critical', + specifying the importance of the notification. + +:title and :body must be provided. Value is an integer (fixnum or +bignum) identifying the notification displayed. + +usage: (haiku-notifications-notify &rest ARGS) */) + (ptrdiff_t nargs, Lisp_Object *args) +{ + Lisp_Object title, body, replaces_id, app_icon, urgency; + Lisp_Object key, value; + ptrdiff_t i; + + /* First, clear each of the variables above. */ + title = body = replaces_id = app_icon = urgency = Qnil; + + /* If NARGS is odd, error. */ + + if (nargs & 1) + error ("Odd number of arguments in call to `haiku-notifications-notify'"); + + /* Next, iterate through ARGS, searching for arguments. */ + + for (i = 0; i < nargs; i += 2) + { + key = args[i]; + value = args[i + 1]; + + if (EQ (key, QCtitle)) + title = value; + else if (EQ (key, QCbody)) + body = value; + else if (EQ (key, QCreplaces_id)) + replaces_id = value; + else if (EQ (key, QCapp_icon)) + app_icon = value; + else if (EQ (key, QCurgency)) + urgency = value; + } + + /* Demand at least TITLE and BODY be present. */ + + if (NILP (title) || NILP (body)) + error ("Title or body not provided"); + + /* Now check the type and possibly expand each non-nil argument. */ + + CHECK_STRING (title); + title = ENCODE_UTF_8 (title); + CHECK_STRING (body); + body = ENCODE_UTF_8 (body); + + if (NILP (urgency)) + urgency = Qlow; + + if (!NILP (app_icon)) + app_icon = Fexpand_file_name (app_icon, Qnil); + + return make_int (haiku_notifications_notify_1 (title, body, + replaces_id, + app_icon, urgency)); +} + void syms_of_haikuselect (void) { @@ -1312,6 +1426,16 @@ keyboard modifiers currently held down. */); DEFSYM (Qdouble, "double"); DEFSYM (Qalready_running, "already-running"); + DEFSYM (QCtitle, ":title"); + DEFSYM (QCbody, ":body"); + DEFSYM (QCreplaces_id, ":replaces-id"); + DEFSYM (QCapp_icon, ":app-icon"); + DEFSYM (QCurgency, ":urgency"); + + DEFSYM (Qlow, "low"); + DEFSYM (Qnormal, "normal"); + DEFSYM (Qcritical, "critical"); + defsubr (&Shaiku_selection_data); defsubr (&Shaiku_selection_timestamp); defsubr (&Shaiku_selection_put); @@ -1320,6 +1444,7 @@ keyboard modifiers currently held down. */); defsubr (&Shaiku_roster_launch); defsubr (&Shaiku_write_node_attribute); defsubr (&Shaiku_send_message); + defsubr (&Shaiku_notifications_notify); haiku_dnd_frame = NULL; } diff --git a/src/haikuselect.h b/src/haikuselect.h index 28a1682e587..c117a2ab4f9 100644 --- a/src/haikuselect.h +++ b/src/haikuselect.h @@ -21,9 +21,11 @@ along with GNU Emacs. If not, see . */ #ifdef __cplusplus #include -#else +#include +#else /* !__cplusplus */ #include -#endif +#include +#endif /* __cplusplus */ #include @@ -37,15 +39,16 @@ enum haiku_clipboard #ifdef __cplusplus extern "C" { -#endif +#endif /* __cplusplus */ /* Defined in haikuselect.c. */ extern void haiku_selection_disowned (enum haiku_clipboard, int64); /* Defined in haiku_select.cc. */ extern void be_clipboard_init (void); -extern char *be_find_clipboard_data (enum haiku_clipboard, const char *, ssize_t *); -extern void be_set_clipboard_data (enum haiku_clipboard, const char *, const char *, - ssize_t, bool); +extern char *be_find_clipboard_data (enum haiku_clipboard, const char *, + ssize_t *); +extern void be_set_clipboard_data (enum haiku_clipboard, const char *, + const char *, ssize_t, bool); extern bool be_clipboard_owner_p (enum haiku_clipboard); extern void be_update_clipboard_count (enum haiku_clipboard); @@ -58,7 +61,8 @@ extern uint32 be_get_message_type (void *); extern void be_set_message_type (void *, uint32); extern void *be_get_message_message (void *, const char *, int32); extern void *be_create_simple_message (void); -extern int be_add_message_data (void *, const char *, int32, const void *, ssize_t); +extern int be_add_message_data (void *, const char *, int32, const void *, + ssize_t); extern int be_add_refs_data (void *, const char *, const char *); extern int be_add_point_data (void *, const char *, float, float); extern int be_add_message_message (void *, const char *, void *); @@ -69,9 +73,14 @@ extern void be_start_watching_selection (enum haiku_clipboard); extern bool be_selection_outdated_p (enum haiku_clipboard, int64); extern int64 be_get_clipboard_count (enum haiku_clipboard); + + +extern intmax_t be_display_notification (const char *, const char *, + intmax_t, int, const char *); + #ifdef __cplusplus }; -#endif +#endif /* __cplusplus */ #endif /* _HAIKU_SELECT_H_ */ // Local Variables: diff --git a/src/haikuterm.c b/src/haikuterm.c index ed28a806ff2..70984936bf9 100644 --- a/src/haikuterm.c +++ b/src/haikuterm.c @@ -4042,6 +4042,19 @@ haiku_read_socket (struct terminal *terminal, struct input_event *hold_quit) handled in Lisp. */ haiku_handle_font_change_event (buf, &inev); break; + + case NOTIFICATION_CLICK_EVENT: + /* This code doesn't function, but the why is unknown. */ +#if 0 + { + struct haiku_notification_click_event *b = buf; + + inev.kind = NOTIFICATION_CLICKED_EVENT; + inev.arg = make_int (b->id); + break; + } +#endif /* 0 */ + case KEY_UP: case DUMMY_EVENT: default: diff --git a/src/termhooks.h b/src/termhooks.h index d978819aeca..6bf507aae33 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -337,6 +337,12 @@ enum event_kind monitor configuration changed. .timestamp gives the time on which the monitors changed. */ , MONITORS_CHANGED_EVENT + +#ifdef HAVE_HAIKU + /* In a NOTIFICATION_CLICKED_EVENT, .arg is an integer identifying + the notification that was clicked. */ + , NOTIFICATION_CLICKED_EVENT +#endif /* HAVE_HAIKU */ }; /* Bit width of an enum event_kind tag at the start of structs and unions. */ -- 2.39.5