diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a2c2affacd1ea1..79fab690dcbd53 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1317,6 +1317,16 @@ impl App { self.platform.unhide_other_apps(); } + /// Sets the label drawn on the application's dock icon, or clears it with + /// `None`. + /// + /// Only macOS implements it; elsewhere the call does nothing. A + /// menu-bar-only app (`ActivationPolicy::Accessory`) has no dock icon, so + /// the label has nowhere to appear. + pub fn set_dock_badge(&self, label: Option<&str>) { + self.platform.set_dock_badge(label); + } + /// Returns the list of currently active displays. pub fn displays(&self) -> Vec> { self.platform.displays() diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 90511beb5280c6..7d8b5cdface35d 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -143,6 +143,11 @@ pub trait Platform: 'static { fn hide_other_apps(&self); fn unhide_other_apps(&self); + /// Sets the label drawn on the application's dock icon, or clears it. + /// + /// Only macOS implements it; the default is a no-op. + fn set_dock_badge(&self, _label: Option<&str>) {} + fn displays(&self) -> Vec>; fn primary_display(&self) -> Option>; fn active_window(&self) -> Option; diff --git a/crates/gpui_macos/Cargo.toml b/crates/gpui_macos/Cargo.toml index ff10902a37c69a..b88878ddfd51b0 100644 --- a/crates/gpui_macos/Cargo.toml +++ b/crates/gpui_macos/Cargo.toml @@ -51,7 +51,7 @@ media.workspace = true metal.workspace = true objc.workspace = true objc2.workspace = true -objc2-app-kit.workspace = true +objc2-app-kit = { workspace = true, features = ["NSApplication", "NSDockTile"] } objc2-foundation.workspace = true objc2-user-notifications.workspace = true parking_lot.workspace = true diff --git a/crates/gpui_macos/src/dock.rs b/crates/gpui_macos/src/dock.rs new file mode 100644 index 00000000000000..997a1ed0c6de19 --- /dev/null +++ b/crates/gpui_macos/src/dock.rs @@ -0,0 +1,21 @@ +//! The dock icon's badge, the one label macOS draws outside the app's windows. +//! +//! The badge belongs to the dock tile, so it only exists while the app has a +//! dock icon: a menu-bar-only app (activation policy `Accessory`) has nothing +//! to draw it on and this becomes a no-op. + +use objc2::MainThreadMarker; +use objc2_app_kit::NSApplication; +use objc2_foundation::NSString; + +/// Sets the dock icon's badge, or clears it with `None`. +pub(crate) fn set_dock_badge(label: Option<&str>) { + let Some(mtm) = MainThreadMarker::new() else { + log::warn!("the dock badge must be set on the main thread"); + return; + }; + let label = label.map(NSString::from_str); + NSApplication::sharedApplication(mtm) + .dockTile() + .setBadgeLabel(label.as_deref()); +} diff --git a/crates/gpui_macos/src/gpui_macos.rs b/crates/gpui_macos/src/gpui_macos.rs index 41984dc9ce009c..819fa84f0d7781 100644 --- a/crates/gpui_macos/src/gpui_macos.rs +++ b/crates/gpui_macos/src/gpui_macos.rs @@ -7,6 +7,7 @@ mod dispatcher; mod display; mod display_link; +mod dock; mod events; mod keyboard; mod pasteboard; diff --git a/crates/gpui_macos/src/platform.rs b/crates/gpui_macos/src/platform.rs index 91dba604977357..6b9013d7d0f8f4 100644 --- a/crates/gpui_macos/src/platform.rs +++ b/crates/gpui_macos/src/platform.rs @@ -837,6 +837,10 @@ impl Platform for MacPlatform { } } + fn set_dock_badge(&self, label: Option<&str>) { + crate::dock::set_dock_badge(label); + } + fn primary_display(&self) -> Option> { Some(Rc::new(MacDisplay::primary())) }