diff --git a/Cargo.toml b/Cargo.toml index 679e4c0..e8f12e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rust-xcb-wm" +name = "xcb-wm" version = "0.1.0" authors = [ "Armin Friedl " ] description = "Rust implementation of xcb-wm - icccm and ewmh extensions for xcb" @@ -7,8 +7,8 @@ readme = "README.md" 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" +repository = "https://git.friedl.net/incubator/xcb-wm" +homepage = "https://git.friedl.net/incubator/xcb-wm" [dependencies] xcb = "1" diff --git a/src/ewmh/atoms.rs b/src/ewmh/atoms.rs index d9230d4..d32224e 100644 --- a/src/ewmh/atoms.rs +++ b/src/ewmh/atoms.rs @@ -85,6 +85,12 @@ const ATOM_NAMES: [&str; 82] = [ "_NET_WM_ACTION_BELOW", ]; +/// Interned [`xcb::x::Atom`]s for the `ewmh` protocol +/// +/// The ids for these atoms are created when the [`crate::ewmh::Connection`] is established. Hence, +/// are bound to the [`crate::ewmh::Connection`] and can only be retrieved from there. +/// +/// See also: [`wm spec 1.5`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5) #[allow(non_snake_case)] pub struct Atoms { // TODO _NET_WM_CM_Sn atoms diff --git a/src/ewmh/connection.rs b/src/ewmh/connection.rs new file mode 100644 index 0000000..ec7dc36 --- /dev/null +++ b/src/ewmh/connection.rs @@ -0,0 +1,468 @@ +use crate::ewmh::atoms::Atoms; +use crate::ewmh::traits::{ + EwmhPropertyCookieChecked, EwmhPropertyCookieUnchecked, EwmhPropertyRequestUnchecked, + EwmhRequest, EwmhVoidRequestChecked, +}; + +/// The main `ewmh` entry point +/// +/// `Connection` is a thin wrapper around [`xcb::Connection`]. It provides a +/// subset of the [`xcb::Connection`] API covering the functionality needed for +/// `ewmh`. +/// +/// 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 needed because `ewmh` has to prepare the `Connection` for +/// `ewmh` requests and store additional data on it. Concretely, this mostly +/// means interning atoms. +pub struct Connection<'a> { + pub(crate) con: &'a xcb::Connection, + + /// Interned [`Atoms`] for the `ewmh` protocol + pub atoms: Atoms, +} + +#[allow(dead_code)] +impl<'a> Connection<'a> { + pub fn connect(xcb_con: &'a xcb::Connection) -> Connection<'a> { + Connection { + atoms: Atoms::intern(xcb_con), + con: xcb_con, + } + } + + fn send_request<'b, R>(&self, request: &'b R) -> R::EwmhCookie + where + R: EwmhRequest<'b>, + { + request.send(self) + } + + fn send_request_checked<'b, R>(&self, request: &'b R) -> xcb::VoidCookieChecked + where + R: EwmhVoidRequestChecked<'b>, + { + request.send(self) + } + + fn send_request_unchecked(&self, request: &R) -> R::EwmhCookie + where + R: EwmhPropertyRequestUnchecked, + { + request.send(self) + } + + fn wait_for_reply(&self, cookie: C) -> C::Reply + where + C: EwmhPropertyCookieChecked, + { + let xcb_reply = self.con.wait_for_reply(cookie.inner()); + xcb_reply.unwrap().into() + } + + fn wait_for_reply_unchecked(&self, cookie: C) -> C::Reply + where + C: EwmhPropertyCookieUnchecked, + { + let xcb_reply = self.con.wait_for_reply_unchecked(cookie.inner()); + xcb_reply.unwrap().unwrap().into() + } +} + +#[cfg(test)] +mod tests { + #[test] + fn supported_atoms_list() { + 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 cookie = ewmh_con.send_request(&request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + + 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()); + } + } + // + // #[test] + // fn client_list() { + // 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::new(); + // let cookie = ewmh_con.send_request(request); + // let reply = ewmh_con.wait_for_reply(cookie); + // println!("{:?}", reply); + // } + // + // #[test] + // fn 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::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); + // } + // + // #[test] + // fn 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::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::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); + // } + // + // #[test] + // fn desktop_current() { + // 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::GetCurrentDesktop {}; + // let cookie = ewmh_con.send_request(request); + // let reply = ewmh_con.wait_for_reply(cookie); + // println!("{:?}", reply); + // } + // + #[test] + fn desktop_names() { + 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 cookie = ewmh_con.send_request(&request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn set_desktop_names() { + 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 cookie = ewmh_con.send_request_checked(&request); + let reply = xcb_con.check_request(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::Connection::connect(&xcb_con); + + let request = crate::ewmh::SetShowingDesktop::new(&ewmh_con, true); + let cookie = ewmh_con.send_request_checked(&request); + let reply = xcb_con.check_request(cookie); + println!("{:?}", reply); + } + + // #[test] + // fn close_window() { + // 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(20979719) }; + // + // let request = + // crate::ewmh::proto::CloseWindow::new(&ewmh_con, window, 0, xcb::x::CURRENT_TIME); + // + // let cookie = ewmh_con.send_request(request); + // let reply = xcb_con.check_request(cookie); + // println!("{:?}", reply); + // } + // + // #[test] + // fn request_frame_extents() { + // 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(20979719) }; + // + // let request = crate::ewmh::proto::RequestFrameExtents::new(&ewmh_con, window); + // + // let cookie = ewmh_con.send_request(request); + // 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/ewmh.rs b/src/ewmh/ewmh.rs deleted file mode 100644 index 40357e6..0000000 --- a/src/ewmh/ewmh.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::ewmh::proto_traits::{EwmhCookie, EwmhRequest}; - -use super::atoms::Atoms; - -pub struct Connection<'a> { - pub(crate) con: &'a xcb::Connection, - pub atoms: Atoms, -} - -#[allow(dead_code)] -impl<'a> Connection<'a> { - pub fn connect(xcb_con: &'a xcb::Connection) -> Connection<'a> { - Connection { - atoms: Atoms::intern(xcb_con), - con: xcb_con, - } - } - - fn send_client_message( - &self, - window: xcb::x::Window, - dest: xcb::x::Window, - atom: xcb::x::Atom, - data: xcb::x::ClientMessageData, - ) -> xcb::VoidCookie { - let event = xcb::x::ClientMessageEvent::new(window, atom, data); - - let request = xcb::x::SendEvent { - propagate: false, - destination: xcb::x::SendEventDest::Window(dest), - event_mask: xcb::x::EventMask::SUBSTRUCTURE_NOTIFY - | xcb::x::EventMask::SUBSTRUCTURE_REDIRECT, - event: &event, - }; - - self.con.send_request(&request) - } - - fn send_request>(&self, request: R) -> R::Cookie { - request.send_request(self) - } - - fn wait_for_reply(&self, cookie: C) -> C::Reply { - cookie.reply(self) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn supported_atoms_list() { - 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::GetSupported::new(); - let cookie = ewmh_con.send_request(request); - let reply = ewmh_con.wait_for_reply(cookie); - println!("{:?}", reply); - - 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()); - } - } - - #[test] - fn client_list() { - 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::new(); - let cookie = ewmh_con.send_request(request); - let reply = ewmh_con.wait_for_reply(cookie); - println!("{:?}", reply); - } - - #[test] - fn 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::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); - } - - #[test] - fn 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::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::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); - } - - #[test] - fn desktop_current() { - 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::GetCurrentDesktop {}; - let cookie = ewmh_con.send_request(request); - let reply = ewmh_con.wait_for_reply(cookie); - println!("{:?}", reply); - } - - #[test] - fn desktop_names() { - 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::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); - } - - #[test] - fn close_window() { - 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(20979719) }; - - let request = - crate::ewmh::proto::CloseWindow::new(&ewmh_con, window, 0, xcb::x::CURRENT_TIME); - - let cookie = ewmh_con.send_request(request); - let reply = xcb_con.check_request(cookie); - println!("{:?}", reply); - } - - #[test] - fn request_frame_extents() { - 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(20979719) }; - - let request = crate::ewmh::proto::RequestFrameExtents::new(&ewmh_con, window); - - let cookie = ewmh_con.send_request(request); - 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/mod.rs b/src/ewmh/mod.rs index e975212..d029a12 100644 --- a/src/ewmh/mod.rs +++ b/src/ewmh/mod.rs @@ -1,6 +1,13 @@ #[macro_use] -mod proto_traits; +mod traits; mod atoms; -mod ewmh; +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::*; diff --git a/src/ewmh/proto/application_props.rs b/src/ewmh/proto/application_props.rs index 040adc2..9f6af9f 100644 --- a/src/ewmh/proto/application_props.rs +++ b/src/ewmh/proto/application_props.rs @@ -1,10 +1,10 @@ //! Application Window Properties //! -//! see: https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm45446104426048 +//! see: use xcb::{Xid, XidNew}; -use crate::ewmh::ewmh::Connection; +use crate::ewmh::connection::Connection; use crate::ewmh::proto_traits::{EwmhCookie, EwmhCookieUnchecked, EwmhRequest, EwmhRequestData}; // _NET_WM_NAME, UTF8_STRING @@ -204,14 +204,13 @@ pub struct GetWmWindowTypeReply { impl From for GetWmWindowTypeReply { fn from(reply: xcb::x::GetPropertyReply) -> Self { GetWmWindowTypeReply { - types: reply.value::().into() + types: reply.value::().into(), } } } // }}} - // _NET_WM_STATE, ATOM[]/32 // {{{ @@ -252,7 +251,7 @@ pub struct GetWmStateReply { impl From for GetWmStateReply { fn from(reply: xcb::x::GetPropertyReply) -> Self { GetWmStateReply { - types: reply.value::().into() + types: reply.value::().into(), } } } diff --git a/src/ewmh/proto/mod.rs b/src/ewmh/proto/mod.rs index 6bb249d..d8df3a7 100644 --- a/src/ewmh/proto/mod.rs +++ b/src/ewmh/proto/mod.rs @@ -1,6 +1,9 @@ -mod application_props; -#[allow(unused)] -mod root_props; +// mod application_props; +// #[allow(unused)] +// mod root_props; +// +// pub use application_props::*; +// pub use root_props::*; -pub use application_props::*; -pub use root_props::*; +mod test_props; +pub(crate) use test_props::*; diff --git a/src/ewmh/proto/root_props.rs b/src/ewmh/proto/root_props.rs index 8e2052f..63e1225 100644 --- a/src/ewmh/proto/root_props.rs +++ b/src/ewmh/proto/root_props.rs @@ -1,10 +1,10 @@ //! Root Window Properties (and Related Messages) //! -//! see: https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#idm45539547193552 +//! see: use xcb::{Xid, XidNew}; -use crate::ewmh::ewmh::Connection; +use crate::ewmh::connection::Connection; use crate::ewmh::proto_traits::{EwmhCookie, EwmhCookieUnchecked, EwmhRequest, EwmhRequestData}; // _NET_SUPPORTED, ATOM[]/32 diff --git a/src/ewmh/proto/test_props.rs b/src/ewmh/proto/test_props.rs new file mode 100644 index 0000000..914276d --- /dev/null +++ b/src/ewmh/proto/test_props.rs @@ -0,0 +1,300 @@ +pub(crate) mod net_supported { + use crate::ewmh::traits::*; + use crate::ewmh::Connection; + + pub struct GetSupported; + + pub struct GetSupportedCookie(xcb::x::GetPropertyCookie); + + pub struct GetSupportedCookieUnchecked(xcb::x::GetPropertyCookieUnchecked); + + #[derive(Debug)] + pub struct GetSupportedReply { + 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 { + atoms: reply.value().into(), + } + } + } +} + +pub(crate) mod net_desktop_names { + use crate::ewmh::traits::*; + use crate::ewmh::Connection; + + pub struct GetDesktopNames; + + pub struct GetDesktopNamesCookie(xcb::x::GetPropertyCookie); + + pub struct GetDesktopNamesCookieUnchecked(xcb::x::GetPropertyCookieUnchecked); + + #[derive(Debug)] + pub struct GetDesktopNamesReply { + 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, + } + } + } + + 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 } + } + } + + 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, + } + } + } +} + +pub(crate) mod net_showing_desktop { + use crate::ewmh::traits::*; + use crate::ewmh::Connection; + + 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 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, + } + } + } +} diff --git a/src/ewmh/proto_traits.rs b/src/ewmh/proto_traits.rs deleted file mode 100644 index 1fa697a..0000000 --- a/src/ewmh/proto_traits.rs +++ /dev/null @@ -1,329 +0,0 @@ -use crate::ewmh::ewmh::Connection; - -pub(crate) trait EwmhRequest<'a>: EwmhRequestData<'a> { - type Cookie; - type CookieUnchecked; - - fn send_request(&self, con: &Connection) -> Self::Cookie; - fn send_request_unchecked(&self, con: &Connection) -> Self::CookieUnchecked; -} - -pub(crate) trait EwmhRequestData<'a> { - type Request: xcb::Request; - fn get_request_data(&'a self, con: &Connection) -> Self::Request; -} - -pub(crate) trait EwmhCookie { - type Reply: From; - fn reply(self, con: &Connection) -> Self::Reply; -} - -pub(crate) trait EwmhCookieUnchecked { - type Reply; - 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 {} - - pub struct $cookie { - cookie: xcb::x::GetPropertyCookie, - } - pub struct $cookie_unchecked { - cookie: xcb::x::GetPropertyCookieUnchecked, - } - - impl $req { - pub fn new() -> $req { - $req {} - } - } - - 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_root_request { - ($req: ident, $prop: ident, UTF8_STRING, $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(), - 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(), - property: con.atoms.$prop, - r#type: xcb::x::$type, - long_offset: 0, - long_length: u32::MAX, - } - } - } - }; -} - -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, - } - } - } - }; -} diff --git a/src/ewmh/traits.rs b/src/ewmh/traits.rs new file mode 100644 index 0000000..f252c39 --- /dev/null +++ b/src/ewmh/traits.rs @@ -0,0 +1,156 @@ +use crate::ewmh::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(crate) trait EwmhRequest<'a> { + /// The underlying [`xcb::Request`] used to do the actual heavy lifting + type XcbRequest: xcb::Request; + + /// The ewmh wrapper for the [`xcb::Request::Cookie`] + type EwmhCookie: EwmhCookie::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 ewmh cookie wrapper + fn convert_cookie( + &'a self, + xcb_cookie: ::Cookie, + ) -> Self::EwmhCookie; + + /// Default implementation. Delegate the request to the [`xcb::Connection`] and wrap the + /// response in the ewmh cookie wrapper. + /// + /// There is usually no need to override the provided default implementation. + fn send(&'a self, con: &Connection) -> Self::EwmhCookie { + 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(crate) trait EwmhVoidRequestChecked<'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(crate) trait EwmhPropertyRequestUnchecked { + type EwmhCookie: EwmhPropertyCookieUnchecked; + + /// 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::EwmhCookie; + + /// Default implementation. Delegate the request to the [`xcb::Connection`]. + /// + /// Returns a wrapped cookie that tracks the EwmhReply 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::EwmhCookie { + let xcb_request = self.xcb_request(con); + let xcb_cookie = con.con.send_request_unchecked(&xcb_request); + self.convert_cookie(xcb_cookie) + } +} + +/// Most generic ewmh wrapper for an [`xcb::Cookie`] +/// +/// This is needed for [`EwmhRequest`] 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(crate) trait EwmhCookie { + /// 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 ewmh cookie wrapper +/// for SetProperty and ClientMessage requests needed. [`xcb::VoidCookie`] satisfies +/// all that is needed for [`ewmh`]. In order to use it with [`EwmhRequest`] we need +/// this impl. +impl EwmhCookie for xcb::VoidCookie { + type XcbCookie = xcb::VoidCookie; +} + +/// ewmh 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(crate) trait EwmhPropertyCookieChecked { + type Reply: EwmhPropertyReply; + + /// 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; +} + +/// ewmh 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(crate) trait EwmhPropertyCookieUnchecked { + type Reply: EwmhPropertyReply; + + /// 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 EwmhPropertyReply for T where T: From {} diff --git a/src/lib.rs b/src/lib.rs index 742e157..2aa3c2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ -mod ewmh; +#![warn(rustdoc::broken_intra_doc_links)] +#![warn(rustdoc::private_intra_doc_links)] + +pub mod ewmh; #[cfg(test)] mod tests {