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

@@ -100,6 +100,15 @@ impl Apps {
Ok(len)
}
/// Every installed app, for a program to read (see `entries`), into
/// `out`; how much.
pub fn apps(&self, out: &mut [u8]) -> Result<usize> {
let (len, _) = self.with_page(wire::APPS, 0)?;
let len = len.min(out.len()).min(PAGE_SIZE);
self.page.copy_to(ByteOffset::ZERO, &mut out[..len]);
Ok(len)
}
/// Takes the capability with catalog index `capability` away from app
/// `id` (stopping it if it runs with it).
pub fn revoke(&self, id: &str, capability: usize) -> Result<()> {
@@ -114,3 +123,37 @@ impl Apps {
self.with_page(wire::ALLOW, capability).map(|_| ())
}
}
/// One installed app, as `Apps::apps` lists it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppEntry<'a> {
pub id: &'a str,
pub name: &'a str,
/// Capability bits (`manifest::Capabilities`).
pub required: u32,
pub optional: u32,
pub granted: u32,
}
/// The apps in a listing; a line that does not parse is skipped.
pub fn entries(text: &str) -> impl Iterator<Item = AppEntry<'_>> {
text.lines().filter_map(|line| {
let mut f = line.split('\t');
let (id, name) = (f.next()?, f.next()?);
let mut bits = || f.next()?.parse().ok();
Some(AppEntry { id, name, required: bits()?, optional: bits()?, granted: bits()? })
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_listing_parses_and_bad_lines_are_skipped() {
let text = "kos.snake\tSnake\t1\t0\t1\nnonsense\nkos.notes\tNotes\t0\t3\t2\n";
let all: alloc::vec::Vec<_> = entries(text).collect();
assert_eq!(all.len(), 2);
assert_eq!(all[1], AppEntry { id: "kos.notes", name: "Notes", required: 0, optional: 3, granted: 2 });
}
}

View File

@@ -12,6 +12,7 @@ pub mod input;
pub mod io;
mod rt;
pub mod spawn;
pub mod session;
pub mod start;
pub mod terminal;
pub mod syscall;

View File

@@ -0,0 +1,33 @@
//! The session capability's client (`types::spawn::WHO`, `SIGN_OUT`): who
//! is signed in, and signing out. Init answers it.
use crate::syscall;
use types::bounded::CapSlot;
use types::spawn::{SIGN_OUT, WHO, role};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
Owner,
Standard,
Guest,
}
/// Who is signed in: their name (into `name`, how long) and role.
pub fn who(session: CapSlot, name: &mut [u8; types::name::BYTES]) -> Option<(usize, Role)> {
let [r, a, b, c] = syscall::request(session, [WHO, 0, 0, 0]).ok()?;
let role = match r {
role::OWNER => Role::Owner,
role::STANDARD => Role::Standard,
role::GUEST => Role::Guest,
_ => return None,
};
let (bytes, len) = types::name::unpack([a, b, c]);
*name = bytes;
Some((len, role))
}
/// Signs the user out: init stops everything the session runs, this
/// program included, and the lock screen comes back.
pub fn sign_out(session: CapSlot) -> bool {
syscall::send_msg(session, [SIGN_OUT, 0, 0, 0], None).is_ok()
}

View File

@@ -44,6 +44,18 @@ pub fn data() -> Option<CapSlot> {
StartArg::decode(arg()).data
}
/// The permission manager's management interface, if this app was granted
/// `permissions`.
pub fn permissions() -> Option<CapSlot> {
StartArg::decode(arg()).permissions
}
/// The session (who is signed in, signing out), if this app was granted
/// `session`.
pub fn session() -> Option<CapSlot> {
StartArg::decode(arg()).session
}
/// The display server endpoint this process was given, if any.
pub fn display() -> Option<CapSlot> {
StartArg::decode(arg()).display