1use crate::clipboard_impl::ClipboardImpl;
5use crate::cursor::CursorFactory;
6use crate::dispatcher_impl::DispatcherImpl;
7use crate::embeddable_top_level_impl::EmbeddableTopLevelImpl;
8use crate::extensions::as_com_bool;
9use crate::ferro_native_application_platform::FerroNativeApplicationPlatform;
10use crate::ferro_native_menu_exporter::FerroNativeMenuExporter;
11use crate::ferro_native_drag_source::{free_data_transfer_handle, FerroNativeDragSource};
12use crate::ferro_native_gl_platform_graphics::FerroNativeGlPlatformGraphics;
13use crate::ferro_native_platform_extensions::{
14 FerroNativePlatformOptions, FerroNativeRenderingMode, MacOSPlatformOptions,
15};
16use crate::ferro_native_render_timer::FerroNativeRenderTimer;
17use crate::frn_dispatcher::FrnDispatcher;
18use crate::frn_string::to_c_string;
19use crate::helpers::ComResultExt;
20use crate::icon_loader::IconLoader;
21use crate::interop::*;
22use crate::mac_os_activatable_lifetime::MacOSActivatableLifetime;
23use crate::mac_os_mounted_volume_info_provider::MacOSMountedVolumeInfoProvider;
24use crate::mac_os_native_menu_commands::MacOSNativeMenuCommands;
25use crate::metal::MetalPlatformGraphics;
26use crate::native_platform_settings::NativePlatformSettings;
27use crate::screen_impl::ScreenImpl;
28use crate::storage_provider_api::StorageProviderApi;
29use crate::tray_icon_impl::TrayIconImpl;
30use crate::window_impl::WindowImpl;
31use ferroui_base::input::platform::{
32 Clipboard, IClipboard, IClipboardImpl, IPlatformDragSource, KeyGestureFormatInfo, PlatformHotkeyConfiguration,
33};
34use ferroui_base::input::{IKeyboardDevice, Key, KeyGesture, KeyModifiers, KeyboardDevice};
35use ferroui_base::platform::{ICursorFactory, IPlatformGraphics, IPlatformGraphicsContext, IPlatformSettings};
36use ferroui_base::rendering::composition::Compositor;
37use ferroui_base::rendering::{IRenderLoop, RenderLoop, ThreadProxyRenderTimer};
38use ferroui_base::threading::Dispatcher;
39use ferroui_base::{FerroLocator, LocatorExtensions};
40use ferroui_controls::application_lifetimes::IActivatableLifetime;
41use ferroui_controls::Application;
42use ferroui_controls::platform::{
43 IMountedVolumeInfoProvider, INativeApplicationCommands, IPlatformIconLoader, IPlatformLifetimeEventsImpl,
44 IScreenImpl, IStorageProviderFactory, ITopLevelImpl, ITrayIconImpl, IWindowImpl, IWindowingPlatform,
45};
46use ferroui_microcom::ComPtr;
47use std::cell::{Cell, RefCell};
48use std::collections::HashMap;
49use std::ffi::{c_char, c_int, c_void, CString};
50use std::rc::Rc;
51use std::sync::Arc;
52
53ferroui_base::ferro_thread_local! {
54 static KEYBOARD_DEVICE: Rc<KeyboardDevice> = KeyboardDevice::new();
55 static COMPOSITOR: RefCell<Option<Rc<Compositor>>> = const { RefCell::new(None) };
56}
57
58extern "C" {
59 fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
60 fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
61}
62
63const RTLD_NOW: c_int = 0x2;
64
65struct GCHandleDeallocator;
69
70impl IFrnGCHandleDeallocatorCallbackImpl for GCHandleDeallocator {
71 fn free_gc_handle(&self, handle: *mut c_void) {
72 crate::callback_base::guard((), || unsafe { free_data_transfer_handle(handle) })
75 }
76}
77
78pub struct FerroNativePlatform {
80 factory: RefCell<Option<ComPtr<IFerroNativeFactory>>>,
81 options: RefCell<Option<FerroNativePlatformOptions>>,
82 platform_graphics: RefCell<Option<Arc<dyn IPlatformGraphics>>>,
83 is_disposed: Cell<bool>,
84 application_menu_exporter: RefCell<Option<Rc<FerroNativeMenuExporter>>>,
86 application_dock_menu_exporter: RefCell<Option<Rc<FerroNativeMenuExporter>>>,
87}
88
89impl FerroNativePlatform {
90 pub fn keyboard_device() -> Rc<KeyboardDevice> {
92 KEYBOARD_DEVICE.with(Rc::clone)
93 }
94
95 pub fn compositor() -> Rc<Compositor> {
100 COMPOSITOR.with(|compositor| compositor.borrow().clone()).expect("the platform is initialized")
101 }
102
103 pub fn initialize_with_factory(
105 factory: ComPtr<IFerroNativeFactory>,
106 options: FerroNativePlatformOptions,
107 ) -> Rc<FerroNativePlatform> {
108 FerroLocator::current_mutable().bind_to_self(Rc::new(factory.clone()));
109
110 let result = Rc::new(FerroNativePlatform {
111 factory: RefCell::new(Some(factory)),
112 options: RefCell::new(None),
113 platform_graphics: RefCell::new(None),
114 is_disposed: Cell::new(false),
115 application_menu_exporter: RefCell::new(None),
116 application_dock_menu_exporter: RefCell::new(None),
117 });
118
119 result.do_initialize(options);
120
121 result
122 }
123
124 pub fn initialize(options: FerroNativePlatformOptions) -> Rc<FerroNativePlatform> {
134 let factory = match &options.ferro_native_library_path {
135 Some(path) => {
136 let path = CString::new(path.as_str()).expect("the library path has no NUL");
137 unsafe {
140 let lib = dlopen(path.as_ptr(), RTLD_NOW);
141 if lib.is_null() {
142 panic!("Unable to load the native library {path:?}");
143 }
144 let proc = dlsym(lib, c"CreateFerroNative".as_ptr());
145 if proc.is_null() {
146 panic!("Unable to get \"CreateFerroNative\" export from the native library");
147 }
148 let d: unsafe extern "C" fn() -> *mut IFerroNativeFactory = std::mem::transmute(proc);
149 ComPtr::from_raw(d())
150 }
151 }
152 None => create_ferro_native(),
153 };
154
155 Self::initialize_with_factory(factory.expect("the native factory"), options)
156 }
157
158 #[track_caller]
159 fn factory(&self) -> ComPtr<IFerroNativeFactory> {
160 match self.factory.borrow().clone() {
161 Some(factory) => factory,
162 None => panic!("Cannot access a disposed object: the native factory"),
163 }
164 }
165
166 pub fn setup_application_menu_exporter(&self) {
170 let exporter = FerroNativeMenuExporter::for_application(&self.factory());
171 *self.application_menu_exporter.borrow_mut() = Some(exporter);
172 }
173
174 pub fn setup_application_dock_menu_exporter(&self) {
177 let exporter = FerroNativeMenuExporter::for_dock(&self.factory());
178 *self.application_dock_menu_exporter.borrow_mut() = Some(exporter);
179 }
180
181 pub fn setup_application_name(&self) {
188 let application = Application::current().expect("the current application");
189 self.setup_application_name_with(application.name().as_deref());
190 }
191
192 pub fn setup_application_name_with(&self, name: Option<&str>) {
195 if let Some(name) = name.filter(|name| !name.trim().is_empty()) {
196 if let Some(mac_options) = self.factory().get_mac_options() {
197 mac_options.set_application_title(Some(&to_c_string(name))).check();
198 }
199 }
200 }
201
202 fn do_initialize(self: &Rc<Self>, options: FerroNativePlatformOptions) {
203 *self.options.borrow_mut() = Some(options.clone());
204 let factory = self.factory();
205 let locator = FerroLocator::current_mutable();
206
207 let application_platform = FerroNativeApplicationPlatform::new(Rc::downgrade(self));
208
209 let mac_opts = FerroLocator::current().get_service::<MacOSPlatformOptions>();
210 let mac_opts = mac_opts.as_deref().cloned().unwrap_or_default();
211
212 if let Some(mac_options) = factory.get_mac_options() {
213 mac_options.set_disable_app_delegate(as_com_bool(mac_opts.disable_ferro_app_delegate)).check();
214 }
215
216 let deallocator = IFrnGCHandleDeallocatorCallback::from_impl(GCHandleDeallocator);
217 let application_events = IFrnApplicationEvents::from_impl(application_platform.clone());
218 let dispatcher = IFrnDispatcher::from_impl(FrnDispatcher);
219 factory.initialize(Some(&deallocator), Some(&application_events), Some(&dispatcher)).check();
220
221 if let Some(mac_options) = factory.get_mac_options() {
222 mac_options.set_show_in_dock(as_com_bool(mac_opts.show_in_dock)).check();
223 mac_options.set_disable_set_process_name(as_com_bool(mac_opts.disable_set_process_name)).check();
224 }
225
226 let clipboard_impl: Rc<dyn IClipboardImpl> =
227 Rc::new(ClipboardImpl::new(factory.create_clipboard().check().expect("the native clipboard")));
228 let clipboard: Rc<dyn IClipboard> = Clipboard::new(clipboard_impl.clone());
229
230 Dispatcher::initialize_ui_thread_dispatcher(DispatcherImpl::new(
231 factory.create_platform_threading_interface().check().expect("the native threading interface"),
232 ));
233
234 let cursor_factory: Rc<dyn ICursorFactory> =
235 Rc::new(CursorFactory::new(factory.create_cursor_factory().check().expect("the native cursor factory")));
236 let screens: Rc<dyn IScreenImpl> = {
237 let factory = factory.clone();
238 ScreenImpl::new(move |events| factory.create_screens(Some(events)).check())
239 };
240 let keyboard_device: Rc<dyn IKeyboardDevice> = Self::keyboard_device();
241 let platform_settings: Rc<dyn IPlatformSettings> =
242 NativePlatformSettings::new(factory.create_platform_settings().check().expect("the native settings"));
243 let windowing_platform: Rc<dyn IWindowingPlatform> = self.clone();
244 let render_timer = Arc::new(FerroNativeRenderTimer::new(
245 factory.create_platform_render_timer().check().expect("the native render timer"),
246 ));
247 let render_loop: Arc<dyn IRenderLoop> = RenderLoop::from_timer(ThreadProxyRenderTimer::new(render_timer));
248 let lifetime_events: Rc<dyn IPlatformLifetimeEventsImpl> = application_platform;
249 let application_commands: Rc<dyn INativeApplicationCommands> = Rc::new(MacOSNativeMenuCommands::new(
250 factory.create_application_commands().check().expect("the native application commands"),
251 ));
252
253 let activatable_lifetime = Rc::new(MacOSActivatableLifetime::new());
254
255 locator
256 .bind::<dyn ICursorFactory>()
257 .to_constant(cursor_factory)
258 .bind::<dyn IScreenImpl>()
259 .to_constant(screens)
260 .bind::<dyn IPlatformIconLoader>()
261 .to_singleton::<IconLoader>(|loader| loader)
262 .bind::<dyn IKeyboardDevice>()
263 .to_constant(keyboard_device)
264 .bind::<dyn IPlatformSettings>()
265 .to_constant(platform_settings)
266 .bind::<dyn IWindowingPlatform>()
267 .to_constant(windowing_platform)
268 .bind::<dyn IClipboardImpl>()
269 .to_constant(clipboard_impl)
270 .bind::<dyn IClipboard>()
271 .to_constant(clipboard)
272 .bind::<Arc<dyn IRenderLoop>>()
273 .to_constant(Rc::new(render_loop))
274 .bind::<dyn IMountedVolumeInfoProvider>()
275 .to_constant(Rc::new(MacOSMountedVolumeInfoProvider))
276 .bind::<dyn IPlatformDragSource>()
277 .to_constant(Rc::new(FerroNativeDragSource::new()))
278 .bind::<dyn IPlatformLifetimeEventsImpl>()
279 .to_constant(lifetime_events)
280 .bind::<dyn INativeApplicationCommands>()
281 .to_constant(application_commands)
282 .bind::<dyn IActivatableLifetime>()
283 .to_constant(activatable_lifetime.clone())
284 .bind_to_self(activatable_lifetime);
287
288 let storage_provider_api = StorageProviderApi::new(
289 factory.create_storage_provider().check().expect("the native storage provider"),
290 options.app_sandbox_enabled,
291 );
292 let storage_provider_factory: Rc<dyn IStorageProviderFactory> = storage_provider_api.clone();
293 locator
294 .bind::<dyn IStorageProviderFactory>()
295 .to_constant(storage_provider_factory)
296 .bind_to_self(storage_provider_api);
300
301 let mut hotkeys =
302 PlatformHotkeyConfiguration::with_modifiers(KeyModifiers::META, KeyModifiers::SHIFT, KeyModifiers::ALT);
303 let command_modifiers = hotkeys.command_modifiers;
304 let selection_modifiers = hotkeys.selection_modifiers;
305 hotkeys.move_cursor_to_the_start_of_line.push(KeyGesture::new(Key::Left, command_modifiers));
306 hotkeys
307 .move_cursor_to_the_start_of_line_with_selection
308 .push(KeyGesture::new(Key::Left, command_modifiers | selection_modifiers));
309 hotkeys.move_cursor_to_the_end_of_line.push(KeyGesture::new(Key::Right, command_modifiers));
310 hotkeys
311 .move_cursor_to_the_end_of_line_with_selection
312 .push(KeyGesture::new(Key::Right, command_modifiers | selection_modifiers));
313
314 locator.bind_to_self(Rc::new(hotkeys));
315
316 locator.bind_to_self(Rc::new(KeyGestureFormatInfo::new(
317 Some(HashMap::from([
318 (Key::Back, "⌫".to_string()),
319 (Key::Down, "↓".to_string()),
320 (Key::End, "↘".to_string()),
321 (Key::Escape, "⎋".to_string()),
322 (Key::Home, "↖".to_string()),
323 (Key::Left, "←".to_string()),
324 (Key::Return, "↩".to_string()),
325 (Key::PageDown, "⇟".to_string()),
326 (Key::PageUp, "⇞".to_string()),
327 (Key::Right, "→".to_string()),
328 (Key::Space, "␣".to_string()),
329 (Key::Tab, "⇥".to_string()),
330 (Key::Up, "↑".to_string()),
331 ])),
332 "⌘",
333 "⌃",
334 "⌥",
335 "⇧",
336 )));
337
338 for mode in &options.rendering_mode {
339 match mode {
340 FerroNativeRenderingMode::OpenGl => {
341 let Ok(Some(display)) = factory.obtain_gl_display() else {
344 continue;
345 };
346 let Ok(gl) = FerroNativeGlPlatformGraphics::new(display, &factory) else {
347 continue;
348 };
349 *self.platform_graphics.borrow_mut() = Some(Arc::new(gl));
350 break;
351 }
352 FerroNativeRenderingMode::Metal => {
353 let Ok(metal) = MetalPlatformGraphics::new(&factory) else {
354 continue;
355 };
356 let Ok(context) = metal.try_create_context() else {
357 continue;
358 };
359 context.dispose();
360 *self.platform_graphics.borrow_mut() = Some(Arc::new(metal));
361 break;
362 }
363 FerroNativeRenderingMode::Software => break,
364 }
365 }
366
367 let platform_graphics = self.platform_graphics.borrow().clone();
368 if let Some(platform_graphics) = &platform_graphics {
369 locator.bind::<Arc<dyn IPlatformGraphics>>().to_constant(Rc::new(platform_graphics.clone()));
373 }
374
375 let compositor = Compositor::new(platform_graphics, true);
379 COMPOSITOR.with(|slot| *slot.borrow_mut() = Some(compositor.clone()));
380 locator.bind_to_self(compositor);
381 }
382
383 pub fn dispose(&self) {
387 if self.is_disposed.replace(true) {
388 return;
389 }
390
391 let exporter = self.application_menu_exporter.borrow_mut().take();
392 drop(exporter);
393 let exporter = self.application_dock_menu_exporter.borrow_mut().take();
394 drop(exporter);
395 let factory = self.factory.borrow_mut().take();
396 drop(factory);
397 }
398
399 fn options(&self) -> FerroNativePlatformOptions {
400 self.options.borrow().clone().unwrap_or_default()
401 }
402}
403
404impl IWindowingPlatform for FerroNativePlatform {
405 fn create_window(&self) -> Rc<dyn IWindowImpl> {
406 WindowImpl::new(self.factory(), self.options())
407 }
408
409 fn create_embeddable_top_level(&self) -> Rc<dyn ITopLevelImpl> {
410 EmbeddableTopLevelImpl::new(self.factory())
411 }
412
413 fn create_embeddable_window(&self) -> Rc<dyn IWindowImpl> {
414 panic!("The method or operation is not implemented.");
415 }
416
417 fn create_tray_icon(&self) -> Option<Rc<dyn ITrayIconImpl>> {
418 Some(Rc::new(TrayIconImpl::new(&self.factory())))
419 }
420
421 fn get_windows_z_order(&self, windows: &[Rc<dyn IWindowImpl>], z_order: &mut [i64]) {
422 for (window, z_order) in windows.iter().zip(z_order.iter_mut()) {
423 *z_order = window
424 .as_any()
425 .downcast_ref::<WindowImpl>()
426 .and_then(WindowImpl::z_order)
427 .map_or(0, |z_order| z_order as i64);
428 }
429 }
430}