mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
build(linux): allow macOS and Windows test builds of the desktop app (#108004)
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).
This commit is contained in:
committed by
GitHub
parent
51d85769b3
commit
9384a40393
@@ -50,6 +50,7 @@ magick 32x32.png -background none -gravity center -extent 32x32 PNG32:32x32.png
|
||||
rsvg-convert -w 128 -h 128 icon-tile.svg -o 128x128.png
|
||||
rsvg-convert -w 256 -h 256 icon-tile.svg -o 128x128@2x.png
|
||||
rsvg-convert -w 512 -h 512 icon-tile.svg -o icon.png
|
||||
magick icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico
|
||||
```
|
||||
|
||||
## Packaging
|
||||
|
||||
@@ -14,10 +14,12 @@ tauri-build = "2.6.3"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
cairo-rs = { version = "0.18.5", features = ["png"] }
|
||||
image = { version = "0.25.10", default-features = false, features = ["jpeg", "png"] }
|
||||
libc = "0.2.186"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.150"
|
||||
tauri = { version = "2.11.5", features = ["image-png", "tray-icon"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
cairo-rs = { version = "0.18.5", features = ["png"] }
|
||||
libc = "0.2.186"
|
||||
webkit2gtk = "2.0.2"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
fn main() {
|
||||
// Command metadata generates capability permissions independently of the
|
||||
// target's invoke handler, so keep the Linux-only command permission known.
|
||||
const COMMANDS: &[&str] = &[
|
||||
"bootstrap",
|
||||
"canvas_a2ui_action",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
@@ -122,8 +122,12 @@ impl OpenClawCli {
|
||||
}
|
||||
|
||||
pub fn openclaw_home() -> Result<PathBuf, CliError> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let home = env::var_os("HOME")
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| CliError::Environment("HOME is not set".to_string()))?;
|
||||
.or_else(|| env::var_os("USERPROFILE").filter(|value| !value.is_empty()));
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let home = env::var_os("HOME").filter(|value| !value.is_empty());
|
||||
let home = home.ok_or_else(|| CliError::Environment("HOME is not set".to_string()))?;
|
||||
Ok(PathBuf::from(home).join(".openclaw"))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use crate::cli::openclaw_home;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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, Emitter, Manager};
|
||||
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)]
|
||||
@@ -20,6 +33,7 @@ pub enum InstallChannel {
|
||||
}
|
||||
|
||||
impl InstallChannel {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn version(self) -> &'static str {
|
||||
match self {
|
||||
Self::Stable => "latest",
|
||||
@@ -29,6 +43,7 @@ impl InstallChannel {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct InstallProgress<'a> {
|
||||
@@ -36,6 +51,12 @@ struct InstallProgress<'a> {
|
||||
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()
|
||||
@@ -107,6 +128,7 @@ pub fn install(app: &AppHandle, channel: InstallChannel) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn stream_lines<R>(
|
||||
stream: &'static str,
|
||||
reader: R,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
mod canvas;
|
||||
mod cli;
|
||||
mod gateway;
|
||||
@@ -280,28 +281,41 @@ async fn gateway_action(
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = canvas::register_protocol(tauri::Builder::default())
|
||||
.setup(|app| {
|
||||
let window = app
|
||||
.get_webview_window("main")
|
||||
.expect("tauri.conf.json must define the main window");
|
||||
let state = DesktopState::new(window.url()?);
|
||||
app.manage(state.clone());
|
||||
match canvas::CanvasBridge::start(app.handle().clone()) {
|
||||
Ok(bridge) => {
|
||||
app.manage(bridge);
|
||||
}
|
||||
Err(error) => eprintln!("Canvas bridge unavailable: {error}"),
|
||||
let builder = tauri::Builder::default();
|
||||
#[cfg(target_os = "linux")]
|
||||
let builder = canvas::register_protocol(builder);
|
||||
|
||||
let builder = builder.setup(|app| {
|
||||
let window = app
|
||||
.get_webview_window("main")
|
||||
.expect("tauri.conf.json must define the main window");
|
||||
let state = DesktopState::new(window.url()?);
|
||||
app.manage(state.clone());
|
||||
#[cfg(target_os = "linux")]
|
||||
match canvas::CanvasBridge::start(app.handle().clone()) {
|
||||
Ok(bridge) => {
|
||||
app.manage(bridge);
|
||||
}
|
||||
state.set_tray(tray::build(app, state.clone())?);
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
bootstrap,
|
||||
canvas::canvas_a2ui_action,
|
||||
install_cli,
|
||||
gateway_action
|
||||
])
|
||||
Err(error) => eprintln!("Canvas bridge unavailable: {error}"),
|
||||
}
|
||||
state.set_tray(tray::build(app, state.clone())?);
|
||||
Ok(())
|
||||
});
|
||||
#[cfg(target_os = "linux")]
|
||||
let builder = builder.invoke_handler(tauri::generate_handler![
|
||||
bootstrap,
|
||||
canvas::canvas_a2ui_action,
|
||||
install_cli,
|
||||
gateway_action
|
||||
]);
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let builder = builder.invoke_handler(tauri::generate_handler![
|
||||
bootstrap,
|
||||
install_cli,
|
||||
gateway_action
|
||||
]);
|
||||
|
||||
let app = builder
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
let state = window.app_handle().state::<DesktopState>();
|
||||
@@ -314,10 +328,13 @@ fn main() {
|
||||
.build(tauri::generate_context!())
|
||||
.expect("OpenClaw desktop app failed");
|
||||
app.run(|app, event| {
|
||||
#[cfg(target_os = "linux")]
|
||||
if matches!(event, tauri::RunEvent::Exit) {
|
||||
if let Some(bridge) = app.try_state::<canvas::CanvasBridge>() {
|
||||
bridge.shutdown();
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let _ = (app, event);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user