mirror of
https://github.com/marcoallegretti/WEFT_OS.git
synced 2026-03-26 17:03:09 +00:00
feat(servo-embed): wire Servo deps and share Wayland surface with shell client
- Add servo/winit/softbuffer as optional deps in weft-servo-shell and weft-app-shell Cargo.toml, gated on servo-embed feature - Replace ShellClient::connect() and connect_as_app() with connect_with_display() and connect_as_app_with_display(), using Backend::from_foreign_display to share the winit wl_display connection - Move ShellClient construction into resumed() in both embedders after winit window and wl_surface are available - Pass actual wl_surface to create_window instead of None - Fix pre-existing field name bug: wayland-scanner generates _type for the reserved keyword arg name=type, not r#type
This commit is contained in:
parent
4edfa00e22
commit
34359acf3f
9 changed files with 7248 additions and 176 deletions
7231
Cargo.lock
generated
7231
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,11 +9,10 @@ name = "weft-app-shell"
|
|||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
# Enable actual Servo rendering. Requires manually adding the deps listed in
|
||||
# weft-servo-shell/SERVO_PIN.md to this file before building; they are not
|
||||
# included here to avoid pulling the Servo monorepo (~1 GB) into every
|
||||
# `cargo check` cycle.
|
||||
servo-embed = ["dep:serde", "dep:toml"]
|
||||
# Enable actual Servo rendering. Deps are declared as optional below and only
|
||||
# fetched when this feature is active. The Servo monorepo (~1 GB) is not
|
||||
# downloaded during a plain `cargo check` or `cargo build` without this feature.
|
||||
servo-embed = ["dep:servo", "dep:winit", "dep:softbuffer", "dep:serde", "dep:toml"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
|
|
@ -25,3 +24,18 @@ wayland-scanner = "0.31"
|
|||
bitflags = "2"
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
toml = { version = "0.8", optional = true }
|
||||
|
||||
[dependencies.servo]
|
||||
git = "https://github.com/marcoallegretti/servo"
|
||||
branch = "servo-weft"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.winit]
|
||||
version = "0.30"
|
||||
optional = true
|
||||
features = ["wayland"]
|
||||
|
||||
[dependencies.softbuffer]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use winit::{
|
|||
event::{ElementState, MouseButton, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
|
||||
keyboard::ModifiersState,
|
||||
platform::wayland::{ActiveEventLoopExtWayland, WindowExtWayland},
|
||||
window::{Window, WindowAttributes, WindowId},
|
||||
};
|
||||
|
||||
|
|
@ -74,6 +75,7 @@ impl RenderingCtx {
|
|||
|
||||
struct App {
|
||||
url: ServoUrl,
|
||||
app_id: String,
|
||||
session_id: u64,
|
||||
ws_port: u16,
|
||||
window: Option<Arc<Window>>,
|
||||
|
|
@ -92,13 +94,14 @@ struct App {
|
|||
impl App {
|
||||
fn new(
|
||||
url: ServoUrl,
|
||||
app_id: String,
|
||||
session_id: u64,
|
||||
ws_port: u16,
|
||||
waker: WeftEventLoopWaker,
|
||||
shell_client: Option<crate::shell_client::ShellClient>,
|
||||
) -> Self {
|
||||
Self {
|
||||
url,
|
||||
app_id,
|
||||
session_id,
|
||||
ws_port,
|
||||
window: None,
|
||||
|
|
@ -111,7 +114,7 @@ impl App {
|
|||
ready_signalled: false,
|
||||
modifiers: ModifiersState::default(),
|
||||
cursor_pos: servo::euclid::default::Point2D::zero(),
|
||||
shell_client,
|
||||
shell_client: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +163,22 @@ impl ApplicationHandler<ServoWake> for App {
|
|||
let size = window.inner_size();
|
||||
self.window = Some(Arc::clone(&window));
|
||||
|
||||
if self.shell_client.is_none() {
|
||||
if let (Some(disp), Some(surf)) =
|
||||
(event_loop.wayland_display(), window.wayland_surface())
|
||||
{
|
||||
match crate::shell_client::ShellClient::connect_as_app_with_display(
|
||||
&self.app_id,
|
||||
self.session_id,
|
||||
disp,
|
||||
surf,
|
||||
) {
|
||||
Ok(sc) => self.shell_client = Some(sc),
|
||||
Err(e) => tracing::warn!(error = %e, "shell protocol unavailable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let servo = ServoBuilder::default()
|
||||
.event_loop_waker(Box::new(self.waker.clone()))
|
||||
.build();
|
||||
|
|
@ -421,7 +440,6 @@ pub fn run(
|
|||
app_id: &str,
|
||||
session_id: u64,
|
||||
ws_port: u16,
|
||||
shell_client: Option<crate::shell_client::ShellClient>,
|
||||
) -> anyhow::Result<()> {
|
||||
let url = resolve_weft_app_url(app_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("no ui/index.html found for app {app_id}"))?;
|
||||
|
|
@ -434,7 +452,7 @@ pub fn run(
|
|||
proxy: Arc::new(Mutex::new(event_loop.create_proxy())),
|
||||
};
|
||||
|
||||
let mut app = App::new(url, session_id, ws_port, waker, shell_client);
|
||||
let mut app = App::new(url, app_id.to_owned(), session_id, ws_port, waker);
|
||||
event_loop
|
||||
.run_app(&mut app)
|
||||
.map_err(|e| anyhow::anyhow!("event loop run: {e}"))
|
||||
|
|
|
|||
|
|
@ -28,18 +28,7 @@ fn main() -> anyhow::Result<()> {
|
|||
|
||||
let ws_port = appd_ws_port();
|
||||
|
||||
let shell = match shell_client::ShellClient::connect_as_app(&app_id, session_id) {
|
||||
Ok(c) => {
|
||||
tracing::info!("app shell window registered with compositor");
|
||||
Some(c)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "shell protocol unavailable; continuing without compositor registration");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
embed_app(&app_id, session_id, ws_port, shell)
|
||||
embed_app(&app_id, session_id, ws_port)
|
||||
}
|
||||
|
||||
fn appd_ws_port() -> u16 {
|
||||
|
|
@ -63,14 +52,13 @@ fn embed_app(
|
|||
app_id: &str,
|
||||
session_id: u64,
|
||||
ws_port: u16,
|
||||
shell_client: Option<shell_client::ShellClient>,
|
||||
) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "servo-embed")]
|
||||
return embedder::run(app_id, session_id, ws_port, shell_client);
|
||||
return embedder::run(app_id, session_id, ws_port);
|
||||
|
||||
#[cfg(not(feature = "servo-embed"))]
|
||||
{
|
||||
let _ = (app_id, session_id, ws_port, shell_client);
|
||||
let _ = (app_id, session_id, ws_port);
|
||||
println!("READY");
|
||||
use std::io::Write;
|
||||
let _ = std::io::stdout().flush();
|
||||
|
|
|
|||
|
|
@ -129,9 +129,26 @@ pub struct ShellClient {
|
|||
}
|
||||
|
||||
impl ShellClient {
|
||||
pub fn connect_as_app(app_id: &str, session_id: u64) -> anyhow::Result<Self> {
|
||||
let conn =
|
||||
Connection::connect_to_env().context("failed to connect to Wayland compositor")?;
|
||||
/// Connect using winit's existing Wayland display handle.
|
||||
///
|
||||
/// See `weft-servo-shell/src/shell_client.rs` for the rationale on
|
||||
/// `Backend::from_foreign_display`. The `surface_ptr` is the `wl_surface*`
|
||||
/// from the same winit connection, enabling the compositor to associate the
|
||||
/// application window with the rendered surface.
|
||||
#[cfg(feature = "servo-embed")]
|
||||
pub fn connect_as_app_with_display(
|
||||
app_id: &str,
|
||||
session_id: u64,
|
||||
display_ptr: *mut std::ffi::c_void,
|
||||
surface_ptr: *mut std::ffi::c_void,
|
||||
) -> anyhow::Result<Self> {
|
||||
use wayland_client::Proxy;
|
||||
use wayland_client::backend::{Backend, ObjectId};
|
||||
|
||||
// Safety: display_ptr is winit's wl_display*, valid for the event loop lifetime.
|
||||
let conn = unsafe {
|
||||
Connection::from_backend(Backend::from_foreign_display(display_ptr as *mut _))
|
||||
};
|
||||
|
||||
let mut event_queue = conn.new_event_queue::<AppData>();
|
||||
let qh = event_queue.handle();
|
||||
|
|
@ -148,13 +165,20 @@ impl ShellClient {
|
|||
"zweft_shell_manager_v1 not advertised; WEFT compositor must be running"
|
||||
);
|
||||
|
||||
// Safety: surface_ptr is winit's wl_surface* on the same wl_display connection.
|
||||
let surface = unsafe {
|
||||
let id = ObjectId::from_ptr(WlSurface::interface(), surface_ptr as *mut _)
|
||||
.context("wl_surface ObjectId import")?;
|
||||
WlSurface::from_id(&conn, id).context("wl_surface from_id")?
|
||||
};
|
||||
|
||||
let manager = data.manager.as_ref().unwrap();
|
||||
let title = format!("{app_id}/{session_id}");
|
||||
let window = manager.create_window(
|
||||
app_id.to_string(),
|
||||
title,
|
||||
"application".to_string(),
|
||||
None::<&WlSurface>,
|
||||
Some(&surface),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ name = "weft-servo-shell"
|
|||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
# Enable actual Servo rendering. Requires manually adding the deps listed in
|
||||
# SERVO_PIN.md to this file before building; they are not included here to
|
||||
# avoid pulling the Servo monorepo (~1 GB) into every `cargo check` cycle.
|
||||
servo-embed = []
|
||||
# Enable actual Servo rendering. Deps are declared as optional below and only
|
||||
# fetched when this feature is active. The Servo monorepo (~1 GB) is not
|
||||
# downloaded during a plain `cargo check` or `cargo build` without this feature.
|
||||
servo-embed = ["dep:servo", "dep:winit", "dep:softbuffer"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
|
|
@ -24,3 +24,18 @@ wayland-scanner = "0.31"
|
|||
bitflags = "2"
|
||||
serde_json = "1"
|
||||
tungstenite = "0.24"
|
||||
|
||||
[dependencies.servo]
|
||||
git = "https://github.com/marcoallegretti/servo"
|
||||
branch = "servo-weft"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.winit]
|
||||
version = "0.30"
|
||||
optional = true
|
||||
features = ["wayland"]
|
||||
|
||||
[dependencies.softbuffer]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use winit::{
|
|||
event::{ElementState, MouseButton, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
|
||||
keyboard::ModifiersState,
|
||||
platform::wayland::{ActiveEventLoopExtWayland, WindowExtWayland},
|
||||
window::{Window, WindowAttributes, WindowId},
|
||||
};
|
||||
|
||||
|
|
@ -102,7 +103,6 @@ impl App {
|
|||
url: ServoUrl,
|
||||
waker: WeftEventLoopWaker,
|
||||
ws_port: u16,
|
||||
shell_client: Option<crate::shell_client::ShellClient>,
|
||||
) -> Self {
|
||||
Self {
|
||||
url,
|
||||
|
|
@ -116,7 +116,7 @@ impl App {
|
|||
shutting_down: false,
|
||||
modifiers: ModifiersState::default(),
|
||||
cursor_pos: servo::euclid::default::Point2D::zero(),
|
||||
shell_client,
|
||||
shell_client: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +164,17 @@ impl ApplicationHandler<ServoWake> for App {
|
|||
let size = window.inner_size();
|
||||
self.window = Some(Arc::clone(&window));
|
||||
|
||||
if self.shell_client.is_none() {
|
||||
if let (Some(disp), Some(surf)) =
|
||||
(event_loop.wayland_display(), window.wayland_surface())
|
||||
{
|
||||
match crate::shell_client::ShellClient::connect_with_display(disp, surf) {
|
||||
Ok(sc) => self.shell_client = Some(sc),
|
||||
Err(e) => tracing::warn!(error = %e, "shell protocol unavailable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let servo = ServoBuilder::default()
|
||||
.event_loop_waker(Box::new(self.waker.clone()))
|
||||
.build();
|
||||
|
|
@ -380,7 +391,6 @@ fn app_store_roots() -> Vec<PathBuf> {
|
|||
pub fn run(
|
||||
html_path: &Path,
|
||||
ws_port: u16,
|
||||
shell_client: Option<crate::shell_client::ShellClient>,
|
||||
) -> anyhow::Result<()> {
|
||||
let url_str = format!("file://{}", html_path.display());
|
||||
let raw_url =
|
||||
|
|
@ -395,7 +405,7 @@ pub fn run(
|
|||
proxy: Arc::new(Mutex::new(event_loop.create_proxy())),
|
||||
};
|
||||
|
||||
let mut app = App::new(url, waker, ws_port, shell_client);
|
||||
let mut app = App::new(url, waker, ws_port);
|
||||
event_loop
|
||||
.run_app(&mut app)
|
||||
.map_err(|e| anyhow::anyhow!("event loop run: {e}"))
|
||||
|
|
|
|||
|
|
@ -32,18 +32,7 @@ fn run() -> anyhow::Result<()> {
|
|||
let ws_port = appd_ws_port();
|
||||
tracing::info!(ws_port, "appd WebSocket port");
|
||||
|
||||
let shell = match shell_client::ShellClient::connect() {
|
||||
Ok(c) => {
|
||||
tracing::info!("shell window registered with compositor");
|
||||
Some(c)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "shell protocol unavailable; continuing without compositor registration");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
embed_servo(&wayland_display, &html_path, ws_port, shell)
|
||||
embed_servo(&wayland_display, &html_path, ws_port)
|
||||
}
|
||||
|
||||
fn system_ui_html_path() -> anyhow::Result<PathBuf> {
|
||||
|
|
@ -84,14 +73,12 @@ fn embed_servo(
|
|||
_wayland_display: &str,
|
||||
html_path: &std::path::Path,
|
||||
ws_port: u16,
|
||||
shell_client: Option<shell_client::ShellClient>,
|
||||
) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "servo-embed")]
|
||||
return embedder::run(html_path, ws_port, shell_client);
|
||||
return embedder::run(html_path, ws_port);
|
||||
|
||||
#[cfg(not(feature = "servo-embed"))]
|
||||
{
|
||||
let _ = shell_client;
|
||||
tracing::warn!(
|
||||
path = %html_path.display(),
|
||||
ws_port,
|
||||
|
|
|
|||
|
|
@ -132,14 +132,14 @@ impl Dispatch<ZweftShellWindowV1, ()> for AppData {
|
|||
tracing::trace!(tv_sec, tv_nsec, refresh, "shell presentation feedback");
|
||||
}
|
||||
zweft_shell_window_v1::Event::NavigationGesture {
|
||||
r#type,
|
||||
_type,
|
||||
fingers,
|
||||
dx,
|
||||
dy,
|
||||
} => {
|
||||
tracing::debug!(r#type, fingers, dx, dy, "navigation gesture from compositor");
|
||||
tracing::debug!(_type, fingers, dx, dy, "navigation gesture from compositor");
|
||||
state.pending_gestures.push(PendingGesture {
|
||||
gesture_type: r#type,
|
||||
gesture_type: _type,
|
||||
fingers,
|
||||
dx,
|
||||
dy,
|
||||
|
|
@ -157,9 +157,23 @@ pub struct ShellClient {
|
|||
}
|
||||
|
||||
impl ShellClient {
|
||||
pub fn connect() -> anyhow::Result<Self> {
|
||||
let conn =
|
||||
Connection::connect_to_env().context("failed to connect to Wayland compositor")?;
|
||||
/// Connect to the compositor using winit's existing Wayland display handle.
|
||||
///
|
||||
/// Using `Backend::from_foreign_display` shares winit's `wl_display` connection so
|
||||
/// that `surface_ptr` (a `wl_surface*` owned by winit) is a valid object on the same
|
||||
/// client, enabling the compositor to link the shell window to the actual surface.
|
||||
#[cfg(feature = "servo-embed")]
|
||||
pub fn connect_with_display(
|
||||
display_ptr: *mut std::ffi::c_void,
|
||||
surface_ptr: *mut std::ffi::c_void,
|
||||
) -> anyhow::Result<Self> {
|
||||
use wayland_client::Proxy;
|
||||
use wayland_client::backend::{Backend, ObjectId};
|
||||
|
||||
// Safety: display_ptr is winit's wl_display*, valid for the event loop lifetime.
|
||||
let conn = unsafe {
|
||||
Connection::from_backend(Backend::from_foreign_display(display_ptr as *mut _))
|
||||
};
|
||||
|
||||
let mut event_queue = conn.new_event_queue::<AppData>();
|
||||
let qh = event_queue.handle();
|
||||
|
|
@ -176,12 +190,19 @@ impl ShellClient {
|
|||
"zweft_shell_manager_v1 not advertised; WEFT compositor must be running"
|
||||
);
|
||||
|
||||
// Safety: surface_ptr is winit's wl_surface* on the same wl_display connection.
|
||||
let surface = unsafe {
|
||||
let id = ObjectId::from_ptr(WlSurface::interface(), surface_ptr as *mut _)
|
||||
.context("wl_surface ObjectId import")?;
|
||||
WlSurface::from_id(&conn, id).context("wl_surface from_id")?
|
||||
};
|
||||
|
||||
let manager = data.manager.as_ref().unwrap();
|
||||
let window = manager.create_window(
|
||||
"org.weft.system.shell".to_string(),
|
||||
"WEFT Shell".to_string(),
|
||||
"panel".to_string(),
|
||||
None::<&WlSurface>,
|
||||
Some(&surface),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
|
|
|||
Loading…
Reference in a new issue