1use crate::epoll_dispatcher_impl::EpollDispatcherImpl;
5use crate::framebuffer_toplevel_impl::FramebufferToplevelImpl;
6use crate::input::ev_dev::EvDevBackend;
7use crate::input::IInputBackend;
8use crate::linux_framebuffer_platform_options::LinuxFramebufferPlatformOptions;
9use crate::output::{DrmOutput, FbDevOutputOptions, FbdevOutput, IOutputBackend};
10use crate::DrmOutputOptions;
11use crate::raw_event_grouping::{
12 IRawEventGrouperDispatchQueue, ManualRawEventGrouperDispatchQueue,
13 ManualRawEventGrouperDispatchQueueDispatcherInputProvider,
14};
15use crate::stubs::CursorFactoryStub;
16use ferroui_base::input::platform::{KeyGestureFormatInfo, PlatformHotkeyConfiguration};
17use ferroui_base::input::{IKeyboardDevice, InputElement, KeyModifiers, KeyboardDevice};
18use ferroui_base::platform::{
19 DefaultPlatformSettings, ICursorFactory, IPlatformGraphics, IPlatformSettings, PixelFormat, SharedBitmapImpl,
20};
21use ferroui_base::reactive::{Disposable, IDisposable};
22use ferroui_base::rendering::composition::Compositor;
23use ferroui_base::rendering::{IRenderLoop, IRenderTimer, RenderLoop, SleepLoopRenderTimer, UiThreadRenderTimer};
24use ferroui_base::threading::{CancellationToken, CancellationTokenSource, Dispatcher};
25use ferroui_base::utilities::HandlerList;
26use ferroui_base::{FerroLocator, LocatorExtensions, Ref};
27use ferroui_controls::application_lifetimes::{
28 ControlledApplicationLifetimeExitEventArgs, ControlledApplicationLifetimeStartupEventArgs, IApplicationLifetime,
29 IControlledApplicationLifetime, ISingleTopLevelApplicationLifetime, ISingleViewApplicationLifetime,
30};
31use ferroui_controls::embedding::EmbeddableControlRoot;
32use ferroui_controls::platform::{IPlatformIconLoader, ITopLevelImpl, IWindowIconImpl};
33use ferroui_controls::{AppBuilder, Control, TopLevel};
34use std::any::Any;
35use std::cell::{Cell, RefCell};
36use std::collections::HashMap;
37use std::io;
38use std::rc::{Rc, Weak};
39use std::sync::Arc;
40
41#[derive(Default)]
43pub(crate) struct LinuxFramebufferIconLoaderStub;
44
45struct IconStub;
46
47impl IWindowIconImpl for IconStub {
48 fn save(&self, _output_stream: &mut dyn io::Write) -> io::Result<()> {
49 Ok(())
50 }
51}
52
53impl IPlatformIconLoader for LinuxFramebufferIconLoaderStub {
54 fn load_icon_from_file(&self, _file_name: &str) -> io::Result<Rc<dyn IWindowIconImpl>> {
55 Ok(Rc::new(IconStub))
56 }
57
58 fn load_icon_from_stream(&self, _stream: &mut dyn io::Read) -> io::Result<Rc<dyn IWindowIconImpl>> {
59 Ok(Rc::new(IconStub))
60 }
61
62 fn load_icon_from_bitmap(&self, _bitmap: Arc<SharedBitmapImpl>) -> Rc<dyn IWindowIconImpl> {
63 Rc::new(IconStub)
64 }
65}
66
67ferroui_base::ferro_thread_local! {
68 static EVENT_GROUPER_DISPATCH_QUEUE: Rc<ManualRawEventGrouperDispatchQueue> =
71 Rc::new(ManualRawEventGrouperDispatchQueue::new());
72 static COMPOSITOR: RefCell<Option<Rc<Compositor>>> = const { RefCell::new(None) };
74 static KEYBOARD_DEVICE: RefCell<Option<Rc<KeyboardDevice>>> = const { RefCell::new(None) };
76}
77
78pub struct LinuxFramebufferPlatform {
79 fb: Arc<dyn IOutputBackend>,
80}
81
82impl LinuxFramebufferPlatform {
83 fn new(backend: Arc<dyn IOutputBackend>) -> Self {
84 Self { fb: backend }
85 }
86
87 pub fn event_grouper_dispatch_queue() -> Rc<ManualRawEventGrouperDispatchQueue> {
88 EVENT_GROUPER_DISPATCH_QUEUE.with(Rc::clone)
89 }
90
91 pub(crate) fn compositor() -> Option<Rc<Compositor>> {
93 COMPOSITOR.with(|compositor| compositor.borrow().clone())
94 }
95
96 pub(crate) fn keyboard_device() -> Option<Rc<KeyboardDevice>> {
99 KEYBOARD_DEVICE.with(|keyboard| keyboard.borrow().clone())
100 }
101
102 fn initialize_platform(&self) {
103 let locator = FerroLocator::current_mutable();
104 let graphics = self.fb.as_gl_output_backend().map(|gl| gl.platform_graphics());
105 if let Some(graphics) = &graphics {
106 locator.bind::<Arc<dyn IPlatformGraphics>>().to_constant(Rc::new(graphics.clone()));
107 }
108
109 let opts = FerroLocator::current()
110 .get_service::<LinuxFramebufferPlatformOptions>()
111 .map_or_else(LinuxFramebufferPlatformOptions::default, |opts| *opts);
112
113 let timer: Arc<dyn IRenderTimer> = if opts.should_render_on_ui_thread {
114 Arc::new(UiThreadRenderTimer::new(opts.fps))
115 } else {
116 Arc::new(SleepLoopRenderTimer::new(opts.fps))
122 };
123
124 Dispatcher::initialize_ui_thread_dispatcher(EpollDispatcherImpl::new(Box::new(
125 ManualRawEventGrouperDispatchQueueDispatcherInputProvider::new(Self::event_grouper_dispatch_queue()),
126 )));
127
128 let render_loop: Arc<dyn IRenderLoop> = RenderLoop::from_timer(timer);
129 let keyboard_device = KeyboardDevice::new();
130 KEYBOARD_DEVICE.with(|keyboard| *keyboard.borrow_mut() = Some(keyboard_device.clone()));
131 let keyboard_device: Rc<dyn IKeyboardDevice> = keyboard_device;
132 let platform_settings: Rc<dyn IPlatformSettings> = Rc::new(DefaultPlatformSettings::new());
133 locator
134 .bind::<Arc<dyn IRenderLoop>>()
135 .to_constant(Rc::new(render_loop))
136 .bind::<dyn ICursorFactory>()
137 .to_transient::<CursorFactoryStub>(|stub| stub)
138 .bind::<dyn IKeyboardDevice>()
139 .to_constant(keyboard_device)
140 .bind::<dyn IPlatformIconLoader>()
141 .to_singleton::<LinuxFramebufferIconLoaderStub>(|stub| stub)
142 .bind::<dyn IPlatformSettings>()
143 .to_constant(platform_settings)
144 .bind_to_self(Rc::new(PlatformHotkeyConfiguration::new(KeyModifiers::CONTROL)))
145 .bind_to_self(Rc::new(KeyGestureFormatInfo::new(Some(HashMap::new()), "Super", "Ctrl", "Alt", "Shift")));
146
147 let compositor = Compositor::new(graphics, false);
148 COMPOSITOR.with(|slot| *slot.borrow_mut() = Some(compositor));
149 }
150
151 pub(crate) fn initialize(
157 builder: &AppBuilder,
158 output_backend: Arc<dyn IOutputBackend>,
159 input_backend: Option<Rc<dyn IInputBackend>>,
160 ) -> Rc<LinuxFramebufferLifetime> {
161 let platform = LinuxFramebufferPlatform::new(output_backend);
162 let fb = platform.fb.clone();
163 builder
164 .use_standard_runtime_platform_subsystem()
165 .use_windowing_subsystem(move || platform.initialize_platform(), "fbdev");
166 LinuxFramebufferLifetime::with_input(fb, input_backend)
167 }
168}
169
170pub struct LinuxFramebufferLifetime {
173 this: Weak<LinuxFramebufferLifetime>,
174 fb: Arc<dyn IOutputBackend>,
175 input_backend: Option<Rc<dyn IInputBackend>>,
176 top_level: RefCell<Option<Ref<EmbeddableControlRoot>>>,
177 main_view: RefCell<Option<Ref<Control>>>,
178 cts: CancellationTokenSource,
179 exit_code: Cell<i32>,
180 startup: HandlerList<dyn Fn(&ControlledApplicationLifetimeStartupEventArgs)>,
181 exit: HandlerList<dyn Fn(&ControlledApplicationLifetimeExitEventArgs)>,
182}
183
184impl LinuxFramebufferLifetime {
185 pub fn new(fb: Arc<dyn IOutputBackend>) -> Rc<Self> {
186 Self::with_input(fb, None)
187 }
188
189 pub fn with_input(fb: Arc<dyn IOutputBackend>, input: Option<Rc<dyn IInputBackend>>) -> Rc<Self> {
190 Rc::new_cyclic(|this| Self {
191 this: this.clone(),
192 fb,
193 input_backend: input,
194 top_level: RefCell::new(None),
195 main_view: RefCell::new(None),
196 cts: CancellationTokenSource::new(),
197 exit_code: Cell::new(0),
198 startup: HandlerList::new(),
199 exit: HandlerList::new(),
200 })
201 }
202
203 pub fn token(&self) -> CancellationToken {
205 self.cts.token()
206 }
207
208 pub fn exit_code(&self) -> i32 {
209 self.exit_code.get()
210 }
211
212 pub fn start(&self, args: &[String]) {
213 let e = ControlledApplicationLifetimeStartupEventArgs::new(args.iter().cloned());
214 for (_, handler) in self.startup.snapshot().iter() {
215 handler(&e);
216 }
217 }
218
219 fn subscribe<F: ?Sized + 'static>(
220 &self,
221 select: fn(&Self) -> &HandlerList<F>,
222 handler: Rc<F>,
223 ) -> Rc<dyn IDisposable> {
224 let token = select(self).add(handler);
225 let weak = self.this.clone();
226 Disposable::create(move || {
227 if let Some(this) = weak.upgrade() {
228 select(&this).remove(token);
229 }
230 })
231 }
232
233 fn ensure_top_level(&self) -> Ref<EmbeddableControlRoot> {
239 if let Some(top_level) = self.top_level.borrow().clone() {
240 return top_level;
241 }
242
243 let input_backend: Rc<dyn IInputBackend> = match &self.input_backend {
244 Some(input_backend) => input_backend.clone(),
245 None => {
246 if std::env::var("FERROUI_USE_EVDEV").is_ok_and(|value| value == "1") {
247 Rc::new(EvDevBackend::create_from_environment().unwrap_or_else(|error| panic!("{error}")))
248 } else {
249 Rc::new(crate::input::lib_input::LibInputBackend::new())
250 }
251 }
252 };
253
254 let queue: Rc<dyn IRawEventGrouperDispatchQueue> = LinuxFramebufferPlatform::event_grouper_dispatch_queue();
255 let platform_impl: Rc<dyn ITopLevelImpl> = FramebufferToplevelImpl::new(
256 self.fb.clone(),
257 input_backend,
258 Some(queue),
259 LinuxFramebufferPlatform::compositor(),
260 );
261 let tl = EmbeddableControlRoot::with_impl(platform_impl);
262 tl.prepare();
263 tl.start_rendering();
264 *self.top_level.borrow_mut() = Some(tl.clone());
265
266 let scope: Ref<InputElement> = tl.clone().upcast();
267 tl.focus_manager().set_focus_scope(&scope);
268 tl
269 }
270}
271
272impl IApplicationLifetime for LinuxFramebufferLifetime {
273 fn as_any(&self) -> &dyn Any {
274 self
275 }
276
277 fn as_controlled_application_lifetime(&self) -> Option<&dyn IControlledApplicationLifetime> {
278 Some(self)
279 }
280
281 fn as_single_view_application_lifetime(&self) -> Option<&dyn ISingleViewApplicationLifetime> {
282 Some(self)
283 }
284
285 fn as_single_top_level_application_lifetime(&self) -> Option<&dyn ISingleTopLevelApplicationLifetime> {
286 Some(self)
287 }
288}
289
290impl IControlledApplicationLifetime for LinuxFramebufferLifetime {
291 fn startup(&self, handler: Rc<dyn Fn(&ControlledApplicationLifetimeStartupEventArgs)>) -> Rc<dyn IDisposable> {
292 self.subscribe(|this| &this.startup, handler)
293 }
294
295 fn exit(&self, handler: Rc<dyn Fn(&ControlledApplicationLifetimeExitEventArgs)>) -> Rc<dyn IDisposable> {
296 self.subscribe(|this| &this.exit, handler)
297 }
298
299 fn shutdown(&self, exit_code: i32) {
300 self.exit_code.set(exit_code);
301 let e = ControlledApplicationLifetimeExitEventArgs::new(exit_code);
302 for (_, handler) in self.exit.snapshot().iter() {
303 handler(&e);
304 }
305 self.exit_code.set(e.application_exit_code());
306 self.cts.cancel();
307 }
308}
309
310impl ISingleViewApplicationLifetime for LinuxFramebufferLifetime {
311 fn main_view(&self) -> Option<Ref<Control>> {
312 self.main_view.borrow().clone()
313 }
314
315 fn set_main_view(&self, value: Option<Ref<Control>>) {
316 let top_level = self.ensure_top_level();
317 *self.main_view.borrow_mut() = value.clone();
318 top_level.set_content(value.map(Control::boxed));
319 }
320}
321
322impl ISingleTopLevelApplicationLifetime for LinuxFramebufferLifetime {
323 fn top_level(&self) -> Option<Ref<TopLevel>> {
324 Some(self.ensure_top_level().upcast())
325 }
326}
327
328pub trait LinuxFramebufferPlatformExtensions {
330 fn start_linux_fb_dev(
334 &self,
335 args: &[String],
336 fbdev: Option<&str>,
337 scaling: f64,
338 input_backend: Option<Rc<dyn IInputBackend>>,
339 ) -> io::Result<i32>;
340
341 fn start_linux_fb_dev_with_format(
344 &self,
345 args: &[String],
346 fbdev: &str,
347 format: Option<PixelFormat>,
348 scaling: f64,
349 input_backend: Option<Rc<dyn IInputBackend>>,
350 ) -> io::Result<i32>;
351
352 fn start_linux_fb_dev_with_options(
355 &self,
356 args: &[String],
357 options: FbDevOutputOptions,
358 input_backend: Option<Rc<dyn IInputBackend>>,
359 ) -> io::Result<i32>;
360
361 fn start_linux_drm(
366 &self,
367 args: &[String],
368 card: Option<&str>,
369 scaling: f64,
370 input_backend: Option<Rc<dyn IInputBackend>>,
371 ) -> io::Result<i32>;
372
373 fn start_linux_drm_with_options(
376 &self,
377 args: &[String],
378 card: Option<&str>,
379 connectors_force_probe: bool,
380 options: Option<DrmOutputOptions>,
381 input_backend: Option<Rc<dyn IInputBackend>>,
382 ) -> io::Result<i32>;
383
384 fn start_linux_direct(
387 &self,
388 args: &[String],
389 output_backend: Arc<dyn IOutputBackend>,
390 input_backend: Option<Rc<dyn IInputBackend>>,
391 ) -> i32;
392
393 fn use_linux_framebuffer(
398 &self,
399 output_backend: Arc<dyn IOutputBackend>,
400 input_backend: Option<Rc<dyn IInputBackend>>,
401 ) -> Rc<LinuxFramebufferLifetime>;
402}
403
404impl LinuxFramebufferPlatformExtensions for AppBuilder {
405 fn start_linux_fb_dev(
406 &self,
407 args: &[String],
408 fbdev: Option<&str>,
409 scaling: f64,
410 input_backend: Option<Rc<dyn IInputBackend>>,
411 ) -> io::Result<i32> {
412 let output = FbdevOutput::with_format(fbdev, None)?;
413 output.set_scaling(scaling);
414 Ok(self.start_linux_direct(args, output, input_backend))
415 }
416
417 fn start_linux_fb_dev_with_format(
418 &self,
419 args: &[String],
420 fbdev: &str,
421 format: Option<PixelFormat>,
422 scaling: f64,
423 input_backend: Option<Rc<dyn IInputBackend>>,
424 ) -> io::Result<i32> {
425 let output = FbdevOutput::with_format(Some(fbdev), format)?;
426 output.set_scaling(scaling);
427 Ok(self.start_linux_direct(args, output, input_backend))
428 }
429
430 fn start_linux_fb_dev_with_options(
431 &self,
432 args: &[String],
433 options: FbDevOutputOptions,
434 input_backend: Option<Rc<dyn IInputBackend>>,
435 ) -> io::Result<i32> {
436 let output = FbdevOutput::with_options(options)?;
437 Ok(self.start_linux_direct(args, output, input_backend))
438 }
439
440 fn start_linux_drm(
441 &self,
442 args: &[String],
443 card: Option<&str>,
444 scaling: f64,
445 input_backend: Option<Rc<dyn IInputBackend>>,
446 ) -> io::Result<i32> {
447 let output = DrmOutput::new(card, false, None)?;
448 output.set_scaling(scaling);
449 Ok(self.start_linux_direct(args, output, input_backend))
450 }
451
452 fn start_linux_drm_with_options(
453 &self,
454 args: &[String],
455 card: Option<&str>,
456 connectors_force_probe: bool,
457 options: Option<DrmOutputOptions>,
458 input_backend: Option<Rc<dyn IInputBackend>>,
459 ) -> io::Result<i32> {
460 let output = DrmOutput::new(card, connectors_force_probe, options)?;
461 Ok(self.start_linux_direct(args, output, input_backend))
462 }
463
464 fn start_linux_direct(
465 &self,
466 args: &[String],
467 output_backend: Arc<dyn IOutputBackend>,
468 input_backend: Option<Rc<dyn IInputBackend>>,
469 ) -> i32 {
470 let lifetime = LinuxFramebufferPlatform::initialize(self, output_backend, input_backend);
471 self.setup_with_lifetime(lifetime.clone());
472 lifetime.start(args);
473 let instance = self.instance().unwrap_or_else(|| panic!("The application was not created"));
474 instance.run_with_token(&lifetime.token());
475 lifetime.exit_code()
476 }
477
478 fn use_linux_framebuffer(
479 &self,
480 output_backend: Arc<dyn IOutputBackend>,
481 input_backend: Option<Rc<dyn IInputBackend>>,
482 ) -> Rc<LinuxFramebufferLifetime> {
483 LinuxFramebufferPlatform::initialize(self, output_backend, input_backend)
484 }
485}