server: give uninitialized and initialized xstate with unique types

This commit is contained in:
En-En 2025-08-03 01:40:26 +00:00 committed by Supreeeme
parent 56d5cce2d0
commit 4280639df8
4 changed files with 180 additions and 137 deletions

View file

@ -1,7 +1,7 @@
mod server; mod server;
pub mod xstate; pub mod xstate;
use crate::server::{PendingSurfaceState, ServerState}; use crate::server::{EarlyConnection, PendingSurfaceState, ServerState};
use crate::xstate::{RealConnection, XState}; use crate::xstate::{RealConnection, XState};
use log::{error, info}; use log::{error, info};
use rustix::event::{poll, PollFd, PollFlags}; use rustix::event::{poll, PollFd, PollFlags};
@ -16,7 +16,6 @@ use xcb::x;
pub trait XConnection: Sized + 'static { pub trait XConnection: Sized + 'static {
type X11Selection: X11Selection; type X11Selection: X11Selection;
fn root_window(&self) -> x::Window;
fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState); fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState);
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool); fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool);
fn focus_window(&mut self, window: x::Window, output_name: Option<String>); fn focus_window(&mut self, window: x::Window, output_name: Option<String>);
@ -30,6 +29,7 @@ pub trait X11Selection {
fn write_to(&self, mime: &str, pipe: WritePipe); fn write_to(&self, mime: &str, pipe: WritePipe);
} }
type EarlyServerState = ServerState<EarlyConnection<<RealConnection as XConnection>::X11Selection>>;
type RealServerState = ServerState<RealConnection>; type RealServerState = ServerState<RealConnection>;
pub trait RunData { pub trait RunData {
@ -58,8 +58,6 @@ pub fn main(mut data: impl RunData) -> Option<()> {
let dh = display.handle(); let dh = display.handle();
data.created_server(); data.created_server();
let mut server_state = RealServerState::new(dh, data.server());
let (xsock_wl, xsock_xwl) = UnixStream::pair().unwrap(); let (xsock_wl, xsock_xwl) = UnixStream::pair().unwrap();
// Prevent creation of new Xwayland command from closing fd // Prevent creation of new Xwayland command from closing fd
rustix::io::fcntl_setfd(&xsock_xwl, rustix::io::FdFlags::empty()).unwrap(); 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.connect(connection);
server_state.run(); server_state.run();
let mut xstate: Option<XState> = None;
// Remove the lifetimes on our fds to avoid borrowing issues, since we know they will exist for // Remove the lifetimes on our fds to avoid borrowing issues, since we know they will exist for
// the rest of our program anyway // the rest of our program anyway
let server_fd = unsafe { BorrowedFd::borrow_raw(server_state.clientside_fd().as_raw_fd()) }; 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::from_borrowed_fd(server_fd, PollFlags::IN),
PollFd::new(&xsock_wl, PollFlags::IN), PollFd::new(&xsock_wl, PollFlags::IN),
PollFd::from_borrowed_fd(display_fd, PollFlags::IN), PollFd::from_borrowed_fd(display_fd, PollFlags::IN),
PollFd::new(&ready_rx, PollFlags::IN),
PollFd::new(&quit_rx, PollFlags::IN), PollFd::new(&quit_rx, PollFlags::IN),
PollFd::new(&ready_rx, PollFlags::IN),
]; ];
let mut ready = false;
loop { loop {
match poll(&mut fds, -1) { match poll(&mut fds, -1) {
Ok(_) => { Ok(_) => {
if !fds[3].revents().is_empty() { 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() { 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<XState> = 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); let status = xwayland_exit_code(&mut quit_rx);
if *status != ExitStatus::default() { if *status != ExitStatus::default() {
error!("Xwayland exited early with {status}"); error!("Xwayland exited early with {status}");
@ -175,46 +228,5 @@ pub fn main(mut data: impl RunData) -> Option<()> {
} }
Err(other) => panic!("Poll failed: {other:?}"), 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);
}
}
} }
} }

View file

@ -393,6 +393,33 @@ struct GlobalOutputOffset {
y: GlobalOutputOffsetDimension, 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<S: X11Selection + 'static> {
_p: std::marker::PhantomData<S>,
}
impl<S: X11Selection> XConnection for EarlyConnection<S> {
type X11Selection = S;
fn focus_window(&mut self, _: x::Window, _: Option<String>) {
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<C: XConnection> { pub struct ServerState<C: XConnection> {
inner: InnerServerState<C::X11Selection>, inner: InnerServerState<C::X11Selection>,
pub connection: Option<C>, pub connection: Option<C>,
@ -425,7 +452,7 @@ pub struct InnerServerState<S: X11Selection> {
new_scale: Option<f64>, new_scale: Option<f64>,
} }
impl<C: XConnection> ServerState<C> { impl<S: X11Selection> ServerState<EarlyConnection<S>> {
pub fn new(dh: DisplayHandle, server_connection: Option<UnixStream>) -> Self { pub fn new(dh: DisplayHandle, server_connection: Option<UnixStream>) -> Self {
let connection = if let Some(stream) = server_connection { let connection = if let Some(stream) = server_connection {
Connection::from_socket(stream).unwrap() Connection::from_socket(stream).unwrap()
@ -460,7 +487,7 @@ impl<C: XConnection> ServerState<C> {
let clipboard_data = manager.map(|manager| ClipboardData { let clipboard_data = manager.map(|manager| ClipboardData {
manager, manager,
device: None, device: None,
source: None::<CopyPasteData<C::X11Selection>>, source: None::<CopyPasteData<S>>,
}); });
let activation_state = ActivationState::bind(&global_list, &qh) let activation_state = ActivationState::bind(&global_list, &qh)
@ -476,10 +503,10 @@ impl<C: XConnection> ServerState<C> {
}) })
.ok(); .ok();
dh.create_global::<InnerServerState<C::X11Selection>, XwaylandShellV1, _>(1, ()); dh.create_global::<InnerServerState<S>, XwaylandShellV1, _>(1, ());
global_list global_list
.contents() .contents()
.with_list(|globals| handle_globals::<C::X11Selection>(&dh, globals)); .with_list(|globals| handle_globals::<S>(&dh, globals));
let inner = InnerServerState { let inner = InnerServerState {
windows: HashMap::new(), windows: HashMap::new(),
@ -516,7 +543,19 @@ impl<C: XConnection> ServerState<C> {
}; };
Self { Self {
inner, inner,
connection: None, connection: Some(EarlyConnection {
_p: std::marker::PhantomData,
}),
}
}
pub fn upgrade_connection<C>(self, connection: C) -> ServerState<C>
where
C: XConnection<X11Selection = S>,
{
ServerState {
inner: self.inner,
connection: Some(connection),
} }
} }
} }

View file

@ -1,5 +1,6 @@
use super::{InnerServerState, ServerState, WindowDims}; use super::{EarlyConnection, InnerServerState, ServerState, WindowDims};
use crate::xstate::{SetState, WinSize, WmName}; use crate::xstate::{SetState, WinSize, WmName};
use crate::XConnection;
use rustix::event::{poll, PollFd, PollFlags}; use rustix::event::{poll, PollFd, PollFlags};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
@ -152,8 +153,8 @@ struct WindowData {
fullscreen: bool, fullscreen: bool,
dims: WindowDims, dims: WindowDims,
} }
#[derive(Default)]
struct FakeXConnection { struct FakeXConnection {
root: Window,
focused_window: Option<Window>, focused_window: Option<Window>,
windows: HashMap<Window, WindowData>, windows: HashMap<Window, WindowData>,
} }
@ -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<testwl::PasteData>; type FakeX11Selection = Vec<testwl::PasteData>;
impl crate::X11Selection for Vec<testwl::PasteData> { impl crate::X11Selection for Vec<testwl::PasteData> {
fn mime_types(&self) -> Vec<&str> { fn mime_types(&self) -> Vec<&str> {
@ -206,10 +197,6 @@ impl crate::X11Selection for Vec<testwl::PasteData> {
impl super::XConnection for FakeXConnection { impl super::XConnection for FakeXConnection {
type X11Selection = FakeX11Selection; type X11Selection = FakeX11Selection;
fn root_window(&self) -> Window {
self.root
}
#[track_caller] #[track_caller]
fn close_window(&mut self, window: Window) { fn close_window(&mut self, window: Window) {
log::debug!("closing window {window:?}"); log::debug!("closing window {window:?}");
@ -252,15 +239,16 @@ impl super::XConnection for FakeXConnection {
} }
} }
type FakeServerState = ServerState<FakeXConnection>; type EarlyServerState = ServerState<EarlyConnection<FakeX11Selection>>;
type EarlyTestFixture = TestFixture<EarlyConnection<FakeX11Selection>>;
struct TestFixture { struct TestFixture<C: XConnection> {
testwl: testwl::Server, testwl: testwl::Server,
satellite: FakeServerState, satellite: ServerState<C>,
/// Our connection to satellite - i.e., where Xwayland sends requests to /// Our connection to satellite - i.e., where Xwayland sends requests to
xwls_connection: Arc<Connection>, xwls_connection: Arc<Connection>,
/// Satellite's display - must dispatch this for our server state to advance /// Satellite's display - must dispatch this for our server state to advance
xwls_display: Display<InnerServerState<FakeX11Selection>>, xwls_display: Display<InnerServerState<C::X11Selection>>,
surface_serial: u64, surface_serial: u64,
registry: TestObject<WlRegistry>, registry: TestObject<WlRegistry>,
} }
@ -336,38 +324,24 @@ impl PreConnectFn for () {}
struct SetupOptions<F> { struct SetupOptions<F> {
pre_connect: Option<F>, pre_connect: Option<F>,
connect_x: bool,
}
impl SetupOptions<()> {
fn dont_connect_x() -> Self {
Self {
pre_connect: None,
connect_x: false,
}
}
} }
impl<F: PreConnectFn> SetupOptions<F> { impl<F: PreConnectFn> SetupOptions<F> {
fn pre_connect(pre_connect: F) -> Self { fn pre_connect(pre_connect: F) -> Self {
Self { Self {
pre_connect: Some(pre_connect), pre_connect: Some(pre_connect),
connect_x: true,
} }
} }
} }
impl Default for SetupOptions<()> { impl Default for SetupOptions<()> {
fn default() -> Self { fn default() -> Self {
Self { Self { pre_connect: None }
pre_connect: None,
connect_x: true,
}
} }
} }
impl TestFixture { impl EarlyTestFixture {
fn new_with_options<F: PreConnectFn>(options: SetupOptions<F>) -> Self { fn new_early_with_options<F: PreConnectFn>(options: SetupOptions<F>) -> Self {
INIT.call_once(|| { INIT.call_once(|| {
env_logger::builder() env_logger::builder()
.is_test(true) .is_test(true)
@ -391,23 +365,19 @@ impl TestFixture {
testwl.dispatch(); testwl.dispatch();
testwl 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 (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); satellite.connect(xwls_server);
if options.connect_x {
satellite.set_x_connection(FakeXConnection::default());
}
let xwls_connection = Connection::from_socket(fake_client).unwrap(); let xwls_connection = Connection::from_socket(fake_client).unwrap();
let registry = TestObject::<WlRegistry>::from_request( let registry = TestObject::<WlRegistry>::from_request(
&xwls_connection.display(), &xwls_connection.display(),
Req::<WlDisplay>::GetRegistry {}, Req::<WlDisplay>::GetRegistry {},
); );
let mut f = TestFixture { let mut f = EarlyTestFixture {
testwl, testwl,
satellite, satellite,
xwls_connection: xwls_connection.into(), xwls_connection: xwls_connection.into(),
@ -419,24 +389,19 @@ impl TestFixture {
f f
} }
fn new() -> Self { fn upgrade_connection(self, connection: FakeXConnection) -> TestFixture<FakeXConnection> {
Self::new_with_options(SetupOptions::default()) TestFixture {
} testwl: self.testwl,
satellite: self.satellite.upgrade_connection(connection),
fn new_pre_connect(pre_connect: impl FnOnce(&mut testwl::Server)) -> Self { xwls_connection: self.xwls_connection,
Self::new_with_options(SetupOptions::pre_connect(pre_connect)) xwls_display: self.xwls_display,
} surface_serial: self.surface_serial,
registry: self.registry,
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()
} }
}
impl<C: XConnection> TestFixture<C> {
fn compositor(&mut self) -> Compositor { fn compositor(&mut self) -> Compositor {
let mut ret = CompositorOptional::default(); let mut ret = CompositorOptional::default();
let events = std::mem::take(&mut *self.registry.data.events.lock().unwrap()); let events = std::mem::take(&mut *self.registry.data.events.lock().unwrap());
@ -495,18 +460,6 @@ impl TestFixture {
ret.into() ret.into()
} }
fn object_data<P>(&self, obj: &P) -> Arc<TestObjectData<P>>
where
P: Proxy + Send + Sync + 'static,
P::Event: Send + Sync + std::fmt::Debug,
{
self.xwls_connection
.get_object_data(obj.id())
.unwrap()
.downcast_arc::<TestObjectData<P>>()
.unwrap()
}
/// Cascade our requests/events through satellite and testwl /// Cascade our requests/events through satellite and testwl
fn run(&mut self) { fn run(&mut self) {
// Flush our requests to satellite // Flush our requests to satellite
@ -573,6 +526,43 @@ impl TestFixture {
self.run(); self.run();
(output, self.testwl.last_created_output()) (output, self.testwl.last_created_output())
} }
}
impl TestFixture<FakeXConnection> {
fn new_with_options<F: PreConnectFn>(options: SetupOptions<F>) -> 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<P>(&self, obj: &P) -> Arc<TestObjectData<P>>
where
P: Proxy + Send + Sync + 'static,
P::Event: Send + Sync + std::fmt::Debug,
{
self.xwls_connection
.get_object_data(obj.id())
.unwrap()
.downcast_arc::<TestObjectData<P>>()
.unwrap()
}
fn enable_xdg_output(&mut self) -> TestObject<ZxdgOutputManagerV1> { fn enable_xdg_output(&mut self) -> TestObject<ZxdgOutputManagerV1> {
self.testwl.enable_xdg_output_manager(); self.testwl.enable_xdg_output_manager();
@ -1561,7 +1551,7 @@ fn output_offset_change() {
f.testwl.move_surface_to_output(id, &output); f.testwl.move_surface_to_output(id, &output);
f.run(); f.run();
let test_position = |f: &TestFixture, x, y| { let test_position = |f: &TestFixture<_>, x, y| {
let data = &f.connection().windows[&window]; let data = &f.connection().windows[&window];
assert_eq!(data.dims.x, x); assert_eq!(data.dims.x, x);
assert_eq!(data.dims.y, y); assert_eq!(data.dims.y, y);
@ -2320,7 +2310,7 @@ fn touch_fractional_scale() {
let server_surface = data.surface.clone(); let server_surface = data.surface.clone();
let fractional = data.fractional.as_ref().cloned().unwrap(); 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.touch().down(0, 0, &server_surface, 0, x, y);
f.testwl.dispatch(); f.testwl.dispatch();
f.run(); f.run();
@ -2416,11 +2406,11 @@ fn tablet_tool_fractional_scale() {
#[test] #[test]
fn output_updated_before_x_connection() { 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 comp = f.compositor();
let (_, output) = f.new_output(-20, -20); 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 window = unsafe { Window::new(1) };
let (_, surface_id) = f.create_toplevel(&comp, window); let (_, surface_id) = f.create_toplevel(&comp, window);

View file

@ -227,10 +227,13 @@ impl XState {
r 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()); let mut c = RealConnection::new(self.connection.clone(), self.atoms.clone());
c.update_outputs(self.root); c.update_outputs(self.root);
server_state.set_x_connection(c); server_state.upgrade_connection(c)
} }
fn set_root_property<P: x::PropEl>(&self, property: x::Atom, r#type: x::Atom, data: &[P]) { fn set_root_property<P: x::PropEl>(&self, property: x::Atom, r#type: x::Atom, data: &[P]) {
@ -1186,15 +1189,14 @@ impl RealConnection {
self.outputs, self.primary_output self.outputs, self.primary_output
); );
} }
}
impl XConnection for RealConnection {
type X11Selection = Selection;
fn root_window(&self) -> x::Window { fn root_window(&self) -> x::Window {
self.connection.get_setup().roots().next().unwrap().root() 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) { fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) {
trace!("set window dimensions {window:?} {dims:?}"); trace!("set window dimensions {window:?} {dims:?}");
unwrap_or_skip_bad_window!(self.connection.send_and_check_request(&x::ConfigureWindow { unwrap_or_skip_bad_window!(self.connection.send_and_check_request(&x::ConfigureWindow {