roxane 0.0.2 → 0.0.4
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/LICENSE +1 -1
- data/README.md +70 -12
- data/lib/roxane/gtk.rb +29 -0
- data/lib/roxane/version.rb +1 -1
- data/lib/roxane/webkit.rb +6 -4
- data/lib/roxane/window.rb +51 -1
- data/lib/roxane.rb +1 -0
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df80c83574d8bd26f0cf1f386bf9c0985810c65cf45775d92e9b4c4ebb1d637b
|
|
4
|
+
data.tar.gz: 33b09ef89cbc7e63ebc07bf3e1989d9d87082818097f28ef6445147449542090
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0482a26303a715cab2289aba9df732ce54bded5bcaf6424ed25e81b1012a7b98d25dcdd2fd61e0f004da719fecd80e33e87d5926f810e21798d43d88e3e1c6f6'
|
|
7
|
+
data.tar.gz: 8dd38d38fc168b5f4449c06de731b9912d70c376b5fbecf9a9a6587f836154b3f569f4db815bd9fc3267f6e171834a8eb14b3b5e6a18d6662792f155805dd90e
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Roxane
|
|
2
2
|
|
|
3
|
+
[](https://github.com/pacificrobots/roxane/actions/workflows/build-gems.yml)
|
|
4
|
+
|
|
3
5
|
A pure-Ruby desktop shell: a native window over the operating system's webview
|
|
4
6
|
(WebView2 / WKWebView / WebKitGTK, via the [webview](https://github.com/webview/webview)
|
|
5
7
|
C library), with a Ruby⇄JS **operation bridge**. A lightweight, Tauri-shaped way
|
|
@@ -14,6 +16,37 @@ Roxane is the **host and data plane** — a window, the system webview, an
|
|
|
14
16
|
you give it and is **agnostic to look-and-feel** (no CSS, no widgets, no layout
|
|
15
17
|
opinions). The same frontend can run in the desktop shell and in a hosted browser.
|
|
16
18
|
|
|
19
|
+
## When to use it
|
|
20
|
+
|
|
21
|
+
Reach for Roxane when you want a **real desktop window** around a web UI, and
|
|
22
|
+
**Ruby owns the logic**:
|
|
23
|
+
|
|
24
|
+
- **Ship a web frontend as a desktop app** without Electron’s bulk — your UI can
|
|
25
|
+
be plain HTML, or a Vite/React/Svelte/etc. build you already have
|
|
26
|
+
- **Ruby on the inside, JS on the glass** — long work (AI calls, imports, jobs)
|
|
27
|
+
runs in Ruby handlers off the UI thread; the page stays responsive via
|
|
28
|
+
`invoke` / `emit`
|
|
29
|
+
- **One frontend, two shells** — develop in the browser against a dev server
|
|
30
|
+
(`load`), ship the same assets in the native window (`serve`)
|
|
31
|
+
- **Local tools and studios** — editors, dashboards, creative apps, ops utilities
|
|
32
|
+
where Ruby is already home and you want a polished window, not a terminal-only UX
|
|
33
|
+
|
|
34
|
+
**Not a fit** if you need native OS widgets, deep App Store chrome, or a batteries-
|
|
35
|
+
included UI framework — Roxane deliberately does none of that. It’s the thin shell
|
|
36
|
+
under *your* frontend.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
gem install roxane
|
|
42
|
+
# or in a Gemfile:
|
|
43
|
+
# gem "roxane", ">= 0.0.4"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Bundler picks the matching **platform gem** when one exists (vendored `libwebview`).
|
|
47
|
+
You still need the OS webview runtime (WebKitGTK on Linux, WebView2 on Windows,
|
|
48
|
+
WKWebView on macOS).
|
|
49
|
+
|
|
17
50
|
## Example
|
|
18
51
|
|
|
19
52
|
```ruby
|
|
@@ -27,6 +60,9 @@ win.on("greet") { |name| "Hello, #{name}!" }
|
|
|
27
60
|
# Ruby → JS: window.roxane.on("tick", ({ at }) => ...)
|
|
28
61
|
win.emit("tick", { at: Time.now.to_i })
|
|
29
62
|
|
|
63
|
+
# Full-content zoom (WebKitGTK today; no-op where the engine binding is absent)
|
|
64
|
+
win.zoom = 1.2
|
|
65
|
+
|
|
30
66
|
win.serve(File.expand_path("ui")) # serve a built frontend bundle, or:
|
|
31
67
|
# win.load("http://localhost:5173") # point at a dev server, or:
|
|
32
68
|
# win.html("<!doctype html>…") # inline HTML
|
|
@@ -39,15 +75,27 @@ thread — so long-running work (e.g. an AI call) never freezes the window.
|
|
|
39
75
|
|
|
40
76
|
## Requirements
|
|
41
77
|
|
|
42
|
-
The `webview` C library (`libwebview`) must be available at runtime.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
78
|
+
The `webview` C library (`libwebview`) must be available at runtime. Precompiled
|
|
79
|
+
platform gems **vendor it**, so you only need the system webview runtime.
|
|
80
|
+
Otherwise set `ROXANE_LIBWEBVIEW` to a `libwebview` path, or have it on the
|
|
81
|
+
loader path.
|
|
46
82
|
|
|
47
83
|
## Packaging (precompiled platform gems)
|
|
48
84
|
|
|
49
85
|
Roxane ships a prebuilt `libwebview` per platform (the nokogiri model) so users
|
|
50
|
-
need no toolchain.
|
|
86
|
+
need no toolchain. CI builds and publishes to [RubyGems](https://rubygems.org/gems/roxane)
|
|
87
|
+
on each `v*` tag for:
|
|
88
|
+
|
|
89
|
+
| Platform gem | Runner |
|
|
90
|
+
|---------------------|---------------|
|
|
91
|
+
| `x86_64-linux` | Ubuntu |
|
|
92
|
+
| `aarch64-linux` | Ubuntu ARM |
|
|
93
|
+
| `arm64-darwin` | macOS Apple Silicon |
|
|
94
|
+
| `x86_64-darwin` | macOS Intel |
|
|
95
|
+
| `x64-mingw-ucrt` | Windows |
|
|
96
|
+
| ruby (no binary) | source gem |
|
|
97
|
+
|
|
98
|
+
Local Linux packaging (Docker):
|
|
51
99
|
|
|
52
100
|
```sh
|
|
53
101
|
rake vendor:linux # build libwebview into vendor/x86_64-linux/
|
|
@@ -55,7 +103,8 @@ rake gem:linux # build roxane-<ver>-x86_64-linux.gem (vendoring the lib)
|
|
|
55
103
|
rake verify:linux # install that gem in a clean container and smoke-test it
|
|
56
104
|
```
|
|
57
105
|
|
|
58
|
-
|
|
106
|
+
Multi-platform gems are produced by GitHub Actions (`.github/workflows/build-gems.yml`),
|
|
107
|
+
not these rake tasks.
|
|
59
108
|
|
|
60
109
|
## Testing
|
|
61
110
|
|
|
@@ -65,14 +114,23 @@ rake test # host: unit tests only (the asset server); the windowed
|
|
|
65
114
|
# smoke test self-skips unless ROXANE_INTEGRATION=1
|
|
66
115
|
```
|
|
67
116
|
|
|
117
|
+
## Linux / Wayland note (window size)
|
|
118
|
+
|
|
119
|
+
On GTK3, upstream `libwebview` implements `WEBVIEW_HINT_NONE` with
|
|
120
|
+
`gtk_window_resize`. Under Wayland that does **not** set the initial xdg
|
|
121
|
+
geometry, so floating compositors (e.g. Hyprland) often map the window at a
|
|
122
|
+
tiny 200×200 square; GNOME hides the same bug. Roxane compensates on Linux by
|
|
123
|
+
calling `gtk_window_set_default_size` and briefly pinning a size-request through
|
|
124
|
+
first map (then releasing it so the window stays resizable).
|
|
125
|
+
|
|
68
126
|
## Status
|
|
69
127
|
|
|
70
|
-
Early but working. The runtime (window + system webview + invoke/emit
|
|
71
|
-
loopback asset server)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
128
|
+
Early but working (**0.0.4**). The runtime (window + system webview + invoke/emit
|
|
129
|
+
bridge + loopback asset server), native zoom on WebKitGTK, and Wayland initial
|
|
130
|
+
sizing are in place. Platform gems publish to RubyGems on version tags.
|
|
131
|
+
|
|
132
|
+
Next: richer window options (menus, frameless), and zoom bindings for
|
|
133
|
+
macOS/Windows engines.
|
|
76
134
|
|
|
77
135
|
## License
|
|
78
136
|
|
data/lib/roxane/gtk.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Roxane
|
|
4
|
+
# Direct GTK3 bindings for window-size affordances the webview C API gets wrong
|
|
5
|
+
# on Wayland. libwebview 0.12 (GTK3) implements WEBVIEW_HINT_NONE with
|
|
6
|
+
# gtk_window_resize only; on Wayland that does not set the initial xdg
|
|
7
|
+
# geometry, so the client commits 200×200 and floating compositors (Hyprland,
|
|
8
|
+
# etc.) keep the tiny square. GNOME/Mutter is more forgiving, which hides the
|
|
9
|
+
# bug there. GTK4 webview already uses gtk_window_set_default_size — we do the
|
|
10
|
+
# same from Ruby, plus a brief size-request so the first map advertises the
|
|
11
|
+
# right min/geometry, then release it so the window stays user-resizable.
|
|
12
|
+
#
|
|
13
|
+
# Where libgtk-3 isn't loadable (macOS/Windows — their backends are separate),
|
|
14
|
+
# AVAILABLE is false and callers no-op.
|
|
15
|
+
module Gtk
|
|
16
|
+
extend ::FFI::Library
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
ffi_lib "libgtk-3.so.0"
|
|
20
|
+
attach_function :gtk_window_set_default_size, [:pointer, :int, :int], :void
|
|
21
|
+
attach_function :gtk_window_resize, [:pointer, :int, :int], :void
|
|
22
|
+
attach_function :gtk_widget_set_size_request, [:pointer, :int, :int], :void
|
|
23
|
+
attach_function :gtk_widget_get_realized, [:pointer], :int
|
|
24
|
+
AVAILABLE = true
|
|
25
|
+
rescue LoadError, ::FFI::NotFoundError
|
|
26
|
+
AVAILABLE = false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/roxane/version.rb
CHANGED
data/lib/roxane/webkit.rb
CHANGED
|
@@ -19,10 +19,11 @@ module Roxane
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
# GLib main-loop scheduling. g_idle_add
|
|
23
|
-
# as a TOP-LEVEL main-loop iteration — a clean stack, never
|
|
24
|
-
# webview dispatch callback (layout-triggering WebKit calls
|
|
25
|
-
# one can reenter the engine and crash; see the zoom work,
|
|
22
|
+
# GLib main-loop scheduling. g_idle_add / g_timeout_add are thread-safe and
|
|
23
|
+
# run the callback as a TOP-LEVEL main-loop iteration — a clean stack, never
|
|
24
|
+
# nested inside a webview dispatch callback (layout-triggering WebKit calls
|
|
25
|
+
# made from inside one can reenter the engine and crash; see the zoom work,
|
|
26
|
+
# 2026-07-03).
|
|
26
27
|
module GLib
|
|
27
28
|
extend ::FFI::Library
|
|
28
29
|
|
|
@@ -30,6 +31,7 @@ module Roxane
|
|
|
30
31
|
ffi_lib "libglib-2.0.so.0"
|
|
31
32
|
callback :g_source_func, [:pointer], :int
|
|
32
33
|
attach_function :g_idle_add, [:g_source_func, :pointer], :uint
|
|
34
|
+
attach_function :g_timeout_add, [:uint, :g_source_func, :pointer], :uint
|
|
33
35
|
AVAILABLE = true
|
|
34
36
|
rescue LoadError, ::FFI::NotFoundError
|
|
35
37
|
AVAILABLE = false
|
data/lib/roxane/window.rb
CHANGED
|
@@ -132,9 +132,59 @@ module Roxane
|
|
|
132
132
|
private
|
|
133
133
|
|
|
134
134
|
def apply_size(size, min_size, resizable)
|
|
135
|
-
width, height = size
|
|
135
|
+
width, height = Integer(size[0]), Integer(size[1])
|
|
136
136
|
FFI.webview_set_size(@w, width, height, resizable ? FFI::HINT_NONE : FFI::HINT_FIXED)
|
|
137
137
|
FFI.webview_set_size(@w, min_size[0], min_size[1], FFI::HINT_MIN) if min_size && resizable
|
|
138
|
+
# GTK3/Wayland: compensate for libwebview using gtk_window_resize (ignored
|
|
139
|
+
# as initial geometry). No-ops on macOS/Windows where Gtk is unavailable.
|
|
140
|
+
apply_gtk_wayland_size(width, height, min_size, resizable)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# See Roxane::Gtk — set_default_size + a temporary size-request so the first
|
|
144
|
+
# xdg configure isn't 200×200 under floating Wayland compositors.
|
|
145
|
+
def apply_gtk_wayland_size(width, height, min_size, resizable)
|
|
146
|
+
return unless Gtk::AVAILABLE && GLib::AVAILABLE
|
|
147
|
+
|
|
148
|
+
handle = FFI.webview_get_native_handle(@w, FFI::NATIVE_HANDLE_UI_WINDOW)
|
|
149
|
+
return if handle.null?
|
|
150
|
+
|
|
151
|
+
Gtk.gtk_window_set_default_size(handle, width, height)
|
|
152
|
+
return unless resizable
|
|
153
|
+
|
|
154
|
+
# Pin geometry through the first map, then release so the user can shrink.
|
|
155
|
+
# (Non-resizable windows keep webview's FIXED size-request permanently.)
|
|
156
|
+
Gtk.gtk_widget_set_size_request(handle, width, height)
|
|
157
|
+
release_initial_gtk_size_request(width, height, min_size)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Poll until the GtkWindow is realized (shown), then drop the temporary
|
|
161
|
+
# size-request. Scheduled from construction — safe if run() is delayed;
|
|
162
|
+
# continues until realize or the window is destroyed.
|
|
163
|
+
def release_initial_gtk_size_request(width, height, min_size)
|
|
164
|
+
state = { seen_realized: false }
|
|
165
|
+
cb = ::FFI::Function.new(:int, [:pointer]) do |_data|
|
|
166
|
+
handle = FFI.webview_get_native_handle(@w, FFI::NATIVE_HANDLE_UI_WINDOW)
|
|
167
|
+
if handle.null?
|
|
168
|
+
0 # G_SOURCE_REMOVE — window gone
|
|
169
|
+
elsif Gtk.gtk_widget_get_realized(handle) == 0
|
|
170
|
+
1 # G_SOURCE_CONTINUE — not mapped yet
|
|
171
|
+
elsif !state[:seen_realized]
|
|
172
|
+
# One extra tick after realize so the compositor has acked min_size.
|
|
173
|
+
state[:seen_realized] = true
|
|
174
|
+
1
|
|
175
|
+
else
|
|
176
|
+
if min_size
|
|
177
|
+
Gtk.gtk_widget_set_size_request(handle, Integer(min_size[0]), Integer(min_size[1]))
|
|
178
|
+
else
|
|
179
|
+
Gtk.gtk_widget_set_size_request(handle, -1, -1)
|
|
180
|
+
end
|
|
181
|
+
Gtk.gtk_window_set_default_size(handle, width, height)
|
|
182
|
+
Gtk.gtk_window_resize(handle, width, height)
|
|
183
|
+
0 # G_SOURCE_REMOVE
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
@callbacks << cb
|
|
187
|
+
GLib.g_timeout_add(50, cb, nil)
|
|
138
188
|
end
|
|
139
189
|
|
|
140
190
|
# One persistent dispatch callback drains a queue of procs on the UI thread —
|
data/lib/roxane.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: roxane
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- BKK Riese
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -39,14 +39,18 @@ files:
|
|
|
39
39
|
- assets/bridge.js
|
|
40
40
|
- lib/roxane.rb
|
|
41
41
|
- lib/roxane/ffi.rb
|
|
42
|
+
- lib/roxane/gtk.rb
|
|
42
43
|
- lib/roxane/static_server.rb
|
|
43
44
|
- lib/roxane/version.rb
|
|
44
45
|
- lib/roxane/webkit.rb
|
|
45
46
|
- lib/roxane/window.rb
|
|
46
|
-
homepage: https://github.com/
|
|
47
|
+
homepage: https://github.com/pacificrobots/roxane
|
|
47
48
|
licenses:
|
|
48
49
|
- MIT
|
|
49
|
-
metadata:
|
|
50
|
+
metadata:
|
|
51
|
+
homepage_uri: https://github.com/pacificrobots/roxane
|
|
52
|
+
source_code_uri: https://github.com/pacificrobots/roxane
|
|
53
|
+
bug_tracker_uri: https://github.com/pacificrobots/roxane/issues
|
|
50
54
|
post_install_message:
|
|
51
55
|
rdoc_options: []
|
|
52
56
|
require_paths:
|