From e1643ae68a87892c2d14eaa7b07ce2162a36a735 Mon Sep 17 00:00:00 2001 From: Pieter van der Heijden Date: Thu, 19 Feb 2026 22:01:07 +0100 Subject: [PATCH] Add macos_dock_badge_on_bell config option Set a "!" badge on kitty's dock icon when a bell fires and kitty is not focused, providing a persistent visual indicator for background notifications. The badge is automatically cleared when kitty regains focus via NSApplicationDidBecomeActiveNotification. The option defaults to yes and can be disabled with `macos_dock_badge_on_bell no`. Co-Authored-By: Claude Opus 4.6 --- kitty/cocoa_window.h | 2 ++ kitty/cocoa_window.m | 29 +++++++++++++++++++++++++++++ kitty/glfw.c | 3 +++ kitty/options/definition.py | 9 +++++++++ kitty/options/types.py | 2 ++ kitty/state.h | 1 + 6 files changed, 46 insertions(+) diff --git a/kitty/cocoa_window.h b/kitty/cocoa_window.h index 69fad6b0b..4baa1ebad 100644 --- a/kitty/cocoa_window.h +++ b/kitty/cocoa_window.h @@ -66,3 +66,5 @@ extern uint8_t* render_single_ascii_char_as_mask(const char ch, size_t *result_w void get_cocoa_key_equivalent(uint32_t, int, char *key, size_t key_sz, int*); void set_cocoa_pending_action(CocoaPendingAction action, const char*); void cocoa_report_live_notifications(const char* ident); +void cocoa_set_dock_badge(const char *label); +void cocoa_clear_dock_badge(void); diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index fa90e9670..dd1f56373 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -1339,6 +1339,28 @@ cocoa_show_progress_bar_on_dock_icon(PyObject *self UNUSED, PyObject *args) { } // }}} +// Dock badge {{{ + +void +cocoa_set_dock_badge(const char *label) { + @autoreleasepool { + NSDockTile *dockTile = [NSApp dockTile]; + [dockTile setBadgeLabel:@(label)]; + [dockTile display]; + } +} + +void +cocoa_clear_dock_badge(void) { + @autoreleasepool { + NSDockTile *dockTile = [NSApp dockTile]; + [dockTile setBadgeLabel:nil]; + [dockTile display]; + } +} + +// }}} + 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, ""}, @@ -1360,5 +1382,12 @@ init_cocoa(PyObject *module) { cocoa_clear_global_shortcuts(); if (PyModule_AddFunctions(module, module_methods) != 0) return false; register_at_exit_cleanup_func(COCOA_CLEANUP_FUNC, cleanup); + [[NSNotificationCenter defaultCenter] + addObserverForName:NSApplicationDidBecomeActiveNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *note __attribute__((unused))) { + cocoa_clear_dock_badge(); + }]; return true; } diff --git a/kitty/glfw.c b/kitty/glfw.c index 200f074bc..1bece8450 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -2201,6 +2201,9 @@ request_window_attention(id_type kitty_window_id, bool audio_bell) { if (w) { if (audio_bell) ring_audio_bell(w); if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle); +#ifdef __APPLE__ + if (OPT(macos_dock_badge_on_bell)) cocoa_set_dock_badge("!"); +#endif glfwPostEmptyEvent(); } } diff --git a/kitty/options/definition.py b/kitty/options/definition.py index d9c7e1ba7..f975ed1b3 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1129,6 +1129,15 @@ window in which the bell occurred. ''' ) +opt('macos_dock_badge_on_bell', 'yes', + option_type='to_bool', ctype='bool', + long_text=''' +Show a badge on kitty's dock icon when a bell occurs and kitty is not the +active application (macOS only). The badge is automatically cleared when kitty +regains focus. +''' + ) + opt('bell_path', 'none', option_type='config_or_absolute_path', ctype='!bell_path', long_text=''' diff --git a/kitty/options/types.py b/kitty/options/types.py index eb68cace3..f4ab3cce6 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -379,6 +379,7 @@ option_names = ( 'listen_on', 'macos_colorspace', 'macos_custom_beam_cursor', + 'macos_dock_badge_on_bell', 'macos_hide_from_tasks', 'macos_menubar_title_max_length', 'macos_option_as_alt', @@ -575,6 +576,7 @@ class Options: listen_on: str = 'none' macos_colorspace: choices_for_macos_colorspace = 'srgb' macos_custom_beam_cursor: bool = False + macos_dock_badge_on_bell: bool = True macos_hide_from_tasks: bool = False macos_menubar_title_max_length: int = 0 macos_option_as_alt: int = 0 diff --git a/kitty/state.h b/kitty/state.h index 6170f2b3f..9a5c5b27a 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -98,6 +98,7 @@ typedef struct Options { bool sync_to_monitor; bool close_on_child_death; bool window_alert_on_bell; + bool macos_dock_badge_on_bell; bool debug_keyboard; bool allow_hyperlinks; struct { monotonic_t on_end, on_pause; } resize_debounce_time;