Allow multiple types per notification

This commit is contained in:
Kovid Goyal
2024-07-29 20:52:54 +05:30
parent 4bc532a2d0
commit 212d7accfc
4 changed files with 25 additions and 18 deletions

View File

@@ -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
<https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#categories>`__
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

View File

@@ -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

View File

@@ -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:

View File

@@ -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