mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 12:54:45 +00:00
examples: Add uefi-demo
This commit is contained in:
parent
6d0d6911f4
commit
decdf07be0
8 changed files with 474 additions and 3 deletions
2
.github/workflows/build_docs.yaml
vendored
2
.github/workflows/build_docs.yaml
vendored
|
@ -107,7 +107,7 @@ jobs:
|
|||
docs/tutorial/cpp/book/html
|
||||
docs/tutorial/node/book/html
|
||||
- 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
|
||||
run: |
|
||||
rm -rf target/doc target/cppdocs target/slintdocs api/node/docs docs/tutorial/rust/book docs/tutorial/cpp/book docs/tutorial/node/book
|
||||
|
|
15
.github/workflows/ci.yaml
vendored
15
.github/workflows/ci.yaml
vendored
|
@ -60,14 +60,14 @@ jobs:
|
|||
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.
|
||||
- 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
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
SLINT_CREATE_SCREENSHOTS: 1
|
||||
with:
|
||||
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
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
|
@ -233,6 +233,17 @@ jobs:
|
|||
- name: Check
|
||||
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:
|
||||
uses: ./.github/workflows/build_docs.yaml
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ members = [
|
|||
'examples/carousel/rust',
|
||||
'examples/energy-monitor',
|
||||
'examples/mcu-board-support',
|
||||
'examples/uefi-demo',
|
||||
'helper_crates/const-field-offset',
|
||||
'helper_crates/vtable',
|
||||
'helper_crates/vtable/macro',
|
||||
|
|
28
examples/uefi-demo/Cargo.toml
Normal file
28
examples/uefi-demo/Cargo.toml
Normal 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" }
|
23
examples/uefi-demo/README.md
Normal file
23
examples/uefi-demo/README.md
Normal 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
|
||||
```
|
12
examples/uefi-demo/build.rs
Normal file
12
examples/uefi-demo/build.rs
Normal 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();
|
||||
}
|
114
examples/uefi-demo/demo.slint
Normal file
114
examples/uefi-demo/demo.slint
Normal 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
282
examples/uefi-demo/main.rs
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue