diff --git a/docs/desktop-notifications.rst b/docs/desktop-notifications.rst index 0a6975335..5e84ce4db 100644 --- a/docs/desktop-notifications.rst +++ b/docs/desktop-notifications.rst @@ -67,8 +67,11 @@ by means of two keys ``f`` which is the application name and ``t`` which is the notification type. These are free form keys, they can contain any values, their purpose is to allow users to easily filter out notifications they do not want. Both keys must have :ref:`base64` -encoded UTF-8 text as their values. Terminals can then present UI to users -to allow them to filter out notifications from applications they do not want. +encoded UTF-8 text as their values. The ``t`` key can be specified multiple +times, as notifications can have more than one type. See the `freedesktop.org +spec +`__ +for examples of notification types. .. note:: The application name should generally be set to the filename of the @@ -273,7 +276,7 @@ Key Value Default Description ``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is :ref:`base64` encoded UTF-8, otherwise it is plain UTF-8 text with no C0 control codes in it -``f`` :ref:`base64` ``unset`` The name of the application sending the notification. Can be used to filter out notifications. +``f`` :ref:`base64` ``unset`` The name of the application sending the notification. Can be used to filter out notifications. encoded UTF-8 application name diff --git a/docs/notifications.py b/docs/notifications.py index 218af47aa..cdfa8d4a2 100644 --- a/docs/notifications.py +++ b/docs/notifications.py @@ -14,7 +14,7 @@ def log_notification(nc: NotificationCommand) -> None: print(f'title: {nc.title}', file=log) print(f'body: {nc.body}', file=log) print(f'app: {nc.application_name}', file=log) - print(f'type: {nc.notification_type}', file=log) + print(f'types: {nc.notification_types}', file=log) print('\n', file=log) @@ -48,7 +48,7 @@ def main(nc: NotificationCommand) -> bool: # run a script if this notification is from myapp and has # type foo, passing in the title and body as command line args # to the script. - if nc.application_name == 'myapp' and nc.notification_type == 'foo': + if nc.application_name == 'myapp' and 'foo' in nc.notification_types: subprocess.Popen(['/path/to/my/script', nc.title, nc.body]) # do some arbitrary actions when this notification is activated diff --git a/kitty/notifications.py b/kitty/notifications.py index 3068d9be5..a09f44a71 100644 --- a/kitty/notifications.py +++ b/kitty/notifications.py @@ -209,7 +209,7 @@ class NotificationCommand: icon_data_key: str = '' icon_names: Tuple[str, ...] = () application_name: str = '' - notification_type: str = '' + notification_types: tuple[str, ...] = () timeout: int = -2 # event callbacks @@ -297,17 +297,17 @@ class NotificationCommand: self.icon_data_key = sanitize_id(v) elif k == 'n': try: - self.icon_names += (base64_decode(v).decode('utf-8', 'replace'),) + self.icon_names += (base64_decode(v).decode('utf-8'),) except Exception: self.log('Ignoring invalid icon name in notification: {v!r}') elif k == 'f': try: - self.application_name = base64_decode(v).decode('utf-8', 'replace') + self.application_name = base64_decode(v).decode('utf-8') except Exception: self.log('Ignoring invalid application_name in notification: {v!r}') elif k == 't': try: - self.notification_type = base64_decode(v).decode('utf-8', 'replace') + self.notification_types += (base64_decode(v).decode('utf-8'),) except Exception: self.log('Ignoring invalid notification type in notification: {v!r}') elif k == 'w': @@ -332,11 +332,11 @@ class NotificationCommand: if not self.icon_data_key: self.icon_data_key = prev.icon_data_key if prev.icon_names: - self.icon_names += prev.icon_names + self.icon_names = prev.icon_names + self.icon_names if not self.application_name: self.application_name = prev.application_name - if not self.notification_type: - self.notification_type = prev.notification_type + if prev.notification_types: + self.notification_types = prev.notification_types + self.notification_types if self.timeout < -1: self.timeout = prev.timeout self.icon_path = prev.icon_path @@ -402,7 +402,11 @@ class NotificationCommand: def matches_rule_item(self, location:str, query:str) -> bool: import re pat = re.compile(query) - val = {'title': self.title, 'body': self.body, 'app': self.application_name, 'type': self.notification_type}[location] + if location == 'type': + for x in self.notification_types: + if pat.search(x) is not None: + return True + val = {'title': self.title, 'body': self.body, 'app': self.application_name}[location] return pat.search(val) is not None def matches_rule(self, rule: str) -> bool: diff --git a/kitty_tests/notifications.py b/kitty_tests/notifications.py index 723460b34..553675625 100644 --- a/kitty_tests/notifications.py +++ b/kitty_tests/notifications.py @@ -14,11 +14,11 @@ from . import BaseTest def n( title='title', body='', urgency=Urgency.Normal, desktop_notification_id=1, icon_names=(), icon_path='', - application_name='', notification_type='', timeout=-1, + application_name='', notification_types=(), timeout=-1, ): return { 'title': title, 'body': body, 'urgency': urgency, 'id': desktop_notification_id, 'icon_names': icon_names, 'icon_path': icon_path, - 'application_name': application_name, 'notification_type': notification_type, 'timeout': timeout + 'application_name': application_name, 'notification_types': notification_types, 'timeout': timeout } @@ -53,7 +53,7 @@ class DesktopIntegration(DesktopIntegration): self.counter += 1 did = self.counter title, body, urgency = cmd.title, cmd.body, (Urgency.Normal if cmd.urgency is None else cmd.urgency) - ans = n(title, body, urgency, did, cmd.icon_names, os.path.basename(cmd.icon_path), cmd.application_name, cmd.notification_type, timeout=cmd.timeout) + ans = n(title, body, urgency, did, cmd.icon_names, os.path.basename(cmd.icon_path), cmd.application_name, cmd.notification_types, timeout=cmd.timeout) self.notifications.append(ans) return self.counter @@ -264,9 +264,9 @@ def do_test(self: 'TestNotifications', tdir: str) -> None: def e(x): return standard_b64encode(x.encode()).decode() - h(f'i=t:d=0:f={e("app")};title') + h(f'i=t:d=0:f={e("app")}:t={e("1")};title') h(f'i=t:t={e("test")}') - self.ae(di.notifications, [n(application_name='app', notification_type='test')]) + self.ae(di.notifications, [n(application_name='app', notification_types=('1', 'test',))]) reset() # Test timeout