mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-28 06:41:14 +00:00
The desktop companion crate was Linux-only. All platform-specific code lives in canvas.rs (WebKitGTK, cairo, unix sockets, SO_PEERCRED), so: move webkit2gtk/cairo-rs/libc under linux target dependencies, gate the canvas module, its protocol registration, invoke handler, and exit shutdown to Linux, add a USERPROFILE home fallback and an explicit no-op installer error for Windows, and add the multi-size icon.ico Windows resource. Linux behavior is unchanged; macOS/Windows builds are for testing (macOS cargo build+test green locally; Windows aarch64-msvc built and launched in a Windows 11 ARM VM).
148 lines
4.2 KiB
Rust
148 lines
4.2 KiB
Rust
#[cfg(not(target_os = "windows"))]
|
|
use crate::cli::openclaw_home;
|
|
use serde::Deserialize;
|
|
#[cfg(not(target_os = "windows"))]
|
|
use serde::Serialize;
|
|
#[cfg(not(target_os = "windows"))]
|
|
use std::collections::VecDeque;
|
|
#[cfg(not(target_os = "windows"))]
|
|
use std::io::{BufRead, BufReader};
|
|
#[cfg(not(target_os = "windows"))]
|
|
use std::process::{Command, Stdio};
|
|
#[cfg(not(target_os = "windows"))]
|
|
use std::sync::mpsc;
|
|
#[cfg(not(target_os = "windows"))]
|
|
use std::thread;
|
|
#[cfg(not(target_os = "windows"))]
|
|
use tauri::path::BaseDirectory;
|
|
use tauri::AppHandle;
|
|
#[cfg(not(target_os = "windows"))]
|
|
use tauri::{Emitter, Manager};
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
const INSTALL_EVENT: &str = "install-progress";
|
|
#[cfg(not(target_os = "windows"))]
|
|
const ERROR_TAIL_LINES: usize = 24;
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum InstallChannel {
|
|
Stable,
|
|
Beta,
|
|
Dev,
|
|
}
|
|
|
|
impl InstallChannel {
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn version(self) -> &'static str {
|
|
match self {
|
|
Self::Stable => "latest",
|
|
Self::Beta => "beta",
|
|
Self::Dev => "main",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
#[derive(Clone, Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct InstallProgress<'a> {
|
|
stream: &'a str,
|
|
line: &'a str,
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn install(_app: &AppHandle, _channel: InstallChannel) -> Result<(), String> {
|
|
Err("CLI installation is unavailable in this Windows test build.".to_string())
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn install(app: &AppHandle, channel: InstallChannel) -> Result<(), String> {
|
|
let script = app
|
|
.path()
|
|
.resolve("install-cli.sh", BaseDirectory::Resource)
|
|
.map_err(|error| format!("Bundled installer is unavailable: {error}"))?;
|
|
let prefix = openclaw_home().map_err(|error| error.to_string())?;
|
|
|
|
let mut command = Command::new("bash");
|
|
command
|
|
.arg(script)
|
|
.args(["--json", "--no-onboard", "--prefix"])
|
|
.arg(&prefix)
|
|
.args(["--version", channel.version()]);
|
|
if matches!(channel, InstallChannel::Dev) {
|
|
command
|
|
.args(["--install-method", "git", "--git-dir"])
|
|
.arg(prefix.join("dev/openclaw"));
|
|
}
|
|
command
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
|
|
let mut child = command
|
|
.spawn()
|
|
.map_err(|error| format!("Could not start bundled installer: {error}"))?;
|
|
let stdout = child
|
|
.stdout
|
|
.take()
|
|
.ok_or_else(|| "Could not read installer output".to_string())?;
|
|
let stderr = child
|
|
.stderr
|
|
.take()
|
|
.ok_or_else(|| "Could not read installer errors".to_string())?;
|
|
let (sender, receiver) = mpsc::channel::<(&'static str, String)>();
|
|
|
|
let stdout_thread = stream_lines("stdout", stdout, sender.clone());
|
|
let stderr_thread = stream_lines("stderr", stderr, sender);
|
|
let mut tail = VecDeque::with_capacity(ERROR_TAIL_LINES);
|
|
for (stream, line) in receiver {
|
|
let _ = app.emit_to(
|
|
"main",
|
|
INSTALL_EVENT,
|
|
InstallProgress {
|
|
stream,
|
|
line: &line,
|
|
},
|
|
);
|
|
if tail.len() == ERROR_TAIL_LINES {
|
|
tail.pop_front();
|
|
}
|
|
tail.push_back(line);
|
|
}
|
|
|
|
let status = child
|
|
.wait()
|
|
.map_err(|error| format!("Could not wait for bundled installer: {error}"))?;
|
|
let _ = stdout_thread.join();
|
|
let _ = stderr_thread.join();
|
|
if status.success() {
|
|
return Ok(());
|
|
}
|
|
|
|
let detail = tail.into_iter().collect::<Vec<_>>().join("\n");
|
|
if detail.is_empty() {
|
|
Err(format!("Installer exited with {status}"))
|
|
} else {
|
|
Err(format!("Installer exited with {status}\n{detail}"))
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn stream_lines<R>(
|
|
stream: &'static str,
|
|
reader: R,
|
|
sender: mpsc::Sender<(&'static str, String)>,
|
|
) -> thread::JoinHandle<()>
|
|
where
|
|
R: std::io::Read + Send + 'static,
|
|
{
|
|
thread::spawn(move || {
|
|
for line in BufReader::new(reader).lines().map_while(Result::ok) {
|
|
if sender.send((stream, line)).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
})
|
|
}
|