Detailed changes
@@ -1182,6 +1182,18 @@ dependencies = [
"workspace-hack",
]
+[[package]]
+name = "auto_update_helper"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "log",
+ "simplelog",
+ "windows 0.61.1",
+ "winresource",
+ "workspace-hack",
+]
+
[[package]]
name = "auto_update_ui"
version = "0.1.0"
@@ -17767,6 +17779,8 @@ dependencies = [
"wasmtime-cranelift",
"wasmtime-environ",
"winapi",
+ "windows-core 0.61.0",
+ "windows-numerics",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
@@ -15,6 +15,7 @@ members = [
"crates/assistant_tools",
"crates/audio",
"crates/auto_update",
+ "crates/auto_update_helper",
"crates/auto_update_ui",
"crates/aws_http_client",
"crates/bedrock",
@@ -222,6 +223,7 @@ assistant_tool = { path = "crates/assistant_tool" }
assistant_tools = { path = "crates/assistant_tools" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
+auto_update_helper = { path = "crates/auto_update_helper" }
auto_update_ui = { path = "crates/auto_update_ui" }
aws_http_client = { path = "crates/aws_http_client" }
bedrock = { path = "crates/bedrock" }
@@ -782,4 +784,12 @@ let_underscore_future = "allow"
too_many_arguments = "allow"
[workspace.metadata.cargo-machete]
-ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme", "workspace-hack"]
+ignored = [
+ "bindgen",
+ "cbindgen",
+ "prost_build",
+ "serde",
+ "component",
+ "linkme",
+ "workspace-hack",
+]
@@ -27,6 +27,8 @@ serde_json.workspace = true
settings.workspace = true
smol.workspace = true
tempfile.workspace = true
-which.workspace = true
workspace.workspace = true
workspace-hack.workspace = true
+
+[target.'cfg(not(target_os = "windows"))'.dependencies]
+which.workspace = true
@@ -23,7 +23,6 @@ use std::{
sync::Arc,
time::Duration,
};
-use which::which;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
@@ -63,7 +62,7 @@ pub struct AutoUpdater {
pending_poll: Option<Task<Option<()>>>,
}
-#[derive(Deserialize)]
+#[derive(Deserialize, Debug)]
pub struct JsonRelease {
pub version: String,
pub url: String,
@@ -237,6 +236,46 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> {
None
}
+#[cfg(not(target_os = "windows"))]
+struct InstallerDir(tempfile::TempDir);
+
+#[cfg(not(target_os = "windows"))]
+impl InstallerDir {
+ async fn new() -> Result<Self> {
+ Ok(Self(
+ tempfile::Builder::new()
+ .prefix("zed-auto-update")
+ .tempdir()?,
+ ))
+ }
+
+ fn path(&self) -> &Path {
+ self.0.path()
+ }
+}
+
+#[cfg(target_os = "windows")]
+struct InstallerDir(PathBuf);
+
+#[cfg(target_os = "windows")]
+impl InstallerDir {
+ async fn new() -> Result<Self> {
+ let installer_dir = std::env::current_exe()?
+ .parent()
+ .context("No parent dir for Zed.exe")?
+ .join("updates");
+ if smol::fs::metadata(&installer_dir).await.is_ok() {
+ smol::fs::remove_dir_all(&installer_dir).await?;
+ }
+ smol::fs::create_dir(&installer_dir).await?;
+ Ok(Self(installer_dir))
+ }
+
+ fn path(&self) -> &Path {
+ self.0.as_path()
+ }
+}
+
impl AutoUpdater {
pub fn get(cx: &mut App) -> Option<Entity<Self>> {
cx.default_global::<GlobalAutoUpdate>().0.clone()
@@ -469,22 +508,21 @@ impl AutoUpdater {
cx.notify();
})?;
- let temp_dir = tempfile::Builder::new()
- .prefix("zed-auto-update")
- .tempdir()?;
-
+ let installer_dir = InstallerDir::new().await?;
let filename = match OS {
"macos" => Ok("Zed.dmg"),
"linux" => Ok("zed.tar.gz"),
+ "windows" => Ok("ZedUpdateInstaller.exe"),
_ => Err(anyhow!("not supported: {:?}", OS)),
}?;
+ #[cfg(not(target_os = "windows"))]
anyhow::ensure!(
- which("rsync").is_ok(),
+ which::which("rsync").is_ok(),
"Aborting. Could not find rsync which is required for auto-updates."
);
- let downloaded_asset = temp_dir.path().join(filename);
+ let downloaded_asset = installer_dir.path().join(filename);
download_release(&downloaded_asset, release, client, &cx).await?;
this.update(&mut cx, |this, cx| {
@@ -493,8 +531,9 @@ impl AutoUpdater {
})?;
let binary_path = match OS {
- "macos" => install_release_macos(&temp_dir, downloaded_asset, &cx).await,
- "linux" => install_release_linux(&temp_dir, downloaded_asset, &cx).await,
+ "macos" => install_release_macos(&installer_dir, downloaded_asset, &cx).await,
+ "linux" => install_release_linux(&installer_dir, downloaded_asset, &cx).await,
+ "windows" => install_release_windows(downloaded_asset).await,
_ => Err(anyhow!("not supported: {:?}", OS)),
}?;
@@ -629,7 +668,7 @@ async fn download_release(
}
async fn install_release_linux(
- temp_dir: &tempfile::TempDir,
+ temp_dir: &InstallerDir,
downloaded_tar_gz: PathBuf,
cx: &AsyncApp,
) -> Result<PathBuf> {
@@ -696,7 +735,7 @@ async fn install_release_linux(
}
async fn install_release_macos(
- temp_dir: &tempfile::TempDir,
+ temp_dir: &InstallerDir,
downloaded_dmg: PathBuf,
cx: &AsyncApp,
) -> Result<PathBuf> {
@@ -743,3 +782,41 @@ async fn install_release_macos(
Ok(running_app_path)
}
+
+async fn install_release_windows(downloaded_installer: PathBuf) -> Result<PathBuf> {
+ let output = Command::new(downloaded_installer)
+ .arg("/verysilent")
+ .arg("/update=true")
+ .arg("!desktopicon")
+ .arg("!quicklaunchicon")
+ .output()
+ .await?;
+ anyhow::ensure!(
+ output.status.success(),
+ "failed to start installer: {:?}",
+ String::from_utf8_lossy(&output.stderr)
+ );
+ Ok(std::env::current_exe()?)
+}
+
+pub fn check_pending_installation() -> bool {
+ let Some(installer_path) = std::env::current_exe()
+ .ok()
+ .and_then(|p| p.parent().map(|p| p.join("updates")))
+ else {
+ return false;
+ };
+
+ // The installer will create a flag file after it finishes updating
+ let flag_file = installer_path.join("versions.txt");
+ if flag_file.exists() {
+ if let Some(helper) = installer_path
+ .parent()
+ .map(|p| p.join("tools\\auto_update_helper.exe"))
+ {
+ let _ = std::process::Command::new(helper).spawn();
+ return true;
+ }
+ }
+ false
+}
@@ -0,0 +1,29 @@
+[package]
+name = "auto_update_helper"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[[bin]]
+name = "auto_update_helper"
+path = "src/auto_update_helper.rs"
+doctest = false
+
+[dependencies]
+anyhow.workspace = true
+log.workspace = true
+simplelog.workspace = true
+workspace-hack.workspace = true
+
+[target.'cfg(target_os = "windows")'.dependencies]
+windows.workspace = true
+
+[target.'cfg(target_os = "windows")'.build-dependencies]
+winresource = "0.1"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-pc-windows-msvc"]
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,15 @@
+fn main() {
+ #[cfg(target_os = "windows")]
+ {
+ println!("cargo:rerun-if-changed=manifest.xml");
+
+ let mut res = winresource::WindowsResource::new();
+ res.set_manifest_file("manifest.xml");
+ res.set_icon("app-icon.ico");
+
+ if let Err(e) = res.compile() {
+ eprintln!("{}", e);
+ std::process::exit(1);
+ }
+ }
+}
@@ -0,0 +1,16 @@
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <asmv3:application>
+ <asmv3:windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+ </asmv3:windowsSettings>
+ </asmv3:application>
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity type='win32'
+ name='Microsoft.Windows.Common-Controls'
+ version='6.0.0.0' processorArchitecture='*'
+ publicKeyToken='6595b64144ccf1df' />
+ </dependentAssembly>
+ </dependency>
+</assembly>
@@ -0,0 +1,94 @@
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+#[cfg(target_os = "windows")]
+mod dialog;
+#[cfg(target_os = "windows")]
+mod updater;
+
+#[cfg(target_os = "windows")]
+fn main() {
+ if let Err(e) = windows_impl::run() {
+ log::error!("Error: Zed update failed, {:?}", e);
+ windows_impl::show_error(format!("Error: {:?}", e));
+ }
+}
+
+#[cfg(not(target_os = "windows"))]
+fn main() {}
+
+#[cfg(target_os = "windows")]
+mod windows_impl {
+ use std::path::Path;
+
+ use super::dialog::create_dialog_window;
+ use super::updater::perform_update;
+ use anyhow::{Context, Result};
+ use windows::{
+ Win32::{
+ Foundation::{HWND, LPARAM, WPARAM},
+ UI::WindowsAndMessaging::{
+ DispatchMessageW, GetMessageW, MB_ICONERROR, MB_SYSTEMMODAL, MSG, MessageBoxW,
+ PostMessageW, WM_USER,
+ },
+ },
+ core::HSTRING,
+ };
+
+ pub(crate) const WM_JOB_UPDATED: u32 = WM_USER + 1;
+ pub(crate) const WM_TERMINATE: u32 = WM_USER + 2;
+
+ pub(crate) fn run() -> Result<()> {
+ let helper_dir = std::env::current_exe()?
+ .parent()
+ .context("No parent directory")?
+ .to_path_buf();
+ init_log(&helper_dir)?;
+ let app_dir = helper_dir
+ .parent()
+ .context("No parent directory")?
+ .to_path_buf();
+
+ log::info!("======= Starting Zed update =======");
+ let (tx, rx) = std::sync::mpsc::channel();
+ let hwnd = create_dialog_window(rx)?.0 as isize;
+ std::thread::spawn(move || {
+ let result = perform_update(app_dir.as_path(), Some(hwnd));
+ tx.send(result).ok();
+ unsafe { PostMessageW(Some(HWND(hwnd as _)), WM_TERMINATE, WPARAM(0), LPARAM(0)) }.ok();
+ });
+ unsafe {
+ let mut message = MSG::default();
+ while GetMessageW(&mut message, None, 0, 0).as_bool() {
+ DispatchMessageW(&message);
+ }
+ }
+ Ok(())
+ }
+
+ fn init_log(helper_dir: &Path) -> Result<()> {
+ simplelog::WriteLogger::init(
+ simplelog::LevelFilter::Info,
+ simplelog::Config::default(),
+ std::fs::File::options()
+ .append(true)
+ .create(true)
+ .open(helper_dir.join("auto_update_helper.log"))?,
+ )?;
+ Ok(())
+ }
+
+ pub(crate) fn show_error(mut content: String) {
+ if content.len() > 600 {
+ content.truncate(600);
+ content.push_str("...\n");
+ }
+ let _ = unsafe {
+ MessageBoxW(
+ None,
+ &HSTRING::from(content),
+ windows::core::w!("Error: Zed update failed."),
+ MB_ICONERROR | MB_SYSTEMMODAL,
+ )
+ };
+ }
+}
@@ -0,0 +1,236 @@
+use std::{cell::RefCell, sync::mpsc::Receiver};
+
+use anyhow::{Context as _, Result};
+use windows::{
+ Win32::{
+ Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM},
+ Graphics::Gdi::{
+ BeginPaint, CLEARTYPE_QUALITY, CLIP_DEFAULT_PRECIS, CreateFontW, DEFAULT_CHARSET,
+ DeleteObject, EndPaint, FW_NORMAL, LOGFONTW, OUT_TT_ONLY_PRECIS, PAINTSTRUCT,
+ ReleaseDC, SelectObject, TextOutW,
+ },
+ System::LibraryLoader::GetModuleHandleW,
+ UI::{
+ Controls::{PBM_SETRANGE, PBM_SETSTEP, PBM_STEPIT, PROGRESS_CLASS},
+ WindowsAndMessaging::{
+ CREATESTRUCTW, CS_HREDRAW, CS_VREDRAW, CreateWindowExW, DefWindowProcW,
+ GWLP_USERDATA, GetDesktopWindow, GetWindowLongPtrW, GetWindowRect, HICON,
+ IMAGE_ICON, LR_DEFAULTSIZE, LR_SHARED, LoadImageW, PostQuitMessage, RegisterClassW,
+ SPI_GETICONTITLELOGFONT, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, SendMessageW,
+ SetWindowLongPtrW, SystemParametersInfoW, WINDOW_EX_STYLE, WM_CLOSE, WM_CREATE,
+ WM_DESTROY, WM_NCCREATE, WM_PAINT, WNDCLASSW, WS_CAPTION, WS_CHILD, WS_EX_TOPMOST,
+ WS_POPUP, WS_VISIBLE,
+ },
+ },
+ },
+ core::HSTRING,
+};
+
+use crate::{
+ updater::JOBS,
+ windows_impl::{WM_JOB_UPDATED, WM_TERMINATE, show_error},
+};
+
+#[repr(C)]
+#[derive(Debug)]
+struct DialogInfo {
+ rx: Receiver<Result<()>>,
+ progress_bar: isize,
+}
+
+pub(crate) fn create_dialog_window(receiver: Receiver<Result<()>>) -> Result<HWND> {
+ unsafe {
+ let class_name = windows::core::w!("Zed-Auto-Updater-Dialog-Class");
+ let module = GetModuleHandleW(None).context("unable to get module handle")?;
+ let handle = LoadImageW(
+ Some(module.into()),
+ windows::core::PCWSTR(1 as _),
+ IMAGE_ICON,
+ 0,
+ 0,
+ LR_DEFAULTSIZE | LR_SHARED,
+ )
+ .context("unable to load icon file")?;
+ let wc = WNDCLASSW {
+ lpfnWndProc: Some(wnd_proc),
+ lpszClassName: class_name,
+ style: CS_HREDRAW | CS_VREDRAW,
+ hIcon: HICON(handle.0),
+ ..Default::default()
+ };
+ RegisterClassW(&wc);
+ let mut rect = RECT::default();
+ GetWindowRect(GetDesktopWindow(), &mut rect)
+ .context("unable to get desktop window rect")?;
+ let width = 400;
+ let height = 150;
+ let info = Box::new(RefCell::new(DialogInfo {
+ rx: receiver,
+ progress_bar: 0,
+ }));
+
+ let hwnd = CreateWindowExW(
+ WS_EX_TOPMOST,
+ class_name,
+ windows::core::w!("Zed Editor"),
+ WS_VISIBLE | WS_POPUP | WS_CAPTION,
+ rect.right / 2 - width / 2,
+ rect.bottom / 2 - height / 2,
+ width,
+ height,
+ None,
+ None,
+ None,
+ Some(Box::into_raw(info) as _),
+ )
+ .context("unable to create dialog window")?;
+ Ok(hwnd)
+ }
+}
+
+macro_rules! return_if_failed {
+ ($e:expr) => {
+ match $e {
+ Ok(v) => v,
+ Err(e) => {
+ return LRESULT(e.code().0 as _);
+ }
+ }
+ };
+}
+
+macro_rules! make_lparam {
+ ($l:expr, $h:expr) => {
+ LPARAM(($l as u32 | ($h as u32) << 16) as isize)
+ };
+}
+
+unsafe extern "system" fn wnd_proc(
+ hwnd: HWND,
+ msg: u32,
+ wparam: WPARAM,
+ lparam: LPARAM,
+) -> LRESULT {
+ match msg {
+ WM_NCCREATE => unsafe {
+ let create_struct = lparam.0 as *const CREATESTRUCTW;
+ let info = (*create_struct).lpCreateParams as *mut RefCell<DialogInfo>;
+ let info = Box::from_raw(info);
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, Box::into_raw(info) as _);
+ DefWindowProcW(hwnd, msg, wparam, lparam)
+ },
+ WM_CREATE => unsafe {
+ // Create progress bar
+ let mut rect = RECT::default();
+ return_if_failed!(GetWindowRect(hwnd, &mut rect));
+ let progress_bar = return_if_failed!(CreateWindowExW(
+ WINDOW_EX_STYLE(0),
+ PROGRESS_CLASS,
+ None,
+ WS_CHILD | WS_VISIBLE,
+ 20,
+ 50,
+ 340,
+ 35,
+ Some(hwnd),
+ None,
+ None,
+ None,
+ ));
+ SendMessageW(
+ progress_bar,
+ PBM_SETRANGE,
+ None,
+ Some(make_lparam!(0, JOBS.len() * 10)),
+ );
+ SendMessageW(progress_bar, PBM_SETSTEP, Some(WPARAM(10)), None);
+ with_dialog_data(hwnd, |data| {
+ data.borrow_mut().progress_bar = progress_bar.0 as isize
+ });
+ LRESULT(0)
+ },
+ WM_PAINT => unsafe {
+ let mut ps = PAINTSTRUCT::default();
+ let hdc = BeginPaint(hwnd, &mut ps);
+
+ let font_name = get_system_ui_font_name();
+ let font = CreateFontW(
+ 24,
+ 0,
+ 0,
+ 0,
+ FW_NORMAL.0 as _,
+ 0,
+ 0,
+ 0,
+ DEFAULT_CHARSET,
+ OUT_TT_ONLY_PRECIS,
+ CLIP_DEFAULT_PRECIS,
+ CLEARTYPE_QUALITY,
+ 0,
+ &HSTRING::from(font_name),
+ );
+ let temp = SelectObject(hdc, font.into());
+ let string = HSTRING::from("Zed Editor is updating...");
+ return_if_failed!(TextOutW(hdc, 20, 15, &string).ok());
+ return_if_failed!(DeleteObject(temp).ok());
+
+ return_if_failed!(EndPaint(hwnd, &ps).ok());
+ ReleaseDC(Some(hwnd), hdc);
+
+ LRESULT(0)
+ },
+ WM_JOB_UPDATED => with_dialog_data(hwnd, |data| {
+ let progress_bar = data.borrow().progress_bar;
+ unsafe { SendMessageW(HWND(progress_bar as _), PBM_STEPIT, None, None) }
+ }),
+ WM_TERMINATE => {
+ with_dialog_data(hwnd, |data| {
+ if let Ok(result) = data.borrow_mut().rx.recv() {
+ if let Err(e) = result {
+ log::error!("Failed to update Zed: {:?}", e);
+ show_error(format!("Error: {:?}", e));
+ }
+ }
+ });
+ unsafe { PostQuitMessage(0) };
+ LRESULT(0)
+ }
+ WM_CLOSE => LRESULT(0), // Prevent user occasionally closing the window
+ WM_DESTROY => {
+ unsafe { PostQuitMessage(0) };
+ LRESULT(0)
+ }
+ _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) },
+ }
+}
+
+fn with_dialog_data<F, T>(hwnd: HWND, f: F) -> T
+where
+ F: FnOnce(&RefCell<DialogInfo>) -> T,
+{
+ let raw = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut RefCell<DialogInfo> };
+ let data = unsafe { Box::from_raw(raw) };
+ let result = f(data.as_ref());
+ unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, Box::into_raw(data) as _) };
+ result
+}
+
+fn get_system_ui_font_name() -> String {
+ unsafe {
+ let mut info: LOGFONTW = std::mem::zeroed();
+ if SystemParametersInfoW(
+ SPI_GETICONTITLELOGFONT,
+ std::mem::size_of::<LOGFONTW>() as u32,
+ Some(&mut info as *mut _ as _),
+ SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
+ )
+ .is_ok()
+ {
+ let font_name = String::from_utf16_lossy(&info.lfFaceName);
+ font_name.trim_matches(char::from(0)).to_owned()
+ } else {
+ "MS Shell Dlg".to_owned()
+ }
+ }
+}
@@ -0,0 +1,171 @@
+use std::{
+ os::windows::process::CommandExt,
+ path::Path,
+ time::{Duration, Instant},
+};
+
+use anyhow::{Context, Result};
+use windows::Win32::{
+ Foundation::{HWND, LPARAM, WPARAM},
+ System::Threading::CREATE_NEW_PROCESS_GROUP,
+ UI::WindowsAndMessaging::PostMessageW,
+};
+
+use crate::windows_impl::WM_JOB_UPDATED;
+
+type Job = fn(&Path) -> Result<()>;
+
+#[cfg(not(test))]
+pub(crate) const JOBS: [Job; 6] = [
+ // Delete old files
+ |app_dir| {
+ let zed_executable = app_dir.join("Zed.exe");
+ log::info!("Removing old file: {}", zed_executable.display());
+ std::fs::remove_file(&zed_executable).context(format!(
+ "Failed to remove old file {}",
+ zed_executable.display()
+ ))
+ },
+ |app_dir| {
+ let zed_cli = app_dir.join("bin\\zed.exe");
+ log::info!("Removing old file: {}", zed_cli.display());
+ std::fs::remove_file(&zed_cli)
+ .context(format!("Failed to remove old file {}", zed_cli.display()))
+ },
+ // Copy new files
+ |app_dir| {
+ let zed_executable_source = app_dir.join("install\\Zed.exe");
+ let zed_executable_dest = app_dir.join("Zed.exe");
+ log::info!(
+ "Copying new file {} to {}",
+ zed_executable_source.display(),
+ zed_executable_dest.display()
+ );
+ std::fs::copy(&zed_executable_source, &zed_executable_dest)
+ .map(|_| ())
+ .context(format!(
+ "Failed to copy new file {} to {}",
+ zed_executable_source.display(),
+ zed_executable_dest.display()
+ ))
+ },
+ |app_dir| {
+ let zed_cli_source = app_dir.join("install\\bin\\zed.exe");
+ let zed_cli_dest = app_dir.join("bin\\zed.exe");
+ log::info!(
+ "Copying new file {} to {}",
+ zed_cli_source.display(),
+ zed_cli_dest.display()
+ );
+ std::fs::copy(&zed_cli_source, &zed_cli_dest)
+ .map(|_| ())
+ .context(format!(
+ "Failed to copy new file {} to {}",
+ zed_cli_source.display(),
+ zed_cli_dest.display()
+ ))
+ },
+ // Clean up installer folder and updates folder
+ |app_dir| {
+ let updates_folder = app_dir.join("updates");
+ log::info!("Cleaning up: {}", updates_folder.display());
+ std::fs::remove_dir_all(&updates_folder).context(format!(
+ "Failed to remove updates folder {}",
+ updates_folder.display()
+ ))
+ },
+ |app_dir| {
+ let installer_folder = app_dir.join("install");
+ log::info!("Cleaning up: {}", installer_folder.display());
+ std::fs::remove_dir_all(&installer_folder).context(format!(
+ "Failed to remove installer folder {}",
+ installer_folder.display()
+ ))
+ },
+];
+
+#[cfg(test)]
+pub(crate) const JOBS: [Job; 2] = [
+ |_| {
+ std::thread::sleep(Duration::from_millis(1000));
+ if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
+ match config.as_str() {
+ "err" => Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Simulated error",
+ ))
+ .context("Anyhow!"),
+ _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
+ }
+ } else {
+ Ok(())
+ }
+ },
+ |_| {
+ std::thread::sleep(Duration::from_millis(1000));
+ if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
+ match config.as_str() {
+ "err" => Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Simulated error",
+ ))
+ .context("Anyhow!"),
+ _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
+ }
+ } else {
+ Ok(())
+ }
+ },
+];
+
+pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>) -> Result<()> {
+ let hwnd = hwnd.map(|ptr| HWND(ptr as _));
+
+ for job in JOBS.iter() {
+ let start = Instant::now();
+ loop {
+ if start.elapsed().as_secs() > 2 {
+ return Err(anyhow::anyhow!("Timed out"));
+ }
+ match (*job)(app_dir) {
+ Ok(_) => {
+ unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
+ break;
+ }
+ Err(err) => {
+ // Check if it's a "not found" error
+ let io_err = err.downcast_ref::<std::io::Error>().unwrap();
+ if io_err.kind() == std::io::ErrorKind::NotFound {
+ log::warn!("File or folder not found.");
+ unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
+ break;
+ }
+
+ log::error!("Operation failed: {}", err);
+ std::thread::sleep(Duration::from_millis(50));
+ }
+ }
+ }
+ }
+ let _ = std::process::Command::new(app_dir.join("Zed.exe"))
+ .creation_flags(CREATE_NEW_PROCESS_GROUP.0)
+ .spawn();
+ log::info!("Update completed successfully");
+ Ok(())
+}
+
+#[cfg(test)]
+mod test {
+ use super::perform_update;
+
+ #[test]
+ fn test_perform_update() {
+ let app_dir = std::path::Path::new("C:/");
+ assert!(perform_update(app_dir, None).is_ok());
+
+ // Simulate a timeout
+ unsafe { std::env::set_var("ZED_AUTO_UPDATE", "err") };
+ let ret = perform_update(app_dir, None);
+ assert!(ret.is_err_and(|e| e.to_string().as_str() == "Timed out"));
+ }
+}
@@ -168,6 +168,16 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
}
fn main() {
+ // Check if there is a pending installer
+ // If there is, run the installer and exit
+ // And we don't want to run the installer if we are not the first instance
+ #[cfg(target_os = "windows")]
+ let is_first_instance = crate::zed::windows_only_instance::is_first_instance();
+ #[cfg(target_os = "windows")]
+ if is_first_instance && auto_update::check_pending_installation() {
+ return;
+ }
+
let args = Args::parse();
// Set custom data directory.
@@ -236,27 +246,30 @@ 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)
- }
+ #[cfg(target_os = "windows")]
+ {
+ !crate::zed::windows_only_instance::handle_single_instance(
+ open_listener.clone(),
+ &args,
+ is_first_instance,
+ )
+ }
- #[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;
@@ -25,7 +25,7 @@ use windows::{
use crate::{Args, OpenListener};
-pub fn check_single_instance(opener: OpenListener, args: &Args) -> bool {
+pub fn is_first_instance() -> bool {
unsafe {
CreateMutexW(
None,
@@ -34,9 +34,11 @@ pub fn check_single_instance(opener: OpenListener, args: &Args) -> bool {
)
.expect("Unable to create instance mutex.")
};
- let first_instance = unsafe { GetLastError() } != ERROR_ALREADY_EXISTS;
+ unsafe { GetLastError() != ERROR_ALREADY_EXISTS }
+}
- if first_instance {
+pub fn handle_single_instance(opener: OpenListener, args: &Args, is_first_instance: bool) -> bool {
+ if is_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 !args.foreground {
@@ -44,7 +46,7 @@ pub fn check_single_instance(opener: OpenListener, args: &Args) -> bool {
send_args_to_instance(args).log_err();
}
- first_instance
+ is_first_instance
}
fn with_pipe(f: impl Fn(String)) {
@@ -512,6 +512,8 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["ring"]
tokio-socks = { version = "0.5", features = ["futures-io"] }
tokio-stream = { version = "0.1", features = ["fs"] }
winapi = { version = "0.3", default-features = false, features = ["cfg", "consoleapi", "errhandlingapi", "evntrace", "fileapi", "handleapi", "in6addr", "inaddr", "knownfolders", "minwinbase", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "sysinfoapi", "winbase", "windef", "winerror", "winioctl"] }
+windows-core = { version = "0.61" }
+windows-numerics = { version = "0.2" }
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }
@@ -533,6 +535,8 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["ring"]
tokio-socks = { version = "0.5", features = ["futures-io"] }
tokio-stream = { version = "0.1", features = ["fs"] }
winapi = { version = "0.3", default-features = false, features = ["cfg", "consoleapi", "errhandlingapi", "evntrace", "fileapi", "handleapi", "in6addr", "inaddr", "knownfolders", "minwinbase", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "sysinfoapi", "winbase", "windef", "winerror", "winioctl"] }
+windows-core = { version = "0.61" }
+windows-numerics = { version = "0.2" }
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }