1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: MIT |
3 | |
4 | #![no_main ] |
5 | #![no_std ] |
6 | |
7 | extern crate alloc; |
8 | |
9 | use alloc::{ |
10 | boxed::Box, |
11 | format, |
12 | rc::Rc, |
13 | string::{String, ToString}, |
14 | }; |
15 | use core::{slice, time::Duration}; |
16 | use slint::{platform::software_renderer, SharedString}; |
17 | use uefi::{prelude::*, proto::console::gop::BltPixel, Char16}; |
18 | use uefi_services::system_table; |
19 | |
20 | slint::include_modules!(); |
21 | |
22 | fn 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 | |
28 | fn 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 | |
47 | fn 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 | |
64 | fn 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 | |
119 | fn 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)] |
147 | struct SlintBltPixel(BltPixel); |
148 | |
149 | impl 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 | |
162 | struct Platform { |
163 | window: Rc<software_renderer::MinimalSoftwareWindow>, |
164 | timer_freq: f64, |
165 | timer_start: f64, |
166 | } |
167 | |
168 | impl 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 | |
180 | impl 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 ] |
261 | fn 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 | |