i_slint_backend_linuxkms/
calloop_backend.rs1use std::cell::RefCell;
5#[cfg(not(feature = "libseat"))]
6use std::fs::OpenOptions;
7use std::os::fd::OwnedFd;
8#[cfg(feature = "libseat")]
9use std::os::fd::{AsFd, AsRawFd, FromRawFd};
10#[cfg(not(feature = "libseat"))]
11use std::os::unix::fs::OpenOptionsExt;
12use std::rc::Rc;
13use std::sync::atomic::AtomicBool;
14use std::sync::{Arc, Mutex};
15
16use calloop::EventLoop;
17use i_slint_core::platform::PlatformError;
18
19use crate::fullscreenwindowadapter::FullscreenWindowAdapter;
20use crate::BackendBuilder;
21
22#[cfg(not(any(target_family = "windows", target_vendor = "apple", target_arch = "wasm32")))]
23mod input;
24
25#[derive(Clone)]
26struct Proxy {
27 loop_signal: Arc<Mutex<Option<calloop::LoopSignal>>>,
28 quit_loop: Arc<AtomicBool>,
29 user_event_channel: Arc<Mutex<calloop::channel::Sender<Box<dyn FnOnce() + Send>>>>,
30}
31
32impl Proxy {
33 fn new(event_channel: calloop::channel::Sender<Box<dyn FnOnce() + Send>>) -> Self {
34 Self {
35 loop_signal: Arc::new(Mutex::new(None)),
36 quit_loop: Arc::new(AtomicBool::new(false)),
37 user_event_channel: Arc::new(Mutex::new(event_channel)),
38 }
39 }
40}
41
42impl i_slint_core::platform::EventLoopProxy for Proxy {
43 fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> {
44 let signal = self.loop_signal.lock().unwrap();
45 signal.as_ref().map_or_else(
46 || Err(i_slint_core::api::EventLoopError::EventLoopTerminated),
47 |signal| {
48 self.quit_loop.store(true, std::sync::atomic::Ordering::Release);
49 signal.wakeup();
50 Ok(())
51 },
52 )
53 }
54
55 fn invoke_from_event_loop(
56 &self,
57 event: Box<dyn FnOnce() + Send>,
58 ) -> Result<(), i_slint_core::api::EventLoopError> {
59 let user_event_channel = self.user_event_channel.lock().unwrap();
60 user_event_channel
61 .send(event)
62 .map_err(|_| i_slint_core::api::EventLoopError::EventLoopTerminated)
63 }
64}
65
66pub struct Backend {
67 #[cfg(feature = "libseat")]
68 seat: Rc<RefCell<libseat::Seat>>,
69 window: RefCell<Option<Rc<FullscreenWindowAdapter>>>,
70 user_event_receiver: RefCell<Option<calloop::channel::Channel<Box<dyn FnOnce() + Send>>>>,
71 proxy: Proxy,
72 renderer_factory: for<'a> fn(
73 &'a crate::DeviceOpener,
74 ) -> Result<
75 Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>,
76 PlatformError,
77 >,
78 sel_clipboard: RefCell<Option<String>>,
79 clipboard: RefCell<Option<String>>,
80 libinput_event_hook: Option<Box<dyn Fn(&::input::Event) -> bool>>,
81}
82
83impl Backend {
84 pub fn build(builder: BackendBuilder) -> Result<Self, PlatformError> {
85 let (user_event_sender, user_event_receiver) = calloop::channel::channel();
86
87 let renderer_factory = match builder.renderer_name.as_deref() {
88 #[cfg(feature = "renderer-skia-vulkan")]
89 Some("skia-vulkan") => crate::renderer::skia::SkiaRendererAdapter::new_vulkan,
90 #[cfg(feature = "renderer-skia-opengl")]
91 Some("skia-opengl") => crate::renderer::skia::SkiaRendererAdapter::new_opengl,
92 #[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-skia-vulkan"))]
93 Some("skia-software") => crate::renderer::skia::SkiaRendererAdapter::new_software,
94 #[cfg(feature = "renderer-femtovg")]
95 Some("femtovg") => crate::renderer::femtovg::FemtoVGRendererAdapter::new,
96 #[cfg(feature = "renderer-software")]
97 Some("software") => crate::renderer::sw::SoftwareRendererAdapter::new,
98 None => crate::renderer::try_skia_then_femtovg_then_software,
99 Some(renderer_name) => {
100 eprintln!(
101 "slint linuxkms backend: unrecognized renderer {}, falling back default",
102 renderer_name
103 );
104 crate::renderer::try_skia_then_femtovg_then_software
105 }
106 };
107
108 #[cfg(feature = "libseat")]
109 let seat_active = Rc::new(RefCell::new(false));
110
111 #[cfg(feature = "libseat")]
114 let mut seat = {
115 let seat_active = seat_active.clone();
116 libseat::Seat::open(move |_seat, event| match event {
117 libseat::SeatEvent::Enable => {
118 *seat_active.borrow_mut() = true;
119 }
120 libseat::SeatEvent::Disable => {
121 unimplemented!("Seat deactivation is not implemented");
122 }
123 })
124 .map_err(|e| format!("Error opening session with libseat: {e}"))?
125 };
126
127 #[cfg(feature = "libseat")]
128 while !(*seat_active.borrow()) {
129 if seat.dispatch(5000).map_err(|e| format!("Error waiting for seat activation: {e}"))?
130 == 0
131 {
132 return Err(format!("Timeout while waiting to activate session").into());
133 }
134 }
135
136 Ok(Backend {
137 #[cfg(feature = "libseat")]
138 seat: Rc::new(RefCell::new(seat)),
139 window: Default::default(),
140 user_event_receiver: RefCell::new(Some(user_event_receiver)),
141 proxy: Proxy::new(user_event_sender),
142 renderer_factory,
143 sel_clipboard: Default::default(),
144 clipboard: Default::default(),
145 libinput_event_hook: builder.libinput_event_hook,
146 })
147 }
148}
149
150impl i_slint_core::platform::Platform for Backend {
151 fn create_window_adapter(
152 &self,
153 ) -> Result<std::rc::Rc<dyn i_slint_core::window::WindowAdapter>, PlatformError> {
154 #[cfg(feature = "libseat")]
155 let device_accessor = |device: &std::path::Path| -> Result<Rc<OwnedFd>, PlatformError> {
156 let device = self
157 .seat
158 .borrow_mut()
159 .open_device(&device)
160 .map_err(|e| format!("Error opening device {}: {e}", device.display()))?;
161
162 let fd = device.as_fd();
164 let flags = nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_GETFL)
165 .map_err(|e| format!("Error getting file descriptor flags: {e}"))?;
166 let mut flags = nix::fcntl::OFlag::from_bits_retain(flags);
167 flags.remove(nix::fcntl::OFlag::O_NONBLOCK);
168 nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_SETFL(flags))
169 .map_err(|e| format!("Error making device fd non-blocking: {e}"))?;
170
171 Ok(Rc::new(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd.as_raw_fd()) }))
173 };
174
175 #[cfg(not(feature = "libseat"))]
176 let device_accessor = |device: &std::path::Path| -> Result<Rc<OwnedFd>, PlatformError> {
177 let device = OpenOptions::new()
178 .custom_flags((nix::fcntl::OFlag::O_NOCTTY | nix::fcntl::OFlag::O_CLOEXEC).bits())
179 .read(true)
180 .write(true)
181 .open(device)
182 .map(|file| file.into())
183 .map_err(|e| format!("Error opening device {}: {e}", device.display()))?;
184
185 Ok(Rc::new(device))
186 };
187
188 let rotation =
190 std::env::var("SLINT_KMS_ROTATION").map_or(Ok(Default::default()), |rot_str| {
191 rot_str
192 .as_str()
193 .try_into()
194 .map_err(|e| format!("Failed to parse SLINT_KMS_ROTATION: {e}"))
195 })?;
196
197 let renderer = (self.renderer_factory)(&device_accessor)?;
198 let adapter = FullscreenWindowAdapter::new(renderer, rotation)?;
199
200 *self.window.borrow_mut() = Some(adapter.clone());
201
202 Ok(adapter)
203 }
204
205 fn run_event_loop(&self) -> Result<(), PlatformError> {
206 let mut event_loop: EventLoop<LoopData> =
207 EventLoop::try_new().map_err(|e| format!("Error creating event loop: {}", e))?;
208
209 let loop_signal = event_loop.get_signal();
210
211 *self.proxy.loop_signal.lock().unwrap() = Some(loop_signal.clone());
212 let quit_loop = self.proxy.quit_loop.clone();
213
214 let mouse_position_property = input::LibInputHandler::init(
215 &self.window,
216 &event_loop.handle(),
217 #[cfg(feature = "libseat")]
218 &self.seat,
219 &self.libinput_event_hook,
220 )?;
221
222 let Some(user_event_receiver) = self.user_event_receiver.borrow_mut().take() else {
223 return Err(
224 format!("Re-entering the linuxkms event loop is currently not supported").into()
225 );
226 };
227
228 let callbacks_to_invoke_per_iteration = Rc::new(RefCell::new(Vec::new()));
229
230 event_loop
231 .handle()
232 .insert_source(user_event_receiver, {
233 let callbacks_to_invoke_per_iteration = callbacks_to_invoke_per_iteration.clone();
234 move |event, _, _| {
235 let calloop::channel::Event::Msg(callback) = event else { return };
236 callbacks_to_invoke_per_iteration.borrow_mut().push(callback);
238 }
239 })
240 .map_err(
241 |e: calloop::InsertError<calloop::channel::Channel<Box<dyn FnOnce() + Send>>>| {
242 format!("Error registering user event channel source: {e}")
243 },
244 )?;
245
246 let mut loop_data = LoopData::default();
247
248 quit_loop.store(false, std::sync::atomic::Ordering::Release);
249
250 while !quit_loop.load(std::sync::atomic::Ordering::Acquire) {
251 i_slint_core::platform::update_timers_and_animations();
252
253 for callback in callbacks_to_invoke_per_iteration.take().into_iter() {
256 callback();
257 }
258
259 if let Some(adapter) = self.window.borrow().as_ref() {
260 adapter.clone().render_if_needed(mouse_position_property.as_ref())?;
261 };
262
263 let next_timeout = i_slint_core::platform::duration_until_next_timer_update();
264 event_loop
265 .dispatch(next_timeout, &mut loop_data)
266 .map_err(|e| format!("Error dispatch events: {e}"))?;
267 }
268
269 Ok(())
270 }
271
272 fn new_event_loop_proxy(&self) -> Option<Box<dyn i_slint_core::platform::EventLoopProxy>> {
273 Some(Box::new(self.proxy.clone()))
274 }
275
276 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
277 match clipboard {
278 i_slint_core::platform::Clipboard::DefaultClipboard => self.clipboard.borrow().clone(),
279 i_slint_core::platform::Clipboard::SelectionClipboard => {
280 self.sel_clipboard.borrow().clone()
281 }
282 _ => None,
283 }
284 }
285 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
286 match clipboard {
287 i_slint_core::platform::Clipboard::DefaultClipboard => {
288 *self.clipboard.borrow_mut() = Some(text.into())
289 }
290 i_slint_core::platform::Clipboard::SelectionClipboard => {
291 *self.sel_clipboard.borrow_mut() = Some(text.into())
292 }
293 _ => (),
294 }
295 }
296}
297
298#[derive(Default)]
299pub struct LoopData {}