From 335677bbcd1b4ab34275992e7ee31effa6629f20 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 16 Oct 2022 21:14:12 +0200 Subject: [PATCH] wayland: implement xwayland_shell_v1 --- src/client.rs | 9 +- src/globals.rs | 2 + src/ifs/wl_surface.rs | 26 ++++ src/ifs/wl_surface/x_surface.rs | 15 ++- .../x_surface/xwayland_surface_v1.rs | 77 +++++++++++ src/ifs/wl_surface/x_surface/xwindow.rs | 2 + src/ifs/wl_surface/xwayland_shell_v1.rs | 120 ++++++++++++++++++ src/xwayland.rs | 3 +- src/xwayland/xwm.rs | 69 +++++++++- wire/xwayland_shell_v1.txt | 8 ++ wire/xwayland_surface_v1.txt | 8 ++ 11 files changed, 332 insertions(+), 7 deletions(-) create mode 100644 src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs create mode 100644 src/ifs/wl_surface/xwayland_shell_v1.rs create mode 100644 wire/xwayland_shell_v1.txt create mode 100644 wire/xwayland_surface_v1.txt diff --git a/src/client.rs b/src/client.rs index c03db579..a329c2a7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,14 +2,14 @@ use { crate::{ async_engine::SpawnedFuture, client::{error::LookupError, objects::Objects}, - ifs::{wl_display::WlDisplay, wl_registry::WlRegistry}, + ifs::{wl_display::WlDisplay, wl_registry::WlRegistry, wl_surface::WlSurface}, leaks::Tracker, object::{Interface, Object, ObjectId, WL_DISPLAY_ID}, state::State, utils::{ asyncevent::AsyncEvent, buffd::{MsgFormatter, MsgParser, MsgParserError, OutBufferSwapchain}, - copyhashmap::Locked, + copyhashmap::{CopyHashMap, Locked}, errorfmt::ErrorFmt, numcell::NumCell, trim::AsciiTrim, @@ -145,6 +145,8 @@ impl Clients { pid_info: get_pid_info(uid, pid), serials: Default::default(), symmetric_delete: Cell::new(false), + last_xwayland_serial: Cell::new(0), + surfaces_by_xwayland_serial: Default::default(), }); track!(data, data); let display = Rc::new(WlDisplay::new(&data)); @@ -214,6 +216,7 @@ impl Drop for ClientHolder { self.data.objects.destroy(); self.data.flush_request.clear(); self.data.shutdown.clear(); + self.data.surfaces_by_xwayland_serial.clear(); } } @@ -251,6 +254,8 @@ pub struct Client { pub pid_info: PidInfo, pub serials: RefCell>, pub symmetric_delete: Cell, + pub last_xwayland_serial: Cell, + pub surfaces_by_xwayland_serial: CopyHashMap>, } pub const NUM_CACHED_SERIAL_RANGES: usize = 64; diff --git a/src/globals.rs b/src/globals.rs index 140f394e..53c1cdbc 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -19,6 +19,7 @@ use { }, wl_shm::WlShmGlobal, wl_subcompositor::WlSubcompositorGlobal, + wl_surface::xwayland_shell_v1::XwaylandShellV1Global, wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1Global, wp_presentation::WpPresentationGlobal, wp_viewporter::WpViewporterGlobal, @@ -152,6 +153,7 @@ impl Globals { add_singleton!(WpViewporterGlobal); add_singleton!(WpFractionalScaleManagerV1Global); add_singleton!(ZwpPointerConstraintsV1Global); + add_singleton!(XwaylandShellV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 0c8ebb26..bcd32402 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -5,6 +5,7 @@ pub mod wp_fractional_scale_v1; pub mod wp_viewport; pub mod x_surface; pub mod xdg_surface; +pub mod xwayland_shell_v1; pub mod zwlr_layer_surface_v1; pub mod zwp_idle_inhibitor_v1; @@ -50,6 +51,7 @@ use { }, wire::{wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id}, xkbcommon::ModifierState, + xwayland::XWaylandEvent, }, ahash::AHashMap, std::{ @@ -258,6 +260,7 @@ pub struct WlSurface { output: CloneCell>, fractional_scale: CloneCell>>, pub constraints: SmallMap, 1>, + xwayland_serial: Cell>, } impl Debug for WlSurface { @@ -349,6 +352,7 @@ struct PendingState { dst_size: Cell>>, scale: Cell>, transform: Cell>, + xwayland_serial: Cell>, } #[derive(Default)] @@ -400,6 +404,7 @@ impl WlSurface { output: CloneCell::new(client.state.dummy_output.get().unwrap()), fractional_scale: Default::default(), constraints: Default::default(), + xwayland_serial: Default::default(), } } @@ -410,6 +415,7 @@ impl WlSurface { let xsurface = Rc::new(XSurface { surface: self.clone(), xwindow: Default::default(), + xwayland_surface: Default::default(), tracker: Default::default(), }); track!(self.client, xsurface); @@ -449,6 +455,10 @@ impl WlSurface { self.toplevel.get() } + pub fn xwayland_serial(&self) -> Option { + self.xwayland_serial.get() + } + fn set_absolute_position(&self, x1: i32, y1: i32) { self.buffer_abs_pos .set(self.buffer_abs_pos.get().at_point(x1, y1)); @@ -621,6 +631,11 @@ impl WlSurface { buffer.send_release(); } } + if let Some(xwayland_serial) = self.xwayland_serial.get() { + self.client + .surfaces_by_xwayland_serial + .remove(&xwayland_serial); + } self.frame_requests.borrow_mut().clear(); self.toplevel.set(None); self.client.remove_obj(self)?; @@ -856,6 +871,17 @@ impl WlSurface { self.opaque_region.set(region); } } + if let Some(xwayland_serial) = self.pending.xwayland_serial.take() { + self.xwayland_serial.set(Some(xwayland_serial)); + self.client + .surfaces_by_xwayland_serial + .set(xwayland_serial, self.clone()); + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::SurfaceSerialAssigned(self.id)); + } if self.need_extents_update.get() { self.calculate_extents(); } diff --git a/src/ifs/wl_surface/x_surface.rs b/src/ifs/wl_surface/x_surface.rs index 76e6d342..722264ab 100644 --- a/src/ifs/wl_surface/x_surface.rs +++ b/src/ifs/wl_surface/x_surface.rs @@ -1,6 +1,9 @@ use { crate::{ - ifs::wl_surface::{x_surface::xwindow::Xwindow, SurfaceExt, WlSurface, WlSurfaceError}, + ifs::wl_surface::{ + x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow}, + SurfaceExt, WlSurface, WlSurfaceError, + }, leaks::Tracker, tree::ToplevelNode, utils::clonecell::CloneCell, @@ -9,11 +12,13 @@ use { std::rc::Rc, }; +pub mod xwayland_surface_v1; pub mod xwindow; pub struct XSurface { pub surface: Rc, pub xwindow: CloneCell>>, + pub xwayland_surface: CloneCell>>, pub tracker: Tracker, } @@ -25,6 +30,9 @@ impl SurfaceExt for XSurface { } fn on_surface_destroy(&self) -> Result<(), WlSurfaceError> { + if self.xwayland_surface.get().is_some() { + return Err(WlSurfaceError::ReloObjectStillExists); + } self.surface.unset_ext(); if let Some(xwindow) = self.xwindow.take() { xwindow.tl_destroy(); @@ -35,7 +43,10 @@ impl SurfaceExt for XSurface { .state .xwayland .queue - .push(XWaylandEvent::SurfaceDestroyed(self.surface.id)); + .push(XWaylandEvent::SurfaceDestroyed( + self.surface.id, + self.surface.xwayland_serial.get(), + )); } Ok(()) } diff --git a/src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs b/src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs new file mode 100644 index 00000000..ea6b16eb --- /dev/null +++ b/src/ifs/wl_surface/x_surface/xwayland_surface_v1.rs @@ -0,0 +1,77 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_surface::{x_surface::XSurface, WlSurfaceError}, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{xwayland_surface_v1::*, XwaylandSurfaceV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct XwaylandSurfaceV1 { + pub id: XwaylandSurfaceV1Id, + pub client: Rc, + pub x: Rc, + pub tracker: Tracker, +} + +impl XwaylandSurfaceV1 { + fn set_serial(&self, parser: MsgParser<'_, '_>) -> Result<(), XwaylandSurfaceV1Error> { + let req: SetSerial = self.client.parse(self, parser)?; + if self.x.surface.xwayland_serial.get().is_some() { + return Err(XwaylandSurfaceV1Error::SerialAlreadySet); + } + let serial = req.serial_lo as u64 | ((req.serial_hi as u64) << 32); + if self.client.last_xwayland_serial.get() >= serial { + return Err(XwaylandSurfaceV1Error::NonMonotonicSerial); + } + self.client.last_xwayland_serial.set(serial); + self.x.surface.pending.xwayland_serial.set(Some(serial)); + Ok(()) + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), XwaylandSurfaceV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + self.x.xwayland_surface.set(None); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + XwaylandSurfaceV1; + + SET_SERIAL => set_serial, + DESTROY => destroy, +} + +impl Object for XwaylandSurfaceV1 { + fn num_requests(&self) -> u32 { + DESTROY + 1 + } + + fn break_loops(&self) { + self.x.xwayland_surface.set(None); + } +} + +simple_add_obj!(XwaylandSurfaceV1); + +#[derive(Debug, Error)] +pub enum XwaylandSurfaceV1Error { + #[error("The serial for this surface is already set")] + SerialAlreadySet, + #[error("The serial is not larger than the previously used serial")] + NonMonotonicSerial, + #[error(transparent)] + WlSurfaceError(#[from] WlSurfaceError), + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(XwaylandSurfaceV1Error, MsgParserError); +efrom!(XwaylandSurfaceV1Error, ClientError); diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index 48064690..256b086b 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -117,6 +117,7 @@ pub struct XwindowData { pub window_id: u32, pub client: Rc, pub surface_id: Cell>, + pub surface_serial: Cell>, pub window: CloneCell>>, pub info: XwindowInfo, pub children: CopyHashMap>, @@ -152,6 +153,7 @@ impl XwindowData { window_id: event.window, client: client.clone(), surface_id: Cell::new(None), + surface_serial: Cell::new(None), window: Default::default(), info: XwindowInfo { override_redirect: Cell::new(event.override_redirect != 0), diff --git a/src/ifs/wl_surface/xwayland_shell_v1.rs b/src/ifs/wl_surface/xwayland_shell_v1.rs new file mode 100644 index 00000000..c4920a5f --- /dev/null +++ b/src/ifs/wl_surface/xwayland_shell_v1.rs @@ -0,0 +1,120 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wl_surface::{x_surface::xwayland_surface_v1::XwaylandSurfaceV1, WlSurfaceError}, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{xwayland_shell_v1::*, WlSurfaceId, XwaylandShellV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct XwaylandShellV1Global { + name: GlobalName, +} + +pub struct XwaylandShellV1 { + id: XwaylandShellV1Id, + client: Rc, + pub version: u32, + pub tracker: Tracker, +} + +impl XwaylandShellV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: XwaylandShellV1Id, + client: &Rc, + version: u32, + ) -> Result<(), XwaylandShellV1Error> { + let obj = Rc::new(XwaylandShellV1 { + id, + client: client.clone(), + version, + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} +impl XwaylandShellV1 { + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), XwaylandShellV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_xwayland_surface(&self, parser: MsgParser<'_, '_>) -> Result<(), XwaylandShellV1Error> { + let req: GetXwaylandSurface = self.client.parse(self, parser)?; + let surface = self.client.lookup(req.surface)?; + let xsurface = surface.get_xsurface()?; + if xsurface.xwayland_surface.get().is_some() { + return Err(XwaylandShellV1Error::AlreadyAttached(surface.id)); + } + let xws = Rc::new(XwaylandSurfaceV1 { + id: req.id, + client: self.client.clone(), + x: xsurface, + tracker: Default::default(), + }); + track!(self.client, xws); + xws.x.xwayland_surface.set(Some(xws.clone())); + self.client.add_client_obj(&xws)?; + Ok(()) + } +} + +global_base!(XwaylandShellV1Global, XwaylandShellV1, XwaylandShellV1Error); + +impl Global for XwaylandShellV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn xwayland_only(&self) -> bool { + true + } +} + +simple_add_global!(XwaylandShellV1Global); + +object_base! { + XwaylandShellV1; + + DESTROY => destroy, + GET_XWAYLAND_SURFACE => get_xwayland_surface, +} + +impl Object for XwaylandShellV1 { + fn num_requests(&self) -> u32 { + GET_XWAYLAND_SURFACE + 1 + } +} + +simple_add_obj!(XwaylandShellV1); + +#[derive(Debug, Error)] +pub enum XwaylandShellV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error("The `wl_surface` {0} already has an extension object")] + AlreadyAttached(WlSurfaceId), + #[error(transparent)] + WlSurfaceError(#[from] WlSurfaceError), +} +efrom!(XwaylandShellV1Error, ClientError); +efrom!(XwaylandShellV1Error, MsgParserError); diff --git a/src/xwayland.rs b/src/xwayland.rs index 6f9ae204..827e96a8 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -271,7 +271,8 @@ async fn log_xwayland(state: Rc, stderr: OwnedFd) { pub enum XWaylandEvent { SurfaceCreated(WlSurfaceId), - SurfaceDestroyed(WlSurfaceId), + SurfaceSerialAssigned(WlSurfaceId), + SurfaceDestroyed(WlSurfaceId, Option), Configure(Rc), Activate(Rc), ActivateRoot, diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 7361ff8f..c513359b 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -124,6 +124,7 @@ atoms! { WINDOW, _WL_SELECTION, WL_SURFACE_ID, + WL_SURFACE_SERIAL, WM_CHANGE_STATE, WM_DELETE_WINDOW, WM_HINTS, @@ -236,6 +237,8 @@ pub struct Wm { client: Rc, windows: AHashMap>, windows_by_surface_id: AHashMap>, + windows_by_surface_serial: AHashMap>, + last_surface_serial: u64, focus_window: Option>, last_input_serial: u64, atom_cache: AHashMap, @@ -514,6 +517,8 @@ impl Wm { client, windows: Default::default(), windows_by_surface_id: Default::default(), + windows_by_surface_serial: Default::default(), + last_surface_serial: 0, focus_window: Default::default(), last_input_serial: 0, atom_cache: Default::default(), @@ -592,8 +597,13 @@ impl Wm { XWaylandEvent::SurfaceCreated(event) => { self.handle_xwayland_surface_created(event).await } + XWaylandEvent::SurfaceSerialAssigned(event) => { + self.handle_xwayland_surface_serial_assigned(event).await + } XWaylandEvent::Configure(event) => self.handle_xwayland_configure(event).await, - XWaylandEvent::SurfaceDestroyed(event) => self.handle_xwayland_surface_destroyed(event), + XWaylandEvent::SurfaceDestroyed(surface_id, serial) => { + self.handle_xwayland_surface_destroyed(surface_id, serial) + } XWaylandEvent::Activate(window) => { self.activate_window(Some(&window), Initiator::Wayland) .await @@ -1433,8 +1443,27 @@ impl Wm { self.create_window(&data, surface).await; } - fn handle_xwayland_surface_destroyed(&mut self, surface: WlSurfaceId) { + async fn handle_xwayland_surface_serial_assigned(&mut self, surface: WlSurfaceId) { + let surface = match self.client.lookup(surface) { + Ok(s) => s, + _ => return, + }; + let serial = match surface.xwayland_serial() { + Some(s) => s, + _ => return, + }; + let data = match self.windows_by_surface_serial.get(&serial) { + Some(w) => w.clone(), + _ => return, + }; + self.create_window(&data, surface).await; + } + + fn handle_xwayland_surface_destroyed(&mut self, surface: WlSurfaceId, serial: Option) { self.windows_by_surface_id.remove(&surface); + if let Some(serial) = serial { + self.windows_by_surface_serial.remove(&serial); + } } async fn handle_event(&mut self, event: &Event) { @@ -1895,6 +1924,9 @@ impl Wm { if let Some(sid) = data.surface_id.take() { self.windows_by_surface_id.remove(&sid); } + if let Some(serial) = data.surface_serial.take() { + self.windows_by_surface_serial.remove(&serial); + } if let Some(window) = data.window.take() { window.destroy(); } @@ -1961,6 +1993,8 @@ impl Wm { self.handle_wm_change_state(&event).await?; } else if event.ty == self.atoms._NET_WM_MOVERESIZE { self.handle_net_wm_moveresize(&event).await?; + } else if event.ty == self.atoms.WL_SURFACE_SERIAL { + self.handle_wl_surface_serial(&event).await?; } Ok(()) } @@ -2334,6 +2368,37 @@ impl Wm { Ok(()) } + async fn handle_wl_surface_serial( + &mut self, + event: &ClientMessage<'_>, + ) -> Result<(), XWaylandError> { + let serial = event.data[0] as u64 | ((event.data[1] as u64) << 32); + if serial <= self.last_surface_serial { + log::error!( + "Surface serial is not monotonic: {} <= {}", + serial, + self.last_surface_serial + ); + return Ok(()); + } + self.last_surface_serial = serial; + let data = match self.windows.get(&event.window) { + Some(d) => d.clone(), + _ => return Ok(()), + }; + if let Some(old) = data.surface_serial.replace(Some(serial)) { + self.windows_by_surface_serial.remove(&old); + } + if let Some(old) = data.window.take() { + old.break_loops(); + } + self.windows_by_surface_serial.insert(serial, data.clone()); + if let Some(surface) = self.client.surfaces_by_xwayland_serial.get(&serial) { + self.create_window(&data, surface).await; + } + Ok(()) + } + async fn handle_wl_surface_id( &mut self, event: &ClientMessage<'_>, diff --git a/wire/xwayland_shell_v1.txt b/wire/xwayland_shell_v1.txt new file mode 100644 index 00000000..fb53c9e9 --- /dev/null +++ b/wire/xwayland_shell_v1.txt @@ -0,0 +1,8 @@ +# requests + +msg destroy = 0 { } + +msg get_xwayland_surface = 1 { + id: id(xwayland_surface_v1), + surface: id(wl_surface), +} diff --git a/wire/xwayland_surface_v1.txt b/wire/xwayland_surface_v1.txt new file mode 100644 index 00000000..d8458c92 --- /dev/null +++ b/wire/xwayland_surface_v1.txt @@ -0,0 +1,8 @@ +# requests + +msg set_serial = 0 { + serial_lo: u32, + serial_hi: u32, +} + +msg destroy = 1 { }