Add XWayland support (#16)

* save dev state

* fix xwayland-satellite supervisor lifecycle, readiness and cleanup

* remove wayland scanner protocol generated files

* default xwayland enabled with path

* remove jargons

* address x11 review: park unconfirmed cleanup, never signal a reaped group, fix storm test and notify dir mode

* fix resize tests: never build Size values from negative dimensions

* add min-width/min-height window rules that replace client minimum sizes

* rename effective_size_constraints to effective_minimum_size and minor cleanups
This commit is contained in:
Samuel Onoja authored and GitHub committed 2026-10-06 19:40:22 +01:00
1 parent ed23949a95
commit a6f83689f8
42 files changed
+7162 -1333

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const STATUS_COMMAND: &str = "xwayland-status";
pub const RETRY_COMMAND: &str = "xwayland-retry";
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum State {
Disabled,
Idle,
Starting,
Running,
Backoff,
Failed,
Stopped,
}
impl State {
pub fn is_inactive(self) -> bool {
matches!(self, Self::Disabled | Self::Idle | Self::Stopped)
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Readiness {
Pending,
Verified,
Rejected,
NotAttempted,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Status {
pub enabled: bool,
pub effective_startup: String,
pub state: State,
pub display: Option<String>,
/// PID of Satellite, not its Xwayland child.
pub satellite_pid: Option<u32>,
pub generation: Option<u64>,
pub readiness: Readiness,
pub recent_failures: u32,
pub restart_required: bool,
pub last_error: Option<String>,
}
impl Status {
pub fn from_value(value: Value) -> serde_json::Result<Self> {
serde_json::from_value(value)
}
pub fn to_value(&self) -> Value {
match serde_json::to_value(self) {
Ok(value) => value,
Err(error) => serde_json::json!({ "error": error.to_string() }),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn states_use_stable_snake_case_names() {
for (state, name) in [
(State::Disabled, "disabled"),
(State::Idle, "idle"),
(State::Starting, "starting"),
(State::Running, "running"),
(State::Backoff, "backoff"),
(State::Failed, "failed"),
(State::Stopped, "stopped"),
] {
assert_eq!(serde_json::to_value(state).unwrap(), json!(name));
}
}
#[test]
fn readiness_values_round_trip() {
for (readiness, name) in [
(Readiness::Pending, "pending"),
(Readiness::Verified, "verified"),
(Readiness::Rejected, "rejected"),
(Readiness::NotAttempted, "not_attempted"),
] {
let encoded = serde_json::to_value(readiness).unwrap();
assert_eq!(encoded, json!(name));
assert_eq!(serde_json::from_value::<Readiness>(encoded).unwrap(), readiness);
}
}
#[test]
fn a_status_snapshot_never_carries_credentials() {
let status = Status {
enabled: true,
effective_startup: "on-demand".to_owned(),
state: State::Running,
display: Some(":7".to_owned()),
satellite_pid: Some(24017),
generation: Some(2),
readiness: Readiness::Verified,
recent_failures: 1,
restart_required: false,
last_error: None,
};
let value = status.to_value();
let text = value.to_string();
assert!(!text.contains("MIT-MAGIC-COOKIE-1"));
assert_eq!(value["satellite_pid"], json!(24017));
assert_eq!(value["display"], json!(":7"));
assert_eq!(value["state"], json!("running"));
assert_eq!(Status::from_value(value).unwrap(), status);
}
#[test]
fn an_unknown_field_from_a_newer_compositor_is_ignored() {
let decoded = Status::from_value(json!({
"enabled": true,
"effective_startup": "eager",
"state": "running",
"display": ":7",
"satellite_pid": 1,
"generation": 1,
"readiness": "verified",
"recent_failures": 0,
"restart_required": false,
"last_error": null,
"future_field": {"anything": true},
}))
.expect("a newer field must not break decoding");
assert_eq!(decoded.state, State::Running);
assert_eq!(decoded.effective_startup, "eager");
}
}