A fork of pulp-os for the xteink4 adding custom apps
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

cleanup

hans 33a8c25a f99b409a

+110 -76
-1
README.txt
··· 91 91 mod.rs SPI/DMA init, peripheral wiring 92 92 action.rs semantic actions, button-to-action mapper 93 93 button.rs button enum, ADC ladder decode 94 - pins.rs GPIO pin map (reference) 95 94 raw_gpio.rs register-level GPIO for unmapped pins 96 95 drivers/ 97 96 ssd1677.rs e-paper controller driver
+3 -3
build.rs
··· 6 6 7 7 fn linker_be_nice() { 8 8 let args: Vec<String> = std::env::args().collect(); 9 - if args.len() > 1 { 9 + // --error-handling-script passes two args: kind and symbol 10 + if args.len() >= 3 { 10 11 let kind = &args[1]; 11 12 let what = &args[2]; 12 13 ··· 127 128 } 128 129 println!("cargo:rerun-if-changed=assets/fonts"); 129 130 130 - // header 131 + // header (clippy suppression lives on the module decl in fonts/mod.rs) 131 132 writeln!(out, "// AUTO-GENERATED by build.rs - do not edit").unwrap(); 132 - writeln!(out, "#[allow(clippy::all)]").unwrap(); 133 133 writeln!( 134 134 out, 135 135 "use crate::fonts::bitmap::{{BitmapFont, BitmapGlyph, GLYPH_COUNT}};"
+13 -2
src/apps/files.rs
··· 10 10 11 11 use crate::apps::{App, AppContext, AppId, Services, Transition}; 12 12 use crate::board::action::{Action, ActionEvent}; 13 + use crate::board::{SCREEN_H, SCREEN_W}; 13 14 use crate::drivers::storage::DirEntry; 14 15 use crate::drivers::strip::StripBuffer; 15 16 use crate::fonts; ··· 179 180 self.needs_load = true; 180 181 self.stale_cache = true; 181 182 self.error = None; 182 - ctx.mark_dirty(Region::new(0, CONTENT_TOP, 480, 800 - CONTENT_TOP)); 183 + ctx.mark_dirty(Region::new( 184 + 0, 185 + CONTENT_TOP, 186 + SCREEN_W, 187 + SCREEN_H - CONTENT_TOP, 188 + )); 183 189 } 184 190 185 191 fn on_exit(&mut self) { ··· 189 195 fn on_suspend(&mut self) {} 190 196 191 197 fn on_resume(&mut self, ctx: &mut AppContext) { 192 - ctx.mark_dirty(Region::new(0, CONTENT_TOP, 480, 800 - CONTENT_TOP)); 198 + ctx.mark_dirty(Region::new( 199 + 0, 200 + CONTENT_TOP, 201 + SCREEN_W, 202 + SCREEN_H - CONTENT_TOP, 203 + )); 193 204 } 194 205 195 206 fn needs_work(&self) -> bool {
+15 -5
src/apps/home.rs
··· 12 12 use crate::apps::reader::RECENT_FILE; 13 13 use crate::apps::{App, AppContext, AppId, Services, Transition}; 14 14 use crate::board::action::{Action, ActionEvent}; 15 + use crate::board::{SCREEN_H, SCREEN_W}; 15 16 use crate::drivers::strip::StripBuffer; 16 17 use crate::fonts; 17 18 use crate::fonts::bitmap::BitmapFont; ··· 22 23 const ITEM_H: u16 = 52; 23 24 const ITEM_GAP: u16 = 14; 24 25 const ITEM_STRIDE: u16 = ITEM_H + ITEM_GAP; 25 - const ITEM_X: u16 = (480 - ITEM_W) / 2; 26 + const ITEM_X: u16 = (SCREEN_W - ITEM_W) / 2; 26 27 const TITLE_ITEM_GAP: u16 = 24; 27 28 const MAX_ITEMS: usize = 5; 28 29 29 30 // bookmark list layout 30 31 const BM_MARGIN: u16 = 8; 31 32 const BM_HEADER_GAP: u16 = 4; 32 - const BM_BOTTOM: u16 = 800 - BUTTON_BAR_H; 33 - const SCREEN_W: u16 = 480; 33 + const BM_BOTTOM: u16 = SCREEN_H - BUTTON_BAR_H; 34 34 35 35 fn compute_item_regions(heading_line_h: u16) -> [Region; MAX_ITEMS] { 36 36 let item_y = CONTENT_TOP + 8 + heading_line_h + TITLE_ITEM_GAP; ··· 210 210 ctx.clear_message(); 211 211 self.state = HomeState::Menu; 212 212 self.selected = 0; 213 - ctx.mark_dirty(Region::new(0, CONTENT_TOP, 480, 800 - CONTENT_TOP)); 213 + ctx.mark_dirty(Region::new( 214 + 0, 215 + CONTENT_TOP, 216 + SCREEN_W, 217 + SCREEN_H - CONTENT_TOP, 218 + )); 214 219 } 215 220 216 221 fn on_resume(&mut self, ctx: &mut AppContext) { 217 222 self.state = HomeState::Menu; 218 223 self.selected = 0; 219 224 self.needs_load_recent = true; 220 - ctx.mark_dirty(Region::new(0, CONTENT_TOP, 480, 800 - CONTENT_TOP)); 225 + ctx.mark_dirty(Region::new( 226 + 0, 227 + CONTENT_TOP, 228 + SCREEN_W, 229 + SCREEN_H - CONTENT_TOP, 230 + )); 221 231 } 222 232 223 233 fn needs_work(&self) -> bool {
+16 -19
src/apps/reader.rs
··· 21 21 use crate::apps::bookmarks; 22 22 use crate::apps::{App, AppContext, Services, Transition}; 23 23 use crate::board::action::{Action, ActionEvent}; 24 + use crate::board::{SCREEN_H, SCREEN_W}; 24 25 use crate::drivers::strip::StripBuffer; 25 26 use crate::fonts; 26 27 use crate::ui::quick_menu::QuickAction; ··· 47 48 const HEADER_REGION: Region = Region::new(MARGIN, HEADER_Y, 300, HEADER_H); 48 49 const STATUS_REGION: Region = Region::new(308, HEADER_Y, 164, HEADER_H); 49 50 50 - const PAGE_REGION: Region = Region::new(0, HEADER_Y, 480, 800 - HEADER_Y); 51 + const PAGE_REGION: Region = Region::new(0, HEADER_Y, SCREEN_W, SCREEN_H - HEADER_Y); 51 52 52 53 const NO_PREFETCH: usize = usize::MAX; 53 54 54 - const TEXT_W: u32 = (480 - 2 * MARGIN) as u32; 55 - const TEXT_AREA_H: u16 = 800 - TEXT_Y - BUTTON_BAR_H; 55 + const TEXT_W: u32 = (SCREEN_W - 2 * MARGIN) as u32; 56 + const TEXT_AREA_H: u16 = SCREEN_H - TEXT_Y - BUTTON_BAR_H; 56 57 const EOCD_TAIL: usize = 512; 57 58 const INDENT_PX: u32 = 24; // px per blockquote indent level 58 59 ··· 64 65 const CHAPTER_CACHE_MAX: usize = 98304; 65 66 66 67 const PROGRESS_H: u16 = 2; 67 - const PROGRESS_Y: u16 = 800 - PROGRESS_H - 1; 68 - const PROGRESS_W: u16 = 480 - 2 * MARGIN; 68 + const PROGRESS_Y: u16 = SCREEN_H - PROGRESS_H - 1; 69 + const PROGRESS_W: u16 = SCREEN_W - 2 * MARGIN; 69 70 70 71 // position overlay: centered banner shown while Next/Prev is held 71 72 const POSITION_OVERLAY_W: u16 = 280; 72 73 const POSITION_OVERLAY_H: u16 = 40; 73 74 const POSITION_OVERLAY: Region = Region::new( 74 - (480 - POSITION_OVERLAY_W) / 2, 75 - (800 - POSITION_OVERLAY_H) / 2, 75 + (SCREEN_W - POSITION_OVERLAY_W) / 2, 76 + (SCREEN_H - POSITION_OVERLAY_H) / 2, 76 77 POSITION_OVERLAY_W, 77 78 POSITION_OVERLAY_H, 78 79 ); ··· 80 81 const LOADING_REGION: Region = Region::new(MARGIN, TEXT_Y, 464, 20); 81 82 82 83 const QA_FONT_SIZE: u8 = 1; 83 - const QA_SAVE_BOOKMARK: u8 = 2; 84 84 const QA_PREV_CHAPTER: u8 = 3; 85 85 const QA_NEXT_CHAPTER: u8 = 4; 86 86 const QA_TOC: u8 = 5; 87 87 88 88 const QA_FONT_OPTIONS: &[&str] = &["Small", "Medium", "Large"]; 89 - const QA_MAX: usize = 5; 89 + const QA_MAX: usize = 4; 90 90 91 91 pub const RECENT_FILE: &str = "RECENT"; 92 92 ··· 322 322 self.book_font_size_idx, 323 323 QA_FONT_OPTIONS, 324 324 ); 325 - n += 1; 326 - 327 - self.qa_buf[n] = QuickAction::trigger(QA_SAVE_BOOKMARK, "Bookmark", "Save pos"); 328 325 n += 1; 329 326 330 327 // chapter nav only for multi-chapter EPUBs ··· 2033 2030 2034 2031 fn on_quick_trigger(&mut self, id: u8, ctx: &mut AppContext) { 2035 2032 match id { 2036 - QA_SAVE_BOOKMARK => { 2037 - log::info!("reader: bookmark save requested via quick menu"); 2038 - } 2039 2033 QA_PREV_CHAPTER => { 2040 2034 if self.is_epub && self.chapter > 0 { 2041 2035 self.chapter -= 1; ··· 2206 2200 let selected = idx == self.toc_selected; 2207 2201 2208 2202 if selected { 2209 - Rectangle::new(Point::new(0, y_top), Size::new(480, line_h as u32)) 2210 - .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) 2211 - .draw(strip) 2212 - .unwrap(); 2203 + Rectangle::new( 2204 + Point::new(0, y_top), 2205 + Size::new(SCREEN_W as u32, line_h as u32), 2206 + ) 2207 + .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) 2208 + .draw(strip) 2209 + .unwrap(); 2213 2210 } 2214 2211 2215 2212 let fg = if selected {
+7 -1
src/apps/settings.rs
··· 4 4 5 5 use crate::apps::{App, AppContext, Services, Transition}; 6 6 use crate::board::action::{Action, ActionEvent}; 7 + use crate::board::{SCREEN_H, SCREEN_W}; 7 8 use crate::drivers::strip::StripBuffer; 8 9 use crate::fonts; 9 10 use crate::fonts::bitmap::BitmapFont; ··· 475 476 fn on_enter(&mut self, ctx: &mut AppContext) { 476 477 self.selected = 0; 477 478 self.save_needed = false; 478 - ctx.mark_dirty(Region::new(0, CONTENT_TOP, 480, 800 - CONTENT_TOP)); 479 + ctx.mark_dirty(Region::new( 480 + 0, 481 + CONTENT_TOP, 482 + SCREEN_W, 483 + SCREEN_H - CONTENT_TOP, 484 + )); 479 485 } 480 486 481 487 fn on_event(&mut self, event: ActionEvent, ctx: &mut AppContext) -> Transition {
+1 -4
src/apps/upload.rs
··· 19 19 use log::info; 20 20 21 21 use crate::apps::settings::WifiConfig; 22 - use crate::board::Epd; 23 22 use crate::board::action::{Action, ActionEvent, ButtonMapper}; 23 + use crate::board::{Epd, SCREEN_H, SCREEN_W}; 24 24 use crate::drivers::sdcard::SdStorage; 25 25 use crate::drivers::storage; 26 26 use crate::drivers::strip::StripBuffer; ··· 30 30 use crate::ui::{Alignment, BitmapLabel, ButtonFeedback, CONTENT_TOP, Region, stack_fmt}; 31 31 32 32 // layout 33 - 34 - const SCREEN_W: u16 = 480; 35 - const SCREEN_H: u16 = 800; 36 33 37 34 const HEADING_X: u16 = 16; 38 35 const HEADING_W: u16 = SCREEN_W - HEADING_X * 2;
+16 -27
src/bin/main.rs
··· 42 42 BAR_HEIGHT, ButtonFeedback, QuickMenu, StatusBar, SystemStatus, free_stack_bytes, paint_stack, 43 43 stack_high_water_mark, 44 44 }; 45 - use static_cell::StaticCell; 45 + use static_cell::{ConstStaticCell, StaticCell}; 46 46 47 47 esp_bootloader_esp_idf::esp_app_desc!(); 48 48 ··· 73 73 } 74 74 } 75 75 76 + // Static dispatch to the active app by AppId. 76 77 macro_rules! with_app { 77 78 ($id:expr, $apps:expr, |$app:ident| $body:expr) => { 78 79 match $id { ··· 99 100 }; 100 101 } 101 102 103 + // Execute a nav-stack transition: lifecycle callbacks, font propagation. 102 104 macro_rules! apply_transition { 103 105 ($nav:expr, $launcher:expr, $apps:expr, $bm_cache:expr, 104 106 $quick_menu:expr, $bumps:expr) => {{ ··· 237 239 }}; 238 240 } 239 241 240 - // draw all layers for a strip: optional statusbar, active app, quick-menu overlay, button labels. 242 + // Build the per-strip draw closure: statusbar, active app, quick-menu overlay, button labels. 243 + // Macro because it captures borrows of different concrete app types (via with_app!). 241 244 macro_rules! draw_scene { 242 245 ($app:expr, $statusbar:expr, $quick_menu:expr, $bumps:expr, $draw_bar:expr) => { 243 246 |s: &mut StripBuffer| { ··· 253 256 }; 254 257 } 255 258 256 - // heavy statics kept out of async future so Embassy's state machine stays ~200B. 257 - // const-fn types -> ConstStaticCell (zero stack, placed in .bss). 258 - // runtime-init types -> StaticCell. 259 - 260 - // value in .bss at link time; as_static_mut() called once 261 - struct ConstStaticCell<T>(core::cell::UnsafeCell<T>); 262 - unsafe impl<T> Sync for ConstStaticCell<T> {} 263 - impl<T> ConstStaticCell<T> { 264 - const fn new(val: T) -> Self { 265 - Self(core::cell::UnsafeCell::new(val)) 266 - } 267 - // safety: called once per static before task spawn; single-core, single-thread 268 - #[allow(clippy::mut_from_ref)] 269 - fn as_static_mut(&self) -> &'static mut T { 270 - unsafe { &mut *self.0.get() } 271 - } 272 - } 259 + // Heavy statics kept out of the async future so Embassy's state machine stays ~200 B. 260 + // const-fn types → ConstStaticCell (value in .bss at link time; .take() panics on double-use). 261 + // runtime-init types → StaticCell (.init(val) panics on double-use). 273 262 274 263 static STRIP: ConstStaticCell<StripBuffer> = ConstStaticCell::new(StripBuffer::new()); 275 264 static STATUSBAR: ConstStaticCell<StatusBar> = ConstStaticCell::new(StatusBar::new()); ··· 311 300 board.display.epd.init(&mut delay); 312 301 info!("hardware initialized."); 313 302 314 - let strip = STRIP.as_static_mut(); 303 + let strip = STRIP.take(); 315 304 316 - let statusbar = STATUSBAR.as_static_mut(); 305 + let statusbar = STATUSBAR.take(); 317 306 let mut sd_ok = board 318 307 .storage 319 308 .sd ··· 333 322 let mut apps = Apps { 334 323 home: HOME.init(HomeApp::new()), 335 324 files: FILES.init(FilesApp::new()), 336 - reader: READER.as_static_mut(), 325 + reader: READER.take(), 337 326 settings: SETTINGS.init(SettingsApp::new()), 338 327 }; 339 328 340 - let launcher = LAUNCHER.as_static_mut(); 341 - let quick_menu = QUICK_MENU.as_static_mut(); 342 - let bumps = BUMPS.as_static_mut(); 329 + let launcher = LAUNCHER.take(); 330 + let quick_menu = QUICK_MENU.take(); 331 + let bumps = BUMPS.take(); 343 332 344 - let dir_cache = DIR_CACHE.as_static_mut(); 345 - let bm_cache = BM_CACHE.as_static_mut(); 333 + let dir_cache = DIR_CACHE.take(); 334 + let bm_cache = BM_CACHE.take(); 346 335 347 336 bm_cache.ensure_loaded(&board.storage.sd); 348 337
+6
src/board/mod.rs
··· 10 10 pub use crate::drivers::strip::StripBuffer; 11 11 pub use button::{Button, ROW1_THRESHOLDS, ROW2_THRESHOLDS, decode_ladder}; 12 12 13 + pub const SCREEN_W: u16 = HEIGHT; // 480 14 + pub const SCREEN_H: u16 = WIDTH; // 800 15 + 13 16 use core::cell::RefCell; 14 17 15 18 use critical_section::Mutex; ··· 91 94 } 92 95 } 93 96 97 + // Takes &Peripherals so init_spi_peripherals can consume them by value. 98 + // Safety: each clone_unchecked targets a distinct GPIO/ADC peripheral; 99 + // no pin is used by both init_input and init_spi_peripherals. 94 100 fn init_input(p: &Peripherals) -> InputHw { 95 101 let mut adc_cfg = AdcConfig::new(); 96 102
+3
src/drivers/battery.rs
··· 44 44 let (mv_lo, pct_lo) = DISCHARGE_CURVE[i + 1]; 45 45 if mv >= mv_lo { 46 46 let span_mv = mv_hi - mv_lo; 47 + if span_mv == 0 { 48 + return pct_hi; 49 + } 47 50 let span_pct = (pct_hi - pct_lo) as u32; 48 51 let frac = mv - mv_lo; 49 52 return (pct_lo as u32 + frac * span_pct / span_mv) as u8;
-1
src/drivers/input.rs
··· 22 22 Repeat(Button), 23 23 } 24 24 25 - // max 2: one debounce cycle pushes at most Release(old) + Press(new) 26 25 struct EventQueue { 27 26 buf: [Option<Event>; 2], 28 27 }
+5 -4
src/drivers/sdcard.rs
··· 4 4 use embedded_sdmmc::{SdCard, TimeSource, Timestamp, VolumeManager}; 5 5 use log::info; 6 6 7 + // No RTC on board; all file timestamps are set to 2025-01-01 00:00:00. 7 8 #[derive(Default, Clone, Copy)] 8 - pub struct DummyTimeSource; 9 + pub struct FixedTimestamp; 9 10 10 - impl TimeSource for DummyTimeSource { 11 + impl TimeSource for FixedTimestamp { 11 12 fn get_timestamp(&self) -> Timestamp { 12 13 Timestamp { 13 14 year_since_1970: 55, ··· 24 25 where 25 26 SPI: embedded_hal::spi::SpiDevice, 26 27 { 27 - pub volume_mgr: VolumeManager<SdCard<SPI, esp_hal::delay::Delay>, DummyTimeSource>, 28 + pub volume_mgr: VolumeManager<SdCard<SPI, esp_hal::delay::Delay>, FixedTimestamp>, 28 29 } 29 30 30 31 impl<SPI> SdStorage<SPI> ··· 39 40 Err(e) => info!("SD card probe failed: {:?}", e), 40 41 } 41 42 42 - let volume_mgr = VolumeManager::new(sdcard, DummyTimeSource); 43 + let volume_mgr = VolumeManager::new(sdcard, FixedTimestamp); 43 44 Self { volume_mgr } 44 45 } 45 46 }
-4
src/drivers/storage.rs
··· 1 1 // SD card file operations and directory cache. 2 - // with_dir! opens volume/root/subdirs; do_*! macros handle file ops. 3 2 // DirCache reads root entries once into RAM, serves pages from there. 4 3 5 4 use embedded_sdmmc::{Mode, VolumeIdx}; ··· 60 59 61 60 const MAX_DIR_ENTRIES: usize = 128; 62 61 63 - // with_dir!: open volume -> root, optionally descend 1 or 2 subdirs 64 62 macro_rules! with_dir { 65 63 // root only 66 64 ($sd:expr, |$dir:ident| $body:expr) => {{ ··· 93 91 $body 94 92 }}; 95 93 } 96 - 97 - // inner-op macros; always invoked inside with_dir! 98 94 99 95 macro_rules! read_loop { 100 96 ($file:expr, $buf:expr) => {{
+1
src/fonts/mod.rs
··· 4 4 5 5 pub mod bitmap; 6 6 7 + #[allow(clippy::all)] 7 8 pub mod font_data { 8 9 include!(concat!(env!("OUT_DIR"), "/font_data.rs")); 9 10 }
+1 -3
src/ui/button_feedback.rs
··· 7 7 use super::widget::{Alignment, Region}; 8 8 use crate::board::action::{Action, ButtonMapper}; 9 9 use crate::board::button::Button; 10 + use crate::board::{SCREEN_H, SCREEN_W}; 10 11 use crate::drivers::strip::StripBuffer; 11 12 use crate::fonts::bitmap::BitmapFont; 12 13 use crate::fonts::font_data; ··· 19 20 20 21 const RIDGE_W: u16 = 22; 21 22 const RIDGE_H: u16 = 36; 22 - 23 - const SCREEN_W: u16 = 480; 24 - const SCREEN_H: u16 = 800; 25 23 26 24 // center positions of each button on the screen edge (px) 27 25 const CX_BACK: u16 = 84;
+2
src/ui/mod.rs
··· 18 18 stack_high_water_mark, 19 19 }; 20 20 pub use widget::{Alignment, Region, wrap_next, wrap_prev}; 21 + 22 + pub use crate::board::{SCREEN_H, SCREEN_W};
+2 -1
src/ui/quick_menu.rs
··· 7 7 8 8 use super::stack_fmt::StackFmt; 9 9 use super::widget::{Alignment, Region, wrap_next, wrap_prev}; 10 + use crate::board::SCREEN_W; 10 11 use crate::board::action::Action; 11 12 use crate::drivers::strip::StripBuffer; 12 13 use crate::fonts::bitmap::BitmapFont; ··· 15 16 // layout constants 16 17 17 18 const OVERLAY_W: u16 = 400; 18 - const OVERLAY_X: u16 = (480 - OVERLAY_W) / 2; 19 + const OVERLAY_X: u16 = (SCREEN_W - OVERLAY_W) / 2; 19 20 const OVERLAY_BOTTOM: u16 = 760; // above button widgets (~14px clearance) 20 21 const ITEM_H: u16 = 40; 21 22 const ITEM_GAP: u16 = 4;
+16
src/ui/stack_fmt.rs
··· 7 7 len: usize, 8 8 } 9 9 10 + impl<const N: usize> Default for StackFmt<N> { 11 + fn default() -> Self { 12 + Self::new() 13 + } 14 + } 15 + 10 16 impl<const N: usize> StackFmt<N> { 11 17 pub const fn new() -> Self { 12 18 Self { ··· 18 24 #[inline] 19 25 pub fn as_str(&self) -> &str { 20 26 core::str::from_utf8(&self.buf[..self.len]).unwrap_or("") 27 + } 28 + 29 + #[inline] 30 + pub fn is_empty(&self) -> bool { 31 + self.len == 0 21 32 } 22 33 23 34 #[inline] ··· 55 66 #[inline] 56 67 pub fn len(&self) -> usize { 57 68 self.pos 69 + } 70 + 71 + #[inline] 72 + pub fn is_empty(&self) -> bool { 73 + self.pos == 0 58 74 } 59 75 } 60 76
+3 -1
src/ui/statusbar.rs
··· 12 12 use embedded_graphics::mono_font::ascii::FONT_6X13; 13 13 use embedded_graphics::pixelcolor::BinaryColor; 14 14 use embedded_graphics::prelude::*; 15 + 16 + use crate::board::SCREEN_W; 15 17 #[cfg(debug_assertions)] 16 18 use embedded_graphics::primitives::PrimitiveStyle; 17 19 #[cfg(debug_assertions)] ··· 28 30 29 31 pub const CONTENT_TOP: u16 = BAR_HEIGHT; 30 32 31 - pub const BAR_REGION: Region = Region::new(0, 0, 480, BAR_HEIGHT); 33 + pub const BAR_REGION: Region = Region::new(0, 0, SCREEN_W, BAR_HEIGHT); 32 34 33 35 pub struct SystemStatus { 34 36 pub uptime_secs: u32,