From 470ef1de0e5551183649df45b8118a63d9f44450 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 8 Jun 2026 20:08:07 -0700 Subject: [PATCH 1/2] fix(quickadd): return focus to the previous app when the popover hides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global ⌘' quick-add overlay is a borderless, transparent, always-on-top accessory window that winit hides with `Visible(false)`. That orders the window out visually but leaves heph-quickadd the *active* application — so after a capture (or Esc / toggle) keyboard focus never returns to the app the user was in, and the lingering overlay can keep intercepting clicks where it used to sit. Hide at the application level instead via `NSApplication.hide:`, which fully orders our windows out and activates the next app in line (the previously focused one). On re-show, `unhide:` clears that hidden flag before the existing viewport `Focus` command makes the field key again. Both are macOS-only no-ops elsewhere, wired through new `app_yield_focus`/`app_take_focus` helpers backed by objc2 / objc2-app-kit (unified to the 0.6/0.3 line global-hotkey already pulls). Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.lock | 2 + crates/heph-quickadd/Cargo.toml | 11 ++++- crates/heph-quickadd/src/app.rs | 43 +++++++++++++++++++ .../feature-quickadd-focus-return.bugfix.md | 1 + 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 docs/changelog.d/feature-quickadd-focus-return.bugfix.md diff --git a/Cargo.lock b/Cargo.lock index be8f974..cc9b3a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2237,6 +2237,8 @@ dependencies = [ "heph-core", "hephd", "libc", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", "serde_json", "winit", ] diff --git a/crates/heph-quickadd/Cargo.toml b/crates/heph-quickadd/Cargo.toml index 5b1889b..57bbb98 100644 --- a/crates/heph-quickadd/Cargo.toml +++ b/crates/heph-quickadd/Cargo.toml @@ -19,7 +19,16 @@ global-hotkey = "0.8" # macOS-only: winit for the accessory-mode activation policy (no Dock icon), # pinned to the same minor eframe carries so cargo unifies to one winit; libc -# for getppid() (orphan detection — self-exit when the supervising daemon dies). +# for getppid() (orphan detection — self-exit when the supervising daemon dies); +# objc2 + objc2-app-kit to hand keyboard focus back to the previously active app +# when the popover hides (NSApplication.hide:/unhide:). Pinned to the 0.6/0.3 +# line global-hotkey already pulls in, so cargo unifies to one copy. [target.'cfg(target_os = "macos")'.dependencies] winit = "0.30" libc = "0.2" +objc2 = "0.6" +objc2-app-kit = { version = "0.3", default-features = false, features = [ + "std", + "NSApplication", + "NSResponder", +] } diff --git a/crates/heph-quickadd/src/app.rs b/crates/heph-quickadd/src/app.rs index b08bf03..a334b22 100644 --- a/crates/heph-quickadd/src/app.rs +++ b/crates/heph-quickadd/src/app.rs @@ -226,6 +226,9 @@ impl QuickAdd { } fn show(&mut self, ctx: &egui::Context) { + // Undo the app-level hide from the previous `hide()` so we can take focus + // again (no-op the first time / off macOS). + app_take_focus(); self.visible = true; self.focus_pending = true; self.current_hint = random_hint(self.current_hint); @@ -256,6 +259,13 @@ impl QuickAdd { ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(egui::vec2(WIN_W, BASE_H))); self.win_h_applied = BASE_H; } + // Hand keyboard focus back to the app underneath us. winit's + // `Visible(false)` alone leaves *us* the active application, so focus + // never returns and the borderless always-on-top overlay can keep eating + // clicks where it used to sit. `NSApplication.hide:` orders our windows + // fully out and activates the next app in line — exactly the one the user + // was in (no-op off macOS). + app_yield_focus(); } /// Optimistic submit: hide now, create in the background. @@ -596,6 +606,39 @@ impl QuickAdd { } } +/// Hide the popover at the *application* level so macOS hands keyboard focus +/// back to the previously active app. `NSApplication.hide:` orders all our +/// windows out and activates the next app in line — the one the user was in — +/// which a plain winit `Visible(false)` does not do. No-op off macOS. +#[cfg(target_os = "macos")] +fn app_yield_focus() { + use objc2::MainThreadMarker; + use objc2_app_kit::NSApplication; + // eframe's `update` runs on the main thread, so this marker is always Some. + if let Some(mtm) = MainThreadMarker::new() { + NSApplication::sharedApplication(mtm).hide(None); + } +} + +#[cfg(not(target_os = "macos"))] +fn app_yield_focus() {} + +/// Undo [`app_yield_focus`]: clear the app-level hidden flag before re-showing, +/// so the window the viewport `Focus` command then makes key actually appears. +/// (`unhide:` also re-activates us; the per-window `Focus`/`Visible` viewport +/// commands do the rest.) No-op off macOS. +#[cfg(target_os = "macos")] +fn app_take_focus() { + use objc2::MainThreadMarker; + use objc2_app_kit::NSApplication; + if let Some(mtm) = MainThreadMarker::new() { + NSApplication::sharedApplication(mtm).unhide(None); + } +} + +#[cfg(not(target_os = "macos"))] +fn app_take_focus() {} + /// The current parent process id, for orphan detection. `None` off macOS (where /// hephd does not supervise a helper — there is no Aqua session to inherit). fn current_parent_pid() -> Option { diff --git a/docs/changelog.d/feature-quickadd-focus-return.bugfix.md b/docs/changelog.d/feature-quickadd-focus-return.bugfix.md new file mode 100644 index 0000000..6835eb5 --- /dev/null +++ b/docs/changelog.d/feature-quickadd-focus-return.bugfix.md @@ -0,0 +1 @@ +Quick-add popover (⌘'): hand keyboard focus back to the previously active app when it hides, and stop the (now invisible) overlay from intercepting clicks where it used to sit. From b34371af873f2d9a6ba3f097093b5efef22e07ef Mon Sep 17 00:00:00 2001 From: Forgejo Actions Date: Mon, 8 Jun 2026 20:24:38 -0700 Subject: [PATCH 2/2] Update changelog for v1.4.1 [skip ci] --- CHANGELOG.md | 8 ++++++++ docs/changelog.d/client-reconnect.bugfix.md | 1 - docs/changelog.d/feature-quickadd-focus-return.bugfix.md | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 docs/changelog.d/client-reconnect.bugfix.md delete mode 100644 docs/changelog.d/feature-quickadd-focus-return.bugfix.md diff --git a/CHANGELOG.md b/CHANGELOG.md index aa29354..1900ad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [v1.4.1] - 2026-06-08 + +### Bug Fixes + +- The `heph` CLI and `heph-tui` now survive a daemon restart. Previously the unix-socket client connected once and never reconnected, so an opt-in self-update or `heph daemon restart` left every subsequent call failing — `heph-tui` would sit on errors until relaunched. The client now reconnects on a dropped socket: a request that never went out is retried transparently, while a reply lost mid-request is surfaced (not silently retried) so a mutation is never double-applied. A long-running TUI self-heals on its next refresh tick. +- Quick-add popover (⌘'): hand keyboard focus back to the previously active app when it hides, and stop the (now invisible) overlay from intercepting clicks where it used to sit. + + ## [v1.4.0] - 2026-06-08 ### Features diff --git a/docs/changelog.d/client-reconnect.bugfix.md b/docs/changelog.d/client-reconnect.bugfix.md deleted file mode 100644 index ae987b8..0000000 --- a/docs/changelog.d/client-reconnect.bugfix.md +++ /dev/null @@ -1 +0,0 @@ -The `heph` CLI and `heph-tui` now survive a daemon restart. Previously the unix-socket client connected once and never reconnected, so an opt-in self-update or `heph daemon restart` left every subsequent call failing — `heph-tui` would sit on errors until relaunched. The client now reconnects on a dropped socket: a request that never went out is retried transparently, while a reply lost mid-request is surfaced (not silently retried) so a mutation is never double-applied. A long-running TUI self-heals on its next refresh tick. diff --git a/docs/changelog.d/feature-quickadd-focus-return.bugfix.md b/docs/changelog.d/feature-quickadd-focus-return.bugfix.md deleted file mode 100644 index 6835eb5..0000000 --- a/docs/changelog.d/feature-quickadd-focus-return.bugfix.md +++ /dev/null @@ -1 +0,0 @@ -Quick-add popover (⌘'): hand keyboard focus back to the previously active app when it hides, and stop the (now invisible) overlay from intercepting clicks where it used to sit.