examples: Add uefi-demo

This commit is contained in:
Jan Janssen 2023-04-08 15:34:34 +02:00 committed by Simon Hausmann
parent 6d0d6911f4
commit decdf07be0
8 changed files with 474 additions and 3 deletions

View file

@ -107,7 +107,7 @@ jobs:
docs/tutorial/cpp/book/html docs/tutorial/cpp/book/html
docs/tutorial/node/book/html docs/tutorial/node/book/html
- name: "Check for docs warnings in internal crates" - name: "Check for docs warnings in internal crates"
run: cargo +nightly doc --workspace --no-deps --all-features --exclude slint-node --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter run: cargo +nightly doc --workspace --no-deps --all-features --exclude slint-node --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter --exclude uefi-demo
- name: Clean cache # Don't cache docs to avoid them including removed classes being published - name: Clean cache # Don't cache docs to avoid them including removed classes being published
run: | run: |
rm -rf target/doc target/cppdocs target/slintdocs api/node/docs docs/tutorial/rust/book docs/tutorial/cpp/book docs/tutorial/node/book rm -rf target/doc target/cppdocs target/slintdocs api/node/docs docs/tutorial/rust/book docs/tutorial/cpp/book docs/tutorial/node/book

View file

@ -60,14 +60,14 @@ jobs:
toolchain: ${{ matrix.rust_version }} toolchain: ${{ matrix.rust_version }}
key: x-v2-${{ steps.node-install.outputs.node-version }} # the cache key consists of a manually bumpable version and the node version, as the cached rustc artifacts contain linking information where to find node.lib, which is in a versioned directory. key: x-v2-${{ steps.node-install.outputs.node-version }} # the cache key consists of a manually bumpable version and the node version, as the cached rustc artifacts contain linking information where to find node.lib, which is in a versioned directory.
- name: Build - name: Build
run: cargo build --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel # mcu backend requires nightly run: cargo build --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude uefi-demo # mcu backend requires nightly
- name: Run tests - name: Run tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
env: env:
SLINT_CREATE_SCREENSHOTS: 1 SLINT_CREATE_SCREENSHOTS: 1
with: with:
command: test command: test
args: --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel # mcu backend requires nightly args: --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude uefi-demo # mcu backend requires nightly
- name: Archive screenshots after failed tests - name: Archive screenshots after failed tests
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -233,6 +233,17 @@ jobs:
- name: Check - name: Check
run: cross check --target=armv7-unknown-linux-gnueabihf -p slint-cpp --no-default-features --features=testing,interpreter run: cross check --target=armv7-unknown-linux-gnueabihf -p slint-cpp --no-default-features --features=testing,interpreter
uefi-demo:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-rust
with:
toolchain: stable
target: x86_64-unknown-uefi
- name: Check
run: cargo check --target=x86_64-unknown-uefi -p uefi-demo
docs: docs:
uses: ./.github/workflows/build_docs.yaml uses: ./.github/workflows/build_docs.yaml

View file

@ -24,6 +24,7 @@ members = [
'examples/carousel/rust', 'examples/carousel/rust',
'examples/energy-monitor', 'examples/energy-monitor',
'examples/mcu-board-support', 'examples/mcu-board-support',
'examples/uefi-demo',
'helper_crates/const-field-offset', 'helper_crates/const-field-offset',
'helper_crates/vtable', 'helper_crates/vtable',
'helper_crates/vtable/macro', 'helper_crates/vtable/macro',

View file

@ -0,0 +1,28 @@
# Copyright © SixtyFPS GmbH <info@slint-ui.com>
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
[package]
name = "uefi-demo"
version = "1.0.0"
edition = "2021"
license = "GPL-3.0-only OR LicenseRef-Slint-commercial"
build = "build.rs"
publish = false
[[bin]]
path = "main.rs"
name = "uefi-demo"
[dependencies]
uefi = "0.20.0"
uefi-services = "0.17.0"
slint = { path = "../../api/rs/slint", default-features = false, features = [
"compat-1-0",
"libm",
"log",
"unsafe-single-threaded",
] }
[build-dependencies]
slint-build = { path = "../../api/rs/build" }

View file

@ -0,0 +1,23 @@
# Slint UEFI demo
This example demonstrates slint in a UEFI environment.
To build this example a suitable UEFI rust target must be installed first:
```
rustup target install x86_64-unknown-uefi
```
To build, simply pass the `--package` and `--target` arguments to cargo:
```
cargo build --package uefi-demo --target x86_64-unknown-uefi
```
The produced UEFI binary can then either be tested on real hardware by booting
it like any other bootloader or directly with QEMU (the firmware location
varies by distro):
```
qemu-system-x86_64 -bios /usr/share/edk2-ovmf/x64/OVMF.fd -kernel target/x86_64-unknown-uefi/debug/uefi-demo.efi
```

View file

@ -0,0 +1,12 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
fn main() {
slint_build::compile_with_config(
"demo.slint",
slint_build::CompilerConfiguration::new()
.with_style("fluent-dark".to_owned())
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
)
.unwrap();
}

View file

@ -0,0 +1,114 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import {
AboutSlint, Button, GridBox, HorizontalBox, LineEdit, Slider,
StandardButton, StandardListView, TabWidget, VerticalBox
} from "std-widgets.slint";
export component Demo inherits Window {
in property <string> firmware-vendor;
in property <string> firmware-version;
in property <string> uefi-version;
in property <bool> secure-boot;
default-font-size: 22px;
default-font-family: "Noto Sans";
TabWidget {
width: root.width;
height: root.height;
Tab {
title: "Info";
GridBox {
Row { Rectangle {} }
Row {
Text {
colspan: 2;
text: "slint UEFI Demo";
horizontal-alignment: center;
font-size: 44px;
font-weight: 600;
}
}
Row {
Text { text: "Firmware vendor:"; horizontal-alignment: right; }
Text { text: firmware-vendor; }
}
Row {
Text { text: "Firmware version:"; horizontal-alignment: right; }
Text { text: firmware-version; }
}
Row {
Text { text: "UEFI version:"; horizontal-alignment: right; }
Text { text: uefi-version; }
}
Row {
Text { text: "Secure boot:"; horizontal-alignment: right; }
Text { text: secure-boot ? "enabled" : "disabled"; }
}
Row {
Text { text: "Resolution:"; horizontal-alignment: right; }
Text { text: "\{floor(root.width / 1px)}x\{floor(root.height / 1px)}"; }
}
Row { Rectangle {} }
}
}
Tab {
title: "Widgets";
VerticalBox {
enabler := Button {
checked: true;
checkable: true;
text: "Widgets enabled";
}
LineEdit {
enabled: enabler.checked;
placeholder-text: "Edit Me!";
}
Slider {
enabled: enabler.checked;
}
StandardListView {
vertical-stretch: 1;
enabled: enabler.checked;
model: [
{ text: "Abydos"}, { text: "Asuras" }, { text: "Athos" },
{ text: "Celestis" }, { text: "Chulak"}, { text: "Dakara"},
{ text: "Earth" }, { text: "Langara" }, { text: "Tollana"},
];
}
HorizontalBox {
alignment: center;
StandardButton { enabled: enabler.checked; kind: ok; }
StandardButton { enabled: enabler.checked; kind: reset; }
StandardButton { enabled: enabler.checked; kind: abort; }
}
}
}
Tab {
title: "V-Sync";
Rectangle {
for color[index] in [
#fff, #f00, #0f0,
#00f, #0ff, #ff0,
#f0f]: Rectangle {
y: 0;
height: parent.height;
x: (parent.width - self.width) * 0.5 *
(1 + 1.1 * sin(animation-tick() * (index + 1) / 17ms * 1deg));
width: 25px + 100px * abs(sin(animation-tick() / 25ms * 1deg));
background: color;
}
}
}
Tab {
title: "About";
AboutSlint {}
}
}
}

282
examples/uefi-demo/main.rs Normal file
View file

@ -0,0 +1,282 @@
// 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
}