mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-08-27 03:42:49 +00:00
On macOS only track nots that request close events
Send an untracked event when giving up tracking of the notification
This commit is contained in:
parent
2bffea2bdc
commit
02bc104091
6 changed files with 59 additions and 27 deletions
|
|
@ -131,13 +131,15 @@ then both the activation report and close notification are sent. If the notifica
|
|||
is updated then the close event is not sent unless the updated notification
|
||||
also requests a close notification.
|
||||
|
||||
.. note:: On macOS the OS does not supply notification
|
||||
closed events to applications. As such close events must be implemented
|
||||
via polling. It is up to the terminal emulator to decide a reasonable
|
||||
time limit for how long to poll, before giving up. kitty polls for 60
|
||||
seconds. Therefore, terminal applications should not rely on close events
|
||||
being authoritative.
|
||||
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::
|
||||
|
||||
<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.
|
||||
|
||||
Updating or closing an existing notification
|
||||
----------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
cocoa_list open_urls, closed_notifications, untracked_notifications;
|
||||
} CocoaPendingActionsData;
|
||||
static CocoaPendingActionsData cocoa_pending_actions_data = {0};
|
||||
|
||||
|
|
@ -1166,6 +1166,7 @@ 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);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -1176,6 +1177,8 @@ set_cocoa_pending_action(CocoaPendingAction action, const char *data) {
|
|||
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:
|
||||
if (cocoa_pending_actions_data.wd) free(cocoa_pending_actions_data.wd);
|
||||
cocoa_pending_actions_data.wd = strdup(data);
|
||||
|
|
@ -1226,14 +1229,26 @@ 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]);
|
||||
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);
|
||||
free(cocoa_pending_actions_data.untracked_notifications.items[cpa]);
|
||||
cocoa_pending_actions_data.untracked_notifications.items[cpa] = NULL;
|
||||
}
|
||||
}
|
||||
cocoa_pending_actions_data.untracked_notifications.count = 0;
|
||||
|
||||
|
||||
memset(cocoa_pending_actions, 0, sizeof(cocoa_pending_actions));
|
||||
has_cocoa_pending_actions = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ typedef enum {
|
|||
QUIT,
|
||||
USER_MENU_ACTION,
|
||||
COCOA_NOTIFICATION_CLOSED,
|
||||
COCOA_NOTIFICATION_UNTRACKED,
|
||||
|
||||
NUM_COCOA_PENDING_ACTIONS
|
||||
} CocoaPendingAction;
|
||||
|
|
@ -59,4 +60,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);
|
||||
void cocoa_report_closed_notification(const char* ident, bool untracked);
|
||||
|
|
|
|||
|
|
@ -500,8 +500,8 @@ poll_for_closed_notifications(void) {
|
|||
}
|
||||
|
||||
void
|
||||
cocoa_report_closed_notification(const char* ident) {
|
||||
do_notification_callback(@(ident), "closed");
|
||||
cocoa_report_closed_notification(const char* ident, bool untracked) {
|
||||
do_notification_callback(@(ident), untracked ? "untracked" : "closed");
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -514,7 +514,15 @@ dispatch_closed_notifications(void) {
|
|||
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 {
|
||||
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];
|
||||
|
|
@ -549,7 +557,7 @@ track_notification(char *ident) {
|
|||
}
|
||||
|
||||
static void
|
||||
schedule_notification(const char *identifier, const char *title, const char *body, int urgency) {
|
||||
schedule_notification(const char *identifier, const char *title, const char *body, bool track_closing, int urgency) {
|
||||
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||
if (!center) return;
|
||||
// Configure the notification's payload.
|
||||
|
|
@ -584,7 +592,7 @@ schedule_notification(const char *identifier, const char *title, const char *bod
|
|||
bool ok = error == nil;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
do_notification_callback(@(duped_ident), ok ? "created" : "closed");
|
||||
if (ok) track_notification(duped_ident);
|
||||
if (ok && track_closing) track_notification(duped_ident);
|
||||
else free(duped_ident);
|
||||
});
|
||||
}];
|
||||
|
|
@ -594,7 +602,7 @@ schedule_notification(const char *identifier, const char *title, const char *bod
|
|||
|
||||
typedef struct {
|
||||
char *identifier, *title, *body;
|
||||
int urgency;
|
||||
int urgency; bool track_closing;
|
||||
} QueuedNotification;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -604,13 +612,13 @@ typedef struct {
|
|||
static NotificationQueue notification_queue = {0};
|
||||
|
||||
static void
|
||||
queue_notification(const char *identifier, const char *title, const char* body, int urgency) {
|
||||
queue_notification(const char *identifier, const char *title, const char* body, bool track_closing, 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->urgency = urgency; n->track_closing = track_closing;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -618,7 +626,7 @@ 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->identifier, n->title, n->body, n->urgency);
|
||||
schedule_notification(n->identifier, n->title, n->body, n->track_closing, n->urgency);
|
||||
}
|
||||
}
|
||||
while(notification_queue.count) {
|
||||
|
|
@ -639,12 +647,13 @@ cocoa_remove_delivered_notification(PyObject *self UNUSED, PyObject *x) {
|
|||
static PyObject*
|
||||
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||
char *identifier = NULL, *title = NULL, *body = NULL; int urgency = 1;
|
||||
if (!PyArg_ParseTuple(args, "sss|i", &identifier, &title, &body, &urgency)) return NULL;
|
||||
int track_closing;
|
||||
if (!PyArg_ParseTuple(args, "sssp|i", &identifier, &title, &body, &track_closing, &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, urgency);
|
||||
queue_notification(identifier, title, body, track_closing, 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.
|
||||
|
|
|
|||
|
|
@ -568,6 +568,7 @@ def cocoa_send_notification(
|
|||
identifier: str,
|
||||
title: str,
|
||||
body: str,
|
||||
track_closing: bool,
|
||||
urgency: int = 1,
|
||||
) -> None:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ class NotificationCommand:
|
|||
|
||||
# event callbacks
|
||||
on_activation: Optional[Callable[['NotificationCommand'], None]] = None
|
||||
on_close: Optional[Callable[['NotificationCommand'], None]] = None
|
||||
on_close: Optional[Callable[['NotificationCommand', bool], None]] = None
|
||||
on_update: Optional[Callable[['NotificationCommand', 'NotificationCommand'], None]] = None
|
||||
|
||||
# metadata
|
||||
|
|
@ -378,6 +378,7 @@ class NotificationCommand:
|
|||
self.title = sanitize_text(self.body)
|
||||
self.body = ''
|
||||
self.urgency = Urgency.Normal if self.urgency is None else self.urgency
|
||||
self.close_response_requested = bool(self.close_response_requested)
|
||||
|
||||
def matches_rule_item(self, location:str, query:str) -> bool:
|
||||
import re
|
||||
|
|
@ -454,7 +455,7 @@ class MacOSIntegration(DesktopIntegration):
|
|||
# for %% escaping.
|
||||
body = (nc.body or ' ')
|
||||
assert nc.urgency is not None
|
||||
cocoa_send_notification(str(desktop_notification_id), nc.title, body, nc.urgency.value)
|
||||
cocoa_send_notification(str(desktop_notification_id), nc.title, body, bool(nc.close_response_requested), nc.urgency.value)
|
||||
return desktop_notification_id
|
||||
|
||||
def notification_activated(self, event: str, ident: str) -> None:
|
||||
|
|
@ -471,6 +472,8 @@ class MacOSIntegration(DesktopIntegration):
|
|||
self.notification_manager.notification_activated(desktop_notification_id)
|
||||
elif event == "closed":
|
||||
self.notification_manager.notification_closed(desktop_notification_id)
|
||||
elif event == "untracked":
|
||||
self.notification_manager.notification_closed(desktop_notification_id, True)
|
||||
|
||||
|
||||
class FreeDesktopIntegration(DesktopIntegration):
|
||||
|
|
@ -669,14 +672,14 @@ class NotificationManager:
|
|||
except Exception as e:
|
||||
self.log('Notification on_update handler failed with error:', e)
|
||||
|
||||
def notification_closed(self, desktop_notification_id: int) -> None:
|
||||
def notification_closed(self, desktop_notification_id: int, untracked: bool = False) -> 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)
|
||||
self.send_closed_response(n.channel_id, n.identifier, untracked)
|
||||
if n.on_close is not None:
|
||||
try:
|
||||
n.on_close(n)
|
||||
n.on_close(n, untracked)
|
||||
except Exception as e:
|
||||
self.log('Notification on_close handler failed with error:', e)
|
||||
|
||||
|
|
@ -778,8 +781,9 @@ class NotificationManager:
|
|||
cmd.set_payload(payload_type, payload_is_encoded, payload, prev_cmd)
|
||||
return cmd
|
||||
|
||||
def send_closed_response(self, channel_id: int, client_id: str) -> None:
|
||||
self.channel.send(channel_id, f'99;i={client_id}:p=close;')
|
||||
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}')
|
||||
|
||||
def purge_notification(self, cmd: NotificationCommand) -> None:
|
||||
self.in_progress_notification_commands_by_client_id.pop(cmd.identifier, None)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue