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 f2e3d128f25e89dc3c18a6ad20cb5b9c9a0154bc..e7b6bff1196a160667bf3b5cb96d1e94d5ea615d 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 c4647593e9ef668d2a8f9a91aeaec39d10fe96a9..0fbdbe1ca7fcd2c134f4c5a577089d2d07cd50c4 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 e23ed695846e7f6ef222a93813195940ebb0e203..d9ffb29c3913c61fb3238d1ebc1e50960dfb20c5 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 1b9b8ee042a2e8326931fbd61b0c6dc45af9e3d2..9e0d0618842c680855a0ae2a2379a3a379ae6dd2 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 0000000000000000000000000000000000000000..e8d32e7ed000104c6c861e15a78e34dcdf3329fa --- /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 +}