* feat: forward requested command-line options The CLI now forwards the `-ac`, `-audit`, `-auth`, `-core`, `+extension`, `-extension`, `-glamor`, `-listen`, `-nolisten`, and `-verbose` flags to `Xwayland`. In addition, command-line flags no longer require the display to be the first parameter. Also the `-help` and `-version` flags were added, which print the relevant message and return 0. The match case structure should make it easier to add new arguments if people have use for them. For example, I chose not to add the `-ld`, `-lm`, and `-ls` flags since they are dependent on `Xwayland`'s compile-time flags.
258 lines
8.4 KiB
Rust
258 lines
8.4 KiB
Rust
mod server;
|
|
pub mod xstate;
|
|
|
|
use crate::server::{NoConnection, PendingSurfaceState, ServerState};
|
|
use crate::xstate::{RealConnection, XState};
|
|
use log::{error, info};
|
|
use rustix::event::{PollFd, PollFlags, Timespec, poll};
|
|
use server::selection::{Clipboard, Primary};
|
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
|
use std::io::{BufRead, BufReader, Read, Write};
|
|
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
|
|
use std::os::unix::net::UnixStream;
|
|
use std::process::{Command, ExitStatus, Stdio};
|
|
use wayland_server::{Display, ListeningSocket};
|
|
use xcb::x;
|
|
|
|
pub trait XConnection: Sized + 'static {
|
|
type X11Selection: X11Selection;
|
|
|
|
fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState) -> bool;
|
|
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool);
|
|
fn focus_window(&mut self, window: x::Window, output_name: Option<String>);
|
|
fn close_window(&mut self, window: x::Window);
|
|
fn unmap_window(&mut self, window: x::Window);
|
|
fn raise_to_top(&mut self, window: x::Window);
|
|
}
|
|
|
|
pub trait X11Selection {
|
|
fn mime_types(&self) -> Vec<&str>;
|
|
fn write_to(&self, mime: &str, pipe: WritePipe);
|
|
}
|
|
|
|
type EarlyServerState = ServerState<NoConnection<<RealConnection as XConnection>::X11Selection>>;
|
|
type RealServerState = ServerState<RealConnection>;
|
|
|
|
pub trait RunData {
|
|
fn display(&self) -> Option<&str>;
|
|
fn listenfds(&mut self) -> Vec<OwnedFd>;
|
|
fn flags(&self) -> &[String] {
|
|
&[]
|
|
}
|
|
fn server(&self) -> Option<UnixStream> {
|
|
None
|
|
}
|
|
fn created_server(&self) {}
|
|
fn connected_server(&self) {}
|
|
fn quit_rx(&self) -> Option<UnixStream> {
|
|
None
|
|
}
|
|
fn xwayland_ready(&self, _display: String, _pid: u32) {}
|
|
fn max_req_len_bytes(&self) -> Option<usize> {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub const fn timespec_from_millis(millis: u64) -> Timespec {
|
|
let d = std::time::Duration::from_millis(millis);
|
|
Timespec {
|
|
tv_sec: d.as_secs() as i64,
|
|
tv_nsec: d.subsec_nanos() as i64,
|
|
}
|
|
}
|
|
|
|
pub fn version() -> &'static str {
|
|
let mut version = env!("VERGEN_GIT_DESCRIBE");
|
|
if version == "VERGEN_IDEMPOTENT_OUTPUT" {
|
|
version = env!("CARGO_PKG_VERSION");
|
|
}
|
|
version
|
|
}
|
|
|
|
pub fn main(mut data: impl RunData) -> Option<()> {
|
|
info!("Starting xwayland-satellite version {}", version());
|
|
|
|
let socket = ListeningSocket::bind_auto("xwls", 1..=128).unwrap();
|
|
let mut display = Display::new().unwrap();
|
|
let dh = display.handle();
|
|
data.created_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();
|
|
|
|
let (ready_tx, ready_rx) = UnixStream::pair().unwrap();
|
|
rustix::io::fcntl_setfd(&ready_tx, rustix::io::FdFlags::empty()).unwrap();
|
|
let mut xwayland = Command::new("Xwayland");
|
|
if let Some(display) = data.display() {
|
|
xwayland.arg(display);
|
|
}
|
|
|
|
let fds = data.listenfds();
|
|
for fd in &fds {
|
|
xwayland.args(["-listenfd", &fd.as_raw_fd().to_string()]);
|
|
}
|
|
|
|
let mut xwayland = xwayland
|
|
.args([
|
|
"-rootless",
|
|
"-force-xrandr-emulation",
|
|
"-wm",
|
|
&xsock_xwl.as_raw_fd().to_string(),
|
|
"-displayfd",
|
|
&ready_tx.as_raw_fd().to_string(),
|
|
])
|
|
.args(data.flags())
|
|
.env("WAYLAND_DISPLAY", socket.socket_name().unwrap())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.unwrap();
|
|
|
|
// Now that Xwayland spawned and got the listenfds, we can close them here.
|
|
drop(fds);
|
|
|
|
let xwl_pid = xwayland.id();
|
|
|
|
let (mut finish_tx, mut finish_rx) = UnixStream::pair().unwrap();
|
|
let stderr = xwayland.stderr.take().unwrap();
|
|
std::thread::spawn(move || {
|
|
let reader = BufReader::new(stderr);
|
|
for line in reader.lines() {
|
|
let line = line.unwrap();
|
|
info!(target: "xwayland_process", "{line}");
|
|
}
|
|
let status = Box::new(xwayland.wait().unwrap());
|
|
let status = Box::into_raw(status) as usize;
|
|
finish_tx.write_all(&status.to_ne_bytes()).unwrap();
|
|
});
|
|
|
|
let mut ready_fds = [
|
|
PollFd::new(&socket, PollFlags::IN),
|
|
PollFd::new(&finish_rx, PollFlags::IN),
|
|
];
|
|
|
|
fn xwayland_exit_code(rx: &mut UnixStream) -> Box<ExitStatus> {
|
|
let mut data = [0; (usize::BITS / 8) as usize];
|
|
rx.read_exact(&mut data).unwrap();
|
|
let data = usize::from_ne_bytes(data);
|
|
unsafe { Box::from_raw(data as *mut _) }
|
|
}
|
|
|
|
let connection = match poll(&mut ready_fds, None) {
|
|
Ok(_) => {
|
|
if !ready_fds[1].revents().is_empty() {
|
|
let status = xwayland_exit_code(&mut finish_rx);
|
|
error!("Xwayland exited early with {status}");
|
|
return None;
|
|
}
|
|
|
|
data.connected_server();
|
|
socket.accept().unwrap().unwrap()
|
|
}
|
|
Err(e) => {
|
|
panic!("first poll failed: {e:?}")
|
|
}
|
|
};
|
|
|
|
let mut server_state = EarlyServerState::new(dh, data.server(), connection);
|
|
server_state.run();
|
|
|
|
// 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()) };
|
|
let display_fd = unsafe { BorrowedFd::borrow_raw(display.backend().poll_fd().as_raw_fd()) };
|
|
|
|
// `finish_rx` only writes the status code of `Xwayland` exiting, so it is reasonable to use as
|
|
// the UnixStream of choice when not running the integration tests.
|
|
let mut quit_rx = data.quit_rx().unwrap_or(finish_rx);
|
|
|
|
let mut fds = [
|
|
PollFd::from_borrowed_fd(server_fd, PollFlags::IN),
|
|
PollFd::new(&xsock_wl, PollFlags::IN),
|
|
PollFd::from_borrowed_fd(display_fd, PollFlags::IN),
|
|
PollFd::new(&quit_rx, PollFlags::IN),
|
|
PollFd::new(&ready_rx, PollFlags::IN),
|
|
];
|
|
|
|
loop {
|
|
match poll(&mut fds, None) {
|
|
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}");
|
|
}
|
|
return None;
|
|
}
|
|
if !fds[4].revents().is_empty() {
|
|
break;
|
|
}
|
|
}
|
|
Err(other) => panic!("Poll failed: {other:?}"),
|
|
}
|
|
|
|
display.dispatch_clients(&mut *server_state).unwrap();
|
|
server_state.run();
|
|
display.flush_clients().unwrap();
|
|
}
|
|
|
|
let mut xstate = XState::new(xsock_wl.as_fd());
|
|
if let Some(bytes) = data.max_req_len_bytes() {
|
|
xstate.set_max_req_bytes(bytes);
|
|
}
|
|
|
|
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(&mut *server_state).unwrap();
|
|
server_state.run();
|
|
display.flush_clients().unwrap();
|
|
|
|
if let Some(sel) = server_state.new_selection::<Clipboard>() {
|
|
xstate.set_clipboard(sel);
|
|
}
|
|
|
|
if let Some(sel) = server_state.new_selection::<Primary>() {
|
|
xstate.set_primary_selection(sel);
|
|
}
|
|
|
|
if let Some(scale) = server_state.new_global_scale() {
|
|
xstate.update_global_scale(scale);
|
|
}
|
|
|
|
match poll(&mut fds, None) {
|
|
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}");
|
|
}
|
|
return None;
|
|
}
|
|
}
|
|
Err(other) => panic!("Poll failed: {other:?}"),
|
|
}
|
|
}
|
|
}
|