diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-07 16:27:42 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-07 16:29:45 +0300 |
| commit | 0956ad8c5883014ebf88664099fae41edd3fdc9e (patch) | |
| tree | 8fb3ad282872547a9be2e9790b157d1bc90ca8c2 /src | |
| parent | 745ce4254c646922d667ec13dd21963656405c12 (diff) | |
feat: add windows preview installer
Diffstat (limited to 'src')
| -rw-r--r-- | src/protocol/wire.rs | 67 | ||||
| -rw-r--r-- | src/update.rs | 121 |
2 files changed, 164 insertions, 24 deletions
diff --git a/src/protocol/wire.rs b/src/protocol/wire.rs index 04d1b1ae..357f414e 100644 --- a/src/protocol/wire.rs +++ b/src/protocol/wire.rs @@ -332,9 +332,6 @@ pub enum ClientMessage { data: Vec<u8>, }, - /// Structured input events from platform clients that do not expose Unix-style raw bytes. - InputEvents { events: Vec<ClientInputEvent> }, - /// Image bytes read from the client's local clipboard for remote paste bridging. ClipboardImage { /// Image file extension without a leading dot. @@ -381,6 +378,9 @@ pub enum ClientMessage { /// Crossterm-compatible modifier bits for forwarded mouse wheel events. modifiers: u8, }, + + /// Structured input events from platform clients that do not expose Unix-style raw bytes. + InputEvents { events: Vec<ClientInputEvent> }, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -929,6 +929,67 @@ mod tests { } #[test] + fn client_message_wire_tags_preserve_protocol_13_order() { + fn tag(msg: &ClientMessage) -> u8 { + *bincode::serde::encode_to_vec(msg, bincode::config::standard()) + .unwrap() + .first() + .expect("encoded client message should include enum tag") + } + + assert_eq!( + tag(&ClientMessage::Hello { + version: PROTOCOL_VERSION, + cols: 80, + rows: 24, + cell_width_px: 8, + cell_height_px: 16, + requested_encoding: RenderEncoding::SemanticFrame, + keybindings: ClientKeybindings::Server, + launch_mode: ClientLaunchMode::App, + }), + 0 + ); + assert_eq!(tag(&ClientMessage::Input { data: Vec::new() }), 1); + assert_eq!( + tag(&ClientMessage::ClipboardImage { + extension: "png".to_owned(), + data: Vec::new(), + }), + 2 + ); + assert_eq!( + tag(&ClientMessage::Resize { + cols: 80, + rows: 24, + cell_width_px: 8, + cell_height_px: 16, + }), + 3 + ); + assert_eq!(tag(&ClientMessage::Detach), 4); + assert_eq!( + tag(&ClientMessage::AttachTerminal { + terminal_id: "term".to_owned(), + takeover: false, + }), + 5 + ); + assert_eq!( + tag(&ClientMessage::AttachScroll { + source: AttachScrollSource::Wheel, + direction: AttachScrollDirection::Up, + lines: 1, + column: None, + row: None, + modifiers: 0, + }), + 6 + ); + assert_eq!(tag(&ClientMessage::InputEvents { events: Vec::new() }), 7); + } + + #[test] fn client_input_events_roundtrip() { let msg = ClientMessage::InputEvents { events: vec![ diff --git a/src/update.rs b/src/update.rs index 4f192e3a..6b428b78 100644 --- a/src/update.rs +++ b/src/update.rs @@ -609,6 +609,42 @@ fn install_downloaded_update(mut update: DownloadedUpdate) -> Result<(), String> Ok(()) } +#[cfg(windows)] +fn install_windows_update_with_installer(channel: UpdateChannel) -> Result<(), String> { + let status = Command::new("powershell") + .args([ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "irm https://herdr.dev/install.ps1 | iex", + ]) + .env("HERDR_CHANNEL", channel.as_str()) + .status() + .map_err(|err| format!("failed to run Windows installer: {err}"))?; + + if !status.success() { + return Err(format!("Windows installer failed with status {status}")); + } + + Ok(()) +} + +#[cfg(windows)] +fn windows_installed_herdr_exe_path() -> Result<PathBuf, String> { + if let Some(install_dir) = env::var_os("HERDR_INSTALL_DIR").filter(|value| !value.is_empty()) { + return Ok(PathBuf::from(install_dir).join("herdr.exe")); + } + + let local_app_data = env::var_os("LOCALAPPDATA") + .ok_or("LOCALAPPDATA is not set; cannot locate Herdr install")?; + Ok(PathBuf::from(local_app_data) + .join("Programs") + .join("Herdr") + .join("bin") + .join("herdr.exe")) +} + // --------------------------------------------------------------------------- // Upgrade flow helpers // --------------------------------------------------------------------------- @@ -1901,32 +1937,73 @@ pub fn self_update(options: SelfUpdateOptions) -> Result<Version, String> { if let Some(commit) = &release.commit { tracing::info!(commit = %commit, build_id = ?release.build_id, "selected preview update build"); } - eprintln!("downloading {}...", release.label()); if let Err(e) = crate::release_notes::save_pending(release.label(), &release.notes_body) { tracing::warn!("failed to save pending release notes: {e}"); } - let downloaded_update = download_update(&release)?; - let updated_exe = downloaded_update.current_exe.clone(); - eprintln!("downloaded {}", release.label()); - if !options.live_handoff - && !prompt_to_complete_plain_update(&server_update_decisions, &release)? + + #[cfg(windows)] { - eprintln!("Herdr was not updated."); - eprintln!("Stop running Herdr sessions when ready, then run `herdr update` again."); - return Ok(current); - } - install_downloaded_update(downloaded_update)?; - eprintln!("installed {}", release.label()); - let server_update_decisions = if options.live_handoff { - server_update_decisions - } else { - mark_plain_update_stop_decisions(server_update_decisions) - }; - let server_update_outcomes = - apply_running_session_update_decisions(&release, &updated_exe, server_update_decisions)?; - print_outdated_integration_notice_with_updated_binary(&updated_exe); + if !options.live_handoff + && !prompt_to_complete_plain_update(&server_update_decisions, &release)? + { + eprintln!("Herdr was not updated."); + eprintln!("Stop running Herdr sessions when ready, then run `herdr update` again."); + return Ok(current); + } - print_running_session_update_outcomes(&server_update_outcomes, &release); + eprintln!( + "installing {} with the Windows installer...", + release.label() + ); + install_windows_update_with_installer(channel)?; + let updated_exe = windows_installed_herdr_exe_path()?; + eprintln!("installed {}", release.label()); + let server_update_decisions = if options.live_handoff { + server_update_decisions + } else { + mark_plain_update_stop_decisions(server_update_decisions) + }; + let server_update_outcomes = apply_running_session_update_decisions( + &release, + &updated_exe, + server_update_decisions, + )?; + print_outdated_integration_notice_with_updated_binary(&updated_exe); + + print_running_session_update_outcomes(&server_update_outcomes, &release); + + return Ok(release.version); + } + + #[cfg(not(windows))] + { + eprintln!("downloading {}...", release.label()); + let downloaded_update = download_update(&release)?; + let updated_exe = downloaded_update.current_exe.clone(); + eprintln!("downloaded {}", release.label()); + if !options.live_handoff + && !prompt_to_complete_plain_update(&server_update_decisions, &release)? + { + eprintln!("Herdr was not updated."); + eprintln!("Stop running Herdr sessions when ready, then run `herdr update` again."); + return Ok(current); + } + install_downloaded_update(downloaded_update)?; + eprintln!("installed {}", release.label()); + let server_update_decisions = if options.live_handoff { + server_update_decisions + } else { + mark_plain_update_stop_decisions(server_update_decisions) + }; + let server_update_outcomes = apply_running_session_update_decisions( + &release, + &updated_exe, + server_update_decisions, + )?; + print_outdated_integration_notice_with_updated_binary(&updated_exe); + + print_running_session_update_outcomes(&server_update_outcomes, &release); + } Ok(release.version) } @@ -2077,6 +2154,8 @@ fn platform_target() -> (&'static str, &'static str) { "linux" } else if cfg!(target_os = "macos") { "macos" + } else if cfg!(target_os = "windows") { + "windows" } else { "unknown" }; |
