From 48a8fca18f51044dbf50c0ff13d7816704acaaf0 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Fri, 13 May 2022 01:08:56 +0200 Subject: [PATCH] ewmh root window properties --- Cargo.toml | 4 +- src/ewmh/atoms.rs | 3 +- src/ewmh/ewmh.rs | 163 +++++++++- src/ewmh/mod.rs | 2 +- src/ewmh/proto.rs | 228 ------------- src/ewmh/proto/mod.rs | 4 + src/ewmh/proto/root_props.rs | 606 +++++++++++++++++++++++++++++++++++ src/ewmh/proto_traits.rs | 113 ++++++- 8 files changed, 867 insertions(+), 256 deletions(-) delete mode 100644 src/ewmh/proto.rs create mode 100644 src/ewmh/proto/mod.rs create mode 100644 src/ewmh/proto/root_props.rs diff --git a/Cargo.toml b/Cargo.toml index f55c807..679e4c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,14 +4,12 @@ version = "0.1.0" authors = [ "Armin Friedl " ] description = "Rust implementation of xcb-wm - icccm and ewmh extensions for xcb" readme = "README.md" -keyworks = ["icccm", "ewmh", "xcb-wm", "xcb", "window", "xlib", "x11"] +keywords = ["icccm", "ewmh", "xcb-wm", "xcb", "window", "xlib", "x11"] license = "MIT" edition = "2018" repository = "https://git.friedl.net/incubator/rust-xcb-wm" homepage = "https://git.friedl.net/incubator/rust-xcb-wm" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] xcb = "1" diff --git a/src/ewmh/atoms.rs b/src/ewmh/atoms.rs index a6cc0fb..9d28179 100644 --- a/src/ewmh/atoms.rs +++ b/src/ewmh/atoms.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -const ATOM_NAMES: [&'static str; 82] = [ +const ATOM_NAMES: [&str; 82] = [ "_NET_SUPPORTED", "_NET_CLIENT_LIST", "_NET_CLIENT_LIST_STACKING", @@ -85,6 +85,7 @@ const ATOM_NAMES: [&'static str; 82] = [ "_NET_WM_ACTION_BELOW" ]; +#[allow(non_snake_case)] pub struct Atoms { // TODO _NET_WM_CM_Sn atoms pub _NET_SUPPORTED: xcb::x::Atom, diff --git a/src/ewmh/ewmh.rs b/src/ewmh/ewmh.rs index 4778a40..995eedf 100644 --- a/src/ewmh/ewmh.rs +++ b/src/ewmh/ewmh.rs @@ -1,5 +1,3 @@ -use xcb::x::{Atom, GetPropertyReply}; - use crate::ewmh::proto_traits::{EwmhCookie, EwmhRequest}; use super::atoms::Atoms; @@ -9,6 +7,7 @@ pub struct Connection<'a> { pub atoms: Atoms, } +#[allow(dead_code)] impl<'a> Connection<'a> { pub fn connect(xcb_con: &'a xcb::Connection) -> Connection<'a> { Connection { @@ -37,7 +36,7 @@ impl<'a> Connection<'a> { self.con.send_request(&request) } - fn send_request(&self, request: R) -> R::Cookie { + fn send_request>(&self, request: R) -> R::Cookie { request.send_request(self) } @@ -53,12 +52,12 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::proto::GetNetSupportedRequest {}; + let request = crate::ewmh::proto::GetSupported::new(); let cookie = ewmh_con.send_request(request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); - for atom in reply.value { + for atom in reply.atoms { let cookie = xcb_con.send_request(&xcb::x::GetAtomName { atom: atom }); println!("{}", xcb_con.wait_for_reply(cookie).unwrap().name()); @@ -70,7 +69,7 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::proto::GetNetClientListRequest {}; + let request = crate::ewmh::proto::GetClientList::new(); let cookie = ewmh_con.send_request(request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); @@ -81,7 +80,29 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::proto::GetNetNumberOfDesktopsRequest {}; + let request = crate::ewmh::proto::GetNumberOfDesktops::new(); + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn set_number_of_desktops() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::SetNumberOfDesktops::new(4); + let cookie = ewmh_con.send_request(request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } + + #[test] + fn number_of_desktops2() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetNumberOfDesktops::new(); let cookie = ewmh_con.send_request(request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); @@ -92,18 +113,51 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::proto::GetNetDesktopGeometryRequest {}; + let request = crate::ewmh::proto::GetNumberOfDesktops {}; let cookie = ewmh_con.send_request(request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); } + #[test] + fn set_desktop_geometry() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::SetDesktopGeometry::new(1024, 800); + let cookie = ewmh_con.send_request(request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } + #[test] fn desktop_viewport() { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::proto::GetNetDesktopViewportRequest {}; + let request = crate::ewmh::proto::GetDesktopViewport {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn set_desktop_viewport() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::SetDesktopViewport::new(200, 200); + let cookie = ewmh_con.send_request(request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } + + #[test] + fn desktop_viewport2() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetDesktopViewport {}; let cookie = ewmh_con.send_request(request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); @@ -114,7 +168,7 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::proto::GetNetCurrentDesktopRequest {}; + let request = crate::ewmh::proto::GetCurrentDesktop {}; let cookie = ewmh_con.send_request(request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); @@ -125,9 +179,96 @@ mod tests { let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); - let request = crate::ewmh::proto::GetNetDesktopNamesRequest {}; + let request = crate::ewmh::proto::GetDesktopNames {}; let cookie = ewmh_con.send_request(request); let reply = ewmh_con.wait_for_reply(cookie); println!("{:?}", reply); } + + #[test] + fn set_active_window() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetClientList {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + + let request = crate::ewmh::proto::SetActiveWindow::new( + &ewmh_con, + reply.clients.last().unwrap().to_owned(), + 1, + xcb::x::CURRENT_TIME, + Option::None, + ); + + let cookie = ewmh_con.send_request(request); + xcb_con.check_request(cookie).unwrap(); + } + + #[test] + fn workarea() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetWorkarea {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn supporting_wm_check() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetSupportingWmCheck {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn virtual_roots() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetVirtualRoots {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn desktop_layout() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetDesktopLayout {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn showing_desktop() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetShowingDesktop {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn set_showing_desktop() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::SetShowingDesktop::new(&ewmh_con, true); + let cookie = ewmh_con.send_request(request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } } diff --git a/src/ewmh/mod.rs b/src/ewmh/mod.rs index 9a6a832..edc6910 100644 --- a/src/ewmh/mod.rs +++ b/src/ewmh/mod.rs @@ -1,4 +1,4 @@ mod ewmh; mod atoms; +mod proto_traits; mod proto; -mod proto_traits; \ No newline at end of file diff --git a/src/ewmh/proto.rs b/src/ewmh/proto.rs deleted file mode 100644 index 2efd3d7..0000000 --- a/src/ewmh/proto.rs +++ /dev/null @@ -1,228 +0,0 @@ -use crate::ewmh::ewmh::Connection; -use crate::ewmh::proto_traits::{EwmhCookie, EwmhCookieUnchecked, EwmhRequest, EwmhRequestData}; -use crate::{request, root_request}; - -root_request! { - GetNetSupportedRequest, - _NET_SUPPORTED, - ATOM_ATOM, - GetNetSupportedCookie, - GetNetSupportedCookieUnchecked, - GetNetSupportedReply -} - -#[derive(Debug)] -pub struct GetNetSupportedReply { - pub value: Vec, -} - -impl From for GetNetSupportedReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetSupportedReply { - value: reply.value().into(), - } - } -} - -root_request! { - GetNetClientListRequest, - _NET_CLIENT_LIST, - ATOM_WINDOW, - GetNetClientListCookie, - GetNetClientListCookieUnchecked, - GetNetClientListReply -} - -#[derive(Debug)] -pub struct GetNetClientListReply { - pub value: Vec, -} - -impl From for GetNetClientListReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetClientListReply { - value: reply.value().into(), - } - } -} - -root_request! { - GetNetClientListStackingRequest, - _NET_CLIENT_LIST_STACKING, - ATOM_WINDOW, - GetNetClientListStackingCookie, - GetNetClientListStackingCookieUnchecked, - GetNetClientListStackingReply -} - -#[derive(Debug)] -pub struct GetNetClientListStackingReply { - pub value: Vec, -} - -impl From for GetNetClientListStackingReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetClientListStackingReply { - value: reply.value().into(), - } - } -} - -root_request! { - GetNetNumberOfDesktopsRequest, - _NET_NUMBER_OF_DESKTOPS, - ATOM_CARDINAL, - GetNetNumberOfDesktopsCookie, - GetNetNumberOfDesktopsCookieUnchecked, - GetNetNumberOfDesktopsReply -} - -#[derive(Debug)] -pub struct GetNetNumberOfDesktopsReply { - pub value: u32, -} - -impl From for GetNetNumberOfDesktopsReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetNumberOfDesktopsReply { - value: reply.value::()[0], - } - } -} - -root_request! { - GetNetDesktopGeometryRequest, - _NET_DESKTOP_GEOMETRY, - ATOM_CARDINAL, - GetNetDesktopGeometryCookie, - GetNetDesktopGeometryCookieUnchecked, - GetNetDesktopGeometryReply -} - -#[derive(Debug)] -pub struct GetNetDesktopGeometryReply { - pub width: u32, - pub height: u32, -} - -impl From for GetNetDesktopGeometryReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetDesktopGeometryReply { - width: reply.value::()[0], - height: reply.value::()[1], - } - } -} - -root_request! { - GetNetDesktopViewportRequest, - _NET_DESKTOP_VIEWPORT, - ATOM_CARDINAL, - GetNetDesktopViewportCookie, - GetNetDesktopViewportCookieUnchecked, - GetNetDesktopViewportReply -} - -#[derive(Debug)] -pub struct GetNetDesktopViewportReply { - pub x: u32, - pub y: u32, -} - -impl From for GetNetDesktopViewportReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetDesktopViewportReply { - x: reply.value::()[0], - y: reply.value::()[1], - } - } -} - -root_request! { - GetNetCurrentDesktopRequest, - _NET_CURRENT_DESKTOP, - ATOM_CARDINAL, - GetNetCurrentDesktopCookie, - GetNetCurrentDesktopCookieUnchecked, - GetNetCurrentDesktopReply -} - -#[derive(Debug)] -pub struct GetNetCurrentDesktopReply { - pub desktop: u32, -} - -impl From for GetNetCurrentDesktopReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetCurrentDesktopReply { - desktop: reply.value::()[0], - } - } -} - -request! { - GetNetDesktopNamesRequest, - GetNetDesktopNamesCookie, - GetNetDesktopNamesCookieUnchecked, - GetNetDesktopNamesReply -} - -#[derive(Debug)] -pub struct GetNetDesktopNamesReply { - pub value: Vec, -} - -impl EwmhRequestData for GetNetDesktopNamesRequest { - fn get_request_data(&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 From for GetNetDesktopNamesReply { - 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(); - } - } - - GetNetDesktopNamesReply { value: vals } - } -} - -root_request! { - GetNetActiveWindowRequest, - _NET_ACTIVE_WINDOW, - ATOM_WINDOW, - GetNetActiveWindowCookie, - GetNetActiveWindowCookieUnchecked, - GetNetActiveWindowReply -} - -#[derive(Debug)] -pub struct GetNetActiveWindowReply { - pub value: xcb::x::Window, -} - -impl From for GetNetActiveWindowReply { - fn from(reply: xcb::x::GetPropertyReply) -> Self { - GetNetActiveWindowReply { - value: reply.value::()[0], - } - } -} diff --git a/src/ewmh/proto/mod.rs b/src/ewmh/proto/mod.rs new file mode 100644 index 0000000..50de4a3 --- /dev/null +++ b/src/ewmh/proto/mod.rs @@ -0,0 +1,4 @@ +#[allow(unused)] +mod root_props; + +pub use root_props::*; diff --git a/src/ewmh/proto/root_props.rs b/src/ewmh/proto/root_props.rs new file mode 100644 index 0000000..5c09942 --- /dev/null +++ b/src/ewmh/proto/root_props.rs @@ -0,0 +1,606 @@ +//! Root Window Properties (and Related Messages) +//! +//! see: https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#idm45539547193552 + +use xcb::{Xid, XidNew}; + +use crate::ewmh::ewmh::Connection; +use crate::ewmh::proto_traits::{EwmhCookie, EwmhCookieUnchecked, EwmhRequest, EwmhRequestData}; + +// _NET_SUPPORTED, ATOM[]/32 +// {{{ +ewmh_get_root_request! { + GetSupported, + _NET_SUPPORTED, + ATOM_ATOM, + GetSupportedCookie, + GetSupportedCookieUnchecked, + GetSupportedReply +} + +#[derive(Debug)] +pub struct GetSupportedReply { + pub atoms: Vec, +} + +impl From for GetSupportedReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetSupportedReply { + atoms: reply.value().into(), + } + } +} +// }}} + +// _NET_CLIENT_LIST, WINDOW[]/32 +// {{{ +ewmh_get_root_request! { + GetClientList, + _NET_CLIENT_LIST, + ATOM_WINDOW, + GetClientListCookie, + GetClientListCookieUnchecked, + GetClientListReply +} + +#[derive(Debug)] +pub struct GetClientListReply { + pub clients: Vec, +} + +impl From for GetClientListReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetClientListReply { + clients: reply.value().into(), + } + } +} +// }}} + +// _NET_CLIENT_LIST_STACKING, WINDOW[]/32 +// {{{ +ewmh_get_root_request! { + GetClientListStacking, + _NET_CLIENT_LIST_STACKING, + ATOM_WINDOW, + GetClientListStackingCookie, + GetClientListStackingCookieUnchecked, + GetClientListStackingReply +} + +#[derive(Debug)] +pub struct GetClientListStackingReply { + pub clients: Vec, +} + +impl From for GetClientListStackingReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetClientListStackingReply { + clients: reply.value().into(), + } + } +} +// }}} + +// _NET_NUMBER_OF_DESKTOPS, CARDINAL/32 +// {{{ +ewmh_get_root_request! { + GetNumberOfDesktops, + _NET_NUMBER_OF_DESKTOPS, + ATOM_CARDINAL, + GetNumberOfDesktopsCookie, + GetNumberOfDesktopsCookieUnchecked, + GetNumberOfDesktopsReply +} + +#[derive(Debug)] +pub struct GetNumberOfDesktopsReply { + pub desktops: u32, +} + +impl From for GetNumberOfDesktopsReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNumberOfDesktopsReply { + desktops: reply.value::()[0], + } + } +} + +pub struct SetNumberOfDesktops { + data: [u32; 1], +} + +impl SetNumberOfDesktops { + pub fn new(desktops: u32) -> SetNumberOfDesktops { + SetNumberOfDesktops { data: [desktops] } + } +} + +ewmh_set_root_request! { + SetNumberOfDesktops, + _NET_NUMBER_OF_DESKTOPS, + ATOM_CARDINAL +} + +// }}} + +// _NET_DESKTOP_GEOMETRY width, height, CARDINAL[2]/32 +// {{{ +ewmh_get_root_request! { + GetDesktopGeometry, + _NET_DESKTOP_GEOMETRY, + ATOM_CARDINAL, + GetDesktopGeometryCookie, + GetDesktopGeometryCookieUnchecked, + GetDesktopGeometryReply +} + +#[derive(Debug)] +pub struct GetDesktopGeometryReply { + pub width: u32, + pub height: u32, +} + +impl From for GetDesktopGeometryReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetDesktopGeometryReply { + width: reply.value::()[0], + height: reply.value::()[0], + } + } +} + +pub struct SetDesktopGeometry { + data: [u32; 2], +} + +impl SetDesktopGeometry { + pub fn new(width: u32, height: u32) -> SetDesktopGeometry { + SetDesktopGeometry { + data: [width, height], + } + } +} + +ewmh_set_root_request! { + SetDesktopGeometry, + _NET_DESKTOP_GEOMETRY, + ATOM_CARDINAL +} + +// }}} + +// _NET_DESTKOP_VIEWPORT x, y, CARDINAL[][2]/32 +// {{{ +ewmh_get_root_request! { + GetDesktopViewport, + _NET_DESKTOP_VIEWPORT, + ATOM_CARDINAL, + GetDesktopViewportCookie, + GetDesktopViewportCookieUnchecked, + GetDesktopViewportReply +} + +#[derive(Debug)] +pub struct GetDesktopViewportReply { + pub x: u32, + pub y: u32, +} + +impl From for GetDesktopViewportReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetDesktopViewportReply { + x: reply.value::()[0], + y: reply.value::()[1], + } + } +} + +pub struct SetDesktopViewport { + data: [u32; 2], +} + +impl SetDesktopViewport { + pub fn new(x: u32, y: u32) -> SetDesktopViewport { + SetDesktopViewport { data: [x, y] } + } +} + +ewmh_set_root_request! { + SetDesktopViewport, + _NET_DESKTOP_VIEWPORT, + ATOM_CARDINAL +} + +// }}} + +// _NET_CURRENT_DESKTOP desktop, CARDINAL/32 +// {{{ +ewmh_get_root_request! { + GetCurrentDesktop, + _NET_CURRENT_DESKTOP, + ATOM_CARDINAL, + GetCurrentDesktopCookie, + GetCurrentDesktopCookieUnchecked, + GetCurrentDesktopReply +} + +#[derive(Debug)] +pub struct GetCurrentDesktopReply { + pub desktop: u32, +} + +impl From for GetCurrentDesktopReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetCurrentDesktopReply { + desktop: reply.value::()[0], + } + } +} + +pub struct SetCurrentDesktop { + data: [u32; 2], +} + +impl SetCurrentDesktop { + pub fn new(new_index: u32, timestamp: u32) -> SetCurrentDesktop { + SetCurrentDesktop { + data: [new_index, timestamp], + } + } +} + +ewmh_set_root_request! { + SetCurrentDesktop, + _NET_CURRENT_DESKTOP, + ATOM_CARDINAL +} + +// }}} + +// _NET_DESKTOP_NAMES desktop, UTF8_STRING[] +// {{{ +ewmh_get_root_request! { + GetDesktopNames, + _NET_DESKTOP_NAMES, + UTF8_STRING, + GetDesktopNamesCookie, + GetDesktopNamesCookieUnchecked, + GetDesktopNamesReply +} + +#[derive(Debug)] +pub struct GetDesktopNamesReply { + pub desktop_names: Vec, +} + +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, + } + } +} + +pub struct SetDesktopNames { + data: Vec, +} + +impl SetDesktopNames { + pub fn new(new_names: Vec<&str>) -> SetDesktopNames { + let mut data: Vec = vec![]; + + // flatten `new_names` into a continuous array of bytes + for name in new_names { + let mut bname = name.as_bytes().to_owned(); + bname.push(0b00); + data.extend(bname) + } + + SetDesktopNames { data } + } +} + +ewmh_set_root_request! { + SetDesktopNames, + _NET_DESKTOP_NAMES, + UTF8_STRING +} + +// }}} + +// _NET_ACTIVE_WINDOW, WINDOW/32 +// {{{ +ewmh_get_root_request! { + GetActiveWindow, + _NET_ACTIVE_WINDOW, + ATOM_WINDOW, + GetActiveWindowCookie, + GetActiveWindowCookieUnchecked, + GetActiveWindowReply +} + +#[derive(Debug)] +pub struct GetActiveWindowReply { + pub value: xcb::x::Window, +} + +impl From for GetActiveWindowReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetActiveWindowReply { + value: reply.value::()[0], + } + } +} + +pub struct SetActiveWindow { + client_message: xcb::x::ClientMessageEvent, +} + +impl SetActiveWindow { + pub fn new( + connection: &Connection, + window: xcb::x::Window, + source_indication: u32, + timestamp: u32, + requestor_window: Option, + ) -> SetActiveWindow { + let data = [ + source_indication, + timestamp, + requestor_window.map_or(0, |w| w.resource_id()), + 0x00, + 0x00, + ]; + + let client_message = xcb::x::ClientMessageEvent::new( + window, + connection.atoms._NET_ACTIVE_WINDOW, + xcb::x::ClientMessageData::Data32(data), + ); + + SetActiveWindow { client_message } + } +} + +impl<'a> EwmhRequest<'a> for SetActiveWindow { + 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 SetActiveWindow { + type Request = xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent>; + + fn get_request_data( + &'a self, + con: &Connection, + ) -> xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent> { + 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, + } + } +} +// }}} + +// _NET_WORKAREA, x, y, width, height, CARDINAL[][4]/32 +// {{{ +ewmh_get_root_request! { + GetWorkarea, + _NET_WORKAREA, + ATOM_CARDINAL, + GetWorkareaCookie, + GetWorkareaCookieUnchecked, + GetWorkareaReply +} + +#[derive(Debug)] +pub struct GetWorkareaReply { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +impl From for GetWorkareaReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetWorkareaReply { + x: reply.value::()[0], + y: reply.value::()[1], + width: reply.value::()[2], + height: reply.value::()[3], + } + } +} +// }}} + +// _NET_SUPPORTING_WM_CHECK, WINDOW/32 +// {{{ +ewmh_get_root_request! { + GetSupportingWmCheck, + _NET_SUPPORTING_WM_CHECK, + ATOM_WINDOW, + GetSupportingWmCheckCookie, + GetSupportingWmCheckCookieUnchecked, + GetSupportingWmCheckReply +} + +#[derive(Debug)] +pub struct GetSupportingWmCheckReply { + pub window: xcb::x::Window, +} + +impl From for GetSupportingWmCheckReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetSupportingWmCheckReply { + window: unsafe { xcb::x::Window::new(reply.value::()[0]) }, + } + } +} +// }}} + +// _NET_VIRTUAL_ROOTS, WINDOW/32 +// {{{ +ewmh_get_root_request! { + GetVirtualRoots, + _NET_VIRTUAL_ROOTS, + ATOM_WINDOW, + GetVirtualRootsCookie, + GetVirtualRootsCookieUnchecked, + GetVirtualRootsReply +} + +#[derive(Debug)] +pub struct GetVirtualRootsReply { + pub window: xcb::x::Window, +} + +impl From for GetVirtualRootsReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetVirtualRootsReply { + window: unsafe { xcb::x::Window::new(reply.value::()[0]) }, + } + } +} +// }}} + +// _NET_DESKTOP_LAYOUT, orientation, columns, rows, starting_corner, CARDINAL[4]/32 +// {{{ +ewmh_get_root_request! { + GetDesktopLayout, + _NET_DESKTOP_LAYOUT, + ATOM_CARDINAL, + GetDesktopLayoutCookie, + GetDesktopLayoutCookieUnchecked, + GetDesktopLayoutReply +} + +#[derive(Debug)] +pub struct GetDesktopLayoutReply { + orientation: u32, + columns: u32, + rows: u32, + starting_corner: u32, +} + +impl From for GetDesktopLayoutReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetDesktopLayoutReply { + orientation: reply.value::()[0], + columns: reply.value::()[1], + rows: reply.value::()[2], + starting_corner: reply.value::()[3], + } + } +} +// }}} + +// _NET_SHOWING_DESKTOP desktop, CARDINAL/32 +// {{{ +ewmh_get_root_request! { + GetShowingDesktop, + _NET_SHOWING_DESKTOP, + ATOM_CARDINAL, + GetShowingDesktopCookie, + GetShowingDesktopCookieUnchecked, + GetShowingDesktopReply +} + +#[derive(Debug)] +pub struct GetShowingDesktopReply { + showing_desktop: bool, +} + +impl From for GetShowingDesktopReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetShowingDesktopReply { + showing_desktop: match reply.value::()[0] { + 0 => false, + 1 => true, + _ => unreachable!(), + }, + } + } +} + +pub struct SetShowingDesktop { + client_message: xcb::x::ClientMessageEvent, +} + +impl SetShowingDesktop { + pub fn new(connection: &Connection, show_desktop: bool) -> SetShowingDesktop { + let data = match show_desktop { + false => 0 as u32, + true => 1 as u32, + }; + + let client_message = xcb::x::ClientMessageEvent::new( + connection.con.get_setup().roots().next().unwrap().root(), + connection.atoms._NET_SHOWING_DESKTOP, + xcb::x::ClientMessageData::Data32([data, 0x00, 0x00, 0x00, 0x00]), + ); + + SetShowingDesktop { client_message } + } +} + +impl<'a> EwmhRequest<'a> for SetShowingDesktop { + 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 SetShowingDesktop { + type Request = xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent>; + + fn get_request_data( + &'a self, + con: &Connection, + ) -> xcb::x::SendEvent<'a, xcb::x::ClientMessageEvent> { + 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, + } + } +} + +// }}} diff --git a/src/ewmh/proto_traits.rs b/src/ewmh/proto_traits.rs index c803569..37a85a8 100644 --- a/src/ewmh/proto_traits.rs +++ b/src/ewmh/proto_traits.rs @@ -1,6 +1,8 @@ +#![macro_use] + use crate::ewmh::ewmh::Connection; -pub(crate) trait EwmhRequest: EwmhRequestData { +pub(crate) trait EwmhRequest<'a>: EwmhRequestData<'a> { type Cookie; type CookieUnchecked; @@ -8,8 +10,9 @@ pub(crate) trait EwmhRequest: EwmhRequestData { fn send_request_unchecked(&self, con: &Connection) -> Self::CookieUnchecked; } -pub(crate) trait EwmhRequestData { - fn get_request_data(&self, con: &Connection) -> xcb::x::GetProperty; +pub(crate) trait EwmhRequestData<'a> { + type Request: xcb::Request; + fn get_request_data(&'a self, con: &Connection) -> Self::Request; } pub(crate) trait EwmhCookie { @@ -22,10 +25,10 @@ pub(crate) trait EwmhCookieUnchecked { fn reply(self, con: &Connection) -> Option; } -#[macro_export] -macro_rules! request { +macro_rules! ewmh_get_request { ($req: ident, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { pub struct $req {} + pub struct $cookie { cookie: xcb::x::GetPropertyCookie, } @@ -33,7 +36,13 @@ macro_rules! request { cookie: xcb::x::GetPropertyCookieUnchecked, } - impl EwmhRequest for $req { + impl $req { + pub fn new() -> $req { + $req {} + } + } + + impl<'a> EwmhRequest<'a> for $req { type Cookie = $cookie; type CookieUnchecked = $cookie_unchecked; @@ -72,13 +81,33 @@ macro_rules! request { }; } -#[macro_export] -macro_rules! root_request { - ($req: ident, $prop: ident, $type: ident, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { - request! {$req, $cookie, $cookie_unchecked, $reply} +macro_rules! ewmh_get_root_request { + ($req: ident, $prop: ident, UTF8_STRING, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { + ewmh_get_request! {$req, $cookie, $cookie_unchecked, $reply} - impl EwmhRequestData for $req { - fn get_request_data(&self, con: &Connection) -> xcb::x::GetProperty { + 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: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$prop, + r#type: con.atoms.UTF8_STRING, + long_offset: 0, + long_length: u32::MAX, + } + } + } + }; + + ($req: ident, $prop: ident, $type: ident, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { + ewmh_get_request! {$req, $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: con.con.get_setup().roots().next().unwrap().root(), @@ -91,3 +120,63 @@ macro_rules! root_request { } }; } + +macro_rules! ewmh_set_root_request { + ($req: ident, $prop: ident, UTF8_STRING) => { + 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, u8>; + + fn get_request_data(&'a self, con: &Connection) -> Self::Request { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$prop, + r#type: con.atoms.UTF8_STRING, + data: self.data.as_slice(), + } + } + } + }; + + ($req: ident, $prop: ident, $type: ident) => { + 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, u32>; + + fn get_request_data(&'a self, con: &Connection) -> Self::Request { + xcb::x::ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$prop, + r#type: xcb::x::$type, + data: &self.data, + } + } + } + }; +}