macOS: Switch to the User Notifications framework
The current notifications framework has been deprecated in Big Sur. The new framework only allows notifications from signed and notarized applications, so people using kitty from HomeBrew/source are out of luck. And notifications can only be displayed once the user grants permission. A completely brain-dead design. Complain to Apple.
This commit is contained in:
parent
ae5ceedfe9
commit
4e3c6e52aa
@ -10,6 +10,12 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
- Add support for displaying correct colors with non-sRGB PNG files (Adds a
|
||||
dependency on liblcms2)
|
||||
|
||||
- macOS: Switch to using the User Notifications framework for notifications.
|
||||
The current notifications framework has been deprecated in Big Sur. The new
|
||||
framework only allows notifications from signed and notarized applications,
|
||||
so people using kitty from homebrew/source are out of luck. Complain to
|
||||
Apple.
|
||||
|
||||
|
||||
0.18.3 [2020-08-11]
|
||||
-------------------
|
||||
|
||||
@ -67,6 +67,8 @@ def notification_activated(identifier: str) -> None:
|
||||
if identifier == 'new-version':
|
||||
from .update_check import notification_activated as do
|
||||
do()
|
||||
elif identifier.startswith('test-notify-'):
|
||||
log_error(f'Test notification {identifier} activated')
|
||||
|
||||
|
||||
def listen_on(spec: str) -> int:
|
||||
@ -1539,4 +1541,7 @@ class Boss:
|
||||
|
||||
def send_test_notification(self) -> None:
|
||||
from .notify import notify
|
||||
notify('Test notification', 'Hello world')
|
||||
from time import monotonic
|
||||
now = monotonic()
|
||||
ident = f'test-notify-{now}'
|
||||
notify(f'Test {now}', f'At: {now}', identifier=ident)
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "state.h"
|
||||
#include "monotonic.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <UserNotifications/UserNotifications.h>
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
// Needed for _NSGetProgname
|
||||
@ -133,7 +134,79 @@ get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
|
||||
}
|
||||
|
||||
static PyObject *notification_activated_callback = NULL;
|
||||
static PyObject*
|
||||
|
||||
@interface NotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
|
||||
@end
|
||||
|
||||
@implementation NotificationDelegate
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
||||
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
||||
withCompletionHandler:(void (^)(void))completionHandler {
|
||||
(void)(center);
|
||||
if (notification_activated_callback) {
|
||||
NSString *identifier = [[[response notification] request] identifier];
|
||||
PyObject *ret = PyObject_CallFunction(notification_activated_callback, "z",
|
||||
identifier ? [identifier UTF8String] : NULL);
|
||||
if (ret == NULL) PyErr_Print();
|
||||
else Py_DECREF(ret);
|
||||
}
|
||||
completionHandler();
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
static void
|
||||
schedule_notification(const char *identifier, const char *title, const char *body) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
if (!center) return;
|
||||
// Configure the notification's payload.
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
if (title) content.title = @(title);
|
||||
if (body) content.body = @(body);
|
||||
// Deliver the notification
|
||||
static unsigned long counter = 1;
|
||||
UNNotificationRequest* request = [
|
||||
UNNotificationRequest requestWithIdentifier:(identifier ? @(identifier) : [NSString stringWithFormat:@"Id_%lu", counter++])
|
||||
content:content trigger:nil];
|
||||
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
log_error("Failed to show notification: %s", [[error localizedDescription] UTF8String]);
|
||||
}
|
||||
}];
|
||||
[content release];
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *identifier, *title, *body;
|
||||
} QueuedNotification;
|
||||
|
||||
typedef struct {
|
||||
QueuedNotification *notifications;
|
||||
size_t count, capacity;
|
||||
} NotificationQueue;
|
||||
static NotificationQueue notification_queue = {0};
|
||||
|
||||
static void
|
||||
queue_notification(const char *identifier, const char *title, const char* body) {
|
||||
ensure_space_for((¬ification_queue), notifications, QueuedNotification, notification_queue.count + 16, capacity, 16, true);
|
||||
QueuedNotification *n = notification_queue.notifications + notification_queue.count++;
|
||||
n->identifier = identifier ? strdup(identifier) : NULL;
|
||||
n->title = title ? strdup(title) : NULL;
|
||||
n->body = body ? strdup(body) : NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
drain_pending_notifications(BOOL granted) {
|
||||
while(notification_queue.count) {
|
||||
QueuedNotification *n = notification_queue.notifications + --notification_queue.count;
|
||||
if (granted) schedule_notification(n->identifier, n->title, n->body);
|
||||
free(n->identifier); free(n->title); free(n->body);
|
||||
n->identifier = NULL; n->title = NULL; n->body = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject*
|
||||
set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
|
||||
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
|
||||
notification_activated_callback = callback;
|
||||
@ -141,65 +214,26 @@ set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@interface NotificationDelegate : NSObject <NSUserNotificationCenterDelegate>
|
||||
@end
|
||||
|
||||
@implementation NotificationDelegate
|
||||
- (void)userNotificationCenter:(NSUserNotificationCenter *)center
|
||||
didDeliverNotification:(NSUserNotification *)notification {
|
||||
(void)(center); (void)(notification);
|
||||
}
|
||||
|
||||
- (BOOL) userNotificationCenter:(NSUserNotificationCenter *)center
|
||||
shouldPresentNotification:(NSUserNotification *)notification {
|
||||
(void)(center); (void)(notification);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) userNotificationCenter:(NSUserNotificationCenter *)center
|
||||
didActivateNotification:(NSUserNotification *)notification {
|
||||
(void)(center); (void)(notification);
|
||||
if (notification_activated_callback) {
|
||||
PyObject *ret = PyObject_CallFunction(notification_activated_callback, "z",
|
||||
notification.userInfo[@"user_id"] ? [notification.userInfo[@"user_id"] UTF8String] : NULL);
|
||||
if (ret == NULL) PyErr_Print();
|
||||
else Py_DECREF(ret);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static PyObject*
|
||||
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||
char *identifier = NULL, *title = NULL, *subtitle = NULL, *informativeText = NULL, *path_to_image = NULL;
|
||||
if (!PyArg_ParseTuple(args, "zssz|z", &identifier, &title, &informativeText, &path_to_image, &subtitle)) return NULL;
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
|
||||
char *identifier = NULL, *title = NULL, *body = NULL;
|
||||
if (!PyArg_ParseTuple(args, "zsz", &identifier, &title, &body)) return NULL;
|
||||
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
if (!center) Py_RETURN_NONE;
|
||||
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
||||
NSUserNotification *n = [NSUserNotification new];
|
||||
NSImage *img = nil;
|
||||
if (path_to_image) {
|
||||
NSString *p = @(path_to_image);
|
||||
NSURL *url = [NSURL fileURLWithPath:p];
|
||||
img = [[NSImage alloc] initWithContentsOfURL:url];
|
||||
[url release]; [p release];
|
||||
if (img) {
|
||||
[n setValue:img forKey:@"_identityImage"];
|
||||
[n setValue:@(false) forKey:@"_identityImageHasBorder"];
|
||||
queue_notification(identifier, title, body);
|
||||
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert)
|
||||
completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
log_error("Failed to request permission for showing notification: %s", [[error localizedDescription] UTF8String]);
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
drain_pending_notifications(granted);
|
||||
});
|
||||
}
|
||||
[img release];
|
||||
}
|
||||
#define SET(x) { \
|
||||
if (x) { \
|
||||
NSString *t = @(x); \
|
||||
n.x = t; \
|
||||
[t release]; \
|
||||
}}
|
||||
SET(title); SET(subtitle); SET(informativeText);
|
||||
#undef SET
|
||||
if (identifier) {
|
||||
n.userInfo = @{@"user_id": @(identifier)};
|
||||
}
|
||||
[center deliverNotification:n];
|
||||
];
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@ -497,8 +531,11 @@ cleanup() {
|
||||
|
||||
if (dockMenu) [dockMenu release];
|
||||
dockMenu = nil;
|
||||
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
|
||||
notification_activated_callback = NULL;
|
||||
if (notification_activated_callback) Py_CLEAR(notification_activated_callback);
|
||||
drain_pending_notifications(NO);
|
||||
free(notification_queue.notifications);
|
||||
notification_queue.notifications = NULL;
|
||||
notification_queue.capacity = 0;
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
|
||||
@ -519,9 +519,7 @@ def dbus_send_notification(
|
||||
def cocoa_send_notification(
|
||||
identifier: Optional[str],
|
||||
title: str,
|
||||
informative_text: str,
|
||||
path_to_img: Optional[str],
|
||||
subtitle: Optional[str] = None
|
||||
body: Optional[str],
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ if is_macos:
|
||||
icon: bool = True,
|
||||
identifier: Optional[str] = None
|
||||
) -> None:
|
||||
cocoa_send_notification(identifier, title, body, None)
|
||||
cocoa_send_notification(identifier, title, body)
|
||||
|
||||
else:
|
||||
|
||||
|
||||
18
setup.py
18
setup.py
@ -331,20 +331,23 @@ def kitty_env() -> Env:
|
||||
cflags.extend(pkg_config('libpng', '--cflags-only-I'))
|
||||
cflags.extend(pkg_config('lcms2', '--cflags-only-I'))
|
||||
if is_macos:
|
||||
font_libs = ['-framework', 'CoreText', '-framework', 'CoreGraphics']
|
||||
platform_libs = [
|
||||
'-framework', 'CoreText', '-framework', 'CoreGraphics',
|
||||
'-framework', 'UserNotifications'
|
||||
]
|
||||
# Apple deprecated OpenGL in Mojave (10.14) silence the endless
|
||||
# warnings about it
|
||||
cppflags.append('-DGL_SILENCE_DEPRECATION')
|
||||
else:
|
||||
cflags.extend(pkg_config('fontconfig', '--cflags-only-I'))
|
||||
font_libs = pkg_config('fontconfig', '--libs')
|
||||
platform_libs = pkg_config('fontconfig', '--libs')
|
||||
cflags.extend(pkg_config('harfbuzz', '--cflags-only-I'))
|
||||
font_libs.extend(pkg_config('harfbuzz', '--libs'))
|
||||
platform_libs.extend(pkg_config('harfbuzz', '--libs'))
|
||||
pylib = get_python_flags(cflags)
|
||||
gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs')
|
||||
libpng = pkg_config('libpng', '--libs')
|
||||
lcms2 = pkg_config('lcms2', '--libs')
|
||||
ans.ldpaths += pylib + font_libs + gl_libs + libpng + lcms2
|
||||
ans.ldpaths += pylib + platform_libs + gl_libs + libpng + lcms2
|
||||
if is_macos:
|
||||
ans.ldpaths.extend('-framework Cocoa'.split())
|
||||
elif not is_openbsd:
|
||||
@ -409,10 +412,6 @@ SPECIAL_SOURCES: Dict[str, Tuple[str, Union[List[str], Callable[[Env, str], Unio
|
||||
'kitty/parser_dump.c': ('kitty/parser.c', ['DUMP_COMMANDS']),
|
||||
'kitty/data-types.c': ('kitty/data-types.c', get_vcs_rev_defines),
|
||||
}
|
||||
NO_WERROR_SOURCES = {
|
||||
# because of deprecation of Notifications API, see https://github.com/kovidgoyal/kitty/pull/2876
|
||||
'kitty/cocoa_window.m',
|
||||
}
|
||||
|
||||
|
||||
def newer(dest: str, *sources: str) -> bool:
|
||||
@ -603,9 +602,6 @@ def compile_c_extension(
|
||||
|
||||
cmd = [kenv.cc, '-MMD'] + cppflags + kenv.cflags
|
||||
cmd += ['-c', src] + ['-o', dest]
|
||||
if src in NO_WERROR_SOURCES:
|
||||
if '-Werror' in cmd:
|
||||
cmd.remove('-Werror')
|
||||
key = CompileKey(original_src, os.path.basename(dest))
|
||||
desc = 'Compiling {} ...'.format(emphasis(desc_prefix + src))
|
||||
compilation_database.add_command(desc, cmd, partial(newer, dest, *dependecies_for(src, dest, headers)), key=key, keyfile=src)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user