elit 3.5.6 → 3.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/desktop/build.rs +83 -0
- package/desktop/icon.rs +106 -0
- package/desktop/lib.rs +2 -0
- package/desktop/main.rs +235 -0
- package/desktop/native_main.rs +128 -0
- package/desktop/native_renderer/action_widgets.rs +184 -0
- package/desktop/native_renderer/app_models.rs +171 -0
- package/desktop/native_renderer/app_runtime.rs +140 -0
- package/desktop/native_renderer/container_rendering.rs +610 -0
- package/desktop/native_renderer/content_widgets.rs +634 -0
- package/desktop/native_renderer/css_models.rs +371 -0
- package/desktop/native_renderer/embedded_surfaces.rs +414 -0
- package/desktop/native_renderer/form_controls.rs +516 -0
- package/desktop/native_renderer/interaction_dispatch.rs +89 -0
- package/desktop/native_renderer/runtime_support.rs +135 -0
- package/desktop/native_renderer/utilities.rs +495 -0
- package/desktop/native_renderer/vector_drawing.rs +491 -0
- package/desktop/native_renderer.rs +4122 -0
- package/desktop/runtime/external.rs +422 -0
- package/desktop/runtime/mod.rs +67 -0
- package/desktop/runtime/quickjs.rs +106 -0
- package/desktop/window.rs +383 -0
- package/dist/build.d.ts +1 -1
- package/dist/cli.cjs +16 -2
- package/dist/cli.mjs +16 -2
- package/dist/config.d.ts +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/desktop-auto-render.cjs +2370 -0
- package/dist/desktop-auto-render.d.ts +13 -0
- package/dist/desktop-auto-render.js +2341 -0
- package/dist/desktop-auto-render.mjs +2344 -0
- package/dist/render-context.cjs +118 -0
- package/dist/render-context.d.ts +39 -0
- package/dist/render-context.js +77 -0
- package/dist/render-context.mjs +87 -0
- package/dist/{server-CNgDUgSZ.d.ts → server-FCdUqabc.d.ts} +1 -1
- package/dist/server.d.ts +1 -1
- package/package.json +26 -3
- package/dist/build.d.mts +0 -20
- package/dist/chokidar.d.mts +0 -134
- package/dist/cli.d.mts +0 -81
- package/dist/config.d.mts +0 -254
- package/dist/coverage.d.mts +0 -85
- package/dist/database.d.mts +0 -52
- package/dist/desktop.d.mts +0 -68
- package/dist/dom.d.mts +0 -87
- package/dist/el.d.mts +0 -208
- package/dist/fs.d.mts +0 -255
- package/dist/hmr.d.mts +0 -38
- package/dist/http.d.mts +0 -169
- package/dist/https.d.mts +0 -108
- package/dist/index.d.mts +0 -13
- package/dist/mime-types.d.mts +0 -48
- package/dist/native.d.mts +0 -136
- package/dist/path.d.mts +0 -163
- package/dist/router.d.mts +0 -49
- package/dist/runtime.d.mts +0 -97
- package/dist/server-D0Dp4R5z.d.mts +0 -449
- package/dist/server.d.mts +0 -7
- package/dist/state.d.mts +0 -117
- package/dist/style.d.mts +0 -232
- package/dist/test-reporter.d.mts +0 -77
- package/dist/test-runtime.d.mts +0 -122
- package/dist/test.d.mts +0 -39
- package/dist/types.d.mts +0 -586
- package/dist/universal.d.mts +0 -21
- package/dist/ws.d.mts +0 -200
- package/dist/wss.d.mts +0 -108
- package/src/build.ts +0 -362
- package/src/chokidar.ts +0 -427
- package/src/cli.ts +0 -1162
- package/src/config.ts +0 -509
- package/src/coverage.ts +0 -1479
- package/src/database.ts +0 -1410
- package/src/desktop-auto-render.ts +0 -317
- package/src/desktop-cli.ts +0 -1533
- package/src/desktop.ts +0 -99
- package/src/dev-build.ts +0 -340
- package/src/dom.ts +0 -901
- package/src/el.ts +0 -183
- package/src/fs.ts +0 -609
- package/src/hmr.ts +0 -149
- package/src/http.ts +0 -856
- package/src/https.ts +0 -411
- package/src/index.ts +0 -16
- package/src/mime-types.ts +0 -222
- package/src/mobile-cli.ts +0 -2313
- package/src/native-background.ts +0 -444
- package/src/native-border.ts +0 -343
- package/src/native-canvas.ts +0 -260
- package/src/native-cli.ts +0 -414
- package/src/native-color.ts +0 -904
- package/src/native-estimation.ts +0 -194
- package/src/native-grid.ts +0 -590
- package/src/native-interaction.ts +0 -1289
- package/src/native-layout.ts +0 -568
- package/src/native-link.ts +0 -76
- package/src/native-render-support.ts +0 -361
- package/src/native-spacing.ts +0 -231
- package/src/native-state.ts +0 -318
- package/src/native-strings.ts +0 -46
- package/src/native-transform.ts +0 -120
- package/src/native-types.ts +0 -439
- package/src/native-typography.ts +0 -254
- package/src/native-units.ts +0 -441
- package/src/native-vector.ts +0 -910
- package/src/native.ts +0 -5606
- package/src/path.ts +0 -493
- package/src/pm-cli.ts +0 -2498
- package/src/preview-build.ts +0 -294
- package/src/render-context.ts +0 -138
- package/src/router.ts +0 -260
- package/src/runtime.ts +0 -97
- package/src/server.ts +0 -2294
- package/src/state.ts +0 -556
- package/src/style.ts +0 -1790
- package/src/test-globals.d.ts +0 -184
- package/src/test-reporter.ts +0 -609
- package/src/test-runtime.ts +0 -1359
- package/src/test.ts +0 -368
- package/src/types.ts +0 -381
- package/src/universal.ts +0 -81
- package/src/wapk-cli.ts +0 -3213
- package/src/workspace-package.ts +0 -102
- package/src/ws.ts +0 -648
- package/src/wss.ts +0 -241
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -955,7 +955,7 @@ The package also exports `elit/test`, `elit/test-runtime`, and `elit/test-report
|
|
|
955
955
|
|
|
956
956
|
Latest release notes live in [CHANGELOG.md](CHANGELOG.md).
|
|
957
957
|
|
|
958
|
-
Highlights in `v3.5.
|
|
958
|
+
Highlights in `v3.5.8`:
|
|
959
959
|
|
|
960
960
|
- Added `elit pm` for detached background process management of shell commands, file targets, and WAPK apps.
|
|
961
961
|
- Added `pm.apps[]` and `pm.dataDir` in `elit.config.*` for config-first process manager workflows.
|
package/desktop/build.rs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#[cfg(windows)]
|
|
2
|
+
fn main() {
|
|
3
|
+
use std::env;
|
|
4
|
+
use std::fs::File;
|
|
5
|
+
use std::io::BufWriter;
|
|
6
|
+
use std::path::{Path, PathBuf};
|
|
7
|
+
|
|
8
|
+
#[path = "icon.rs"]
|
|
9
|
+
mod icon;
|
|
10
|
+
|
|
11
|
+
use image::ImageFormat;
|
|
12
|
+
use icon::load_icon_bitmap;
|
|
13
|
+
|
|
14
|
+
println!("cargo:rerun-if-env-changed=ELIT_DESKTOP_EXE_ICON");
|
|
15
|
+
println!("cargo:rerun-if-env-changed=WAPK_EXE_ICON");
|
|
16
|
+
|
|
17
|
+
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()));
|
|
18
|
+
|
|
19
|
+
let mut icon_path = env::var("ELIT_DESKTOP_EXE_ICON")
|
|
20
|
+
.ok()
|
|
21
|
+
.or_else(|| env::var("WAPK_EXE_ICON").ok())
|
|
22
|
+
.map(PathBuf::from);
|
|
23
|
+
|
|
24
|
+
if icon_path.is_none() {
|
|
25
|
+
let ico = manifest_dir.join("icon.ico");
|
|
26
|
+
let png = manifest_dir.join("icon.png");
|
|
27
|
+
let svg = manifest_dir.join("icon.svg");
|
|
28
|
+
if ico.exists() {
|
|
29
|
+
icon_path = Some(ico);
|
|
30
|
+
} else if png.exists() {
|
|
31
|
+
icon_path = Some(png);
|
|
32
|
+
} else if svg.exists() {
|
|
33
|
+
icon_path = Some(svg);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let Some(icon_path) = icon_path else {
|
|
38
|
+
return;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
println!("cargo:rerun-if-changed={}", icon_path.display());
|
|
42
|
+
|
|
43
|
+
let resource_icon = match icon_path.extension().and_then(|s| s.to_str()).map(|s| s.to_ascii_lowercase()) {
|
|
44
|
+
Some(ext) if ext == "ico" => Some(icon_path.clone()),
|
|
45
|
+
Some(ext) if ext == "png" || ext == "svg" => {
|
|
46
|
+
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap_or_else(|_| ".".to_string()));
|
|
47
|
+
let out_ico = out_dir.join("wapk-auto-icon.ico");
|
|
48
|
+
if let Err(err) = convert_icon_to_ico(&icon_path, &out_ico) {
|
|
49
|
+
println!("cargo:warning=Failed to convert icon to ICO: {err}");
|
|
50
|
+
None
|
|
51
|
+
} else {
|
|
52
|
+
Some(out_ico)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
_ => {
|
|
56
|
+
println!(
|
|
57
|
+
"cargo:warning=Unsupported icon format for ELIT_DESKTOP_EXE_ICON: {}",
|
|
58
|
+
icon_path.display()
|
|
59
|
+
);
|
|
60
|
+
None
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if let Some(icon_file) = resource_icon {
|
|
65
|
+
let mut res = winres::WindowsResource::new();
|
|
66
|
+
res.set_icon(icon_file.to_string_lossy().as_ref());
|
|
67
|
+
if let Err(err) = res.compile() {
|
|
68
|
+
println!("cargo:warning=Failed to embed exe icon resource: {err}");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn convert_icon_to_ico(input_icon: &Path, output_ico: &Path) -> Result<(), String> {
|
|
73
|
+
let rgba = load_icon_bitmap(input_icon)?.into_dynamic_image()?;
|
|
74
|
+
let file = File::create(output_ico).map_err(|e| format!("create ICO failed: {e}"))?;
|
|
75
|
+
let mut writer = BufWriter::new(file);
|
|
76
|
+
rgba.write_to(&mut writer, ImageFormat::Ico)
|
|
77
|
+
.map_err(|e| format!("write ICO failed: {e}"))?;
|
|
78
|
+
Ok(())
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[cfg(not(windows))]
|
|
83
|
+
fn main() {}
|
package/desktop/icon.rs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use image::{DynamicImage, RgbaImage};
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ICON_SIDE: u32 = 256;
|
|
6
|
+
|
|
7
|
+
pub struct IconBitmap {
|
|
8
|
+
rgba: Vec<u8>,
|
|
9
|
+
width: u32,
|
|
10
|
+
height: u32,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl IconBitmap {
|
|
14
|
+
#[allow(dead_code)]
|
|
15
|
+
pub fn into_rgba(self) -> (Vec<u8>, u32, u32) {
|
|
16
|
+
(self.rgba, self.width, self.height)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[allow(dead_code)]
|
|
20
|
+
pub fn into_dynamic_image(self) -> Result<DynamicImage, String> {
|
|
21
|
+
let Some(image) = RgbaImage::from_raw(self.width, self.height, self.rgba) else {
|
|
22
|
+
return Err(String::from("icon decode produced an invalid RGBA buffer"));
|
|
23
|
+
};
|
|
24
|
+
Ok(DynamicImage::ImageRgba8(image))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn load_icon_bitmap(path: &Path) -> Result<IconBitmap, String> {
|
|
29
|
+
match icon_extension(path).as_deref() {
|
|
30
|
+
Some("svg") => load_svg_icon_bitmap(path),
|
|
31
|
+
_ => load_raster_icon_bitmap(path),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn icon_extension(path: &Path) -> Option<String> {
|
|
36
|
+
path.extension()
|
|
37
|
+
.and_then(|ext| ext.to_str())
|
|
38
|
+
.map(|ext| ext.to_ascii_lowercase())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn load_raster_icon_bitmap(path: &Path) -> Result<IconBitmap, String> {
|
|
42
|
+
let image = image::open(path)
|
|
43
|
+
.map_err(|err| format!("decode icon failed for {}: {err}", path.display()))?;
|
|
44
|
+
bitmap_from_dynamic_image(image)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn load_svg_icon_bitmap(path: &Path) -> Result<IconBitmap, String> {
|
|
48
|
+
use resvg::{tiny_skia, usvg};
|
|
49
|
+
|
|
50
|
+
let svg = std::fs::read(path)
|
|
51
|
+
.map_err(|err| format!("read SVG failed for {}: {err}", path.display()))?;
|
|
52
|
+
let options = usvg::Options::default();
|
|
53
|
+
let tree = usvg::Tree::from_data(&svg, &options)
|
|
54
|
+
.map_err(|err| format!("parse SVG failed for {}: {err}", path.display()))?;
|
|
55
|
+
|
|
56
|
+
let size = tree.size();
|
|
57
|
+
let width = size.width().ceil().max(1.0) as u32;
|
|
58
|
+
let height = size.height().ceil().max(1.0) as u32;
|
|
59
|
+
let side = width.max(height).max(DEFAULT_ICON_SIDE);
|
|
60
|
+
|
|
61
|
+
let mut pixmap = tiny_skia::Pixmap::new(side, side)
|
|
62
|
+
.ok_or_else(|| format!("allocate SVG pixmap failed for {}", path.display()))?;
|
|
63
|
+
|
|
64
|
+
let scale = (side as f32 / width as f32).min(side as f32 / height as f32);
|
|
65
|
+
let scaled_width = width as f32 * scale;
|
|
66
|
+
let scaled_height = height as f32 * scale;
|
|
67
|
+
let translate_x = ((side as f32 - scaled_width) / 2.0).max(0.0);
|
|
68
|
+
let translate_y = ((side as f32 - scaled_height) / 2.0).max(0.0);
|
|
69
|
+
let transform = tiny_skia::Transform::from_translate(translate_x, translate_y)
|
|
70
|
+
.post_scale(scale, scale);
|
|
71
|
+
|
|
72
|
+
resvg::render(&tree, transform, &mut pixmap.as_mut());
|
|
73
|
+
|
|
74
|
+
Ok(IconBitmap {
|
|
75
|
+
rgba: pixmap.take(),
|
|
76
|
+
width: side,
|
|
77
|
+
height: side,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn bitmap_from_dynamic_image(image: DynamicImage) -> Result<IconBitmap, String> {
|
|
82
|
+
let squared = to_square_rgba(image);
|
|
83
|
+
let width = squared.width();
|
|
84
|
+
let height = squared.height();
|
|
85
|
+
|
|
86
|
+
Ok(IconBitmap {
|
|
87
|
+
rgba: squared.into_raw(),
|
|
88
|
+
width,
|
|
89
|
+
height,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn to_square_rgba(image: DynamicImage) -> RgbaImage {
|
|
94
|
+
let rgba = image.to_rgba8();
|
|
95
|
+
let (width, height) = rgba.dimensions();
|
|
96
|
+
if width == height {
|
|
97
|
+
return rgba;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let side = width.max(height);
|
|
101
|
+
let mut square = RgbaImage::new(side, side);
|
|
102
|
+
let x = (side - width) / 2;
|
|
103
|
+
let y = (side - height) / 2;
|
|
104
|
+
image::imageops::overlay(&mut square, &rgba, i64::from(x), i64::from(y));
|
|
105
|
+
square
|
|
106
|
+
}
|
package/desktop/lib.rs
ADDED
package/desktop/main.rs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
|
|
2
|
+
|
|
3
|
+
mod runtime;
|
|
4
|
+
mod icon;
|
|
5
|
+
mod window;
|
|
6
|
+
|
|
7
|
+
use std::env;
|
|
8
|
+
|
|
9
|
+
const EMBED_MAGIC_V1: &[u8; 8] = b"WAPKJS\x00\x01";
|
|
10
|
+
const EMBED_MAGIC_V2: &[u8; 8] = b"WAPKRT\x00\x02";
|
|
11
|
+
|
|
12
|
+
struct EmbeddedApp {
|
|
13
|
+
runtime: String,
|
|
14
|
+
code: String,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn embedded_runtime_name(code: u8) -> Option<&'static str> {
|
|
18
|
+
match code {
|
|
19
|
+
1 => Some("quickjs"),
|
|
20
|
+
2 => Some("bun"),
|
|
21
|
+
3 => Some("node"),
|
|
22
|
+
4 => Some("deno"),
|
|
23
|
+
_ => None,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn read_embedded_js(
|
|
28
|
+
file: &mut std::fs::File,
|
|
29
|
+
file_size: u64,
|
|
30
|
+
js_size: usize,
|
|
31
|
+
trailer_size: usize,
|
|
32
|
+
) -> Option<String> {
|
|
33
|
+
use std::io::{Read, Seek, SeekFrom};
|
|
34
|
+
|
|
35
|
+
let js_offset = (file_size as usize).checked_sub(trailer_size + js_size)?;
|
|
36
|
+
file.seek(SeekFrom::Start(js_offset as u64)).ok()?;
|
|
37
|
+
|
|
38
|
+
let mut js_bytes = vec![0u8; js_size];
|
|
39
|
+
file.read_exact(&mut js_bytes).ok()?;
|
|
40
|
+
String::from_utf8(js_bytes).ok()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Read JS embedded in this binary (appended after the PE).
|
|
44
|
+
/// V1 layout: [exe bytes][js bytes][js_size: u64 LE][EMBED_MAGIC_V1: 8 bytes]
|
|
45
|
+
/// V2 layout: [exe bytes][js bytes][js_size: u64 LE][runtime_code: u8][EMBED_MAGIC_V2: 8 bytes]
|
|
46
|
+
fn try_embedded_app() -> Option<EmbeddedApp> {
|
|
47
|
+
use std::io::{Read, Seek, SeekFrom};
|
|
48
|
+
|
|
49
|
+
let exe_path = std::env::current_exe().ok()?;
|
|
50
|
+
let mut f = std::fs::File::open(&exe_path).ok()?;
|
|
51
|
+
let file_size = f.metadata().ok()?.len();
|
|
52
|
+
|
|
53
|
+
if file_size >= 17 {
|
|
54
|
+
f.seek(SeekFrom::End(-17)).ok()?;
|
|
55
|
+
let mut trailer = [0u8; 17];
|
|
56
|
+
f.read_exact(&mut trailer).ok()?;
|
|
57
|
+
|
|
58
|
+
if &trailer[9..17] == EMBED_MAGIC_V2 {
|
|
59
|
+
let js_size = u64::from_le_bytes(trailer[0..8].try_into().ok()?) as usize;
|
|
60
|
+
let runtime = embedded_runtime_name(trailer[8])?.to_string();
|
|
61
|
+
let code = read_embedded_js(&mut f, file_size, js_size, 17)?;
|
|
62
|
+
return Some(EmbeddedApp { runtime, code });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if file_size >= 16 {
|
|
67
|
+
f.seek(SeekFrom::End(-16)).ok()?;
|
|
68
|
+
let mut trailer = [0u8; 16];
|
|
69
|
+
f.read_exact(&mut trailer).ok()?;
|
|
70
|
+
|
|
71
|
+
if &trailer[8..16] == EMBED_MAGIC_V1 {
|
|
72
|
+
let js_size = u64::from_le_bytes(trailer[0..8].try_into().ok()?) as usize;
|
|
73
|
+
let code = read_embedded_js(&mut f, file_size, js_size, 16)?;
|
|
74
|
+
return Some(EmbeddedApp {
|
|
75
|
+
runtime: String::from("quickjs"),
|
|
76
|
+
code,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
None
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn print_help() {
|
|
85
|
+
eprintln!(
|
|
86
|
+
r#"elit-desktop — native WebView runtime for Elit
|
|
87
|
+
|
|
88
|
+
Usage:
|
|
89
|
+
elit-desktop [OPTIONS] <script.js>
|
|
90
|
+
|
|
91
|
+
Options:
|
|
92
|
+
--runtime <name> JS runtime to use (default: quickjs)
|
|
93
|
+
Choices: quickjs | bun | node | deno
|
|
94
|
+
--help Show this help
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
elit-desktop app.js
|
|
98
|
+
elit-desktop --runtime bun app.js
|
|
99
|
+
elit-desktop --runtime node app.js
|
|
100
|
+
"#
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Look for `<exe-stem>.js` next to the running binary (enables standalone app.exe).
|
|
105
|
+
fn auto_detect_script() -> Option<String> {
|
|
106
|
+
let exe = std::env::current_exe().ok()?;
|
|
107
|
+
let stem = exe.file_stem()?.to_str()?.to_string();
|
|
108
|
+
let dir = exe.parent()?;
|
|
109
|
+
let candidate = dir.join(format!("{}.js", stem));
|
|
110
|
+
if candidate.exists() {
|
|
111
|
+
Some(candidate.to_string_lossy().into_owned())
|
|
112
|
+
} else {
|
|
113
|
+
None
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn parse_args() -> (String, String) {
|
|
118
|
+
let args: Vec<String> = env::args().skip(1).collect();
|
|
119
|
+
|
|
120
|
+
// No args → try auto-detect bundled script, otherwise show help
|
|
121
|
+
if args.is_empty() {
|
|
122
|
+
if let Some(script) = auto_detect_script() {
|
|
123
|
+
return (String::from("quickjs"), script);
|
|
124
|
+
}
|
|
125
|
+
print_help();
|
|
126
|
+
std::process::exit(0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if args.iter().any(|a| a == "--help" || a == "-h") {
|
|
130
|
+
print_help();
|
|
131
|
+
std::process::exit(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let mut runtime = String::from("quickjs");
|
|
135
|
+
let mut script = String::new();
|
|
136
|
+
let mut i = 0;
|
|
137
|
+
|
|
138
|
+
while i < args.len() {
|
|
139
|
+
match args[i].as_str() {
|
|
140
|
+
"--runtime" | "-r" => {
|
|
141
|
+
i += 1;
|
|
142
|
+
if i < args.len() {
|
|
143
|
+
runtime = args[i].clone();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
arg if !arg.starts_with('-') => {
|
|
147
|
+
script = arg.to_string();
|
|
148
|
+
}
|
|
149
|
+
_ => {}
|
|
150
|
+
}
|
|
151
|
+
i += 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if script.is_empty() {
|
|
155
|
+
if let Some(auto) = auto_detect_script() {
|
|
156
|
+
return (runtime, auto);
|
|
157
|
+
}
|
|
158
|
+
eprintln!("Error: no script file specified.");
|
|
159
|
+
print_help();
|
|
160
|
+
std::process::exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
(runtime, script)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn main() {
|
|
167
|
+
// ── Embedded JS (single-file distribution) ──────────────────────────────
|
|
168
|
+
if let Some(EmbeddedApp { runtime, code }) = try_embedded_app() {
|
|
169
|
+
match runtime.as_str() {
|
|
170
|
+
#[cfg(feature = "runtime-quickjs")]
|
|
171
|
+
"quickjs" => {
|
|
172
|
+
window::run(move |proxy, ipc_rx| {
|
|
173
|
+
runtime::quickjs::run(&code, proxy, ipc_rx);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[cfg(feature = "runtime-external")]
|
|
178
|
+
rt @ ("bun" | "node" | "nodejs" | "deno") => {
|
|
179
|
+
let name = rt.to_string();
|
|
180
|
+
window::run(move |proxy, ipc_rx| {
|
|
181
|
+
runtime::external::run_embedded(&name, &code, proxy, ipc_rx);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
other => {
|
|
186
|
+
eprintln!(
|
|
187
|
+
"Error: embedded runtime '{}' is not available in this build.",
|
|
188
|
+
other
|
|
189
|
+
);
|
|
190
|
+
std::process::exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let (runtime_name, script_path) = parse_args();
|
|
197
|
+
|
|
198
|
+
// Validate file exists
|
|
199
|
+
if !std::path::Path::new(&script_path).exists() {
|
|
200
|
+
eprintln!("Error: '{}' not found.", script_path);
|
|
201
|
+
std::process::exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
match runtime_name.as_str() {
|
|
205
|
+
// ── Embedded QuickJS (default) ──────────────────────────────────────
|
|
206
|
+
#[cfg(feature = "runtime-quickjs")]
|
|
207
|
+
"quickjs" => {
|
|
208
|
+
let code = std::fs::read_to_string(&script_path)
|
|
209
|
+
.unwrap_or_else(|_| panic!("Cannot read '{}'", script_path));
|
|
210
|
+
|
|
211
|
+
window::run(move |proxy, ipc_rx| {
|
|
212
|
+
runtime::quickjs::run(&code, proxy, ipc_rx);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── External runtime (Bun / Node / Deno) ────────────────────────────
|
|
217
|
+
#[cfg(feature = "runtime-external")]
|
|
218
|
+
rt @ ("bun" | "node" | "nodejs" | "deno") => {
|
|
219
|
+
let path = script_path.clone();
|
|
220
|
+
let name = rt.to_string();
|
|
221
|
+
|
|
222
|
+
window::run(move |proxy, ipc_rx| {
|
|
223
|
+
runtime::external::run(&name, &path, proxy, ipc_rx);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
other => {
|
|
228
|
+
eprintln!(
|
|
229
|
+
"Error: unknown runtime '{}'.\nAvailable: quickjs, bun, node, deno",
|
|
230
|
+
other
|
|
231
|
+
);
|
|
232
|
+
std::process::exit(1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
|
|
2
|
+
|
|
3
|
+
use std::env;
|
|
4
|
+
|
|
5
|
+
use elit_desktop::native_renderer;
|
|
6
|
+
|
|
7
|
+
const EMBED_NATIVE_MAGIC_V1: &[u8; 8] = b"ELITNUI1";
|
|
8
|
+
|
|
9
|
+
fn read_embedded_payload(
|
|
10
|
+
file: &mut std::fs::File,
|
|
11
|
+
file_size: u64,
|
|
12
|
+
payload_size: usize,
|
|
13
|
+
trailer_size: usize,
|
|
14
|
+
) -> Option<String> {
|
|
15
|
+
use std::io::{Read, Seek, SeekFrom};
|
|
16
|
+
|
|
17
|
+
let payload_offset = (file_size as usize).checked_sub(trailer_size + payload_size)?;
|
|
18
|
+
file.seek(SeekFrom::Start(payload_offset as u64)).ok()?;
|
|
19
|
+
|
|
20
|
+
let mut payload_bytes = vec![0u8; payload_size];
|
|
21
|
+
file.read_exact(&mut payload_bytes).ok()?;
|
|
22
|
+
String::from_utf8(payload_bytes).ok()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn try_embedded_payload() -> Option<String> {
|
|
26
|
+
use std::io::{Read, Seek, SeekFrom};
|
|
27
|
+
|
|
28
|
+
let exe_path = std::env::current_exe().ok()?;
|
|
29
|
+
let mut file = std::fs::File::open(&exe_path).ok()?;
|
|
30
|
+
let file_size = file.metadata().ok()?.len();
|
|
31
|
+
|
|
32
|
+
if file_size < 16 {
|
|
33
|
+
return None;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
file.seek(SeekFrom::End(-16)).ok()?;
|
|
37
|
+
let mut trailer = [0u8; 16];
|
|
38
|
+
file.read_exact(&mut trailer).ok()?;
|
|
39
|
+
|
|
40
|
+
if &trailer[8..16] != EMBED_NATIVE_MAGIC_V1 {
|
|
41
|
+
return None;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let payload_size = u64::from_le_bytes(trailer[0..8].try_into().ok()?) as usize;
|
|
45
|
+
read_embedded_payload(&mut file, file_size, payload_size, 16)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn print_help() {
|
|
49
|
+
eprintln!(
|
|
50
|
+
r#"elit-desktop-native — native UI runtime for Elit
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
elit-desktop-native [payload.json]
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
--help Show this help
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
elit-desktop-native app.json
|
|
60
|
+
"#
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn auto_detect_payload() -> Option<String> {
|
|
65
|
+
let exe = std::env::current_exe().ok()?;
|
|
66
|
+
let stem = exe.file_stem()?.to_str()?.to_string();
|
|
67
|
+
let dir = exe.parent()?;
|
|
68
|
+
let candidate = dir.join(format!("{}.json", stem));
|
|
69
|
+
if candidate.exists() {
|
|
70
|
+
Some(candidate.to_string_lossy().into_owned())
|
|
71
|
+
} else {
|
|
72
|
+
None
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn parse_payload_path() -> String {
|
|
77
|
+
let args: Vec<String> = env::args().skip(1).collect();
|
|
78
|
+
|
|
79
|
+
if args.iter().any(|arg| arg == "--help" || arg == "-h") {
|
|
80
|
+
print_help();
|
|
81
|
+
std::process::exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if args.is_empty() {
|
|
85
|
+
if let Some(payload) = auto_detect_payload() {
|
|
86
|
+
return payload;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
print_help();
|
|
90
|
+
std::process::exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let payload_path = args
|
|
94
|
+
.iter()
|
|
95
|
+
.find(|arg| !arg.starts_with('-'))
|
|
96
|
+
.cloned();
|
|
97
|
+
|
|
98
|
+
if let Some(payload_path) = payload_path {
|
|
99
|
+
return payload_path;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if let Some(payload) = auto_detect_payload() {
|
|
103
|
+
return payload;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
eprintln!("Error: no payload file specified.");
|
|
107
|
+
print_help();
|
|
108
|
+
std::process::exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn main() {
|
|
112
|
+
let payload_json = if let Some(embedded_payload) = try_embedded_payload() {
|
|
113
|
+
embedded_payload
|
|
114
|
+
} else {
|
|
115
|
+
let payload_path = parse_payload_path();
|
|
116
|
+
std::fs::read_to_string(&payload_path).unwrap_or_else(|error| {
|
|
117
|
+
eprintln!("Error: failed to read native desktop payload '{}': {}", payload_path, error);
|
|
118
|
+
std::process::exit(1);
|
|
119
|
+
})
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let payload: native_renderer::NativeDesktopPayload = serde_json::from_str(&payload_json).unwrap_or_else(|error| {
|
|
123
|
+
eprintln!("Error: failed to parse native desktop payload: {}", error);
|
|
124
|
+
std::process::exit(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
native_renderer::run(payload);
|
|
128
|
+
}
|