windows: Dock menu impl 2 (#26010)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
张小白 2025-03-06 20:40:34 +08:00 committed by GitHub
parent 84f4d2630f
commit 05df3d1bd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 163 additions and 62 deletions

View file

@ -217,29 +217,27 @@ fn main() {
let (open_listener, mut open_rx) = OpenListener::new();
let failed_single_instance_check =
if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
false
} else {
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
}
let failed_single_instance_check = if *db::ZED_STATELESS
|| *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
{
false
} else {
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
}
#[cfg(target_os = "windows")]
{
!crate::zed::windows_only_instance::check_single_instance(
open_listener.clone(),
args.foreground,
)
}
#[cfg(target_os = "windows")]
{
!crate::zed::windows_only_instance::check_single_instance(open_listener.clone(), &args)
}
#[cfg(target_os = "macos")]
{
use zed::mac_only_instance::*;
ensure_only_instance() != IsOnlyInstance::Yes
}
};
#[cfg(target_os = "macos")]
{
use zed::mac_only_instance::*;
ensure_only_instance() != IsOnlyInstance::Yes
}
};
if failed_single_instance_check {
println!("zed is already running");
return;
@ -643,6 +641,11 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
return;
}
if let Some(action_index) = request.dock_menu_action {
cx.perform_dock_menu_action(action_index);
return;
}
if let Some(connection_options) = request.ssh_connection {
cx.spawn(|mut cx| async move {
let paths_with_position =
@ -953,7 +956,14 @@ struct Args {
/// Run zed in the foreground, only used on Windows, to match the behavior of the behavior on macOS.
#[arg(long)]
#[cfg(target_os = "windows")]
#[arg(hide = true)]
foreground: bool,
/// The dock action to perform. This is used on Windows only.
#[arg(long)]
#[cfg(target_os = "windows")]
#[arg(hide = true)]
dock_action: Option<usize>,
}
#[derive(Clone, Debug)]

View file

@ -34,6 +34,7 @@ pub struct OpenRequest {
pub open_channel_notes: Vec<(u64, Option<String>)>,
pub join_channel: Option<u64>,
pub ssh_connection: Option<SshConnectionOptions>,
pub dock_menu_action: Option<usize>,
}
impl OpenRequest {
@ -42,6 +43,8 @@ impl OpenRequest {
for url in urls {
if let Some(server_name) = url.strip_prefix("zed-cli://") {
this.cli_connection = Some(connect_to_cli(server_name)?);
} else if let Some(action_index) = url.strip_prefix("zed-dock-action://") {
this.dock_menu_action = Some(action_index.parse()?);
} else if let Some(file) = url.strip_prefix("file://") {
this.parse_file_path(file)
} else if let Some(file) = url.strip_prefix("zed://file") {

View file

@ -1,7 +1,6 @@
use std::{sync::Arc, thread::JoinHandle};
use anyhow::Context;
use clap::Parser;
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
use parking_lot::Mutex;
use release_channel::app_identifier;
@ -26,23 +25,23 @@ use windows::{
use crate::{Args, OpenListener};
pub fn check_single_instance(opener: OpenListener, run_foreground: bool) -> bool {
pub fn check_single_instance(opener: OpenListener, args: &Args) -> bool {
unsafe {
CreateMutexW(
None,
false,
&HSTRING::from(format!("{}-Instance-Mutex", app_identifier())),
)
.expect("Unable to create instance sync event")
.expect("Unable to create instance mutex.")
};
let first_instance = unsafe { GetLastError() } != ERROR_ALREADY_EXISTS;
if first_instance {
// We are the first instance, listen for messages sent from other instances
std::thread::spawn(move || with_pipe(|url| opener.open_urls(vec![url])));
} else if !run_foreground {
} else if !args.foreground {
// We are not the first instance, send args to the first instance
send_args_to_instance().log_err();
send_args_to_instance(args).log_err();
}
first_instance
@ -95,31 +94,45 @@ fn retrieve_message_from_pipe_inner(pipe: HANDLE) -> anyhow::Result<String> {
}
// This part of code is mostly from crates/cli/src/main.rs
fn send_args_to_instance() -> anyhow::Result<()> {
let Args { paths_or_urls, .. } = Args::parse();
fn send_args_to_instance(args: &Args) -> anyhow::Result<()> {
if let Some(dock_menu_action_idx) = args.dock_action {
let url = format!("zed-dock-action://{}", dock_menu_action_idx);
return write_message_to_instance_pipe(url.as_bytes());
}
let (server, server_name) =
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
let url = format!("zed-cli://{server_name}");
let mut paths = vec![];
let mut urls = vec![];
for path in paths_or_urls.into_iter() {
match std::fs::canonicalize(&path) {
Ok(path) => paths.push(path.to_string_lossy().to_string()),
Err(error) => {
if path.starts_with("zed://")
|| path.starts_with("http://")
|| path.starts_with("https://")
|| path.starts_with("file://")
|| path.starts_with("ssh://")
{
urls.push(path);
} else {
log::error!("error parsing path argument: {}", error);
let request = {
let mut paths = vec![];
let mut urls = vec![];
for path in args.paths_or_urls.iter() {
match std::fs::canonicalize(&path) {
Ok(path) => paths.push(path.to_string_lossy().to_string()),
Err(error) => {
if path.starts_with("zed://")
|| path.starts_with("http://")
|| path.starts_with("https://")
|| path.starts_with("file://")
|| path.starts_with("ssh://")
{
urls.push(path.clone());
} else {
log::error!("error parsing path argument: {}", error);
}
}
}
}
}
CliRequest::Open {
paths,
urls,
wait: false,
open_new_workspace: None,
env: None,
}
};
let exit_status = Arc::new(Mutex::new(None));
let sender: JoinHandle<anyhow::Result<()>> = std::thread::spawn({
let exit_status = exit_status.clone();
@ -127,13 +140,7 @@ fn send_args_to_instance() -> anyhow::Result<()> {
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
let (tx, rx) = (handshake.requests, handshake.responses);
tx.send(CliRequest::Open {
paths,
urls,
wait: false,
open_new_workspace: None,
env: None,
})?;
tx.send(request)?;
while let Ok(response) = rx.recv() {
match response {
@ -150,6 +157,15 @@ fn send_args_to_instance() -> anyhow::Result<()> {
}
});
write_message_to_instance_pipe(url.as_bytes())?;
sender.join().unwrap()?;
if let Some(exit_status) = exit_status.lock().take() {
std::process::exit(exit_status);
}
Ok(())
}
fn write_message_to_instance_pipe(message: &[u8]) -> anyhow::Result<()> {
unsafe {
let pipe = CreateFileW(
&HSTRING::from(format!("\\\\.\\pipe\\{}-Named-Pipe", app_identifier())),
@ -160,14 +176,8 @@ fn send_args_to_instance() -> anyhow::Result<()> {
FILE_FLAGS_AND_ATTRIBUTES::default(),
None,
)?;
let message = url.as_bytes();
let mut bytes_written = 0;
WriteFile(pipe, Some(message), Some(&mut bytes_written), None)?;
WriteFile(pipe, Some(message), None, None)?;
CloseHandle(pipe)?;
}
sender.join().unwrap()?;
if let Some(exit_status) = exit_status.lock().take() {
std::process::exit(exit_status);
}
Ok(())
}