ruby_everywhere 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/README.md +86 -2
- data/bridge/README.md +70 -1
- data/bridge/everywhere/bridge.js +151 -0
- data/bridge/everywhere/native.css +61 -0
- data/lib/everywhere/auth_handoff.rb +8 -2
- data/lib/everywhere/builders/desktop.rb +326 -0
- data/lib/everywhere/child_processes.rb +74 -0
- data/lib/everywhere/commands/build.rb +44 -5
- data/lib/everywhere/commands/dev.rb +426 -59
- data/lib/everywhere/commands/install.rb +20 -0
- data/lib/everywhere/commands/release.rb +2 -2
- data/lib/everywhere/config.rb +298 -4
- data/lib/everywhere/console.rb +117 -0
- data/lib/everywhere/desktop_assets.rb +150 -0
- data/lib/everywhere/dock/footer.rb +150 -0
- data/lib/everywhere/dock/screen.rb +114 -0
- data/lib/everywhere/dock/state.rb +59 -0
- data/lib/everywhere/dock.rb +238 -0
- data/lib/everywhere/emulator.rb +2 -2
- data/lib/everywhere/fatal.rb +20 -0
- data/lib/everywhere/line_pump.rb +89 -0
- data/lib/everywhere/log_filter.rb +37 -0
- data/lib/everywhere/native_helper.rb +38 -5
- data/lib/everywhere/paths.rb +62 -11
- data/lib/everywhere/relay.rb +77 -0
- data/lib/everywhere/shellout.rb +90 -15
- data/lib/everywhere/task_pool.rb +123 -0
- data/lib/everywhere/ui.rb +54 -11
- data/lib/everywhere/version.rb +1 -1
- data/support/desktop/README.md +121 -0
- data/support/{shell → desktop}/src-tauri/Cargo.lock +23 -0
- data/support/{shell → desktop}/src-tauri/Cargo.toml +19 -1
- data/support/{shell → desktop}/src-tauri/capabilities/default.json +7 -0
- data/support/desktop/src-tauri/gen/schemas/capabilities.json +1 -0
- data/support/desktop/src-tauri/src/extension_host.rs +53 -0
- data/support/desktop/src-tauri/src/extensions/mod.rs +39 -0
- data/support/{shell → desktop}/src-tauri/src/main.rs +355 -10
- data/support/{shell → desktop}/src-tauri/tauri.conf.json +2 -2
- data/support/{macos → release/macos}/notarize.sh +2 -2
- metadata +31 -17
- data/support/github/build.yml +0 -85
- data/support/shell/src-tauri/gen/schemas/capabilities.json +0 -1
- /data/support/{shell → desktop}/splash/index.html +0 -0
- /data/support/{shell → desktop}/src-tauri/build.rs +0 -0
- /data/support/{shell → desktop}/src-tauri/gen/schemas/acl-manifests.json +0 -0
- /data/support/{shell → desktop}/src-tauri/gen/schemas/desktop-schema.json +0 -0
- /data/support/{shell → desktop}/src-tauri/gen/schemas/macOS-schema.json +0 -0
- /data/support/{shell → desktop}/src-tauri/icons/icon.png +0 -0
- /data/support/{shell → desktop}/src-tauri/src/updater.rs +0 -0
- /data/support/{macos → release/macos}/entitlements.plist +0 -0
|
@@ -25,6 +25,15 @@ use tauri::{Manager, RunEvent, WebviewUrl, WebviewWindowBuilder};
|
|
|
25
25
|
|
|
26
26
|
mod updater;
|
|
27
27
|
|
|
28
|
+
// The event bridge extension commands answer on. Frozen shell code.
|
|
29
|
+
mod extension_host;
|
|
30
|
+
|
|
31
|
+
// The app's own Rust, from native/desktop/ in the app repo. The CLI regenerates
|
|
32
|
+
// src/extensions/mod.rs on every build from what `native.desktop` declares in
|
|
33
|
+
// everywhere.yml; the copy checked in here registers nothing, so the bare
|
|
34
|
+
// template still builds. See support/desktop/README.md for the freeze contract.
|
|
35
|
+
mod extensions;
|
|
36
|
+
|
|
28
37
|
struct Sidecar(Mutex<Option<Child>>);
|
|
29
38
|
|
|
30
39
|
struct MenuEntry {
|
|
@@ -44,6 +53,28 @@ struct AppConfig {
|
|
|
44
53
|
background_light: Option<(u8, u8, u8)>,
|
|
45
54
|
menu: Vec<MenuEntry>,
|
|
46
55
|
tray: Vec<TrayEntry>,
|
|
56
|
+
window: WindowConfig,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Window chrome, from `window:` in everywhere.yml. Every field is optional and
|
|
60
|
+
// the defaults live HERE rather than in the Ruby config, so there is exactly one
|
|
61
|
+
// place each default is written down.
|
|
62
|
+
struct WindowConfig {
|
|
63
|
+
title_bar: TitleBar,
|
|
64
|
+
// Tri-state: None leaves the shell's default (show the title) alone.
|
|
65
|
+
title: Option<bool>,
|
|
66
|
+
resizable: Option<bool>,
|
|
67
|
+
size: Option<(f64, f64)>,
|
|
68
|
+
min_size: Option<(f64, f64)>,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[derive(Clone, Copy, PartialEq)]
|
|
72
|
+
enum TitleBar {
|
|
73
|
+
Decorated,
|
|
74
|
+
// macOS: traffic lights float over the page, no title text. There is no
|
|
75
|
+
// Windows/Linux equivalent, so it degrades to Frameless there.
|
|
76
|
+
Overlay,
|
|
77
|
+
Frameless,
|
|
47
78
|
}
|
|
48
79
|
|
|
49
80
|
struct TrayEntry {
|
|
@@ -112,9 +143,32 @@ fn load_config() -> AppConfig {
|
|
|
112
143
|
.collect()
|
|
113
144
|
})
|
|
114
145
|
.unwrap_or_default(),
|
|
146
|
+
// Indexing a missing key yields Null rather than panicking, so an app
|
|
147
|
+
// with no `window:` at all lands on every default below.
|
|
148
|
+
window: WindowConfig {
|
|
149
|
+
title_bar: match value["window"]["title_bar"].as_str() {
|
|
150
|
+
Some("overlay") => TitleBar::Overlay,
|
|
151
|
+
Some("frameless") => TitleBar::Frameless,
|
|
152
|
+
_ => TitleBar::Decorated,
|
|
153
|
+
},
|
|
154
|
+
title: value["window"]["title"].as_bool(),
|
|
155
|
+
resizable: value["window"]["resizable"].as_bool(),
|
|
156
|
+
size: dimensions(&value["window"]["size"]),
|
|
157
|
+
min_size: dimensions(&value["window"]["min_size"]),
|
|
158
|
+
},
|
|
115
159
|
}
|
|
116
160
|
}
|
|
117
161
|
|
|
162
|
+
// A [width, height] pair. The Ruby side already rejects the near-misses with a
|
|
163
|
+
// readable error; this is the belt-and-braces so a hand-edited everywhere.json
|
|
164
|
+
// can't produce a zero-pixel window.
|
|
165
|
+
fn dimensions(value: &serde_json::Value) -> Option<(f64, f64)> {
|
|
166
|
+
let pair = value.as_array()?;
|
|
167
|
+
let width = pair.first()?.as_f64()?;
|
|
168
|
+
let height = pair.get(1)?.as_f64()?;
|
|
169
|
+
(width > 0.0 && height > 0.0).then_some((width, height))
|
|
170
|
+
}
|
|
171
|
+
|
|
118
172
|
fn parse_hex(hex: &str) -> Option<(u8, u8, u8)> {
|
|
119
173
|
let h = hex.trim_start_matches('#');
|
|
120
174
|
match h.len() {
|
|
@@ -130,6 +184,92 @@ fn parse_hex(hex: &str) -> Option<(u8, u8, u8)> {
|
|
|
130
184
|
}
|
|
131
185
|
}
|
|
132
186
|
|
|
187
|
+
// --- app assets (native/desktop/assets) -------------------------------------
|
|
188
|
+
//
|
|
189
|
+
// Images the APP ships for the SHELL to draw: the tray icon, splash artwork,
|
|
190
|
+
// anything extension Rust loads. Not for pages — those keep using the asset
|
|
191
|
+
// pipeline, which already fingerprints and serves them.
|
|
192
|
+
|
|
193
|
+
// Same resolution order as load_config, for the same reason: under `every dev`
|
|
194
|
+
// there is no .app to look inside, so the CLI stages the folder and points here.
|
|
195
|
+
fn assets_dir() -> Option<PathBuf> {
|
|
196
|
+
if let Ok(dir) = std::env::var("NATIVE_ASSETS_DIR") {
|
|
197
|
+
let path = PathBuf::from(dir);
|
|
198
|
+
if path.is_dir() {
|
|
199
|
+
return Some(path);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let exe = std::env::current_exe().ok()?;
|
|
203
|
+
let path = exe.parent()?.join("../Resources/assets");
|
|
204
|
+
path.is_dir().then_some(path)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Resolve a logical asset name to a file. The folder is flat and the suffixes
|
|
208
|
+
// carry the variants (<name>[@2x|@3x][~dark].<ext>) — the CLI validated them,
|
|
209
|
+
// so anything unparseable here simply doesn't match.
|
|
210
|
+
//
|
|
211
|
+
// Highest scale wins: nothing at this layer knows which display the image will
|
|
212
|
+
// land on, and downscaling a @3x looks far better than upscaling a 1x.
|
|
213
|
+
fn asset_path(name: &str, dark: bool) -> Option<PathBuf> {
|
|
214
|
+
let dir = assets_dir()?;
|
|
215
|
+
let mut best: Option<(u8, PathBuf)> = None;
|
|
216
|
+
for entry in std::fs::read_dir(dir).ok()?.flatten() {
|
|
217
|
+
let path = entry.path();
|
|
218
|
+
let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
|
|
219
|
+
continue;
|
|
220
|
+
};
|
|
221
|
+
let (base, scale, is_dark) = parse_asset_stem(stem);
|
|
222
|
+
if base != name || is_dark != dark {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if best.as_ref().map_or(true, |(best_scale, _)| scale > *best_scale) {
|
|
226
|
+
best = Some((scale, path));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
best.map(|(_, path)| path)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// The ~dark variant when the app shipped one, else the plain asset.
|
|
233
|
+
fn asset(name: &str, dark: bool) -> Option<PathBuf> {
|
|
234
|
+
if dark {
|
|
235
|
+
if let Some(path) = asset_path(name, true) {
|
|
236
|
+
return Some(path);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
asset_path(name, false)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Trailing @2x/@3x and ~dark markers, in either order. Mirrors the Ruby
|
|
243
|
+
// DesktopAssets#parse — one filename convention across all three platforms.
|
|
244
|
+
fn parse_asset_stem(stem: &str) -> (String, u8, bool) {
|
|
245
|
+
let mut name = stem.to_string();
|
|
246
|
+
let mut scale = 1u8;
|
|
247
|
+
let mut dark = false;
|
|
248
|
+
loop {
|
|
249
|
+
if let Some(rest) = name.strip_suffix("~dark") {
|
|
250
|
+
dark = true;
|
|
251
|
+
name = rest.to_string();
|
|
252
|
+
} else if let Some(rest) = name.strip_suffix("@2x") {
|
|
253
|
+
scale = 2;
|
|
254
|
+
name = rest.to_string();
|
|
255
|
+
} else if let Some(rest) = name.strip_suffix("@3x") {
|
|
256
|
+
scale = 3;
|
|
257
|
+
name = rest.to_string();
|
|
258
|
+
} else {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
(name, scale, dark)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn asset_mime(path: &Path) -> &'static str {
|
|
266
|
+
match path.extension().and_then(|e| e.to_str()).unwrap_or("").to_ascii_lowercase().as_str() {
|
|
267
|
+
"jpg" | "jpeg" => "image/jpeg",
|
|
268
|
+
"ico" => "image/x-icon",
|
|
269
|
+
_ => "image/png",
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
133
273
|
fn sidecar_log_path(app_data: &Path) -> PathBuf {
|
|
134
274
|
let dir = app_data.join("log");
|
|
135
275
|
let _ = std::fs::create_dir_all(&dir);
|
|
@@ -216,7 +356,7 @@ fn main() {
|
|
|
216
356
|
.plugin(tauri_plugin_dialog::init())
|
|
217
357
|
.plugin(tauri_plugin_clipboard_manager::init())
|
|
218
358
|
.plugin(window_state)
|
|
219
|
-
.invoke_handler(
|
|
359
|
+
.invoke_handler(extensions::handler())
|
|
220
360
|
.setup(move |app| {
|
|
221
361
|
// A persisted user channel choice (Everywhere.updates.setChannel)
|
|
222
362
|
// overrides everywhere.yml's updates.channel. Fold it in before
|
|
@@ -247,6 +387,12 @@ fn main() {
|
|
|
247
387
|
build_menu(app, &config)?;
|
|
248
388
|
updater::init(app, &config.raw_json);
|
|
249
389
|
|
|
390
|
+
// The app's own Rust gets the handle before any window exists, so it
|
|
391
|
+
// can register plugins, open ports, or spawn workers that the first
|
|
392
|
+
// page load already depends on. It also installs the event-based
|
|
393
|
+
// command transport remote-mode pages use.
|
|
394
|
+
extensions::setup(app)?;
|
|
395
|
+
|
|
250
396
|
// Dev mode (`every dev`): no sidecar, just point the webview at the
|
|
251
397
|
// developer's normal dev server.
|
|
252
398
|
if let Ok(dev_url) = std::env::var("NATIVE_DEV_URL") {
|
|
@@ -401,6 +547,17 @@ fn main() {
|
|
|
401
547
|
.build(tauri::generate_context!())
|
|
402
548
|
.expect("error while building tauri application")
|
|
403
549
|
.run(|app, event| {
|
|
550
|
+
// Tauri applies its own compiled-in icons/icon.png to the Dock as
|
|
551
|
+
// part of finishing launch — AFTER setup() — so setting ours there
|
|
552
|
+
// gets stomped. Ready is the first point we're guaranteed to win.
|
|
553
|
+
#[cfg(target_os = "macos")]
|
|
554
|
+
if let RunEvent::Ready = event {
|
|
555
|
+
if let Ok(icon) = std::env::var("NATIVE_ICON_PATH") {
|
|
556
|
+
if !icon.is_empty() {
|
|
557
|
+
set_dock_icon(&icon);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
404
561
|
if let RunEvent::Exit = event {
|
|
405
562
|
// A fully staged silent update (updates.auto: install) swaps
|
|
406
563
|
// in now — two renames, no perceptible quit latency.
|
|
@@ -581,8 +738,28 @@ fn build_tray(app: &tauri::App, config: &AppConfig) -> tauri::Result<()> {
|
|
|
581
738
|
.menu(&menu)
|
|
582
739
|
.show_menu_on_left_click(true)
|
|
583
740
|
.tooltip(&config.name);
|
|
584
|
-
|
|
585
|
-
|
|
741
|
+
|
|
742
|
+
// A `tray` asset (native/desktop/assets/tray.png) beats the app icon here.
|
|
743
|
+
// An app icon is a 1024px full-colour square; macOS squeezes that into
|
|
744
|
+
// ~22pt of menu bar and it turns to mush.
|
|
745
|
+
//
|
|
746
|
+
// `tray-template` is the macOS convention instead: a black-and-alpha image
|
|
747
|
+
// the system recolours itself, so it stays legible in a light menu bar, a
|
|
748
|
+
// dark one, and under a highlight. Two names rather than a config key —
|
|
749
|
+
// whether an image is a template is a property of the image.
|
|
750
|
+
let (template, icon_path) = match asset("tray-template", false) {
|
|
751
|
+
Some(path) => (true, Some(path)),
|
|
752
|
+
None => (false, asset("tray", false)),
|
|
753
|
+
};
|
|
754
|
+
match icon_path.and_then(|path| tauri::image::Image::from_path(path).ok()) {
|
|
755
|
+
Some(icon) => {
|
|
756
|
+
tray = tray.icon(icon).icon_as_template(template);
|
|
757
|
+
}
|
|
758
|
+
None => {
|
|
759
|
+
if let Some(icon) = app.default_window_icon() {
|
|
760
|
+
tray = tray.icon(icon.clone());
|
|
761
|
+
}
|
|
762
|
+
}
|
|
586
763
|
}
|
|
587
764
|
tray.on_menu_event(|app, event| handle_menu_id(app, event.id().0.as_str()))
|
|
588
765
|
.build(app)?;
|
|
@@ -644,6 +821,10 @@ const INIT_SCRIPT: &str = r#"
|
|
|
644
821
|
// fall back to the app name, suffix "(dev)" in dev mode.
|
|
645
822
|
const syncTitle = () => {
|
|
646
823
|
try {
|
|
824
|
+
// `window.title: false` means the window has no title text, so don't put
|
|
825
|
+
// one back on the next page load — this runs on every visit and would
|
|
826
|
+
// otherwise quietly undo what build_window set.
|
|
827
|
+
if (cfg.window && cfg.window.title === false) return;
|
|
647
828
|
const base = document.title || cfg.name || "";
|
|
648
829
|
const title = cfg.dev ? base + " (dev)" : base;
|
|
649
830
|
if (title && window.__TAURI__ && window.__TAURI__.window) {
|
|
@@ -658,6 +839,53 @@ const INIT_SCRIPT: &str = r#"
|
|
|
658
839
|
});
|
|
659
840
|
document.addEventListener('turbo:load', syncTitle);
|
|
660
841
|
|
|
842
|
+
// Overlay and frameless windows have no system title bar left to grab: the
|
|
843
|
+
// webview covers that strip, so every mousedown in it belongs to the page and
|
|
844
|
+
// the window stops being draggable. Rather than make every app hand-roll a
|
|
845
|
+
// drag region just to hide a title, the top strip drags the window by default.
|
|
846
|
+
//
|
|
847
|
+
// Interactive elements keep their clicks — a toolbar button up there still
|
|
848
|
+
// works — and `window.drag_height: 0` in everywhere.yml turns it off for an
|
|
849
|
+
// app that wants to own the whole area itself.
|
|
850
|
+
const dragTop = () => {
|
|
851
|
+
const win = cfg.window || {};
|
|
852
|
+
if (!win.title_bar || win.title_bar === 'decorated') return;
|
|
853
|
+
const height = win.drag_height === undefined ? 28 : win.drag_height;
|
|
854
|
+
if (!height) return;
|
|
855
|
+
|
|
856
|
+
const NO_DRAG = 'a,button,input,select,textarea,label,summary,[role="button"],' +
|
|
857
|
+
'[contenteditable],[data-no-drag]';
|
|
858
|
+
// data-tauri-drag-region is deliberately NOT deferred to. Tauri's own
|
|
859
|
+
// handling of it round-trips through the IPC, which is blocked on http
|
|
860
|
+
// origins — the same wall invoke hits — so on a RubyEverywhere page that
|
|
861
|
+
// attribute does nothing on its own. We honour it here instead, and extend
|
|
862
|
+
// it past the strip: marking a region draggable should work wherever it is.
|
|
863
|
+
const draggable = (e) => {
|
|
864
|
+
if (e.button !== 0 || !e.target || !e.target.closest) return false;
|
|
865
|
+
if (e.target.closest(NO_DRAG)) return false;
|
|
866
|
+
return e.target.closest('[data-tauri-drag-region]') !== null || e.clientY <= height;
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// Double-click-to-zoom is detected here rather than with a dblclick
|
|
870
|
+
// listener: startDragging() takes over the mouse on mousedown, so the
|
|
871
|
+
// dblclick event never arrives.
|
|
872
|
+
let lastDown = 0;
|
|
873
|
+
document.addEventListener('mousedown', (e) => {
|
|
874
|
+
if (!draggable(e)) return;
|
|
875
|
+
try {
|
|
876
|
+
const w = window.__TAURI__.window.getCurrentWindow();
|
|
877
|
+
e.preventDefault();
|
|
878
|
+
// Keep Tauri's own drag-region listener from starting a second drag on
|
|
879
|
+
// top of ours if a future version stops needing the IPC for it.
|
|
880
|
+
e.stopPropagation();
|
|
881
|
+
const now = Date.now();
|
|
882
|
+
if (now - lastDown < 400) { lastDown = 0; w.toggleMaximize(); }
|
|
883
|
+
else { lastDown = now; w.startDragging(); }
|
|
884
|
+
} catch (_) {}
|
|
885
|
+
});
|
|
886
|
+
};
|
|
887
|
+
dragTop();
|
|
888
|
+
|
|
661
889
|
const hookMenu = () => {
|
|
662
890
|
if (window.__TAURI__ && window.__TAURI__.event) {
|
|
663
891
|
window.__TAURI__.event.listen('everywhere:menu', (e) => {
|
|
@@ -682,6 +910,32 @@ const INIT_SCRIPT: &str = r#"
|
|
|
682
910
|
// tells it the request came from the native shell and which app version (from
|
|
683
911
|
// everywhere.yml → the submitted build) made it. Version is absent only in bare
|
|
684
912
|
// `cargo run` dev where no config was passed.
|
|
913
|
+
// --- macOS Dock icon under `every dev` --------------------------------------
|
|
914
|
+
//
|
|
915
|
+
// The app NAME is not handled here: macOS reads it from CFBundleName, and the
|
|
916
|
+
// menu bar plus the About/Hide/Quit items are built from the bundle before any
|
|
917
|
+
// of our Rust runs. (Setting NSProcessInfo's processName doesn't move them —
|
|
918
|
+
// tried it.) `every dev` solves that by running this binary from inside a
|
|
919
|
+
// throwaway .app it assembles; see Commands::Dev#stage_dev_bundle.
|
|
920
|
+
//
|
|
921
|
+
// The icon does need doing here, because Tauri bakes icons/icon.png into the
|
|
922
|
+
// binary and applies it at launch — which in dev is the gem's placeholder, and
|
|
923
|
+
// it would win over a bundle icns anyway. This replaces it with the app's own,
|
|
924
|
+
// already shaped by the CLI exactly like the packaged .icns, so the Dock looks
|
|
925
|
+
// the same in dev as it does in a release build.
|
|
926
|
+
#[cfg(target_os = "macos")]
|
|
927
|
+
fn set_dock_icon(path: &str) {
|
|
928
|
+
use objc2::{AllocAnyThread, MainThreadMarker};
|
|
929
|
+
use objc2_app_kit::{NSApplication, NSImage};
|
|
930
|
+
use objc2_foundation::NSString;
|
|
931
|
+
|
|
932
|
+
let Some(mtm) = MainThreadMarker::new() else { return };
|
|
933
|
+
let image = NSImage::initWithContentsOfFile(NSImage::alloc(), &NSString::from_str(path));
|
|
934
|
+
if let Some(image) = image {
|
|
935
|
+
unsafe { NSApplication::sharedApplication(mtm).setApplicationIconImage(Some(&image)) };
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
685
939
|
fn ua_marker(config: &AppConfig) -> String {
|
|
686
940
|
let os = std::env::consts::OS;
|
|
687
941
|
match &config.version {
|
|
@@ -739,16 +993,51 @@ fn build_window(
|
|
|
739
993
|
url: WebviewUrl,
|
|
740
994
|
) -> tauri::Result<tauri::WebviewWindow> {
|
|
741
995
|
let dev_mode = std::env::var("NATIVE_DEV_URL").is_ok();
|
|
996
|
+
let window = &config.window;
|
|
997
|
+
let (width, height) = window.size.unwrap_or((1100.0, 750.0));
|
|
742
998
|
|
|
743
999
|
let mut builder = WebviewWindowBuilder::new(app, "main", url)
|
|
744
|
-
|
|
745
|
-
|
|
1000
|
+
// `title: false` blanks the text everywhere it can still show up (the
|
|
1001
|
+
// Window menu, Mission Control) — not just in the title bar. The " (dev)"
|
|
1002
|
+
// suffix the callers add goes with it; the bridge already tells the page
|
|
1003
|
+
// it's a dev build via config.dev.
|
|
1004
|
+
.title(if window.title == Some(false) { "" } else { title })
|
|
1005
|
+
.inner_size(width, height)
|
|
746
1006
|
// Injected into every page: app config global, remote console, native
|
|
747
1007
|
// confirm default, and the menu-navigation listener.
|
|
748
1008
|
.initialization_script(INIT_SCRIPT.replace("/*__CONFIG_JSON__*/ null", &config.raw_json));
|
|
749
1009
|
if let Some((r, g, b)) = config.background_light {
|
|
750
1010
|
builder = builder.background_color(tauri::window::Color(r, g, b, 255));
|
|
751
1011
|
}
|
|
1012
|
+
if let Some((min_width, min_height)) = window.min_size {
|
|
1013
|
+
builder = builder.min_inner_size(min_width, min_height);
|
|
1014
|
+
}
|
|
1015
|
+
if let Some(resizable) = window.resizable {
|
|
1016
|
+
builder = builder.resizable(resizable);
|
|
1017
|
+
}
|
|
1018
|
+
// Title bar. Overlay is a real macOS window style — the system keeps drawing
|
|
1019
|
+
// (and positioning) the traffic lights, we just hide the title and let the
|
|
1020
|
+
// webview run full-height underneath. Windows and Linux have no equivalent,
|
|
1021
|
+
// so overlay degrades to frameless there and the page supplies its own
|
|
1022
|
+
// controls through Everywhere.window.
|
|
1023
|
+
match window.title_bar {
|
|
1024
|
+
TitleBar::Decorated => {}
|
|
1025
|
+
TitleBar::Overlay => {
|
|
1026
|
+
#[cfg(target_os = "macos")]
|
|
1027
|
+
{
|
|
1028
|
+
builder = builder
|
|
1029
|
+
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
|
1030
|
+
.hidden_title(true);
|
|
1031
|
+
}
|
|
1032
|
+
#[cfg(not(target_os = "macos"))]
|
|
1033
|
+
{
|
|
1034
|
+
builder = builder.decorations(false);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
TitleBar::Frameless => {
|
|
1038
|
+
builder = builder.decorations(false);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
752
1041
|
|
|
753
1042
|
// Cookie / session persistence: give the webview a stable data store so
|
|
754
1043
|
// logins survive relaunches. macOS carries it (and the UA marker) on a
|
|
@@ -791,8 +1080,46 @@ fn build_window(
|
|
|
791
1080
|
// everywhere.yml) loads via data: URL — no server needed before Rails boots.
|
|
792
1081
|
fn custom_splash_url() -> Option<WebviewUrl> {
|
|
793
1082
|
let exe = std::env::current_exe().ok()?;
|
|
794
|
-
let html = std::fs::
|
|
795
|
-
|
|
1083
|
+
let html = std::fs::read_to_string(exe.parent()?.join("../Resources/splash.html")).ok()?;
|
|
1084
|
+
let html = inline_splash_assets(html);
|
|
1085
|
+
format!("data:text/html;base64,{}", base64(html.as_bytes())).parse().ok().map(WebviewUrl::External)
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// A data: URL page has no base to resolve relative URLs against, so an <img
|
|
1089
|
+
// src="assets/logo.png"> in a splash would silently render nothing. Rewrite
|
|
1090
|
+
// those references into data: URIs first, so the markup works the way it reads.
|
|
1091
|
+
//
|
|
1092
|
+
// Substitution rather than parsing: the splash is one small file the app author
|
|
1093
|
+
// wrote, the asset names come from a directory we control, and a real HTML
|
|
1094
|
+
// parse would pull in a dependency to solve a problem nobody has.
|
|
1095
|
+
fn inline_splash_assets(html: String) -> String {
|
|
1096
|
+
let Some(dir) = assets_dir() else {
|
|
1097
|
+
return html;
|
|
1098
|
+
};
|
|
1099
|
+
let Ok(entries) = std::fs::read_dir(dir) else {
|
|
1100
|
+
return html;
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
let mut out = html;
|
|
1104
|
+
for entry in entries.flatten() {
|
|
1105
|
+
let path = entry.path();
|
|
1106
|
+
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
|
|
1107
|
+
continue;
|
|
1108
|
+
};
|
|
1109
|
+
if !out.contains(name) {
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
let Ok(bytes) = std::fs::read(&path) else {
|
|
1113
|
+
continue;
|
|
1114
|
+
};
|
|
1115
|
+
let uri = format!("data:{};base64,{}", asset_mime(&path), base64(&bytes));
|
|
1116
|
+
// Longest prefix first: replacing the bare "assets/…" first would strip
|
|
1117
|
+
// the path off "./assets/…" and leave a dangling "./" glued to the URI.
|
|
1118
|
+
for prefix in ["./assets/", "/assets/", "assets/"] {
|
|
1119
|
+
out = out.replace(&format!("{prefix}{name}"), &uri);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
out
|
|
796
1123
|
}
|
|
797
1124
|
|
|
798
1125
|
fn base64(data: &[u8]) -> String {
|
|
@@ -836,6 +1163,17 @@ fn authorize_remote_origin(app: &tauri::App, remote_url: &str) -> tauri::Result<
|
|
|
836
1163
|
"permissions": [
|
|
837
1164
|
"core:default",
|
|
838
1165
|
"core:window:allow-set-title",
|
|
1166
|
+
// Window controls (Everywhere.window.*): a frameless or overlay
|
|
1167
|
+
// title bar is drawn by the page, so the page must be able to drag
|
|
1168
|
+
// and close the window it lives in. Kept in lockstep with
|
|
1169
|
+
// capabilities/default.json — see the mirroring rule above.
|
|
1170
|
+
"core:window:allow-minimize",
|
|
1171
|
+
"core:window:allow-maximize",
|
|
1172
|
+
"core:window:allow-unmaximize",
|
|
1173
|
+
"core:window:allow-toggle-maximize",
|
|
1174
|
+
"core:window:allow-close",
|
|
1175
|
+
"core:window:allow-is-maximized",
|
|
1176
|
+
"core:window:allow-start-dragging",
|
|
839
1177
|
"notification:default",
|
|
840
1178
|
"dialog:default",
|
|
841
1179
|
"clipboard-manager:allow-read-text",
|
|
@@ -898,7 +1236,14 @@ fn notify(app: &tauri::AppHandle, title: &str, body: &str) {
|
|
|
898
1236
|
}
|
|
899
1237
|
}
|
|
900
1238
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1239
|
+
// The shell's own Tauri commands. In a module rather than at the crate root
|
|
1240
|
+
// because #[tauri::command] expands to helper macros that generate_handler!
|
|
1241
|
+
// resolves by path — and a command sitting in main.rs can only be named by an
|
|
1242
|
+
// absolute `crate::` path, which is exactly the one form that doesn't work from
|
|
1243
|
+
// another module. The extensions registry has to name this, so it lives here.
|
|
1244
|
+
pub(crate) mod commands {
|
|
1245
|
+
#[tauri::command]
|
|
1246
|
+
pub fn notify_command(app: tauri::AppHandle, title: String, body: String) {
|
|
1247
|
+
super::notify(&app, &title, &body);
|
|
1248
|
+
}
|
|
904
1249
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://schema.tauri.app/config/2",
|
|
3
|
-
"productName": "
|
|
3
|
+
"productName": "RubyEverywhere Shell",
|
|
4
4
|
"version": "0.2.0",
|
|
5
|
-
"identifier": "com.rubyeverywhere.
|
|
5
|
+
"identifier": "com.rubyeverywhere.shell",
|
|
6
6
|
"build": {
|
|
7
7
|
"frontendDist": "../splash"
|
|
8
8
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# easiest way is to source your release env first:
|
|
8
8
|
#
|
|
9
9
|
# source ~/Projects/atlastools.dev/atlas-workspaces/.env.release
|
|
10
|
-
# cli/support/macos/notarize.sh "example/dist/Example Notes.app"
|
|
10
|
+
# cli/support/release/macos/notarize.sh "example/dist/Example Notes.app"
|
|
11
11
|
#
|
|
12
12
|
# Required env vars (these match Tauri's names, so your existing .env works):
|
|
13
13
|
# APPLE_SIGNING_IDENTITY e.g. "Developer ID Application: Your Name (TEAMID)"
|
|
@@ -33,7 +33,7 @@ APP="${1:?usage: notarize.sh /path/to/App.app}"
|
|
|
33
33
|
# containing spaces/parens (e.g. APPLE_SIGNING_IDENTITY=Developer ID
|
|
34
34
|
# Application: Name (TEAM)) that plain `source .env.release` cannot.
|
|
35
35
|
#
|
|
36
|
-
# ENV_FILE=.env.release cli/support/macos/notarize.sh "App.app"
|
|
36
|
+
# ENV_FILE=.env.release cli/support/release/macos/notarize.sh "App.app"
|
|
37
37
|
if [ -n "${ENV_FILE:-}" ]; then
|
|
38
38
|
[ -f "$ENV_FILE" ] || { echo "ENV_FILE not found: $ENV_FILE" >&2; exit 1; }
|
|
39
39
|
while IFS= read -r line || [ -n "$line" ]; do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_everywhere
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrea Fomera
|
|
@@ -94,7 +94,9 @@ files:
|
|
|
94
94
|
- lib/everywhere/blake2b.rb
|
|
95
95
|
- lib/everywhere/boot.rb
|
|
96
96
|
- lib/everywhere/builders/android.rb
|
|
97
|
+
- lib/everywhere/builders/desktop.rb
|
|
97
98
|
- lib/everywhere/builders/ios.rb
|
|
99
|
+
- lib/everywhere/child_processes.rb
|
|
98
100
|
- lib/everywhere/cli.rb
|
|
99
101
|
- lib/everywhere/commands/build.rb
|
|
100
102
|
- lib/everywhere/commands/clean.rb
|
|
@@ -113,12 +115,20 @@ files:
|
|
|
113
115
|
- lib/everywhere/commands/shell_dir.rb
|
|
114
116
|
- lib/everywhere/commands/updates_keygen.rb
|
|
115
117
|
- lib/everywhere/config.rb
|
|
118
|
+
- lib/everywhere/console.rb
|
|
116
119
|
- lib/everywhere/database.rb
|
|
120
|
+
- lib/everywhere/desktop_assets.rb
|
|
121
|
+
- lib/everywhere/dock.rb
|
|
122
|
+
- lib/everywhere/dock/footer.rb
|
|
123
|
+
- lib/everywhere/dock/screen.rb
|
|
124
|
+
- lib/everywhere/dock/state.rb
|
|
117
125
|
- lib/everywhere/emulator.rb
|
|
118
126
|
- lib/everywhere/engine.rb
|
|
127
|
+
- lib/everywhere/fatal.rb
|
|
119
128
|
- lib/everywhere/framework.rb
|
|
120
129
|
- lib/everywhere/icon.rb
|
|
121
130
|
- lib/everywhere/ignore.rb
|
|
131
|
+
- lib/everywhere/line_pump.rb
|
|
122
132
|
- lib/everywhere/log_filter.rb
|
|
123
133
|
- lib/everywhere/minisign.rb
|
|
124
134
|
- lib/everywhere/mobile_config_endpoint.rb
|
|
@@ -132,16 +142,31 @@ files:
|
|
|
132
142
|
- lib/everywhere/png.rb
|
|
133
143
|
- lib/everywhere/raster.rb
|
|
134
144
|
- lib/everywhere/receipt.rb
|
|
145
|
+
- lib/everywhere/relay.rb
|
|
135
146
|
- lib/everywhere/s3.rb
|
|
136
147
|
- lib/everywhere/shellout.rb
|
|
137
148
|
- lib/everywhere/simulator.rb
|
|
149
|
+
- lib/everywhere/task_pool.rb
|
|
138
150
|
- lib/everywhere/ui.rb
|
|
139
151
|
- lib/everywhere/update_manifest.rb
|
|
140
152
|
- lib/everywhere/version.rb
|
|
141
153
|
- lib/ruby_everywhere.rb
|
|
142
|
-
- support/
|
|
143
|
-
- support/
|
|
144
|
-
- support/
|
|
154
|
+
- support/desktop/README.md
|
|
155
|
+
- support/desktop/splash/index.html
|
|
156
|
+
- support/desktop/src-tauri/Cargo.lock
|
|
157
|
+
- support/desktop/src-tauri/Cargo.toml
|
|
158
|
+
- support/desktop/src-tauri/build.rs
|
|
159
|
+
- support/desktop/src-tauri/capabilities/default.json
|
|
160
|
+
- support/desktop/src-tauri/gen/schemas/acl-manifests.json
|
|
161
|
+
- support/desktop/src-tauri/gen/schemas/capabilities.json
|
|
162
|
+
- support/desktop/src-tauri/gen/schemas/desktop-schema.json
|
|
163
|
+
- support/desktop/src-tauri/gen/schemas/macOS-schema.json
|
|
164
|
+
- support/desktop/src-tauri/icons/icon.png
|
|
165
|
+
- support/desktop/src-tauri/src/extension_host.rs
|
|
166
|
+
- support/desktop/src-tauri/src/extensions/mod.rs
|
|
167
|
+
- support/desktop/src-tauri/src/main.rs
|
|
168
|
+
- support/desktop/src-tauri/src/updater.rs
|
|
169
|
+
- support/desktop/src-tauri/tauri.conf.json
|
|
145
170
|
- support/mobile/android/README.md
|
|
146
171
|
- support/mobile/android/app/build.gradle.kts
|
|
147
172
|
- support/mobile/android/app/everywhere.properties
|
|
@@ -228,19 +253,8 @@ files:
|
|
|
228
253
|
- support/mobile/ios/NativeExtensions/Package.swift
|
|
229
254
|
- support/mobile/ios/NativeExtensions/Sources/NativeExtensions/Exports.swift
|
|
230
255
|
- support/mobile/ios/README.md
|
|
231
|
-
- support/
|
|
232
|
-
- support/
|
|
233
|
-
- support/shell/src-tauri/Cargo.toml
|
|
234
|
-
- support/shell/src-tauri/build.rs
|
|
235
|
-
- support/shell/src-tauri/capabilities/default.json
|
|
236
|
-
- support/shell/src-tauri/gen/schemas/acl-manifests.json
|
|
237
|
-
- support/shell/src-tauri/gen/schemas/capabilities.json
|
|
238
|
-
- support/shell/src-tauri/gen/schemas/desktop-schema.json
|
|
239
|
-
- support/shell/src-tauri/gen/schemas/macOS-schema.json
|
|
240
|
-
- support/shell/src-tauri/icons/icon.png
|
|
241
|
-
- support/shell/src-tauri/src/main.rs
|
|
242
|
-
- support/shell/src-tauri/src/updater.rs
|
|
243
|
-
- support/shell/src-tauri/tauri.conf.json
|
|
256
|
+
- support/release/macos/entitlements.plist
|
|
257
|
+
- support/release/macos/notarize.sh
|
|
244
258
|
homepage: https://rubyeverywhere.com
|
|
245
259
|
licenses:
|
|
246
260
|
- MIT
|