layer_shika_composition/
event_loop.rs

1use crate::{Error, Result};
2use layer_shika_adapters::errors::EventLoopError;
3use layer_shika_adapters::platform::calloop::{
4    EventSource, Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer,
5    channel,
6};
7use layer_shika_adapters::{AppState, WaylandSystemOps};
8use std::cell::RefCell;
9use std::os::unix::io::AsFd;
10use std::rc::{Rc, Weak};
11use std::time::{Duration, Instant};
12
13pub trait FromAppState<'a> {
14    fn from_app_state(app_state: &'a mut AppState) -> Self;
15}
16
17/// Main event loop for the shell runtime
18///
19/// Manages the Wayland event loop and custom event sources.
20/// Created internally by `Shell` and started via `Shell::run()`.
21pub struct ShellEventLoop {
22    inner: Rc<RefCell<dyn WaylandSystemOps>>,
23}
24
25impl ShellEventLoop {
26    pub fn new(inner: Rc<RefCell<dyn WaylandSystemOps>>) -> Self {
27        Self { inner }
28    }
29
30    pub fn run(&mut self) -> Result<()> {
31        self.inner.borrow_mut().run()?;
32        Ok(())
33    }
34
35    pub fn get_handle(&self) -> EventLoopHandle {
36        EventLoopHandle::new(Rc::downgrade(&self.inner))
37    }
38}
39
40/// Handle for registering custom event sources with the event loop
41///
42/// Supports timers, channels, file descriptors, and custom event sources.
43pub struct EventLoopHandle {
44    system: Weak<RefCell<dyn WaylandSystemOps>>,
45}
46
47impl EventLoopHandle {
48    pub fn new(system: Weak<RefCell<dyn WaylandSystemOps>>) -> Self {
49        Self { system }
50    }
51
52    /// Register a custom event source with the event loop
53    ///
54    /// Returns a registration token that can be used to remove the source later.
55    pub fn insert_source<S, F, R>(&self, source: S, callback: F) -> Result<RegistrationToken>
56    where
57        S: EventSource<Ret = R> + 'static,
58        F: FnMut(S::Event, &mut S::Metadata, &mut AppState) -> R + 'static,
59    {
60        let system = self.system.upgrade().ok_or(Error::SystemDropped)?;
61        let loop_handle = system.borrow().event_loop_handle();
62
63        loop_handle.insert_source(source, callback).map_err(|e| {
64            Error::Adapter(
65                EventLoopError::InsertSource {
66                    message: format!("{e:?}"),
67                }
68                .into(),
69            )
70        })
71    }
72
73    /// Add a timer that fires after the specified duration
74    ///
75    /// Return `TimeoutAction::Drop` for one-shot, `ToDuration(d)` to repeat, or `ToInstant(i)` for next deadline.
76    pub fn add_timer<F>(&self, duration: Duration, mut callback: F) -> Result<RegistrationToken>
77    where
78        F: FnMut(Instant, &mut AppState) -> TimeoutAction + 'static,
79    {
80        let timer = Timer::from_duration(duration);
81        self.insert_source(timer, move |deadline, (), app_state| {
82            callback(deadline, app_state)
83        })
84    }
85
86    /// Add a timer that fires at a specific instant
87    ///
88    /// Callback receives the deadline and can return `TimeoutAction::ToInstant` to reschedule.
89    pub fn add_timer_at<F>(&self, deadline: Instant, mut callback: F) -> Result<RegistrationToken>
90    where
91        F: FnMut(Instant, &mut AppState) -> TimeoutAction + 'static,
92    {
93        let timer = Timer::from_deadline(deadline);
94        self.insert_source(timer, move |deadline, (), app_state| {
95            callback(deadline, app_state)
96        })
97    }
98
99    /// Add a channel for sending messages to the event loop from any thread
100    ///
101    /// The sender can be cloned and sent across threads. Messages are queued and processed on the main thread.
102    pub fn add_channel<T, F>(
103        &self,
104        mut callback: F,
105    ) -> Result<(RegistrationToken, channel::Sender<T>)>
106    where
107        T: 'static,
108        F: FnMut(T, &mut AppState) + 'static,
109    {
110        let (sender, receiver) = channel::channel();
111        let token = self.insert_source(receiver, move |event, (), app_state| {
112            if let channel::Event::Msg(msg) = event {
113                callback(msg, app_state);
114            }
115        })?;
116        Ok((token, sender))
117    }
118
119    /// Add a file descriptor to be monitored for readability or writability
120    ///
121    /// Callback is invoked when the file descriptor becomes ready according to the interest.
122    pub fn add_fd<F, T>(
123        &self,
124        fd: T,
125        interest: Interest,
126        mode: Mode,
127        mut callback: F,
128    ) -> Result<RegistrationToken>
129    where
130        T: AsFd + 'static,
131        F: FnMut(&mut AppState) + 'static,
132    {
133        let generic = Generic::new(fd, interest, mode);
134        self.insert_source(generic, move |_readiness, _fd, app_state| {
135            callback(app_state);
136            Ok(PostAction::Continue)
137        })
138    }
139}