Add command-line options (#342)

* 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.
This commit is contained in:
En-En 2026-01-24 18:26:00 +00:00 committed by GitHub
parent 7af39ce419
commit 62bafcc3c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 332 additions and 33 deletions

View file

@ -36,6 +36,9 @@ type RealServerState = ServerState<RealConnection>;
pub trait RunData { pub trait RunData {
fn display(&self) -> Option<&str>; fn display(&self) -> Option<&str>;
fn listenfds(&mut self) -> Vec<OwnedFd>; fn listenfds(&mut self) -> Vec<OwnedFd>;
fn flags(&self) -> &[String] {
&[]
}
fn server(&self) -> Option<UnixStream> { fn server(&self) -> Option<UnixStream> {
None None
} }
@ -58,12 +61,16 @@ pub const fn timespec_from_millis(millis: u64) -> Timespec {
} }
} }
pub fn main(mut data: impl RunData) -> Option<()> { pub fn version() -> &'static str {
let mut version = env!("VERGEN_GIT_DESCRIBE"); let mut version = env!("VERGEN_GIT_DESCRIBE");
if version == "VERGEN_IDEMPOTENT_OUTPUT" { if version == "VERGEN_IDEMPOTENT_OUTPUT" {
version = env!("CARGO_PKG_VERSION"); version = env!("CARGO_PKG_VERSION");
} }
info!("Starting xwayland-satellite version {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 socket = ListeningSocket::bind_auto("xwls", 1..=128).unwrap();
let mut display = Display::new().unwrap(); let mut display = Display::new().unwrap();
@ -95,6 +102,7 @@ pub fn main(mut data: impl RunData) -> Option<()> {
"-displayfd", "-displayfd",
&ready_tx.as_raw_fd().to_string(), &ready_tx.as_raw_fd().to_string(),
]) ])
.args(data.flags())
.env("WAYLAND_DISPLAY", socket.socket_name().unwrap()) .env("WAYLAND_DISPLAY", socket.socket_name().unwrap())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()

View file

@ -1,4 +1,4 @@
use std::os::fd::{FromRawFd, OwnedFd, RawFd}; use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
fn main() { fn main() {
pretty_env_logger::formatted_timed_builder() pretty_env_logger::formatted_timed_builder()
@ -8,9 +8,11 @@ fn main() {
xwayland_satellite::main(parse_args()); xwayland_satellite::main(parse_args());
} }
#[derive(Default)]
struct RealData { struct RealData {
display: Option<String>, display: Option<String>,
listenfds: Vec<OwnedFd>, listenfds: Vec<OwnedFd>,
flags: Vec<String>,
} }
impl xwayland_satellite::RunData for RealData { impl xwayland_satellite::RunData for RealData {
fn display(&self) -> Option<&str> { fn display(&self) -> Option<&str> {
@ -20,46 +22,227 @@ impl xwayland_satellite::RunData for RealData {
fn listenfds(&mut self) -> Vec<OwnedFd> { fn listenfds(&mut self) -> Vec<OwnedFd> {
std::mem::take(&mut self.listenfds) std::mem::take(&mut self.listenfds)
} }
fn flags(&self) -> &[String] {
&self.flags
}
}
struct ParsedFlags {
disable_ac: bool,
audit_level: u32,
auth_file: Option<String>,
coredump: bool,
extension_plus: Vec<String>,
extension_minus: Vec<String>,
glamor: Option<&'static str>,
listen_plus: Vec<String>,
listen_minus: Vec<String>,
verbosity: u32,
}
impl Default for ParsedFlags {
fn default() -> Self {
Self {
disable_ac: false,
audit_level: 1,
auth_file: None,
coredump: false,
extension_plus: vec![],
extension_minus: vec![],
glamor: None,
listen_plus: vec![],
listen_minus: vec![],
verbosity: 0,
}
}
}
impl ParsedFlags {
fn to_vec(&self) -> Vec<String> {
let mut ret: Vec<&str> = vec![];
if self.disable_ac {
ret.push("-ac");
}
let audit = self.audit_level.to_string();
ret.extend(["-audit", &audit]);
if let Some(ref auth) = self.auth_file {
ret.extend(["-auth", auth]);
}
if self.coredump {
ret.push("-core");
}
for ext in self.extension_plus.iter() {
ret.extend(["+extension", ext]);
}
for ext in self.extension_minus.iter() {
ret.extend(["-extension", ext]);
}
if let Some(glamor) = self.glamor {
ret.extend(glamor.split_ascii_whitespace());
}
for protocol in self.listen_plus.iter() {
ret.extend(["-listen", protocol]);
}
for protocol in self.listen_minus.iter() {
ret.extend(["-nolisten", protocol]);
}
let verbosity = self.verbosity.to_string();
ret.extend(["-verbose", &verbosity]);
ret.into_iter().map(str::to_string).collect()
}
} }
fn parse_args() -> RealData { fn parse_args() -> RealData {
let mut data = RealData { let mut data = RealData::default();
display: None, let mut flags = ParsedFlags::default();
listenfds: Vec::new(), let mut args = std::env::args().skip(1).peekable();
};
let mut args: Vec<_> = std::env::args().collect(); // The first argument (other than the skipped-over binary name) is optionally the display name.
if args.len() < 2 { let Some(arg) = args.peek() else {
return data; return data;
};
if arg.starts_with(':') {
data.display = Some(arg.to_owned());
args.next();
} }
// Argument at index 1 is our display name. The rest can be -listenfd. // All other options (including the first if it was not a display name) are supported flags
let mut i = 2; while let Some(arg) = args.next() {
while i < args.len() { match arg.as_str() {
let arg = &args[i]; "-ac" => {
if arg == "-listenfd" { flags.disable_ac = true;
let next = i + 1; }
if next == args.len() { "-audit" => {
// Matches the Xwayland error message. flags.audit_level = args
panic!("Required argument to -listenfd not specified"); .next()
.and_then(|n| n.parse().ok())
.expect("argument to -audit not provided or integer");
}
"-auth" => {
// X.org lets you pass multiple `-auth` parameters but only uses the last one.
// This is unintuitive enough that passing multiple `-auth` should be an error.
if flags.auth_file.is_some() {
panic!("Multiple `-auth` flags passed");
}
let Some(ref file) = args.next() else {
panic!("No authorization file passed");
};
std::fs::OpenOptions::new()
.read(true)
.open(file)
.expect("Could not open authorization file");
flags.auth_file = Some(file.to_owned());
}
"-core" => {
flags.coredump = true;
}
"+extension" => {
let ext = args.next().expect("argument to +extension not provided");
if let Some(idx) = flags.extension_minus.iter().position(|e| *e == ext) {
flags.extension_minus.swap_remove(idx);
}
flags.extension_plus.push(ext.to_owned());
}
"-extension" => {
let ext = args.next().expect("argument to -extension not provided");
// Do not disable essential extensions (see XState::new for this list)
if !["COMPOSITE", "RANDR", "XFIXES", "X-Resource"].contains(&&ext[..]) {
if let Some(idx) = flags.extension_plus.iter().position(|e| *e == ext) {
flags.extension_plus.swap_remove(idx);
}
flags.extension_minus.push(ext.to_owned());
}
}
"-glamor" => {
let api = args.next().expect("argument to -glamor not provided");
match &api[..] {
"gl" => flags.glamor = Some("-glamor gl"),
"es" => flags.glamor = Some("-glamor es"),
// For maximum compatability with Xwayland compiled without Glamor support, this
// is equivalent to -glamor none and is always available
"none" => flags.glamor = Some("-shm"),
e => panic!("unknown rendering API passed: {e}"),
};
}
"-help" => {
// Wording for most help messages taken directly from Xwayland
println!("use: xwayland-satellite [:<display>] [option]");
println!("{:<25} disable access control restrictions", "-ac");
println!("{:<25} set audit trail level", "-audit <int>");
println!(
"{:<25} generate core dump of Xwayland on fatal error",
"-core"
);
println!("{:<25} Enable extension", "+extension name");
println!("{:<25} Disable extension", "-extension name");
println!(
"{:<25} use given API for Glamor acceleration",
"-glamor [gl|es|none]"
);
println!("{:<25} prints message with these options", "-help");
println!("{:<25} listen on protocol", "-listen");
println!("{:<25} don't listen on protocol", "-nolisten");
println!("{:<25} add given fd as a listen socket", "-listenfd");
println!(
"{:<25} return 0 if supports -listenfd",
"--test-listenfd-support"
);
println!("{:<25} verbose startup messages", "-verbose [n]");
println!(
"{:<25} show the xwayland-satellite version and exit",
"-version"
);
std::process::exit(0);
}
"-listen" => {
let protocol = args.next().expect("argument to -listen not provided");
if let Some(idx) = flags.listen_minus.iter().position(|p| *p == protocol) {
flags.listen_minus.swap_remove(idx);
}
flags.listen_plus.push(protocol.to_owned());
}
"-nolisten" => {
let protocol = args.next().expect("argument to -nolisten not provided");
if let Some(idx) = flags.listen_plus.iter().position(|p| *p == protocol) {
flags.listen_plus.swap_remove(idx);
}
flags.listen_minus.push(protocol.to_owned());
}
"-listenfd" => {
let fd: RawFd = args
.next()
.expect("Required argument to -listenfd not specified")
.parse()
.expect("Error parsing -listenfd number");
// SAFETY:
// - whoever runs the binary must ensure this fd is open and valid.
// - parse_args() must only be called once to avoid double closing.
// - no fd can be provided multiple times to avoid double closing.
assert!(
data.listenfds.iter().any(|l| l.as_raw_fd() == fd),
"Multiple -listenfd with the same fd is not allowed"
);
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
data.listenfds.push(fd);
}
"--test-listenfd-support" => std::process::exit(0),
"-verbose" => {
if let Some(v) = args.peek().and_then(|n| n.parse::<u32>().ok()) {
flags.verbosity = v;
args.next();
} else {
flags.verbosity += 1;
}
}
"-version" => {
println!("{}", xwayland_satellite::version());
std::process::exit(0);
}
_ => {
panic!("Unrecognized argument: {arg}");
} }
let fd: RawFd = args[next].parse().expect("Error parsing -listenfd number");
// SAFETY:
// - whoever runs the binary must ensure this fd is open and valid.
// - parse_args() must only be called once to avoid double closing.
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
data.listenfds.push(fd);
i += 2;
} else if arg == "--test-listenfd-support" {
std::process::exit(0);
} else {
panic!("Unrecognized argument: {arg}");
} }
} }
data.flags = flags.to_vec();
data.display = Some(args.swap_remove(1));
data data
} }

108
xwayland-satellite.man Normal file
View file

@ -0,0 +1,108 @@
.TH XWAYLAND\-SATELLITE 1
.SH NAME
xwayland\-satellite \- Rootless Xwayland integration for Wayland compositors
.SH SYNOPSIS
.B xwayland\-satellite
[\fB:\fR\fIdisplay\fR]
[\fIoption\fR ...]
.SH DESCRIPTION
.B xwayland\-satellite
is a Wayland client providing a rootless
.BR Xwayland (1)
integration to any Wayland compositor implementing xdg_wm_base and wm_viewporter.
.B xwayland\-satellite
acts as an
.BR X (7)
window manager, useful for Wayland compositors which do not want to implement an
.BR Xwayland (1)
integration themselves.
.SH OPTIONS
Most of the options of
.B xwayland\-satellite
are forwarded to Xwayland. See
.BR Xserver (1)
and
.BR Xwayland (1)
for more details on these flags.
.TP
\fB:\fR\fIdisplay\fR
Run the X server on the given \fIdisplay\fR.
.TP
\fB\-ac\fR
Disable host-based access control mechanisms. Do not use unless you know exactly what you are doing.
.TP
\fB\-audit\fR \fIlevel\fR
Set the audit trail verbosity level. These logging lines are captured by the logs in the `xwayland_process` category.
.TP
\fB\-auth\fR \fIauthorization-file\fR
Specify a file with authorization records. This flag can be passed a maximum of one time. See the
.BR xdm (1)
and
.BR Xsecurity (7)
man pages.
.TP
\fB\-core\fR
Cause Xwayland to generate a core dump on fatal errors. Note this does not generate a core dump on fatal
.BR xwayland-satellite
errors.
.TP
\fB+extension\fR \fIextension-name\fR
Enable the given extension. Attempting to enable an unrecognized is not a fatal error.
.TP
\fB\-extension\fR \fIextension-name\fR
Disable the given extension. Attempting to disable an unrecognized extension is not a fatal error.
.RS
In addition, the following extensions are required and cannot be disabled:
.RS 4
.nf
COMPOSITE
RANDR
XFIXES
X-Resource
.fi
.RE -4
.TP
\fB\-glamor\fR [\fIgl|es|none\fR]
Force the use of the given rendering API for Glamor acceleration. Valid parameters are \fIgl\fR to use OpenGL, \fIes\fR to use GL ES, and \fInone\fR to use the shared memory backend. For maximum compatibility with
.BR Xwayland (1)
builds without the Glamor backend, the \fInone\fR option is converted to the \fI\-shm\fR flag under the hood.
\fB\-help\fR
Prints a basic usage message and list of accepted flags and returns 0.
.TP
\fB\-listen\fR
Enable a transport mechanism. This option can be used multiple times to enable multiple transport mechanisms. This option overrides a previous \fB\-nolisten\fR sent with the same parameter.
.TP
\fB\-nolisten\fR
Disable a transport mechanism. This option can be used multiple times to disable multiple transport mechanisms. This option overrides a previous \fB\-listen\fR sent with the same parameter.
.TP
\fB\-listenfd\fR \fIfd\fR
Add the given \fIfd\fR as a socket for X clients to connect to. This option is used by integrating Wayland compositors to automatically start \fBxwayland\-satellite\fR when an X application is started.
.TP
\fB\-\-test\-listenfd\-support\fR
Exit with status code 0 if the build of \fBxwayland\-satellite\fR supports the \fB\-listenfd\fR flag. For maximum compatibility with older versions of \fBxwayland\-satellite\fR, it is necessary to always pass the \fB:\fR\fIdisplay\fR option when using this flag and the \fB\-listenfd\fR flag.
.TP
\fB\-verbose\fR [\fIn\fR]
Increases the verbosity of \fBXwayland\fR's logging. If a numeric variable is passed, set the verbosity to that level, otherwise increasing the verbosity level by 1. See the \fBRUST_LOG\fR environment variable for changing \fBxwayland\-satellite\fR's logging.
.TP
\fB\-version\fR
Display the version identifier of \fBxwayland\-satellite\fR and return 0.
.SH ENVIRONMENT
.TP
\fBRUST_LOG\fR
Configures the output of the logs. See
.BR https://docs.rs/env\-logger/#filtering\-results/
for more information on how to set this variable.
.TP
\fBWAYLAND_DISPLAY\fR
Name of the Wayland server's display.
.TP
\fBXDG_RUNTIME_DIR\fR
The directory used by \fBxwayland\-satellite\fR to both look for the Wayland socket to connect to and to create the Wayland socket \fBXwayland\fR will connect to.
.SH BUGS
If you have found a bug in \fBxwayland\-satellite\fR, please report it to
.BR https://github.com/Supreeeme/xwayland\-satellite/issues/ "."
.
.SH SEE ALSO
.BR X (7),
.BR Xserver (1),
.BR Xwayland (1),