feat(settings): settings app: apps list + per-capability allow/revoke, about (who/role), sign out; system-only caps permissions/session (install refused); pm MANAGE_BADGE copy (list/apps/allow/revoke only), APPS op; init WHO + session pass-on; factory decisions seeded for new system apps; k-ui list_item/toggle, screens/back/scroll

This commit is contained in:
2026-10-28 13:14:35 +01:00
parent a2d4a7d085
commit 29becf297c
38 changed files with 947 additions and 85 deletions

View File

@@ -24,6 +24,15 @@ pub const LIST: usize = 5;
/// owner takes it away (a running app that had it is stopped) or gives it.
pub const REVOKE: usize = 6;
pub const ALLOW: usize = 7;
/// Writes one line per installed app into the page, for a program to read:
/// id, name, then the required, optional and granted capabilities' bits
/// (decimal), tab separated. Reply `OK` with the text's length in word 1.
pub const APPS: usize = 8;
/// On the badge of the copy of its endpoint the permission manager gives
/// an app granted `permissions`: through it only `LIST`, `APPS`, `REVOKE`
/// and `ALLOW` work, never installing or launching.
pub const MANAGE_BADGE: usize = 1 << 61;
/// The request page's size.
pub const PAGE_SIZE: usize = 4096;
@@ -46,4 +55,6 @@ pub mod reply {
pub const FAILED: usize = 9;
/// The offered folder has no `manifest.toml`.
pub const NO_MANIFEST: usize = 10;
/// Word 1: the capabilities it asks for that only system apps may have.
pub const SYSTEM_ONLY: usize = 11;
}

View File

@@ -104,27 +104,9 @@ pub mod window {
pub const OK: usize = 0;
pub const PIXEL_BYTES: usize = 4;
/// Longest name the OS bar shows.
pub const NAME_BYTES: usize = 24;
pub use crate::name::BYTES as NAME_BYTES;
/// A name in three words, NUL padded, cut at `NAME_BYTES`.
pub fn pack_name(name: &[u8]) -> [usize; 3] {
let mut bytes = [0u8; NAME_BYTES];
let n = name.len().min(NAME_BYTES);
bytes[..n].copy_from_slice(&name[..n]);
core::array::from_fn(|i| {
u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap_or([0; 8])) as usize
})
}
/// The name in `words`, and how long it is.
pub fn unpack_name(words: [usize; 3]) -> ([u8; NAME_BYTES], usize) {
let mut bytes = [0u8; NAME_BYTES];
for (chunk, w) in bytes.chunks_mut(8).zip(words) {
chunk.copy_from_slice(&(w as u64).to_le_bytes());
}
let len = bytes.iter().position(|b| *b == 0).unwrap_or(NAME_BYTES);
(bytes, len)
}
pub use crate::name::{pack as pack_name, unpack as unpack_name};
/// A rectangle in two words: `x | y << 16` and `w | h << 16`.
pub fn pack_rect(x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension) -> [usize; 2] {

View File

@@ -15,6 +15,7 @@ pub mod input;
pub mod log;
pub mod macros;
pub mod mapping;
pub mod name;
pub mod permission;
#[cfg(kani)]
pub mod proofs;

23
crates/types/src/name.rs Normal file
View File

@@ -0,0 +1,23 @@
//! A short name (a user's, an app's) in three message words, NUL padded.
pub const BYTES: usize = 24;
/// `name`, cut at `BYTES`.
pub fn pack(name: &[u8]) -> [usize; 3] {
let mut bytes = [0u8; BYTES];
let n = name.len().min(BYTES);
bytes[..n].copy_from_slice(&name[..n]);
core::array::from_fn(|i| {
u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap_or([0; 8])) as usize
})
}
/// The name in `words`, and how long it is.
pub fn unpack(words: [usize; 3]) -> ([u8; BYTES], usize) {
let mut bytes = [0u8; BYTES];
for (chunk, w) in bytes.chunks_mut(8).zip(words) {
chunk.copy_from_slice(&(w as u64).to_le_bytes());
}
let len = bytes.iter().position(|b| *b == 0).unwrap_or(BYTES);
(bytes, len)
}

View File

@@ -20,6 +20,15 @@ pub const NONE: usize = usize::MAX;
/// Sent one way (no reply: the sender is about to be stopped) through the
/// endpoint copy init gives a session's shell: the user signs out.
pub const SIGN_OUT: usize = 1;
/// Through the same copy: who is signed in. Reply `[role, name...]`, the
/// name in three words (`types::name`); role as in `role`.
pub const WHO: usize = 2;
pub mod role {
pub const OWNER: usize = 1;
pub const STANDARD: usize = 2;
pub const GUEST: usize = 3;
}
/// The badge on that copy; spawn clients' badges are small indices.
pub const SESSION_BADGE: usize = 0x5E55;

View File

@@ -10,6 +10,8 @@ const STORAGE_SHIFT: u32 = 16;
const ARGS_BIT: usize = 1 << 24;
const DISPLAY_SHIFT: u32 = 32;
const DATA_SHIFT: u32 = 40;
const PERMISSIONS_SHIFT: u32 = 48;
const SESSION_SHIFT: u32 = 56;
/// Where the arguments page sits: the program's arguments, NUL separated,
/// the first its name. Below the stack, which ends at the image.
@@ -27,6 +29,10 @@ pub struct StartArg {
pub display: Option<CapSlot>,
/// The app's own data folder, read-write (its `storage` capability).
pub data: Option<CapSlot>,
/// Managing other apps' permissions (the `permissions` capability).
pub permissions: Option<CapSlot>,
/// Who is signed in, and signing out (the `session` capability).
pub session: Option<CapSlot>,
}
fn field(arg: usize, shift: u32) -> Option<CapSlot> {
@@ -46,6 +52,8 @@ impl StartArg {
| if self.args { ARGS_BIT } else { 0 }
| put(self.display, DISPLAY_SHIFT)
| put(self.data, DATA_SHIFT)
| put(self.permissions, PERMISSIONS_SHIFT)
| put(self.session, SESSION_SHIFT)
}
pub fn decode(arg: usize) -> Self {
@@ -56,6 +64,8 @@ impl StartArg {
args: arg & ARGS_BIT != 0,
display: field(arg, DISPLAY_SHIFT),
data: field(arg, DATA_SHIFT),
permissions: field(arg, PERMISSIONS_SHIFT),
session: field(arg, SESSION_SHIFT),
}
}
}
@@ -73,6 +83,8 @@ mod tests {
args: true,
display: CapSlot::new(7),
data: CapSlot::new(62),
permissions: CapSlot::new(9),
session: CapSlot::new(63),
};
assert_eq!(StartArg::decode(a.encode()), a);
assert_eq!(StartArg::decode(0), StartArg::default());