diff --git a/src/lib.rs b/src/lib.rs index 92f6235..69568d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ mod server; pub mod xstate; -use crate::server::{PendingSurfaceState, ServerState}; +use crate::server::{EarlyConnection, PendingSurfaceState, ServerState}; use crate::xstate::{RealConnection, XState}; use log::{error, info}; use rustix::event::{poll, PollFd, PollFlags}; @@ -16,7 +16,6 @@ use xcb::x; pub trait XConnection: Sized + 'static { type X11Selection: X11Selection; - fn root_window(&self) -> x::Window; fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState); fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool); fn focus_window(&mut self, window: x::Window, output_name: Option); @@ -30,6 +29,7 @@ pub trait X11Selection { fn write_to(&self, mime: &str, pipe: WritePipe); } +type EarlyServerState = ServerState::X11Selection>>; type RealServerState = ServerState; pub trait RunData { @@ -58,8 +58,6 @@ pub fn main(mut data: impl RunData) -> Option<()> { let dh = display.handle(); data.created_server(); - let mut server_state = RealServerState::new(dh, data.server()); - let (xsock_wl, xsock_xwl) = UnixStream::pair().unwrap(); // Prevent creation of new Xwayland command from closing fd rustix::io::fcntl_setfd(&xsock_xwl, rustix::io::FdFlags::empty()).unwrap(); @@ -136,11 +134,10 @@ pub fn main(mut data: impl RunData) -> Option<()> { } }; + let mut server_state = EarlyServerState::new(dh, data.server()); server_state.connect(connection); server_state.run(); - let mut xstate: Option = None; - // Remove the lifetimes on our fds to avoid borrowing issues, since we know they will exist for // the rest of our program anyway let server_fd = unsafe { BorrowedFd::borrow_raw(server_state.clientside_fd().as_raw_fd()) }; @@ -154,18 +151,74 @@ pub fn main(mut data: impl RunData) -> Option<()> { PollFd::from_borrowed_fd(server_fd, PollFlags::IN), PollFd::new(&xsock_wl, PollFlags::IN), PollFd::from_borrowed_fd(display_fd, PollFlags::IN), - PollFd::new(&ready_rx, PollFlags::IN), PollFd::new(&quit_rx, PollFlags::IN), + PollFd::new(&ready_rx, PollFlags::IN), ]; - let mut ready = false; loop { match poll(&mut fds, -1) { Ok(_) => { if !fds[3].revents().is_empty() { - ready = true; + let status = xwayland_exit_code(&mut quit_rx); + if *status != ExitStatus::default() { + error!("Xwayland exited early with {status}"); + } + return None; } if !fds[4].revents().is_empty() { + break; + } + } + Err(other) => panic!("Poll failed: {other:?}"), + } + + display.dispatch_clients(server_state.inner_mut()).unwrap(); + server_state.run(); + display.flush_clients().unwrap(); + } + + let mut xstate: Option = None; + let xstate = xstate.insert(XState::new(xsock_wl.as_fd())); + let mut reader = BufReader::new(&ready_rx); + { + let mut display = String::new(); + reader.read_line(&mut display).unwrap(); + display.pop(); + display.insert(0, ':'); + info!("Connected to Xwayland on {display}"); + data.xwayland_ready(display, xwl_pid); + } + let mut server_state = xstate.server_state_setup(server_state); + + #[cfg(feature = "systemd")] + { + match sd_notify::notify(true, &[sd_notify::NotifyState::Ready]) { + Ok(()) => info!("Successfully notified systemd of ready state."), + Err(e) => log::warn!("Systemd notify failed: {e:?}"), + } + } + + #[cfg(not(feature = "systemd"))] + info!("Systemd support disabled."); + + loop { + xstate.handle_events(&mut server_state); + + display.dispatch_clients(server_state.inner_mut()).unwrap(); + server_state.run(); + display.flush_clients().unwrap(); + + if let Some(sel) = server_state.new_selection() { + xstate.set_clipboard(sel); + } + + if let Some(scale) = server_state.new_global_scale() { + xstate.update_global_scale(scale); + } + + match poll(&mut fds, -1) { + Ok(_) => { + if !fds[3].revents().is_empty() { let status = xwayland_exit_code(&mut quit_rx); if *status != ExitStatus::default() { error!("Xwayland exited early with {status}"); @@ -175,46 +228,5 @@ pub fn main(mut data: impl RunData) -> Option<()> { } Err(other) => panic!("Poll failed: {other:?}"), } - - if xstate.is_none() && ready { - let xstate = xstate.insert(XState::new(xsock_wl.as_fd())); - let mut reader = BufReader::new(&ready_rx); - let mut display = String::new(); - reader.read_line(&mut display).unwrap(); - display.pop(); - display.insert(0, ':'); - info!("Connected to Xwayland on {display}"); - data.xwayland_ready(display, xwl_pid); - xstate.server_state_setup(&mut server_state); - - #[cfg(feature = "systemd")] - { - match sd_notify::notify(true, &[sd_notify::NotifyState::Ready]) { - Ok(()) => info!("Successfully notified systemd of ready state."), - Err(e) => log::warn!("Systemd notify failed: {e:?}"), - } - } - - #[cfg(not(feature = "systemd"))] - info!("Systemd support disabled."); - } - - if let Some(xstate) = &mut xstate { - xstate.handle_events(&mut server_state); - } - - display.dispatch_clients(server_state.inner_mut()).unwrap(); - server_state.run(); - display.flush_clients().unwrap(); - - if let Some(xstate) = &mut xstate { - if let Some(sel) = server_state.new_selection() { - xstate.set_clipboard(sel); - } - - if let Some(scale) = server_state.new_global_scale() { - xstate.update_global_scale(scale); - } - } } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 902fe2b..542368e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -393,6 +393,33 @@ struct GlobalOutputOffset { y: GlobalOutputOffsetDimension, } +/// The state of the X11 connection before XState has been fully initialized. +/// It implements XConnection minimally, gracefully doing nothing but logging the called functions. +pub struct EarlyConnection { + _p: std::marker::PhantomData, +} +impl XConnection for EarlyConnection { + type X11Selection = S; + fn focus_window(&mut self, _: x::Window, _: Option) { + debug!("could not focus window without XWayland initialized"); + } + fn close_window(&mut self, _: x::Window) { + debug!("could not close window without XWayland initialized"); + } + fn unmap_window(&mut self, _: x::Window) { + debug!("could not unmap window without XWayland initialized"); + } + fn raise_to_top(&mut self, _: x::Window) { + debug!("could not raise window to top without XWayland initialized"); + } + fn set_fullscreen(&mut self, _: x::Window, _: bool) { + debug!("could not toggle fullscreen without XWayland initialized"); + } + fn set_window_dims(&mut self, _: x::Window, _: crate::server::PendingSurfaceState) { + debug!("could not set window dimensions without XWayland initialized"); + } +} + pub struct ServerState { inner: InnerServerState, pub connection: Option, @@ -425,7 +452,7 @@ pub struct InnerServerState { new_scale: Option, } -impl ServerState { +impl ServerState> { pub fn new(dh: DisplayHandle, server_connection: Option) -> Self { let connection = if let Some(stream) = server_connection { Connection::from_socket(stream).unwrap() @@ -460,7 +487,7 @@ impl ServerState { let clipboard_data = manager.map(|manager| ClipboardData { manager, device: None, - source: None::>, + source: None::>, }); let activation_state = ActivationState::bind(&global_list, &qh) @@ -476,10 +503,10 @@ impl ServerState { }) .ok(); - dh.create_global::, XwaylandShellV1, _>(1, ()); + dh.create_global::, XwaylandShellV1, _>(1, ()); global_list .contents() - .with_list(|globals| handle_globals::(&dh, globals)); + .with_list(|globals| handle_globals::(&dh, globals)); let inner = InnerServerState { windows: HashMap::new(), @@ -516,7 +543,19 @@ impl ServerState { }; Self { inner, - connection: None, + connection: Some(EarlyConnection { + _p: std::marker::PhantomData, + }), + } + } + + pub fn upgrade_connection(self, connection: C) -> ServerState + where + C: XConnection, + { + ServerState { + inner: self.inner, + connection: Some(connection), } } } diff --git a/src/server/tests.rs b/src/server/tests.rs index 6ee35d6..212fdd3 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -1,5 +1,6 @@ -use super::{InnerServerState, ServerState, WindowDims}; +use super::{EarlyConnection, InnerServerState, ServerState, WindowDims}; use crate::xstate::{SetState, WinSize, WmName}; +use crate::XConnection; use rustix::event::{poll, PollFd, PollFlags}; use std::collections::HashMap; use std::io::Write; @@ -152,8 +153,8 @@ struct WindowData { fullscreen: bool, dims: WindowDims, } +#[derive(Default)] struct FakeXConnection { - root: Window, focused_window: Option, windows: HashMap, } @@ -174,16 +175,6 @@ impl FakeXConnection { } } -impl Default for FakeXConnection { - fn default() -> Self { - Self { - root: unsafe { Window::new(9001) }, - focused_window: None, - windows: HashMap::new(), - } - } -} - type FakeX11Selection = Vec; impl crate::X11Selection for Vec { fn mime_types(&self) -> Vec<&str> { @@ -206,10 +197,6 @@ impl crate::X11Selection for Vec { impl super::XConnection for FakeXConnection { type X11Selection = FakeX11Selection; - fn root_window(&self) -> Window { - self.root - } - #[track_caller] fn close_window(&mut self, window: Window) { log::debug!("closing window {window:?}"); @@ -252,15 +239,16 @@ impl super::XConnection for FakeXConnection { } } -type FakeServerState = ServerState; +type EarlyServerState = ServerState>; +type EarlyTestFixture = TestFixture>; -struct TestFixture { +struct TestFixture { testwl: testwl::Server, - satellite: FakeServerState, + satellite: ServerState, /// Our connection to satellite - i.e., where Xwayland sends requests to xwls_connection: Arc, /// Satellite's display - must dispatch this for our server state to advance - xwls_display: Display>, + xwls_display: Display>, surface_serial: u64, registry: TestObject, } @@ -336,38 +324,24 @@ impl PreConnectFn for () {} struct SetupOptions { pre_connect: Option, - connect_x: bool, -} - -impl SetupOptions<()> { - fn dont_connect_x() -> Self { - Self { - pre_connect: None, - connect_x: false, - } - } } impl SetupOptions { fn pre_connect(pre_connect: F) -> Self { Self { pre_connect: Some(pre_connect), - connect_x: true, } } } impl Default for SetupOptions<()> { fn default() -> Self { - Self { - pre_connect: None, - connect_x: true, - } + Self { pre_connect: None } } } -impl TestFixture { - fn new_with_options(options: SetupOptions) -> Self { +impl EarlyTestFixture { + fn new_early_with_options(options: SetupOptions) -> Self { INIT.call_once(|| { env_logger::builder() .is_test(true) @@ -391,23 +365,19 @@ impl TestFixture { testwl.dispatch(); testwl }); - let mut satellite = FakeServerState::new(display.handle(), Some(client_s)); - let testwl = thread.join().unwrap(); let (fake_client, xwls_server) = UnixStream::pair().unwrap(); + let mut satellite = EarlyServerState::new(display.handle(), Some(client_s)); + let testwl = thread.join().unwrap(); satellite.connect(xwls_server); - if options.connect_x { - satellite.set_x_connection(FakeXConnection::default()); - } - let xwls_connection = Connection::from_socket(fake_client).unwrap(); let registry = TestObject::::from_request( &xwls_connection.display(), Req::::GetRegistry {}, ); - let mut f = TestFixture { + let mut f = EarlyTestFixture { testwl, satellite, xwls_connection: xwls_connection.into(), @@ -419,24 +389,19 @@ impl TestFixture { f } - fn new() -> Self { - Self::new_with_options(SetupOptions::default()) - } - - fn new_pre_connect(pre_connect: impl FnOnce(&mut testwl::Server)) -> Self { - Self::new_with_options(SetupOptions::pre_connect(pre_connect)) - } - - fn new_with_compositor() -> (Self, Compositor) { - let mut f = Self::new(); - let compositor = f.compositor(); - (f, compositor) - } - - fn connection(&self) -> &FakeXConnection { - self.satellite.connection.as_ref().unwrap() + fn upgrade_connection(self, connection: FakeXConnection) -> TestFixture { + TestFixture { + testwl: self.testwl, + satellite: self.satellite.upgrade_connection(connection), + xwls_connection: self.xwls_connection, + xwls_display: self.xwls_display, + surface_serial: self.surface_serial, + registry: self.registry, + } } +} +impl TestFixture { fn compositor(&mut self) -> Compositor { let mut ret = CompositorOptional::default(); let events = std::mem::take(&mut *self.registry.data.events.lock().unwrap()); @@ -495,18 +460,6 @@ impl TestFixture { ret.into() } - fn object_data

(&self, obj: &P) -> Arc> - where - P: Proxy + Send + Sync + 'static, - P::Event: Send + Sync + std::fmt::Debug, - { - self.xwls_connection - .get_object_data(obj.id()) - .unwrap() - .downcast_arc::>() - .unwrap() - } - /// Cascade our requests/events through satellite and testwl fn run(&mut self) { // Flush our requests to satellite @@ -573,6 +526,43 @@ impl TestFixture { self.run(); (output, self.testwl.last_created_output()) } +} + +impl TestFixture { + fn new_with_options(options: SetupOptions) -> Self { + EarlyTestFixture::new_early_with_options(options) + .upgrade_connection(FakeXConnection::default()) + } + + fn new() -> Self { + Self::new_with_options(SetupOptions::default()) + } + + fn new_pre_connect(pre_connect: impl FnOnce(&mut testwl::Server)) -> Self { + Self::new_with_options(SetupOptions::pre_connect(pre_connect)) + } + + fn new_with_compositor() -> (Self, Compositor) { + let mut f = Self::new(); + let compositor = f.compositor(); + (f, compositor) + } + + fn connection(&self) -> &FakeXConnection { + self.satellite.connection.as_ref().unwrap() + } + + fn object_data

(&self, obj: &P) -> Arc> + where + P: Proxy + Send + Sync + 'static, + P::Event: Send + Sync + std::fmt::Debug, + { + self.xwls_connection + .get_object_data(obj.id()) + .unwrap() + .downcast_arc::>() + .unwrap() + } fn enable_xdg_output(&mut self) -> TestObject { self.testwl.enable_xdg_output_manager(); @@ -1561,7 +1551,7 @@ fn output_offset_change() { f.testwl.move_surface_to_output(id, &output); f.run(); - let test_position = |f: &TestFixture, x, y| { + let test_position = |f: &TestFixture<_>, x, y| { let data = &f.connection().windows[&window]; assert_eq!(data.dims.x, x); assert_eq!(data.dims.y, y); @@ -2320,7 +2310,7 @@ fn touch_fractional_scale() { let server_surface = data.surface.clone(); let fractional = data.fractional.as_ref().cloned().unwrap(); - let do_touch = |f: &mut TestFixture, x, y| { + let do_touch = |f: &mut TestFixture<_>, x, y| { f.testwl.touch().down(0, 0, &server_surface, 0, x, y); f.testwl.dispatch(); f.run(); @@ -2416,11 +2406,11 @@ fn tablet_tool_fractional_scale() { #[test] fn output_updated_before_x_connection() { - let mut f = TestFixture::new_with_options(SetupOptions::dont_connect_x()); + let mut f = EarlyTestFixture::new_early_with_options(SetupOptions::default()); let comp = f.compositor(); let (_, output) = f.new_output(-20, -20); - f.satellite.set_x_connection(FakeXConnection::default()); + let mut f = f.upgrade_connection(FakeXConnection::default()); let window = unsafe { Window::new(1) }; let (_, surface_id) = f.create_toplevel(&comp, window); diff --git a/src/xstate/mod.rs b/src/xstate/mod.rs index 32427df..41c4f94 100644 --- a/src/xstate/mod.rs +++ b/src/xstate/mod.rs @@ -227,10 +227,13 @@ impl XState { r } - pub fn server_state_setup(&self, server_state: &mut super::RealServerState) { + pub fn server_state_setup( + &self, + server_state: super::EarlyServerState, + ) -> super::RealServerState { let mut c = RealConnection::new(self.connection.clone(), self.atoms.clone()); c.update_outputs(self.root); - server_state.set_x_connection(c); + server_state.upgrade_connection(c) } fn set_root_property(&self, property: x::Atom, r#type: x::Atom, data: &[P]) { @@ -1186,15 +1189,14 @@ impl RealConnection { self.outputs, self.primary_output ); } -} - -impl XConnection for RealConnection { - type X11Selection = Selection; fn root_window(&self) -> x::Window { self.connection.get_setup().roots().next().unwrap().root() } +} +impl XConnection for RealConnection { + type X11Selection = Selection; fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) { trace!("set window dimensions {window:?} {dims:?}"); unwrap_or_skip_bad_window!(self.connection.send_and_check_request(&x::ConfigureWindow {