Update sound support in desktop notifications spec

Add a short list of standard sound names.
This commit is contained in:
Kovid Goyal 2024-08-04 20:19:39 +05:30
parent 40d1781404
commit 2c743dcdb2
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
7 changed files with 53 additions and 18 deletions

View file

@ -343,13 +343,25 @@ By default, notifications may or may not have a sound associated with them
depending on the policies of the OS notifications service. Sometimes it
might be useful to ensure a notification is not accompanied by a sound.
This can be done by using the ``s`` key which accepts :ref:`base64` encoded
UTF-8 text as its value. Using a value of ``silent`` means the notification
will not be accompanied with a sound. A value of ``system`` (the default)
means that the OS notifications default policies are followed. Any other name
is implementation dependent. For example, on Linux, one can use the `standard
sound names
<https://specifications.freedesktop.org/sound-naming-spec/latest/#names>`__.
Support for sounds can be queried as described below.
UTF-8 text as its value. The set of known sounds names is in the table below,
any other names are implementation dependent, for instance, on Linux, terminal emulators will
probably support the `standard sound names
<https://specifications.freedesktop.org/sound-naming-spec/latest/#names>`__
.. table:: Standard sound names
======================== ==============================================
Name Description
======================== ==============================================
``system`` The default system sound for a notification, which may be some kind of beep or just silence
``silent`` No sound must accompany the notification
``error`` A sound associated with error messages
``warn``, ``warning`` A sound associated with warning messages
``info`` A sound associated with information messages
``question`` A sound associated with questions
======================== ==============================================
Support for sound names can be queried as described below.
Querying for support
@ -391,11 +403,9 @@ Key Value
``p`` key that the terminal implements). These must contain at least
``title``.
``s`` Comma separated list of keywords ``silent`` and ``xdg-names`` indicating
support for silent notifications and for passing of `Freedesktop
standard sound names
<https://specifications.freedesktop.org/sound-naming-spec/latest/#names>`__ to the
desktop notification service for custom sounds.
``s`` Comma separated list of sound names from the table of standard sound names above.
Terminals will report the list of standard sound names they support.
Terminals *should* support atleast ``system`` and ``silent``.
``u`` Comma separated list of urgency values that the terminal implements.
If urgency is not supported, the ``u`` key must be absent from the

View file

@ -13,6 +13,7 @@
#include <Carbon/Carbon.h>
#include <Cocoa/Cocoa.h>
#include <UserNotifications/UserNotifications.h>
#import <AudioToolbox/AudioServices.h>
#include <AvailabilityMacros.h>
// Needed for _NSGetProgname
@ -1185,7 +1186,15 @@ - (BOOL)openFileURLs:(NSPasteboard*)pasteboard
return convert_image_to_png(icon, image_size, output_path);
}}
static PyObject*
play_system_sound_by_id_async(PyObject *self UNUSED, PyObject *which) {
if (!PyLong_Check(which)) { PyErr_SetString(PyExc_TypeError, "system sound id must be an integer"); return NULL; }
AudioServicesPlaySystemSound(PyLong_AsUnsignedLong(which));
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{"cocoa_play_system_sound_by_id_async", play_system_sound_by_id_async, METH_O, ""},
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""},
{"cocoa_send_notification", (PyCFunction)(void(*)(void))cocoa_send_notification, METH_VARARGS | METH_KEYWORDS, ""},

View file

@ -204,6 +204,16 @@ def wakeup_io_loop() -> None:
'text-editor': ('utilities-text-editor', '📄'),
}
# See https://github.com/TUNER88/iOSSystemSoundsLibrary for Apple's system
# sound ids not all of which are available on macOS.
standard_sound_names = {
'error': ('dialog-error', 1),
'info': ('dialog-information', 2),
'warning': ('dialog-warning', 3),
'warn': ('dialog-warning', 3),
'question': ('dialog-question', 4),
}
def glfw_path(module: str) -> str:
prefix = 'kitty.' if getattr(sys, 'frozen', False) else ''

View file

@ -1702,6 +1702,7 @@ def timed_debug_print(x: str) -> None: ...
def opengl_version_string() -> str: ...
def systemd_move_pid_into_new_scope(pid: int, scope_name: str, description: str) -> str: ...
def play_desktop_sound_async(name: str, event_id: str = 'test sound', is_path: bool = False, theme_name: str = '') -> str: ...
def cocoa_play_system_sound_by_id_async(sound_id: int) -> None: ...
class MousePosition(TypedDict):
cell_x: int

View file

@ -12,7 +12,7 @@
from typing import Any, Callable, NamedTuple, Optional, Set, Union
from weakref import ReferenceType, ref
from .constants import cache_dir, config_dir, is_macos, logo_png_file, standard_icon_names
from .constants import cache_dir, config_dir, is_macos, logo_png_file, standard_icon_names, standard_sound_names
from .fast_data_types import ESC_OSC, StreamingBase64Decoder, add_timer, base64_decode, current_focused_os_window_id, get_boss, get_options
from .types import run_once
from .typing import WindowType
@ -486,7 +486,7 @@ def query_response(self, identifier: str) -> str:
i = f'i={identifier or "0"}:'
p = ','.join(x.value for x in PayloadType if x.value and self.payload_type_supported(x))
c = ':c=1' if self.supports_close_events else ''
s = 'silent' + (f',{self.supports_sound_names}' if self.supports_sound_names else '')
s = 'system,silent,' + ','.join(sorted(standard_sound_names))
return f'99;{i}p=?;a={actions}:o={when}:u={urgency}:p={p}{c}:w=1:s={s}'
@ -596,7 +596,7 @@ def notify(self, nc: NotificationCommand, existing_desktop_notification_id: Opti
cocoa_send_notification(
nc.application_name or 'kitty', str(desktop_notification_id), nc.title, body,
category=category, categories=categories, image_path=image_path, urgency=nc.urgency.value,
muted=nc.sound_name == 'silent',
muted=nc.sound_name == 'silent' or nc.sound_name in standard_sound_names,
)
return desktop_notification_id
@ -616,9 +616,12 @@ def notification_activated(self, event: str, ident: str, button_id: str) -> None
log_error(f'Got unexpected notification activated event with id: {ident!r} from cocoa')
return
if event == 'created':
self.notification_manager.notification_created(desktop_notification_id)
n = self.notification_manager.notification_created(desktop_notification_id)
from .fast_data_types import cocoa_live_delivered_notifications
cocoa_live_delivered_notifications() # so that we purge dead notifications
if n and n.sound_name in standard_sound_names:
from .fast_data_types import cocoa_play_system_sound_by_id_async
cocoa_play_system_sound_by_id_async(standard_sound_names[n.sound_name][1])
elif event == 'activated':
self.notification_manager.notification_activated(desktop_notification_id, 0)
elif event == 'creation_failed':
@ -690,8 +693,9 @@ def created(self, dbus_notification_id: int, desktop_notification_id: int) -> No
# supports named sounds or not so we play the named sound
# ourselves and tell the server to mute any sound it might play.
if n.sound_name not in ('system', 'silent'):
sn = standard_sound_names[n.sound_name][0] if n.sound_name in standard_sound_names else n.sound_name
from .fast_data_types import play_desktop_sound_async
play_desktop_sound_async(n.sound_name, event_id='desktop notification')
play_desktop_sound_async(sn, event_id='desktop notification')
def dispatch_event_from_desktop(self, event_type: str, dbus_notification_id: int, extra: Union[int, str]) -> None:
if event_type == 'capabilities':

View file

@ -249,7 +249,7 @@ def enc(x):
# Test querying
h('i=xyz:p=?')
self.assertFalse(di.notifications)
qr = 'a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close,icon,alive,buttons:c=1:w=1:s=silent,xdg-names'
qr = 'a=focus,report:o=always,unfocused,invisible:u=0,1,2:p=title,body,?,close,icon,alive,buttons:c=1:w=1:s=system,silent,error,info,question,warn,warning'
self.ae(ch.responses, [f'99;i=xyz:p=?;{qr}'])
reset()
h('p=?')

View file

@ -618,6 +618,7 @@ def kitty_env(args: Options) -> Env:
if is_macos:
platform_libs = [
'-framework', 'Carbon', '-framework', 'CoreText', '-framework', 'CoreGraphics',
'-framework', 'AudioToolbox',
]
test_program_src = '''#include <UserNotifications/UserNotifications.h>
int main(void) { return 0; }\n'''