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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0ef9393493008dcc4763293fa6ebd36ebb6fdd891b1e2358100eb442ed729ec
4
- data.tar.gz: 1fbfe739d81d37b48fcaf9ac428db7d60319c864a63e6b3a9ef9819cb305a65f
3
+ metadata.gz: df80c83574d8bd26f0cf1f386bf9c0985810c65cf45775d92e9b4c4ebb1d637b
4
+ data.tar.gz: 33b09ef89cbc7e63ebc07bf3e1989d9d87082818097f28ef6445147449542090
5
5
  SHA512:
6
- metadata.gz: 2a84c3f0dc6f20488b7d1bfea51a327fafa1b77e4cd393685645b64e843dc8609c3eeb2a5fc6ab7cff8ec810610cd36afafc8766860003732073b8111464e11f
7
- data.tar.gz: 100d61215f9c9c821da5c76648ac6bebd4b59a19aabf342f8928246fe8530243730a73128c013cdb094d3555f4d9933e5bb5e1ff115b2506dcbdcb4891bb4a8c
6
+ metadata.gz: '0482a26303a715cab2289aba9df732ce54bded5bcaf6424ed25e81b1012a7b98d25dcdd2fd61e0f004da719fecd80e33e87d5926f810e21798d43d88e3e1c6f6'
7
+ data.tar.gz: 8dd38d38fc168b5f4449c06de731b9912d70c376b5fbecf9a9a6587f836154b3f569f4db815bd9fc3267f6e171834a8eb14b3b5e6a18d6662792f155805dd90e
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 BKK Riese
3
+ Copyright (c) 2026 Pacific Robots, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Roxane
2
2
 
3
+ [![build-gems](https://github.com/pacificrobots/roxane/actions/workflows/build-gems.yml/badge.svg)](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. The
43
- precompiled platform gem (e.g. `x86_64-linux`) **vendors it**, so you only need
44
- the system webview runtime (WebKitGTK on Linux). Otherwise set `ROXANE_LIBWEBVIEW`
45
- to a `libwebview` path, or have it on the loader path.
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. For x86_64-linux:
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
- macOS / Windows / aarch64 cross-builds are next.
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 bridge +
71
- loopback asset server) is green headlessly, and **precompiled x86_64-linux
72
- packaging works** `rake verify:linux` builds the platform gem and proves it
73
- installs and runs on a clean machine (system WebKitGTK only, no toolchain). Next:
74
- macOS / Windows / aarch64 cross-builds + RubyGems publish, then richer window
75
- options (menus, frameless).
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Roxane
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.4"
5
5
  end
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 is thread-safe and runs the callback
23
- # as a TOP-LEVEL main-loop iteration — a clean stack, never nested inside a
24
- # webview dispatch callback (layout-triggering WebKit calls made from inside
25
- # one can reenter the engine and crash; see the zoom work, 2026-07-03).
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
@@ -37,5 +37,6 @@ end
37
37
 
38
38
  require_relative "roxane/ffi"
39
39
  require_relative "roxane/webkit"
40
+ require_relative "roxane/gtk"
40
41
  require_relative "roxane/static_server"
41
42
  require_relative "roxane/window"
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.2
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-03 00:00:00.000000000 Z
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/quarterstack/roxane
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: