windows: Implement AutoUpdater
(#25734)
Part of #24800 https://github.com/user-attachments/assets/e70d594e-3635-4f93-9073-5abf7e9d2b20 Release Notes: - N/A
This commit is contained in:
parent
584fa3db53
commit
1d9915f88a
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1182,6 +1182,18 @@ dependencies = [
|
|||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auto_update_helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"log",
|
||||||
|
"simplelog",
|
||||||
|
"windows 0.61.1",
|
||||||
|
"winresource",
|
||||||
|
"workspace-hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "auto_update_ui"
|
name = "auto_update_ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -17767,6 +17779,8 @@ dependencies = [
|
|||||||
"wasmtime-cranelift",
|
"wasmtime-cranelift",
|
||||||
"wasmtime-environ",
|
"wasmtime-environ",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
"windows-core 0.61.0",
|
||||||
|
"windows-numerics",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
|
12
Cargo.toml
12
Cargo.toml
@ -15,6 +15,7 @@ members = [
|
|||||||
"crates/assistant_tools",
|
"crates/assistant_tools",
|
||||||
"crates/audio",
|
"crates/audio",
|
||||||
"crates/auto_update",
|
"crates/auto_update",
|
||||||
|
"crates/auto_update_helper",
|
||||||
"crates/auto_update_ui",
|
"crates/auto_update_ui",
|
||||||
"crates/aws_http_client",
|
"crates/aws_http_client",
|
||||||
"crates/bedrock",
|
"crates/bedrock",
|
||||||
@ -222,6 +223,7 @@ assistant_tool = { path = "crates/assistant_tool" }
|
|||||||
assistant_tools = { path = "crates/assistant_tools" }
|
assistant_tools = { path = "crates/assistant_tools" }
|
||||||
audio = { path = "crates/audio" }
|
audio = { path = "crates/audio" }
|
||||||
auto_update = { path = "crates/auto_update" }
|
auto_update = { path = "crates/auto_update" }
|
||||||
|
auto_update_helper = { path = "crates/auto_update_helper" }
|
||||||
auto_update_ui = { path = "crates/auto_update_ui" }
|
auto_update_ui = { path = "crates/auto_update_ui" }
|
||||||
aws_http_client = { path = "crates/aws_http_client" }
|
aws_http_client = { path = "crates/aws_http_client" }
|
||||||
bedrock = { path = "crates/bedrock" }
|
bedrock = { path = "crates/bedrock" }
|
||||||
@ -782,4 +784,12 @@ let_underscore_future = "allow"
|
|||||||
too_many_arguments = "allow"
|
too_many_arguments = "allow"
|
||||||
|
|
||||||
[workspace.metadata.cargo-machete]
|
[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
|
settings.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
which.workspace = true
|
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||||
|
which.workspace = true
|
||||||
|
@ -23,7 +23,6 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use which::which;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
|
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<()>>>,
|
pending_poll: Option<Task<Option<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct JsonRelease {
|
pub struct JsonRelease {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
@ -237,6 +236,46 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> {
|
|||||||
None
|
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 {
|
impl AutoUpdater {
|
||||||
pub fn get(cx: &mut App) -> Option<Entity<Self>> {
|
pub fn get(cx: &mut App) -> Option<Entity<Self>> {
|
||||||
cx.default_global::<GlobalAutoUpdate>().0.clone()
|
cx.default_global::<GlobalAutoUpdate>().0.clone()
|
||||||
@ -469,22 +508,21 @@ impl AutoUpdater {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let temp_dir = tempfile::Builder::new()
|
let installer_dir = InstallerDir::new().await?;
|
||||||
.prefix("zed-auto-update")
|
|
||||||
.tempdir()?;
|
|
||||||
|
|
||||||
let filename = match OS {
|
let filename = match OS {
|
||||||
"macos" => Ok("Zed.dmg"),
|
"macos" => Ok("Zed.dmg"),
|
||||||
"linux" => Ok("zed.tar.gz"),
|
"linux" => Ok("zed.tar.gz"),
|
||||||
|
"windows" => Ok("ZedUpdateInstaller.exe"),
|
||||||
_ => Err(anyhow!("not supported: {:?}", OS)),
|
_ => Err(anyhow!("not supported: {:?}", OS)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
which("rsync").is_ok(),
|
which::which("rsync").is_ok(),
|
||||||
"Aborting. Could not find rsync which is required for auto-updates."
|
"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?;
|
download_release(&downloaded_asset, release, client, &cx).await?;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
@ -493,8 +531,9 @@ impl AutoUpdater {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let binary_path = match OS {
|
let binary_path = match OS {
|
||||||
"macos" => install_release_macos(&temp_dir, downloaded_asset, &cx).await,
|
"macos" => install_release_macos(&installer_dir, downloaded_asset, &cx).await,
|
||||||
"linux" => install_release_linux(&temp_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)),
|
_ => Err(anyhow!("not supported: {:?}", OS)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
@ -629,7 +668,7 @@ async fn download_release(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn install_release_linux(
|
async fn install_release_linux(
|
||||||
temp_dir: &tempfile::TempDir,
|
temp_dir: &InstallerDir,
|
||||||
downloaded_tar_gz: PathBuf,
|
downloaded_tar_gz: PathBuf,
|
||||||
cx: &AsyncApp,
|
cx: &AsyncApp,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<PathBuf> {
|
||||||
@ -696,7 +735,7 @@ async fn install_release_linux(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn install_release_macos(
|
async fn install_release_macos(
|
||||||
temp_dir: &tempfile::TempDir,
|
temp_dir: &InstallerDir,
|
||||||
downloaded_dmg: PathBuf,
|
downloaded_dmg: PathBuf,
|
||||||
cx: &AsyncApp,
|
cx: &AsyncApp,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<PathBuf> {
|
||||||
@ -743,3 +782,41 @@ async fn install_release_macos(
|
|||||||
|
|
||||||
Ok(running_app_path)
|
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
|
||||||
|
}
|
||||||
|
29
crates/auto_update_helper/Cargo.toml
Normal file
29
crates/auto_update_helper/Cargo.toml
Normal file
@ -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"]
|
1
crates/auto_update_helper/LICENSE-GPL
Symbolic link
1
crates/auto_update_helper/LICENSE-GPL
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-GPL
|
BIN
crates/auto_update_helper/app-icon.ico
Normal file
BIN
crates/auto_update_helper/app-icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 577 KiB |
15
crates/auto_update_helper/build.rs
Normal file
15
crates/auto_update_helper/build.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
crates/auto_update_helper/manifest.xml
Normal file
16
crates/auto_update_helper/manifest.xml
Normal file
@ -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>
|
94
crates/auto_update_helper/src/auto_update_helper.rs
Normal file
94
crates/auto_update_helper/src/auto_update_helper.rs
Normal file
@ -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,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
236
crates/auto_update_helper/src/dialog.rs
Normal file
236
crates/auto_update_helper/src/dialog.rs
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
crates/auto_update_helper/src/updater.rs
Normal file
171
crates/auto_update_helper/src/updater.rs
Normal file
@ -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() {
|
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();
|
let args = Args::parse();
|
||||||
|
|
||||||
// Set custom data directory.
|
// Set custom data directory.
|
||||||
@ -236,27 +246,30 @@ fn main() {
|
|||||||
|
|
||||||
let (open_listener, mut open_rx) = OpenListener::new();
|
let (open_listener, mut open_rx) = OpenListener::new();
|
||||||
|
|
||||||
let failed_single_instance_check = if *db::ZED_STATELESS
|
let failed_single_instance_check =
|
||||||
|| *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
|
if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
|
||||||
{
|
false
|
||||||
false
|
} else {
|
||||||
} else {
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
{
|
||||||
{
|
crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
|
||||||
crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
!crate::zed::windows_only_instance::check_single_instance(open_listener.clone(), &args)
|
!crate::zed::windows_only_instance::handle_single_instance(
|
||||||
}
|
open_listener.clone(),
|
||||||
|
&args,
|
||||||
|
is_first_instance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
use zed::mac_only_instance::*;
|
use zed::mac_only_instance::*;
|
||||||
ensure_only_instance() != IsOnlyInstance::Yes
|
ensure_only_instance() != IsOnlyInstance::Yes
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if failed_single_instance_check {
|
if failed_single_instance_check {
|
||||||
println!("zed is already running");
|
println!("zed is already running");
|
||||||
return;
|
return;
|
||||||
|
@ -25,7 +25,7 @@ use windows::{
|
|||||||
|
|
||||||
use crate::{Args, OpenListener};
|
use crate::{Args, OpenListener};
|
||||||
|
|
||||||
pub fn check_single_instance(opener: OpenListener, args: &Args) -> bool {
|
pub fn is_first_instance() -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
CreateMutexW(
|
CreateMutexW(
|
||||||
None,
|
None,
|
||||||
@ -34,9 +34,11 @@ pub fn check_single_instance(opener: OpenListener, args: &Args) -> bool {
|
|||||||
)
|
)
|
||||||
.expect("Unable to create instance mutex.")
|
.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
|
// We are the first instance, listen for messages sent from other instances
|
||||||
std::thread::spawn(move || with_pipe(|url| opener.open_urls(vec![url])));
|
std::thread::spawn(move || with_pipe(|url| opener.open_urls(vec![url])));
|
||||||
} else if !args.foreground {
|
} 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();
|
send_args_to_instance(args).log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
first_instance
|
is_first_instance
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_pipe(f: impl Fn(String)) {
|
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-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
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"] }
|
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-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-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"] }
|
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-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
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"] }
|
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-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-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"] }
|
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"] }
|
||||||
|
Loading…
Reference in New Issue
Block a user