diff --git a/src/platform/linux.rs b/src/platform/linux.rs index b2ff866c2..5ae180c62 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -374,6 +374,12 @@ pub struct WaylandDisplayInfo { pub height: i32, pub logical_size: Option<(i32, i32)>, pub refresh_rate: i32, + /// Output rotation in degrees (0/90/180/270), from `wl_output.geometry`. The mode keeps its + /// unrotated dimensions and `logical_size` arrives already swapped, so without this field a + /// rotated output is indistinguishable from a scaled one. Flipped variants map to their + /// rotation. Defaulted so a serialized snapshot from an older probe child still deserializes. + #[serde(default)] + pub transform: i32, } /// The isolated socket-probe fallback, in its own file and behind the `wayland_probe` feature so @@ -400,6 +406,20 @@ pub fn get_wayland_displays() -> ResultType> { } } +/// `wl_output::Transform` as degrees. Flipped variants report their rotation: the frame still +/// needs that turn to read upright, and a desktop compositor flipping an output without rotating +/// it is not a case any of ours can produce to test the mirror half against. +fn transform_degrees(t: sctk::reexports::client::protocol::wl_output::Transform) -> i32 { + use sctk::reexports::client::protocol::wl_output::Transform; + match t { + Transform::Normal | Transform::Flipped => 0, + Transform::_90 | Transform::Flipped90 => 90, + Transform::_180 | Transform::Flipped180 => 180, + Transform::_270 | Transform::Flipped270 => 270, + _ => 0, + } +} + fn collect_wayland_displays(conn: &Connection) -> ResultType> { struct WaylandEnv { registry_state: RegistryState, @@ -452,6 +472,7 @@ fn collect_wayland_displays(conn: &Connection) -> ResultType ResultType Option { mod tests { use super::*; + #[test] + fn test_transform_degrees_maps_all_eight_variants() { + use sctk::reexports::client::protocol::wl_output::Transform; + // Flipped variants report their rotation: the frame still needs that turn to read + // upright, and the mirror half has no producer among desktop compositors to test. + for (t, deg) in [ + (Transform::Normal, 0), + (Transform::_90, 90), + (Transform::_180, 180), + (Transform::_270, 270), + (Transform::Flipped, 0), + (Transform::Flipped90, 90), + (Transform::Flipped180, 180), + (Transform::Flipped270, 270), + ] { + assert_eq!(transform_degrees(t), deg, "{t:?}"); + } + } + + #[test] + fn test_display_info_without_transform_defaults_to_zero() { + // A snapshot serialized by an older probe child carries no transform field; it must + // deserialize with 0 rather than fail, or a greeter-side child update becomes a + // lockstep upgrade. + let old = r#"{"name":"HDMI-1","x":0,"y":0,"width":1920,"height":1080,"logical_size":null,"refresh_rate":60}"#; + let info: WaylandDisplayInfo = serde_json::from_str(old).unwrap(); + assert_eq!(info.transform, 0); + let roundtrip: WaylandDisplayInfo = + serde_json::from_str(&serde_json::to_string(&info).unwrap()).unwrap(); + assert_eq!(roundtrip.transform, 0); + } + #[test] fn test_run_cmds_trim_newline() { assert_eq!(run_cmds_trim_newline("echo -n 123").unwrap(), "123");