From bee554c057fea955c800f2fcddda8c06acf3c5bd Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Wed, 18 May 2022 23:10:54 +0200 Subject: [PATCH] Implement new macros, restructure in sub-modules --- src/ewmh/atoms.rs | 1 + src/ewmh/connection.rs | 8 +- src/ewmh/mod.rs | 5 +- src/ewmh/proto/macros.rs | 237 ++++++++++++++++++++++++++++++ src/ewmh/proto/mod.rs | 17 ++- src/ewmh/proto/test_props.rs | 277 ++++++++--------------------------- src/lib.rs | 1 + 7 files changed, 324 insertions(+), 222 deletions(-) create mode 100644 src/ewmh/proto/macros.rs diff --git a/src/ewmh/atoms.rs b/src/ewmh/atoms.rs index d32224e..519c241 100644 --- a/src/ewmh/atoms.rs +++ b/src/ewmh/atoms.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +// TODO use xcb::atom_struct!{} for this? const ATOM_NAMES: [&str; 82] = [ "_NET_SUPPORTED", "_NET_CLIENT_LIST", diff --git a/src/ewmh/connection.rs b/src/ewmh/connection.rs index ec7dc36..b55c342 100644 --- a/src/ewmh/connection.rs +++ b/src/ewmh/connection.rs @@ -78,7 +78,7 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::GetSupported; + let request = crate::ewmh::proto::GetSupported; let cookie = ewmh_con.send_request(&request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); @@ -205,7 +205,7 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::GetDesktopNames {}; + let request = crate::ewmh::proto::GetDesktopNames {}; let cookie = ewmh_con.send_request(&request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); @@ -216,7 +216,7 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::SetDesktopNames::new(vec!["X", "Y", "Z"]); + let request = crate::ewmh::proto::SetDesktopNames::new(vec!["A", "B", "Z"]); let cookie = ewmh_con.send_request_checked(&request); let reply = xcb_con.check_request(cookie); println!("{:?}", reply); @@ -303,7 +303,7 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::SetShowingDesktop::new(&ewmh_con, true); + let request = crate::ewmh::proto::SetShowingDesktop::new(&ewmh_con, true); let cookie = ewmh_con.send_request_checked(&request); let reply = xcb_con.check_request(cookie); println!("{:?}", reply); diff --git a/src/ewmh/mod.rs b/src/ewmh/mod.rs index d029a12..02389c6 100644 --- a/src/ewmh/mod.rs +++ b/src/ewmh/mod.rs @@ -3,11 +3,8 @@ mod traits; mod atoms; mod connection; -mod proto; pub use atoms::Atoms; pub use connection::Connection; -pub use proto::net_desktop_names::*; -pub use proto::net_showing_desktop::*; -pub use proto::net_supported::*; +pub mod proto; diff --git a/src/ewmh/proto/macros.rs b/src/ewmh/proto/macros.rs new file mode 100644 index 0000000..be437ba --- /dev/null +++ b/src/ewmh/proto/macros.rs @@ -0,0 +1,237 @@ +macro_rules! _get_property_base { + ($reply:ident, $cookie:ident, $cookie_unchecked:ident) => { + impl EwmhCookie for $cookie { + type XcbCookie = xcb::x::GetPropertyCookie; + } + + impl EwmhPropertyCookieChecked for $cookie { + type Reply = $reply; + + fn inner(self) -> xcb::x::GetPropertyCookie { + self.0 + } + } + + impl EwmhPropertyCookieUnchecked for $cookie_unchecked { + type Reply = $reply; + + fn inner(self) -> xcb::x::GetPropertyCookieUnchecked { + self.0 + } + } + }; +} + +macro_rules! _get_property_request { + (root_window, $property:ident, UTF8_STRING) => { + fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$property, + r#type: con.atoms.UTF8_STRING, + long_offset: 0, + long_length: u32::MAX, + } + } + }; + + (root_window, $property:ident, $xtype:ident) => { + fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$property, + r#type: xcb::x::$xtype, + long_offset: 0, + long_length: u32::MAX, + } + } + }; + + ($window:ident, $property:ident, UTF8_STRING) => { + fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: $window, + property: con.atoms.$property, + r#type: con.atoms.UTF8_STRING, + long_offset: 0, + long_length: u32::MAX, + } + } + }; + + ($window:ident, $property:ident, $xtype:ident) => { + fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: $window, + property: con.atoms.$property, + r#type: xcb::x::$xtype, + long_offset: 0, + long_length: u32::MAX, + } + } + }; +} + +macro_rules! ewmh_get_property { + (request=$request:ident{ + window: $window:ident, + property: $property:ident, + xtype: $xtype: ident + }, + reply=$reply:ident, + cookie=$cookie:ident, + cookie_unchecked=$cookie_unchecked:ident) => { + impl<'a> EwmhRequest<'a> for $request { + type XcbRequest = xcb::x::GetProperty; + type EwmhCookie = $cookie; + + _get_property_request! {$window, $property, $xtype} + + fn convert_cookie(&'a self, xcb_cookie: xcb::x::GetPropertyCookie) -> Self::EwmhCookie { + $cookie(xcb_cookie) + } + } + + impl EwmhPropertyRequestUnchecked for $request { + type EwmhCookie = $cookie_unchecked; + + + #[rustfmt::skip] + fn convert_cookie(&self,xcb_cookie: xcb::x::GetPropertyCookieUnchecked,) -> Self::EwmhCookie { + $cookie_unchecked(xcb_cookie) + } + + _get_property_request! {$window, $property, $xtype} + } + + _get_property_base! {$reply, $cookie, $cookie_unchecked} + }; +} + +macro_rules! _set_property_base { + (root_window, $property:ident, UTF8_STRING) => { + fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, u8> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$property, + r#type: con.atoms.UTF8_STRING, + data: &self.data, + } + } + }; + + (root_window, $property:ident, $xtype:ident) => { + fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, u8> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$property, + r#type: xcb::x::$xtype, + data: &self.data, + } + } + }; + + ($window:ident, $property:ident, UTF8_STRING) => { + fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, u8> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: $window, + property: con.atoms.$property, + r#type: con.atoms.UTF8_STRING, + data: &self.data, + } + } + }; + + ($window:ident, $property:ident, $xtype:ident) => { + fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, u8> { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: $window, + property: con.atoms.$property, + r#type: xcb::x::$xtype, + data: &self.data, + } + } + }; +} + +macro_rules! ewmh_set_property { + (request=$request:ident{ + window: $window:ident, + property: $property:ident, + xtype: $xtype: ident + }) => { + impl<'a> EwmhRequest<'a> for $request { + // TODO fixing to u8 is probably not flexible enough + type XcbRequest = xcb::x::ChangeProperty<'a, u8>; + type EwmhCookie = xcb::VoidCookie; + + _set_property_base! {$window, $property, $xtype} + + fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::EwmhCookie { + xcb_cookie + } + } + + impl<'a> EwmhVoidRequestChecked<'a> for $request { + type XcbRequest = xcb::x::ChangeProperty<'a, u8>; + + _set_property_base! {$window, $property, $xtype} + } + }; +} + +macro_rules! _client_message_base { + (root_window) => { + fn xcb_request(&'a self, con: &Connection) -> Self::XcbRequest { + xcb::x::SendEvent { + propagate: false, + destination: xcb::x::SendEventDest::Window( + con.con.get_setup().roots().next().unwrap().root(), + ), + event_mask: xcb::x::EventMask::SUBSTRUCTURE_NOTIFY + | xcb::x::EventMask::SUBSTRUCTURE_REDIRECT, + event: &self.client_message, + } + } + }; + + ($destination: ident) => { + fn xcb_request(&'a self, con: &Connection) -> Self::XcbRequest { + xcb::x::SendEvent { + propagate: false, + destination: xcb::x::SendEventDest::Window($destination), + event_mask: xcb::x::EventMask::SUBSTRUCTURE_NOTIFY + | xcb::x::EventMask::SUBSTRUCTURE_REDIRECT, + event: &self.client_message, + } + } + }; +} + +macro_rules! ewmh_client_message { + (request=$request:ident{destination: $destination:ident}) => { + impl<'a> EwmhRequest<'a> for $request { + type XcbRequest = xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent>; + type EwmhCookie = xcb::VoidCookie; + + _client_message_base! {$destination} + + fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::EwmhCookie { + xcb_cookie + } + } + + impl<'a> EwmhVoidRequestChecked<'a> for $request { + type XcbRequest = xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent>; + _client_message_base! {$destination} + } + }; +} diff --git a/src/ewmh/proto/mod.rs b/src/ewmh/proto/mod.rs index d8df3a7..b74a10e 100644 --- a/src/ewmh/proto/mod.rs +++ b/src/ewmh/proto/mod.rs @@ -1,9 +1,24 @@ +//! Request and response definitions for the ewmh protocol +//! +//! # Example +//! ``` +//! let connection = xcb_wm::ewmh::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 test_props; -pub(crate) use test_props::*; + +pub use test_props::net_desktop_names::*; +pub use test_props::net_showing_desktop::*; +pub use test_props::net_supported::*; diff --git a/src/ewmh/proto/test_props.rs b/src/ewmh/proto/test_props.rs index 914276d..c4c9c68 100644 --- a/src/ewmh/proto/test_props.rs +++ b/src/ewmh/proto/test_props.rs @@ -1,3 +1,34 @@ +fn x_buffer_to_strings(xbuf: &[u8]) -> Vec { + let mut vals = vec![]; + let mut buf = vec![]; + + for b in xbuf { + if *b != 0x00 { + buf.push(*b) + } else if !buf.is_empty() { + vals.push(String::from_utf8(buf.clone()).unwrap()); + buf.clear(); + } else { + buf.clear(); + } + } + + vals +} + +fn strings_to_x_buffer(strings: Vec<&str>) -> Vec { + let mut data = vec![]; + + // flatten `strings` into a continuous, NULL separated, array of bytes + for s in strings { + let mut bs = s.as_bytes().to_owned(); + bs.push(0b00); + data.extend(bs) + } + + data +} + pub(crate) mod net_supported { use crate::ewmh::traits::*; use crate::ewmh::Connection; @@ -13,68 +44,6 @@ pub(crate) mod net_supported { pub atoms: Vec, } - impl<'a> EwmhRequest<'a> for GetSupported { - type XcbRequest = xcb::x::GetProperty; - type EwmhCookie = GetSupportedCookie; - - fn xcb_request(&self, con: &Connection) -> Self::XcbRequest { - xcb::x::GetProperty { - delete: false, - window: con.con.get_setup().roots().next().unwrap().root(), - property: con.atoms._NET_SUPPORTED, - r#type: xcb::x::ATOM_ATOM, - long_offset: 0, - long_length: u32::MAX, - } - } - - fn convert_cookie(&'a self, xcb_cookie: xcb::x::GetPropertyCookie) -> Self::EwmhCookie { - GetSupportedCookie(xcb_cookie) - } - } - - impl EwmhPropertyRequestUnchecked for GetSupported { - type EwmhCookie = GetSupportedCookieUnchecked; - - fn convert_cookie( - &self, - xcb_cookie: xcb::x::GetPropertyCookieUnchecked, - ) -> Self::EwmhCookie { - GetSupportedCookieUnchecked(xcb_cookie) - } - - fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { - xcb::x::GetProperty { - delete: false, - window: con.con.get_setup().roots().next().unwrap().root(), - property: con.atoms._NET_SUPPORTED, - r#type: xcb::x::ATOM_ATOM, - long_offset: 0, - long_length: u32::MAX, - } - } - } - - impl EwmhCookie for GetSupportedCookie { - type XcbCookie = xcb::x::GetPropertyCookie; - } - - impl EwmhPropertyCookieChecked for GetSupportedCookie { - type Reply = GetSupportedReply; - - fn inner(self) -> xcb::x::GetPropertyCookie { - self.0 - } - } - - impl EwmhPropertyCookieUnchecked for GetSupportedCookieUnchecked { - type Reply = GetSupportedReply; - - fn inner(self) -> xcb::x::GetPropertyCookieUnchecked { - self.0 - } - } - impl From for GetSupportedReply { fn from(reply: xcb::x::GetPropertyReply) -> Self { GetSupportedReply { @@ -82,9 +51,21 @@ pub(crate) mod net_supported { } } } + + ewmh_get_property! { + request=GetSupported{ + window: root_window, + property: _NET_SUPPORTED, + xtype: ATOM_ATOM + }, + reply=GetSupportedReply, + cookie=GetSupportedCookie, + cookie_unchecked=GetSupportedCookieUnchecked + } } pub(crate) mod net_desktop_names { + use crate::ewmh::proto::test_props::{strings_to_x_buffer, x_buffer_to_strings}; use crate::ewmh::traits::*; use crate::ewmh::Connection; @@ -99,139 +80,42 @@ pub(crate) mod net_desktop_names { pub desktop_names: Vec, } - impl<'a> EwmhRequest<'a> for GetDesktopNames { - type XcbRequest = xcb::x::GetProperty; - type EwmhCookie = GetDesktopNamesCookie; - - fn xcb_request(&self, con: &Connection) -> Self::XcbRequest { - xcb::x::GetProperty { - delete: false, - window: con.con.get_setup().roots().next().unwrap().root(), - property: con.atoms._NET_DESKTOP_NAMES, - r#type: con.atoms.UTF8_STRING, - long_offset: 0, - long_length: u32::MAX, - } - } - - fn convert_cookie(&'a self, xcb_cookie: xcb::x::GetPropertyCookie) -> Self::EwmhCookie { - GetDesktopNamesCookie(xcb_cookie) - } - } - - impl EwmhPropertyRequestUnchecked for GetDesktopNames { - type EwmhCookie = GetDesktopNamesCookieUnchecked; - - fn convert_cookie( - &self, - xcb_cookie: xcb::x::GetPropertyCookieUnchecked, - ) -> Self::EwmhCookie { - GetDesktopNamesCookieUnchecked(xcb_cookie) - } - - fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty { - xcb::x::GetProperty { - delete: false, - window: con.con.get_setup().roots().next().unwrap().root(), - property: con.atoms._NET_DESKTOP_NAMES, - r#type: con.atoms.UTF8_STRING, - long_offset: 0, - long_length: u32::MAX, - } - } - } - - impl EwmhCookie for GetDesktopNamesCookie { - type XcbCookie = xcb::x::GetPropertyCookie; - } - - impl EwmhPropertyCookieChecked for GetDesktopNamesCookie { - type Reply = GetDesktopNamesReply; - - fn inner(self) -> xcb::x::GetPropertyCookie { - self.0 - } - } - - impl EwmhPropertyCookieUnchecked for GetDesktopNamesCookieUnchecked { - type Reply = GetDesktopNamesReply; - - fn inner(self) -> xcb::x::GetPropertyCookieUnchecked { - self.0 - } - } - impl From for GetDesktopNamesReply { fn from(reply: xcb::x::GetPropertyReply) -> Self { - let mut vals = vec![]; - let mut buf = vec![]; - - for b in reply.value::() { - if *b != 0x00 { - buf.push(*b) - } else if !buf.is_empty() { - vals.push(String::from_utf8(buf.clone()).unwrap()); - buf.clear(); - } else { - buf.clear(); - } - } - GetDesktopNamesReply { - desktop_names: vals, + desktop_names: x_buffer_to_strings(reply.value::()), } } } + ewmh_get_property! { + request=GetDesktopNames{ + window: root_window, + property: _NET_DESKTOP_NAMES, + xtype: UTF8_STRING + }, + reply=GetDesktopNamesReply, + cookie=GetDesktopNamesCookie, + cookie_unchecked=GetDesktopNamesCookieUnchecked + } + pub struct SetDesktopNames { data: Vec, } impl SetDesktopNames { pub fn new(names: Vec<&str>) -> SetDesktopNames { - let mut data: Vec = vec![]; - - // flatten `new_names` into a continuous array of bytes - for name in names { - let mut bname = name.as_bytes().to_owned(); - bname.push(0b00); - data.extend(bname) + SetDesktopNames { + data: strings_to_x_buffer(names), } - - SetDesktopNames { data } } } - impl<'a> EwmhRequest<'a> for SetDesktopNames { - type XcbRequest = xcb::x::ChangeProperty<'a, u8>; - type EwmhCookie = xcb::VoidCookie; - - fn xcb_request(&'a self, con: &Connection) -> Self::XcbRequest { - xcb::x::ChangeProperty { - mode: xcb::x::PropMode::Replace, - window: con.con.get_setup().roots().next().unwrap().root(), - property: con.atoms._NET_DESKTOP_NAMES, - r#type: con.atoms.UTF8_STRING, - data: &self.data, - } - } - - fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::EwmhCookie { - xcb_cookie - } - } - - impl<'a> EwmhVoidRequestChecked<'a> for SetDesktopNames { - type XcbRequest = xcb::x::ChangeProperty<'a, u8>; - - fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, u8> { - xcb::x::ChangeProperty { - mode: xcb::x::PropMode::Replace, - window: con.con.get_setup().roots().next().unwrap().root(), - property: con.atoms._NET_DESKTOP_NAMES, - r#type: con.atoms.UTF8_STRING, - data: &self.data, - } + ewmh_set_property! { + request=SetDesktopNames{ + window: root_window, + property: _NET_DESKTOP_NAMES, + xtype: UTF8_STRING } } } @@ -261,40 +145,7 @@ pub(crate) mod net_showing_desktop { } } - impl<'a> EwmhRequest<'a> for SetShowingDesktop { - type XcbRequest = xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent>; - type EwmhCookie = xcb::VoidCookie; - - fn xcb_request(&'a self, con: &Connection) -> Self::XcbRequest { - xcb::x::SendEvent { - propagate: false, - destination: xcb::x::SendEventDest::Window( - con.con.get_setup().roots().next().unwrap().root(), - ), - event_mask: xcb::x::EventMask::SUBSTRUCTURE_NOTIFY - | xcb::x::EventMask::SUBSTRUCTURE_REDIRECT, - event: &self.client_message, - } - } - - fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::EwmhCookie { - xcb_cookie - } - } - - impl<'a> EwmhVoidRequestChecked<'a> for SetShowingDesktop { - type XcbRequest = xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent>; - - fn xcb_request(&'a self, con: &Connection) -> Self::XcbRequest { - xcb::x::SendEvent { - propagate: false, - destination: xcb::x::SendEventDest::Window( - con.con.get_setup().roots().next().unwrap().root(), - ), - event_mask: xcb::x::EventMask::SUBSTRUCTURE_NOTIFY - | xcb::x::EventMask::SUBSTRUCTURE_REDIRECT, - event: &self.client_message, - } - } + ewmh_client_message! { + request=SetShowingDesktop{destination: root_window} } } diff --git a/src/lib.rs b/src/lib.rs index 2aa3c2a..ed64637 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![warn(rustdoc::broken_intra_doc_links)] #![warn(rustdoc::private_intra_doc_links)] +#[cfg(feature = "ewmh")] pub mod ewmh; #[cfg(test)]