// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial use std::cell::RefCell; #[cfg(not(feature = "libseat"))] use std::fs::OpenOptions; use std::os::fd::{AsFd, BorrowedFd, OwnedFd, RawFd}; #[cfg(feature = "libseat")] use std::os::fd::{AsRawFd, FromRawFd}; #[cfg(not(feature = "libseat"))] use std::os::unix::fs::OpenOptionsExt; use std::rc::Rc; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use calloop::{EventLoop, RegistrationToken}; use i_slint_core::platform::PlatformError; use crate::fullscreenwindowadapter::FullscreenWindowAdapter; #[cfg(not(any( target_family = "windows", target_os = "macos", target_os = "ios", target_arch = "wasm32" )))] mod input; #[derive(Clone)] struct Proxy { loop_signal: Arc>>, quit_loop: Arc, user_event_channel: Arc>>>, } impl Proxy { fn new(event_channel: calloop::channel::Sender>) -> Self { Self { loop_signal: Arc::new(Mutex::new(None)), quit_loop: Arc::new(AtomicBool::new(false)), user_event_channel: Arc::new(Mutex::new(event_channel)), } } } impl i_slint_core::platform::EventLoopProxy for Proxy { fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> { let signal = self.loop_signal.lock().unwrap(); signal.as_ref().map_or_else( || Err(i_slint_core::api::EventLoopError::EventLoopTerminated), |signal| { self.quit_loop.store(true, std::sync::atomic::Ordering::Release); signal.wakeup(); Ok(()) }, ) } fn invoke_from_event_loop( &self, event: Box, ) -> Result<(), i_slint_core::api::EventLoopError> { let user_event_channel = self.user_event_channel.lock().unwrap(); user_event_channel .send(event) .map_err(|_| i_slint_core::api::EventLoopError::EventLoopTerminated) } } pub struct Backend { #[cfg(feature = "libseat")] seat: Rc>, window: RefCell>>, user_event_receiver: RefCell>>>, proxy: Proxy, renderer_factory: for<'a> fn( &'a crate::DeviceOpener, ) -> Result< Box, PlatformError, >, sel_clipboard: RefCell>, clipboard: RefCell>, } impl Backend { pub fn new() -> Result { Self::new_with_renderer_by_name(None) } pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result { let (user_event_sender, user_event_receiver) = calloop::channel::channel(); let renderer_factory = match renderer_name { #[cfg(feature = "renderer-skia-vulkan")] Some("skia-vulkan") => crate::renderer::skia::SkiaRendererAdapter::new_vulkan, #[cfg(feature = "renderer-skia-opengl")] Some("skia-opengl") => crate::renderer::skia::SkiaRendererAdapter::new_opengl, #[cfg(feature = "renderer-femtovg")] Some("femtovg") => crate::renderer::femtovg::FemtoVGRendererAdapter::new, None => crate::renderer::try_skia_then_femtovg, Some(renderer_name) => { eprintln!( "slint linuxkms backend: unrecognized renderer {}, falling back default", renderer_name ); crate::renderer::try_skia_then_femtovg } }; #[cfg(feature = "libseat")] let seat_active = Rc::new(RefCell::new(false)); //libseat::set_log_level(libseat::LogLevel::Debug); #[cfg(feature = "libseat")] let mut seat = { let seat_active = seat_active.clone(); libseat::Seat::open(move |_seat, event| match event { libseat::SeatEvent::Enable => { *seat_active.borrow_mut() = true; } libseat::SeatEvent::Disable => { unimplemented!("Seat deactivation is not implemented"); } }) .map_err(|e| format!("Error opening session with libseat: {e}"))? }; #[cfg(feature = "libseat")] while !(*seat_active.borrow()) { if seat.dispatch(5000).map_err(|e| format!("Error waiting for seat activation: {e}"))? == 0 { return Err(format!("Timeout while waiting to activate session").into()); } } Ok(Backend { #[cfg(feature = "libseat")] seat: Rc::new(RefCell::new(seat)), window: Default::default(), user_event_receiver: RefCell::new(Some(user_event_receiver)), proxy: Proxy::new(user_event_sender), renderer_factory, sel_clipboard: Default::default(), clipboard: Default::default(), }) } } impl i_slint_core::platform::Platform for Backend { fn create_window_adapter( &self, ) -> Result, PlatformError> { #[cfg(feature = "libseat")] let device_accessor = |device: &std::path::Path| -> Result, PlatformError> { let device = self .seat .borrow_mut() .open_device(&device) .map_err(|e| format!("Error opening device: {e}"))?; // For polling for drm::control::Event::PageFlip we need a blocking FD. Would be better to do this non-blocking let fd = device.as_fd().as_raw_fd(); let flags = nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_GETFL) .map_err(|e| format!("Error getting file descriptor flags: {e}"))?; let mut flags = nix::fcntl::OFlag::from_bits_retain(flags); flags.remove(nix::fcntl::OFlag::O_NONBLOCK); nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_SETFL(flags)) .map_err(|e| format!("Error making device fd non-blocking: {e}"))?; // Safety: We take ownership of the now shared FD, ... although we should be using libseat's close_device.... Ok(Rc::new(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) })) }; #[cfg(not(feature = "libseat"))] let device_accessor = |device: &std::path::Path| -> Result, PlatformError> { let device = OpenOptions::new() .custom_flags((nix::fcntl::OFlag::O_NOCTTY | nix::fcntl::OFlag::O_CLOEXEC).bits()) .read(true) .write(true) .open(device) .map(|file| file.into()) .map_err(|e| format!("Error opening device: {e}"))?; Ok(Rc::new(device)) }; // This could be per-screen, once we support multiple outputs let rotation = std::env::var("SLINT_KMS_ROTATION").map_or(Ok(Default::default()), |rot_str| { rot_str .as_str() .try_into() .map_err(|e| format!("Failed to parse SLINT_KMS_ROTATION: {e}")) })?; let renderer = (self.renderer_factory)(&device_accessor)?; let adapter = FullscreenWindowAdapter::new(renderer, rotation)?; *self.window.borrow_mut() = Some(adapter.clone()); Ok(adapter) } fn run_event_loop(&self) -> Result<(), PlatformError> { let mut event_loop: EventLoop = EventLoop::try_new().map_err(|e| format!("Error creating event loop: {}", e))?; let loop_signal = event_loop.get_signal(); *self.proxy.loop_signal.lock().unwrap() = Some(loop_signal.clone()); let quit_loop = self.proxy.quit_loop.clone(); let mouse_position_property = input::LibInputHandler::init( &self.window, &event_loop.handle(), #[cfg(feature = "libseat")] &self.seat, )?; let Some(user_event_receiver) = self.user_event_receiver.borrow_mut().take() else { return Err( format!("Re-entering the linuxkms event loop is currently not supported").into() ); }; event_loop .handle() .insert_source(user_event_receiver, |event, _, _| { let calloop::channel::Event::Msg(callback) = event else { return }; callback(); }) .map_err( |e: calloop::InsertError>>| { format!("Error registering user event channel source: {e}") }, )?; let mut loop_data = LoopData::default(); quit_loop.store(false, std::sync::atomic::Ordering::Release); let mut page_flip_handler_registration_token = None; while !quit_loop.load(std::sync::atomic::Ordering::Acquire) { i_slint_core::platform::update_timers_and_animations(); if let Some(adapter) = self.window.borrow().as_ref() { if page_flip_handler_registration_token.is_none() { page_flip_handler_registration_token = adapter.register_page_flip_handler(event_loop.handle())?; } adapter.render_if_needed(mouse_position_property.as_ref())?; }; let next_timeout = i_slint_core::platform::duration_until_next_timer_update(); event_loop .dispatch(next_timeout, &mut loop_data) .map_err(|e| format!("Error dispatch events: {e}"))?; } if let Some(token) = page_flip_handler_registration_token.take() { event_loop.handle().remove(token); } Ok(()) } fn set_event_loop_quit_on_last_window_closed(&self, quit_on_last_window_closed: bool) { QUIT_ON_LAST_WINDOW_CLOSED .store(quit_on_last_window_closed, std::sync::atomic::Ordering::Relaxed); } fn new_event_loop_proxy(&self) -> Option> { Some(Box::new(self.proxy.clone())) } fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option { match clipboard { i_slint_core::platform::Clipboard::DefaultClipboard => self.clipboard.borrow().clone(), i_slint_core::platform::Clipboard::SelectionClipboard => { self.sel_clipboard.borrow().clone() } _ => None, } } fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) { match clipboard { i_slint_core::platform::Clipboard::DefaultClipboard => { *self.clipboard.borrow_mut() = Some(text.into()) } i_slint_core::platform::Clipboard::SelectionClipboard => { *self.sel_clipboard.borrow_mut() = Some(text.into()) } _ => (), } } } #[derive(Default)] pub struct LoopData {} struct Device { // in the future, use this from libseat: device_id: i32, fd: RawFd, } impl AsFd for Device { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { unsafe { BorrowedFd::borrow_raw(self.fd) } } } pub(crate) static QUIT_ON_LAST_WINDOW_CLOSED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true); /// Calloop has sophisticated ways of associating event sources with callbacks that /// handle activity on the source, with life times, etc. For the page flip handling /// we really just want to watch for activity on a file descriptor and then invoke /// a callback to read from it, in the same thread, as-is. This helper provides /// that ... simplification. pub struct FileDescriptorActivityNotifier { fd: Rc, token: Option, callback: Box, interest: calloop::Interest, } impl FileDescriptorActivityNotifier { pub fn new( handle: &EventLoopHandle, interest: calloop::Interest, fd: Rc, callback: Box, ) -> Result { let notifier = Self { fd, token: None, interest, callback }; handle .insert_source(notifier, move |_, _, _| {}) .map_err(|e| format!("Error registering page flip handler: {e}").into()) } } impl calloop::EventSource for FileDescriptorActivityNotifier { type Event = (); type Metadata = (); type Ret = (); type Error = PlatformError; fn process_events( &mut self, _readiness: calloop::Readiness, token: calloop::Token, _callback: F, ) -> Result where F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, { if Some(token) == self.token { (self.callback)(); } Ok(calloop::PostAction::Continue) } fn register( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, ) -> calloop::Result<()> { self.token = Some(token_factory.token()); unsafe { poll.register(&self.fd, self.interest, calloop::Mode::Level, self.token.unwrap()) } } fn reregister( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, ) -> calloop::Result<()> { self.token = Some(token_factory.token()); poll.reregister(&self.fd, self.interest, calloop::Mode::Level, self.token.unwrap()) } fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { self.token = None; poll.unregister(&self.fd) } } pub type EventLoopHandle<'a> = calloop::LoopHandle<'a, LoopData>;