mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-08-27 03:42:49 +00:00
Start work on refactoring notifications handling
Makes the code cleaner and easily mockable for testing. Also, add code to handle closing notifications on Linux.
This commit is contained in:
parent
31cee3e966
commit
d68e49fe64
14 changed files with 798 additions and 497 deletions
164
kitty_tests/notifications.py
Normal file
164
kitty_tests/notifications.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from base64 import standard_b64encode
|
||||
from typing import Optional
|
||||
|
||||
from kitty.notifications import Channel, DesktopIntegration, NotificationManager, UIState, Urgency
|
||||
|
||||
from . import BaseTest
|
||||
|
||||
|
||||
def n(title='title', body='', urgency=Urgency.Normal, desktop_notification_id=1):
|
||||
return {'title': title, 'body': body, 'urgency': urgency, 'id': desktop_notification_id}
|
||||
|
||||
|
||||
class DesktopIntegration(DesktopIntegration):
|
||||
def initialize(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.notifications = []
|
||||
self.close_events = []
|
||||
self.counter = 0
|
||||
|
||||
def close_notification(self, desktop_notification_id: int) -> bool:
|
||||
self.close_events.append(desktop_notification_id)
|
||||
|
||||
def notify(self,
|
||||
title: str,
|
||||
body: str,
|
||||
timeout: int = -1,
|
||||
application: str = 'kitty',
|
||||
icon: bool = True,
|
||||
subtitle: Optional[str] = None,
|
||||
urgency: Urgency = Urgency.Normal,
|
||||
) -> int:
|
||||
self.counter += 1
|
||||
self.notifications.append(n(title, body, urgency, self.counter))
|
||||
return self.counter
|
||||
|
||||
|
||||
class Channel(Channel):
|
||||
|
||||
focused = visible = True
|
||||
|
||||
def __init__(self, *a):
|
||||
super().__init__(*a)
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.responses = []
|
||||
self.focus_events = []
|
||||
|
||||
def ui_state(self, channel_id):
|
||||
return UIState(self.focused, self.visible)
|
||||
|
||||
def focus(self, channel_id: int, activation_token: str) -> None:
|
||||
self.focus_events.append(activation_token)
|
||||
|
||||
def send(self, channel_id: int, osc_escape_code: str) -> bool:
|
||||
self.responses.append(osc_escape_code)
|
||||
|
||||
|
||||
def do_test(self: 'TestNotifications') -> None:
|
||||
di = DesktopIntegration(None)
|
||||
ch = Channel()
|
||||
nm = NotificationManager(di, ch, lambda *a, **kw: None)
|
||||
di.notification_manager = nm
|
||||
|
||||
def reset():
|
||||
di.reset()
|
||||
ch.reset()
|
||||
nm.reset()
|
||||
|
||||
def h(raw_data, osc_code=99, channel_id=1):
|
||||
nm.handle_notification_cmd(channel_id, osc_code, raw_data)
|
||||
|
||||
def activate(which=0):
|
||||
n = di.notifications[which]
|
||||
nm.notification_activated(n['id'])
|
||||
|
||||
h('test it', osc_code=9)
|
||||
self.ae(di.notifications, [n(title='test it')])
|
||||
activate()
|
||||
assert_events()
|
||||
reset()
|
||||
|
||||
h('d=0:u=2:i=x;title')
|
||||
h('d=1:i=x:p=body;body')
|
||||
self.ae(notifications, [n(client_id='x', body='body', urgency=Urgency.Critical)])
|
||||
activate()
|
||||
assert_events('x')
|
||||
reset()
|
||||
|
||||
h('i=x:p=body:a=-focus;body')
|
||||
self.ae(notifications, [n(client_id='x', title='body')])
|
||||
activate()
|
||||
assert_events('x', focus=False)
|
||||
reset()
|
||||
|
||||
h('i=x:e=1;' + standard_b64encode(b'title').decode('ascii'))
|
||||
self.ae(notifications, [n(client_id='x', )])
|
||||
activate()
|
||||
assert_events('x')
|
||||
reset()
|
||||
|
||||
h('e=1;' + standard_b64encode(b'title').decode('ascii'))
|
||||
self.ae(notifications, [n()])
|
||||
activate()
|
||||
assert_events()
|
||||
reset()
|
||||
|
||||
h('d=0:i=x:a=-report;title')
|
||||
h('d=1:i=x:a=report;body')
|
||||
self.ae(notifications, [n(client_id='x', title='titlebody')])
|
||||
activate()
|
||||
assert_events('x', report=True)
|
||||
reset()
|
||||
|
||||
h('d=0:i=y;title')
|
||||
h('d=1:i=y:p=xxx;title')
|
||||
self.ae(notifications, [n(client_id='y')])
|
||||
reset()
|
||||
|
||||
# test closing interactions with reporting and activation
|
||||
h('i=c;title')
|
||||
self.ae(notifications, [n(client_id='c')])
|
||||
close()
|
||||
assert_events('c', focus=False, close=True)
|
||||
reset()
|
||||
h('i=c;title')
|
||||
self.ae(notifications, [n(client_id='c')])
|
||||
h('i=c:p=close')
|
||||
self.ae(notifications, [n(client_id='c')])
|
||||
assert_events('c', focus=False, close=True)
|
||||
reset()
|
||||
h('i=c;title')
|
||||
h('i=c:p=close;notify')
|
||||
assert_events('c', focus=False, close=True, close_response=True)
|
||||
reset()
|
||||
|
||||
h(';title')
|
||||
self.ae(notifications, [n()])
|
||||
activate()
|
||||
assert_events()
|
||||
reset()
|
||||
|
||||
# Test querying
|
||||
h('i=xyz:p=?')
|
||||
self.assertFalse(notifications)
|
||||
qr = 'a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close'
|
||||
self.ae(query_responses, [f'99;i=xyz:p=?;{qr}'])
|
||||
reset()
|
||||
h('p=?')
|
||||
self.assertFalse(notifications)
|
||||
self.ae(query_responses, [f'99;i=0:p=?;{qr}'])
|
||||
|
||||
|
||||
class TestNotifications(BaseTest):
|
||||
|
||||
def test_desktop_notify(self):
|
||||
do_test(self)
|
||||
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from base64 import standard_b64encode
|
||||
from binascii import hexlify
|
||||
from functools import partial
|
||||
|
||||
|
|
@ -15,7 +14,6 @@ from kitty.fast_data_types import (
|
|||
test_find_either_of_two_bytes,
|
||||
test_utf8_decode_to_sentinel,
|
||||
)
|
||||
from kitty.notify import NotificationCommand, QueryResponse, Urgency, handle_notification_cmd, notification_activated, reset_registry
|
||||
|
||||
from . import BaseTest, parse_bytes
|
||||
|
||||
|
|
@ -521,92 +519,6 @@ class TestParser(BaseTest):
|
|||
c.clear()
|
||||
pb('\033]52;p;xyz\x07', ('clipboard_control', 52, 'p;xyz'))
|
||||
|
||||
def test_desktop_notify(self):
|
||||
reset_registry()
|
||||
notifications = []
|
||||
activations = []
|
||||
query_responses = []
|
||||
prev_cmd = NotificationCommand()
|
||||
|
||||
def reset():
|
||||
nonlocal prev_cmd
|
||||
reset_registry()
|
||||
del notifications[:]
|
||||
del activations[:]
|
||||
del query_responses[:]
|
||||
prev_cmd = NotificationCommand()
|
||||
|
||||
def notify(title, body, identifier, urgency=Urgency.Normal):
|
||||
notifications.append((title, body, identifier, urgency))
|
||||
|
||||
def h(raw_data, osc_code=99, window_id=1):
|
||||
nonlocal prev_cmd
|
||||
try:
|
||||
x = handle_notification_cmd(osc_code, raw_data, window_id, prev_cmd, notify, log_warnings=False)
|
||||
if x is not None and osc_code == 99:
|
||||
prev_cmd = x
|
||||
except QueryResponse as err:
|
||||
query_responses.append(err.response_string)
|
||||
|
||||
def activated(identifier, window_id, focus, report):
|
||||
activations.append((identifier, window_id, focus, report))
|
||||
|
||||
h('test it', osc_code=9)
|
||||
self.ae(notifications, [('test it', '', 'i0', Urgency.Normal)])
|
||||
notification_activated(notifications[-1][-2], activated)
|
||||
self.ae(activations, [('0', 1, True, False)])
|
||||
reset()
|
||||
|
||||
h('d=0:u=2:i=x;title')
|
||||
h('d=1:i=x:p=body;body')
|
||||
self.ae(notifications, [('title', 'body', 'i0', Urgency.Critical)])
|
||||
notification_activated(notifications[-1][-2], activated)
|
||||
self.ae(activations, [('x', 1, True, False)])
|
||||
reset()
|
||||
|
||||
h('i=x:p=body:a=-focus;body')
|
||||
self.ae(notifications, [('body', '', 'i0', Urgency.Normal)])
|
||||
notification_activated(notifications[-1][-2], activated)
|
||||
self.ae(activations, [])
|
||||
reset()
|
||||
|
||||
h('i=x:e=1;' + standard_b64encode(b'title').decode('ascii'))
|
||||
self.ae(notifications, [('title', '', 'i0', Urgency.Normal)])
|
||||
notification_activated(notifications[-1][-2], activated)
|
||||
self.ae(activations, [('x', 1, True, False)])
|
||||
reset()
|
||||
|
||||
h('e=1;' + standard_b64encode(b'title').decode('ascii'))
|
||||
self.ae(notifications, [('title', '', 'i0', Urgency.Normal)])
|
||||
notification_activated(notifications[-1][-2], activated)
|
||||
self.ae(activations, [('0', 1, True, False)])
|
||||
reset()
|
||||
|
||||
h('d=0:i=x:a=-report;title')
|
||||
h('d=1:i=x:a=report;body')
|
||||
self.ae(notifications, [('titlebody', '', 'i0', Urgency.Normal)])
|
||||
notification_activated(notifications[-1][-2], activated)
|
||||
self.ae(activations, [('x', 1, True, True)])
|
||||
reset()
|
||||
|
||||
h('d=0:i=y;title')
|
||||
h('d=1:i=y:p=xxx;title')
|
||||
self.ae(notifications, [('title', '', 'i0', Urgency.Normal)])
|
||||
reset()
|
||||
|
||||
h(';title')
|
||||
self.ae(notifications, [('title', '', 'i0', Urgency.Normal)])
|
||||
notification_activated(notifications[-1][-2], activated)
|
||||
self.ae(activations, [('0', 1, True, False)])
|
||||
reset()
|
||||
h('i=xyz:p=?')
|
||||
self.assertFalse(notifications)
|
||||
self.ae(query_responses, ['99;i=xyz:p=?;a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close'])
|
||||
reset()
|
||||
h('p=?')
|
||||
self.assertFalse(notifications)
|
||||
self.ae(query_responses, ['99;i=0:p=?;a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close'])
|
||||
|
||||
def test_dcs_codes(self):
|
||||
s = self.create_screen()
|
||||
c = s.callbacks
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue