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.
@@ -0,0 +1,245 @@
1
+ use std::cell::RefCell;
2
+ use std::collections::HashMap;
3
+ use magnus::{function, gc, method, prelude::*, Error, RArray, Ruby, Symbol, Value};
4
+ use global_hotkey::hotkey::{Code, Modifiers as GhModifiers};
5
+ use global_hotkey::GlobalHotKeyManager;
6
+
7
+ thread_local! {
8
+ static HOTKEY_MANAGER: RefCell<Option<GlobalHotKeyManager>> = const { RefCell::new(None) };
9
+ static HOTKEY_HANDLERS: RefCell<HashMap<u32, Value>> = RefCell::new(HashMap::new());
10
+ }
11
+
12
+ pub fn init_manager() {
13
+ let manager = GlobalHotKeyManager::new().expect("Failed to create GlobalHotKeyManager");
14
+ HOTKEY_MANAGER.with(|cell| {
15
+ *cell.borrow_mut() = Some(manager);
16
+ });
17
+ }
18
+
19
+ pub fn dispatch_hotkey_event(id: u32) {
20
+ HOTKEY_HANDLERS.with(|cell| {
21
+ let handlers = cell.borrow();
22
+ if let Some(handler) = handlers.get(&id) {
23
+ let result: Result<Value, _> = handler.funcall("call", ());
24
+ if let Err(e) = result {
25
+ eprintln!("Ranma: Error in hotkey handler: {}", e);
26
+ }
27
+ }
28
+ });
29
+ }
30
+
31
+ pub fn clear_all() {
32
+ HOTKEY_HANDLERS.with(|cell| {
33
+ cell.borrow_mut().clear();
34
+ });
35
+ HOTKEY_MANAGER.with(|cell| {
36
+ *cell.borrow_mut() = None;
37
+ });
38
+ }
39
+
40
+ fn symbol_to_code(ruby: &Ruby, sym: Symbol) -> Result<Code, Error> {
41
+ let name = unsafe { sym.to_s() }.map_err(|_| {
42
+ Error::new(ruby.exception_type_error(), "Expected a Symbol for key")
43
+ })?;
44
+ let code = match name.as_ref() {
45
+ "a" => Code::KeyA,
46
+ "b" => Code::KeyB,
47
+ "c" => Code::KeyC,
48
+ "d" => Code::KeyD,
49
+ "e" => Code::KeyE,
50
+ "f" => Code::KeyF,
51
+ "g" => Code::KeyG,
52
+ "h" => Code::KeyH,
53
+ "i" => Code::KeyI,
54
+ "j" => Code::KeyJ,
55
+ "k" => Code::KeyK,
56
+ "l" => Code::KeyL,
57
+ "m" => Code::KeyM,
58
+ "n" => Code::KeyN,
59
+ "o" => Code::KeyO,
60
+ "p" => Code::KeyP,
61
+ "q" => Code::KeyQ,
62
+ "r" => Code::KeyR,
63
+ "s" => Code::KeyS,
64
+ "t" => Code::KeyT,
65
+ "u" => Code::KeyU,
66
+ "v" => Code::KeyV,
67
+ "w" => Code::KeyW,
68
+ "x" => Code::KeyX,
69
+ "y" => Code::KeyY,
70
+ "z" => Code::KeyZ,
71
+ "0" => Code::Digit0,
72
+ "1" => Code::Digit1,
73
+ "2" => Code::Digit2,
74
+ "3" => Code::Digit3,
75
+ "4" => Code::Digit4,
76
+ "5" => Code::Digit5,
77
+ "6" => Code::Digit6,
78
+ "7" => Code::Digit7,
79
+ "8" => Code::Digit8,
80
+ "9" => Code::Digit9,
81
+ "f1" => Code::F1,
82
+ "f2" => Code::F2,
83
+ "f3" => Code::F3,
84
+ "f4" => Code::F4,
85
+ "f5" => Code::F5,
86
+ "f6" => Code::F6,
87
+ "f7" => Code::F7,
88
+ "f8" => Code::F8,
89
+ "f9" => Code::F9,
90
+ "f10" => Code::F10,
91
+ "f11" => Code::F11,
92
+ "f12" => Code::F12,
93
+ "up" => Code::ArrowUp,
94
+ "down" => Code::ArrowDown,
95
+ "left" => Code::ArrowLeft,
96
+ "right" => Code::ArrowRight,
97
+ "home" => Code::Home,
98
+ "end" => Code::End,
99
+ "page_up" => Code::PageUp,
100
+ "page_down" => Code::PageDown,
101
+ "backspace" => Code::Backspace,
102
+ "delete" => Code::Delete,
103
+ "insert" => Code::Insert,
104
+ "enter" => Code::Enter,
105
+ "tab" => Code::Tab,
106
+ "space" => Code::Space,
107
+ "escape" => Code::Escape,
108
+ "comma" => Code::Comma,
109
+ "period" => Code::Period,
110
+ "slash" => Code::Slash,
111
+ "backslash" => Code::Backslash,
112
+ "semicolon" => Code::Semicolon,
113
+ "quote" => Code::Quote,
114
+ "left_bracket" => Code::BracketLeft,
115
+ "right_bracket" => Code::BracketRight,
116
+ "minus" => Code::Minus,
117
+ "equal" => Code::Equal,
118
+ "backquote" => Code::Backquote,
119
+ "caps_lock" => Code::CapsLock,
120
+ "num_lock" => Code::NumLock,
121
+ "scroll_lock" => Code::ScrollLock,
122
+ "print_screen" => Code::PrintScreen,
123
+ "pause" => Code::Pause,
124
+ _ => {
125
+ return Err(Error::new(
126
+ ruby.exception_runtime_error(),
127
+ format!("Unknown key: :{}", name),
128
+ ));
129
+ }
130
+ };
131
+ Ok(code)
132
+ }
133
+
134
+ fn symbols_to_modifiers(ruby: &Ruby, arr: RArray) -> Result<GhModifiers, Error> {
135
+ let mut mods = GhModifiers::empty();
136
+ for val in arr.into_iter() {
137
+ let sym = Symbol::try_convert(val).map_err(|_| {
138
+ Error::new(ruby.exception_type_error(), "Expected Symbol in modifiers array")
139
+ })?;
140
+ let name = unsafe { sym.to_s() }.map_err(|_| {
141
+ Error::new(ruby.exception_type_error(), "Expected a Symbol for modifier")
142
+ })?;
143
+ match name.as_ref() {
144
+ "alt" => mods |= GhModifiers::ALT,
145
+ "shift" => mods |= GhModifiers::SHIFT,
146
+ "control" | "ctrl" => mods |= GhModifiers::CONTROL,
147
+ "super" | "meta" | "command" | "cmd" => mods |= GhModifiers::SUPER,
148
+ _ => {
149
+ return Err(Error::new(
150
+ ruby.exception_runtime_error(),
151
+ format!("Unknown modifier: :{}", name),
152
+ ));
153
+ }
154
+ }
155
+ }
156
+ Ok(mods)
157
+ }
158
+
159
+ #[magnus::wrap(class = "Ranma::HotKey", free_immediately, size)]
160
+ pub struct RbHotKey {
161
+ hotkey: global_hotkey::hotkey::HotKey,
162
+ }
163
+
164
+ unsafe impl Send for RbHotKey {}
165
+
166
+ impl RbHotKey {
167
+ fn new(modifiers: RArray, key: Symbol) -> Result<Self, Error> {
168
+ let ruby = unsafe { Ruby::get_unchecked() };
169
+ let gh_mods = symbols_to_modifiers(&ruby, modifiers)?;
170
+ let code = symbol_to_code(&ruby, key)?;
171
+ let mods_opt = if gh_mods.is_empty() {
172
+ None
173
+ } else {
174
+ Some(gh_mods)
175
+ };
176
+ let hotkey = global_hotkey::hotkey::HotKey::new(mods_opt, code);
177
+ Ok(RbHotKey { hotkey })
178
+ }
179
+
180
+ fn id(&self) -> u32 {
181
+ self.hotkey.id()
182
+ }
183
+
184
+ fn register(&self, handler: Value) -> Result<(), Error> {
185
+ let ruby = unsafe { Ruby::get_unchecked() };
186
+ gc::register_mark_object(handler);
187
+ HOTKEY_MANAGER.with(|cell| {
188
+ let borrow = cell.borrow();
189
+ let manager = borrow.as_ref().ok_or_else(|| {
190
+ Error::new(
191
+ ruby.exception_runtime_error(),
192
+ "GlobalHotKeyManager not initialized. Call App.start first.",
193
+ )
194
+ })?;
195
+ manager.register(self.hotkey).map_err(|e| {
196
+ Error::new(
197
+ ruby.exception_runtime_error(),
198
+ format!("Failed to register hotkey: {}", e),
199
+ )
200
+ })?;
201
+ HOTKEY_HANDLERS.with(|h| {
202
+ h.borrow_mut().insert(self.hotkey.id(), handler);
203
+ });
204
+ Ok(())
205
+ })
206
+ }
207
+
208
+ fn unregister(&self) -> Result<(), Error> {
209
+ let ruby = unsafe { Ruby::get_unchecked() };
210
+ HOTKEY_MANAGER.with(|cell| {
211
+ let borrow = cell.borrow();
212
+ let manager = borrow.as_ref().ok_or_else(|| {
213
+ Error::new(
214
+ ruby.exception_runtime_error(),
215
+ "GlobalHotKeyManager not initialized.",
216
+ )
217
+ })?;
218
+ manager.unregister(self.hotkey).map_err(|e| {
219
+ Error::new(
220
+ ruby.exception_runtime_error(),
221
+ format!("Failed to unregister hotkey: {}", e),
222
+ )
223
+ })?;
224
+ HOTKEY_HANDLERS.with(|h| {
225
+ h.borrow_mut().remove(&self.hotkey.id());
226
+ });
227
+ Ok(())
228
+ })
229
+ }
230
+
231
+ fn inspect(&self) -> String {
232
+ format!("#<Ranma::HotKey id={}>", self.hotkey.id())
233
+ }
234
+ }
235
+
236
+ pub fn define_hotkey_class(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
237
+ let class = module.define_class("HotKey", ruby.class_object())?;
238
+ class.define_singleton_method("new", function!(RbHotKey::new, 2))?;
239
+ class.define_method("id", method!(RbHotKey::id, 0))?;
240
+ class.define_method("register", method!(RbHotKey::register, 1))?;
241
+ class.define_method("unregister", method!(RbHotKey::unregister, 0))?;
242
+ class.define_method("inspect", method!(RbHotKey::inspect, 0))?;
243
+ class.define_method("to_s", method!(RbHotKey::inspect, 0))?;
244
+ Ok(())
245
+ }
@@ -0,0 +1,318 @@
1
+ //! macOS IME preedit support via ObjC runtime method swizzling.
2
+ //!
3
+ //! tao 0.34 only emits `ReceivedImeText` for committed text.
4
+ //! This module swizzles `setMarkedText:selectedRange:replacementRange:`
5
+ //! and `unmarkText` on tao's NSView to capture preedit (composition) text.
6
+ //!
7
+ //! When a candidate is selected from the IME candidate window,
8
+ //! macOS calls `unmarkText` WITHOUT calling `insertText:replacementRange:`.
9
+ //! This module detects that case and generates a commit event from the
10
+ //! last preedit text.
11
+ //!
12
+ //! The swizzled methods do NOT touch Ruby. They only store the preedit text
13
+ //! in a thread-local queue. Events are dispatched from the event loop via
14
+ //! `dispatch_pending_preedit()`.
15
+
16
+ #[cfg(target_os = "macos")]
17
+ use std::cell::RefCell;
18
+ #[cfg(target_os = "macos")]
19
+ use std::ffi::{c_char, c_void, CStr};
20
+ #[cfg(target_os = "macos")]
21
+ use std::sync::atomic::{AtomicBool, Ordering};
22
+
23
+ #[cfg(target_os = "macos")]
24
+ use magnus::{Ruby, Symbol};
25
+ #[cfg(target_os = "macos")]
26
+ use tao::window::WindowId;
27
+
28
+ // --- ObjC runtime FFI ---
29
+
30
+ #[cfg(target_os = "macos")]
31
+ type Id = *mut c_void;
32
+ #[cfg(target_os = "macos")]
33
+ type Sel = *mut c_void;
34
+ #[cfg(target_os = "macos")]
35
+ type Class = *mut c_void;
36
+ #[cfg(target_os = "macos")]
37
+ type Method = *mut c_void;
38
+ #[cfg(target_os = "macos")]
39
+ type Imp = *mut c_void;
40
+
41
+ #[cfg(target_os = "macos")]
42
+ #[repr(C)]
43
+ #[derive(Copy, Clone)]
44
+ struct NSRange {
45
+ location: u64,
46
+ length: u64,
47
+ }
48
+
49
+ #[cfg(target_os = "macos")]
50
+ extern "C" {
51
+ fn sel_registerName(name: *const c_char) -> Sel;
52
+ fn object_getClass(obj: Id) -> Class;
53
+ fn class_getInstanceMethod(cls: Class, sel: Sel) -> Method;
54
+ fn method_getImplementation(method: Method) -> Imp;
55
+ fn method_setImplementation(method: Method, imp: Imp) -> Imp;
56
+ fn objc_msgSend();
57
+ }
58
+
59
+ // --- Original IMPs ---
60
+
61
+ #[cfg(target_os = "macos")]
62
+ static mut ORIGINAL_SET_MARKED_TEXT: Option<
63
+ unsafe extern "C" fn(Id, Sel, Id, NSRange, NSRange),
64
+ > = None;
65
+
66
+ #[cfg(target_os = "macos")]
67
+ static mut ORIGINAL_UNMARK_TEXT: Option<unsafe extern "C" fn(Id, Sel)> = None;
68
+
69
+ #[cfg(target_os = "macos")]
70
+ static mut ORIGINAL_INSERT_TEXT: Option<
71
+ unsafe extern "C" fn(Id, Sel, Id, NSRange),
72
+ > = None;
73
+
74
+ // --- Pending preedit queue ---
75
+
76
+ #[cfg(target_os = "macos")]
77
+ thread_local! {
78
+ static IME_WINDOW_ID: RefCell<Option<WindowId>> = RefCell::new(None);
79
+ static PENDING_PREEDIT: RefCell<Option<(String, usize)>> = RefCell::new(None);
80
+ static LAST_PREEDIT_TEXT: RefCell<String> = RefCell::new(String::new());
81
+ static INSERT_TEXT_CALLED: RefCell<bool> = RefCell::new(false);
82
+ static PENDING_COMMIT: RefCell<Option<String>> = RefCell::new(None);
83
+ }
84
+
85
+ #[cfg(target_os = "macos")]
86
+ static SWIZZLED: AtomicBool = AtomicBool::new(false);
87
+
88
+ // --- NSString helpers ---
89
+
90
+ #[cfg(target_os = "macos")]
91
+ unsafe fn nsstring_to_rust(obj: Id) -> String {
92
+ if obj.is_null() {
93
+ return String::new();
94
+ }
95
+
96
+ let string_sel = sel_registerName(b"string\0".as_ptr() as *const c_char);
97
+ let responds_sel =
98
+ sel_registerName(b"respondsToSelector:\0".as_ptr() as *const c_char);
99
+
100
+ let responds_fn: unsafe extern "C" fn(Id, Sel, Sel) -> bool =
101
+ std::mem::transmute(objc_msgSend as *const ());
102
+ let has_string = responds_fn(obj, responds_sel, string_sel);
103
+
104
+ let ns_string = if has_string {
105
+ let get_string: unsafe extern "C" fn(Id, Sel) -> Id =
106
+ std::mem::transmute(objc_msgSend as *const ());
107
+ let s = get_string(obj, string_sel);
108
+ if s.is_null() {
109
+ return String::new();
110
+ }
111
+ s
112
+ } else {
113
+ obj
114
+ };
115
+
116
+ let utf8_sel = sel_registerName(b"UTF8String\0".as_ptr() as *const c_char);
117
+ let get_utf8: unsafe extern "C" fn(Id, Sel) -> *const c_char =
118
+ std::mem::transmute(objc_msgSend as *const ());
119
+ let utf8_ptr = get_utf8(ns_string, utf8_sel);
120
+
121
+ if utf8_ptr.is_null() {
122
+ return String::new();
123
+ }
124
+
125
+ CStr::from_ptr(utf8_ptr).to_string_lossy().into_owned()
126
+ }
127
+
128
+ // --- Swizzled methods (NO Ruby access!) ---
129
+
130
+ #[cfg(target_os = "macos")]
131
+ unsafe extern "C" fn swizzled_set_marked_text(
132
+ this: Id,
133
+ cmd: Sel,
134
+ string: Id,
135
+ selected_range: NSRange,
136
+ replacement_range: NSRange,
137
+ ) {
138
+ let text = nsstring_to_rust(string);
139
+ let cursor_pos = (selected_range.location + selected_range.length) as usize;
140
+
141
+ LAST_PREEDIT_TEXT.with(|cell| {
142
+ *cell.borrow_mut() = text.clone();
143
+ });
144
+ INSERT_TEXT_CALLED.with(|cell| {
145
+ *cell.borrow_mut() = false;
146
+ });
147
+
148
+ if let Some(original) = ORIGINAL_SET_MARKED_TEXT {
149
+ original(this, cmd, string, selected_range, replacement_range);
150
+ }
151
+
152
+ PENDING_PREEDIT.with(|cell| {
153
+ *cell.borrow_mut() = Some((text, cursor_pos));
154
+ });
155
+ }
156
+
157
+ #[cfg(target_os = "macos")]
158
+ unsafe extern "C" fn swizzled_unmark_text(this: Id, cmd: Sel) {
159
+ let insert_was_called = INSERT_TEXT_CALLED.with(|cell| *cell.borrow());
160
+ let last_text = LAST_PREEDIT_TEXT.with(|cell| cell.borrow().clone());
161
+
162
+ if !insert_was_called && !last_text.is_empty() {
163
+ PENDING_COMMIT.with(|cell| {
164
+ *cell.borrow_mut() = Some(last_text);
165
+ });
166
+ }
167
+
168
+ PENDING_PREEDIT.with(|cell| {
169
+ *cell.borrow_mut() = Some((String::new(), 0));
170
+ });
171
+
172
+ LAST_PREEDIT_TEXT.with(|cell| {
173
+ cell.borrow_mut().clear();
174
+ });
175
+ INSERT_TEXT_CALLED.with(|cell| {
176
+ *cell.borrow_mut() = false;
177
+ });
178
+
179
+ if let Some(original) = ORIGINAL_UNMARK_TEXT {
180
+ original(this, cmd);
181
+ }
182
+ }
183
+
184
+ #[cfg(target_os = "macos")]
185
+ unsafe extern "C" fn swizzled_insert_text(
186
+ this: Id,
187
+ cmd: Sel,
188
+ string: Id,
189
+ replacement_range: NSRange,
190
+ ) {
191
+ INSERT_TEXT_CALLED.with(|cell| {
192
+ *cell.borrow_mut() = true;
193
+ });
194
+ LAST_PREEDIT_TEXT.with(|cell| {
195
+ cell.borrow_mut().clear();
196
+ });
197
+
198
+ if let Some(original) = ORIGINAL_INSERT_TEXT {
199
+ original(this, cmd, string, replacement_range);
200
+ }
201
+ }
202
+
203
+ // --- Public API ---
204
+
205
+ #[cfg(target_os = "macos")]
206
+ pub fn setup_ime_swizzle(window_id: WindowId, ns_view_ptr: usize) {
207
+ if SWIZZLED.load(Ordering::Relaxed) {
208
+ IME_WINDOW_ID.with(|cell| {
209
+ *cell.borrow_mut() = Some(window_id);
210
+ });
211
+ return;
212
+ }
213
+
214
+ unsafe {
215
+ let view = ns_view_ptr as Id;
216
+ if view.is_null() {
217
+ return;
218
+ }
219
+
220
+ let class = object_getClass(view);
221
+
222
+ // --- setMarkedText:selectedRange:replacementRange: ---
223
+ let smt_sel = sel_registerName(
224
+ b"setMarkedText:selectedRange:replacementRange:\0".as_ptr()
225
+ as *const c_char,
226
+ );
227
+ let smt_method = class_getInstanceMethod(class, smt_sel);
228
+ if !smt_method.is_null() {
229
+ let original_imp = method_getImplementation(smt_method);
230
+ ORIGINAL_SET_MARKED_TEXT = Some(std::mem::transmute(original_imp));
231
+
232
+ let new_imp: unsafe extern "C" fn(Id, Sel, Id, NSRange, NSRange) =
233
+ swizzled_set_marked_text;
234
+ method_setImplementation(smt_method, new_imp as Imp);
235
+ }
236
+
237
+ // --- unmarkText ---
238
+ let umt_sel =
239
+ sel_registerName(b"unmarkText\0".as_ptr() as *const c_char);
240
+ let umt_method = class_getInstanceMethod(class, umt_sel);
241
+ if !umt_method.is_null() {
242
+ let original_imp = method_getImplementation(umt_method);
243
+ ORIGINAL_UNMARK_TEXT = Some(std::mem::transmute(original_imp));
244
+
245
+ let new_imp: unsafe extern "C" fn(Id, Sel) = swizzled_unmark_text;
246
+ method_setImplementation(umt_method, new_imp as Imp);
247
+ }
248
+
249
+ // --- insertText:replacementRange: ---
250
+ let it_sel = sel_registerName(
251
+ b"insertText:replacementRange:\0".as_ptr() as *const c_char,
252
+ );
253
+ let it_method = class_getInstanceMethod(class, it_sel);
254
+ if !it_method.is_null() {
255
+ let original_imp = method_getImplementation(it_method);
256
+ ORIGINAL_INSERT_TEXT = Some(std::mem::transmute(original_imp));
257
+
258
+ let new_imp: unsafe extern "C" fn(Id, Sel, Id, NSRange) =
259
+ swizzled_insert_text;
260
+ method_setImplementation(it_method, new_imp as Imp);
261
+ }
262
+
263
+ IME_WINDOW_ID.with(|cell| {
264
+ *cell.borrow_mut() = Some(window_id);
265
+ });
266
+
267
+ SWIZZLED.store(true, Ordering::Relaxed);
268
+ }
269
+ }
270
+
271
+ #[cfg(target_os = "macos")]
272
+ fn sym(ruby: &Ruby, name: &str) -> Symbol {
273
+ ruby.sym_new(name).into()
274
+ }
275
+
276
+ #[cfg(target_os = "macos")]
277
+ pub fn dispatch_pending_preedit() {
278
+ let ruby = unsafe { Ruby::get_unchecked() };
279
+
280
+ // Dispatch pending preedit (composition text display)
281
+ PENDING_PREEDIT.with(|cell| {
282
+ if let Some((text, cursor_pos)) = cell.borrow_mut().take() {
283
+ IME_WINDOW_ID.with(|wid_cell| {
284
+ if let Some(window_id) = *wid_cell.borrow() {
285
+ let hash = ruby.hash_new();
286
+ let _ = hash.aset(sym(&ruby, "type"), sym(&ruby, "ime_preedit"));
287
+ let _ = hash.aset(sym(&ruby, "text"), text);
288
+ let _ = hash.aset(sym(&ruby, "cursor_pos"), cursor_pos);
289
+ crate::window_store::dispatch_event(&ruby, &window_id, hash);
290
+ }
291
+ });
292
+ }
293
+ });
294
+
295
+ // Dispatch pending committed text (from candidate window selection)
296
+ PENDING_COMMIT.with(|cell| {
297
+ if let Some(text) = cell.borrow_mut().take() {
298
+ IME_WINDOW_ID.with(|wid_cell| {
299
+ if let Some(window_id) = *wid_cell.borrow() {
300
+ let hash = ruby.hash_new();
301
+ let _ = hash.aset(sym(&ruby, "type"), sym(&ruby, "received_ime_text"));
302
+ let _ = hash.aset(sym(&ruby, "text"), text);
303
+ crate::window_store::dispatch_event(&ruby, &window_id, hash);
304
+ }
305
+ });
306
+ }
307
+ });
308
+ }
309
+
310
+ #[cfg(not(target_os = "macos"))]
311
+ pub fn setup_ime_swizzle(
312
+ _window_id: tao::window::WindowId,
313
+ _ns_view_ptr: usize,
314
+ ) {
315
+ }
316
+
317
+ #[cfg(not(target_os = "macos"))]
318
+ pub fn dispatch_pending_preedit() {}
@@ -0,0 +1,42 @@
1
+ mod app;
2
+ mod clipboard;
3
+ mod dpi;
4
+ mod event_loop;
5
+ mod events;
6
+ mod hotkey;
7
+ mod ime;
8
+ mod menu;
9
+ mod monitor;
10
+ mod painter;
11
+ mod proxy;
12
+ mod surface;
13
+ mod theme;
14
+ mod tray;
15
+ mod webview;
16
+ mod window;
17
+ mod window_store;
18
+
19
+ use magnus::{Error, Ruby};
20
+
21
+ #[magnus::init]
22
+ fn init(ruby: &Ruby) -> Result<(), Error> {
23
+ let module = ruby.define_module("Ranma")?;
24
+
25
+ dpi::define_dpi_classes(ruby, &module)?;
26
+ window::define_window_class(ruby, &module)?;
27
+ window::define_app_window_class(ruby, &module)?;
28
+ event_loop::define_event_loop(ruby, &module)?;
29
+ proxy::define_proxy_class(ruby, &module)?;
30
+ monitor::define_monitor_class(ruby, &module)?;
31
+ app::define_app(ruby, &module)?;
32
+ menu::define_menu_classes(ruby, &module)?;
33
+ tray::define_tray_class(ruby, &module)?;
34
+ theme::define_theme_class(ruby, &module)?;
35
+ clipboard::define_clipboard_class(ruby, &module)?;
36
+ hotkey::define_hotkey_class(ruby, &module)?;
37
+ webview::define_webview_class(ruby, &module)?;
38
+ surface::define_surface_class(ruby, &module)?;
39
+ painter::define_painter_classes(ruby, &module)?;
40
+
41
+ Ok(())
42
+ }