1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT
3
4#![no_main]
5#![no_std]
6
7extern crate alloc;
8
9use alloc::{
10 boxed::Box,
11 format,
12 rc::Rc,
13 string::{String, ToString},
14};
15use core::{slice, time::Duration};
16use slint::{platform::software_renderer, SharedString};
17use uefi::{prelude::*, proto::console::gop::BltPixel, Char16};
18use uefi_services::system_table;
19
20slint::include_modules!();
21
22fn st() -> &'static mut SystemTable<Boot> {
23 // SAFETY: uefi_services::init() is always called first in main()
24 // and we never operate outside boot services
25 unsafe { system_table().as_mut() }
26}
27
28fn timer_tick() -> u64 {
29 #[cfg(target_arch = "x86")]
30 unsafe {
31 core::arch::x86::_rdtsc()
32 }
33
34 #[cfg(target_arch = "x86_64")]
35 unsafe {
36 core::arch::x86_64::_rdtsc()
37 }
38
39 #[cfg(target_arch = "aarch64")]
40 unsafe {
41 let mut ticks: u64;
42 core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks);
43 ticks
44 }
45}
46
47fn timer_freq() -> u64 {
48 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
49 {
50 let start: u64 = timer_tick();
51 st().boot_services().stall(time:1000);
52 let end: u64 = timer_tick();
53 (end - start) * 1000
54 }
55
56 #[cfg(target_arch = "aarch64")]
57 unsafe {
58 let mut freq: u64;
59 core::arch::asm!("mrs {}, cntfrq_el0", out(reg) freq);
60 freq
61 }
62}
63
64fn get_key_press() -> Option<char> {
65 use slint::platform::Key::*;
66 use uefi::proto::console::text::Key as UefiKey;
67 use uefi::proto::console::text::ScanCode as Scan;
68
69 let nl = Char16::try_from('\r').unwrap();
70
71 match st().stdin().read_key() {
72 Err(_) | Ok(None) => None,
73 Ok(Some(UefiKey::Printable(key))) if key == nl => Some('\n'),
74 Ok(Some(UefiKey::Printable(key))) => Some(char::from(key)),
75 Ok(Some(UefiKey::Special(key))) => Some(
76 match key {
77 Scan::UP => UpArrow,
78 Scan::DOWN => DownArrow,
79 Scan::RIGHT => RightArrow,
80 Scan::LEFT => LeftArrow,
81 Scan::HOME => Home,
82 Scan::END => End,
83 Scan::INSERT => Insert,
84 Scan::DELETE => Delete,
85 Scan::PAGE_UP => PageUp,
86 Scan::PAGE_DOWN => PageDown,
87 Scan::ESCAPE => Escape,
88 Scan::FUNCTION_1 => F1,
89 Scan::FUNCTION_2 => F2,
90 Scan::FUNCTION_3 => F3,
91 Scan::FUNCTION_4 => F4,
92 Scan::FUNCTION_5 => F5,
93 Scan::FUNCTION_6 => F6,
94 Scan::FUNCTION_7 => F7,
95 Scan::FUNCTION_8 => F8,
96 Scan::FUNCTION_9 => F9,
97 Scan::FUNCTION_10 => F10,
98 Scan::FUNCTION_11 => F11,
99 Scan::FUNCTION_12 => F12,
100 Scan::FUNCTION_13 => F13,
101 Scan::FUNCTION_14 => F14,
102 Scan::FUNCTION_15 => F15,
103 Scan::FUNCTION_16 => F16,
104 Scan::FUNCTION_17 => F17,
105 Scan::FUNCTION_18 => F18,
106 Scan::FUNCTION_19 => F19,
107 Scan::FUNCTION_20 => F20,
108 Scan::FUNCTION_21 => F21,
109 Scan::FUNCTION_22 => F22,
110 Scan::FUNCTION_23 => F23,
111 Scan::FUNCTION_24 => F24,
112 _ => return None,
113 }
114 .into(),
115 ),
116 }
117}
118
119fn wait_for_input(max_timeout: Option<Duration>) {
120 use uefi::table::boot::*;
121
122 let watchdog_timeout = Duration::from_secs(120);
123 let timeout = watchdog_timeout.min(max_timeout.unwrap_or(watchdog_timeout));
124
125 let bs = st().boot_services();
126
127 // SAFETY: The event is closed before returning from this function.
128 let timer = unsafe { bs.create_event(EventType::TIMER, Tpl::APPLICATION, None, None).unwrap() };
129 bs.set_timer(&timer, TimerTrigger::Periodic((timeout.as_nanos() / 100) as u64)).unwrap();
130
131 bs.set_watchdog_timer(2 * watchdog_timeout.as_micros() as usize, 0x10000, None).unwrap();
132
133 {
134 // SAFETY: The cloned handles are only used to wait for further input events and
135 // are then immediately dropped.
136 let mut events =
137 unsafe { [st().stdin().wait_for_key_event().unsafe_clone(), timer.unsafe_clone()] };
138 bs.wait_for_event(&mut events).unwrap();
139 }
140
141 bs.set_watchdog_timer(2 * watchdog_timeout.as_micros() as usize, 0x10000, None).unwrap();
142 bs.close_event(timer).unwrap();
143}
144
145#[repr(transparent)]
146#[derive(Clone, Copy)]
147struct SlintBltPixel(BltPixel);
148
149impl software_renderer::TargetPixel for SlintBltPixel {
150 fn blend(&mut self, color: software_renderer::PremultipliedRgbaColor) {
151 let a: u16 = (u8::MAX - color.alpha) as u16;
152 self.0.red = (self.0.red as u16 * a / 255) as u8 + color.red;
153 self.0.green = (self.0.green as u16 * a / 255) as u8 + color.green;
154 self.0.blue = (self.0.blue as u16 * a / 255) as u8 + color.blue;
155 }
156
157 fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
158 SlintBltPixel(BltPixel::new(red, green, blue))
159 }
160}
161
162struct Platform {
163 window: Rc<software_renderer::MinimalSoftwareWindow>,
164 timer_freq: f64,
165 timer_start: f64,
166}
167
168impl Default for Platform {
169 fn default() -> Self {
170 Self {
171 window: software_renderer::MinimalSoftwareWindow::new(
172 software_renderer::RepaintBufferType::ReusedBuffer,
173 ),
174 timer_freq: timer_freq() as f64,
175 timer_start: timer_tick() as f64,
176 }
177 }
178}
179
180impl slint::platform::Platform for Platform {
181 fn create_window_adapter(
182 &self,
183 ) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
184 Ok(self.window.clone())
185 }
186
187 fn duration_since_start(&self) -> Duration {
188 Duration::from_secs_f64((timer_tick() as f64 - self.timer_start) / self.timer_freq)
189 }
190
191 fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
192 use uefi::{proto::console::gop::*, table::boot::*};
193
194 let bs = st().boot_services();
195
196 let gop_handle = bs.get_handle_for_protocol::<GraphicsOutput>().unwrap();
197
198 // SAFETY: uefi-rs wants us to use open_protocol_exclusive(), which will not work
199 // on real hardware. We can only hope that any other users of this
200 // handle/protocol behave and don't interfere with our uses of it.
201 let mut gop = unsafe {
202 bs.open_protocol::<GraphicsOutput>(
203 OpenProtocolParams {
204 handle: gop_handle,
205 agent: bs.image_handle(),
206 controller: None,
207 },
208 OpenProtocolAttributes::GetProtocol,
209 )
210 .unwrap()
211 };
212
213 let info = gop.current_mode_info();
214 let mut fb = alloc::vec![SlintBltPixel(BltPixel::new(0, 0, 0)); info.resolution().0 * info.resolution().1];
215
216 self.window.set_size(slint::PhysicalSize::new(
217 info.resolution().0.try_into().unwrap(),
218 info.resolution().1.try_into().unwrap(),
219 ));
220
221 loop {
222 slint::platform::update_timers_and_animations();
223
224 while let Some(key) = get_key_press() {
225 // EFI does not distinguish between pressed and released events.
226 let text = SharedString::from(key);
227 self.window.dispatch_event(slint::platform::WindowEvent::KeyPressed {
228 text: text.clone(),
229 });
230 self.window.dispatch_event(slint::platform::WindowEvent::KeyReleased { text });
231 }
232
233 self.window.draw_if_needed(|renderer| {
234 renderer.render(&mut fb, info.resolution().0);
235
236 // SAFETY: SlintBltPixel is a repr(transparent) BltPixel so it is safe to transform.
237 let blt_fb =
238 unsafe { slice::from_raw_parts(fb.as_ptr() as *const BltPixel, fb.len()) };
239
240 // We could let the software renderer draw to gop.frame_buffer() directly, but that
241 // requires dealing with different frame buffer formats. The blit buffer is easier to
242 // deal with and guaranteed to be available by the UEFI spec. This also reduces tearing
243 // by quite a bit.
244 gop.blt(BltOp::BufferToVideo {
245 buffer: blt_fb,
246 src: BltRegion::Full,
247 dest: (0, 0),
248 dims: info.resolution(),
249 })
250 .unwrap();
251 });
252
253 if !self.window.has_active_animations() {
254 wait_for_input(slint::platform::duration_until_next_timer_update());
255 }
256 }
257 }
258}
259
260#[entry]
261fn main(_image_handle: Handle, mut st: SystemTable<Boot>) -> Status {
262 uefi_services::init(&mut st).unwrap();
263
264 slint::platform::set_platform(Box::<Platform>::default()).unwrap();
265
266 let ui: Demo = Demo::new().unwrap();
267
268 ui.set_firmware_vendor(String::from_utf16_lossy(st.firmware_vendor().to_u16_slice()).into());
269 ui.set_firmware_version(
270 format!("{}.{:02}", st.firmware_revision() >> 16, st.firmware_revision() & 0xffff).into(),
271 );
272 ui.set_uefi_version(st.uefi_revision().to_string().into());
273
274 let mut buf: [u8; 1] = [0u8; 1];
275 let guid: VariableVendor = uefi::table::runtime::VariableVendor::GLOBAL_VARIABLE;
276 let sb: Result<(&[u8], VariableAttributes), …> = st.runtime_services().get_variable(name:cstr16!("SecureBoot"), &guid, &mut buf);
277 ui.set_secure_boot(if sb.is_ok() { buf[0] == 1 } else { false });
278
279 ui.run().unwrap();
280
281 Status::SUCCESS
282}
283