diff --git a/crates/weft-appd/src/main.rs b/crates/weft-appd/src/main.rs index 30262fa..8fcc014 100644 --- a/crates/weft-appd/src/main.rs +++ b/crates/weft-appd/src/main.rs @@ -286,7 +286,7 @@ pub(crate) async fn dispatch(req: Request, registry: &Registry) -> Response { } } -fn app_store_roots() -> Vec { +pub(crate) fn app_store_roots() -> Vec { if let Ok(explicit) = std::env::var("WEFT_APP_STORE") { return vec![std::path::PathBuf::from(explicit)]; } diff --git a/crates/weft-appd/src/runtime.rs b/crates/weft-appd/src/runtime.rs index f95e8e4..cf142fd 100644 --- a/crates/weft-appd/src/runtime.rs +++ b/crates/weft-appd/src/runtime.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::time::Duration; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -9,6 +10,71 @@ use crate::ipc::{AppStateKind, Response}; const READY_TIMEOUT: Duration = Duration::from_secs(30); +fn resolve_preopens(app_id: &str) -> Vec<(String, String)> { + #[derive(serde::Deserialize)] + struct Pkg { + capabilities: Option>, + } + #[derive(serde::Deserialize)] + struct M { + package: Pkg, + } + + let pkg_dir = crate::app_store_roots().into_iter().find_map(|root| { + let dir = root.join(app_id); + if dir.join("wapp.toml").exists() { + Some(dir) + } else { + None + } + }); + + let caps = match pkg_dir { + None => return Vec::new(), + Some(dir) => { + let Ok(text) = std::fs::read_to_string(dir.join("wapp.toml")) else { + return Vec::new(); + }; + match toml::from_str::(&text) { + Ok(m) => m.package.capabilities.unwrap_or_default(), + Err(_) => return Vec::new(), + } + } + }; + + let home = match std::env::var("HOME") { + Ok(h) => PathBuf::from(h), + Err(_) => return Vec::new(), + }; + + let mut preopens = Vec::new(); + for cap in &caps { + match cap.as_str() { + "fs:rw:app-data" | "fs:read:app-data" => { + let data_dir = home + .join(".local/share/weft/apps") + .join(app_id) + .join("data"); + let _ = std::fs::create_dir_all(&data_dir); + preopens.push((data_dir.to_string_lossy().into_owned(), "/data".to_string())); + } + "fs:rw:xdg-documents" | "fs:read:xdg-documents" => { + let docs = home.join("Documents"); + if docs.exists() { + preopens.push(( + docs.to_string_lossy().into_owned(), + "/xdg/documents".to_string(), + )); + } + } + other => { + tracing::debug!(capability = other, "not mapped to preopen; skipped"); + } + } + } + preopens +} + pub(crate) async fn supervise( session_id: u64, app_id: &str, @@ -37,6 +103,10 @@ pub(crate) async fn supervise( cmd.arg("--ipc-socket").arg(sock); } + for (host, guest) in resolve_preopens(app_id) { + cmd.arg("--preopen").arg(format!("{host}::{guest}")); + } + let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => { diff --git a/crates/weft-pack/src/main.rs b/crates/weft-pack/src/main.rs index 92fe791..15cf265 100644 --- a/crates/weft-pack/src/main.rs +++ b/crates/weft-pack/src/main.rs @@ -17,6 +17,7 @@ struct PackageMeta { version: String, description: Option, author: Option, + capabilities: Option>, } #[derive(Debug, Deserialize)] @@ -141,6 +142,11 @@ fn print_info(m: &Manifest) { } println!("module: {}", m.runtime.module); println!("ui: {}", m.ui.entry); + if let Some(ref caps) = m.package.capabilities { + for cap in caps { + println!("cap: {cap}"); + } + } } fn is_valid_app_id(id: &str) -> bool {