mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2025-12-27 07:45:28 +00:00
WIP gallery app
This commit is contained in:
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -1149,6 +1149,15 @@ dependencies = [
|
|||||||
"pin-utils",
|
"pin-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gallery"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"abi",
|
||||||
|
"embedded-graphics",
|
||||||
|
"tinybmp",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@@ -2282,6 +2291,15 @@ dependencies = [
|
|||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinybmp"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df43af2cb7b369009aa14144959bb4f2720ab62034c9073242f2d3a186c2edb6"
|
||||||
|
dependencies = [
|
||||||
|
"embedded-graphics",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "trouble-host"
|
name = "trouble-host"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["kernel", "abi", "shared", "user-apps/calculator", "user-apps/snake"]
|
members = [
|
||||||
|
"kernel",
|
||||||
|
"abi",
|
||||||
|
"shared",
|
||||||
|
"user-apps/calculator",
|
||||||
|
"user-apps/snake",
|
||||||
|
"user-apps/gallery",
|
||||||
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use abi_sys::{RngRequest, draw_iter, gen_rand};
|
use abi_sys::{RngRequest, draw_iter, gen_rand};
|
||||||
pub use abi_sys::{get_key, lock_display, print, sleep};
|
pub use abi_sys::{file_len, get_key, list_dir, lock_display, print, read_file, sleep};
|
||||||
use rand_core::RngCore;
|
use rand_core::RngCore;
|
||||||
pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
|
pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
|
||||||
use talc::*;
|
use talc::*;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub enum CallAbiTable {
|
|||||||
GenRand = 5,
|
GenRand = 5,
|
||||||
ListDir = 6,
|
ListDir = 6,
|
||||||
ReadFile = 7,
|
ReadFile = 7,
|
||||||
|
FileLen = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize);
|
pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize);
|
||||||
@@ -95,11 +96,15 @@ pub fn gen_rand(req: &mut RngRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ListDir =
|
pub type ListDir = extern "C" fn(
|
||||||
extern "C" fn(str: *const u8, len: usize, files: *mut Option<DirEntry>, file_len: usize);
|
str: *const u8,
|
||||||
|
len: usize,
|
||||||
|
files: *mut Option<DirEntry>,
|
||||||
|
file_len: usize,
|
||||||
|
) -> usize;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn list_dir(path: &str, files: &mut [Option<DirEntry>]) {
|
pub fn list_dir(path: &str, files: &mut [Option<DirEntry>]) -> usize {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize];
|
let ptr = CALL_ABI_TABLE[CallAbiTable::ListDir as usize];
|
||||||
let f: ListDir = core::mem::transmute(ptr);
|
let f: ListDir = core::mem::transmute(ptr);
|
||||||
@@ -129,3 +134,14 @@ pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn file_len(file: &str) -> usize {
|
||||||
|
unsafe {
|
||||||
|
let ptr = CALL_ABI_TABLE[CallAbiTable::FileLen as usize];
|
||||||
|
let f: FileLen = core::mem::transmute(ptr);
|
||||||
|
f(file.as_ptr(), file.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
16
justfile
16
justfile
@@ -1,8 +1,12 @@
|
|||||||
binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\""
|
|
||||||
|
|
||||||
kernel:
|
kernel:
|
||||||
cargo run --bin kernel
|
cargo run --bin kernel
|
||||||
calculator:
|
|
||||||
{{binary-args}} cargo build --bin calculator --profile release-binary
|
binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\""
|
||||||
snake:
|
|
||||||
{{binary-args}} cargo build --bin snake --profile release-binary
|
userapp app:
|
||||||
|
{{binary-args}} cargo build --bin {{app}} --profile release-binary
|
||||||
|
|
||||||
|
userapps:
|
||||||
|
just userapp calculator
|
||||||
|
just userapp snake
|
||||||
|
just userapp gallery
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use abi_sys::{
|
use abi_sys::{
|
||||||
DrawIterAbi, GenRand, GetKeyAbi, ListDir, LockDisplay, Modifiers, PrintAbi, ReadFile,
|
DrawIterAbi, FileLen, GenRand, GetKeyAbi, ListDir, LockDisplay, Modifiers, PrintAbi, ReadFile,
|
||||||
RngRequest, SleepAbi,
|
RngRequest, SleepAbi,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::{format, vec::Vec};
|
||||||
use core::sync::atomic::Ordering;
|
use core::sync::atomic::Ordering;
|
||||||
use embassy_rp::clocks::{RoscRng, clk_sys_freq};
|
use embassy_rp::clocks::{RoscRng, clk_sys_freq};
|
||||||
use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565};
|
use embedded_graphics::{Pixel, draw_target::DrawTarget, pixelcolor::Rgb565};
|
||||||
use embedded_sdmmc::{DirEntry, ShortFileName};
|
use embedded_sdmmc::{DirEntry, LfnBuffer, ShortFileName};
|
||||||
use heapless::spsc::Queue;
|
use heapless::spsc::Queue;
|
||||||
use shared::keyboard::KeyEvent;
|
use shared::keyboard::KeyEvent;
|
||||||
|
|
||||||
@@ -80,25 +80,25 @@ pub extern "C" fn gen_rand(req: &mut RngRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_dir_entries(dir: &Dir, files: &mut [Option<DirEntry>]) {
|
fn get_dir_entries(dir: &Dir, files: &mut [Option<DirEntry>]) -> usize {
|
||||||
let mut files = files.iter_mut();
|
let mut i = 0;
|
||||||
|
|
||||||
dir.iterate_dir(|entry| {
|
dir.iterate_dir(|entry| {
|
||||||
if let Some(f) = files.next() {
|
if i < files.len() {
|
||||||
*f = Some(entry.clone())
|
files[i] = Some(entry.clone());
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
i
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recurse_dir(dir: &Dir, dirs: &[&str], files: &mut [Option<DirEntry>]) {
|
fn recurse_dir(dir: &Dir, dirs: &[&str], files: &mut [Option<DirEntry>]) -> usize {
|
||||||
if dirs.is_empty() {
|
if dirs.is_empty() {
|
||||||
get_dir_entries(dir, files);
|
return get_dir_entries(dir, files);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dir = dir.open_dir(dirs[0]).unwrap();
|
let dir = dir.open_dir(dirs[0]).unwrap();
|
||||||
recurse_dir(&dir, &dirs[1..], files);
|
recurse_dir(&dir, &dirs[1..], files)
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: ListDir = list_dir;
|
const _: ListDir = list_dir;
|
||||||
@@ -107,7 +107,7 @@ pub extern "C" fn list_dir(
|
|||||||
len: usize,
|
len: usize,
|
||||||
files: *mut Option<DirEntry>,
|
files: *mut Option<DirEntry>,
|
||||||
files_len: usize,
|
files_len: usize,
|
||||||
) {
|
) -> usize {
|
||||||
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
|
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
|
||||||
let files = unsafe { core::slice::from_raw_parts_mut(files, files_len) };
|
let files = unsafe { core::slice::from_raw_parts_mut(files, files_len) };
|
||||||
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
|
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
|
||||||
@@ -116,23 +116,35 @@ pub extern "C" fn list_dir(
|
|||||||
|
|
||||||
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
|
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
|
||||||
let sd = guard.as_mut().unwrap();
|
let sd = guard.as_mut().unwrap();
|
||||||
|
|
||||||
|
let mut wrote = 0;
|
||||||
sd.access_root_dir(|root| {
|
sd.access_root_dir(|root| {
|
||||||
if !dir.is_empty() {
|
if dirs[0] == "" && dirs.len() >= 2 {
|
||||||
if dirs[0].is_empty() {
|
if dir == "/" {
|
||||||
get_dir_entries(&root, files);
|
wrote = get_dir_entries(&root, files);
|
||||||
} else {
|
} else {
|
||||||
recurse_dir(&root, &dirs[1..], files);
|
wrote = recurse_dir(&root, &dirs[1..], files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
wrote
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recurse_file<T>(dir: &Dir, dirs: &[&str], mut access: impl FnMut(&mut File) -> T) -> T {
|
fn recurse_file<T>(dir: &Dir, dirs: &[&str], mut access: impl FnMut(&mut File) -> T) -> T {
|
||||||
if dirs.len() == 1 {
|
if dirs.len() == 1 {
|
||||||
let file_name = ShortFileName::create_from_str(dirs[0]).unwrap();
|
let mut b = [0_u8; 50];
|
||||||
|
let mut buf = LfnBuffer::new(&mut b);
|
||||||
|
let mut short_name = None;
|
||||||
|
dir.iterate_dir_lfn(&mut buf, |entry, name| {
|
||||||
|
if let Some(name) = name {
|
||||||
|
if name == dirs[0] {
|
||||||
|
short_name = Some(entry.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
let mut file = dir
|
let mut file = dir
|
||||||
.open_file_in_dir(file_name, embedded_sdmmc::Mode::ReadWriteAppend)
|
.open_file_in_dir(short_name.unwrap(), embedded_sdmmc::Mode::ReadWriteAppend)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
return access(&mut file);
|
return access(&mut file);
|
||||||
}
|
}
|
||||||
@@ -160,15 +172,28 @@ pub extern "C" fn read_file(
|
|||||||
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
|
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
|
||||||
let sd = guard.as_mut().unwrap();
|
let sd = guard.as_mut().unwrap();
|
||||||
if !file.is_empty() {
|
if !file.is_empty() {
|
||||||
if file[0].is_empty() {
|
sd.access_root_dir(|root| {
|
||||||
} else {
|
res = recurse_file(&root, &file[1..], |file| {
|
||||||
sd.access_root_dir(|root| {
|
file.seek_from_start(start_from as u32).unwrap();
|
||||||
res = recurse_file(&root, &file[1..], |file| {
|
file.read(&mut buf).unwrap()
|
||||||
file.seek_from_start(start_from as u32).unwrap();
|
|
||||||
file.read(&mut buf).unwrap()
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: FileLen = file_len;
|
||||||
|
pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
|
||||||
|
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
|
||||||
|
let file = unsafe { core::str::from_raw_parts(str, len) };
|
||||||
|
let file: Vec<&str> = file.split('/').collect();
|
||||||
|
|
||||||
|
let mut res = 0;
|
||||||
|
|
||||||
|
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
|
||||||
|
let sd = guard.as_mut().unwrap();
|
||||||
|
if !file.is_empty() {
|
||||||
|
sd.access_root_dir(|root| res = recurse_file(&root, &file[1..], |file| file.length()));
|
||||||
|
}
|
||||||
|
res as usize
|
||||||
|
}
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ fn patch_abi(
|
|||||||
CallAbiTable::GenRand => abi::gen_rand as usize,
|
CallAbiTable::GenRand => abi::gen_rand as usize,
|
||||||
CallAbiTable::ListDir => abi::list_dir as usize,
|
CallAbiTable::ListDir => abi::list_dir as usize,
|
||||||
CallAbiTable::ReadFile => abi::read_file as usize,
|
CallAbiTable::ReadFile => abi::read_file as usize,
|
||||||
|
CallAbiTable::FileLen => abi::file_len as usize,
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
table_base.add(idx as usize).write(ptr);
|
table_base.add(idx as usize).write(ptr);
|
||||||
|
|||||||
9
user-apps/gallery/Cargo.toml
Normal file
9
user-apps/gallery/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "gallery"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
abi = { path = "../../abi" }
|
||||||
|
embedded-graphics = "0.8.1"
|
||||||
|
tinybmp = "0.6.0"
|
||||||
28
user-apps/gallery/build.rs
Normal file
28
user-apps/gallery/build.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//! This build script copies the `memory.x` file from the crate root into
|
||||||
|
//! a directory where the linker can always find it at build time.
|
||||||
|
//! For many projects this is optional, as the linker always searches the
|
||||||
|
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||||
|
//! are using a workspace or have a more complicated build setup, this
|
||||||
|
//! build script becomes required. Additionally, by requesting that
|
||||||
|
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||||
|
//! updating `memory.x` ensures a rebuild of the application with the
|
||||||
|
//! new memory settings.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Put `memory.x` in our output directory and ensure it's
|
||||||
|
// on the linker search path.
|
||||||
|
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||||
|
File::create(out.join("memory.x"))
|
||||||
|
.unwrap()
|
||||||
|
.write_all(include_bytes!("../memory.x"))
|
||||||
|
.unwrap();
|
||||||
|
println!("cargo:rustc-link-search={}", out.display());
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=memory.x");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
|
||||||
|
}
|
||||||
56
user-apps/gallery/src/main.rs
Normal file
56
user-apps/gallery/src/main.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
use abi::{
|
||||||
|
KeyCode, KeyState, Rng,
|
||||||
|
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
|
||||||
|
file_len, get_key, list_dir, lock_display, print, read_file, sleep,
|
||||||
|
};
|
||||||
|
use alloc::{format, vec::Vec};
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
use embedded_graphics::{Drawable, image::Image, prelude::*};
|
||||||
|
use tinybmp::Bmp;
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
print(&format!(
|
||||||
|
"user panic: {} @ {:?}",
|
||||||
|
info.message(),
|
||||||
|
info.location(),
|
||||||
|
));
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "Rust" fn _start() {
|
||||||
|
main()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
print("Starting Gallery app");
|
||||||
|
let mut display = Display;
|
||||||
|
|
||||||
|
let file = "/images/ferriseyes_tiny.bmp";
|
||||||
|
|
||||||
|
let mut bmp_buf = [0_u8; 3_000];
|
||||||
|
let read = read_file(file, 0, &mut bmp_buf);
|
||||||
|
let bmp = Bmp::from_slice(&bmp_buf[..read]).unwrap();
|
||||||
|
|
||||||
|
// ensure all draws show up at once
|
||||||
|
lock_display(true);
|
||||||
|
Image::new(&bmp, Point::new(10, 20))
|
||||||
|
.draw(&mut display)
|
||||||
|
.unwrap();
|
||||||
|
lock_display(false);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let event = get_key();
|
||||||
|
if event.state != KeyState::Idle {
|
||||||
|
match event.key {
|
||||||
|
KeyCode::Esc => return,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user