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) <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.
This commit is contained in:
Po Lu 2023-08-17 08:34:32 +00:00
parent c1a45041d6
commit 5856ea5e4e
11 changed files with 415 additions and 12 deletions

View 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

View file

@ -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

View 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"

View 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 ();

View 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 <Message.h>
#include <Path.h>
#include <Entry.h>
#include <Message.h>
#include <Notification.h>
#include <OS.h>
#include <Path.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;
/* 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;
}

View file

@ -581,6 +581,24 @@ class EmacsScreenChangeMonitor : public BWindow
}
};
#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 @@ class Emacs : public BApplication
{
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 @@ class Emacs : public BApplication
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

View 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;

View file

@ -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;
}

View 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);
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:

View 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:

View 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. */