@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}.
@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
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
(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)
((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"
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 ();
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"
/* And the clipboard. */
static bool owned_clipboard;
+\f
+
+/* C++ clipboard support. */
+
static BClipboard *
get_clipboard_object (enum haiku_clipboard clipboard)
{
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;
+}
}
};
+#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:
{
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 ();
}
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
MENU_BAR_LEFT,
CLIPBOARD_CHANGED_EVENT,
FONT_CHANGE_EVENT,
+ NOTIFICATION_CLICK_EVENT,
};
struct haiku_clipboard_changed_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;
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)
{
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);
defsubr (&Shaiku_roster_launch);
defsubr (&Shaiku_write_node_attribute);
defsubr (&Shaiku_send_message);
+ defsubr (&Shaiku_notifications_notify);
haiku_dnd_frame = NULL;
}
#ifdef __cplusplus
#include <cstdio>
-#else
+#include <cstdint>
+#else /* !__cplusplus */
#include <stdio.h>
-#endif
+#include <stdint.h>
+#endif /* __cplusplus */
#include <SupportDefs.h>
#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);
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 *);
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:
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:
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. */