From 2d7a01cc5d5081dff9cdd81e238f8e4a5a242e46 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 29 May 2022 20:12:31 +0200 Subject: [PATCH] Begin ICCCM implementation WM_NAME, WM_ICON_NAME, WM_COLORMAP_WINDOWS, WM_CLIENT_MACHINE, WM_TRANSIENT_FOR Note that polymorphic text handling is not implemented correctly (especially for COMPOUND text) --- src/ewmh/mod.rs | 2 + src/icccm/atoms.rs | 44 +++++ src/icccm/connection.rs | 111 +++++++++++ src/icccm/mod.rs | 12 ++ src/icccm/proto/client_props.rs | 259 +++++++++++++++++++++++++ src/icccm/proto/macros/get_property.rs | 99 ++++++++++ src/icccm/proto/macros/mod.rs | 5 + src/icccm/proto/macros/set_property.rs | 128 ++++++++++++ src/icccm/proto/mod.rs | 21 ++ src/icccm/traits.rs | 156 +++++++++++++++ src/lib.rs | 3 + 11 files changed, 840 insertions(+) create mode 100644 src/icccm/atoms.rs create mode 100644 src/icccm/connection.rs create mode 100644 src/icccm/mod.rs create mode 100644 src/icccm/proto/client_props.rs create mode 100644 src/icccm/proto/macros/get_property.rs create mode 100644 src/icccm/proto/macros/mod.rs create mode 100644 src/icccm/proto/macros/set_property.rs create mode 100644 src/icccm/proto/mod.rs create mode 100644 src/icccm/traits.rs diff --git a/src/ewmh/mod.rs b/src/ewmh/mod.rs index 02389c6..1fa71e2 100644 --- a/src/ewmh/mod.rs +++ b/src/ewmh/mod.rs @@ -1,3 +1,5 @@ +//! see: + #[macro_use] mod traits; diff --git a/src/icccm/atoms.rs b/src/icccm/atoms.rs new file mode 100644 index 0000000..a8fa440 --- /dev/null +++ b/src/icccm/atoms.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +// TODO use xcb::atom_struct!{} for this? +const ATOM_NAMES: [&str; 1] = ["WM_COLORMAP_WINDOWS"]; + +/// Interned [`xcb::x::Atom`]s for the `icccm` protocol +/// +/// The ids for these atoms are created when the [`crate::icccm::Connection`] is established. Hence, +/// are bound to the [`crate::icccm::Connection`] and can only be retrieved from there. +/// +/// See also: [`icccm spec`](https://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html) +#[allow(non_snake_case)] +pub struct Atoms { + pub WM_COLORMAP_WINDOWS: xcb::x::Atom, +} + +impl Atoms { + pub(crate) fn intern(con: &xcb::Connection) -> Atoms { + let mut cookies: HashMap<&'static str, xcb::x::InternAtomCookie> = HashMap::new(); + + for atom in ATOM_NAMES { + let intern_atom = xcb::x::InternAtom { + only_if_exists: false, + name: atom.as_bytes(), + }; + + cookies.insert(atom, con.send_request(&intern_atom)); + } + + let interned_atoms: HashMap<&'static str, xcb::x::Atom> = cookies + .into_iter() + .map(|(atom_name, cookie)| (atom_name, con.wait_for_reply(cookie).unwrap())) + .map(|(atom_name, reply)| (atom_name, reply.atom())) + .collect(); + + Atoms::from_interned_atoms(interned_atoms) + } + + fn from_interned_atoms(mut atoms: HashMap<&'static str, xcb::x::Atom>) -> Atoms { + Atoms { + WM_COLORMAP_WINDOWS: atoms.remove("WM_COLORMAP_WINDOWS").unwrap(), + } + } +} diff --git a/src/icccm/connection.rs b/src/icccm/connection.rs new file mode 100644 index 0000000..d24d956 --- /dev/null +++ b/src/icccm/connection.rs @@ -0,0 +1,111 @@ +use crate::icccm::atoms::Atoms; +use crate::icccm::traits::{ + IcccmPropertyCookieChecked, IcccmPropertyCookieUnchecked, IcccmPropertyRequestUnchecked, + IcccmRequest, IcccmVoidRequestChecked, +}; + +/// The main `icccm` entry point +/// +/// `Connection` is a thin wrapper around [`xcb::Connection`]. It provides a +/// subset of the [`xcb::Connection`] API covering the functionality needed for +/// `icccm`. +/// +/// The provided subset works the same as the corresponding API in +/// [`xcb::Connection`]. That is, methods with the same name do the same thing. +/// The general usage pattern is the same for both crates. +/// +/// This wrapper is not strictly needed for `icccm` (ICCCM Atoms are pre-defined +/// in the core protocol [1]). However, to align with `ewmh` we provide the same +/// usage pattern/API as for `ewmh`. +/// +/// [1] +/// +pub struct Connection<'a> { + pub(crate) con: &'a xcb::Connection, + + /// Interned [`Atoms`] for the `icccm` protocol + pub atoms: Atoms, +} + +#[allow(dead_code)] +impl<'a> Connection<'a> { + pub fn connect(xcb_con: &'a xcb::Connection) -> Connection<'a> { + Connection { + con: xcb_con, + atoms: Atoms::intern(xcb_con), + } + } + + pub fn send_request<'b, R>(&self, request: &'b R) -> R::IcccmCookie + where + R: IcccmRequest<'b>, + { + request.send(self) + } + + pub fn send_request_checked<'b, R>(&self, request: &'b R) -> xcb::VoidCookieChecked + where + R: IcccmVoidRequestChecked<'b>, + { + request.send(self) + } + + pub fn send_request_unchecked(&self, request: &R) -> R::IcccmCookie + where + R: IcccmPropertyRequestUnchecked, + { + request.send(self) + } + + pub fn wait_for_reply(&self, cookie: C) -> C::Reply + where + C: IcccmPropertyCookieChecked, + { + let xcb_reply = self.con.wait_for_reply(cookie.inner()); + xcb_reply.unwrap().into() + } + + pub fn wait_for_reply_unchecked(&self, cookie: C) -> C::Reply + where + C: IcccmPropertyCookieUnchecked, + { + let xcb_reply = self.con.wait_for_reply_unchecked(cookie.inner()); + xcb_reply.unwrap().unwrap().into() + } +} + +#[cfg(test)] +mod tests { + use crate::icccm::proto::SetWmName; + + #[test] + fn number_of_desktops() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let icccm_con = crate::icccm::Connection::connect(&xcb_con); + + use xcb::XidNew; + + let window = unsafe { xcb::x::Window::new(0x2e0013e) }; + + let request = crate::icccm::proto::GetWmName::new(window); + let cookie = icccm_con.send_request(&request); + let reply = icccm_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn set_number_of_desktops() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let icccm_con = crate::icccm::Connection::connect(&xcb_con); + + use xcb::XidNew; + + let window = unsafe { xcb::x::Window::new(0x4600003) }; + let name = String::from("NEW NAME").into_bytes(); + + let request = SetWmName::new(window, xcb::x::ATOM_STRING, name); + let cookie = icccm_con.send_request_checked(&request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } +} diff --git a/src/icccm/mod.rs b/src/icccm/mod.rs new file mode 100644 index 0000000..c91e919 --- /dev/null +++ b/src/icccm/mod.rs @@ -0,0 +1,12 @@ +//! Request and response definitions for the icccm protocol +//! +//! see: + +mod traits; + +mod atoms; + +mod connection; +pub use connection::Connection; + +pub mod proto; diff --git a/src/icccm/proto/client_props.rs b/src/icccm/proto/client_props.rs new file mode 100644 index 0000000..78fda87 --- /dev/null +++ b/src/icccm/proto/client_props.rs @@ -0,0 +1,259 @@ +//! Client Properties +//! +//! see: + +#![allow(dead_code)] + +use xcb::{Xid, XidNew}; + +use crate::icccm::traits::*; +use crate::icccm::Connection; + +use paste::paste; // Needed for macros + +// WM_NAME, TEXT +// {{{ +#[derive(Debug)] +pub struct GetWmNameReply { + pub name: String, +} + +impl From for GetWmNameReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let x = reply.value::().into(); + GetWmNameReply { + // TODO Compound string + name: String::from_utf8(x).unwrap(), + } + } +} + +icccm_get_property! { + request=GetWmName{ + window: client, + property: ATOM_WM_NAME, + xtype: ATOM_ANY + }, + reply=GetWmNameReply +} + +pub struct SetWmName { + window: xcb::x::Window, + encoding: xcb::x::Atom, + data: Vec, +} + +impl SetWmName { + pub fn new(window: xcb::x::Window, encoding: xcb::x::Atom, name: Vec) -> SetWmName { + SetWmName { + window: window, + encoding: encoding, + data: name, + } + } +} + +icccm_set_text_property! { + request=SetWmName{ + property: ATOM_WM_NAME + } +} +// }}} + +// WM_ICON_NAME, TEXT +// {{{ +#[derive(Debug)] +pub struct GetWmIconNameReply { + pub name: String, +} + +impl From for GetWmIconNameReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let x = reply.value::().into(); + GetWmIconNameReply { + // TODO Compound string + name: String::from_utf8(x).unwrap(), + } + } +} + +icccm_get_property! { + request=GetWmIconName{ + window: client, + property: ATOM_WM_ICON_NAME, + xtype: ATOM_ANY + }, + reply=GetWmIconNameReply +} + +pub struct SetWmIconName { + window: xcb::x::Window, + encoding: xcb::x::Atom, + data: Vec, +} + +impl SetWmIconName { + pub fn new(window: xcb::x::Window, encoding: xcb::x::Atom, name: Vec) -> SetWmIconName { + SetWmIconName { + window: window, + encoding: encoding, + data: name, + } + } +} + +icccm_set_text_property! { + request=SetWmIconName{ + property: ATOM_WM_ICON_NAME + } +} +// }}} + +// WM_COLORMAP_WINDOWS, WINDOW[]/32 +// {{{ +#[derive(Debug)] +pub struct GetWmColorMapWindowsReply { + pub windows: Vec, +} + +impl From for GetWmColorMapWindowsReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetWmColorMapWindowsReply { + windows: reply.value().into(), + } + } +} + +icccm_get_property! { + request=GetWmColorMapWindows { + window: client, + property: WM_COLORMAP_WINDOWS, + xtype: ATOM_WINDOW + }, + reply=GetWmIconNameReply +} + +pub struct SetWmColorMapWindows { + window: xcb::x::Window, + data: Vec, +} + +impl SetWmColorMapWindows { + pub fn new( + window: xcb::x::Window, + colormap_windows: Vec, + ) -> SetWmColorMapWindows { + SetWmColorMapWindows { + window: window, + data: colormap_windows, + } + } +} + +icccm_set_property! { + request=SetWmColorMapWindows{ + property: con.WM_COLORMAP_WINDOWS, + xtype: ATOM_WINDOW + } +} +// }}} + +// WM_CLIENT_MACHINE, TEXT +// {{{ +#[derive(Debug)] +pub struct GetWmClientMachineReply { + pub name: String, +} + +impl From for GetWmClientMachineReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let x = reply.value::().into(); + GetWmClientMachineReply { + // TODO Compound string + name: String::from_utf8(x).unwrap(), + } + } +} + +icccm_get_property! { + request=GetWmClientMachine{ + window: client, + property: ATOM_WM_CLIENT_MACHINE, + xtype: ATOM_ANY + }, + reply=GetWmClientMachineReply +} + +pub struct SetWmClientMachine { + window: xcb::x::Window, + encoding: xcb::x::Atom, + data: Vec, +} + +impl SetWmClientMachine { + pub fn new( + window: xcb::x::Window, + encoding: xcb::x::Atom, + name: Vec, + ) -> SetWmClientMachine { + SetWmClientMachine { + window: window, + encoding: encoding, + data: name, + } + } +} + +icccm_set_text_property! { + request=SetWmClientMachine{ + property: ATOM_WM_CLIENT_MACHINE + } +} +// }}} + +// WM_TRANSIENT_FOR, WINDOW/32 +// {{{ +#[derive(Debug)] +pub struct GetWmTransientForReply { + pub window: xcb::x::Window, +} + +impl From for GetWmTransientForReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetWmTransientForReply { + window: unsafe { xcb::x::Window::new(reply.value::()[0]) }, + } + } +} + +icccm_get_property! { + request=GetWmTransientFor{ + window: client, + property: ATOM_WM_TRANSIENT_FOR, + xtype: ATOM_WINDOW + }, + reply=GetWmTransientForReply +} + +pub struct SetWmTransientFor { + window: xcb::x::Window, + data: Vec, +} + +impl SetWmTransientFor { + // TODO better name for second window + pub fn new(window: xcb::x::Window, window2: xcb::x::Window) -> SetWmTransientFor { + SetWmTransientFor { + window: window, + data: vec![window2], + } + } +} + +icccm_set_property! { + request=SetWmTransientFor{ + property: ATOM_WM_CLIENT_MACHINE, + xtype: ATOM_WINDOW + } +} +// }}} diff --git a/src/icccm/proto/macros/get_property.rs b/src/icccm/proto/macros/get_property.rs new file mode 100644 index 0000000..e0badf6 --- /dev/null +++ b/src/icccm/proto/macros/get_property.rs @@ -0,0 +1,99 @@ +macro_rules! _get_property_cookies { + (@icccm_priv $request:ident, $reply:ident) => { + paste! { + impl IcccmCookie for [<$request Cookie>] { + type XcbCookie = xcb::x::GetPropertyCookie; + } + + impl IcccmPropertyCookieChecked for [<$request Cookie>] { + type Reply = $reply; + + fn inner(self) -> xcb::x::GetPropertyCookie { + self.0 + } + } + + impl IcccmPropertyCookieUnchecked for [<$request CookieUnchecked>] { + type Reply = $reply; + + fn inner(self) -> xcb::x::GetPropertyCookieUnchecked { + self.0 + } + } + } + }; +} + +macro_rules! _get_property_request { + //TODO do we need the `client` here? Probs just c&p + (@icccm_priv client, WM_COLORMAP_WINDOWS, $xtype:ident) => { + fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: self.0, + property: con.atoms.WM_COLORMAP_WINDOWS, + r#type: xcb::x::$xtype, + long_offset: 0, + long_length: u32::MAX, + } + } + }; + + (@icccm_priv client, $property:ident, $xtype:ident) => { + fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: self.0, + property: xcb::x::$property, + r#type: xcb::x::$xtype, + long_offset: 0, + long_length: u32::MAX, + } + } + }; +} + +macro_rules! icccm_get_property { + (request=$request:ident{ + window: $window:ident, + property: $property:ident, + xtype: $xtype: ident + }, + reply=$reply:ident) => { + paste!{ + pub struct $request(xcb::x::Window); + pub struct [<$request Cookie>](xcb::x::GetPropertyCookie); + pub struct [<$request CookieUnchecked>](xcb::x::GetPropertyCookieUnchecked); + + impl $request { + pub fn new(window: xcb::x::Window) -> $request { + $request(window) + } + } + + impl<'a> IcccmRequest<'a> for $request { + type XcbRequest = xcb::x::GetProperty; + type IcccmCookie = [<$request Cookie>]; + + _get_property_request! {@icccm_priv $window, $property, $xtype} + + fn convert_cookie(&'a self, xcb_cookie: xcb::x::GetPropertyCookie) -> Self::IcccmCookie { + [<$request Cookie>](xcb_cookie) + } + } + + impl IcccmPropertyRequestUnchecked for $request { + paste!{type IcccmCookie = [<$request CookieUnchecked>];} + + #[rustfmt::skip] + fn convert_cookie(&self, xcb_cookie: xcb::x::GetPropertyCookieUnchecked) -> Self::IcccmCookie { + [<$request CookieUnchecked>](xcb_cookie) + } + + _get_property_request! {@icccm_priv $window, $property, $xtype} + } + + _get_property_cookies! {@icccm_priv $request, $reply} + } + }; +} diff --git a/src/icccm/proto/macros/mod.rs b/src/icccm/proto/macros/mod.rs new file mode 100644 index 0000000..6993b87 --- /dev/null +++ b/src/icccm/proto/macros/mod.rs @@ -0,0 +1,5 @@ +#[macro_use] +mod get_property; + +#[macro_use] +mod set_property; diff --git a/src/icccm/proto/macros/set_property.rs b/src/icccm/proto/macros/set_property.rs new file mode 100644 index 0000000..cb47c7c --- /dev/null +++ b/src/icccm/proto/macros/set_property.rs @@ -0,0 +1,128 @@ +macro_rules! icccm_set_text_property { + (request=$request:ident{ + property: $property:ident + }) => { + impl<'a, T: 'a + xcb::x::PropEl> IcccmRequest<'a> for $request { + type XcbRequest = xcb::x::ChangeProperty<'a, T>; + type IcccmCookie = xcb::VoidCookie; + + fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, T> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: xcb::x::$property, + r#type: self.encoding, + data: &self.data, + } + } + + fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::IcccmCookie { + xcb_cookie + } + } + + impl<'a, T: 'a + xcb::x::PropEl> IcccmVoidRequestChecked<'a> for $request { + type XcbRequest = xcb::x::ChangeProperty<'a, T>; + + fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, T> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: xcb::x::$property, + r#type: self.encoding, + data: &self.data, + } + } + } + }; +} + +macro_rules! icccm_set_property { + (request=$request:ident{ + property: con.$property:ident, + xtype: $type:ident + }) => { + impl<'a> IcccmRequest<'a> for $request { + type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>; + type IcccmCookie = xcb::VoidCookie; + + fn xcb_request( + &'a self, + con: &Connection, + ) -> xcb::x::ChangeProperty<'a, xcb::x::Window> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: con.atoms.$property, + r#type: xcb::x::$type, + data: &self.data, + } + } + + fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::IcccmCookie { + xcb_cookie + } + } + + impl<'a> IcccmVoidRequestChecked<'a> for $request { + type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>; + + fn xcb_request( + &'a self, + con: &Connection, + ) -> xcb::x::ChangeProperty<'a, xcb::x::Window> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: con.atoms.$property, + r#type: xcb::x::$type, + data: &self.data, + } + } + } + }; + + (request=$request:ident{ + property: $property:ident, + xtype: $type:ident + }) => { + impl<'a> IcccmRequest<'a> for $request { + type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>; + type IcccmCookie = xcb::VoidCookie; + + fn xcb_request( + &'a self, + con: &Connection, + ) -> xcb::x::ChangeProperty<'a, xcb::x::Window> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: xcb::x::$property, + r#type: xcb::x::$type, + data: &self.data, + } + } + + fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::IcccmCookie { + xcb_cookie + } + } + + impl<'a> IcccmVoidRequestChecked<'a> for $request { + type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>; + + fn xcb_request( + &'a self, + con: &Connection, + ) -> xcb::x::ChangeProperty<'a, xcb::x::Window> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: xcb::x::$property, + r#type: xcb::x::$type, + data: &self.data, + } + } + } + }; +} diff --git a/src/icccm/proto/mod.rs b/src/icccm/proto/mod.rs new file mode 100644 index 0000000..f0305fa --- /dev/null +++ b/src/icccm/proto/mod.rs @@ -0,0 +1,21 @@ +//! Request and response definitions for the icccm protocol +//! +//! # Example +//! ``` +//! let connection = xcb_wm::icccm::Connection(); +//! let request = xcb_wm::ewmh::proto::GetSupported; +//! let cookie = connection.send_request(request); +//! let response = connection.wait_for_reply(cookie); +//! ``` + +// mod application_props; +// #[allow(unused)] +// mod root_props; +// +// pub use application_props::*; +// pub use root_props::*; +#[macro_use] +mod macros; + +mod client_props; +pub use client_props::*; diff --git a/src/icccm/traits.rs b/src/icccm/traits.rs new file mode 100644 index 0000000..bd87226 --- /dev/null +++ b/src/icccm/traits.rs @@ -0,0 +1,156 @@ +use crate::icccm::connection::Connection; + +/// Default for a request sent by [`Connection::send_request`] +/// +/// For `GetProperty` request (i.e. requests that expect a reply) this is usually +/// a checked request. +/// For `SetProperty` and `ClientMessage` requests (i.e. requests that expect void/no reply) this +/// is usually an unchecked request. +/// +/// This is a bit mind-bending and makes these traits hard to grasp. This was however done to align +/// with how the parent [`xcb`] crate works. +pub trait IcccmRequest<'a> { + /// The underlying [`xcb::Request`] used to do the actual heavy lifting + type XcbRequest: xcb::Request; + + /// The icccm wrapper for the [`xcb::Request::Cookie`] + type IcccmCookie: IcccmCookie::Cookie>; + + /// Construct the [`xcb::Request`]. This is implementation specific. + fn xcb_request(&'a self, con: &Connection) -> Self::XcbRequest; + + /// Convert the [`xcb::Request::Cookie`] to the icccm cookie wrapper + fn convert_cookie( + &'a self, + xcb_cookie: ::Cookie, + ) -> Self::IcccmCookie; + + /// Default implementation. Delegate the request to the [`xcb::Connection`] and wrap the + /// response in the icccm cookie wrapper. + /// + /// There is usually no need to override the provided default implementation. + fn send(&'a self, con: &Connection) -> Self::IcccmCookie { + let xcb_request = self.xcb_request(con); + let xcb_cookie = con.con.send_request(&xcb_request); + self.convert_cookie(xcb_cookie) + } +} + +/// Requests that can be sent by [`Connection::send_request_checked`] +/// +/// To quote the parent [`xcb`]: +/// > Checked requests do not expect a reply, but the returned cookie can be used to check for +/// > errors using Connection::check_request. +/// +/// For [`xcb-wm`] this means either `SetProperty` or `ClientMessage` requests. +pub trait IcccmVoidRequestChecked<'a> { + type XcbRequest: xcb::RequestWithoutReply; + + /// Construct the [`xcb::Request`]. This is implementation specific. + fn xcb_request(&'a self, con: &Connection) -> Self::XcbRequest; + + /// Default implementation. Delegate the request to the [`xcb::Connection`]. + /// + /// Since we don't need to convert the reply we just re-use the [`xcb::VoidCookieChecked`] + /// without wrapping it. + /// + /// There is usually no need to override the provided default implementation. + fn send(&'a self, con: &Connection) -> xcb::VoidCookieChecked { + let xcb_request = self.xcb_request(con); + con.con.send_request_checked(&xcb_request) + } +} + +/// Requests that can be sent by [`Connection::send_request_unchecked`] +/// +/// To quote the parent [`xcb`]: +/// > Unchecked requests expect a reply that is to be retrieved by Connection::wait_for_reply_unchecked. +/// > Unchecked means that the error is not checked when the reply is fetched. Instead, the error will +/// > be sent to the event loop +/// +/// For [`xcb-wm`] this means a `GetProperty` requests. +pub trait IcccmPropertyRequestUnchecked { + type IcccmCookie: IcccmPropertyCookieUnchecked; + + /// Construct the [`xcb::Request`]. This is implementation specific. + fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty; + + /// Convert the [`xcb::Request::Cookie`] to the ewmh cookie wrapper + fn convert_cookie(&self, xcb_cookie: xcb::x::GetPropertyCookieUnchecked) -> Self::IcccmCookie; + + /// Default implementation. Delegate the request to the [`xcb::Connection`]. + /// + /// Returns a wrapped cookie that tracks the IcccmReply type which is needed + /// to convert the reply after it is retrieved via [`Connection::wait_for_reply_unchecked`]. + /// + /// There is usually no need to override the provided default implementation. + fn send(&self, con: &Connection) -> Self::IcccmCookie { + let xcb_request = self.xcb_request(con); + let xcb_cookie = con.con.send_request_unchecked(&xcb_request); + self.convert_cookie(xcb_cookie) + } +} + +/// Most generic icccm wrapper for an [`xcb::Cookie`] +/// +/// This is needed for [`IcccmRequest`] which basically cuts through +/// all traits. I.e. it is an _unchecked_ request for void requests +/// but a _checked_ request for requests with replies. +/// +/// At the same time it may have a _response_ for reply requests or it +/// may have no _response_ for void requests. +pub trait IcccmCookie { + /// The wrapped [`xcb::Cookie`] + type XcbCookie: xcb::Cookie; +} + +/// Blanket impl for [`xcb::VoidCookie`] (basically unchecked void cookies) +/// +/// This is implemented here because there are no special icccm cookie wrapper +/// for SetProperty and ClientMessage requests needed. [`xcb::VoidCookie`] satisfies +/// all that is needed for [`icccm`]. In order to use it with [`IcccmRequest`] we need +/// this impl. +impl IcccmCookie for xcb::VoidCookie { + type XcbCookie = xcb::VoidCookie; +} + +/// icccm wrapper for checked GetProperty requests +/// +/// This is needed for 2 purposes: +/// - Carry the reply type from the request to the retrieval of the response +/// - Restrict the methods that can be called with it, i.e. [`Connection::wait_for_reply`] +pub trait IcccmPropertyCookieChecked { + type Reply: IcccmPropertyReply; + + /// Retrieve the inner [`xcb::Cookie`] + /// + /// This is needed for sending a [`xcb::Connection::wait_reply`] request which + /// expects the xcb cookie + fn inner(self) -> xcb::x::GetPropertyCookie; +} + +/// icccm wrapper for unchecked GetProperty requests +/// +/// This is needed for 2 purposes: +/// - Carry the reply type from the request to the retrieval of the response +/// - Restrict the methods that can be called with it, i.e. [`Connection::wait_for_reply_unchecked`] +pub trait IcccmPropertyCookieUnchecked { + type Reply: IcccmPropertyReply; + + /// Retrieve the inner [`xcb::Cookie`] + /// + /// This is needed for sending a [`xcb::Connection::wait_reply_unchecked`] request which + /// expects the xcb cookie + fn inner(self) -> xcb::x::GetPropertyCookieUnchecked; +} + +/// Marker trait with blanket implementation for everything that implements +/// [`From {} +impl IcccmPropertyReply for T where T: From {} diff --git a/src/lib.rs b/src/lib.rs index ed64637..5011458 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,9 @@ #[cfg(feature = "ewmh")] pub mod ewmh; +#[cfg(feature = "icccm")] +pub mod icccm; + #[cfg(test)] mod tests { #[test]