diff --git a/docs/desktop-notifications.rst b/docs/desktop-notifications.rst index a157499e5..9b20dacc2 100644 --- a/docs/desktop-notifications.rst +++ b/docs/desktop-notifications.rst @@ -110,18 +110,16 @@ notification when closing succeeds, send the following instead:: Then, the terminal will respond with:: - i= : p=close ; + i= : p=close ; # notification was closed + or + i= : p=close ; ENOENT ; # notification did not exist -This escape code is sent by the terminal if the notification is closed -or was already closed when the close request arrives, or a notification -with the specified identifier does not exist. If the notification is activated, -before it can be closed, then the close response is sent only if there is no -activation response. In other words, if you close a response and request -notification, you will get either of the following two responses:: - - i= : p=close ; # closed - - i= ; # activated +This escape code is sent by the terminal if the notification is closed or a +notification with the specified identifier does not exist. If the notification +is activated or closed before the close request is received, then the notification does +not exist as far as the terminal is concerned, and an ``ENOENT`` response is +sent. To detect activation, before close, enable reporting with ``a=report`` +when creating the notification. Querying for support diff --git a/kitty/notifications.py b/kitty/notifications.py index dd5d36b8b..377c49d0b 100644 --- a/kitty/notifications.py +++ b/kitty/notifications.py @@ -521,7 +521,7 @@ class NotificationManager: self.send_closed_response(to_close.channel_id, to_close.identifier) self.purge_notification(to_close) else: - self.send_closed_response(channel_id, cmd.identifier) + self.send_closed_response(channel_id, cmd.identifier, not_found=True) return None if payload_type is PayloadType.unknown: @@ -531,8 +531,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, not_found: bool = False) -> None: + trailer = 'ENOENT;' if not_found else '' + self.channel.send(channel_id, f'99;i={client_id}:p=close;{trailer}') def purge_notification(self, cmd: NotificationCommand) -> None: self.in_progress_notification_commands_by_client_id.pop(cmd.identifier, None) diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 7d48505f4..28a456a19 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -584,7 +584,7 @@ class TestDataTypes(BaseTest): self.assertNotEqual(SingleKey(key=1, mods=2), SingleKey(key=1)) def test_notify_identifier_sanitization(self): - from kitty.notify import sanitize_identifier_pat + from kitty.notifications import sanitize_identifier_pat self.ae(sanitize_identifier_pat().sub('', '\x1b\nabc\n[*'), 'abc') def test_bracketed_paste_sanitizer(self): diff --git a/kitty_tests/notifications.py b/kitty_tests/notifications.py index 2b4784f8f..10742cad2 100644 --- a/kitty_tests/notifications.py +++ b/kitty_tests/notifications.py @@ -93,7 +93,7 @@ def do_test(self: 'TestNotifications') -> None: n = di.notifications[which] di.close_notification(n['id']) - def assert_events(focus=True, close=0, report='', close_response=''): + def assert_events(focus=True, close=0, report='', close_response='', enoent=False): self.ae(ch.focus_events, [''] if focus else []) if report: self.assertIn(f'99;i={report};', ch.responses) @@ -102,7 +102,8 @@ def do_test(self: 'TestNotifications') -> None: m = re.match(r'99;i=[a-z0-9]+;', r) self.assertIsNone(m, f'Unexpectedly found report response: {r}') if close_response: - self.assertIn(f'99;i={close_response}:p=close;', ch.responses) + t = 'ENOENT;' if enoent else '' + self.assertIn(f'99;i={close_response}:p=close;{t}', ch.responses) else: for r in ch.responses: m = re.match(r'99;i=[a-z0-9]+:p=close;', r) @@ -172,8 +173,21 @@ def do_test(self: 'TestNotifications') -> None: reset() h('i=c;title') h('i=c:p=close;notify') + self.ae(di.notifications, [n()]) assert_events(focus=False, close=True, close_response='c') reset() + h('i=c;title') + activate() + h('i=c:p=close;notify') + self.ae(di.notifications, [n()]) + assert_events(focus=True, close_response='c', enoent=True) + reset() + h('i=c:a=report;title') + activate() + h('i=c:p=close;notify') + self.ae(di.notifications, [n()]) + assert_events(focus=True, report='c', close_response='c', enoent=True) + reset() h(';title') self.ae(di.notifications, [n()])