mirror of
https://github.com/marcoallegretti/WEFT_OS.git
synced 2026-03-26 17:03:09 +00:00
feat(servo-shell): add servo-shell skeleton, system UI, service unit, and Wayland input audit
Includes winit Wayland input audit for servo-shell integration planning. New files: - crates/weft-servo-shell/: new workspace member - Cargo.toml: anyhow + tracing deps; no servo dep yet (requires git dependency on github.com/servo/servo with multi-minute build; deferred until embedder contract is confirmed) - src/main.rs: reads WAYLAND_DISPLAY and WEFT_SYSTEM_UI_HTML, locates system-ui.html from packaged path, calls embed_servo() stub that returns a descriptive error explaining the integration work remaining - infra/shell/system-ui.html: system UI document per blueprint Section 5 DOM structure (weft-desktop, weft-wallpaper, weft-taskbar, weft-launcher, weft-notification-center, weft-window); includes clock and launcher toggle - infra/systemd/servo-shell.service: Requires+After weft-compositor.service, Type=simple, Restart=on-failure - docs/architecture/winit-wayland-audit.md: audit of winit 0.30.x Wayland backend against WEFT input requirements; identifies keyboard shortcut inhibit gap, touch gesture gap, IME incomplete (zwp_text_input_v3), frame pacing absent (wp_presentation_time), DMA-BUF unverified; none block initial integration; all tracked as pre-GA work items Modified: - Cargo.toml: add weft-servo-shell to workspace members - scripts/wsl-check.sh: switch to --workspace for all three gates
This commit is contained in:
parent
61bef1a0a7
commit
fc5ada2079
7 changed files with 395 additions and 7 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/weft-build-meta", "crates/weft-compositor"]
|
members = ["crates/weft-build-meta", "crates/weft-compositor", "crates/weft-servo-shell"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
|
||||||
14
crates/weft-servo-shell/Cargo.toml
Normal file
14
crates/weft-servo-shell/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "weft-servo-shell"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "weft-servo-shell"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
65
crates/weft-servo-shell/src/main.rs
Normal file
65
crates/weft-servo-shell/src/main.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> anyhow::Result<()> {
|
||||||
|
let wayland_display = std::env::var("WAYLAND_DISPLAY")
|
||||||
|
.context("WAYLAND_DISPLAY not set; weft-compositor must be running")?;
|
||||||
|
|
||||||
|
tracing::info!(socket = %wayland_display, "connecting to Wayland compositor");
|
||||||
|
|
||||||
|
let html_path = system_ui_html_path()?;
|
||||||
|
tracing::info!(path = %html_path.display(), "system UI entry point located");
|
||||||
|
|
||||||
|
embed_servo(&wayland_display, &html_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system_ui_html_path() -> anyhow::Result<PathBuf> {
|
||||||
|
if let Ok(p) = std::env::var("WEFT_SYSTEM_UI_HTML") {
|
||||||
|
return Ok(PathBuf::from(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
let packaged = PathBuf::from("/packages/system/servo-shell/active/share/weft/system-ui.html");
|
||||||
|
if packaged.exists() {
|
||||||
|
return Ok(packaged);
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!(
|
||||||
|
"system-ui.html not found; set WEFT_SYSTEM_UI_HTML or install the servo-shell package"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn embed_servo(_wayland_display: &str, _html_path: &std::path::Path) -> anyhow::Result<()> {
|
||||||
|
// Wave 4 skeleton entry point.
|
||||||
|
//
|
||||||
|
// Full implementation requires completion of the items in
|
||||||
|
// docs/architecture/winit-wayland-audit.md before production readiness,
|
||||||
|
// and the following integration work:
|
||||||
|
//
|
||||||
|
// 1. Add servo git dependency (not on crates.io; requires building Servo)
|
||||||
|
// 2. Implement servo::EmbedderMethods and servo::WindowMethods for the
|
||||||
|
// WEFT Wayland surface (winit + EGL, or smithay-client-toolkit directly)
|
||||||
|
// 3. Call servo::Servo::new() with the window and embedder
|
||||||
|
// 4. Load the system UI via servo::ServoUrl::parse(html_path)
|
||||||
|
// 5. Run the Servo event loop, forwarding Wayland events from winit
|
||||||
|
//
|
||||||
|
// The Servo dependency is intentionally absent from Cargo.toml at this stage.
|
||||||
|
// It requires a git dependency on github.com/servo/servo which embeds
|
||||||
|
// SpiderMonkey (GeckoMedia) and has a multi-minute build time. It is added
|
||||||
|
// when the embedder contract is ready.
|
||||||
|
anyhow::bail!(
|
||||||
|
"Servo embedding not yet implemented; \
|
||||||
|
see docs/architecture/winit-wayland-audit.md for gap assessment"
|
||||||
|
)
|
||||||
|
}
|
||||||
125
docs/architecture/winit-wayland-audit.md
Normal file
125
docs/architecture/winit-wayland-audit.md
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
# winit Wayland Input Audit
|
||||||
|
|
||||||
|
Audit of winit's Wayland backend against WEFT OS system shell input requirements.
|
||||||
|
Required by Wave 4 gate: Servo Wayland input audit result assessed.
|
||||||
|
|
||||||
|
Source: blueprint Section 11, GAP 1. Servo version audited: main branch (2025).
|
||||||
|
winit version audited: 0.30.x (smithay-client-toolkit backend).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Audit Scope
|
||||||
|
|
||||||
|
WEFT requires correct and reliable keyboard, mouse, touch, and IME input for
|
||||||
|
the system shell. Input regressions are system-level failures because there is
|
||||||
|
no fallback input path.
|
||||||
|
|
||||||
|
Servo delegates all windowing and input to winit. winit's Wayland backend uses
|
||||||
|
smithay-client-toolkit (sctk) as the protocol implementation layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### Keyboard input
|
||||||
|
|
||||||
|
**Status: FUNCTIONAL with known limitation**
|
||||||
|
|
||||||
|
Basic key events, modifiers, and repeat work correctly via xkb.
|
||||||
|
The `xdg_keyboard_shortcuts_inhibit` protocol is not implemented in winit's
|
||||||
|
Wayland backend, so system keyboard shortcuts (e.g. Alt+F4) cannot be
|
||||||
|
inhibited by client surfaces. This affects the system shell if it needs to
|
||||||
|
handle those key combinations before the compositor does.
|
||||||
|
|
||||||
|
Relevant winit issue: https://github.com/rust-windowing/winit/issues/2787 (open).
|
||||||
|
|
||||||
|
### Pointer input
|
||||||
|
|
||||||
|
**Status: FUNCTIONAL**
|
||||||
|
|
||||||
|
Button, scroll, and motion events work correctly. `zwp_relative_pointer_v1`
|
||||||
|
(relative motion for pointer locking) is implemented. `zwp_pointer_constraints_v1`
|
||||||
|
(locked/confined pointer) is implemented in winit 0.30+.
|
||||||
|
Frame-accurate pointer position via `wl_pointer.frame` is handled.
|
||||||
|
|
||||||
|
### Touch input
|
||||||
|
|
||||||
|
**Status: PARTIAL**
|
||||||
|
|
||||||
|
Single-touch is functional. Multi-touch slots are tracked via `wl_touch` protocol.
|
||||||
|
Gesture recognition is not implemented in winit — gestures from the compositor
|
||||||
|
(`zwp_pointer_gestures_v1`) are not consumed. This affects swipe/pinch gesture
|
||||||
|
handling in the system shell.
|
||||||
|
|
||||||
|
Relevant winit issue: not filed as of audit date.
|
||||||
|
|
||||||
|
### IME (Input Method Editor)
|
||||||
|
|
||||||
|
**Status: INCOMPLETE**
|
||||||
|
|
||||||
|
`zwp_text_input_v3` is implemented in sctk 0.18+ but winit's integration is
|
||||||
|
incomplete. Specifically:
|
||||||
|
- Pre-edit text display is not forwarded to the application's IME event stream
|
||||||
|
in all cases.
|
||||||
|
- `done` events with surrounding text are not always handled correctly.
|
||||||
|
|
||||||
|
This means CJK and other IME-dependent input in the system shell HTML will not
|
||||||
|
work correctly.
|
||||||
|
|
||||||
|
Relevant sctk issue: https://github.com/Smithay/client-toolkit/issues/605 (open).
|
||||||
|
|
||||||
|
### Frame pacing (vsync alignment)
|
||||||
|
|
||||||
|
**Status: NOT IMPLEMENTED**
|
||||||
|
|
||||||
|
winit does not implement `wp_presentation_time` (the Wayland presentation
|
||||||
|
feedback protocol). Frame timing is based on `wl_callback` only. This means
|
||||||
|
Servo cannot align rendering to compositor vsync, causing frame pacing issues
|
||||||
|
on variable-refresh-rate displays and tearing on fixed-refresh displays.
|
||||||
|
|
||||||
|
This must be fixed before the system shell is suitable for production use.
|
||||||
|
Relevant Servo issue: not filed as of audit date.
|
||||||
|
|
||||||
|
### DMA-BUF surface sharing
|
||||||
|
|
||||||
|
**Status: UNVERIFIED**
|
||||||
|
|
||||||
|
The Servo → WebRender → wgpu → wl_surface pipeline on Wayland may or may not
|
||||||
|
use `zwp_linux_dmabuf_v1` for zero-copy buffer sharing. This audit did not
|
||||||
|
test it under the WEFT compositor (requires QEMU or real hardware).
|
||||||
|
Must be verified when DRM backend testing is available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Assessment
|
||||||
|
|
||||||
|
| Input area | Status | Blocks Wave 4 skeleton? |
|
||||||
|
|---------------------|-------------|-------------------------|
|
||||||
|
| Keyboard (basic) | Functional | No |
|
||||||
|
| Keyboard shortcuts | Gap | No (deferred) |
|
||||||
|
| Pointer | Functional | No |
|
||||||
|
| Touch (single) | Functional | No |
|
||||||
|
| Touch (gestures) | Gap | No (deferred) |
|
||||||
|
| IME | Incomplete | No (system shell uses minimal JS) |
|
||||||
|
| Frame pacing | Not impl. | No (deferred, required before GA) |
|
||||||
|
| DMA-BUF | Unverified | No (requires hardware test) |
|
||||||
|
|
||||||
|
None of the identified gaps block the Wave 4 skeleton or initial integration.
|
||||||
|
They block production readiness, as documented in the blueprint.
|
||||||
|
|
||||||
|
**Gate decision**: Wave 4 may proceed. The gaps above are tracked as known
|
||||||
|
work items, not blocking conditions for skeleton implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Follow-up
|
||||||
|
|
||||||
|
Before WEFT OS reaches GA:
|
||||||
|
|
||||||
|
1. Contribute `wp_presentation_time` support to winit (or contribute to Servo
|
||||||
|
to work around it via the compositor's presentation feedback).
|
||||||
|
2. Contribute `zwp_text_input_v3` fix to sctk and winit for correct IME.
|
||||||
|
3. File and track a winit issue for `zwp_pointer_gestures_v1`.
|
||||||
|
4. Verify DMA-BUF path under the WEFT DRM compositor (requires hardware).
|
||||||
|
5. File issues for all confirmed gaps in the Servo and winit issue trackers
|
||||||
|
per the blueprint contribution workflow.
|
||||||
167
infra/shell/system-ui.html
Normal file
167
infra/shell/system-ui.html
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>WEFT Desktop</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--surface-bg: rgba(255, 255, 255, 0.07);
|
||||||
|
--surface-border: rgba(255, 255, 255, 0.12);
|
||||||
|
--taskbar-height: 48px;
|
||||||
|
--text-primary: rgba(255, 255, 255, 0.92);
|
||||||
|
--text-secondary: rgba(255, 255, 255, 0.55);
|
||||||
|
--accent: #5b8af5;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #0f1117;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
color: var(--text-primary);
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100dvw;
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-desktop {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr var(--taskbar-height);
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100dvw;
|
||||||
|
contain: layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-wallpaper {
|
||||||
|
display: block;
|
||||||
|
grid-row: 1;
|
||||||
|
background: linear-gradient(
|
||||||
|
145deg,
|
||||||
|
#0f1117 0%,
|
||||||
|
#161b2e 40%,
|
||||||
|
#0d2040 70%,
|
||||||
|
#0a1a30 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-taskbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
grid-row: 2;
|
||||||
|
height: var(--taskbar-height);
|
||||||
|
padding: 0 12px;
|
||||||
|
gap: 8px;
|
||||||
|
background: var(--surface-bg);
|
||||||
|
border-top: 1px solid var(--surface-border);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-taskbar-launcher {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-taskbar-launcher:hover {
|
||||||
|
background: var(--surface-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-taskbar-clock {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-launcher {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
bottom: var(--taskbar-height);
|
||||||
|
background: rgba(10, 12, 20, 0.85);
|
||||||
|
backdrop-filter: blur(32px);
|
||||||
|
-webkit-backdrop-filter: blur(32px);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-launcher:not([hidden]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-notification-center {
|
||||||
|
position: fixed;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
width: 360px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
z-index: 200;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
weft-window {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<weft-desktop>
|
||||||
|
<weft-wallpaper></weft-wallpaper>
|
||||||
|
<weft-taskbar>
|
||||||
|
<weft-taskbar-launcher id="launcher-btn" title="Apps">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<rect x="1" y="1" width="6" height="6" rx="1.5"
|
||||||
|
fill="rgba(255,255,255,0.7)" />
|
||||||
|
<rect x="11" y="1" width="6" height="6" rx="1.5"
|
||||||
|
fill="rgba(255,255,255,0.7)" />
|
||||||
|
<rect x="1" y="11" width="6" height="6" rx="1.5"
|
||||||
|
fill="rgba(255,255,255,0.7)" />
|
||||||
|
<rect x="11" y="11" width="6" height="6" rx="1.5"
|
||||||
|
fill="rgba(255,255,255,0.7)" />
|
||||||
|
</svg>
|
||||||
|
</weft-taskbar-launcher>
|
||||||
|
<weft-taskbar-clock id="clock"></weft-taskbar-clock>
|
||||||
|
</weft-taskbar>
|
||||||
|
</weft-desktop>
|
||||||
|
|
||||||
|
<weft-launcher hidden id="launcher"></weft-launcher>
|
||||||
|
<weft-notification-center id="notifications"></weft-notification-center>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var clockEl = document.getElementById('clock');
|
||||||
|
function updateClock() {
|
||||||
|
clockEl.textContent = new Date().toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateClock();
|
||||||
|
setInterval(updateClock, 10000);
|
||||||
|
|
||||||
|
document.getElementById('launcher-btn').addEventListener('click', function () {
|
||||||
|
var launcher = document.getElementById('launcher');
|
||||||
|
if (launcher.hasAttribute('hidden')) {
|
||||||
|
launcher.removeAttribute('hidden');
|
||||||
|
} else {
|
||||||
|
launcher.setAttribute('hidden', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}());
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
infra/systemd/servo-shell.service
Normal file
17
infra/systemd/servo-shell.service
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[Unit]
|
||||||
|
Description=WEFT OS Servo Shell
|
||||||
|
Documentation=https://github.com/weft-os/weft
|
||||||
|
Requires=weft-compositor.service
|
||||||
|
After=weft-compositor.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/packages/system/servo-shell/active/bin/weft-servo-shell
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
# WAYLAND_DISPLAY is exported by weft-compositor after sd_notify(READY=1).
|
||||||
|
# Downstream services that need the shell ready must declare
|
||||||
|
# After=servo-shell.service and a suitable readiness mechanism.
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical.target
|
||||||
|
|
@ -31,13 +31,13 @@ export PKG_CONFIG_PATH="$FAKE_PC_DIR:/usr/lib64/pkgconfig:/usr/share/pkgconfig"
|
||||||
cd "$PROJECT"
|
cd "$PROJECT"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> cargo check -p weft-compositor"
|
echo "==> cargo check --workspace"
|
||||||
cargo check -p weft-compositor 2>&1
|
cargo check --workspace 2>&1
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> cargo clippy -p weft-compositor -- -D warnings"
|
echo "==> cargo clippy --workspace -- -D warnings"
|
||||||
cargo clippy -p weft-compositor -- -D warnings 2>&1
|
cargo clippy --workspace -- -D warnings 2>&1
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> cargo fmt --check -p weft-compositor"
|
echo "==> cargo fmt --check --all"
|
||||||
cargo fmt --check -p weft-compositor 2>&1
|
cargo fmt --check --all 2>&1
|
||||||
echo ""
|
echo ""
|
||||||
echo "ALL DONE"
|
echo "ALL DONE"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue