feat(cli): full command-line interface via clap
Replace the ad-hoc `--serve` arg sniff with a clap-parsed CLI: - positional <file> to open at startup (also the OS file-association path) - --new, --read-only, --backend, --safe-mode/--no-gpu - --serve/--port (headless JSON automation, unchanged behaviour) - --export IN OUT (headless format convert via the automation load/save infra) - --script FILE (run command lines at startup) - --log/RUST_LOG, plus --version/--help for free GUI-only options pass to app::boot through a OnceLock config; read-only gates the save paths. The web build skips parsing (no args). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7e70a2a97c
commit
79bb1d67b1
8 changed files with 399 additions and 30 deletions
169
Cargo.lock
generated
169
Cargo.lock
generated
|
|
@ -18,8 +18,10 @@ version = "0.6.2"
|
|||
dependencies = [
|
||||
"acadrust",
|
||||
"bytemuck",
|
||||
"clap",
|
||||
"console_error_panic_hook",
|
||||
"cosmic-text",
|
||||
"env_logger",
|
||||
"flate2",
|
||||
"fontdb",
|
||||
"glam 0.33.0",
|
||||
|
|
@ -240,6 +242,56 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
|
|
@ -908,6 +960,46 @@ dependencies = [
|
|||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.1"
|
||||
|
|
@ -964,6 +1056,12 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
|
|
@ -1377,6 +1475,29 @@ dependencies = [
|
|||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equator"
|
||||
version = "0.4.2"
|
||||
|
|
@ -2415,6 +2536,12 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
|
|
@ -2454,6 +2581,30 @@ version = "1.0.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
|
|
@ -3638,6 +3789,12 @@ version = "1.21.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.5"
|
||||
|
|
@ -5202,6 +5359,12 @@ dependencies = [
|
|||
"unicode-properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
|
|
@ -5928,6 +6091,12 @@ version = "0.8.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.23.2"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ truck-modeling = "0.6"
|
|||
truck-meshalgo = { version = "0.4", default-features = false, features = ["tessellation"], optional = true }
|
||||
truck-polymesh = "0.6"
|
||||
rfd = "0.17"
|
||||
# Command-line parsing: file arg, --serve/--export/--script and the headless flags.
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
# Opt-in logging via --log / RUST_LOG (surfaces wgpu / iced / winit diagnostics).
|
||||
env_logger = "0.11"
|
||||
acadrust = "0.3.4"
|
||||
printpdf = "0.9.1"
|
||||
flate2 = "1"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,29 @@ pub fn serve() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Headless one-shot format conversion (`--export IN OUT`). Loads `input`,
|
||||
/// writes `output` (format chosen from `output`'s extension), and returns a
|
||||
/// process exit code (0 on success). No window is created.
|
||||
pub fn export_headless(input: &std::path::Path, output: &std::path::Path) -> i32 {
|
||||
let doc = match crate::io::load_file(input) {
|
||||
Ok(doc) => doc,
|
||||
Err(e) => {
|
||||
eprintln!("export: cannot read {}: {e}", input.display());
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
match crate::io::save(&doc, output) {
|
||||
Ok(()) => {
|
||||
println!("Exported {} → {}", input.display(), output.display());
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("export: cannot write {}: {e}", output.display());
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `--port <N>` if present on the command line.
|
||||
fn port_arg() -> Option<u16> {
|
||||
let mut args = std::env::args();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod automation;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use automation::serve;
|
||||
pub use automation::{export_headless, serve};
|
||||
mod cmd_result;
|
||||
mod commands;
|
||||
mod document;
|
||||
|
|
@ -332,6 +332,9 @@ pub(super) struct OpenCADStudio {
|
|||
/// Whether the one-time default-association prompt has already been shown.
|
||||
/// Persisted via [`settings::UserSettings`] so it survives restarts.
|
||||
default_assoc_prompted: bool,
|
||||
/// Read-only session (`--read-only`): editing is allowed but every save
|
||||
/// path is refused. Set once at boot from the CLI config.
|
||||
read_only: bool,
|
||||
/// Tag of the latest available release (without the leading "v"),
|
||||
/// e.g. `"0.3.0"`. `None` when up-to-date or check hasn't returned.
|
||||
update_notice_version: Option<String>,
|
||||
|
|
@ -1594,6 +1597,7 @@ impl OpenCADStudio {
|
|||
point_size_buf: String::new(),
|
||||
point_size_relative: true,
|
||||
default_assoc_prompted: false,
|
||||
read_only: false,
|
||||
update_notice_version: None,
|
||||
update_notice_body: None,
|
||||
clipboard: Vec::new(),
|
||||
|
|
@ -1834,18 +1838,32 @@ impl OpenCADStudio {
|
|||
Message::UpdateCheckResult,
|
||||
);
|
||||
let focus_cmd = s.focus_cmd_input();
|
||||
// Open a file path passed on the command line. Used by the
|
||||
// OS file association: double-clicking a .dwg in the file
|
||||
// explorer launches the binary with the file path as the
|
||||
// first positional argument. Unknown / flag-style args are
|
||||
// ignored. `Message::OpenRecent` does the existence check
|
||||
// and reports a clean error if the path is bogus.
|
||||
let cli_open: Task<Message> = std::env::args_os()
|
||||
.nth(1)
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| !p.as_os_str().to_string_lossy().starts_with('-'))
|
||||
.map(|p| Task::done(Message::OpenRecent(p)))
|
||||
.unwrap_or_else(Task::none);
|
||||
// Startup configuration from the command line (see `cli`). A file
|
||||
// argument — also how the OS file association launches us when a .dwg
|
||||
// is double-clicked — opens via `OpenRecent`, which existence-checks
|
||||
// the path and reports a clean error if it is bogus. `--new` opens a
|
||||
// fresh drawing tab instead of the welcome screen. `--read-only`
|
||||
// disables saving. `--script` queues command lines to run once up.
|
||||
let cfg = crate::cli::gui_config();
|
||||
s.read_only = cfg.read_only;
|
||||
let cli_open: Task<Message> = if let Some(p) = cfg.file {
|
||||
Task::done(Message::OpenRecent(p))
|
||||
} else if cfg.new {
|
||||
Task::done(Message::TabNew)
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
// Startup command script: each line dispatched as if typed at the
|
||||
// command line, in order, after any file open is requested.
|
||||
let script: Task<Message> = if cfg.script_lines.is_empty() {
|
||||
Task::none()
|
||||
} else {
|
||||
Task::batch(
|
||||
cfg.script_lines
|
||||
.into_iter()
|
||||
.map(|line| Task::done(Message::Command(line))),
|
||||
)
|
||||
};
|
||||
// One-time prompt offering to make Open CAD Studio the default app for
|
||||
// .dwg / .dxf. Shown only on the first launch that hasn't answered it
|
||||
// yet; the flag is persisted so we never ask twice.
|
||||
|
|
@ -1855,7 +1873,14 @@ impl OpenCADStudio {
|
|||
}
|
||||
(
|
||||
s,
|
||||
Task::batch([open_main, check_update, focus_cmd, cli_open, assoc_prompt]),
|
||||
Task::batch([
|
||||
open_main,
|
||||
check_update,
|
||||
focus_cmd,
|
||||
cli_open,
|
||||
script,
|
||||
assoc_prompt,
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -947,6 +947,11 @@ impl OpenCADStudio {
|
|||
Message::ObjImportPath(None) => Task::none(),
|
||||
|
||||
Message::SaveFile => {
|
||||
if self.read_only {
|
||||
self.command_line
|
||||
.push_error("Read-only session (--read-only): saving is disabled.");
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
// Stamp the live grid/snap toggles onto the VPort so the file
|
||||
// reflects them even if they came from settings with no
|
||||
|
|
@ -973,6 +978,11 @@ impl OpenCADStudio {
|
|||
}
|
||||
|
||||
Message::SaveAs => {
|
||||
if self.read_only {
|
||||
self.command_line
|
||||
.push_error("Read-only session (--read-only): saving is disabled.");
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
self.save_dialog_for_unsaved = false;
|
||||
self.open_save_dialog_window(i)
|
||||
|
|
|
|||
88
src/cli.rs
Normal file
88
src/cli.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! Command-line interface.
|
||||
//!
|
||||
//! Parsing lives here; `main` interprets the result. Three run modes split out
|
||||
//! of the parsed args:
|
||||
//! - `--serve` headless JSON automation server (see `app::serve`)
|
||||
//! - `--export IN OUT` one-shot headless format conversion, then exit
|
||||
//! - otherwise launch the GUI editor, configured via [`GuiConfig`]
|
||||
//!
|
||||
//! GUI-only options (open-file, `--new`, `--read-only`, `--script`) are stashed
|
||||
//! in [`GUI_CONFIG`] for `app::boot` to read, since the iced daemon boots with
|
||||
//! no arguments of its own.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
/// Open CAD Studio command-line options.
|
||||
#[derive(Parser, Debug, Default)]
|
||||
#[command(
|
||||
name = "OpenCADStudio",
|
||||
version,
|
||||
about = "Open CAD Studio — 2D/3D CAD editor",
|
||||
long_about = None,
|
||||
)]
|
||||
pub struct Cli {
|
||||
/// CAD file to open at startup (.dwg / .dxf). Also used by the OS file
|
||||
/// association when a drawing is double-clicked.
|
||||
pub file: Option<PathBuf>,
|
||||
|
||||
/// Start with a new empty drawing, ignoring any file argument.
|
||||
#[arg(long)]
|
||||
pub new: bool,
|
||||
|
||||
/// Open read-only: editing is allowed but saving is disabled.
|
||||
#[arg(long)]
|
||||
pub read_only: bool,
|
||||
|
||||
/// Restrict the GPU backend (e.g. dx12, vulkan, gl, metal). Sets WGPU_BACKEND.
|
||||
#[arg(long, value_name = "BACKEND")]
|
||||
pub backend: Option<String>,
|
||||
|
||||
/// Safe mode: force the GL backend, for flaky/hybrid GPU drivers.
|
||||
#[arg(long, visible_alias = "no-gpu")]
|
||||
pub safe_mode: bool,
|
||||
|
||||
/// Run the headless JSON automation server (stdin/stdout, or --port).
|
||||
#[arg(long)]
|
||||
pub serve: bool,
|
||||
|
||||
/// TCP port for --serve (defaults to stdin/stdout).
|
||||
#[arg(long, value_name = "PORT")]
|
||||
pub port: Option<u16>,
|
||||
|
||||
/// Headless convert: read IN, write OUT (format from OUT's extension), exit.
|
||||
#[arg(long, num_args = 2, value_names = ["IN", "OUT"])]
|
||||
pub export: Option<Vec<PathBuf>>,
|
||||
|
||||
/// Run a command script at startup: one command line per line of FILE.
|
||||
#[arg(long, value_name = "FILE")]
|
||||
pub script: Option<PathBuf>,
|
||||
|
||||
/// Log level (error|warn|info|debug|trace). Also honours RUST_LOG.
|
||||
#[arg(long, value_name = "LEVEL")]
|
||||
pub log: Option<String>,
|
||||
}
|
||||
|
||||
/// GUI startup configuration, handed from `main` to `app::boot` out-of-band
|
||||
/// because the iced daemon's boot closure takes no arguments.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct GuiConfig {
|
||||
/// File to open on launch (`None` for a blank session).
|
||||
pub file: Option<PathBuf>,
|
||||
/// Open a fresh drawing tab on launch instead of the welcome screen.
|
||||
pub new: bool,
|
||||
/// Saving disabled for this session.
|
||||
pub read_only: bool,
|
||||
/// Command lines to run once the editor is up.
|
||||
pub script_lines: Vec<String>,
|
||||
}
|
||||
|
||||
/// Set once by `main` before the GUI boots; read by `app::boot`.
|
||||
pub static GUI_CONFIG: OnceLock<GuiConfig> = OnceLock::new();
|
||||
|
||||
/// The GUI config, or a default empty one if `main` never set it (e.g. tests).
|
||||
pub fn gui_config() -> GuiConfig {
|
||||
GUI_CONFIG.get().cloned().unwrap_or_default()
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod app;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod cli;
|
||||
pub mod command;
|
||||
pub mod entities;
|
||||
pub mod io;
|
||||
|
|
|
|||
80
src/main.rs
80
src/main.rs
|
|
@ -5,6 +5,8 @@
|
|||
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
|
||||
|
||||
mod app;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod cli;
|
||||
mod command;
|
||||
mod entities;
|
||||
mod io;
|
||||
|
|
@ -20,32 +22,78 @@ mod sys;
|
|||
mod update_check;
|
||||
|
||||
fn main() -> iced::Result {
|
||||
// On some Windows hybrid-GPU laptops the AMD OpenGL driver (atio6axx.dll)
|
||||
// access-violates the moment wgpu enumerates its GL backend at startup,
|
||||
// killing the app before any window appears — even though DX12 would work
|
||||
// fine (#55). Restrict wgpu to DX12/Vulkan so the GL ICD is never touched.
|
||||
// An explicit user-set WGPU_BACKEND still wins.
|
||||
#[cfg(target_os = "windows")]
|
||||
if std::env::var_os("WGPU_BACKEND").is_none() {
|
||||
std::env::set_var("WGPU_BACKEND", "dx12,vulkan");
|
||||
}
|
||||
|
||||
// Web (wasm) uses the single-window entry; native uses the multi-window
|
||||
// daemon. Trunk calls `main` from its generated JS bootstrap.
|
||||
// daemon. Trunk calls `main` from its generated JS bootstrap. The web build
|
||||
// takes no CLI args, so it skips parsing entirely.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
console_error_panic_hook::set_once();
|
||||
app::run_web()
|
||||
return app::run_web();
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
// Headless automation server — drive the app over JSON on stdin/stdout
|
||||
// with no GUI (issue #29). `--serve` is the opt-in; everything else
|
||||
// launches the editor.
|
||||
if std::env::args().skip(1).any(|a| a == "--serve") {
|
||||
use clap::Parser;
|
||||
let args = cli::Cli::parse();
|
||||
|
||||
// Opt-in logging. `--log LEVEL` seeds RUST_LOG; the subscriber then
|
||||
// surfaces wgpu / iced / winit diagnostics that are otherwise silent.
|
||||
if let Some(level) = &args.log {
|
||||
std::env::set_var("RUST_LOG", level);
|
||||
}
|
||||
if std::env::var_os("RUST_LOG").is_some() {
|
||||
let _ = env_logger::try_init();
|
||||
}
|
||||
|
||||
// GPU backend selection. Explicit `--backend` wins; `--safe-mode`
|
||||
// forces GL for flaky drivers. On Windows, fall back to DX12/Vulkan so
|
||||
// the AMD OpenGL ICD (atio6axx.dll) is never touched at startup — it
|
||||
// access-violates on some hybrid-GPU laptops before any window appears
|
||||
// (#55). An already-set WGPU_BACKEND always wins.
|
||||
if let Some(backend) = &args.backend {
|
||||
std::env::set_var("WGPU_BACKEND", backend);
|
||||
} else if args.safe_mode {
|
||||
std::env::set_var("WGPU_BACKEND", "gl");
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
if std::env::var_os("WGPU_BACKEND").is_none() {
|
||||
std::env::set_var("WGPU_BACKEND", "dx12,vulkan");
|
||||
}
|
||||
|
||||
// Headless modes exit without ever creating a window.
|
||||
if args.serve {
|
||||
// `app::serve` reads --port itself from the raw args.
|
||||
app::serve();
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(io) = &args.export {
|
||||
// clap enforces exactly two values for --export.
|
||||
let code = app::export_headless(&io[0], &io[1]);
|
||||
std::process::exit(code);
|
||||
}
|
||||
|
||||
// GUI: stash the startup config for `app::boot` to pick up.
|
||||
let script_lines = args
|
||||
.script
|
||||
.as_ref()
|
||||
.map(|p| match std::fs::read_to_string(p) {
|
||||
Ok(text) => text
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|l| !l.is_empty() && !l.starts_with('#'))
|
||||
.map(str::to_string)
|
||||
.collect(),
|
||||
Err(e) => {
|
||||
eprintln!("--script: cannot read {}: {e}", p.display());
|
||||
Vec::new()
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let _ = cli::GUI_CONFIG.set(cli::GuiConfig {
|
||||
file: if args.new { None } else { args.file },
|
||||
new: args.new,
|
||||
read_only: args.read_only,
|
||||
script_lines,
|
||||
});
|
||||
app::run()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue