mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-11-03 21:24:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			282 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
 | 
						|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
 | 
						|
 | 
						|
#![no_main]
 | 
						|
#![no_std]
 | 
						|
 | 
						|
extern crate alloc;
 | 
						|
 | 
						|
use alloc::{
 | 
						|
    boxed::Box,
 | 
						|
    format,
 | 
						|
    rc::Rc,
 | 
						|
    string::{String, ToString},
 | 
						|
};
 | 
						|
use core::{slice, time::Duration};
 | 
						|
use slint::{platform::software_renderer, SharedString};
 | 
						|
use uefi::{prelude::*, proto::console::gop::BltPixel, Char16};
 | 
						|
use uefi_services::system_table;
 | 
						|
 | 
						|
slint::include_modules!();
 | 
						|
 | 
						|
fn st() -> &'static mut SystemTable<Boot> {
 | 
						|
    // SAFETY: uefi_services::init() is always called first in main()
 | 
						|
    // and we never operate outside boot services
 | 
						|
    unsafe { system_table().as_mut() }
 | 
						|
}
 | 
						|
 | 
						|
fn timer_tick() -> u64 {
 | 
						|
    #[cfg(target_arch = "x86")]
 | 
						|
    unsafe {
 | 
						|
        core::arch::x86::_rdtsc()
 | 
						|
    }
 | 
						|
 | 
						|
    #[cfg(target_arch = "x86_64")]
 | 
						|
    unsafe {
 | 
						|
        core::arch::x86_64::_rdtsc()
 | 
						|
    }
 | 
						|
 | 
						|
    #[cfg(target_arch = "aarch64")]
 | 
						|
    unsafe {
 | 
						|
        let mut ticks: u64;
 | 
						|
        core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks);
 | 
						|
        ticks
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn timer_freq() -> u64 {
 | 
						|
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
 | 
						|
    {
 | 
						|
        let start = timer_tick();
 | 
						|
        st().boot_services().stall(1000);
 | 
						|
        let end = timer_tick();
 | 
						|
        (end - start) * 1000
 | 
						|
    }
 | 
						|
 | 
						|
    #[cfg(target_arch = "aarch64")]
 | 
						|
    unsafe {
 | 
						|
        let mut freq: u64;
 | 
						|
        core::arch::asm!("mrs {}, cntfrq_el0", out(reg) freq);
 | 
						|
        freq
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn get_key_press() -> Option<char> {
 | 
						|
    use slint::platform::Key::*;
 | 
						|
    use uefi::proto::console::text::Key as UefiKey;
 | 
						|
    use uefi::proto::console::text::ScanCode as Scan;
 | 
						|
 | 
						|
    let nl = Char16::try_from('\r').unwrap();
 | 
						|
 | 
						|
    match st().stdin().read_key() {
 | 
						|
        Err(_) | Ok(None) => None,
 | 
						|
        Ok(Some(UefiKey::Printable(key))) if key == nl => Some('\n'),
 | 
						|
        Ok(Some(UefiKey::Printable(key))) => Some(char::from(key)),
 | 
						|
        Ok(Some(UefiKey::Special(key))) => Some(
 | 
						|
            match key {
 | 
						|
                Scan::UP => UpArrow,
 | 
						|
                Scan::DOWN => DownArrow,
 | 
						|
                Scan::RIGHT => RightArrow,
 | 
						|
                Scan::LEFT => LeftArrow,
 | 
						|
                Scan::HOME => Home,
 | 
						|
                Scan::END => End,
 | 
						|
                Scan::INSERT => Insert,
 | 
						|
                Scan::DELETE => Delete,
 | 
						|
                Scan::PAGE_UP => PageUp,
 | 
						|
                Scan::PAGE_DOWN => PageDown,
 | 
						|
                Scan::ESCAPE => Escape,
 | 
						|
                Scan::FUNCTION_1 => F1,
 | 
						|
                Scan::FUNCTION_2 => F2,
 | 
						|
                Scan::FUNCTION_3 => F3,
 | 
						|
                Scan::FUNCTION_4 => F4,
 | 
						|
                Scan::FUNCTION_5 => F5,
 | 
						|
                Scan::FUNCTION_6 => F6,
 | 
						|
                Scan::FUNCTION_7 => F7,
 | 
						|
                Scan::FUNCTION_8 => F8,
 | 
						|
                Scan::FUNCTION_9 => F9,
 | 
						|
                Scan::FUNCTION_10 => F10,
 | 
						|
                Scan::FUNCTION_11 => F11,
 | 
						|
                Scan::FUNCTION_12 => F12,
 | 
						|
                Scan::FUNCTION_13 => F13,
 | 
						|
                Scan::FUNCTION_14 => F14,
 | 
						|
                Scan::FUNCTION_15 => F15,
 | 
						|
                Scan::FUNCTION_16 => F16,
 | 
						|
                Scan::FUNCTION_17 => F17,
 | 
						|
                Scan::FUNCTION_18 => F18,
 | 
						|
                Scan::FUNCTION_19 => F19,
 | 
						|
                Scan::FUNCTION_20 => F20,
 | 
						|
                Scan::FUNCTION_21 => F21,
 | 
						|
                Scan::FUNCTION_22 => F22,
 | 
						|
                Scan::FUNCTION_23 => F23,
 | 
						|
                Scan::FUNCTION_24 => F24,
 | 
						|
                _ => return None,
 | 
						|
            }
 | 
						|
            .into(),
 | 
						|
        ),
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn wait_for_input(max_timeout: Option<Duration>) {
 | 
						|
    use uefi::table::boot::*;
 | 
						|
 | 
						|
    let watchdog_timeout = Duration::from_secs(120);
 | 
						|
    let timeout = watchdog_timeout.min(max_timeout.unwrap_or(watchdog_timeout));
 | 
						|
 | 
						|
    let bs = st().boot_services();
 | 
						|
 | 
						|
    // SAFETY: The event is closed before returning from this function.
 | 
						|
    let timer = unsafe { bs.create_event(EventType::TIMER, Tpl::APPLICATION, None, None).unwrap() };
 | 
						|
    bs.set_timer(&timer, TimerTrigger::Periodic((timeout.as_nanos() / 100) as u64)).unwrap();
 | 
						|
 | 
						|
    bs.set_watchdog_timer(2 * watchdog_timeout.as_micros() as usize, 0x10000, None).unwrap();
 | 
						|
 | 
						|
    {
 | 
						|
        // SAFETY: The cloned handles are only used to wait for further input events and
 | 
						|
        // are then immediately dropped.
 | 
						|
        let mut events =
 | 
						|
            unsafe { [st().stdin().wait_for_key_event().unsafe_clone(), timer.unsafe_clone()] };
 | 
						|
        bs.wait_for_event(&mut events).unwrap();
 | 
						|
    }
 | 
						|
 | 
						|
    bs.set_watchdog_timer(2 * watchdog_timeout.as_micros() as usize, 0x10000, None).unwrap();
 | 
						|
    bs.close_event(timer).unwrap();
 | 
						|
}
 | 
						|
 | 
						|
#[repr(transparent)]
 | 
						|
#[derive(Clone, Copy)]
 | 
						|
struct SlintBltPixel(BltPixel);
 | 
						|
 | 
						|
impl software_renderer::TargetPixel for SlintBltPixel {
 | 
						|
    fn blend(&mut self, color: software_renderer::PremultipliedRgbaColor) {
 | 
						|
        let a = (u8::MAX - color.alpha) as u16;
 | 
						|
        self.0.red = (self.0.red as u16 * a / 255) as u8 + color.red;
 | 
						|
        self.0.green = (self.0.green as u16 * a / 255) as u8 + color.green;
 | 
						|
        self.0.blue = (self.0.blue as u16 * a / 255) as u8 + color.blue;
 | 
						|
    }
 | 
						|
 | 
						|
    fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
 | 
						|
        SlintBltPixel(BltPixel::new(red, green, blue))
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
struct Platform {
 | 
						|
    window: Rc<software_renderer::MinimalSoftwareWindow>,
 | 
						|
    timer_freq: f64,
 | 
						|
    timer_start: f64,
 | 
						|
}
 | 
						|
 | 
						|
impl Default for Platform {
 | 
						|
    fn default() -> Self {
 | 
						|
        Self {
 | 
						|
            window: software_renderer::MinimalSoftwareWindow::new(
 | 
						|
                software_renderer::RepaintBufferType::ReusedBuffer,
 | 
						|
            ),
 | 
						|
            timer_freq: timer_freq() as f64,
 | 
						|
            timer_start: timer_tick() as f64,
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
impl slint::platform::Platform for Platform {
 | 
						|
    fn create_window_adapter(
 | 
						|
        &self,
 | 
						|
    ) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
 | 
						|
        Ok(self.window.clone())
 | 
						|
    }
 | 
						|
 | 
						|
    fn duration_since_start(&self) -> Duration {
 | 
						|
        Duration::from_secs_f64((timer_tick() as f64 - self.timer_start) / self.timer_freq)
 | 
						|
    }
 | 
						|
 | 
						|
    fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
 | 
						|
        use uefi::{proto::console::gop::*, table::boot::*};
 | 
						|
 | 
						|
        let bs = st().boot_services();
 | 
						|
 | 
						|
        let gop_handle = bs.get_handle_for_protocol::<GraphicsOutput>().unwrap();
 | 
						|
 | 
						|
        // SAFETY: uefi-rs wants us to use open_protocol_exclusive(), which will not work
 | 
						|
        // on real hardware. We can only hope that any other users of this
 | 
						|
        // handle/protocol behave and don't interfere with our uses of it.
 | 
						|
        let mut gop = unsafe {
 | 
						|
            bs.open_protocol::<GraphicsOutput>(
 | 
						|
                OpenProtocolParams {
 | 
						|
                    handle: gop_handle,
 | 
						|
                    agent: bs.image_handle(),
 | 
						|
                    controller: None,
 | 
						|
                },
 | 
						|
                OpenProtocolAttributes::GetProtocol,
 | 
						|
            )
 | 
						|
            .unwrap()
 | 
						|
        };
 | 
						|
 | 
						|
        let info = gop.current_mode_info();
 | 
						|
        let mut fb = alloc::vec![SlintBltPixel(BltPixel::new(0, 0, 0)); info.resolution().0 * info.resolution().1];
 | 
						|
 | 
						|
        self.window.set_size(slint::PhysicalSize::new(
 | 
						|
            info.resolution().0.try_into().unwrap(),
 | 
						|
            info.resolution().1.try_into().unwrap(),
 | 
						|
        ));
 | 
						|
 | 
						|
        loop {
 | 
						|
            slint::platform::update_timers_and_animations();
 | 
						|
 | 
						|
            while let Some(key) = get_key_press() {
 | 
						|
                // EFI does not distinguish between pressed and released events.
 | 
						|
                let text = SharedString::from(key);
 | 
						|
                self.window.dispatch_event(slint::platform::WindowEvent::KeyPressed {
 | 
						|
                    text: text.clone(),
 | 
						|
                });
 | 
						|
                self.window.dispatch_event(slint::platform::WindowEvent::KeyReleased { text });
 | 
						|
            }
 | 
						|
 | 
						|
            self.window.draw_if_needed(|renderer| {
 | 
						|
                renderer.render(&mut fb, info.resolution().0);
 | 
						|
 | 
						|
                // SAFETY: SlintBltPixel is a repr(transparent) BltPixel so it is safe to transform.
 | 
						|
                let blt_fb =
 | 
						|
                    unsafe { slice::from_raw_parts(fb.as_ptr() as *const BltPixel, fb.len()) };
 | 
						|
 | 
						|
                // We could let the software renderer draw to gop.frame_buffer() directly, but that
 | 
						|
                // requires dealing with different frame buffer formats. The blit buffer is easier to
 | 
						|
                // deal with and guaranteed to be available by the UEFI spec. This also reduces tearing
 | 
						|
                // by quite a bit.
 | 
						|
                gop.blt(BltOp::BufferToVideo {
 | 
						|
                    buffer: blt_fb,
 | 
						|
                    src: BltRegion::Full,
 | 
						|
                    dest: (0, 0),
 | 
						|
                    dims: info.resolution(),
 | 
						|
                })
 | 
						|
                .unwrap();
 | 
						|
            });
 | 
						|
 | 
						|
            if !self.window.has_active_animations() {
 | 
						|
                wait_for_input(slint::platform::duration_until_next_timer_update());
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[entry]
 | 
						|
fn main(_image_handle: Handle, mut st: SystemTable<Boot>) -> Status {
 | 
						|
    uefi_services::init(&mut st).unwrap();
 | 
						|
 | 
						|
    slint::platform::set_platform(Box::<Platform>::default()).unwrap();
 | 
						|
 | 
						|
    let ui = Demo::new().unwrap();
 | 
						|
 | 
						|
    ui.set_firmware_vendor(String::from_utf16_lossy(st.firmware_vendor().to_u16_slice()).into());
 | 
						|
    ui.set_firmware_version(
 | 
						|
        format!("{}.{:02}", st.firmware_revision() >> 16, st.firmware_revision() & 0xffff).into(),
 | 
						|
    );
 | 
						|
    ui.set_uefi_version(st.uefi_revision().to_string().into());
 | 
						|
 | 
						|
    let mut buf = [0u8; 1];
 | 
						|
    let guid = uefi::table::runtime::VariableVendor::GLOBAL_VARIABLE;
 | 
						|
    let sb = st.runtime_services().get_variable(cstr16!("SecureBoot"), &guid, &mut buf);
 | 
						|
    ui.set_secure_boot(if sb.is_ok() { buf[0] == 1 } else { false });
 | 
						|
 | 
						|
    ui.run().unwrap();
 | 
						|
 | 
						|
    Status::SUCCESS
 | 
						|
}
 |