Get closing of notifications working on macOS

This commit is contained in:
Kovid Goyal 2024-07-25 16:35:32 +05:30
parent 3593e8d78d
commit 9024341b1f
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 50 additions and 13 deletions

View file

@ -376,7 +376,7 @@ def __init__(
self.mouse_handler: Optional[Callable[[WindowSystemMouseEvent], None]] = None
set_boss(self)
self.mappings: Mappings = Mappings(global_shortcuts, self.refresh_active_tab_bar)
self.notification_manager: NotificationManager = NotificationManager()
self.notification_manager: NotificationManager = NotificationManager(debug=self.args.debug_keyboard or self.args.debug_rendering)
def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Iterable[Session] = ()) -> None:
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)

View file

@ -429,11 +429,15 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
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);
if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) {
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);
} else if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) {
NSLog(@"Notification was dismissed: %@", response.notification);
}
}
completionHandler();
}
@ -455,6 +459,14 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
return center;
}
static bool
remove_delivered_notification(const char *identifier) {
UNUserNotificationCenter *center = get_notification_center_safely();
if (!center) return false;
[center removeDeliveredNotificationsWithIdentifiers:@[ @(identifier) ]];
return true;
}
static void
schedule_notification(const char *identifier, const char *title, const char *body, const char *subtitle, int urgency) {
UNUserNotificationCenter *center = get_notification_center_safely();
@ -532,10 +544,17 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
}
}
static PyObject*
cocoa_remove_delivered_notification(PyObject *self UNUSED, PyObject *x) {
if (!PyUnicode_Check(x)) { PyErr_SetString(PyExc_TypeError, "identifier must be a string"); return NULL; }
if (remove_delivered_notification(PyUnicode_AsUTF8(x))) { Py_RETURN_TRUE; }
Py_RETURN_FALSE;
}
static PyObject*
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
char *identifier = NULL, *title = NULL, *body = NULL, *subtitle = NULL; int urgency = 1;
if (!PyArg_ParseTuple(args, "zsz|zi", &identifier, &title, &body, &subtitle, &urgency)) return NULL;
if (!PyArg_ParseTuple(args, "ssz|zi", &identifier, &title, &body, &subtitle, &urgency)) return NULL;
UNUserNotificationCenter *center = get_notification_center_safely();
if (!center) Py_RETURN_NONE;
@ -546,7 +565,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
// otherwise macOS refuses to show the preference checkbox for enable/disable notification sound.
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (error != nil) {
if (!granted && error != nil) {
log_error("Failed to request permission for showing notification: %s", [[error localizedDescription] UTF8String]);
}
dispatch_async(dispatch_get_main_queue(), ^{
@ -1048,6 +1067,7 @@ - (BOOL)openFileURLs:(NSPasteboard*)pasteboard
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""},
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
{"cocoa_remove_delivered_notification", (PyCFunction)cocoa_remove_delivered_notification, METH_O, ""},
{"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
{"cocoa_set_url_handler", (PyCFunction)cocoa_set_url_handler, METH_VARARGS, ""},
{"cocoa_set_app_icon", (PyCFunction)cocoa_set_app_icon, METH_VARARGS, ""},

View file

@ -543,7 +543,7 @@ def os_window_has_background_image(os_window_id: int) -> bool:
pass
def dbus_set_notification_callback(c: Callable[[str, int, Union[str, int]], None]) -> None: ...
def dbus_set_notification_callback(c: Optional[Callable[[str, int, Union[str, int]], None]]) -> None: ...
def dbus_send_notification(
app_name: str,
@ -561,7 +561,7 @@ def dbus_close_notification(dbus_notification_id: int) -> bool: ...
def cocoa_send_notification(
identifier: Optional[str],
identifier: str,
title: str,
body: Optional[str],
subtitle: Optional[str],
@ -569,6 +569,7 @@ def cocoa_send_notification(
) -> None:
pass
def cocoa_remove_delivered_notification(identifier: str) -> bool: ...
def create_os_window(
get_window_size: Callable[[int, int, int, int, float, float], Tuple[int,

View file

@ -1423,7 +1423,8 @@ static PyObject *dbus_notification_callback = NULL;
static PyObject*
dbus_set_notification_callback(PyObject *self UNUSED, PyObject *callback) {
Py_CLEAR(dbus_notification_callback); dbus_notification_callback = callback; Py_INCREF(callback);
Py_CLEAR(dbus_notification_callback);
if (callback && callback != Py_None) { dbus_notification_callback = callback; Py_INCREF(callback); }
Py_RETURN_NONE;
}

View file

@ -279,9 +279,16 @@ class MacOSIntegration(DesktopIntegration):
def initialize(self) -> None:
from .fast_data_types import cocoa_set_notification_activated_callback
self.id_counter = count()
self.id_counter = count(start=1)
cocoa_set_notification_activated_callback(self.notification_activated)
def close_notification(self, desktop_notification_id: int) -> bool:
from .fast_data_types import cocoa_remove_delivered_notification
close_succeeded = cocoa_remove_delivered_notification(str(desktop_notification_id))
if debug_desktop_integration:
log_error(f'Close request for {desktop_notification_id=} {"succeeded" if close_succeeded else "failed"}')
return close_succeeded
def notify(self,
title: str,
body: str,
@ -293,10 +300,15 @@ def notify(self,
) -> int:
desktop_notification_id = next(self.id_counter)
from .fast_data_types import cocoa_send_notification
cocoa_send_notification(str(desktop_notification_id), title, body, subtitle, urgency.value)
# 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.
cocoa_send_notification(str(desktop_notification_id), title, body or ' ', subtitle, urgency.value)
return desktop_notification_id
def notification_activated(self, ident: str) -> None:
if debug_desktop_integration:
log_error(f'Notification {ident} activated')
try:
desktop_notification_id = int(ident)
except Exception:
@ -434,7 +446,10 @@ def __init__(
desktop_integration: Optional[DesktopIntegration] = None,
channel: Channel = Channel(),
log: Log = Log(),
debug: bool = False,
):
global debug_desktop_integration
debug_desktop_integration = debug
if desktop_integration is None:
self.desktop_integration = MacOSIntegration(self) if is_macos else FreeDesktopIntegration(self)
else: