Get notification icons with data working on macOS

This commit is contained in:
Kovid Goyal
2024-07-30 08:46:10 +05:30
parent f998af56fc
commit 3cc2a8c484
3 changed files with 42 additions and 18 deletions

View File

@@ -453,7 +453,7 @@ live_delivered_notifications(void) {
}
static void
schedule_notification(const char *appname, const char *identifier, const char *title, const char *body, int urgency) {
schedule_notification(const char *appname, const char *identifier, const char *title, const char *body, const char *image_path, int urgency) {
UNUserNotificationCenter *center = get_notification_center_safely();
if (!center) return;
// Configure the notification's payload.
@@ -478,6 +478,18 @@ schedule_notification(const char *appname, const char *identifier, const char *t
[content setValue:@(level) forKey:@"interruptionLevel"];
}
#endif
if (image_path) {
@try {
NSError *error;
NSURL *image_url = [NSURL fileURLWithFileSystemRepresentation:image_path isDirectory:NO relativeToURL:nil]; // autoreleased
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:image_url options:nil error:&error]; // autoreleased
if (attachment) { content.attachments = @[ attachment ]; }
else NSLog(@"Error attaching image %@ to notification: %@", @(image_path), error.localizedDescription);
} @catch(NSException *exc) {
NSLog(@"Creating image attachment %@ for notification failed with error: %@", @(image_path), exc.reason);
}
}
// Deliver the notification
static unsigned long counter = 1;
UNNotificationRequest* request = [
@@ -497,7 +509,7 @@ schedule_notification(const char *appname, const char *identifier, const char *t
typedef struct {
char *identifier, *title, *body, *appname;
char *identifier, *title, *body, *appname, *image_path;
int urgency;
} QueuedNotification;
@@ -508,13 +520,12 @@ typedef struct {
static NotificationQueue notification_queue = {0};
static void
queue_notification(const char *appname, const char *identifier, const char *title, const char* body, int urgency) {
queue_notification(const char *appname, const char *identifier, const char *title, const char* body, const char *image_path, int urgency) {
ensure_space_for((&notification_queue), notifications, QueuedNotification, notification_queue.count + 16, capacity, 16, true);
QueuedNotification *n = notification_queue.notifications + notification_queue.count++;
n->appname = appname ? strdup(appname) : NULL;
n->identifier = identifier ? strdup(identifier) : NULL;
n->title = title ? strdup(title) : NULL;
n->body = body ? strdup(body) : NULL;
#define d(x) n->x = (x && x[0]) ? strdup(x) : NULL;
d(appname); d(identifier); d(title); d(body); d(image_path);
#undef d
n->urgency = urgency;
}
@@ -523,13 +534,13 @@ drain_pending_notifications(BOOL granted) {
if (granted) {
for (size_t i = 0; i < notification_queue.count; i++) {
QueuedNotification *n = notification_queue.notifications + i;
schedule_notification(n->appname, n->identifier, n->title, n->body, n->urgency);
schedule_notification(n->appname, n->identifier, n->title, n->body, n->image_path, n->urgency);
}
}
while(notification_queue.count) {
QueuedNotification *n = notification_queue.notifications + --notification_queue.count;
if (!granted) do_notification_callback(@(n->identifier), "creation_failed");
free(n->identifier); free(n->title); free(n->body); free(n->appname);
free(n->identifier); free(n->title); free(n->body); free(n->appname); free(n->image_path);
memset(n, 0, sizeof(QueuedNotification));
}
}
@@ -551,14 +562,14 @@ cocoa_live_delivered_notifications(PyObject *self UNUSED, PyObject *x UNUSED) {
static PyObject*
cocoa_send_notification(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
const char *identifier = "", *title = "", *body = "", *appname = ""; int urgency = 1;
static const char* kwlist[] = {"appname", "identifier", "title", "body", "urgency", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssss|i", (char**)kwlist, &appname, &identifier, &title, &body, &urgency)) return NULL;
const char *identifier = "", *title = "", *body = "", *appname = "", *image_path = ""; int urgency = 1;
static const char* kwlist[] = {"appname", "identifier", "title", "body", "image_path", "urgency", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssss|si", (char**)kwlist, &appname, &identifier, &title, &body, &image_path, &urgency)) return NULL;
UNUserNotificationCenter *center = get_notification_center_safely();
if (!center) Py_RETURN_NONE;
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
queue_notification(appname, identifier, title, body, urgency);
queue_notification(appname, identifier, title, body, image_path, urgency);
// The badge permission needs to be requested as well, even though it is not used,
// otherwise macOS refuses to show the preference checkbox for enable/disable notification sound.

View File

@@ -566,6 +566,7 @@ def cocoa_send_notification(
identifier: str,
title: str,
body: str,
image_path: str = "",
urgency: int = 1,
) -> None:
pass

View File

@@ -20,6 +20,16 @@ from .utils import get_custom_window_icon, log_error, sanitize_control_codes
debug_desktop_integration = False
def image_type(data: bytes) -> str:
if data[:8] == b"\211PNG\r\n\032\n":
return 'png'
if data[:6] in (b'GIF87a', b'GIF89a'):
return 'gif'
if data[:2] == b'\xff\xd8':
return 'jpeg'
return 'unknown'
class IconDataCache:
@@ -52,7 +62,7 @@ class IconDataCache:
def hash(self, data: bytes) -> str:
from kittens.transfer.rsync import xxh128_hash_with_seed
d = xxh128_hash_with_seed(data, self.seed)
return d.hex()
return d.hex() + '.' + image_type(data)
def add_icon(self, key: str, data: bytes) -> str:
self._ensure_state()
@@ -482,12 +492,14 @@ class MacOSIntegration(DesktopIntegration):
# If the body is not set macos makes the title the body and uses
# "kitty" as the title. So use a single space for the body in this
# case. Although https://developer.apple.com/documentation/usernotifications/unnotificationcontent/body?language=objc
# says printf style strings are stripped this does not actually happen,
# so dont double %
# for %% escaping.
# says printf style strings are stripped this does not actually happen, so dont double % for %% escaping.
body = (nc.body or ' ')
assert nc.urgency is not None
cocoa_send_notification(nc.application_name or 'kitty', str(desktop_notification_id), nc.title, body, nc.urgency.value)
image_path = nc.icon_path
cocoa_send_notification(
nc.application_name or 'kitty', str(desktop_notification_id), nc.title, body,
image_path=image_path, urgency=nc.urgency.value,
)
return desktop_notification_id
def notification_activated(self, event: str, ident: str) -> None: