diff --git a/src/ewmh/ewmh.rs b/src/ewmh/ewmh.rs index 6939dcc..40357e6 100644 --- a/src/ewmh/ewmh.rs +++ b/src/ewmh/ewmh.rs @@ -304,4 +304,128 @@ mod tests { let reply = xcb_con.check_request(cookie); println!("{:?}", reply); } + + #[test] + fn wm_name() { + use xcb::XidNew; + + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let window = unsafe { xcb::x::Window::new(77594744) }; + + let request = crate::ewmh::proto::GetWmName::new(window); + + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn wm_visible_name() { + use xcb::XidNew; + + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let window = unsafe { xcb::x::Window::new(20983603) }; + + let request = crate::ewmh::proto::GetWmVisibleName::new(window); + + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn wm_desktop() { + use xcb::XidNew; + + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let window = unsafe { xcb::x::Window::new(20983603) }; + + let request = crate::ewmh::proto::GetWmDesktop::new(window); + + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn set_wm_type() { + use xcb::XidNew; + + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let window = unsafe { xcb::x::Window::new(20995834) }; + + let request = crate::ewmh::proto::SetWmWindowType::new( + window, + vec![ + ewmh_con.atoms._NET_WM_WINDOW_TYPE_UTILITY, + ewmh_con.atoms._NET_WM_WINDOW_TYPE_SPLASH, + ], + ); + + let cookie = ewmh_con.send_request(request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } + + #[test] + fn get_wm_type() { + use xcb::XidNew; + + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let window = unsafe { xcb::x::Window::new(20995834) }; + + let request = crate::ewmh::proto::GetWmWindowType::new(window); + + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn set_wm_state() { + use xcb::XidNew; + + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let window = unsafe { xcb::x::Window::new(21002424) }; + + let request = crate::ewmh::proto::SetWmState::new( + window, + vec![ + ewmh_con.atoms._NET_WM_STATE_STICKY, + ewmh_con.atoms._NET_WM_STATE_DEMANDS_ATTENTION, + ], + ); + + let cookie = ewmh_con.send_request(request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } + + #[test] + fn get_wm_state() { + use xcb::XidNew; + + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let window = unsafe { xcb::x::Window::new(21002424) }; + + let request = crate::ewmh::proto::GetWmWindowType::new(window); + + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } } diff --git a/src/ewmh/proto/application_props.rs b/src/ewmh/proto/application_props.rs new file mode 100644 index 0000000..040adc2 --- /dev/null +++ b/src/ewmh/proto/application_props.rs @@ -0,0 +1,260 @@ +//! Application Window Properties +//! +//! see: https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm45446104426048 + +use xcb::{Xid, XidNew}; + +use crate::ewmh::ewmh::Connection; +use crate::ewmh::proto_traits::{EwmhCookie, EwmhCookieUnchecked, EwmhRequest, EwmhRequestData}; + +// _NET_WM_NAME, UTF8_STRING +// {{{ +ewmh_get_window_request! { + GetWmName, + _NET_WM_NAME, + UTF8_STRING, + GetWmNameCookie, + GetWmNameCookieUnchecked, + GetWmNameReply +} + +#[derive(Debug)] +pub struct GetWmNameReply { + pub name: String, +} + +impl From for GetWmNameReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let mut buf = vec![]; + + for b in reply.value::() { + if *b != 0x00 { + buf.push(*b) + } + } + + GetWmNameReply { + name: String::from_utf8(buf).unwrap(), + } + } +} +// }}} + +// _NET_WM_VISIBLE_NAME, UTF8_STRING +// {{{ +ewmh_get_window_request! { + GetWmVisibleName, + _NET_WM_VISIBLE_NAME, + UTF8_STRING, + GetWmVisibleNameCooke, + GetWmVisibleNameCookieUnchecked, + GetWmVisibleNameReply +} + +#[derive(Debug)] +pub struct GetWmVisibleNameReply { + pub name: String, +} + +impl From for GetWmVisibleNameReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let mut buf = vec![]; + + for b in reply.value::() { + if *b != 0x00 { + buf.push(*b) + } + } + + GetWmVisibleNameReply { + name: String::from_utf8(buf).unwrap(), + } + } +} +// }}} + +// _NET_WM_ICON_NAME, UTF8_STRING +// {{{ +ewmh_get_window_request! { + GetWmIconName, + _NET_WM_ICON_NAME, + UTF8_STRING, + GetWmIconNameCooke, + GetWmIconNameCookieUnchecked, + GetWmIconNameReply +} + +#[derive(Debug)] +pub struct GetWmIconNameReply { + pub name: String, +} + +impl From for GetWmIconNameReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let mut buf = vec![]; + + for b in reply.value::() { + if *b != 0x00 { + buf.push(*b) + } + } + + GetWmIconNameReply { + name: String::from_utf8(buf).unwrap(), + } + } +} +// }}} + +// _NET_WM_VISIBLE_ICON_NAME, UTF8_STRING +// {{{ +ewmh_get_window_request! { + GetWmVisibleIconName, + _NET_WM_VISIBLE_ICON_NAME, + UTF8_STRING, + GetWmVisibleIconNameCooke, + GetWmVisibleIconNameCookieUnchecked, + GetWmVisibleIconNameReply +} + +#[derive(Debug)] +pub struct GetWmVisibleIconNameReply { + pub name: String, +} + +impl From for GetWmVisibleIconNameReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let mut buf = vec![]; + + for b in reply.value::() { + if *b != 0x00 { + buf.push(*b) + } + } + + GetWmVisibleIconNameReply { + name: String::from_utf8(buf).unwrap(), + } + } +} +// }}} + +// _NET_WM_DESKTOP, CARDINAL/32 +// {{{ +ewmh_get_window_request! { + GetWmDesktop, + _NET_WM_DESKTOP, + CARDINAL, + GetWmDesktopCooke, + GetWmDesktopCookieUnchecked, + GetWmDesktopReply +} + +#[derive(Debug)] +pub struct GetWmDesktopReply { + pub desktop: u32, +} + +impl From for GetWmDesktopReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetWmDesktopReply { + desktop: reply.value::()[0], + } + } +} +// }}} + +// _NET_WM_WINDOW_TYPE, ATOM[]/32 +// {{{ + +pub struct SetWmWindowType { + window: xcb::x::Window, + data: Vec, +} + +impl SetWmWindowType { + pub fn new(window: xcb::x::Window, types: Vec) -> SetWmWindowType { + SetWmWindowType { + window, + data: types, + } + } +} + +ewmh_set_window_request! { + SetWmWindowType, + _NET_WM_WINDOW_TYPE, + { xcb::x::ATOM_ATOM } +} + +ewmh_get_window_request! { + GetWmWindowType, + _NET_WM_WINDOW_TYPE, + ATOM, + GetWmWindowTypeCooke, + GetWmWindowTypeCookieUnchecked, + GetWmWindowTypeReply +} + +#[derive(Debug)] +pub struct GetWmWindowTypeReply { + pub types: Vec, +} + +impl From for GetWmWindowTypeReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetWmWindowTypeReply { + types: reply.value::().into() + } + } +} + +// }}} + + +// _NET_WM_STATE, ATOM[]/32 +// {{{ + +pub struct SetWmState { + window: xcb::x::Window, + data: Vec, +} + +impl SetWmState { + pub fn new(window: xcb::x::Window, types: Vec) -> SetWmState { + SetWmState { + window, + data: types, + } + } +} + +ewmh_set_window_request! { + SetWmState, + _NET_WM_STATE, + { xcb::x::ATOM_ATOM } +} + +ewmh_get_window_request! { + GetWmState, + _NET_WM_STATE, + ATOM, + GetWmStateCooke, + GetWmStateCookieUnchecked, + GetWmStateReply +} + +#[derive(Debug)] +pub struct GetWmStateReply { + pub types: Vec, +} + +impl From for GetWmStateReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetWmStateReply { + types: reply.value::().into() + } + } +} + +// }}} diff --git a/src/ewmh/proto/mod.rs b/src/ewmh/proto/mod.rs index 50de4a3..6bb249d 100644 --- a/src/ewmh/proto/mod.rs +++ b/src/ewmh/proto/mod.rs @@ -1,4 +1,6 @@ +mod application_props; #[allow(unused)] mod root_props; +pub use application_props::*; pub use root_props::*; diff --git a/src/ewmh/proto_traits.rs b/src/ewmh/proto_traits.rs index 37a85a8..1fa697a 100644 --- a/src/ewmh/proto_traits.rs +++ b/src/ewmh/proto_traits.rs @@ -1,5 +1,3 @@ -#![macro_use] - use crate::ewmh::ewmh::Connection; pub(crate) trait EwmhRequest<'a>: EwmhRequestData<'a> { @@ -25,6 +23,155 @@ pub(crate) trait EwmhCookieUnchecked { fn reply(self, con: &Connection) -> Option; } +macro_rules! ewmh_get_window_request_private { + ($req: ident, $prop: ident, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { + pub struct $req { + window: xcb::x::Window, + } + + pub struct $cookie { + cookie: xcb::x::GetPropertyCookie, + } + pub struct $cookie_unchecked { + cookie: xcb::x::GetPropertyCookieUnchecked, + } + + impl $req { + pub fn new(window: xcb::x::Window) -> $req { + $req { window } + } + } + + impl<'a> EwmhRequest<'a> for $req { + type Cookie = $cookie; + type CookieUnchecked = $cookie_unchecked; + + fn send_request(&self, con: &Connection) -> Self::Cookie { + $cookie { + cookie: con.con.send_request(&self.get_request_data(con)), + } + } + + fn send_request_unchecked(&self, con: &Connection) -> Self::CookieUnchecked { + $cookie_unchecked { + cookie: con.con.send_request_unchecked(&self.get_request_data(con)), + } + } + } + + impl EwmhCookie for $cookie { + type Reply = $reply; + + fn reply(self, con: &Connection) -> Self::Reply { + let reply = con.con.wait_for_reply(self.cookie).unwrap(); + reply.into() + } + } + + impl EwmhCookieUnchecked for $cookie_unchecked { + type Reply = $reply; + + fn reply(self, con: &Connection) -> Option { + con.con + .wait_for_reply_unchecked(self.cookie) + .unwrap() + .map(|reply| reply.into()) + } + } + }; +} + +macro_rules! ewmh_get_window_request { + ($req: ident, $prop: ident, UTF8_STRING, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { + ewmh_get_window_request_private! {$req, $prop, $cookie, $cookie_unchecked, $reply} + + impl<'a> EwmhRequestData<'a> for $req { + type Request = xcb::x::GetProperty; + + fn get_request_data(&'a self, con: &Connection) -> Self::Request { + xcb::x::GetProperty { + delete: false, + window: self.window, + property: con.atoms.$prop, + r#type: con.atoms.UTF8_STRING, + long_offset: 0, + long_length: u32::MAX, + } + } + } + }; + + ($req: ident, $prop: ident, CARDINAL, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { + ewmh_get_window_request_private! {$req, $prop, $cookie, $cookie_unchecked, $reply} + + impl<'a> EwmhRequestData<'a> for $req { + type Request = xcb::x::GetProperty; + + fn get_request_data(&'a self, con: &Connection) -> Self::Request { + xcb::x::GetProperty { + delete: false, + window: self.window, + property: con.atoms.$prop, + r#type: xcb::x::ATOM_CARDINAL, + long_offset: 0, + long_length: u32::MAX, + } + } + } + }; + + ($req: ident, $prop: ident, ATOM, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { + ewmh_get_window_request_private! {$req, $prop, $cookie, $cookie_unchecked, $reply} + + impl<'a> EwmhRequestData<'a> for $req { + type Request = xcb::x::GetProperty; + + fn get_request_data(&'a self, con: &Connection) -> Self::Request { + xcb::x::GetProperty { + delete: false, + window: self.window, + property: con.atoms.$prop, + r#type: xcb::x::ATOM_ATOM, + long_offset: 0, + long_length: u32::MAX, + } + } + } + }; + +} + +macro_rules! ewmh_set_window_request { + ($req: ident, $prop: ident, $type: tt) => { + impl<'a> EwmhRequest<'a> for $req { + type Cookie = xcb::VoidCookieChecked; + type CookieUnchecked = xcb::VoidCookie; + + fn send_request(&self, con: &Connection) -> Self::Cookie { + con.con.send_request_checked(&self.get_request_data(con)) + } + + fn send_request_unchecked(&self, con: &Connection) -> Self::CookieUnchecked { + con.con.send_request(&self.get_request_data(con)) + } + } + + impl<'a> EwmhRequestData<'a> for $req { + type Request = xcb::x::ChangeProperty<'a, xcb::x::Atom>; + + fn get_request_data(&'a self, con: &Connection) -> Self::Request { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: con.atoms.$prop, + r#type: $type, + data: &self.data, + } + } + } + }; +} + macro_rules! ewmh_get_request { ($req: ident, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { pub struct $req {}