From 4edfa00e220fcd08577be8f2d98043c7eaa48213 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Thu, 12 Mar 2026 12:59:24 +0100 Subject: [PATCH] feat(runtime): sys:clipboard host functions via wl-paste/wl-copy --- crates/weft-runtime/src/main.rs | 53 ++++++++++++++++++++++++++++ crates/weft-runtime/wit/weft-app.wit | 10 ++++++ 2 files changed, 63 insertions(+) diff --git a/crates/weft-runtime/src/main.rs b/crates/weft-runtime/src/main.rs index 85b0c74..48becc5 100644 --- a/crates/weft-runtime/src/main.rs +++ b/crates/weft-runtime/src/main.rs @@ -267,6 +267,28 @@ fn run_module( ) .context("define weft:app/notifications#notify")?; + linker + .instance("weft:app/clipboard@0.1.0") + .context("define weft:app/clipboard instance")? + .func_wrap( + "read", + |_: wasmtime::StoreContextMut<'_, State>, + ()| + -> wasmtime::Result<(Result,)> { + Ok((host_clipboard_read(),)) + }, + ) + .context("define weft:app/clipboard#read")? + .func_wrap( + "write", + |_: wasmtime::StoreContextMut<'_, State>, + (text,): (String,)| + -> wasmtime::Result<(Result<(), String>,)> { + Ok((host_clipboard_write(&text),)) + }, + ) + .context("define weft:app/clipboard#write")?; + let mut ctx_builder = WasiCtxBuilder::new(); ctx_builder.inherit_stdout().inherit_stderr(); @@ -346,6 +368,37 @@ fn host_fetch( Err("net-fetch capability not compiled in".to_owned()) } +#[cfg(feature = "wasmtime-runtime")] +fn host_clipboard_read() -> Result { + let out = std::process::Command::new("wl-paste") + .arg("--no-newline") + .output() + .map_err(|e| e.to_string())?; + if out.status.success() { + String::from_utf8(out.stdout).map_err(|e| e.to_string()) + } else { + Err(String::from_utf8_lossy(&out.stderr).trim().to_owned()) + } +} + +#[cfg(feature = "wasmtime-runtime")] +fn host_clipboard_write(text: &str) -> Result<(), String> { + use std::io::Write; + let mut child = std::process::Command::new("wl-copy") + .stdin(std::process::Stdio::piped()) + .spawn() + .map_err(|e| e.to_string())?; + if let Some(stdin) = child.stdin.as_mut() { + stdin.write_all(text.as_bytes()).map_err(|e| e.to_string())?; + } + let status = child.wait().map_err(|e| e.to_string())?; + if status.success() { + Ok(()) + } else { + Err(format!("wl-copy exited with {status}")) + } +} + #[cfg(feature = "wasmtime-runtime")] fn host_notify(title: &str, body: &str, icon: Option<&str>) -> Result<(), String> { let mut cmd = std::process::Command::new("notify-send"); diff --git a/crates/weft-runtime/wit/weft-app.wit b/crates/weft-runtime/wit/weft-app.wit index f861b70..818cfcb 100644 --- a/crates/weft-runtime/wit/weft-app.wit +++ b/crates/weft-runtime/wit/weft-app.wit @@ -50,3 +50,13 @@ interface notifications { icon: option, ) -> result<_, string>; } + +/// Host interface for reading and writing the Wayland clipboard. +/// Requires sys:clipboard:read and/or sys:clipboard:write capabilities. +interface clipboard { + /// Return the current clipboard text contents. + read: func() -> result; + + /// Replace the clipboard contents with the given text. + write: func(text: string) -> result<_, string>; +}