]> git.eshelyaron.com Git - emacs.git/commitdiff
Introduce support for Desktop Notifications on Haiku
authorPo Lu <luangruo@yahoo.com>
Thu, 17 Aug 2023 08:34:32 +0000 (08:34 +0000)
committerPo Lu <luangruo@yahoo.com>
Thu, 17 Aug 2023 08:34:32 +0000 (08:34 +0000)
* 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) <NOTIFICATION_CLICK_EVENT>: 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
etc/NEWS
lisp/org/org-clock.el
src/haiku_io.c
src/haiku_select.cc
src/haiku_support.cc
src/haiku_support.h
src/haikuselect.c
src/haikuselect.h
src/haikuterm.c
src/termhooks.h

index be8624e0fdec201e1ed8c084cd642517416de4a2..b6d34ae0a3d99709fab5ba981d676307d7fbbcd1 100644 (file)
@@ -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
index 808b599672958300543675bf58fc233dd25b0d32..966d1f8c2923bffea575a796519945055739029f 100644 (file)
--- 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
index b85ce6a636877f584852f8efe13a233321fd6858..32ef0eb42911a3e164326a4f8188bd271eee08a6 100644 (file)
@@ -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"
index 4f1b1435b4bad0c78a3909eb7bf66b1cbdec7411..c6d7108bf4948db6e699ee8f611103566dd13dc2 100644 (file)
@@ -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 ();
index fe46075a007cd30f73aa3a1d8e9fbbea1d0a46c0..76e2d82920488548789c31cf074ae52b81d0e88c 100644 (file)
@@ -17,15 +17,24 @@ You should have received a copy of the GNU General Public License
 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include <config.h>
+#include <intprops.h>
 
 #include <Application.h>
+#include <Bitmap.h>
 #include <Clipboard.h>
+#include <Entry.h>
 #include <Message.h>
+#include <Notification.h>
+#include <OS.h>
 #include <Path.h>
-#include <Entry.h>
+#include <String.h>
+
+#include <translation/TranslationUtils.h>
 
 #include <cstdlib>
 #include <cstring>
+#include <cstdint>
+#include <cstdio>
 
 #include "haikuselect.h"
 
@@ -58,6 +67,10 @@ static bool owned_secondary;
 /* And the clipboard.  */
 static bool owned_clipboard;
 
+\f
+
+/* 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 ();
 }
+
+\f
+
+/* 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;
+}
index 28d8fae39b76f02914af5e788a3e0b8265d4e878..d5649e4d2ce4317ad32f79bd96ffe5c345aede92 100644 (file)
@@ -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
index 564f61f57c799268586ef943d164641ee3d8e04c..5c22eb3b0db290fd1e46ae18121fb61f17aee2ae 100644 (file)
@@ -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;
index b57c336c2647324c4ab5adf2de0070b1339d0fd5..608b8e8fe30894fba583ba1c2c22f17a7a1c9849 100644 (file)
@@ -1255,6 +1255,120 @@ haiku_start_watching_selections (void)
   be_start_watching_selection (CLIPBOARD_SECONDARY);
 }
 
+\f
+
+/* 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;
 }
index 28a1682e587d3632b5b8ac82952473c9a41b98d0..c117a2ab4f943ba12dae73cd04f1f889dc65f88b 100644 (file)
@@ -21,9 +21,11 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #ifdef __cplusplus
 #include <cstdio>
-#else
+#include <cstdint>
+#else /* !__cplusplus */
 #include <stdio.h>
-#endif
+#include <stdint.h>
+#endif /* __cplusplus */
 
 #include <SupportDefs.h>
 
@@ -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);
 
+\f
+
+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:
index ed28a806ff20560048aff4b8d620657a0484c684..70984936bf940392885c384dfef919934534754e 100644 (file)
@@ -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:
index d978819aeca0b6bd8b4784347aa025b4ee2bfe1c..6bf507aae3368371ef63aba996b8194c0648d982 100644 (file)
@@ -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.  */