mirror of
https://github.com/slint-ui/slint.git
synced 2025-07-08 05:35:24 +00:00
439 lines
16 KiB
Rust
439 lines
16 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
#![no_main]
|
||
#![no_std]
|
||
#![cfg(target_os = "uefi")]
|
||
|
||
extern crate alloc;
|
||
|
||
use alloc::boxed::Box;
|
||
use alloc::format;
|
||
use alloc::rc::Rc;
|
||
use alloc::string::{String, ToString};
|
||
use alloc::vec;
|
||
use core::slice;
|
||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||
use core::time::Duration;
|
||
use log::info;
|
||
use slint::platform::{PointerEventButton, WindowEvent};
|
||
use slint::{platform::software_renderer, SharedString};
|
||
use uefi::boot::ScopedProtocol;
|
||
use uefi::prelude::*;
|
||
use uefi::proto::console::{gop::BltPixel, pointer::Pointer};
|
||
use uefi::Char16;
|
||
|
||
slint::include_modules!();
|
||
|
||
static MOUSE_POINTER: AtomicPtr<ScopedProtocol<Pointer>> = AtomicPtr::new(core::ptr::null_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();
|
||
uefi::boot::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 pointer_init() {
|
||
// mouse pointer
|
||
let handle = uefi::boot::get_handle_for_protocol::<Pointer>().expect("miss Pointer protocol");
|
||
let mut pointer = uefi::boot::open_protocol_exclusive::<Pointer>(handle)
|
||
.expect("can't open Pointer protocol.");
|
||
pointer.reset(false).expect("Failed to reset pointer device.");
|
||
info!("pointer inited, mode = {:?}.", pointer.mode());
|
||
let raw_ptr = Box::into_raw(Box::new(pointer));
|
||
MOUSE_POINTER.store(raw_ptr, Ordering::Relaxed);
|
||
}
|
||
|
||
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 uefi::system::with_stdin(|stdin| 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::boot::*;
|
||
|
||
let watchdog_timeout = Duration::from_secs(120);
|
||
let timeout = watchdog_timeout.min(max_timeout.unwrap_or(watchdog_timeout));
|
||
|
||
// SAFETY: The event is closed before returning from this function.
|
||
let timer = unsafe {
|
||
uefi::boot::create_event(EventType::TIMER, Tpl::APPLICATION, None, None).unwrap()
|
||
};
|
||
uefi::boot::set_timer(&timer, TimerTrigger::Periodic((timeout.as_nanos() / 100) as u64))
|
||
.unwrap();
|
||
|
||
uefi::boot::set_watchdog_timer(2 * watchdog_timeout.as_micros() as usize, 0x10000, None)
|
||
.unwrap();
|
||
|
||
uefi::system::with_stdin(|stdin| {
|
||
// SAFETY: The cloned handles are only used to wait for further input events and
|
||
// are then immediately dropped.
|
||
let ptr = MOUSE_POINTER.load(Ordering::Relaxed);
|
||
let pointer_ref = unsafe { &*ptr };
|
||
let mut events = unsafe {
|
||
[
|
||
stdin.wait_for_key_event().unwrap(),
|
||
pointer_ref.wait_for_input_event().unwrap(),
|
||
timer.unsafe_clone(),
|
||
]
|
||
};
|
||
uefi::boot::wait_for_event(&mut events).unwrap();
|
||
});
|
||
|
||
uefi::boot::set_watchdog_timer(2 * watchdog_timeout.as_micros() as usize, 0x10000, None)
|
||
.unwrap();
|
||
uefi::boot::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))
|
||
}
|
||
}
|
||
|
||
#[repr(transparent)]
|
||
#[derive(Clone, Copy)]
|
||
/// RGBA-8-8-8-8
|
||
struct PngRGBAPixel([u8; 4]);
|
||
|
||
impl PngRGBAPixel {
|
||
fn new() -> Self {
|
||
PngRGBAPixel([254, 254, 254, 0])
|
||
}
|
||
fn from_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) {
|
||
self.0 = [r, g, b, a];
|
||
}
|
||
|
||
fn blend_blt_pixel(&self, background: &mut BltPixel) {
|
||
// Alpha Blending
|
||
// Result = Foreground×α + Background×(1−α)
|
||
let alpha = self.0[3] as f32 / 255.0;
|
||
let r = self.0[0] as f32;
|
||
let g = self.0[1] as f32;
|
||
let b = self.0[2] as f32;
|
||
|
||
let blended_r = ((1.0 - alpha) * background.red as f32 + alpha * r) as u8;
|
||
let blended_g = ((1.0 - alpha) * background.green as f32 + alpha * g) as u8;
|
||
let blended_b = ((1.0 - alpha) * background.blue as f32 + alpha * b) as u8;
|
||
|
||
background.red = blended_r;
|
||
background.green = blended_g;
|
||
background.blue = blended_b;
|
||
}
|
||
}
|
||
|
||
struct Platform {
|
||
window: Rc<software_renderer::MinimalSoftwareWindow>,
|
||
timer_freq: f64,
|
||
timer_start: f64,
|
||
}
|
||
|
||
impl Default for Platform {
|
||
fn default() -> Self {
|
||
pointer_init();
|
||
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::{boot::*, proto::console::gop::*};
|
||
|
||
let gop_handle = uefi::boot::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 {
|
||
uefi::boot::open_protocol::<GraphicsOutput>(
|
||
OpenProtocolParams {
|
||
handle: gop_handle,
|
||
agent: uefi::boot::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];
|
||
|
||
//mouse pixel
|
||
let png: &[u8] = &include_bytes!("resource/cursor.png")[..];
|
||
let header = minipng::decode_png_header(png).expect("bad PNG");
|
||
let mut buffer = vec![0; header.required_bytes_rgba8bpc()];
|
||
let mut image = minipng::decode_png(png, &mut buffer).expect("bad PNG");
|
||
image.convert_to_rgba8bpc().expect("Failed to convert to RGBA8bit");
|
||
info!("pointer png image size: {}x{} ", image.width(), image.height());
|
||
let pointer_x = image.width() as usize;
|
||
let pointer_y = image.height() as usize;
|
||
let image_size: usize = (image.width() * image.height()) as usize;
|
||
let mut vec_png = alloc::vec![PngRGBAPixel::new(); image_size];
|
||
let mut mfb = alloc::vec![BltPixel::new(254, 254, 254); image_size];
|
||
for i in 0..image_size {
|
||
vec_png[i].from_rgba(
|
||
image.pixels()[4 * i + 0], //r
|
||
image.pixels()[4 * i + 1], //g
|
||
image.pixels()[4 * i + 2], //b
|
||
image.pixels()[4 * i + 3], //a
|
||
);
|
||
vec_png[i].blend_blt_pixel(&mut mfb[i]);
|
||
}
|
||
|
||
self.window.set_size(slint::PhysicalSize::new(
|
||
info.resolution().0.try_into().unwrap(),
|
||
info.resolution().1.try_into().unwrap(),
|
||
));
|
||
|
||
let mut position = slint::LogicalPosition::new(0.0, 0.0);
|
||
|
||
let ptr = MOUSE_POINTER.load(Ordering::Relaxed);
|
||
let mpointer = unsafe { &mut *ptr };
|
||
let conpointer = unsafe { &*ptr };
|
||
let mouse_mode = conpointer.mode();
|
||
let mut is_mouse_move = false;
|
||
|
||
loop {
|
||
slint::platform::update_timers_and_animations();
|
||
|
||
// key handle until no input
|
||
while let Some(key) = get_key_press() {
|
||
// EFI does not distinguish between pressed and released events.
|
||
let text = SharedString::from(key);
|
||
self.window.try_dispatch_event(WindowEvent::KeyPressed { text: text.clone() })?;
|
||
self.window.try_dispatch_event(WindowEvent::KeyReleased { text })?;
|
||
}
|
||
// mouse handle until no input
|
||
while let Some(mut mouse) =
|
||
mpointer.read_state().expect("Failed to read state from Pointer.")
|
||
{
|
||
position.x +=
|
||
(mouse.relative_movement[0] as f32) / (mouse_mode.resolution[0] as f32);
|
||
position.y +=
|
||
(mouse.relative_movement[1] as f32) / (mouse_mode.resolution[1] as f32);
|
||
|
||
let button: PointerEventButton = match mouse.button {
|
||
[true, true] => PointerEventButton::Left,
|
||
[true, false] => PointerEventButton::Left,
|
||
[false, true] => PointerEventButton::Right,
|
||
[false, false] => PointerEventButton::Other,
|
||
};
|
||
|
||
if position.x < 0.0 {
|
||
position.x = 0.0;
|
||
} else if position.x > (info.resolution().0 - pointer_x) as f32 {
|
||
position.x = (info.resolution().0 - pointer_x) as f32;
|
||
mouse.relative_movement[0] = (info.resolution().0) as i32;
|
||
}
|
||
|
||
if position.y < 0.0 {
|
||
position.y = 0.0;
|
||
} else if position.y > (info.resolution().1 - pointer_y) as f32 {
|
||
position.y = (info.resolution().1 - pointer_y) as f32;
|
||
mouse.relative_movement[1] = (info.resolution().1) as i32;
|
||
}
|
||
|
||
self.window.try_dispatch_event(WindowEvent::PointerMoved { position })?;
|
||
self.window.try_dispatch_event(WindowEvent::PointerExited {})?;
|
||
self.window.try_dispatch_event(WindowEvent::PointerPressed { position, button })?;
|
||
self.window
|
||
.try_dispatch_event(WindowEvent::PointerReleased { position, button })?;
|
||
is_mouse_move = true;
|
||
}
|
||
|
||
if is_mouse_move {
|
||
self.window.request_redraw();
|
||
is_mouse_move = false;
|
||
};
|
||
|
||
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()) };
|
||
let blt_mfb = unsafe {
|
||
slice::from_raw_parts_mut(mfb.as_mut_ptr() as *mut BltPixel, mfb.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();
|
||
|
||
// get framebuffer from UEFI.
|
||
gop.blt(BltOp::VideoToBltBuffer {
|
||
buffer: blt_mfb,
|
||
src: (position.x as usize, position.y as usize),
|
||
dest: BltRegion::Full,
|
||
dims: (pointer_x, pointer_y),
|
||
})
|
||
.unwrap();
|
||
|
||
// mouse cursor RGBA render to framebuffer.
|
||
for y in 0..pointer_y {
|
||
for x in 0..pointer_x {
|
||
vec_png[x + y * pointer_x].blend_blt_pixel(&mut mfb[x + y * pointer_x]);
|
||
}
|
||
}
|
||
|
||
// write framebuffer to UEFI.
|
||
gop.blt(BltOp::BufferToVideo {
|
||
buffer: blt_mfb,
|
||
src: BltRegion::Full,
|
||
dest: (position.x as usize, position.y as usize),
|
||
dims: (pointer_x, pointer_y),
|
||
})
|
||
.unwrap();
|
||
});
|
||
|
||
if !self.window.has_active_animations() {
|
||
wait_for_input(slint::platform::duration_until_next_timer_update());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[entry]
|
||
fn main() -> Status {
|
||
slint::platform::set_platform(Box::<Platform>::default()).unwrap();
|
||
|
||
let ui = Demo::new().unwrap();
|
||
|
||
ui.set_firmware_vendor(
|
||
String::from_utf16_lossy(uefi::system::firmware_vendor().to_u16_slice()).into(),
|
||
);
|
||
ui.set_firmware_version(
|
||
format!(
|
||
"{}.{:02}",
|
||
uefi::system::firmware_revision() >> 16,
|
||
uefi::system::firmware_revision() & 0xffff
|
||
)
|
||
.into(),
|
||
);
|
||
ui.set_uefi_version(uefi::system::uefi_revision().to_string().into());
|
||
|
||
let mut buf = [0u8; 1];
|
||
let guid = uefi::runtime::VariableVendor::GLOBAL_VARIABLE;
|
||
let sb = uefi::runtime::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
|
||
}
|