roxane 0.0.1 → 0.0.3

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: e5b9afbc87612a9f5e97a4f57fea36a3556e988bd193cc72f624fdcaed7cabe1
4
- data.tar.gz: c5870e5c200f2deaedfb5e3a55f00b172d68611df04d2c0e2722dd75093bcf76
3
+ metadata.gz: 949821f1324cd761b172fc4b4f7519459b3d70c3a53db0bc9ba3ad694ffe3e80
4
+ data.tar.gz: dd6ea3c05680732a4c6d8cb08702626735675b2035d6cdc1c7822a08e43fac93
5
5
  SHA512:
6
- metadata.gz: 9a71c4851681187f76a8e1ac1675962a8eef44df6b25908c8b05d628441ef8c3e3a7b1290dfbf64fd0b37c5c556827932c05944e2b2f483a71f1048ffd47c421
7
- data.tar.gz: aeb62cb2ea3659aa06b7faaf911d9b269026f64998842ef9818b53b33a60472d38608597c6fd6d46cdc333e040365c4a3bc63c775a021c3453998409db6054fe
6
+ metadata.gz: edbfde719e7560dcd6f2e68c8509be66d44118035fe02009f6d285482583abf14c5f6bcdfc8bb577c62f60db7cd615c720b9ca18f13c7259269fb8bb52b341bf
7
+ data.tar.gz: c70573dc5ad95b1d59ab06ed5714dd88a420410857e5c3fcad2c974834429f19a8679a85b2933408a40f821374064ea675d6f3980e3f7f93a9d720797fdaf243
data/README.md CHANGED
@@ -65,6 +65,15 @@ rake test # host: unit tests only (the asset server); the windowed
65
65
  # smoke test self-skips unless ROXANE_INTEGRATION=1
66
66
  ```
67
67
 
68
+ ## Linux / Wayland note (window size)
69
+
70
+ On GTK3, upstream `libwebview` implements `WEBVIEW_HINT_NONE` with
71
+ `gtk_window_resize`. Under Wayland that does **not** set the initial xdg
72
+ geometry, so floating compositors (e.g. Hyprland) often map the window at a
73
+ tiny 200×200 square; GNOME hides the same bug. Roxane compensates on Linux by
74
+ calling `gtk_window_set_default_size` and briefly pinning a size-request through
75
+ first map (then releasing it so the window stays resizable).
76
+
68
77
  ## Status
69
78
 
70
79
  Early but working. The runtime (window + system webview + invoke/emit bridge +
data/lib/roxane/ffi.rb CHANGED
@@ -15,6 +15,12 @@ module Roxane
15
15
  HINT_MAX = 2
16
16
  HINT_FIXED = 3
17
17
 
18
+ # webview_native_handle_kind_t — BROWSER_CONTROLLER is the engine view
19
+ # (WebKitWebView* on GTK), the door to engine calls webview doesn't wrap.
20
+ NATIVE_HANDLE_UI_WINDOW = 0
21
+ NATIVE_HANDLE_UI_WIDGET = 1
22
+ NATIVE_HANDLE_BROWSER_CONTROLLER = 2
23
+
18
24
  begin
19
25
  ffi_lib Roxane.library_path
20
26
 
@@ -31,6 +37,7 @@ module Roxane
31
37
  attach_function :webview_set_html, [:pointer, :string], :int
32
38
  attach_function :webview_init, [:pointer, :string], :int
33
39
  attach_function :webview_eval, [:pointer, :string], :int
40
+ attach_function :webview_get_native_handle, [:pointer, :int], :pointer
34
41
 
35
42
  callback :bind_fn, [:string, :string, :pointer], :void
36
43
  attach_function :webview_bind, [:pointer, :string, :bind_fn, :pointer], :int
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.1"
4
+ VERSION = "0.0.3"
5
5
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roxane
4
+ # Direct WebKitGTK bindings for the few affordances the webview C API doesn't
5
+ # wrap (today: full-content zoom). On Linux the library is already linked into
6
+ # the process by libwebview — we bind the soname libwebview itself links (4.1,
7
+ # falling back to 4.0). Where neither loads (macOS/Windows — their engines get
8
+ # their own bindings later), AVAILABLE is false and callers no-op gracefully.
9
+ module WebKit
10
+ extend ::FFI::Library
11
+
12
+ begin
13
+ ffi_lib ["libwebkit2gtk-4.1.so.0", "libwebkit2gtk-4.0.so.37"]
14
+ attach_function :webkit_web_view_set_zoom_level, [:pointer, :double], :void
15
+ attach_function :webkit_web_view_get_zoom_level, [:pointer], :double
16
+ AVAILABLE = true
17
+ rescue LoadError, ::FFI::NotFoundError
18
+ AVAILABLE = false
19
+ end
20
+ end
21
+
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).
27
+ module GLib
28
+ extend ::FFI::Library
29
+
30
+ begin
31
+ ffi_lib "libglib-2.0.so.0"
32
+ callback :g_source_func, [:pointer], :int
33
+ attach_function :g_idle_add, [:g_source_func, :pointer], :uint
34
+ attach_function :g_timeout_add, [:uint, :g_source_func, :pointer], :uint
35
+ AVAILABLE = true
36
+ rescue LoadError, ::FFI::NotFoundError
37
+ AVAILABLE = false
38
+ end
39
+ end
40
+ end
data/lib/roxane/window.rb CHANGED
@@ -26,6 +26,7 @@ module Roxane
26
26
  @on_close = nil
27
27
  @server = nil
28
28
  @ui_queue = Queue.new # procs to run on the UI thread
29
+ @zoom = 1.0
29
30
 
30
31
  @w = FFI.webview_create(debug ? 1 : 0, nil)
31
32
  raise Error, "could not create webview" if @w.null?
@@ -83,6 +84,31 @@ module Roxane
83
84
  self
84
85
  end
85
86
 
87
+ # Full-content zoom, like a browser's Ctrl+± (1.0 = natural size). Backed by
88
+ # the engine's native zoom (WebKitGTK today); where that binding isn't
89
+ # available the setter no-ops and the reader reports the last requested
90
+ # level. Safe from any thread: scheduled with g_idle_add, which runs on the
91
+ # UI thread as a TOP-LEVEL loop iteration — never nested inside a webview
92
+ # dispatch callback, where a layout-triggering WebKit call can reenter the
93
+ # engine mid-resolve and crash it. Rapid calls coalesce (each application
94
+ # reads the latest requested level; extra idles are harmless).
95
+ def zoom
96
+ @zoom
97
+ end
98
+
99
+ def zoom=(factor)
100
+ @zoom = Float(factor)
101
+ return unless WebKit::AVAILABLE && GLib::AVAILABLE
102
+
103
+ @apply_zoom ||= ::FFI::Function.new(:int, [:pointer]) do |_data|
104
+ view = FFI.webview_get_native_handle(@w, FFI::NATIVE_HANDLE_BROWSER_CONTROLLER)
105
+ WebKit.webkit_web_view_set_zoom_level(view, @zoom) unless view.null?
106
+ 0 # G_SOURCE_REMOVE
107
+ end
108
+ @callbacks << @apply_zoom unless @callbacks.include?(@apply_zoom)
109
+ GLib.g_idle_add(@apply_zoom, nil)
110
+ end
111
+
86
112
  def on_close(&block)
87
113
  @on_close = block
88
114
  self
@@ -106,9 +132,59 @@ module Roxane
106
132
  private
107
133
 
108
134
  def apply_size(size, min_size, resizable)
109
- width, height = size
135
+ width, height = Integer(size[0]), Integer(size[1])
110
136
  FFI.webview_set_size(@w, width, height, resizable ? FFI::HINT_NONE : FFI::HINT_FIXED)
111
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)
112
188
  end
113
189
 
114
190
  # One persistent dispatch callback drains a queue of procs on the UI thread —
data/lib/roxane.rb CHANGED
@@ -36,5 +36,7 @@ module Roxane
36
36
  end
37
37
 
38
38
  require_relative "roxane/ffi"
39
+ require_relative "roxane/webkit"
40
+ require_relative "roxane/gtk"
39
41
  require_relative "roxane/static_server"
40
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.1
4
+ version: 0.0.3
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-06-18 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,8 +39,10 @@ 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
45
+ - lib/roxane/webkit.rb
44
46
  - lib/roxane/window.rb
45
47
  homepage: https://github.com/quarterstack/roxane
46
48
  licenses: