macOS 上点关闭按钮改为把 ShellRS 隐藏到后台:连接、传输和端口转发照常运行,点 Dock 图标恢复窗口,⌘Q 才退出

This commit is contained in:
Xuz committed 2026-10-01 21:00:22 +08:00
1 parent bcf2db0413
commit ef2bd8c3a6
7 files changed
+77 -32

No files matched your search

+2
View File
@@ -4,6 +4,7 @@ mod actions;
mod app_icon;
mod assets;
mod paths;
mod window_hiding;
pub use actions::*;
pub use app_icon::show_logo_when_unbundled;
@@ -11,6 +12,7 @@ pub use assets::{AppAssets, CatalogIcon};
pub use paths::{
cli_endpoint, cli_socket_path, data_dir, database_path, keys_dir, settings_path, updates_dir,
};
pub use window_hiding::{bring_forward, hide_when_closed};
use gpui_kit::component::{Theme, dock::ToggleZoom};
use gpui_kit::*;
+35
View File
@@ -0,0 +1,35 @@
//! On macOS the window's close button hides ShellRS instead of closing it.
//!
//! ShellRS has one window, and the workspace in it owns everything that
//! runs: connections, transfers, port forwards, the external CLI. Closing
//! it would stop all of that yet leave the app in the Dock with no window,
//! and the Dock icon could not bring one back. So, like Mac terminals,
//! closing hides the app: the Dock icon, ⌘Tab or opening ShellRS again
//! shows the window as it was, and ⌘Q quits. Windows and Linux quit when
//! the window closes, as GPUI does there by default.
use gpui_kit::{App, Window};
/// Make the window's close button hide ShellRS. Nothing outside macOS.
#[cfg(target_os = "macos")]
pub fn hide_when_closed(window: &Window, cx: &App) {
window.on_window_should_close(cx, |_, cx| {
cx.hide();
false
});
}
#[cfg(not(target_os = "macos"))]
pub fn hide_when_closed(_: &Window, _: &App) {}
/// Show the window again, in front of other apps: when the Dock icon is
/// clicked, or ShellRS is opened again while it runs.
pub fn bring_forward(window: &mut Window, cx: &mut App) {
// Activating alone may leave a hidden app's windows hidden.
#[cfg(target_os = "macos")]
if let Some(main_thread) = objc2::MainThreadMarker::new() {
objc2_app_kit::NSApplication::sharedApplication(main_thread).unhide(None);
}
cx.activate(true);
window.activate_window();
}
+35 -30
View File
@@ -22,38 +22,43 @@ fn main() {
if shellrs::cli::activate_running_app(&shellrs::app::cli_socket_path()) {
return;
}
gpui_kit::application()
.with_assets(shellrs::app::AppAssets)
.run(|cx| {
shellrs::init(cx);
shellrs::app::show_logo_when_unbundled(cx);
cx.activate(true);
let app = gpui_kit::application().with_assets(shellrs::app::AppAssets);
// macOS: the Dock icon of a ShellRS whose window was closed, which only
// hid it (see `hide_when_closed`).
app.on_reopen(|cx| {
if let Some(window) = cx.windows().first().copied() {
window
.update(cx, |_, window, cx| shellrs::app::bring_forward(window, cx))
.ok();
}
});
app.run(|cx| {
shellrs::init(cx);
shellrs::app::show_logo_when_unbundled(cx);
cx.activate(true);
let options = shellrs::workspace::window_options(cx);
cx.spawn(async move |cx| {
cx.open_window(options, |window, cx| {
window.activate_window();
window.set_window_title("ShellRS");
let (store, problem) = open_store();
let store = cx.new(|_| store);
let (settings, settings_problem) = open_settings();
let settings = cx.new(|_| settings);
let workspace = cx
.new(|cx| shellrs::workspace::Workspace::new(store, settings, window, cx));
// Not pushed here: the window has no `Root` to show it yet.
for problem in [problem, settings_problem].into_iter().flatten() {
shellrs::workspace::notify_once_open(
Notification::error(problem),
window,
cx,
);
}
cx.new(|cx| Root::new(workspace, window, cx))
})
.expect("failed to open window");
let options = shellrs::workspace::window_options(cx);
cx.spawn(async move |cx| {
cx.open_window(options, |window, cx| {
window.activate_window();
window.set_window_title("ShellRS");
shellrs::app::hide_when_closed(window, cx);
let (store, problem) = open_store();
let store = cx.new(|_| store);
let (settings, settings_problem) = open_settings();
let settings = cx.new(|_| settings);
let workspace =
cx.new(|cx| shellrs::workspace::Workspace::new(store, settings, window, cx));
// Not pushed here: the window has no `Root` to show it yet.
for problem in [problem, settings_problem].into_iter().flatten() {
shellrs::workspace::notify_once_open(Notification::error(problem), window, cx);
}
cx.new(|cx| Root::new(workspace, window, cx))
})
.detach();
});
.expect("failed to open window");
})
.detach();
});
// On Windows the event loop ends and `run` returns: an update that is
// to be installed now starts its setup, which waits for this process.
shellrs::update::start_handed_over();
+1 -2
View File
@@ -251,8 +251,7 @@ impl Workspace {
.as_ref()
.is_some_and(|server| server.take_activation());
if asked {
cx.activate(true);
window.activate_window();
crate::app::bring_forward(window, cx);
}
});
if open.is_err() {