1#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
2
3#[cfg(target_os = "windows")]
4mod dialog;
5#[cfg(target_os = "windows")]
6mod updater;
7
8#[cfg(target_os = "windows")]
9fn main() {
10 if let Err(e) = windows_impl::run() {
11 log::error!("Error: Zed update failed, {:?}", e);
12 windows_impl::show_error(format!("Error: {:?}", e));
13 }
14}
15
16#[cfg(not(target_os = "windows"))]
17fn main() {}
18
19#[cfg(target_os = "windows")]
20mod windows_impl {
21 use std::path::Path;
22
23 use super::dialog::create_dialog_window;
24 use super::updater::perform_update;
25 use anyhow::{Context as _, Result};
26 use windows::{
27 Win32::{
28 Foundation::{HWND, LPARAM, WPARAM},
29 UI::WindowsAndMessaging::{
30 DispatchMessageW, GetMessageW, MB_ICONERROR, MB_SYSTEMMODAL, MSG, MessageBoxW,
31 PostMessageW, WM_USER,
32 },
33 },
34 core::HSTRING,
35 };
36
37 pub(crate) const WM_JOB_UPDATED: u32 = WM_USER + 1;
38 pub(crate) const WM_TERMINATE: u32 = WM_USER + 2;
39
40 #[derive(Debug)]
41 struct Args {
42 launch: Option<bool>,
43 }
44
45 pub(crate) fn run() -> Result<()> {
46 let helper_dir = std::env::current_exe()?
47 .parent()
48 .context("No parent directory")?
49 .to_path_buf();
50 init_log(&helper_dir)?;
51 let app_dir = helper_dir
52 .parent()
53 .context("No parent directory")?
54 .to_path_buf();
55
56 log::info!("======= Starting Zed update =======");
57 let (tx, rx) = std::sync::mpsc::channel();
58 let hwnd = create_dialog_window(rx)?.0 as isize;
59 let args = parse_args();
60 std::thread::spawn(move || {
61 let result = perform_update(app_dir.as_path(), Some(hwnd), args.launch.unwrap_or(true));
62 tx.send(result).ok();
63 unsafe { PostMessageW(Some(HWND(hwnd as _)), WM_TERMINATE, WPARAM(0), LPARAM(0)) }.ok();
64 });
65 unsafe {
66 let mut message = MSG::default();
67 while GetMessageW(&mut message, None, 0, 0).as_bool() {
68 DispatchMessageW(&message);
69 }
70 }
71 Ok(())
72 }
73
74 fn init_log(helper_dir: &Path) -> Result<()> {
75 simplelog::WriteLogger::init(
76 simplelog::LevelFilter::Info,
77 simplelog::Config::default(),
78 std::fs::File::options()
79 .append(true)
80 .create(true)
81 .open(helper_dir.join("auto_update_helper.log"))?,
82 )?;
83 Ok(())
84 }
85
86 fn parse_args() -> Args {
87 let mut result = Args { launch: None };
88 if let Some(candidate) = std::env::args().nth(1) {
89 parse_single_arg(&candidate, &mut result);
90 }
91
92 result
93 }
94
95 fn parse_single_arg(arg: &str, result: &mut Args) {
96 let Some((key, value)) = arg.strip_prefix("--").and_then(|arg| arg.split_once('=')) else {
97 log::error!(
98 "Invalid argument format: '{}'. Expected format: --key=value",
99 arg
100 );
101 return;
102 };
103
104 match key {
105 "launch" => parse_launch_arg(value, &mut result.launch),
106 _ => log::error!("Unknown argument: --{}", key),
107 }
108 }
109
110 fn parse_launch_arg(value: &str, arg: &mut Option<bool>) {
111 match value {
112 "true" => *arg = Some(true),
113 "false" => *arg = Some(false),
114 _ => log::error!(
115 "Invalid value for --launch: '{}'. Expected 'true' or 'false'",
116 value
117 ),
118 }
119 }
120
121 pub(crate) fn show_error(mut content: String) {
122 if content.len() > 600 {
123 content.truncate(600);
124 content.push_str("...\n");
125 }
126 let _ = unsafe {
127 MessageBoxW(
128 None,
129 &HSTRING::from(content),
130 windows::core::w!("Error: Zed update failed."),
131 MB_ICONERROR | MB_SYSTEMMODAL,
132 )
133 };
134 }
135
136 #[cfg(test)]
137 mod tests {
138 use crate::windows_impl::{Args, parse_launch_arg, parse_single_arg};
139
140 #[test]
141 fn test_parse_launch_arg() {
142 let mut arg = None;
143 parse_launch_arg("true", &mut arg);
144 assert_eq!(arg, Some(true));
145
146 let mut arg = None;
147 parse_launch_arg("false", &mut arg);
148 assert_eq!(arg, Some(false));
149
150 let mut arg = None;
151 parse_launch_arg("invalid", &mut arg);
152 assert_eq!(arg, None);
153 }
154
155 #[test]
156 fn test_parse_single_arg() {
157 let mut args = Args { launch: None };
158 parse_single_arg("--launch=true", &mut args);
159 assert_eq!(args.launch, Some(true));
160
161 let mut args = Args { launch: None };
162 parse_single_arg("--launch=false", &mut args);
163 assert_eq!(args.launch, Some(false));
164
165 let mut args = Args { launch: None };
166 parse_single_arg("--launch=invalid", &mut args);
167 assert_eq!(args.launch, None);
168
169 let mut args = Args { launch: None };
170 parse_single_arg("--launch", &mut args);
171 assert_eq!(args.launch, None);
172
173 let mut args = Args { launch: None };
174 parse_single_arg("--unknown", &mut args);
175 assert_eq!(args.launch, None);
176 }
177 }
178}