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>();
|
||||
}
|
||||
Reference in New Issue
Block a user