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:
parent
7af39ce419
commit
62bafcc3c9
3 changed files with 332 additions and 33 deletions
12
src/lib.rs
12
src/lib.rs
|
|
@ -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()
|
||||||
|
|
|
||||||
245
src/main.rs
245
src/main.rs
|
|
@ -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
108
xwayland-satellite.man
Normal 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),
|
||||||
Loading…
Add table
Add a link
Reference in a new issue