Report if close events are supported in the query response

This commit is contained in:
Kovid Goyal
2024-07-25 18:59:31 +05:30
parent 9c772915fb
commit 0813a3daff
3 changed files with 32 additions and 8 deletions

View File

@@ -99,6 +99,9 @@ to display it based on what it does understand.
Being informed when a notification is closed Being informed when a notification is closed
------------------------------------------------ ------------------------------------------------
.. versionadded:: 0.36.0
Notifications of close events were added in kitty version 0.36.0
If you wish to be informed when a notification is closed, you can specify If you wish to be informed when a notification is closed, you can specify
``c=1`` when sending the notification. For example:: ``c=1`` when sending the notification. For example::
@@ -113,6 +116,12 @@ If no notification id was specified ``i=0`` will be used.
If ``a=report`` is specified and the notification is activated/clicked on If ``a=report`` is specified and the notification is activated/clicked on
then both the activation report and close notification are sent. then both the activation report and close notification are sent.
.. note::
Close events are best effort, some platforms such as macOS do not have
events when notifications are closed. Applications can use the
:ref:`notifications_query` to check if close events are supported
by the current terminal emulator.
Closing an existing notification Closing an existing notification
---------------------------------- ----------------------------------
@@ -128,6 +137,8 @@ This will close a previous notification with the specified id. If no such
notification exists (perhaps because it was already closed or it was activated) notification exists (perhaps because it was already closed or it was activated)
then the request is ignored. then the request is ignored.
.. _notifications_query:
Querying for support Querying for support
------------------------- -------------------------
@@ -167,6 +178,9 @@ Key Value
``p`` Comma spearated list of supported payload types (i.e. values of the ``p`` Comma spearated list of supported payload types (i.e. values of the
``p`` key that the terminal implements). These must contain at least ``p`` key that the terminal implements). These must contain at least
``title`` and ``body``. ``title`` and ``body``.
``c`` ``c=1`` if the terminal supports close events, otherwise the ``c``
must be omitted.
======= ================================================================================ ======= ================================================================================
In the future, if this protocol expands, more keys might be added. Clients must In the future, if this protocol expands, more keys might be added. Clients must

View File

@@ -249,6 +249,8 @@ class NotificationCommand:
class DesktopIntegration: class DesktopIntegration:
supports_close_events: bool = True
def __init__(self, notification_manager: 'NotificationManager'): def __init__(self, notification_manager: 'NotificationManager'):
self.notification_manager = notification_manager self.notification_manager = notification_manager
self.initialize() self.initialize()
@@ -274,9 +276,20 @@ class DesktopIntegration:
from .update_check import notification_activated from .update_check import notification_activated
notification_activated() notification_activated()
def query_response(self, identifier: str) -> str:
actions = ','.join(x.value for x in Action)
when = ','.join(x.value for x in OnlyWhen if x.value)
urgency = ','.join(str(x.value) for x in Urgency)
i = f'i={identifier or "0"}:'
p = ','.join(x.value for x in PayloadType if x.value)
c = ':c=1' if self.supports_close_events else ''
return f'99;{i}p=?;a={actions}:o={when}:u={urgency}:p={p}{c}'
class MacOSIntegration(DesktopIntegration): class MacOSIntegration(DesktopIntegration):
supports_close_events: bool = False
def initialize(self) -> None: def initialize(self) -> None:
from .fast_data_types import cocoa_set_notification_activated_callback from .fast_data_types import cocoa_set_notification_activated_callback
self.id_counter = count(start=1) self.id_counter = count(start=1)
@@ -543,7 +556,9 @@ class NotificationManager:
_, cmd = self.in_progress_notification_commands.popitem(False) _, cmd = self.in_progress_notification_commands.popitem(False)
self.in_progress_notification_commands_by_client_id.pop(cmd.identifier, None) self.in_progress_notification_commands_by_client_id.pop(cmd.identifier, None)
def parse_notification_cmd(self, prev_cmd: NotificationCommand, channel_id: int, raw: str) -> Optional[NotificationCommand]: def parse_notification_cmd(
self, prev_cmd: NotificationCommand, channel_id: int, raw: str
) -> Optional[NotificationCommand]:
metadata, payload = raw.partition(';')[::2] metadata, payload = raw.partition(';')[::2]
cmd = NotificationCommand() cmd = NotificationCommand()
try: try:
@@ -552,12 +567,7 @@ class NotificationManager:
self.log('Malformed metadata section in OSC 99: ' + metadata) self.log('Malformed metadata section in OSC 99: ' + metadata)
return None return None
if payload_type is PayloadType.query: if payload_type is PayloadType.query:
actions = ','.join(x.value for x in Action) self.channel.send(channel_id, self.desktop_integration.query_response(cmd.identifier))
when = ','.join(x.value for x in OnlyWhen if x.value)
urgency = ','.join(str(x.value) for x in Urgency)
i = f'i={cmd.identifier or "0"}:'
p = ','.join(x.value for x in PayloadType if x.value)
self.channel.send(channel_id, f'99;{i}p=?;a={actions}:o={when}:u={urgency}:p={p}')
return None return None
if payload_type is PayloadType.close: if payload_type is PayloadType.close:
if cmd.identifier: if cmd.identifier:

View File

@@ -204,7 +204,7 @@ def do_test(self: 'TestNotifications') -> None:
# Test querying # Test querying
h('i=xyz:p=?') h('i=xyz:p=?')
self.assertFalse(di.notifications) self.assertFalse(di.notifications)
qr = 'a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close' qr = 'a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close:c=1'
self.ae(ch.responses, [f'99;i=xyz:p=?;{qr}']) self.ae(ch.responses, [f'99;i=xyz:p=?;{qr}'])
reset() reset()
h('p=?') h('p=?')