notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit f2cbab62d8272d385cf5381078ea43842596f773
parent aa22d1916da51fa80e0e79a07018b26ca494e135
Author: William Casarin <jb55@jb55.com>
Date:   Mon,  9 Feb 2026 16:09:56 -0800

dave: use NSApplication activate to bring app to front on macOS

ViewportCommand::Focus alone does not reliably bring the app window
in front of other applications on macOS. Add a direct call to
NSApplication::activate via objc2-app-kit to ensure the app
actually comes to the foreground when auto-steal focus triggers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Diffstat:
MCargo.lock | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcrates/notedeck_dave/Cargo.toml | 4++++
Mcrates/notedeck_dave/src/lib.rs | 24++++++++++++++++++++++--
3 files changed, 83 insertions(+), 9 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -4151,6 +4151,8 @@ dependencies = [ "nostrdb", "notedeck", "notedeck_ui", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", "rand 0.9.2", "rfd", "serde", @@ -4367,10 +4369,10 @@ dependencies = [ "block2 0.5.1", "libc", "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -4381,10 +4383,15 @@ checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.9.1", "block2 0.6.1", + "libc", "objc2 0.6.1", + "objc2-cloud-kit 0.3.1", + "objc2-core-data 0.3.1", "objc2-core-foundation", "objc2-core-graphics", + "objc2-core-image 0.3.1", "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", ] [[package]] @@ -4401,6 +4408,17 @@ dependencies = [ ] [[package]] +name = "objc2-cloud-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] name = "objc2-contacts" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4424,6 +4442,17 @@ dependencies = [ ] [[package]] +name = "objc2-core-data" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] name = "objc2-core-foundation" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4460,6 +4489,16 @@ dependencies = [ ] [[package]] +name = "objc2-core-image" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +dependencies = [ + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] name = "objc2-core-location" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4551,6 +4590,17 @@ dependencies = [ ] [[package]] +name = "objc2-quartz-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] name = "objc2-symbols" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4569,13 +4619,13 @@ dependencies = [ "bitflags 2.9.1", "block2 0.5.1", "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", + "objc2-cloud-kit 0.2.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", diff --git a/crates/notedeck_dave/Cargo.toml b/crates/notedeck_dave/Cargo.toml @@ -33,6 +33,10 @@ dirs = "5" [target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies] rfd = { workspace = true } +[target.'cfg(target_os = "macos")'.dependencies] +objc2 = "0.6.1" +objc2-app-kit = { version = "0.3.1", features = ["NSApplication", "NSResponder", "NSRunningApplication"] } + [dev-dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros", "test-util"] } diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -908,8 +908,7 @@ impl notedeck::App for Dave { // Raise the OS window when auto-steal switches to a NeedsInput session if stole_focus { - ui.ctx() - .send_viewport_cmd(egui::ViewportCommand::Focus); + activate_app(ui.ctx()); } // Render UI and handle actions @@ -927,3 +926,24 @@ impl notedeck::App for Dave { AppResponse::action(app_action) } } + +/// Bring the application to the front. +/// +/// On macOS, egui's ViewportCommand::Focus focuses the window but doesn't +/// always activate the app (bring it in front of other apps), so we also +/// call NSApplication::activateIgnoringOtherApps. +fn activate_app(ctx: &egui::Context) { + ctx.send_viewport_cmd(egui::ViewportCommand::Focus); + + #[cfg(target_os = "macos")] + { + use objc2::MainThreadMarker; + use objc2_app_kit::NSApplication; + + // Safety: UI update runs on the main thread + if let Some(mtm) = MainThreadMarker::new() { + let app = NSApplication::sharedApplication(mtm); + unsafe { app.activate() }; + } + } +}