Change memory regions to prevent crashes; Improve GUI

This commit is contained in:
Jakub Hlusička 2026-01-26 19:21:49 +01:00
parent b33f4852b2
commit 9c2a614aff
15 changed files with 225 additions and 63 deletions

View file

@ -95,6 +95,14 @@ mod console;
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();
// Memory allocation regions.
// These can be debugged using `xtensa-esp32s3-elf-size -A <path-to-binary>`.
/// Total heap size
const HEAP_SIZE: usize = 128 * 1024;
/// Size of the app core's stack
const STACK_SIZE_CORE_APP: usize = 64 * 1024;
// const FRAME_DURATION_MIN: Duration = Duration::from_millis(40); // 25 FPS
const FRAME_DURATION_MIN: Duration = Duration::from_millis(100); // 10 FPS
@ -149,12 +157,10 @@ async fn main(_spawner: Spawner) {
let alt_uart_rx_task = async {};
// Use the internal DRAM as the heap.
// TODO: Can we combine the regular link section with dram2?
// esp_alloc::heap_allocator!(size: 80 * 1024);
// esp_alloc::heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 72 * 1024);
//esp_alloc::heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 64 * 1024);
esp_alloc::heap_allocator!(#[ram(reclaimed)] size: 64 * 1024);
esp_alloc::heap_allocator!(size: 32 * 1024);
// Memory reclaimed from the esp-idf bootloader.
const HEAP_SIZE_RECLAIMED: usize = 64 * 1024;
esp_alloc::heap_allocator!(#[ram(reclaimed)] size: HEAP_SIZE_RECLAIMED);
esp_alloc::heap_allocator!(size: HEAP_SIZE - HEAP_SIZE_RECLAIMED);
info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats());
// Initialize the PSRAM allocator.
@ -235,15 +241,15 @@ async fn main(_spawner: Spawner) {
// Initialize USB
#[cfg(not(feature = "no-usb"))]
let usb_driver = {
use core::ptr::addr_of_mut;
use esp_hal::otg_fs::Usb;
use esp_hal::otg_fs::asynch::{Config, Driver};
static mut EP_MEMORY: [u8; 1024] = [0; 1024];
static EP_MEMORY: StaticCell<[u8; 1024]> = StaticCell::new();
let ep_memory = EP_MEMORY.init_with(|| [0_u8; _]);
let usb = Usb::new(peripherals.USB0, peripherals.GPIO20, peripherals.GPIO19);
// Create the driver, from the HAL.
let config = Config::default();
let driver = Driver::new(usb, unsafe { &mut *addr_of_mut!(EP_MEMORY) }, config);
let driver = Driver::new(usb, ep_memory, config);
info!("USB driver for RMK built!");
@ -381,7 +387,7 @@ async fn main(_spawner: Spawner) {
let window_size = [framebuffer.height, framebuffer.width];
let framebuffer_ptr = FramebufferPtr(framebuffer.as_target_pixels() as _);
static SECOND_CORE_STACK: StaticCell<Stack<{ 16 * 1024 }>> = StaticCell::new();
static SECOND_CORE_STACK: StaticCell<Stack<STACK_SIZE_CORE_APP>> = StaticCell::new();
let second_core_stack = SECOND_CORE_STACK.init(Stack::new());
esp_rtos::start_second_core(
peripherals.CPU_CTRL,

View file

@ -9,6 +9,7 @@ use alloc::{
borrow::Cow,
boxed::Box,
ffi::CString,
rc::Rc,
string::{String, ToString},
vec,
vec::Vec,
@ -25,7 +26,7 @@ use itertools::Itertools;
use log::{info, warn};
use rmk::futures::TryFutureExt;
use serde::{Deserialize, Serialize};
use slint::SharedString;
use slint::{ModelRc, SharedString, StandardListViewItem, VecModel};
use spectre_api_sys::{
SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreResultType, SpectreUserKey,
};
@ -243,7 +244,25 @@ pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: Partition
let main = AppWindow::new().unwrap();
main.on_accepted(|string| {
main.on_login_pw_accepted({
let main = main.clone_strong();
move |username, password| {
info!("username = {username:?}, password = {password:?}");
main.set_app_state(AppState::UserSites);
}
});
// let sites = Rc::new(VecModel::default());
// sites.push("First".into());
// sites.push("Second".into());
// main.set_sites(ModelRc::new(ModelRc::new(sites.clone()).map(
// |mut site: StandardListViewItem| {
// site.text += "10";
// site
// },
// )));
main.on_site_pw_accepted(|string| {
warn!("Accepted: {string}");
let Ok(c_string) = CString::new(&*string) else {
warn!("String cannot be converted to a C string: {string:?}");

View file

@ -0,0 +1,3 @@
export global Style {
in property <length> spacing: 8px;
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 365 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-key"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>

After

Width:  |  Height:  |  Size: 352 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-in"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 368 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 367 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sliders"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>

After

Width:  |  Height:  |  Size: 611 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash-2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 448 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>

After

Width:  |  Height:  |  Size: 400 B

View file

@ -0,0 +1,38 @@
import { ComboBox, LineEdit } from "std-widgets.slint";
import { Style } from "globals.slint";
import { IconButton } from "widgets/icon-button.slint";
export component LoginView inherits VerticalLayout {
padding: Style.spacing;
spacing: Style.spacing;
callback pw_accepted(string, string);
Rectangle { }
HorizontalLayout {
spacing: Style.spacing;
IconButton {
icon: @image-url("images/users.svg");
}
combo_box_username := ComboBox {
model: ["first", "second"];
}
line_edit_user_pw := LineEdit {
input-type: InputType.password;
placeholder-text: "Password";
accepted(text) => {
root.pw_accepted(combo_box_username.current-value, text);
}
}
IconButton {
icon: @image-url("images/log-in.svg");
clicked => {
root.pw_accepted(combo_box_username.current-value, line_edit_user_pw.text);
}
}
}
Rectangle { }
}

View file

@ -8,27 +8,46 @@ import {
Button,
} from "std-widgets.slint";
*/
import { Button, VerticalBox, LineEdit, GridBox } from "std-widgets.slint";
import {
Button,
VerticalBox,
LineEdit,
GridBox,
StandardListView,
ListView,
ComboBox,
} from "std-widgets.slint";
// See https://github.com/slint-ui/slint/issues/4956 for issues with fonts.
import "../fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf";
import { UserSitesView } from "user-sites-view.slint";
import { Style } from "globals.slint";
import { UsersView } from "users-view.slint";
import { LoginView } from "login-view.slint";
/*
TODO: A bigger stack for the 2nd core might be needed to prevent crashing.
export enum AppState {
PasswordForm,
login,
user-sites,
users,
}
*/
export component AppWindow inherits Window {
in property <string> dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°";
in property <string> dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°´ˇ¨";
default-font-family: "IBM Plex Mono";
default-font-size: 16pt;
height: 368px;
width: 960px;
in-out property <int> counter: 42;
callback request-increase-value();
callback accepted(string);
in property <AppState> app-state: AppState.login;
// Login View
callback login_pw_accepted <=> login_view.pw_accepted;
// Sites View
in property <[StandardListViewItem]> sites <=> user_sites_view.model;
callback site_pw_edited <=> user_sites_view.pw_edited;
callback site_pw_accepted <=> user_sites_view.pw_accepted;
// Sites View
in property <[StandardListViewItem]> usernames <=> users_view.model;
callback user_username_edited <=> users_view.pw_edited;
callback user_username_accepted <=> users_view.pw_accepted;
VerticalBox {
width: 960px;
height: 368px;
@ -36,53 +55,21 @@ export component AppWindow inherits Window {
padding-top: 120px;
padding-bottom: 8px;
Rectangle {
height: 240px;
background: #00141d;
// For debugging bounds.
// background: #2c82ff;
// border-color: #ffcf00;
// border-width: 1px;
GridBox {
VerticalBox {
Text {
text: "Counter: \{root.counter}";
}
login_view := LoginView {
visible: app-state == AppState.login;
}
Button {
text: "Increase value";
clicked => {
root.counter += 1;
root.request-increase-value();
}
}
user_sites_view := UserSitesView {
visible: app-state == AppState.user-sites;
}
LineEdit {
input-type: InputType.text;
text: "LineEdit";
accepted(text) => {
root.accepted(text);
}
/*changed text => {
root.changed(self.text);
}*/
}
}
/*
TabWidget {
Tab {
title: "first";
Button {
text: "First";
}
}
Tab {
title: "second";
Button {
text: "Second";
}
}
}
*/
users_view := UsersView {
visible: app-state == AppState.users;
}
}
}

View file

@ -0,0 +1,53 @@
import { LineEdit, StandardListView, Button } from "std-widgets.slint";
import { Style } from "globals.slint";
import { IconButton } from "widgets/icon-button.slint";
export component UserSitesView inherits HorizontalLayout {
padding: Style.spacing;
spacing: Style.spacing;
in property <[StandardListViewItem]> model <=> list_view_sites.model;
in-out property <int> current-item <=> list_view_sites.current-item;
callback pw_edited <=> line_edit_site_pw.edited;
callback pw_accepted <=> line_edit_site_pw.accepted;
VerticalLayout {
spacing: Style.spacing;
Text {
text: "Send password for:";
}
line_edit_site_pw := LineEdit {
input-type: InputType.text;
placeholder-text: "example.org";
}
list_view_sites := StandardListView {
model: [
{ text: "Test" },
{ text: "Test" },
];
}
}
VerticalLayout {
spacing: Style.spacing;
// IconButton {
// icon: @image-url("images/log-out.svg");
// }
IconButton {
icon: @image-url("images/sliders.svg");
}
IconButton {
icon: @image-url("images/help-circle.svg");
}
IconButton {
icon: @image-url("images/key.svg");
}
IconButton {
icon: @image-url("images/trash-2.svg");
}
}
}

View file

@ -0,0 +1,41 @@
import { LineEdit, StandardListView, Button } from "std-widgets.slint";
import { Style } from "globals.slint";
import { IconButton } from "widgets/icon-button.slint";
export component UsersView inherits HorizontalLayout {
padding: Style.spacing;
spacing: Style.spacing;
in property <[StandardListViewItem]> model <=> list_view_sites.model;
in-out property <int> current-item <=> list_view_sites.current-item;
callback pw_edited <=> line_edit_site_pw.edited;
callback pw_accepted <=> line_edit_site_pw.accepted;
VerticalLayout {
spacing: Style.spacing;
Text {
text: "Username:";
}
line_edit_site_pw := LineEdit {
input-type: InputType.text;
placeholder-text: "Full Name";
}
list_view_sites := StandardListView {
model: [
{ text: "Test" },
{ text: "Test" },
];
}
}
VerticalLayout {
spacing: Style.spacing;
IconButton {
icon: @image-url("images/key.svg");
}
IconButton {
icon: @image-url("images/trash-2.svg");
}
}
}

View file

@ -0,0 +1,8 @@
import { Button } from "std-widgets.slint";
export component IconButton inherits Button {
colorize-icon: true;
icon-size: 24px;
width: 48px;
height: 48px;
}