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.
Files changed (128) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +1 -1
  3. package/desktop/build.rs +83 -0
  4. package/desktop/icon.rs +106 -0
  5. package/desktop/lib.rs +2 -0
  6. package/desktop/main.rs +235 -0
  7. package/desktop/native_main.rs +128 -0
  8. package/desktop/native_renderer/action_widgets.rs +184 -0
  9. package/desktop/native_renderer/app_models.rs +171 -0
  10. package/desktop/native_renderer/app_runtime.rs +140 -0
  11. package/desktop/native_renderer/container_rendering.rs +610 -0
  12. package/desktop/native_renderer/content_widgets.rs +634 -0
  13. package/desktop/native_renderer/css_models.rs +371 -0
  14. package/desktop/native_renderer/embedded_surfaces.rs +414 -0
  15. package/desktop/native_renderer/form_controls.rs +516 -0
  16. package/desktop/native_renderer/interaction_dispatch.rs +89 -0
  17. package/desktop/native_renderer/runtime_support.rs +135 -0
  18. package/desktop/native_renderer/utilities.rs +495 -0
  19. package/desktop/native_renderer/vector_drawing.rs +491 -0
  20. package/desktop/native_renderer.rs +4122 -0
  21. package/desktop/runtime/external.rs +422 -0
  22. package/desktop/runtime/mod.rs +67 -0
  23. package/desktop/runtime/quickjs.rs +106 -0
  24. package/desktop/window.rs +383 -0
  25. package/dist/build.d.ts +1 -1
  26. package/dist/cli.cjs +16 -2
  27. package/dist/cli.mjs +16 -2
  28. package/dist/config.d.ts +1 -1
  29. package/dist/coverage.d.ts +1 -1
  30. package/dist/desktop-auto-render.cjs +2370 -0
  31. package/dist/desktop-auto-render.d.ts +13 -0
  32. package/dist/desktop-auto-render.js +2341 -0
  33. package/dist/desktop-auto-render.mjs +2344 -0
  34. package/dist/render-context.cjs +118 -0
  35. package/dist/render-context.d.ts +39 -0
  36. package/dist/render-context.js +77 -0
  37. package/dist/render-context.mjs +87 -0
  38. package/dist/{server-CNgDUgSZ.d.ts → server-FCdUqabc.d.ts} +1 -1
  39. package/dist/server.d.ts +1 -1
  40. package/package.json +26 -3
  41. package/dist/build.d.mts +0 -20
  42. package/dist/chokidar.d.mts +0 -134
  43. package/dist/cli.d.mts +0 -81
  44. package/dist/config.d.mts +0 -254
  45. package/dist/coverage.d.mts +0 -85
  46. package/dist/database.d.mts +0 -52
  47. package/dist/desktop.d.mts +0 -68
  48. package/dist/dom.d.mts +0 -87
  49. package/dist/el.d.mts +0 -208
  50. package/dist/fs.d.mts +0 -255
  51. package/dist/hmr.d.mts +0 -38
  52. package/dist/http.d.mts +0 -169
  53. package/dist/https.d.mts +0 -108
  54. package/dist/index.d.mts +0 -13
  55. package/dist/mime-types.d.mts +0 -48
  56. package/dist/native.d.mts +0 -136
  57. package/dist/path.d.mts +0 -163
  58. package/dist/router.d.mts +0 -49
  59. package/dist/runtime.d.mts +0 -97
  60. package/dist/server-D0Dp4R5z.d.mts +0 -449
  61. package/dist/server.d.mts +0 -7
  62. package/dist/state.d.mts +0 -117
  63. package/dist/style.d.mts +0 -232
  64. package/dist/test-reporter.d.mts +0 -77
  65. package/dist/test-runtime.d.mts +0 -122
  66. package/dist/test.d.mts +0 -39
  67. package/dist/types.d.mts +0 -586
  68. package/dist/universal.d.mts +0 -21
  69. package/dist/ws.d.mts +0 -200
  70. package/dist/wss.d.mts +0 -108
  71. package/src/build.ts +0 -362
  72. package/src/chokidar.ts +0 -427
  73. package/src/cli.ts +0 -1162
  74. package/src/config.ts +0 -509
  75. package/src/coverage.ts +0 -1479
  76. package/src/database.ts +0 -1410
  77. package/src/desktop-auto-render.ts +0 -317
  78. package/src/desktop-cli.ts +0 -1533
  79. package/src/desktop.ts +0 -99
  80. package/src/dev-build.ts +0 -340
  81. package/src/dom.ts +0 -901
  82. package/src/el.ts +0 -183
  83. package/src/fs.ts +0 -609
  84. package/src/hmr.ts +0 -149
  85. package/src/http.ts +0 -856
  86. package/src/https.ts +0 -411
  87. package/src/index.ts +0 -16
  88. package/src/mime-types.ts +0 -222
  89. package/src/mobile-cli.ts +0 -2313
  90. package/src/native-background.ts +0 -444
  91. package/src/native-border.ts +0 -343
  92. package/src/native-canvas.ts +0 -260
  93. package/src/native-cli.ts +0 -414
  94. package/src/native-color.ts +0 -904
  95. package/src/native-estimation.ts +0 -194
  96. package/src/native-grid.ts +0 -590
  97. package/src/native-interaction.ts +0 -1289
  98. package/src/native-layout.ts +0 -568
  99. package/src/native-link.ts +0 -76
  100. package/src/native-render-support.ts +0 -361
  101. package/src/native-spacing.ts +0 -231
  102. package/src/native-state.ts +0 -318
  103. package/src/native-strings.ts +0 -46
  104. package/src/native-transform.ts +0 -120
  105. package/src/native-types.ts +0 -439
  106. package/src/native-typography.ts +0 -254
  107. package/src/native-units.ts +0 -441
  108. package/src/native-vector.ts +0 -910
  109. package/src/native.ts +0 -5606
  110. package/src/path.ts +0 -493
  111. package/src/pm-cli.ts +0 -2498
  112. package/src/preview-build.ts +0 -294
  113. package/src/render-context.ts +0 -138
  114. package/src/router.ts +0 -260
  115. package/src/runtime.ts +0 -97
  116. package/src/server.ts +0 -2294
  117. package/src/state.ts +0 -556
  118. package/src/style.ts +0 -1790
  119. package/src/test-globals.d.ts +0 -184
  120. package/src/test-reporter.ts +0 -609
  121. package/src/test-runtime.ts +0 -1359
  122. package/src/test.ts +0 -368
  123. package/src/types.ts +0 -381
  124. package/src/universal.ts +0 -81
  125. package/src/wapk-cli.ts +0 -3213
  126. package/src/workspace-package.ts +0 -102
  127. package/src/ws.ts +0 -648
  128. package/src/wss.ts +0 -241
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "elit-desktop"
3
- version = "3.5.6"
3
+ version = "3.5.8"
4
4
  edition = "2021"
5
5
  build = "desktop/build.rs"
6
6
 
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.6`:
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.
@@ -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() {}
@@ -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
@@ -0,0 +1,2 @@
1
+ pub mod icon;
2
+ pub mod native_renderer;
@@ -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
+ }