mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-28 06:14:10 +00:00
394 lines
14 KiB
Rust
394 lines
14 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// 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<Mutex<Option<calloop::LoopSignal>>>,
|
|
quit_loop: Arc<AtomicBool>,
|
|
user_event_channel: Arc<Mutex<calloop::channel::Sender<Box<dyn FnOnce() + Send>>>>,
|
|
}
|
|
|
|
impl Proxy {
|
|
fn new(event_channel: calloop::channel::Sender<Box<dyn FnOnce() + Send>>) -> 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<dyn FnOnce() + Send>,
|
|
) -> 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<RefCell<libseat::Seat>>,
|
|
window: RefCell<Option<Rc<FullscreenWindowAdapter>>>,
|
|
user_event_receiver: RefCell<Option<calloop::channel::Channel<Box<dyn FnOnce() + Send>>>>,
|
|
proxy: Proxy,
|
|
renderer_factory: for<'a> fn(
|
|
&'a crate::DeviceOpener,
|
|
) -> Result<
|
|
Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>,
|
|
PlatformError,
|
|
>,
|
|
sel_clipboard: RefCell<Option<String>>,
|
|
clipboard: RefCell<Option<String>>,
|
|
}
|
|
|
|
impl Backend {
|
|
pub fn new() -> Result<Self, PlatformError> {
|
|
Self::new_with_renderer_by_name(None)
|
|
}
|
|
pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
|
|
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<std::rc::Rc<dyn i_slint_core::window::WindowAdapter>, PlatformError> {
|
|
#[cfg(feature = "libseat")]
|
|
let device_accessor = |device: &std::path::Path| -> Result<Rc<OwnedFd>, 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<Rc<OwnedFd>, 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<LoopData> =
|
|
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<calloop::channel::Channel<Box<dyn FnOnce() + Send>>>| {
|
|
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<Box<dyn i_slint_core::platform::EventLoopProxy>> {
|
|
Some(Box::new(self.proxy.clone()))
|
|
}
|
|
|
|
fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
|
|
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<OwnedFd>,
|
|
token: Option<calloop::Token>,
|
|
callback: Box<dyn FnMut()>,
|
|
interest: calloop::Interest,
|
|
}
|
|
|
|
impl FileDescriptorActivityNotifier {
|
|
pub fn new(
|
|
handle: &EventLoopHandle,
|
|
interest: calloop::Interest,
|
|
fd: Rc<OwnedFd>,
|
|
callback: Box<dyn FnMut()>,
|
|
) -> Result<RegistrationToken, PlatformError> {
|
|
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<F>(
|
|
&mut self,
|
|
_readiness: calloop::Readiness,
|
|
token: calloop::Token,
|
|
_callback: F,
|
|
) -> Result<calloop::PostAction, Self::Error>
|
|
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>;
|