Skip to main content

servoshell/desktop/
event_loop.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! An event loop implementation that works in headless mode.
6
7use std::sync::{Arc, Condvar, Mutex};
8use std::time;
9
10use gilrs::Event;
11use log::warn;
12use servo::{EventLoopWaker, GamepadIndex};
13use winit::event_loop::{EventLoop, EventLoop as WinitEventLoop, EventLoopProxy};
14
15use super::app::App;
16
17#[derive(Debug)]
18pub enum AppEvent {
19    /// Another process or thread has kicked the OS event loop with EventLoopWaker.
20    Waker,
21    Accessibility(egui_winit::accesskit_winit::Event),
22    Gamepad(Event, String, GamepadIndex),
23}
24
25impl From<egui_winit::accesskit_winit::Event> for AppEvent {
26    fn from(event: egui_winit::accesskit_winit::Event) -> AppEvent {
27        AppEvent::Accessibility(event)
28    }
29}
30
31/// A headed or headless event loop. Headless event loops are necessary for environments without a
32/// display server. Ideally, we could use the headed winit event loop in both modes, but on Linux,
33/// the event loop requires a display server, which prevents running servoshell in a console.
34#[allow(clippy::large_enum_variant)]
35pub(crate) enum ServoShellEventLoop {
36    /// A real Winit windowing event loop.
37    Winit(EventLoop<AppEvent>),
38    /// A fake event loop which contains a signalling flag used to ensure
39    /// that pending events get processed in a timely fashion, and a condition
40    /// variable to allow waiting on that flag changing state.
41    Headless(Arc<HeadlessEventLoop>),
42}
43
44impl ServoShellEventLoop {
45    pub(crate) fn headless() -> ServoShellEventLoop {
46        ServoShellEventLoop::Headless(Default::default())
47    }
48
49    pub(crate) fn headed() -> ServoShellEventLoop {
50        ServoShellEventLoop::Winit(
51            WinitEventLoop::with_user_event()
52                .build()
53                .expect("Could not start winit event loop"),
54        )
55    }
56}
57
58impl ServoShellEventLoop {
59    pub(crate) fn event_loop_proxy(&self) -> Option<EventLoopProxy<AppEvent>> {
60        match self {
61            ServoShellEventLoop::Winit(event_loop) => Some(event_loop.create_proxy()),
62            ServoShellEventLoop::Headless(..) => None,
63        }
64    }
65
66    pub fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> {
67        match self {
68            ServoShellEventLoop::Winit(event_loop) => {
69                Box::new(HeadedEventLoopWaker::new(event_loop))
70            },
71            ServoShellEventLoop::Headless(data) => Box::new(HeadlessEventLoopWaker(data.clone())),
72        }
73    }
74
75    pub fn run_app(self, app: &mut App) {
76        match self {
77            ServoShellEventLoop::Winit(event_loop) => {
78                event_loop
79                    .run_app(app)
80                    .expect("Failed while running events loop");
81            },
82            ServoShellEventLoop::Headless(event_loop) => event_loop.run_app(app),
83        }
84    }
85}
86
87#[derive(Clone)]
88struct HeadedEventLoopWaker {
89    proxy: Arc<Mutex<EventLoopProxy<AppEvent>>>,
90}
91
92impl HeadedEventLoopWaker {
93    fn new(event_loop: &EventLoop<AppEvent>) -> HeadedEventLoopWaker {
94        let proxy = Arc::new(Mutex::new(event_loop.create_proxy()));
95        HeadedEventLoopWaker { proxy }
96    }
97}
98
99impl EventLoopWaker for HeadedEventLoopWaker {
100    fn wake(&self) {
101        // Kick the OS event loop awake.
102        if let Err(err) = self.proxy.lock().unwrap().send_event(AppEvent::Waker) {
103            warn!("Failed to wake up event loop ({}).", err);
104        }
105    }
106
107    fn clone_box(&self) -> Box<dyn EventLoopWaker> {
108        Box::new(self.clone())
109    }
110}
111
112/// The [`HeadlessEventLoop`] is used when running in headless mode. The event
113/// loop just loops over a condvar to simulate a real windowing system event loop.
114#[derive(Default)]
115pub(crate) struct HeadlessEventLoop {
116    guard: Arc<Mutex<bool>>,
117    condvar: Condvar,
118}
119
120impl HeadlessEventLoop {
121    fn run_app(&self, app: &mut App) {
122        app.init(None);
123
124        loop {
125            self.sleep();
126            if !app.pump_servo_event_loop(None) {
127                break;
128            }
129            *self.guard.lock().unwrap() = false;
130        }
131    }
132
133    fn sleep(&self) {
134        // To avoid sleeping when we should be processing events, do two things:
135        // * before sleeping, check whether our signalling flag has been set
136        // * wait on a condition variable with a maximum timeout, to allow
137        //   being woken up by any signals that occur while sleeping.
138        let guard = self.guard.lock().unwrap();
139        if *guard {
140            return;
141        }
142        let _ = self
143            .condvar
144            .wait_timeout(guard, time::Duration::from_millis(5))
145            .unwrap();
146    }
147}
148
149#[derive(Clone)]
150struct HeadlessEventLoopWaker(Arc<HeadlessEventLoop>);
151
152impl EventLoopWaker for HeadlessEventLoopWaker {
153    fn wake(&self) {
154        // Set the signalling flag and notify the condition variable.
155        // This ensures that any sleep operation is interrupted,
156        // and any non-sleeping operation will have a change to check
157        // the flag before going to sleep.
158        let mut flag = self.0.guard.lock().unwrap();
159        *flag = true;
160        self.0.condvar.notify_all();
161    }
162
163    fn clone_box(&self) -> Box<dyn EventLoopWaker> {
164        Box::new(self.clone())
165    }
166}