Refactor and simplify traits, rename to xcb-wm

This commit is contained in:
Armin Friedl 2022-05-17 22:27:01 +02:00
parent 0980df1e2b
commit fe183b4c4b
12 changed files with 960 additions and 778 deletions

View file

@ -1,5 +1,5 @@
[package]
name = "rust-xcb-wm"
name = "xcb-wm"
version = "0.1.0"
authors = [ "Armin Friedl <dev@friedl.net>" ]
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"

View file

@ -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

468
src/ewmh/connection.rs Normal file
View file

@ -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<R>(&self, request: &R) -> R::EwmhCookie
where
R: EwmhPropertyRequestUnchecked,
{
request.send(self)
}
fn wait_for_reply<C>(&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<C>(&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);
// }
}

View file

@ -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<R: EwmhRequest<'a>>(&self, request: R) -> R::Cookie {
request.send_request(self)
}
fn wait_for_reply<C: EwmhCookie>(&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);
}
}

View file

@ -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::*;

View file

@ -1,10 +1,10 @@
//! Application Window Properties
//!
//! see: https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm45446104426048
//! 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::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<xcb::x::GetPropertyReply> for GetWmWindowTypeReply {
fn from(reply: xcb::x::GetPropertyReply) -> Self {
GetWmWindowTypeReply {
types: reply.value::<xcb::x::Atom>().into()
types: reply.value::<xcb::x::Atom>().into(),
}
}
}
// }}}
// _NET_WM_STATE, ATOM[]/32
// {{{
@ -252,7 +251,7 @@ pub struct GetWmStateReply {
impl From<xcb::x::GetPropertyReply> for GetWmStateReply {
fn from(reply: xcb::x::GetPropertyReply) -> Self {
GetWmStateReply {
types: reply.value::<xcb::x::Atom>().into()
types: reply.value::<xcb::x::Atom>().into(),
}
}
}

View file

@ -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::*;

View file

@ -1,10 +1,10 @@
//! Root Window Properties (and Related Messages)
//!
//! see: https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#idm45539547193552
//! see: <https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#idm45539547193552>
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

View file

@ -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<xcb::x::Atom>,
}
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<xcb::x::GetPropertyReply> 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<String>,
}
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<xcb::x::GetPropertyReply> for GetDesktopNamesReply {
fn from(reply: xcb::x::GetPropertyReply) -> Self {
let mut vals = vec![];
let mut buf = vec![];
for b in reply.value::<u8>() {
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<u8>,
}
impl SetDesktopNames {
pub fn new(names: Vec<&str>) -> SetDesktopNames {
let mut data: Vec<u8> = 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,
}
}
}
}

View file

@ -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<xcb::x::GetPropertyReply>;
fn reply(self, con: &Connection) -> Self::Reply;
}
pub(crate) trait EwmhCookieUnchecked {
type Reply;
fn reply(self, con: &Connection) -> Option<Self::Reply>;
}
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<Self::Reply> {
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<Self::Reply> {
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,
}
}
}
};
}

156
src/ewmh/traits.rs Normal file
View file

@ -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<XcbCookie = <Self::XcbRequest as xcb::Request>::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: <Self::XcbRequest as xcb::Request>::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<xcb::x::GetPropertyReply`].
///
/// The ewmh property reply trait is used to convert a generic reply to a [`xcb::x::GetProperty`]
/// request to a specific ewmh reply struct.
///
/// The connection between a ewmh request and the reply struct is made via ewmh property cookies
/// ([`EwmhPropertyCookieChecked`] and [`EwmhPropertyCookieUnchecked`]
pub(crate) trait EwmhPropertyReply: From<xcb::x::GetPropertyReply> {}
impl<T> EwmhPropertyReply for T where T: From<xcb::x::GetPropertyReply> {}

View file

@ -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 {