From 3c53832141dac7ae67949e659086d31aafc8a81f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Thu, 29 Aug 2024 10:26:24 +0800 Subject: [PATCH] windows: Implement single instance (#15371) This PR implements a single instance mechanism using the `CreateEventW` function to create a mutex. If the identifier name begins with `Local`, the single instance applies only to processes under the same user. If the identifier begins with `Global`, it applies to all users. Additionally, I was thinking that perhaps we should integrate the single instance functionality into `gpui`. I believe applications developed using `gpui` would benefit from this feature. Furthermore, incorporating the single instance implementation into `gpui` would facilitate the `set_dock_menu` functionality. As I mentioned in #12068, the implementation of `set_dock_menu` on Windows depends on the single instance feature. When a user clicks the "dock menu", Windows will open a new application instance. To achieve behavior similar to macOS, we need to prevent the new instance from launching and instead pass the parameters to the existing instance. Any advice and suggestions are welcome. https://github.com/user-attachments/assets/c46f7e92-4411-4fa9-830e-383798a9dd93 Release Notes: - N/A --- Cargo.lock | 1 + crates/zed/Cargo.toml | 7 +++-- crates/zed/src/main.rs | 11 +++++++- crates/zed/src/zed.rs | 4 ++- crates/zed/src/zed/single_instance.rs | 39 +++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 crates/zed/src/zed/single_instance.rs diff --git a/Cargo.lock b/Cargo.lock index f2e3d128f2..e7b6bff119 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14212,6 +14212,7 @@ dependencies = [ "uuid", "vim", "welcome", + "windows 0.58.0", "winresource", "workspace", "zed_actions", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c4647593e9..0fbdbe1ca7 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -67,7 +67,7 @@ log.workspace = true markdown_preview.workspace = true menu.workspace = true mimalloc = { version = "0.1", optional = true } -nix = {workspace = true, features = ["pthread", "signal"] } +nix = { workspace = true, features = ["pthread", "signal"] } node_runtime.workspace = true notifications.workspace = true outline.workspace = true @@ -99,7 +99,7 @@ tab_switcher.workspace = true supermaven.workspace = true task.workspace = true tasks_ui.workspace = true -time.workspace = true +time.workspace = true telemetry_events.workspace = true terminal_view.workspace = true theme.workspace = true @@ -114,6 +114,9 @@ welcome.workspace = true workspace.workspace = true zed_actions.workspace = true +[target.'cfg(target_os = "windows")'.dependencies] +windows.workspace = true + [target.'cfg(target_os = "windows")'.build-dependencies] winresource = "0.1" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e23ed69584..d9ffb29c39 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -318,6 +318,15 @@ fn init_ui( } fn main() { + #[cfg(target_os = "windows")] + { + use zed::single_instance::*; + if !check_single_instance() { + println!("zed is already running"); + return; + } + } + let start_time = std::time::Instant::now(); menu::init(); zed_actions::init(); @@ -360,7 +369,7 @@ fn main() { } } } - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "linux", target_os = "windows")))] { use zed::only_instance::*; if ensure_only_instance() != IsOnlyInstance::Yes { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1b9b8ee042..9e0d061884 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2,9 +2,11 @@ mod app_menus; pub mod inline_completion_registry; #[cfg(target_os = "linux")] pub(crate) mod linux_prompts; -#[cfg(not(target_os = "linux"))] +#[cfg(not(any(target_os = "linux", target_os = "windows")))] pub(crate) mod only_instance; mod open_listener; +#[cfg(target_os = "windows")] +pub(crate) mod single_instance; pub use app_menus::*; use assistant::PromptBuilder; diff --git a/crates/zed/src/zed/single_instance.rs b/crates/zed/src/zed/single_instance.rs new file mode 100644 index 0000000000..e8d32e7ed0 --- /dev/null +++ b/crates/zed/src/zed/single_instance.rs @@ -0,0 +1,39 @@ +use release_channel::ReleaseChannel; +use windows::{ + core::HSTRING, + Win32::{ + Foundation::{GetLastError, ERROR_ALREADY_EXISTS}, + System::Threading::CreateEventW, + }, +}; + +fn retrieve_app_instance_event_identifier() -> &'static str { + match *release_channel::RELEASE_CHANNEL { + ReleaseChannel::Dev => "Local\\Zed-Editor-Dev-Instance-Event", + ReleaseChannel::Nightly => "Local\\Zed-Editor-Nightly-Instance-Event", + ReleaseChannel::Preview => "Local\\Zed-Editor-Preview-Instance-Event", + ReleaseChannel::Stable => "Local\\Zed-Editor-Stable-Instance-Event", + } +} + +pub fn check_single_instance() -> bool { + if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev { + return true; + } + + check_single_instance_event() +} + +fn check_single_instance_event() -> bool { + unsafe { + CreateEventW( + None, + false, + false, + &HSTRING::from(retrieve_app_instance_event_identifier()), + ) + .expect("Unable to create instance sync event") + }; + let last_err = unsafe { GetLastError() }; + last_err != ERROR_ALREADY_EXISTS +}