feat(desktop): home app (system-only launch cap, LAUNCH_BADGE: apps+launch only), pm starts it with session (relaunch <=5); display keys F1/Alt+Tab next, F2 home, F4 close app (ROLE from launcher, terminal exempt), close returns to previous window; closed window -> app exits; shell 'name &'; launch by id; pm stack 8, storage 16 clients
This commit is contained in:
15
crates/apps/home/Cargo.toml
Normal file
15
crates/apps/home/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "home"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "home"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
k-ui = { workspace = true }
|
||||
manifest = { workspace = true }
|
||||
10
crates/apps/home/manifest.toml
Normal file
10
crates/apps/home/manifest.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[app]
|
||||
id = "kos.home"
|
||||
name = "Home"
|
||||
version = "1.0.0"
|
||||
binary = "home"
|
||||
author = "K-OS"
|
||||
memory = "3 MiB"
|
||||
|
||||
[capabilities]
|
||||
required = ["display", "launch"]
|
||||
118
crates/apps/home/src/main.rs
Normal file
118
crates/apps/home/src/main.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
//! Home: the session's desktop, which the permission manager starts with
|
||||
//! it. It lists every app with a window and opens one when it is pressed,
|
||||
//! as many as there is room for, each in its own window. A system app:
|
||||
//! `launch` is only ever granted to apps on the system image.
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
extern crate libkos;
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use k_ui::{App, Element, Spacing, TextColour, TextStyle, column, list_item, text};
|
||||
use libkos::apps::Apps;
|
||||
use manifest::{Capabilities, Capability};
|
||||
use types::address::VirtualAddress;
|
||||
use types::apps::reply;
|
||||
|
||||
/// Where the request page for the permission manager is mapped.
|
||||
const REQUEST_PAGE_VA: VirtualAddress = VirtualAddress::from_raw(0x0190_0000);
|
||||
const LISTING: usize = 4096;
|
||||
const ME: &str = "kos.home";
|
||||
|
||||
struct Entry {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
struct Home {
|
||||
apps: Option<Apps>,
|
||||
entries: Vec<Entry>,
|
||||
/// What went wrong last, shown until the next press.
|
||||
trouble: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Event {
|
||||
/// Open the `n`th app listed.
|
||||
Open(usize),
|
||||
}
|
||||
|
||||
impl Home {
|
||||
/// Every installed app that has a window, but Home.
|
||||
fn reload(&mut self) {
|
||||
let Some(apps) = &self.apps else { return };
|
||||
// On the heap: an app's stack is a few pages.
|
||||
let mut buf = alloc::vec![0u8; LISTING];
|
||||
let Ok(n) = apps.apps(&mut buf) else { return };
|
||||
let text = core::str::from_utf8(&buf[..n]).unwrap_or("");
|
||||
self.entries = libkos::apps::entries(text)
|
||||
.filter(|e| e.id != ME)
|
||||
.filter(|e| Capabilities::from_bits(e.required).union(Capabilities::from_bits(e.optional)).contains(Capability::Display))
|
||||
.map(|e| Entry { id: e.id.to_string(), name: e.name.to_string() })
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn open(&mut self, index: usize) {
|
||||
let (Some(apps), Some(e)) = (&self.apps, self.entries.get(index)) else { return };
|
||||
self.trouble = match apps.launch(&[e.id.as_str()], None, None) {
|
||||
// Its window comes to the front; it runs on its own.
|
||||
Ok((process, _)) => {
|
||||
libkos::syscall::drop_cap(process);
|
||||
None
|
||||
}
|
||||
Err(r) if r.code == reply::BUSY => Some("There is no room for another app: close one first (F4).".to_string()),
|
||||
Err(r) => Some(format!("{} did not open (code {}).", e.name, r.code)),
|
||||
};
|
||||
self.reload();
|
||||
}
|
||||
}
|
||||
|
||||
impl App for Home {
|
||||
type Event = Event;
|
||||
|
||||
fn init() -> Self {
|
||||
let apps = libkos::start::permissions().and_then(|ep| Apps::connect(ep, REQUEST_PAGE_VA));
|
||||
let mut home = Home { apps, entries: Vec::new(), trouble: None };
|
||||
home.reload();
|
||||
home
|
||||
}
|
||||
|
||||
fn update(&mut self, event: Event) {
|
||||
match event {
|
||||
Event::Open(i) => self.open(i),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Event> {
|
||||
let hint = "Enter opens an app. Alt+Tab or F1: the next window. F2: back here. F4: close the app in front.";
|
||||
let mut view = column()
|
||||
.padding(Spacing::Xl)
|
||||
.gap(Spacing::Md)
|
||||
.child(text("Home").style(TextStyle::Heading))
|
||||
.child(text(hint).style(TextStyle::Caption).colour(TextColour::Secondary))
|
||||
.children(self.entries.iter().enumerate().map(|(i, e)| list_item(e.name.clone(), Event::Open(i)).subtitle(e.id.clone())));
|
||||
if self.entries.is_empty() {
|
||||
view = view.child(text("No apps with a window are installed."));
|
||||
}
|
||||
if let Some(t) = &self.trouble {
|
||||
view = view.child(text(t.clone()).colour(TextColour::Accent));
|
||||
}
|
||||
view.into()
|
||||
}
|
||||
|
||||
/// Home is not left: Escape stays.
|
||||
fn back(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
k_ui::run::<Home>();
|
||||
}
|
||||
@@ -140,8 +140,11 @@ fn refused(what: &str, r: Refused) {
|
||||
}
|
||||
|
||||
/// Runs app `args[0]` with `args` in the current folder, and waits for it
|
||||
/// to finish or Ctrl+C.
|
||||
/// to finish or Ctrl+C; with a last argument `&`, does not wait.
|
||||
fn run_program(sh: &mut Shell, args: &[&str]) {
|
||||
if let Some((&"&", args)) = args.split_last() {
|
||||
return start_program(sh, args);
|
||||
}
|
||||
let folder = sh.fs.grant(sh.cwd.as_bytes(), false).ok();
|
||||
// A Ctrl+C typed since the last program is not for this one.
|
||||
let _ = syscall::poll(sh.done);
|
||||
@@ -166,6 +169,21 @@ fn run_program(sh: &mut Shell, args: &[&str]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts app `args[0]` and lets it run on its own (its window, if it has
|
||||
/// one, is closed with F4). It does not get the current folder: nobody
|
||||
/// would be there to take the grant back when it is done.
|
||||
fn start_program(sh: &mut Shell, args: &[&str]) {
|
||||
let Some(name) = args.first() else { return println!("usage: <app> [args] &") };
|
||||
match sh.apps.launch(args, Some(TERMINAL), None) {
|
||||
Ok((process, _)) => {
|
||||
// Not waited on: the handle is not needed.
|
||||
syscall::drop_cap(process);
|
||||
println!("{} started", name);
|
||||
}
|
||||
Err(r) => refused(name, r),
|
||||
}
|
||||
}
|
||||
|
||||
/// Installs the app in folder `from` (its manifest and binary); the owner
|
||||
/// is asked on the terminal.
|
||||
fn install(sh: &mut Shell, from: &str) {
|
||||
@@ -232,6 +250,7 @@ fn run(sh: &mut Shell, line: &str) {
|
||||
println!("anything else runs an app: try ls, cat readme.txt, snake");
|
||||
println!("apps see the current directory, read-only, and only what you allowed");
|
||||
println!("Ctrl+C stops a running program; snake quits with q");
|
||||
println!("end with & to leave it running (snake &); F4 closes the app in front");
|
||||
}
|
||||
Some("apps") => list(sh),
|
||||
Some("install") => match args.get(1) {
|
||||
|
||||
@@ -77,25 +77,23 @@ enum Input {
|
||||
Other,
|
||||
}
|
||||
|
||||
/// The next key, if one is waiting: q or Escape quits.
|
||||
/// The next key, if one is waiting: q or Escape quits, and so does the
|
||||
/// window going.
|
||||
fn next_input(window: CapSlot) -> Input {
|
||||
let Ok([tag, kind, ch, _]) = syscall::request(window, [types::display::window::POLL, 0, 0, 0]) else {
|
||||
return Input::None;
|
||||
};
|
||||
if tag == 0 {
|
||||
return Input::None; // NoEvent: queue drained
|
||||
}
|
||||
if tag != 1 {
|
||||
return Input::Other; // a key let go
|
||||
}
|
||||
// Arrows, or WASD (kind 9 is a character).
|
||||
match (kind, ch as u8) {
|
||||
(5, _) | (9, b'q') => Input::Quit,
|
||||
(0, _) | (9, b'w') => Input::Turn(Direction::Up),
|
||||
(1, _) | (9, b's') => Input::Turn(Direction::Down),
|
||||
(2, _) | (9, b'a') => Input::Turn(Direction::Left),
|
||||
(3, _) | (9, b'd') => Input::Turn(Direction::Right),
|
||||
_ => Input::Other,
|
||||
use libkos::display::Event;
|
||||
use libkos::input::{InputEvent, Key};
|
||||
match libkos::display::poll(window) {
|
||||
None => Input::None,
|
||||
Some(Event::Closed) => Input::Quit,
|
||||
Some(Event::Input(InputEvent::KeyDown(k))) => match k {
|
||||
Key::Escape | Key::Char(b'q') => Input::Quit,
|
||||
Key::Up | Key::Char(b'w') => Input::Turn(Direction::Up),
|
||||
Key::Down | Key::Char(b's') => Input::Turn(Direction::Down),
|
||||
Key::Left | Key::Char(b'a') => Input::Turn(Direction::Left),
|
||||
Key::Right | Key::Char(b'd') => Input::Turn(Direction::Right),
|
||||
_ => Input::Other,
|
||||
},
|
||||
Some(_) => Input::Other,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user