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:
1 parent
ed23949a95
commit
a6f83689f8
42 files changed
+7162
-1333
No files matched your search
@@ -136,8 +136,11 @@ impl cosmic::Application for Consent {
|
||||
Message::Opened(id) => {
|
||||
let parent = self.prompt.parent.clone();
|
||||
return cosmic::iced::window::run(id, move |window| {
|
||||
let parent = crate::parent::Parent::attach(window, &parent)?.map(Arc::new);
|
||||
Ok((parent, ModalMaterial::attach(window)))
|
||||
let attached = crate::parent::Parent::attach(window, &parent)?;
|
||||
if let Some(diagnostic) = attached.diagnostic {
|
||||
eprintln!("ferese portal: {diagnostic}");
|
||||
}
|
||||
Ok((attached.parent.map(Arc::new), ModalMaterial::attach(window)))
|
||||
})
|
||||
.map(|result| cosmic::Action::App(Message::Attached(result)));
|
||||
}
|
||||
|
||||
+150
-13
@@ -13,6 +13,62 @@ use wayland_protocols::xdg::foreign::zv2::client::zxdg_imported_v2;
|
||||
use wayland_protocols::xdg::foreign::zv2::client::zxdg_imported_v2::ZxdgImportedV2;
|
||||
use wayland_protocols::xdg::foreign::zv2::client::zxdg_importer_v2::ZxdgImporterV2;
|
||||
|
||||
const MAX_PARENT_IDENTIFIER: usize = 4096;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ParentAttachment {
|
||||
pub parent: Option<Parent>,
|
||||
pub diagnostic: Option<String>,
|
||||
}
|
||||
|
||||
fn classify_parent<'a>(parent: &'a str) -> ParentKind<'a> {
|
||||
if parent.is_empty() {
|
||||
return ParentKind::None;
|
||||
}
|
||||
if parent.len() > MAX_PARENT_IDENTIFIER {
|
||||
return ParentKind::Unsupported;
|
||||
}
|
||||
if let Some(handle) = parent.strip_prefix("wayland:") {
|
||||
return if handle.is_empty() {
|
||||
ParentKind::Unsupported
|
||||
} else {
|
||||
ParentKind::Wayland(handle)
|
||||
};
|
||||
}
|
||||
if let Some(handle) = parent.strip_prefix("x11:") {
|
||||
return if is_x11_window_identifier(handle) {
|
||||
ParentKind::X11
|
||||
} else {
|
||||
// A malformed XID is a protocol error, not an X11 parent to
|
||||
// degrade from: accepting arbitrary text here would let any string
|
||||
// be reported as an X11 window.
|
||||
ParentKind::Unsupported
|
||||
};
|
||||
}
|
||||
ParentKind::Unsupported
|
||||
}
|
||||
|
||||
/// Whether `identifier` is a syntactically valid X11 window identifier.
|
||||
///
|
||||
/// XIDs are unsigned 32-bit hex, optionally with the `0x` prefix, as X11
|
||||
/// clients write them (for example `0x2400003` or `1a2b3c`).
|
||||
fn is_x11_window_identifier(identifier: &str) -> bool {
|
||||
let digits = identifier
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| identifier.strip_prefix("0X"))
|
||||
.unwrap_or(identifier);
|
||||
|
||||
!digits.is_empty() && digits.len() <= 8 && digits.bytes().all(|byte| byte.is_ascii_hexdigit())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum ParentKind<'a> {
|
||||
None,
|
||||
Wayland(&'a str),
|
||||
X11,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Parent {
|
||||
connection: Connection,
|
||||
@@ -21,15 +77,25 @@ pub(crate) struct Parent {
|
||||
}
|
||||
|
||||
impl Parent {
|
||||
pub(crate) fn attach(window: &dyn cosmic::iced::window::Window, parent: &str) -> Result<Option<Self>, String> {
|
||||
pub(crate) fn attach(window: &dyn cosmic::iced::window::Window, parent: &str) -> Result<ParentAttachment, String> {
|
||||
use cosmic::iced::window::raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
if parent.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let handle = parent
|
||||
.strip_prefix("wayland:")
|
||||
.filter(|handle| !handle.is_empty() && handle.len() <= 4096)
|
||||
.ok_or("Unsupported portal parent window identifier")?;
|
||||
let handle = match classify_parent(parent) {
|
||||
ParentKind::None => {
|
||||
return Ok(ParentAttachment::default());
|
||||
}
|
||||
ParentKind::X11 => {
|
||||
return Ok(ParentAttachment {
|
||||
parent: None,
|
||||
diagnostic: Some(
|
||||
"showing this dialog unparented: X11 parent windows are not supported yet".to_owned(),
|
||||
),
|
||||
});
|
||||
}
|
||||
ParentKind::Unsupported => {
|
||||
return Err("Unsupported portal parent window identifier".into());
|
||||
}
|
||||
ParentKind::Wayland(handle) => handle,
|
||||
};
|
||||
let display = window.display_handle().map_err(|error| error.to_string())?;
|
||||
let surface = window.window_handle().map_err(|error| error.to_string())?;
|
||||
let (RawDisplayHandle::Wayland(display), RawWindowHandle::Wayland(surface)) =
|
||||
@@ -65,11 +131,14 @@ impl Parent {
|
||||
}
|
||||
imported.set_parent_of(&surface);
|
||||
connection.flush().map_err(|error| error.to_string())?;
|
||||
Ok(Some(Self {
|
||||
connection,
|
||||
imported,
|
||||
importer,
|
||||
}))
|
||||
Ok(ParentAttachment {
|
||||
parent: Some(Self {
|
||||
connection,
|
||||
imported,
|
||||
importer,
|
||||
}),
|
||||
diagnostic: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,3 +183,71 @@ impl Dispatch<ZxdgImportedV2, ()> for State {
|
||||
}
|
||||
|
||||
delegate_noop!(State: ignore ZxdgImporterV2);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn an_x11_parent_degrades_instead_of_failing_the_request() {
|
||||
assert!(matches!(classify_parent("x11:0x2400003"), ParentKind::X11));
|
||||
assert!(matches!(classify_parent("x11:1a2b3c"), ParentKind::X11));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_x11_parent_is_never_reinterpreted_as_a_wayland_handle() {
|
||||
match classify_parent("x11:0x2400003") {
|
||||
ParentKind::Wayland(handle) => panic!("an X11 parent must not become a Wayland handle: {handle}"),
|
||||
ParentKind::X11 => {}
|
||||
other => panic!("unexpected classification: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wayland_parents_are_still_imported() {
|
||||
assert!(matches!(
|
||||
classify_parent("wayland:handle-1"),
|
||||
ParentKind::Wayland("handle-1")
|
||||
));
|
||||
assert_eq!(classify_parent(""), ParentKind::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_x11_window_identifiers_are_errors_not_parents() {
|
||||
// Anything that is not an X11 window identifier must never be reported
|
||||
// as one: doing so would turn arbitrary caller text into a window.
|
||||
for parent in [
|
||||
"x11:",
|
||||
"x11:not-a-window",
|
||||
"x11:0xzzzz",
|
||||
"x11:0x2400003extra",
|
||||
"x11:9999999999",
|
||||
"x11:-1",
|
||||
] {
|
||||
assert!(
|
||||
matches!(classify_parent(parent), ParentKind::Unsupported),
|
||||
"{parent} must stay a hard error"
|
||||
);
|
||||
}
|
||||
|
||||
// Every form an X11 client actually produces is still accepted.
|
||||
for parent in ["x11:0x2400003", "x11:1a2b3c", "x11:0X1", "x11:ffffffff"] {
|
||||
assert!(
|
||||
matches!(classify_parent(parent), ParentKind::X11),
|
||||
"{parent} is a valid window identifier"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_identifiers_remain_hard_errors() {
|
||||
for parent in ["x11:", "wayland:", "dbus:", "not-a-parent", "X11:0x1"] {
|
||||
assert!(
|
||||
matches!(classify_parent(parent), ParentKind::Unsupported),
|
||||
"{parent} must stay a hard error"
|
||||
);
|
||||
}
|
||||
let huge = format!("wayland:{}", "a".repeat(MAX_PARENT_IDENTIFIER + 1));
|
||||
assert!(matches!(classify_parent(&huge), ParentKind::Unsupported));
|
||||
}
|
||||
}
|
||||
@@ -177,8 +177,14 @@ impl cosmic::Application for Picker {
|
||||
Message::WindowOpened(id) => {
|
||||
let parent = self.prompt.parent.clone();
|
||||
return cosmic::iced::window::run(id, move |window| {
|
||||
let parent = crate::parent::Parent::attach(window, &parent)?.map(std::sync::Arc::new);
|
||||
Ok((parent, ferese_theme_client::material::ModalMaterial::attach(window)))
|
||||
let attached = crate::parent::Parent::attach(window, &parent)?;
|
||||
if let Some(diagnostic) = attached.diagnostic {
|
||||
eprintln!("ferese portal: {diagnostic}");
|
||||
}
|
||||
Ok((
|
||||
attached.parent.map(std::sync::Arc::new),
|
||||
ferese_theme_client::material::ModalMaterial::attach(window),
|
||||
))
|
||||
})
|
||||
.map(|result| cosmic::Action::App(Message::Attached(result)));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user