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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4e222fd8e74b0c921b4cdab9d681b0f06bad2d94aae8dbe999415824508a1f5
|
|
4
|
+
data.tar.gz: e3a49bb93e2e49924c70cb622943e02b9bb302ddb828d29fc4c2ee5064c01e0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5756a97bf1012daf2069fdb186de16f22a1bdbed9972ec42e384dc8b9b24a84b03b004568af26eeb6990b53336ceb3bfd6c4f3177ec9b0b43e2682db15b382a1
|
|
7
|
+
data.tar.gz: aba1e8c877b1ecd3b9b975a434c89831a9a0b302cbc2bea567472d650f28a442666735cfb9171a20aa615894c7e9bd5de52d2f9e334b7b9af00f750489fac14f
|
data/README.md
CHANGED
|
@@ -63,6 +63,7 @@ every doctor # check the toolchain before your first build
|
|
|
63
63
|
every dev # dev server + native shells on demand, live reload
|
|
64
64
|
every dev --ios # ...straight into the iOS Simulator
|
|
65
65
|
every dev --android # ...straight into the Android emulator
|
|
66
|
+
every dev --mobile # ...both at once, built in parallel
|
|
66
67
|
|
|
67
68
|
every build # package the macOS .app
|
|
68
69
|
every build --ios # build the iOS shell
|
|
@@ -78,6 +79,28 @@ boot files.
|
|
|
78
79
|
`every dev` never packages anything: it runs your ordinary dev server and points
|
|
79
80
|
the native shells at it, so the edit-refresh loop is untouched Rails.
|
|
80
81
|
|
|
82
|
+
It keeps a dock pinned to the bottom of the terminal showing what each target is
|
|
83
|
+
doing, while the logs scroll normally above it — ordinary scrollback, so you can
|
|
84
|
+
still scroll up and search a backtrace:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
web │ Started GET "/notes" for 127.0.0.1
|
|
88
|
+
ios │ ▸ compiling Swift sources
|
|
89
|
+
────────────────────────────────────────────────────────────────
|
|
90
|
+
● web :3000 ○ desktop ⠙ iOS 42s ✗ Android see dist/android-build.log
|
|
91
|
+
⌨ d desktop · i iOS · a Android · b browser · l logs · r restart · ? help · q quit
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Every target runs on its own thread, so builds happen in parallel and the keys
|
|
95
|
+
keep working while one is going. A build that fails marks that target red and
|
|
96
|
+
leaves everything else — the dev server included — running; fix the error and
|
|
97
|
+
press the key again. `r` restarts just the dev server, `?` expands the full key
|
|
98
|
+
list, and each line is tagged with the source that printed it once more than one
|
|
99
|
+
thing is talking.
|
|
100
|
+
|
|
101
|
+
Set `EVERY_NO_DOCK=1` (or pipe the output anywhere) to get plain, unadorned
|
|
102
|
+
lines instead.
|
|
103
|
+
|
|
81
104
|
## Configuration
|
|
82
105
|
|
|
83
106
|
Everything per-app lives in `config/everywhere.yml`. The native shell templates
|
|
@@ -101,6 +124,13 @@ appearance:
|
|
|
101
124
|
remote:
|
|
102
125
|
url: https://app.example.com # remote mode: the deployed app to wrap
|
|
103
126
|
|
|
127
|
+
window: # desktop window chrome
|
|
128
|
+
title_bar: overlay # decorated (default) | overlay | frameless
|
|
129
|
+
title: false # draw the title text
|
|
130
|
+
size: [1100, 750]
|
|
131
|
+
min_size: [600, 400]
|
|
132
|
+
drag_height: 28 # top strip that drags the window; 0 to opt out
|
|
133
|
+
|
|
104
134
|
tabs: # the native mobile tab bar
|
|
105
135
|
- name: Builds
|
|
106
136
|
path: /builds
|
|
@@ -117,6 +147,12 @@ rules: # Hotwire Native path configuration
|
|
|
117
147
|
properties:
|
|
118
148
|
context: modal
|
|
119
149
|
|
|
150
|
+
native: # code compiled into the native shells
|
|
151
|
+
desktop:
|
|
152
|
+
commands: [scan_ports] # Rust in native/desktop/, callable from the page
|
|
153
|
+
crates:
|
|
154
|
+
serialport: "4.3"
|
|
155
|
+
|
|
120
156
|
build:
|
|
121
157
|
ruby: "4.0.6"
|
|
122
158
|
targets: [macos-arm64, ios-arm64, android-arm64]
|
|
@@ -134,6 +170,53 @@ Also supported: `auth:` (native OAuth sign-in with a callback scheme),
|
|
|
134
170
|
`native/ios/` that `every build` compiles into the shell, including SwiftUI
|
|
135
171
|
screens, bridge components, a custom splash, and pinned SPM packages.
|
|
136
172
|
|
|
173
|
+
Desktop has the same seam in `native/desktop/`, shaped for what a desktop app
|
|
174
|
+
actually wants: not native screens (the whole UI is a webview) but the machine
|
|
175
|
+
underneath it. Drop in Rust, pin the crates you need, and call it from the page:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
native:
|
|
179
|
+
desktop:
|
|
180
|
+
commands: [scan_ports]
|
|
181
|
+
setup: true # run native/desktop/setup.rs at boot
|
|
182
|
+
crates:
|
|
183
|
+
serialport: "4.3"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```rust
|
|
187
|
+
// native/desktop/serial.rs
|
|
188
|
+
pub fn scan_ports(_app: &tauri::AppHandle, payload: serde_json::Value)
|
|
189
|
+
-> Result<serde_json::Value, String> { … }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
const ports = await Everywhere.desktop.invoke("scan_ports", { baud: 9600 })
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Declaring anything under `native.desktop` has a real cost the mobile sections
|
|
197
|
+
don't: the desktop shell stops being one prebuilt binary shared by every app and
|
|
198
|
+
gets compiled per app, so the first build takes minutes instead of seconds. Apps
|
|
199
|
+
that declare nothing are untouched.
|
|
200
|
+
|
|
201
|
+
Images the desktop shell itself draws live in `native/desktop/assets/`, using the
|
|
202
|
+
same `<name>[@2x|@3x][~dark].png` filenames the mobile assets folders use.
|
|
203
|
+
Name one `tray` (or `tray-template` for a macOS template image) and it becomes
|
|
204
|
+
the menu-bar icon; a custom `splash:` can reference the rest as
|
|
205
|
+
`assets/logo.png`. These are for the shell, not your pages — images your HTML
|
|
206
|
+
references should keep coming from the asset pipeline.
|
|
207
|
+
|
|
208
|
+
`window:` shapes the desktop frame. `title_bar: overlay` is the macOS look where
|
|
209
|
+
the traffic lights float over your page and there's no title text — pair it with
|
|
210
|
+
the `.everywhere-titlebar-inset` class from `everywhere/native.css`, which
|
|
211
|
+
reserves exactly the space they sit in. `frameless` removes the system chrome
|
|
212
|
+
altogether; the page then draws its own bar and drives the buttons through
|
|
213
|
+
`Everywhere.window`. Windows and Linux have no overlay style, so `overlay`
|
|
214
|
+
behaves as `frameless` there.
|
|
215
|
+
|
|
216
|
+
Either way the window stays draggable with no markup: the shell makes the top
|
|
217
|
+
28px of the page drag it (and double-click zoom it), while buttons and links up
|
|
218
|
+
there keep their clicks. `window.drag_height: 0` hands that back to you.
|
|
219
|
+
|
|
137
220
|
Tabs and path rules ship two ways from that one source: baked into the app
|
|
138
221
|
bundle for offline/first launch, and served live from
|
|
139
222
|
`/everywhere/{ios,android}_v1.json`, so changing them deploys with your web app.
|
|
@@ -159,6 +242,7 @@ Everywhere.badge.setTab("/inbox", 3)
|
|
|
159
242
|
Everywhere.permissions.request("camera")
|
|
160
243
|
Everywhere.storage.set("theme", "dark")
|
|
161
244
|
Everywhere.updates.check()
|
|
245
|
+
Everywhere.window.close() // desktop, for a page-drawn title bar
|
|
162
246
|
```
|
|
163
247
|
|
|
164
248
|
Everything degrades gracefully — the same page works in a plain browser tab and
|
|
@@ -187,7 +271,7 @@ Plus `native_app?`, `native_platform`, `everywhere_badge`,
|
|
|
187
271
|
| --- | --- |
|
|
188
272
|
| `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks) |
|
|
189
273
|
| `every doctor` | Check the toolchain — Tebako, Rust, brew deps, Xcode, Android SDK/JDK |
|
|
190
|
-
| `every dev` | Dev server with native shells on demand (`--desktop --ios --android --browser`) |
|
|
274
|
+
| `every dev` | Dev server with native shells on demand (`--desktop --ios --android --mobile --browser`) |
|
|
191
275
|
| `every logs` | Stream shell logs from the Simulator or Android device |
|
|
192
276
|
| `every build` | Package natively — desktop, `--ios`, or `--android` |
|
|
193
277
|
| `every icon` | Generate `.icns` / `.ico` / Linux icons from a source PNG |
|
|
@@ -237,7 +321,7 @@ and complete on its own.
|
|
|
237
321
|
lib/everywhere/ the CLI, config, builders, and Rails engine
|
|
238
322
|
exe/every, exe/rbe the executables
|
|
239
323
|
bridge/ @rubyeverywhere/bridge — the JS half
|
|
240
|
-
support/
|
|
324
|
+
support/desktop/ the Tauri desktop shell template
|
|
241
325
|
support/mobile/ios/ the Hotwire Native iOS shell template
|
|
242
326
|
support/mobile/android/ the Hotwire Native Android shell template
|
|
243
327
|
```
|
data/bridge/README.md
CHANGED
|
@@ -135,6 +135,75 @@ tap, and `<meta name="everywhere:badge">` / `<meta name="everywhere:tab-badge">`
|
|
|
135
135
|
tags (the `everywhere_badge` / `everywhere_tab_badge` Rails helpers) are
|
|
136
136
|
applied on every Turbo visit.
|
|
137
137
|
|
|
138
|
+
### Desktop extensions
|
|
139
|
+
|
|
140
|
+
Rust the app keeps in `native/desktop/` and declares under
|
|
141
|
+
`native.desktop.commands` in `everywhere.yml`:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
Everywhere.desktop.supported // true in the desktop shell
|
|
145
|
+
|
|
146
|
+
const ports = await Everywhere.desktop.invoke("scan_ports", { baud: 9600 })
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The payload arrives in Rust as a `serde_json::Value` and whatever the command
|
|
150
|
+
returns comes back as the resolved value; a command that returns `Err` rejects
|
|
151
|
+
with its own message.
|
|
152
|
+
|
|
153
|
+
Unlike the rest of the bridge this does **not** degrade — there's no web
|
|
154
|
+
equivalent for "talk to a serial port", so off the desktop it rejects rather
|
|
155
|
+
than resolving something untrue. Gate on `Everywhere.desktop.supported`.
|
|
156
|
+
|
|
157
|
+
Under the hood this is an event round-trip, not Tauri's `invoke`: a
|
|
158
|
+
RubyEverywhere page is always served over http, and Tauri v2 refuses `invoke`
|
|
159
|
+
from any http origin — including the packaged app's own localhost server.
|
|
160
|
+
|
|
161
|
+
### Window controls
|
|
162
|
+
|
|
163
|
+
Desktop apps that draw their own title bar — `window.title_bar: overlay` or
|
|
164
|
+
`frameless` in `everywhere.yml` — drive the window from the page:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
Everywhere.window.supported // true in the desktop shell
|
|
168
|
+
|
|
169
|
+
Everywhere.window.minimize()
|
|
170
|
+
Everywhere.window.maximize()
|
|
171
|
+
Everywhere.window.unmaximize()
|
|
172
|
+
Everywhere.window.toggleMaximize() // what a title-bar double-click does
|
|
173
|
+
Everywhere.window.close()
|
|
174
|
+
await Everywhere.window.isMaximized() // Promise<boolean>
|
|
175
|
+
Everywhere.window.startDragging() // from a pointerdown on a custom bar
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`overlay` keeps the macOS traffic lights and only hides the title, so the page
|
|
179
|
+
just needs to stay clear of them; `frameless` removes every control, so the page
|
|
180
|
+
supplies its own.
|
|
181
|
+
|
|
182
|
+
**Dragging works out of the box.** With no system title bar left to grab, the
|
|
183
|
+
shell makes the top 28px of the page drag the window, and double-click there
|
|
184
|
+
zooms it — no markup required. Buttons, links and inputs in that strip keep
|
|
185
|
+
their clicks:
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<header class="everywhere-titlebar">
|
|
189
|
+
<span>My App</span>
|
|
190
|
+
<button onclick="Everywhere.window.close()">×</button>
|
|
191
|
+
</header>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
To make something *outside* the strip draggable, mark it
|
|
195
|
+
`data-tauri-drag-region`. To own dragging yourself, set `window.drag_height: 0`
|
|
196
|
+
in `everywhere.yml` and call `startDragging()` from your own handlers.
|
|
197
|
+
|
|
198
|
+
`everywhere/native.css` ships `.everywhere-titlebar` (a drag strip that clears
|
|
199
|
+
the traffic lights in overlay mode) plus `.everywhere-titlebar-inset` and
|
|
200
|
+
`.everywhere-titlebar-controls-inset` padding utilities. They key off an
|
|
201
|
+
attribute the bridge stamps on `<html>`, so they're inert in a browser tab, on
|
|
202
|
+
mobile, and in a normal decorated window.
|
|
203
|
+
|
|
204
|
+
Everything here resolves to `null` off the desktop, so the same markup is safe
|
|
205
|
+
everywhere.
|
|
206
|
+
|
|
138
207
|
### Auto-updates
|
|
139
208
|
|
|
140
209
|
Inside the desktop shell, apps with an update feed configured can check,
|
|
@@ -164,7 +233,7 @@ In a plain browser tab the updates API is inert: `check()` resolves
|
|
|
164
233
|
## Platforms
|
|
165
234
|
|
|
166
235
|
- **desktop** — the Tauri-based RubyEverywhere shell. Notifications, dialogs,
|
|
167
|
-
clipboard, shell events, and auto-updates are native.
|
|
236
|
+
clipboard, shell events, window controls, and auto-updates are native.
|
|
168
237
|
- **browser** — honest web fallbacks: the Notification API, `window.confirm`,
|
|
169
238
|
`navigator.clipboard`, DOM CustomEvents.
|
|
170
239
|
- **mobile** — Hotwire Native (iOS shell). `notify()` delivers a native local
|
data/bridge/everywhere/bridge.js
CHANGED
|
@@ -178,6 +178,19 @@ function detectPlatform() {
|
|
|
178
178
|
|
|
179
179
|
// --- desktop: the Tauri-based RubyEverywhere shell -------------------------
|
|
180
180
|
|
|
181
|
+
// Distinguishes concurrent desktop command calls. Only has to be unique within
|
|
182
|
+
// one page, so a counter is enough; the random suffix just keeps two rapid
|
|
183
|
+
// calls from colliding if the page is restored from bfcache mid-flight.
|
|
184
|
+
let commandSequence = 0
|
|
185
|
+
const COMMAND_TIMEOUT = 30000
|
|
186
|
+
|
|
187
|
+
// The shell's own window handle. Named so it can't shadow the global `window`
|
|
188
|
+
// inside the adapter below, where every other method still needs the real one.
|
|
189
|
+
function tauriWindow() {
|
|
190
|
+
const api = window.__TAURI__ && window.__TAURI__.window
|
|
191
|
+
return api && api.getCurrentWindow ? api.getCurrentWindow() : null
|
|
192
|
+
}
|
|
193
|
+
|
|
181
194
|
const desktop = {
|
|
182
195
|
notify({ title, body }) {
|
|
183
196
|
return window.__TAURI__.event.emit("everywhere:notify", { title, body })
|
|
@@ -210,6 +223,56 @@ const desktop = {
|
|
|
210
223
|
return window.__TAURI__.clipboardManager.readText()
|
|
211
224
|
},
|
|
212
225
|
|
|
226
|
+
// Window controls, for apps that draw their own title bar (`window.title_bar:
|
|
227
|
+
// frameless`, and `overlay` on Windows/Linux where there's no native overlay
|
|
228
|
+
// style). Without a system title bar there's nothing to drag or click, so the
|
|
229
|
+
// page supplies both and calls these. One entry point rather than six methods
|
|
230
|
+
// because the Tauri window API is already uniform: every one of these is a
|
|
231
|
+
// no-arg call returning a promise.
|
|
232
|
+
//
|
|
233
|
+
// These are ACL-gated. The matching core:window:allow-* permissions have to be
|
|
234
|
+
// in BOTH capability sets (capabilities/default.json for local origins, and
|
|
235
|
+
// the mirrored list in authorize_remote_origin for remote mode) or they reject
|
|
236
|
+
// at runtime.
|
|
237
|
+
windowControl(action) {
|
|
238
|
+
const win = tauriWindow()
|
|
239
|
+
return win ? win[action]() : Promise.resolve(null)
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
// Call a Rust function the app keeps in native/desktop/. Events, not invoke:
|
|
243
|
+
// a RubyEverywhere page is always served over http (127.0.0.1 in local mode,
|
|
244
|
+
// your domain in remote mode) and Tauri v2 refuses invoke from any http
|
|
245
|
+
// origin — even the packaged app's own localhost server. See the comment on
|
|
246
|
+
// install_command_bridge in the shell's extension_host.rs.
|
|
247
|
+
//
|
|
248
|
+
// The id round-trips so concurrent calls can't take each other's answers,
|
|
249
|
+
// which a bare request/response event pair would otherwise allow.
|
|
250
|
+
desktopInvoke(name, payload) {
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
const id = `${commandSequence++}-${Math.random().toString(36).slice(2)}`
|
|
253
|
+
let off = null
|
|
254
|
+
const settle = (fn, value) => {
|
|
255
|
+
clearTimeout(timer)
|
|
256
|
+
if (off) off()
|
|
257
|
+
fn(value)
|
|
258
|
+
}
|
|
259
|
+
const timer = setTimeout(
|
|
260
|
+
() => settle(reject, new Error(`desktop command ${name} timed out`)),
|
|
261
|
+
COMMAND_TIMEOUT
|
|
262
|
+
)
|
|
263
|
+
off = this.on("command-result", (result) => {
|
|
264
|
+
if (!result || result.id !== id) return
|
|
265
|
+
if (result.ok) settle(resolve, result.value)
|
|
266
|
+
else settle(reject, new Error(result.error || `desktop command ${name} failed`))
|
|
267
|
+
})
|
|
268
|
+
window.__TAURI__.event.emit("everywhere:command", {
|
|
269
|
+
id,
|
|
270
|
+
name,
|
|
271
|
+
payload: payload === undefined ? null : payload
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
},
|
|
275
|
+
|
|
213
276
|
// Ask the shell to check its update feed; resolve off whichever update
|
|
214
277
|
// event answers first. The shell always replies to an explicit check
|
|
215
278
|
// (available / none / error), the timeout is just a safety net.
|
|
@@ -295,6 +358,22 @@ const browser = {
|
|
|
295
358
|
return null
|
|
296
359
|
},
|
|
297
360
|
|
|
361
|
+
// A browser tab has no window of its own to drive — the one thing close()
|
|
362
|
+
// could map to (window.close) only works for script-opened windows, which
|
|
363
|
+
// this never is. Resolving null keeps a page that draws a custom title bar
|
|
364
|
+
// from throwing when it's opened in a plain browser.
|
|
365
|
+
windowControl() {
|
|
366
|
+
return Promise.resolve(null)
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
// There is no Rust here. Rejecting (rather than resolving null) is the honest
|
|
370
|
+
// answer: unlike the rest of the bridge, a desktop command has no web
|
|
371
|
+
// equivalent to degrade to, and silently resolving would let a page act on a
|
|
372
|
+
// result it never got. Gate on Everywhere.desktop.supported.
|
|
373
|
+
desktopInvoke(name) {
|
|
374
|
+
return Promise.reject(new Error(`desktop command ${name} is unavailable outside the desktop shell`))
|
|
375
|
+
},
|
|
376
|
+
|
|
298
377
|
// Honest approximation: the Vibration API where it exists (Android
|
|
299
378
|
// browsers); silently nothing elsewhere (iOS Safari has no equivalent).
|
|
300
379
|
haptic(kind, value) {
|
|
@@ -764,6 +843,17 @@ const adapter = adapters[platform]
|
|
|
764
843
|
// registered by the time app code first calls notify().
|
|
765
844
|
if (platform === "mobile") installWebBridge()
|
|
766
845
|
|
|
846
|
+
// Publish the window chrome mode to CSS. The titlebar utilities in native.css
|
|
847
|
+
// key off this attribute, which is the only way they can know a mode that lives
|
|
848
|
+
// in everywhere.yml — and it keeps them inert everywhere else, since nothing
|
|
849
|
+
// stamps it in a browser tab, on mobile, or in a normally decorated window.
|
|
850
|
+
if (platform === "desktop" && typeof document !== "undefined") {
|
|
851
|
+
const titleBar = config.window && config.window.title_bar
|
|
852
|
+
if (titleBar && titleBar !== "decorated") {
|
|
853
|
+
document.documentElement.dataset.everywhereTitlebar = titleBar
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
767
857
|
// Mutable so setChannel can keep updates.channel truthful without a reload.
|
|
768
858
|
let updatesChannel = (config.updates && config.updates.channel) || null
|
|
769
859
|
|
|
@@ -1034,6 +1124,67 @@ export const Everywhere = {
|
|
|
1034
1124
|
}
|
|
1035
1125
|
},
|
|
1036
1126
|
|
|
1127
|
+
// Rust the app keeps in native/desktop/ and declares under
|
|
1128
|
+
// `native.desktop.commands` in everywhere.yml:
|
|
1129
|
+
//
|
|
1130
|
+
// const ports = await Everywhere.desktop.invoke("scan_ports", { baud: 9600 })
|
|
1131
|
+
//
|
|
1132
|
+
// The payload arrives as serde_json::Value and whatever the command returns
|
|
1133
|
+
// comes back as the resolved value. Errors reject with the command's own
|
|
1134
|
+
// message. Rejects immediately off the desktop — there's no web fallback for
|
|
1135
|
+
// "talk to a serial port", so check `supported` first.
|
|
1136
|
+
desktop: {
|
|
1137
|
+
get supported() {
|
|
1138
|
+
return platform === "desktop"
|
|
1139
|
+
},
|
|
1140
|
+
invoke(name, payload) {
|
|
1141
|
+
return adapter.desktopInvoke(name, payload)
|
|
1142
|
+
}
|
|
1143
|
+
},
|
|
1144
|
+
|
|
1145
|
+
// Desktop window controls, for apps that draw their own title bar. Set
|
|
1146
|
+
// `window.title_bar: overlay` (macOS traffic lights float over your content)
|
|
1147
|
+
// or `frameless` (no system chrome at all) in everywhere.yml; frameless is
|
|
1148
|
+
// the one that needs these, since the page then owns dragging and the
|
|
1149
|
+
// minimize/maximize/close buttons:
|
|
1150
|
+
//
|
|
1151
|
+
// <header data-tauri-drag-region>
|
|
1152
|
+
// <button onclick="Everywhere.window.close()">×</button>
|
|
1153
|
+
// </header>
|
|
1154
|
+
//
|
|
1155
|
+
// Everything resolves to null in a browser tab and on mobile, where the OS
|
|
1156
|
+
// owns the frame — so the same markup is safe everywhere.
|
|
1157
|
+
window: {
|
|
1158
|
+
get supported() {
|
|
1159
|
+
return platform === "desktop"
|
|
1160
|
+
},
|
|
1161
|
+
minimize() {
|
|
1162
|
+
return adapter.windowControl("minimize")
|
|
1163
|
+
},
|
|
1164
|
+
maximize() {
|
|
1165
|
+
return adapter.windowControl("maximize")
|
|
1166
|
+
},
|
|
1167
|
+
unmaximize() {
|
|
1168
|
+
return adapter.windowControl("unmaximize")
|
|
1169
|
+
},
|
|
1170
|
+
// What a double-click on a title bar normally does.
|
|
1171
|
+
toggleMaximize() {
|
|
1172
|
+
return adapter.windowControl("toggleMaximize")
|
|
1173
|
+
},
|
|
1174
|
+
close() {
|
|
1175
|
+
return adapter.windowControl("close")
|
|
1176
|
+
},
|
|
1177
|
+
isMaximized() {
|
|
1178
|
+
return adapter.windowControl("isMaximized").then((maximized) => !!maximized)
|
|
1179
|
+
},
|
|
1180
|
+
// Call from a pointerdown handler on your custom title bar. Tauri's
|
|
1181
|
+
// data-tauri-drag-region attribute does this for you; this is the escape
|
|
1182
|
+
// hatch for a drag region you compute yourself.
|
|
1183
|
+
startDragging() {
|
|
1184
|
+
return adapter.windowControl("startDragging")
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1187
|
+
|
|
1037
1188
|
updates: {
|
|
1038
1189
|
// True only when the shell actually has a feed to check (everywhere.yml
|
|
1039
1190
|
// updates: url + public_key made it into the shipped config).
|
|
@@ -201,3 +201,64 @@ html[data-bridge-platform] [data-everywhere-nav-menu] {
|
|
|
201
201
|
@keyframes everywhere-sheet-rise {
|
|
202
202
|
from { transform: translateY(1rem); opacity: 0; }
|
|
203
203
|
}
|
|
204
|
+
|
|
205
|
+
/* ---------------------------------------------------------------------------
|
|
206
|
+
* Desktop window chrome — for `window.title_bar: overlay | frameless` in
|
|
207
|
+
* everywhere.yml. The bridge stamps data-everywhere-titlebar on <html>, so
|
|
208
|
+
* every rule here is scoped to the mode that needs it: on mobile, in a plain
|
|
209
|
+
* browser tab, and in a normal decorated window the attribute is absent and
|
|
210
|
+
* these classes do nothing. Safe to apply unconditionally.
|
|
211
|
+
* ------------------------------------------------------------------------- */
|
|
212
|
+
|
|
213
|
+
:root {
|
|
214
|
+
/* macOS traffic lights: a ~78px cluster inside a 28px band at the top left.
|
|
215
|
+
* Override both if you draw a taller bar. */
|
|
216
|
+
--everywhere-titlebar-height: 28px;
|
|
217
|
+
--everywhere-titlebar-controls: 78px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* Overlay mode: the system still draws (and positions) the traffic lights over
|
|
221
|
+
* your page. Reserve the band so your content doesn't slide underneath them.
|
|
222
|
+
* Frameless draws no controls at all, so it reserves nothing — the page owns
|
|
223
|
+
* that space and decides what goes there. */
|
|
224
|
+
[data-everywhere-titlebar="overlay"] .everywhere-titlebar-inset {
|
|
225
|
+
padding-top: var(--everywhere-titlebar-height);
|
|
226
|
+
}
|
|
227
|
+
[data-everywhere-titlebar="overlay"] .everywhere-titlebar-controls-inset {
|
|
228
|
+
padding-left: var(--everywhere-titlebar-controls);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* A title-bar strip across the top of the window. Purely layout — the shell
|
|
232
|
+
* already makes the top 28px drag the window, so this needs no attribute:
|
|
233
|
+
*
|
|
234
|
+
* <header class="everywhere-titlebar">My App</header>
|
|
235
|
+
*
|
|
236
|
+
* Buttons and links inside it keep their clicks. Mark a region outside the
|
|
237
|
+
* strip with data-tauri-drag-region to make that draggable too, or set
|
|
238
|
+
* window.drag_height: 0 in everywhere.yml to take dragging over entirely.
|
|
239
|
+
*
|
|
240
|
+
* In overlay mode it clears the traffic lights; in frameless it starts at the
|
|
241
|
+
* left edge, because there's nothing there to clear. */
|
|
242
|
+
[data-everywhere-titlebar] .everywhere-titlebar {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
gap: 0.5rem;
|
|
246
|
+
height: var(--everywhere-titlebar-height);
|
|
247
|
+
user-select: none;
|
|
248
|
+
-webkit-user-select: none;
|
|
249
|
+
cursor: default;
|
|
250
|
+
}
|
|
251
|
+
[data-everywhere-titlebar="overlay"] .everywhere-titlebar {
|
|
252
|
+
padding-left: var(--everywhere-titlebar-controls);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Anything clickable inside the drag strip has to opt out, or the drag region
|
|
256
|
+
* swallows the click before the control ever sees it. */
|
|
257
|
+
[data-everywhere-titlebar] .everywhere-titlebar button,
|
|
258
|
+
[data-everywhere-titlebar] .everywhere-titlebar a,
|
|
259
|
+
[data-everywhere-titlebar] .everywhere-titlebar input,
|
|
260
|
+
[data-everywhere-titlebar] .everywhere-titlebar select,
|
|
261
|
+
[data-everywhere-titlebar] .everywhere-titlebar [data-tauri-drag-region="false"] {
|
|
262
|
+
-webkit-app-region: no-drag;
|
|
263
|
+
cursor: default;
|
|
264
|
+
}
|
|
@@ -151,8 +151,14 @@ module Everywhere
|
|
|
151
151
|
# A provider path requested from inside the shell's web view — the visits
|
|
152
152
|
# the shell couldn't intercept natively (a `data-turbo="false"` link, or the
|
|
153
153
|
# POST that OmniAuth 2 requires). Bounce to a page that can ask it to.
|
|
154
|
+
#
|
|
155
|
+
# mobile_platform_of, not native_platform_of: the whole handoff exists
|
|
156
|
+
# because a phone shell must open the provider in ASWebAuthenticationSession
|
|
157
|
+
# / Custom Tabs. The desktop shell already sends external links to the system
|
|
158
|
+
# browser, so diverting there would bounce sign-in to a page nothing can
|
|
159
|
+
# complete.
|
|
154
160
|
def divert(env, config)
|
|
155
|
-
return nil unless Everywhere.
|
|
161
|
+
return nil unless Everywhere.mobile_platform_of(env["HTTP_USER_AGENT"])
|
|
156
162
|
return nil unless config.oauth_path?(env["PATH_INFO"])
|
|
157
163
|
|
|
158
164
|
redirect("#{NATIVE}?#{Rack::Utils.build_query("to" => path_with_query(env))}")
|
|
@@ -172,7 +178,7 @@ module Everywhere
|
|
|
172
178
|
# Only ever the auth browser: inside the web view the app is already
|
|
173
179
|
# holding the jar we would be handing it, and a stale marker there must
|
|
174
180
|
# not divert an ordinary visit into a callback URL it can't open.
|
|
175
|
-
marker = auth_marker(env) unless Everywhere.
|
|
181
|
+
marker = auth_marker(env) unless Everywhere.mobile_platform_of(env["HTTP_USER_AGENT"])
|
|
176
182
|
return @app.call(env) if marker.nil? || config.oauth_path?(env["PATH_INFO"]) || !navigation?(env)
|
|
177
183
|
|
|
178
184
|
_, headers, body = @app.call(env)
|