ranma 0.1.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 +7 -0
- data/Cargo.lock +5571 -0
- data/Cargo.toml +3 -0
- data/LICENSE +21 -0
- data/README.md +293 -0
- data/ext/ranma/Cargo.toml +23 -0
- data/ext/ranma/extconf.rb +4 -0
- data/ext/ranma/src/app.rs +231 -0
- data/ext/ranma/src/blit.wgsl +32 -0
- data/ext/ranma/src/clipboard.rs +57 -0
- data/ext/ranma/src/dpi.rs +227 -0
- data/ext/ranma/src/event_loop.rs +145 -0
- data/ext/ranma/src/events.rs +305 -0
- data/ext/ranma/src/hotkey.rs +245 -0
- data/ext/ranma/src/ime.rs +318 -0
- data/ext/ranma/src/lib.rs +42 -0
- data/ext/ranma/src/menu.rs +588 -0
- data/ext/ranma/src/monitor.rs +149 -0
- data/ext/ranma/src/painter.rs +1082 -0
- data/ext/ranma/src/proxy.rs +34 -0
- data/ext/ranma/src/surface.rs +389 -0
- data/ext/ranma/src/theme.rs +20 -0
- data/ext/ranma/src/tray.rs +251 -0
- data/ext/ranma/src/webview.rs +334 -0
- data/ext/ranma/src/window.rs +691 -0
- data/ext/ranma/src/window_store.rs +81 -0
- data/lib/ranma/version.rb +3 -0
- data/lib/ranma.rb +61 -0
- metadata +86 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
use std::cell::RefCell;
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use magnus::{function, gc, method, prelude::*, Error, RHash, Ruby, TryConvert, Value};
|
|
4
|
+
use wry::dpi::{LogicalPosition, LogicalSize};
|
|
5
|
+
use wry::{Rect, WebViewBuilder};
|
|
6
|
+
|
|
7
|
+
use crate::window::RbAppWindow;
|
|
8
|
+
use crate::window_store;
|
|
9
|
+
|
|
10
|
+
thread_local! {
|
|
11
|
+
static WEBVIEWS: RefCell<HashMap<u64, wry::WebView>> = RefCell::new(HashMap::new());
|
|
12
|
+
static WEBVIEW_HANDLERS: RefCell<HashMap<u64, WebViewHandlers>> = RefCell::new(HashMap::new());
|
|
13
|
+
static NEXT_WEBVIEW_ID: RefCell<u64> = const { RefCell::new(1) };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
struct WebViewHandlers {
|
|
17
|
+
ipc_handler: Option<Value>,
|
|
18
|
+
navigation_handler: Option<Value>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[magnus::wrap(class = "Ranma::WebView", free_immediately, size)]
|
|
22
|
+
pub struct RbWebView {
|
|
23
|
+
webview_id: u64,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
unsafe impl Send for RbWebView {}
|
|
27
|
+
|
|
28
|
+
impl RbWebView {
|
|
29
|
+
fn with_wry<F, R>(&self, f: F) -> Result<R, Error>
|
|
30
|
+
where
|
|
31
|
+
F: FnOnce(&wry::WebView) -> R,
|
|
32
|
+
{
|
|
33
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
34
|
+
WEBVIEWS.with(|cell| {
|
|
35
|
+
let wvs = cell.borrow();
|
|
36
|
+
wvs.get(&self.webview_id)
|
|
37
|
+
.map(|wv| f(wv))
|
|
38
|
+
.ok_or_else(|| {
|
|
39
|
+
Error::new(ruby.exception_runtime_error(), "WebView not found")
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn new(window: &RbAppWindow, opts: Option<RHash>) -> Result<Self, Error> {
|
|
45
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
46
|
+
let window_id = window.raw_window_id();
|
|
47
|
+
|
|
48
|
+
let webview_id = NEXT_WEBVIEW_ID.with(|cell| {
|
|
49
|
+
let mut id = cell.borrow_mut();
|
|
50
|
+
let current = *id;
|
|
51
|
+
*id += 1;
|
|
52
|
+
current
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let mut url_str: Option<String> = None;
|
|
56
|
+
let mut html_str: Option<String> = None;
|
|
57
|
+
|
|
58
|
+
if let Some(opts) = opts {
|
|
59
|
+
if let Some(u) = opts.get(ruby.sym_new("url")) {
|
|
60
|
+
if let Ok(s) = String::try_convert(u) {
|
|
61
|
+
url_str = Some(s);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if let Some(h) = opts.get(ruby.sym_new("html")) {
|
|
65
|
+
if let Ok(s) = String::try_convert(h) {
|
|
66
|
+
html_str = Some(s);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let wv_id_ipc = webview_id;
|
|
72
|
+
let wv_id_nav = webview_id;
|
|
73
|
+
|
|
74
|
+
let webview = window_store::with_window(&window_id, |tao_window| {
|
|
75
|
+
let mut builder = WebViewBuilder::new()
|
|
76
|
+
.with_visible(false)
|
|
77
|
+
.with_ipc_handler(move |request| {
|
|
78
|
+
let body = request.body().clone();
|
|
79
|
+
WEBVIEW_HANDLERS.with(|cell| {
|
|
80
|
+
let handlers = cell.borrow();
|
|
81
|
+
if let Some(h) = handlers.get(&wv_id_ipc) {
|
|
82
|
+
if let Some(ref handler) = h.ipc_handler {
|
|
83
|
+
let result: Result<Value, _> = handler.funcall("call", (body,));
|
|
84
|
+
if let Err(e) = result {
|
|
85
|
+
eprintln!("Ranma: Error in IPC handler: {}", e);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
})
|
|
91
|
+
.with_navigation_handler(move |url: String| -> bool {
|
|
92
|
+
WEBVIEW_HANDLERS.with(|cell| {
|
|
93
|
+
let handlers = cell.borrow();
|
|
94
|
+
if let Some(h) = handlers.get(&wv_id_nav) {
|
|
95
|
+
if let Some(ref handler) = h.navigation_handler {
|
|
96
|
+
let result: Result<Value, _> = handler.funcall("call", (url,));
|
|
97
|
+
match result {
|
|
98
|
+
Ok(val) => bool::try_convert(val).unwrap_or(true),
|
|
99
|
+
Err(e) => {
|
|
100
|
+
eprintln!("Ranma: Error in navigation handler: {}", e);
|
|
101
|
+
true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
true
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
true
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if let Some(ref u) = url_str {
|
|
114
|
+
builder = builder.with_url(u);
|
|
115
|
+
} else if let Some(ref h) = html_str {
|
|
116
|
+
builder = builder.with_html(h);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
builder.build_as_child(tao_window)
|
|
120
|
+
})
|
|
121
|
+
.ok_or_else(|| {
|
|
122
|
+
Error::new(ruby.exception_runtime_error(), "Window not found")
|
|
123
|
+
})?
|
|
124
|
+
.map_err(|e| {
|
|
125
|
+
Error::new(
|
|
126
|
+
ruby.exception_runtime_error(),
|
|
127
|
+
format!("Failed to create WebView: {}", e),
|
|
128
|
+
)
|
|
129
|
+
})?;
|
|
130
|
+
|
|
131
|
+
WEBVIEWS.with(|cell| {
|
|
132
|
+
cell.borrow_mut().insert(webview_id, webview);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
WEBVIEW_HANDLERS.with(|cell| {
|
|
136
|
+
cell.borrow_mut().insert(
|
|
137
|
+
webview_id,
|
|
138
|
+
WebViewHandlers {
|
|
139
|
+
ipc_handler: None,
|
|
140
|
+
navigation_handler: None,
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
Ok(RbWebView { webview_id })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn load_url(&self, url: String) -> Result<(), Error> {
|
|
149
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
150
|
+
self.with_wry(|wv| {
|
|
151
|
+
wv.load_url(&url).map_err(|e| {
|
|
152
|
+
Error::new(
|
|
153
|
+
ruby.exception_runtime_error(),
|
|
154
|
+
format!("Failed to load URL: {}", e),
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
})?
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn load_html(&self, html: String) -> Result<(), Error> {
|
|
161
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
162
|
+
self.with_wry(|wv| {
|
|
163
|
+
wv.load_html(&html).map_err(|e| {
|
|
164
|
+
Error::new(
|
|
165
|
+
ruby.exception_runtime_error(),
|
|
166
|
+
format!("Failed to load HTML: {}", e),
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
})?
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fn evaluate_script(&self, js: String) -> Result<(), Error> {
|
|
173
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
174
|
+
self.with_wry(|wv| {
|
|
175
|
+
wv.evaluate_script(&js).map_err(|e| {
|
|
176
|
+
Error::new(
|
|
177
|
+
ruby.exception_runtime_error(),
|
|
178
|
+
format!("Failed to evaluate script: {}", e),
|
|
179
|
+
)
|
|
180
|
+
})
|
|
181
|
+
})?
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fn set_visible(&self, visible: bool) -> Result<(), Error> {
|
|
185
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
186
|
+
self.with_wry(|wv| {
|
|
187
|
+
wv.set_visible(visible).map_err(|e| {
|
|
188
|
+
Error::new(
|
|
189
|
+
ruby.exception_runtime_error(),
|
|
190
|
+
format!("Failed to set visibility: {}", e),
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
})?
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fn set_bounds(&self, x: f64, y: f64, w: f64, h: f64) -> Result<(), Error> {
|
|
197
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
198
|
+
self.with_wry(|wv| {
|
|
199
|
+
wv.set_bounds(Rect {
|
|
200
|
+
position: wry::dpi::Position::Logical(LogicalPosition::new(x, y)),
|
|
201
|
+
size: wry::dpi::Size::Logical(LogicalSize::new(w, h)),
|
|
202
|
+
})
|
|
203
|
+
.map_err(|e| {
|
|
204
|
+
Error::new(
|
|
205
|
+
ruby.exception_runtime_error(),
|
|
206
|
+
format!("Failed to set bounds: {}", e),
|
|
207
|
+
)
|
|
208
|
+
})
|
|
209
|
+
})?
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn url(&self) -> Result<String, Error> {
|
|
213
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
214
|
+
self.with_wry(|wv| {
|
|
215
|
+
wv.url().map_err(|e| {
|
|
216
|
+
Error::new(
|
|
217
|
+
ruby.exception_runtime_error(),
|
|
218
|
+
format!("Failed to get URL: {}", e),
|
|
219
|
+
)
|
|
220
|
+
})
|
|
221
|
+
})?
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fn set_devtools(&self, open: bool) -> Result<(), Error> {
|
|
225
|
+
self.with_wry(|wv| {
|
|
226
|
+
if open {
|
|
227
|
+
wv.open_devtools();
|
|
228
|
+
} else {
|
|
229
|
+
wv.close_devtools();
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn set_background_color(&self, r: u8, g: u8, b: u8, a: u8) -> Result<(), Error> {
|
|
235
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
236
|
+
self.with_wry(|wv| {
|
|
237
|
+
wv.set_background_color((r, g, b, a)).map_err(|e| {
|
|
238
|
+
Error::new(
|
|
239
|
+
ruby.exception_runtime_error(),
|
|
240
|
+
format!("Failed to set background color: {}", e),
|
|
241
|
+
)
|
|
242
|
+
})
|
|
243
|
+
})?
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fn on_ipc_message(&self, handler: Value) -> Result<(), Error> {
|
|
247
|
+
gc::register_mark_object(handler);
|
|
248
|
+
let id = self.webview_id;
|
|
249
|
+
WEBVIEW_HANDLERS.with(|cell| {
|
|
250
|
+
if let Some(h) = cell.borrow_mut().get_mut(&id) {
|
|
251
|
+
h.ipc_handler = Some(handler);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
Ok(())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn on_navigation(&self, handler: Value) -> Result<(), Error> {
|
|
258
|
+
gc::register_mark_object(handler);
|
|
259
|
+
let id = self.webview_id;
|
|
260
|
+
WEBVIEW_HANDLERS.with(|cell| {
|
|
261
|
+
if let Some(h) = cell.borrow_mut().get_mut(&id) {
|
|
262
|
+
h.navigation_handler = Some(handler);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
Ok(())
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn focus(&self) -> Result<(), Error> {
|
|
269
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
270
|
+
self.with_wry(|wv| {
|
|
271
|
+
wv.focus().map_err(|e| {
|
|
272
|
+
Error::new(
|
|
273
|
+
ruby.exception_runtime_error(),
|
|
274
|
+
format!("Failed to focus: {}", e),
|
|
275
|
+
)
|
|
276
|
+
})
|
|
277
|
+
})?
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
fn reload(&self) -> Result<(), Error> {
|
|
281
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
282
|
+
self.with_wry(|wv| {
|
|
283
|
+
wv.reload().map_err(|e| {
|
|
284
|
+
Error::new(
|
|
285
|
+
ruby.exception_runtime_error(),
|
|
286
|
+
format!("Failed to reload: {}", e),
|
|
287
|
+
)
|
|
288
|
+
})
|
|
289
|
+
})?
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fn zoom(&self, factor: f64) -> Result<(), Error> {
|
|
293
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
294
|
+
self.with_wry(|wv| {
|
|
295
|
+
wv.zoom(factor).map_err(|e| {
|
|
296
|
+
Error::new(
|
|
297
|
+
ruby.exception_runtime_error(),
|
|
298
|
+
format!("Failed to zoom: {}", e),
|
|
299
|
+
)
|
|
300
|
+
})
|
|
301
|
+
})?
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
fn inspect(&self) -> String {
|
|
305
|
+
format!("#<Ranma::WebView id={}>", self.webview_id)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
pub fn clear_all() {
|
|
310
|
+
WEBVIEW_HANDLERS.with(|cell| cell.borrow_mut().clear());
|
|
311
|
+
WEBVIEWS.with(|cell| cell.borrow_mut().clear());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
pub fn define_webview_class(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
|
|
315
|
+
let class = module.define_class("WebView", ruby.class_object())?;
|
|
316
|
+
class.define_singleton_method("new", function!(RbWebView::new, 2))?;
|
|
317
|
+
class.define_method("load_url", method!(RbWebView::load_url, 1))?;
|
|
318
|
+
class.define_method("load_html", method!(RbWebView::load_html, 1))?;
|
|
319
|
+
class.define_method("evaluate_script", method!(RbWebView::evaluate_script, 1))?;
|
|
320
|
+
class.define_method("set_visible", method!(RbWebView::set_visible, 1))?;
|
|
321
|
+
class.define_method("visible=", method!(RbWebView::set_visible, 1))?;
|
|
322
|
+
class.define_method("set_bounds", method!(RbWebView::set_bounds, 4))?;
|
|
323
|
+
class.define_method("url", method!(RbWebView::url, 0))?;
|
|
324
|
+
class.define_method("set_devtools", method!(RbWebView::set_devtools, 1))?;
|
|
325
|
+
class.define_method("set_background_color", method!(RbWebView::set_background_color, 4))?;
|
|
326
|
+
class.define_method("on_ipc_message", method!(RbWebView::on_ipc_message, 1))?;
|
|
327
|
+
class.define_method("on_navigation", method!(RbWebView::on_navigation, 1))?;
|
|
328
|
+
class.define_method("focus", method!(RbWebView::focus, 0))?;
|
|
329
|
+
class.define_method("reload", method!(RbWebView::reload, 0))?;
|
|
330
|
+
class.define_method("zoom", method!(RbWebView::zoom, 1))?;
|
|
331
|
+
class.define_method("inspect", method!(RbWebView::inspect, 0))?;
|
|
332
|
+
class.define_method("to_s", method!(RbWebView::inspect, 0))?;
|
|
333
|
+
Ok(())
|
|
334
|
+
}
|