Implement a polling mode for close events
This commit is contained in:
parent
02bc104091
commit
083f158bbd
7 changed files with 115 additions and 133 deletions
|
|
@ -132,14 +132,22 @@ is updated then the close event is not sent unless the updated notification
|
|||
also requests a close notification.
|
||||
|
||||
Note that on some platforms, such as macOS, the OS does not inform applications
|
||||
when notifications are closed, on such platforms, terminals may reply with::
|
||||
when notifications are closed, on such platforms, terminals reply with::
|
||||
|
||||
<OSC> 99 ; i=mynotification : p=close ; untracked <terminator>
|
||||
|
||||
This means that the terminal has no way of knowing when the notification is
|
||||
closed. |kitty|, on macOS, manually tracks notifications by polling the OS
|
||||
for a short period to see if they are closed, after which it gives up
|
||||
and replies with an ``untracked`` response.
|
||||
closed. Instead, applications can poll the terminal to determine which
|
||||
notifications are still alive (not closed), with::
|
||||
|
||||
<OSC> 99 ; i=myid : p=alive ; <terminator>
|
||||
|
||||
The terminal will reply with::
|
||||
<OSC> 99 ; i=myid : p=alive ; id1,id2,id3 <terminator>
|
||||
|
||||
Here, ``myid`` is present for multiplxer support. The reponse from the terminal
|
||||
contains a comma separated list of ids that are still alive.
|
||||
|
||||
|
||||
Updating or closing an existing notification
|
||||
----------------------------------------------
|
||||
|
|
@ -244,7 +252,7 @@ Key Value Default Description
|
|||
``p`` One of ``title``, ``title`` Whether the payload is the notification title or body or query. If a
|
||||
``body``, notification has no title, the body will be used as title. Terminal
|
||||
``close``, emulators should ignore payloads of unknown type to allow for future
|
||||
``?`` expansion of this protocol.
|
||||
``?``, ``alive`` expansion of this protocol.
|
||||
|
||||
|
||||
``o`` One of ``always``, ``always`` When to honor the notification request. ``unfocused`` means when the window
|
||||
|
|
|
|||
|
|
@ -1145,7 +1145,7 @@ static bool has_cocoa_pending_actions = false;
|
|||
typedef struct cocoa_list { char **items; size_t count, capacity; } cocoa_list;
|
||||
typedef struct {
|
||||
char* wd;
|
||||
cocoa_list open_urls, closed_notifications, untracked_notifications;
|
||||
cocoa_list open_urls, untracked_notifications;
|
||||
} CocoaPendingActionsData;
|
||||
static CocoaPendingActionsData cocoa_pending_actions_data = {0};
|
||||
|
||||
|
|
@ -1165,7 +1165,6 @@ static void
|
|||
cocoa_free_actions_data(void) {
|
||||
if (cocoa_pending_actions_data.wd) { free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = NULL; }
|
||||
cocoa_free_pending_list(&cocoa_pending_actions_data.open_urls);
|
||||
cocoa_free_pending_list(&cocoa_pending_actions_data.closed_notifications);
|
||||
cocoa_free_pending_list(&cocoa_pending_actions_data.untracked_notifications);
|
||||
}
|
||||
|
||||
|
|
@ -1175,8 +1174,6 @@ set_cocoa_pending_action(CocoaPendingAction action, const char *data) {
|
|||
switch(action) {
|
||||
case LAUNCH_URLS:
|
||||
cocoa_append_to_pending_list(&cocoa_pending_actions_data.open_urls, data); break;
|
||||
case COCOA_NOTIFICATION_CLOSED:
|
||||
cocoa_append_to_pending_list(&cocoa_pending_actions_data.closed_notifications, data); break;
|
||||
case COCOA_NOTIFICATION_UNTRACKED:
|
||||
cocoa_append_to_pending_list(&cocoa_pending_actions_data.untracked_notifications, data); break;
|
||||
default:
|
||||
|
|
@ -1230,18 +1227,9 @@ process_cocoa_pending_actions(void) {
|
|||
}
|
||||
cocoa_pending_actions_data.open_urls.count = 0;
|
||||
|
||||
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.closed_notifications.count; cpa++) {
|
||||
if (cocoa_pending_actions_data.closed_notifications.items[cpa]) {
|
||||
cocoa_report_closed_notification(cocoa_pending_actions_data.closed_notifications.items[cpa], false);
|
||||
free(cocoa_pending_actions_data.closed_notifications.items[cpa]);
|
||||
cocoa_pending_actions_data.closed_notifications.items[cpa] = NULL;
|
||||
}
|
||||
}
|
||||
cocoa_pending_actions_data.closed_notifications.count = 0;
|
||||
|
||||
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.untracked_notifications.count; cpa++) {
|
||||
if (cocoa_pending_actions_data.untracked_notifications.items[cpa]) {
|
||||
cocoa_report_closed_notification(cocoa_pending_actions_data.untracked_notifications.items[cpa], true);
|
||||
cocoa_report_live_notifications(cocoa_pending_actions_data.untracked_notifications.items[cpa]);
|
||||
free(cocoa_pending_actions_data.untracked_notifications.items[cpa]);
|
||||
cocoa_pending_actions_data.untracked_notifications.items[cpa] = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ typedef enum {
|
|||
MINIMIZE,
|
||||
QUIT,
|
||||
USER_MENU_ACTION,
|
||||
COCOA_NOTIFICATION_CLOSED,
|
||||
COCOA_NOTIFICATION_UNTRACKED,
|
||||
|
||||
NUM_COCOA_PENDING_ACTIONS
|
||||
|
|
@ -60,4 +59,4 @@ bool cocoa_render_line_of_text(const char *text, const color_type fg, const colo
|
|||
extern uint8_t* render_single_ascii_char_as_mask(const char ch, size_t *result_width, size_t *result_height);
|
||||
void get_cocoa_key_equivalent(uint32_t, int, char *key, size_t key_sz, int*);
|
||||
void set_cocoa_pending_action(CocoaPendingAction action, const char*);
|
||||
void cocoa_report_closed_notification(const char* ident, bool untracked);
|
||||
void cocoa_report_live_notifications(const char* ident);
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|||
do_notification_callback([[[response notification] request] identifier], "activated");
|
||||
} else if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) {
|
||||
// this never actually happens on macOS. Bloody Crapple.
|
||||
do_notification_callback([[[response notification] request] identifier], "closed");
|
||||
// do_notification_callback([[[response notification] request] identifier], "closed");
|
||||
}
|
||||
completionHandler();
|
||||
}
|
||||
|
|
@ -457,23 +457,6 @@ - (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 NSLock *notifications_polling_lock = NULL;
|
||||
static bool polling_notifications = false;
|
||||
typedef struct tn { char *ident; bool closed; monotonic_t creation_time; } tn;
|
||||
static struct { tn *items; size_t count, capacity; monotonic_t creation_time; } tracked_notifications;
|
||||
static void dispatch_closed_notifications(void);
|
||||
#define CLOSE_POLL_TIME 60.
|
||||
#define CLOSE_POLL_INTERVAL 750
|
||||
|
||||
static bool
|
||||
ident_in_list_of_notifications(NSString *ident, NSArray<UNNotification*> *list) {
|
||||
for (UNNotification *n in list) {
|
||||
|
|
@ -482,82 +465,40 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
poll_for_closed_notifications(void) {
|
||||
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||
if (!center) return;
|
||||
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * notifications) {
|
||||
// NSLog(@"num of delivered but not closed nots: %lu", (unsigned long)[notifications count]);
|
||||
[notifications_polling_lock lock];
|
||||
for (size_t i = 0; i < tracked_notifications.count; i++) {
|
||||
if (!ident_in_list_of_notifications(@(tracked_notifications.items[i].ident), notifications)) tracked_notifications.items[i].closed = true;
|
||||
}
|
||||
[notifications_polling_lock unlock];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
dispatch_closed_notifications();
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_report_closed_notification(const char* ident, bool untracked) {
|
||||
do_notification_callback(@(ident), untracked ? "untracked" : "closed");
|
||||
cocoa_report_live_notifications(const char* ident) {
|
||||
do_notification_callback(@(ident), "live");
|
||||
}
|
||||
|
||||
static void
|
||||
dispatch_closed_notifications(void) {
|
||||
bool poll = false;
|
||||
[notifications_polling_lock lock];
|
||||
monotonic_t now = monotonic();
|
||||
for (size_t i = tracked_notifications.count; i-- > 0; ) {
|
||||
if (tracked_notifications.items[i].closed) {
|
||||
set_cocoa_pending_action(COCOA_NOTIFICATION_CLOSED, tracked_notifications.items[i].ident);
|
||||
free(tracked_notifications.items[i].ident);
|
||||
remove_i_from_array(tracked_notifications.items, i, tracked_notifications.count);
|
||||
} else {
|
||||
if (now - tracked_notifications.items[i].creation_time < s_double_to_monotonic_t(CLOSE_POLL_TIME)) {
|
||||
poll = true;
|
||||
} else {
|
||||
set_cocoa_pending_action(COCOA_NOTIFICATION_UNTRACKED, tracked_notifications.items[i].ident);
|
||||
free(tracked_notifications.items[i].ident);
|
||||
remove_i_from_array(tracked_notifications.items, i, tracked_notifications.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
polling_notifications = poll;
|
||||
[notifications_polling_lock unlock];
|
||||
if (poll) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, CLOSE_POLL_INTERVAL * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
||||
poll_for_closed_notifications();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
track_notification(char *ident) {
|
||||
static bool
|
||||
remove_delivered_notification(const char *identifier) {
|
||||
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||
if (center) {
|
||||
[notifications_polling_lock lock];
|
||||
bool has_existing = false;
|
||||
for (size_t i = 0; i < tracked_notifications.count; i++) {
|
||||
if (strcmp(tracked_notifications.items[i].ident, ident) == 0) {
|
||||
tracked_notifications.items[i].creation_time = monotonic();
|
||||
has_existing = true;
|
||||
break;
|
||||
}
|
||||
if (!center) return false;
|
||||
char *ident = strdup(identifier);
|
||||
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * notifications) {
|
||||
if (ident_in_list_of_notifications(@(ident), notifications)) {
|
||||
[center removeDeliveredNotificationsWithIdentifiers:@[ @(ident) ]];
|
||||
}
|
||||
if (!has_existing) {
|
||||
ensure_space_for(&tracked_notifications, items, tn, tracked_notifications.count + 1, capacity, 8, false);
|
||||
tracked_notifications.items[tracked_notifications.count++] = (tn){.ident=ident, .creation_time=monotonic()};
|
||||
}
|
||||
bool needs_poll = !polling_notifications;
|
||||
[notifications_polling_lock unlock];
|
||||
if (needs_poll) poll_for_closed_notifications();
|
||||
}
|
||||
free(ident);
|
||||
}];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
live_delivered_notifications(void) {
|
||||
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||
if (!center) return false;
|
||||
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * notifications) {
|
||||
NSMutableString *buffer = [[NSMutableString stringWithCapacity:1024] autorelease];
|
||||
for (UNNotification *n in notifications) [buffer appendFormat:@"%@,", [[n request] identifier]];
|
||||
const char *val = [buffer UTF8String];
|
||||
set_cocoa_pending_action(COCOA_NOTIFICATION_UNTRACKED, val ? val : "");
|
||||
}];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
schedule_notification(const char *identifier, const char *title, const char *body, bool track_closing, int urgency) {
|
||||
schedule_notification(const char *identifier, const char *title, const char *body, int urgency) {
|
||||
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||
if (!center) return;
|
||||
// Configure the notification's payload.
|
||||
|
|
@ -591,9 +532,8 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|||
if (error != nil) log_error("Failed to show notification: %s", [[error localizedDescription] UTF8String]);
|
||||
bool ok = error == nil;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
do_notification_callback(@(duped_ident), ok ? "created" : "closed");
|
||||
if (ok && track_closing) track_notification(duped_ident);
|
||||
else free(duped_ident);
|
||||
do_notification_callback(@(duped_ident), ok ? "created" : "creation_failed");
|
||||
free(duped_ident);
|
||||
});
|
||||
}];
|
||||
[content release];
|
||||
|
|
@ -602,7 +542,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|||
|
||||
typedef struct {
|
||||
char *identifier, *title, *body;
|
||||
int urgency; bool track_closing;
|
||||
int urgency;
|
||||
} QueuedNotification;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -612,13 +552,13 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|||
static NotificationQueue notification_queue = {0};
|
||||
|
||||
static void
|
||||
queue_notification(const char *identifier, const char *title, const char* body, bool track_closing, int urgency) {
|
||||
queue_notification(const char *identifier, const char *title, const char* body, int urgency) {
|
||||
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;
|
||||
n->urgency = urgency; n->track_closing = track_closing;
|
||||
n->urgency = urgency;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -626,12 +566,12 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|||
if (granted) {
|
||||
for (size_t i = 0; i < notification_queue.count; i++) {
|
||||
QueuedNotification *n = notification_queue.notifications + i;
|
||||
schedule_notification(n->identifier, n->title, n->body, n->track_closing, n->urgency);
|
||||
schedule_notification(n->identifier, n->title, n->body, n->urgency);
|
||||
}
|
||||
}
|
||||
while(notification_queue.count) {
|
||||
QueuedNotification *n = notification_queue.notifications + --notification_queue.count;
|
||||
if (!granted) do_notification_callback(@(n->identifier), "closed");
|
||||
if (!granted) do_notification_callback(@(n->identifier), "creation_failed");
|
||||
free(n->identifier); free(n->title); free(n->body);
|
||||
memset(n, 0, sizeof(QueuedNotification));
|
||||
}
|
||||
|
|
@ -644,16 +584,23 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cocoa_live_delivered_notifications(PyObject *self UNUSED, PyObject *x UNUSED) {
|
||||
if (live_delivered_notifications()) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static PyObject*
|
||||
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||
char *identifier = NULL, *title = NULL, *body = NULL; int urgency = 1;
|
||||
int track_closing;
|
||||
if (!PyArg_ParseTuple(args, "sssp|i", &identifier, &title, &body, &track_closing, &urgency)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "sss|i", &identifier, &title, &body, &urgency)) return NULL;
|
||||
|
||||
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||
if (!center) Py_RETURN_NONE;
|
||||
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
||||
queue_notification(identifier, title, body, track_closing, urgency);
|
||||
queue_notification(identifier, title, body, 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.
|
||||
|
|
@ -1123,8 +1070,6 @@ - (BOOL)openFileURLs:(NSPasteboard*)pasteboard
|
|||
dockMenu = nil;
|
||||
if (beep_sound) [beep_sound release];
|
||||
beep_sound = nil;
|
||||
if (notifications_polling_lock) [notifications_polling_lock release];
|
||||
notifications_polling_lock = nil;
|
||||
|
||||
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
|
||||
drain_pending_notifications(NO);
|
||||
|
|
@ -1164,6 +1109,7 @@ - (BOOL)openFileURLs:(NSPasteboard*)pasteboard
|
|||
{"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_live_delivered_notifications", (PyCFunction)cocoa_live_delivered_notifications, METH_NOARGS, ""},
|
||||
{"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, ""},
|
||||
|
|
@ -1175,7 +1121,6 @@ - (BOOL)openFileURLs:(NSPasteboard*)pasteboard
|
|||
init_cocoa(PyObject *module) {
|
||||
cocoa_clear_global_shortcuts();
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
notifications_polling_lock = [NSLock new];
|
||||
register_at_exit_cleanup_func(COCOA_CLEANUP_FUNC, cleanup);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -568,12 +568,12 @@ def cocoa_send_notification(
|
|||
identifier: str,
|
||||
title: str,
|
||||
body: str,
|
||||
track_closing: bool,
|
||||
urgency: int = 1,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def cocoa_remove_delivered_notification(identifier: str) -> bool: ...
|
||||
def cocoa_live_delivered_notifications() -> bool: ...
|
||||
|
||||
def create_os_window(
|
||||
get_window_size: Callable[[int, int, int, int, float, float], Tuple[int,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
from contextlib import suppress
|
||||
from enum import Enum
|
||||
from itertools import count
|
||||
from typing import Any, Callable, Dict, FrozenSet, Iterator, List, NamedTuple, Optional, Set, Tuple, Union
|
||||
from typing import Any, Callable, Dict, FrozenSet, Iterator, List, NamedTuple, Optional, Sequence, Set, Tuple, Union
|
||||
from weakref import ReferenceType, ref
|
||||
|
||||
from .constants import cache_dir, config_dir, is_macos, logo_png_file
|
||||
|
|
@ -111,6 +111,7 @@ class PayloadType(Enum):
|
|||
query = '?'
|
||||
close = 'close'
|
||||
icon = 'icon'
|
||||
alive = 'alive'
|
||||
|
||||
@property
|
||||
def is_text(self) -> bool:
|
||||
|
|
@ -203,7 +204,7 @@ class NotificationCommand:
|
|||
|
||||
# event callbacks
|
||||
on_activation: Optional[Callable[['NotificationCommand'], None]] = None
|
||||
on_close: Optional[Callable[['NotificationCommand', bool], None]] = None
|
||||
on_close: Optional[Callable[['NotificationCommand'], None]] = None
|
||||
on_update: Optional[Callable[['NotificationCommand', 'NotificationCommand'], None]] = None
|
||||
|
||||
# metadata
|
||||
|
|
@ -410,6 +411,9 @@ def __init__(self, notification_manager: 'NotificationManager'):
|
|||
def initialize(self) -> None:
|
||||
pass
|
||||
|
||||
def query_live_notifications(self, channel_id: int, identifier: str) -> None:
|
||||
raise NotImplementedError('Implement me in subclass')
|
||||
|
||||
def close_notification(self, desktop_notification_id: int) -> bool:
|
||||
raise NotImplementedError('Implement me in subclass')
|
||||
|
||||
|
|
@ -432,11 +436,21 @@ def query_response(self, identifier: str) -> str:
|
|||
|
||||
class MacOSIntegration(DesktopIntegration):
|
||||
|
||||
supports_close_events: bool = False
|
||||
|
||||
def initialize(self) -> None:
|
||||
from .fast_data_types import cocoa_set_notification_activated_callback
|
||||
self.id_counter = count(start=1)
|
||||
self.live_notification_queries: List[Tuple[int, str]] = []
|
||||
cocoa_set_notification_activated_callback(self.notification_activated)
|
||||
|
||||
def query_live_notifications(self, channel_id: int, identifier: str) -> None:
|
||||
from .fast_data_types import cocoa_live_delivered_notifications
|
||||
if not cocoa_live_delivered_notifications():
|
||||
self.notification_manager.send_live_response(channel_id, identifier, ())
|
||||
else:
|
||||
self.live_notification_queries.append((channel_id, identifier))
|
||||
|
||||
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))
|
||||
|
|
@ -455,12 +469,19 @@ def notify(self, nc: NotificationCommand, existing_desktop_notification_id: Opti
|
|||
# for %% escaping.
|
||||
body = (nc.body or ' ')
|
||||
assert nc.urgency is not None
|
||||
cocoa_send_notification(str(desktop_notification_id), nc.title, body, bool(nc.close_response_requested), nc.urgency.value)
|
||||
cocoa_send_notification(str(desktop_notification_id), nc.title, body, nc.urgency.value)
|
||||
return desktop_notification_id
|
||||
|
||||
def notification_activated(self, event: str, ident: str) -> None:
|
||||
if debug_desktop_integration:
|
||||
log_error(f'Notification {ident} {event=}')
|
||||
if event == 'live':
|
||||
live_ids = tuple(int(x) for x in ident.split(',') if x)
|
||||
self.notification_manager.purge_dead_notifications(live_ids)
|
||||
self.live_notification_queries, queries = [], self.live_notification_queries
|
||||
for channel_id, req_id in queries:
|
||||
self.notification_manager.send_live_response(channel_id, req_id, live_ids)
|
||||
return
|
||||
try:
|
||||
desktop_notification_id = int(ident)
|
||||
except Exception:
|
||||
|
|
@ -468,12 +489,12 @@ def notification_activated(self, event: str, ident: str) -> None:
|
|||
return
|
||||
if event == "created":
|
||||
self.notification_manager.notification_created(desktop_notification_id)
|
||||
from .fast_data_types import cocoa_live_delivered_notifications
|
||||
cocoa_live_delivered_notifications() # so that we purge dead notifications
|
||||
elif event == "activated":
|
||||
self.notification_manager.notification_activated(desktop_notification_id)
|
||||
elif event == "closed":
|
||||
elif event == "creation_failed":
|
||||
self.notification_manager.notification_closed(desktop_notification_id)
|
||||
elif event == "untracked":
|
||||
self.notification_manager.notification_closed(desktop_notification_id, True)
|
||||
|
||||
|
||||
class FreeDesktopIntegration(DesktopIntegration):
|
||||
|
|
@ -486,6 +507,9 @@ def initialize(self) -> None:
|
|||
self.dbus_to_desktop: 'OrderedDict[int, int]' = OrderedDict()
|
||||
self.desktop_to_dbus: Dict[int, int] = {}
|
||||
|
||||
def query_live_notifications(self, channel_id: int, identifier: str) -> None:
|
||||
self.notification_manager.send_live_response(channel_id, identifier, tuple(self.desktop_to_dbus))
|
||||
|
||||
def close_notification(self, desktop_notification_id: int) -> bool:
|
||||
from .fast_data_types import dbus_close_notification
|
||||
close_succeeded = False
|
||||
|
|
@ -672,14 +696,14 @@ def notification_replaced(self, old_cmd: NotificationCommand, new_cmd: Notificat
|
|||
except Exception as e:
|
||||
self.log('Notification on_update handler failed with error:', e)
|
||||
|
||||
def notification_closed(self, desktop_notification_id: int, untracked: bool = False) -> None:
|
||||
def notification_closed(self, desktop_notification_id: int) -> None:
|
||||
if n := self.in_progress_notification_commands.get(desktop_notification_id):
|
||||
self.purge_notification(n)
|
||||
if n.close_response_requested:
|
||||
self.send_closed_response(n.channel_id, n.identifier, untracked)
|
||||
self.send_closed_response(n.channel_id, n.identifier)
|
||||
if n.on_close is not None:
|
||||
try:
|
||||
n.on_close(n, untracked)
|
||||
n.on_close(n)
|
||||
except Exception as e:
|
||||
self.log('Notification on_close handler failed with error:', e)
|
||||
|
||||
|
|
@ -740,6 +764,8 @@ def notify_with_command(self, cmd: NotificationCommand, channel_id: int) -> Opti
|
|||
self.register_in_progress_notification(cmd, desktop_notification_id)
|
||||
if existing_cmd:
|
||||
self.notification_replaced(existing_cmd, cmd)
|
||||
if not self.desktop_integration.supports_close_events and cmd.close_response_requested:
|
||||
self.send_closed_response(channel_id, cmd.identifier, untracked=True)
|
||||
return desktop_notification_id
|
||||
|
||||
def register_in_progress_notification(self, cmd: NotificationCommand, desktop_notification_id: int) -> None:
|
||||
|
|
@ -764,6 +790,10 @@ def parse_notification_cmd(
|
|||
if payload_type is PayloadType.query:
|
||||
self.channel.send(channel_id, self.desktop_integration.query_response(cmd.identifier))
|
||||
return None
|
||||
if payload_type is PayloadType.alive:
|
||||
if cmd.identifier:
|
||||
self.desktop_integration.query_live_notifications(channel_id, cmd.identifier)
|
||||
return None
|
||||
if payload_type is PayloadType.close:
|
||||
if cmd.identifier:
|
||||
to_close = self.in_progress_notification_commands_by_client_id.get(cmd.identifier)
|
||||
|
|
@ -783,7 +813,19 @@ def parse_notification_cmd(
|
|||
|
||||
def send_closed_response(self, channel_id: int, client_id: str, untracked: bool = False) -> None:
|
||||
payload = 'untracked' if untracked else ''
|
||||
self.channel.send(channel_id, f'99;i={client_id}:p=close;{payload}')
|
||||
self.channel.send(channel_id, f'99;i={client_id}:p={PayloadType.close.value};{payload}')
|
||||
|
||||
def send_live_response(self, channel_id: int, client_id: str, live_desktop_ids: Sequence[int]) -> None:
|
||||
ids = []
|
||||
for desktop_notification_id in live_desktop_ids:
|
||||
if n := self.in_progress_notification_commands.get(desktop_notification_id):
|
||||
if n.identifier and n.channel_id == channel_id:
|
||||
ids.append(n.identifier)
|
||||
self.channel.send(channel_id, f'99;i={client_id}:p={PayloadType.alive.value};{",".join(ids)}')
|
||||
|
||||
def purge_dead_notifications(self, live_desktop_ids: Sequence[int]) -> None:
|
||||
for d in set(self.in_progress_notification_commands) - set(live_desktop_ids):
|
||||
self.purge_notification(self.in_progress_notification_commands[d])
|
||||
|
||||
def purge_notification(self, cmd: NotificationCommand) -> None:
|
||||
self.in_progress_notification_commands_by_client_id.pop(cmd.identifier, None)
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ def assert_events(focus=True, close=0, report='', close_response=''):
|
|||
# Test querying
|
||||
h('i=xyz:p=?')
|
||||
self.assertFalse(di.notifications)
|
||||
qr = 'a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close,icon:c=1'
|
||||
qr = 'a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close,icon,alive:c=1'
|
||||
self.ae(ch.responses, [f'99;i=xyz:p=?;{qr}'])
|
||||
reset()
|
||||
h('p=?')
|
||||
|
|
|
|||
Loading…
Reference in a new issue