Wayland: When the compositor supports the xdg-system-bell protocol use it to play the default bell sound

This commit is contained in:
Kovid Goyal 2025-04-16 15:48:51 +05:30
parent 4f6d97c143
commit 5d2e258c35
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
13 changed files with 48 additions and 13 deletions

View file

@ -103,6 +103,10 @@ Detailed list of changes
- Fix a regression that caused automatic color themes to not be re-applied after config file reload (:iss:`8530`)
- Wayland: When the compositor supports the `xdg-system-bell
<https://wayland.app/protocols/xdg-system-bell-v1>`__ protocol use it to play
the default bell sound
0.41.1 [2025-04-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -324,6 +324,7 @@ def generate_wrappers(glfw_header: str) -> None:
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle)
bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle)
bool glfwWaylandBeep(GLFWwindow *handle)
void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c)
pid_t glfwWaylandCompositorPID(void)
unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data)

View file

@ -85,6 +85,7 @@
"staging/single-pixel-buffer/single-pixel-buffer-v1.xml",
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
"staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml",
"staging/xdg-system-bell/xdg-system-bell-v1.xml",
"kwin-blur-v1.xml",
"wlr-layer-shell-unstable-v1.xml"

7
glfw/wl_init.c vendored
View file

@ -603,6 +603,9 @@ static void registryHandleGlobal(void* data UNUSED,
else if (is(xdg_toplevel_icon_manager_v1)) {
_glfw.wl.xdg_toplevel_icon_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_icon_manager_v1_interface, 1);
}
else if (is(xdg_system_bell_v1)) {
_glfw.wl.xdg_system_bell_v1 = wl_registry_bind(registry, name, &xdg_system_bell_v1_interface, 1);
}
#undef is
}
@ -712,7 +715,7 @@ get_compositor_missing_capabilities(void) {
C(blur, org_kde_kwin_blur_manager); C(server_side_decorations, decorationManager);
C(cursor_shape, wp_cursor_shape_manager_v1); C(layer_shell, zwlr_layer_shell_v1);
C(single_pixel_buffer, wp_single_pixel_buffer_manager_v1); C(preferred_scale, has_preferred_buffer_scale);
C(idle_inhibit, idle_inhibit_manager); C(icon, xdg_toplevel_icon_manager_v1);
C(idle_inhibit, idle_inhibit_manager); C(icon, xdg_toplevel_icon_manager_v1); C(bell, xdg_system_bell_v1);
if (_glfw.wl.xdg_wm_base_version < 6) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "window-state-suspended");
if (_glfw.wl.xdg_wm_base_version < 5) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "window-capabilities");
#undef C
@ -889,6 +892,8 @@ void _glfwPlatformTerminate(void)
xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1);
if (_glfw.wl.xdg_toplevel_icon_manager_v1)
xdg_toplevel_icon_manager_v1_destroy(_glfw.wl.xdg_toplevel_icon_manager_v1);
if (_glfw.wl.xdg_system_bell_v1)
xdg_system_bell_v1_destroy(_glfw.wl.xdg_system_bell_v1);
if (_glfw.wl.wp_single_pixel_buffer_manager_v1)
wp_single_pixel_buffer_manager_v1_destroy(_glfw.wl.wp_single_pixel_buffer_manager_v1);
if (_glfw.wl.wp_cursor_shape_manager_v1)

2
glfw/wl_platform.h vendored
View file

@ -67,6 +67,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "wayland-single-pixel-buffer-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#include "wayland-xdg-toplevel-icon-v1-client-protocol.h"
#include "wayland-xdg-system-bell-v1-client-protocol.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@ -338,6 +339,7 @@ typedef struct _GLFWlibraryWayland
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
struct xdg_activation_v1* xdg_activation_v1;
struct xdg_toplevel_icon_manager_v1* xdg_toplevel_icon_manager_v1;
struct xdg_system_bell_v1* xdg_system_bell_v1;
struct wp_cursor_shape_manager_v1* wp_cursor_shape_manager_v1;
struct wp_cursor_shape_device_v1* wp_cursor_shape_device_v1;
struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1;

8
glfw/wl_window.c vendored
View file

@ -2835,3 +2835,11 @@ _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) {
window = window->next;
}
}
GLFWAPI bool glfwWaylandBeep(GLFWwindow *handle) {
if (!_glfw.wl.xdg_system_bell_v1) return false;
_GLFWwindow *window = (_GLFWwindow*)handle;
xdg_system_bell_v1_ring(_glfw.wl.xdg_system_bell_v1, window ? window->wl.surface : NULL);
return true;
}

View file

@ -1501,7 +1501,7 @@ class Boss:
else:
self.visual_window_select_action_trigger(self.current_visual_select.window_ids[0] if self.current_visual_select.window_ids else 0)
if get_options().enable_audio_bell:
ring_bell()
ring_bell(tab.os_window_id)
def visual_window_select_action_trigger(self, window_id: int = 0) -> None:
if self.current_visual_select:
@ -1545,7 +1545,7 @@ class Boss:
if len(selectable_windows) < 2:
self.visual_window_select_action_trigger(selectable_windows[0][0] if selectable_windows else 0)
if get_options().enable_audio_bell:
ring_bell()
ring_bell(tab.os_window_id)
return None
cvs = self.current_visual_select

View file

@ -1012,7 +1012,7 @@ def set_active_window(os_window_id: int, tab_id: int, window_id: int) -> None:
pass
def ring_bell() -> None:
def ring_bell(os_window_id: int = 0) -> None:
pass

3
kitty/glfw-wrapper.c generated
View file

@ -488,6 +488,9 @@ load_glfw(const char* path) {
*(void **) (&glfwWaylandIsWindowFullyCreated_impl) = dlsym(handle, "glfwWaylandIsWindowFullyCreated");
if (glfwWaylandIsWindowFullyCreated_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwWaylandBeep_impl) = dlsym(handle, "glfwWaylandBeep");
if (glfwWaylandBeep_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwWaylandSetupLayerShellForNextWindow_impl) = dlsym(handle, "glfwWaylandSetupLayerShellForNextWindow");
if (glfwWaylandSetupLayerShellForNextWindow_impl == NULL) dlerror(); // clear error indicator

4
kitty/glfw-wrapper.h generated
View file

@ -2344,6 +2344,10 @@ typedef bool (*glfwWaylandIsWindowFullyCreated_func)(GLFWwindow*);
GFW_EXTERN glfwWaylandIsWindowFullyCreated_func glfwWaylandIsWindowFullyCreated_impl;
#define glfwWaylandIsWindowFullyCreated glfwWaylandIsWindowFullyCreated_impl
typedef bool (*glfwWaylandBeep_func)(GLFWwindow*);
GFW_EXTERN glfwWaylandBeep_func glfwWaylandBeep_impl;
#define glfwWaylandBeep glfwWaylandBeep_impl
typedef void (*glfwWaylandSetupLayerShellForNextWindow_func)(const GLFWLayerShellConfig*);
GFW_EXTERN glfwWaylandSetupLayerShellForNextWindow_func glfwWaylandSetupLayerShellForNextWindow_impl;
#define glfwWaylandSetupLayerShellForNextWindow glfwWaylandSetupLayerShellForNextWindow_impl

View file

@ -1814,7 +1814,7 @@ cocoa_hide_other_apps(PYNOARG) {
}
static void
ring_audio_bell(void) {
ring_audio_bell(OSWindow *w) {
static monotonic_t last_bell_at = -1;
monotonic_t now = monotonic();
if (last_bell_at >= 0 && now - last_bell_at <= ms_to_monotonic_t(100ll)) return;
@ -1823,13 +1823,19 @@ ring_audio_bell(void) {
cocoa_system_beep(OPT(bell_path));
#else
if (OPT(bell_path)) play_canberra_sound(OPT(bell_path), "kitty bell", true, "event", OPT(bell_theme));
else play_canberra_sound("bell", "kitty bell", false, "event", OPT(bell_theme));
else {
if (!global_state.is_wayland || !glfwWaylandBeep(w ? w->handle : NULL)) play_canberra_sound(
"bell", "kitty bell", false, "event", OPT(bell_theme));
}
#endif
}
static PyObject*
ring_bell(PYNOARG) {
ring_audio_bell();
ring_bell(PyObject *self UNUSED, PyObject *args) {
unsigned long long os_window_id = 0;
if (!PyArg_ParseTuple(args, "|K", &os_window_id)) return NULL;
OSWindow *w = os_window_for_id(os_window_id);
ring_audio_bell(w);
Py_RETURN_NONE;
}
@ -1918,7 +1924,7 @@ void
request_window_attention(id_type kitty_window_id, bool audio_bell) {
OSWindow *w = os_window_for_kitty_window(kitty_window_id);
if (w) {
if (audio_bell) ring_audio_bell();
if (audio_bell) ring_audio_bell(w);
if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle);
glfwPostEmptyEvent();
}
@ -2408,7 +2414,7 @@ static PyMethodDef module_methods[] = {
METHODB(get_clipboard_mime, METH_VARARGS),
METHODB(toggle_secure_input, METH_NOARGS),
METHODB(get_content_scale_for_window, METH_NOARGS),
METHODB(ring_bell, METH_NOARGS),
METHODB(ring_bell, METH_VARARGS),
METHODB(toggle_fullscreen, METH_VARARGS),
METHODB(toggle_maximized, METH_VARARGS),
METHODB(change_os_window_state, METH_VARARGS),

View file

@ -1039,7 +1039,8 @@ MP3 or WAV on macOS (NSSound).
opt('linux_bell_theme', '__custom', ctype='!bell_theme',
long_text='''
The XDG Sound Theme kitty will use to play the bell sound.
Defaults to the custom theme name specified in the
On Wayland, when the compositor supports it, it is asked to play the system default
bell sound. Otherwise, defaults to the custom theme name specified in the
:link:`XDG Sound theme specification <https://specifications.freedesktop.org/sound-theme-spec/latest/sound_lookup.html>,
falling back to the default freedesktop theme if it does not exist.
To change your sound theme desktop wide, create :file:`~/.local/share/sounds/__custom/index.theme` with the contents:

View file

@ -451,7 +451,7 @@ class Tab: # {{{
if w is not None and self.resize_window_by(
w.id, increment, is_horizontal) is not None:
if get_options().enable_audio_bell:
ring_bell()
ring_bell(self.os_window_id)
@ac('win', 'Reset window sizes undoing any dynamic resizing of windows')
def reset_window_sizes(self) -> None:
@ -463,7 +463,7 @@ class Tab: # {{{
ret = self.current_layout.layout_action(action_name, args, self.windows)
if ret is None:
if get_options().enable_audio_bell:
ring_bell()
ring_bell(self.os_window_id)
return
self.relayout()