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 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 any values, their purpose is to allow users to easily filter out
notifications they do not want. Both keys must have :ref:`base64` 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 encoded UTF-8 text as their values. The ``t`` key can be specified multiple
to allow them to filter out notifications from applications they do not want. 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:: .. note::
The application name should generally be set to the filename of the 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, ``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 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 encoded UTF-8
application name application name

View File

@@ -14,7 +14,7 @@ def log_notification(nc: NotificationCommand) -> None:
print(f'title: {nc.title}', file=log) print(f'title: {nc.title}', file=log)
print(f'body: {nc.body}', file=log) print(f'body: {nc.body}', file=log)
print(f'app: {nc.application_name}', 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) print('\n', file=log)
@@ -48,7 +48,7 @@ def main(nc: NotificationCommand) -> bool:
# run a script if this notification is from myapp and has # run a script if this notification is from myapp and has
# type foo, passing in the title and body as command line args # type foo, passing in the title and body as command line args
# to the script. # 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]) subprocess.Popen(['/path/to/my/script', nc.title, nc.body])
# do some arbitrary actions when this notification is activated # do some arbitrary actions when this notification is activated

View File

@@ -209,7 +209,7 @@ class NotificationCommand:
icon_data_key: str = '' icon_data_key: str = ''
icon_names: Tuple[str, ...] = () icon_names: Tuple[str, ...] = ()
application_name: str = '' application_name: str = ''
notification_type: str = '' notification_types: tuple[str, ...] = ()
timeout: int = -2 timeout: int = -2
# event callbacks # event callbacks
@@ -297,17 +297,17 @@ class NotificationCommand:
self.icon_data_key = sanitize_id(v) self.icon_data_key = sanitize_id(v)
elif k == 'n': elif k == 'n':
try: try:
self.icon_names += (base64_decode(v).decode('utf-8', 'replace'),) self.icon_names += (base64_decode(v).decode('utf-8'),)
except Exception: except Exception:
self.log('Ignoring invalid icon name in notification: {v!r}') self.log('Ignoring invalid icon name in notification: {v!r}')
elif k == 'f': elif k == 'f':
try: try:
self.application_name = base64_decode(v).decode('utf-8', 'replace') self.application_name = base64_decode(v).decode('utf-8')
except Exception: except Exception:
self.log('Ignoring invalid application_name in notification: {v!r}') self.log('Ignoring invalid application_name in notification: {v!r}')
elif k == 't': elif k == 't':
try: try:
self.notification_type = base64_decode(v).decode('utf-8', 'replace') self.notification_types += (base64_decode(v).decode('utf-8'),)
except Exception: except Exception:
self.log('Ignoring invalid notification type in notification: {v!r}') self.log('Ignoring invalid notification type in notification: {v!r}')
elif k == 'w': elif k == 'w':
@@ -332,11 +332,11 @@ class NotificationCommand:
if not self.icon_data_key: if not self.icon_data_key:
self.icon_data_key = prev.icon_data_key self.icon_data_key = prev.icon_data_key
if prev.icon_names: if prev.icon_names:
self.icon_names += prev.icon_names self.icon_names = prev.icon_names + self.icon_names
if not self.application_name: if not self.application_name:
self.application_name = prev.application_name self.application_name = prev.application_name
if not self.notification_type: if prev.notification_types:
self.notification_type = prev.notification_type self.notification_types = prev.notification_types + self.notification_types
if self.timeout < -1: if self.timeout < -1:
self.timeout = prev.timeout self.timeout = prev.timeout
self.icon_path = prev.icon_path self.icon_path = prev.icon_path
@@ -402,7 +402,11 @@ class NotificationCommand:
def matches_rule_item(self, location:str, query:str) -> bool: def matches_rule_item(self, location:str, query:str) -> bool:
import re import re
pat = re.compile(query) 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 return pat.search(val) is not None
def matches_rule(self, rule: str) -> bool: def matches_rule(self, rule: str) -> bool:

View File

@@ -14,11 +14,11 @@ from . import BaseTest
def n( def n(
title='title', body='', urgency=Urgency.Normal, desktop_notification_id=1, icon_names=(), icon_path='', 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 { return {
'title': title, 'body': body, 'urgency': urgency, 'id': desktop_notification_id, 'icon_names': icon_names, 'icon_path': icon_path, '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 self.counter += 1
did = self.counter did = self.counter
title, body, urgency = cmd.title, cmd.body, (Urgency.Normal if cmd.urgency is None else cmd.urgency) 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) self.notifications.append(ans)
return self.counter return self.counter
@@ -264,9 +264,9 @@ def do_test(self: 'TestNotifications', tdir: str) -> None:
def e(x): def e(x):
return standard_b64encode(x.encode()).decode() 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")}') 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() reset()
# Test timeout # Test timeout