mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
fix(macos): answer the Dock's reopen so a retired tty7 can come back
Closing the last window with the tray icon on retires tty7 to the tray: process alive, Dock icon up, nothing on screen. That state had no way back through the icon. macOS relaunching an already-running app arrives as `applicationShouldHandleReopen:hasVisibleWindows:`, gpui's delegate forwards it to a callback registered with `Application::on_reopen`, and tty7 registered none — so the click was a no-op, and the only ways back in were `⌘N`, the tray's "Show tty7", or quitting and relaunching. `windows::reopen` takes that callback, in the two shapes the state has: a window still registered is activated rather than doubled, and no window at all goes through the pathless-launch restore (`restore_target` + `open_at` + `announce_detached_at_launch`) — the same path the tray's windowless branch takes, so the workspace that retired is the one that returns and not a blank one beside it. `reopen_with` is the seam the tests drive, so a reopen that opens a second window beside the one on screen cannot pass. `Application::on_reopen` is registered beside `on_open_urls` in `main`, because it has to exist before `run` — `keymap::init` runs inside the loop — and the callback defers to the loop with `cx.spawn` rather than opening windows on AppKit's delegate stack, the shape `on_open_urls` already uses. `activate_window` is `makeKeyAndOrderFront:` on macOS. Reported from a macOS machine where a lid close and wake left the process frontmost with no window: `launchservicesd SETFRONT` at 23:51:40 with the process still reported `running-active-NotVisible`, and the layout only back after a quit and relaunch. The window itself being lost across display sleep → wake is not explained by this change and carries no guess-fix here: nothing in tty7 or in the pinned gpui hangs off display sleep or wake. cargo fmt --check; cargo check --locked -p tty7 --tests; cargo test --locked -p tty7 --bin tty7-app -- 1888 passed, 0 failed. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
co-authored by
CommandCodeBot
parent
66c42e48b7
commit
0499890f76
+18
@@ -638,6 +638,24 @@ fn main() {
|
||||
application.on_open_urls(move |urls| {
|
||||
let _ = external_open_tx.try_send(urls);
|
||||
});
|
||||
// macOS relaunching an app that is already running — the Dock icon, a
|
||||
// double-click on the bundle, `open -a tty7` — only reaches this process
|
||||
// as `applicationShouldHandleReopen:`, and gpui's delegate does nothing
|
||||
// with it unless a handler is registered. That is the one entrance a
|
||||
// tray-resident tty7 has: with `show_tray_icon` on, closing the last
|
||||
// window keeps the process and its Dock icon alive with nothing on
|
||||
// screen, and without this the icon's click was a no-op (the only ways
|
||||
// back in were ⌘N, the tray's "Show tty7", or quitting and relaunching).
|
||||
// Like `on_open_urls`, the hook lives on `Application`, so it cannot go
|
||||
// beside the other app-level handlers in `keymap::init`; and like that
|
||||
// path it defers to the loop rather than opening windows on AppKit's
|
||||
// delegate stack, which is also how Zed's own reopen handler runs.
|
||||
application.on_reopen(|cx| {
|
||||
cx.spawn(async move |cx| {
|
||||
let _ = cx.update(crate::ui::windows::reopen);
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
application.run(move |cx| {
|
||||
// gpui invokes this callback without an `App` context. Bridge it
|
||||
// back onto the application loop instead of touching UI state on
|
||||
|
||||
@@ -480,6 +480,30 @@ fn open_missing_cli_window_with(
|
||||
announce_detached_at_launch(cx, restore);
|
||||
}
|
||||
|
||||
/// Brings the UI back when macOS relaunches a tty7 that is already running.
|
||||
///
|
||||
/// AppKit only asks when it found no visible window, and that is a state
|
||||
/// tty7 lives in on purpose: closing the last window with the tray icon on
|
||||
/// retires to the tray, process alive and Dock icon up. A window that is
|
||||
/// still there is activated rather than doubled — `activate_window` is
|
||||
/// `makeKeyAndOrderFront:`, which is what orders a window AppKit left behind
|
||||
/// back to the front. None at all gets the last layout back the way a
|
||||
/// pathless launch and the tray's windowless path do, so the workspace that
|
||||
/// retired is the one that returns and not a blank one beside it.
|
||||
pub fn reopen(cx: &mut App) {
|
||||
reopen_with(cx, open_at);
|
||||
}
|
||||
|
||||
fn reopen_with(
|
||||
cx: &mut App,
|
||||
open: impl FnOnce(&mut App, Option<WorkspaceId>, Option<std::path::PathBuf>),
|
||||
) {
|
||||
match WindowRegistry::most_recent(cx) {
|
||||
Some(workspace) => activate(cx, workspace),
|
||||
None => open_missing_cli_window_with(cx, None, open),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_menu(cx: &mut App) {
|
||||
crate::ui::theme::set_menus(cx);
|
||||
}
|
||||
@@ -988,6 +1012,65 @@ mod tests {
|
||||
assert_eq!(opened, Some((None, None)));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_reopen_with_no_window_up_restores_the_workspace_that_retired(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
// The Dock icon after the last window retired to the tray: the
|
||||
// process is alive with nothing on screen, and the click has to bring
|
||||
// back the layout that was there, not mint a blank workspace beside it.
|
||||
let view = WindowView::default();
|
||||
let restored = view.id;
|
||||
let mut opened = None;
|
||||
|
||||
cx.update(|cx| {
|
||||
WindowRegistry::init(cx);
|
||||
WorkspaceStore::install_for_test(
|
||||
cx,
|
||||
WindowViews {
|
||||
views: vec![view],
|
||||
active: Some(restored),
|
||||
},
|
||||
);
|
||||
reopen_with(cx, |_, workspace, path| {
|
||||
opened = Some((workspace, path));
|
||||
});
|
||||
});
|
||||
|
||||
assert_eq!(opened, Some((Some(restored), None)));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_reopen_with_a_window_up_activates_it_instead_of_opening_another(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
// AppKit also asks while a window is still there but off screen. That
|
||||
// window is the thing to activate; a second one would take the
|
||||
// restore away from it.
|
||||
use gpui::VisualContext as _;
|
||||
|
||||
let (app, mut vcx) = crate::ui::app::test_window::harness(cx);
|
||||
let handle = vcx.window_handle();
|
||||
app.update_in(&mut vcx, |_, _, cx| {
|
||||
WindowRegistry::init(cx);
|
||||
let view = WindowView::default();
|
||||
let open = view.id;
|
||||
WorkspaceStore::install_for_test(
|
||||
cx,
|
||||
WindowViews {
|
||||
views: vec![view],
|
||||
active: Some(open),
|
||||
},
|
||||
);
|
||||
WindowRegistry::register(cx, open, handle, app.downgrade());
|
||||
|
||||
reopen_with(cx, |_, workspace, _| {
|
||||
panic!("a reopen with a window up opened another for {workspace:?}");
|
||||
});
|
||||
assert_eq!(WindowRegistry::count(cx), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_request_carrying_a_path_restores_the_layout_and_brings_the_path_along(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
|
||||
Reference in New Issue
Block a user