Begin ICCCM implementation
WM_NAME, WM_ICON_NAME, WM_COLORMAP_WINDOWS, WM_CLIENT_MACHINE, WM_TRANSIENT_FOR Note that polymorphic text handling is not implemented correctly (especially for COMPOUND text)
This commit is contained in:
parent
c8568f528c
commit
2d7a01cc5d
11 changed files with 840 additions and 0 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
//! see: <https://specifications.freedesktop.org/wm-spec/1.5/ar01s03.html#idm45539547193552>
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod traits;
|
mod traits;
|
||||||
|
|
||||||
|
|
44
src/icccm/atoms.rs
Normal file
44
src/icccm/atoms.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// TODO use xcb::atom_struct!{} for this?
|
||||||
|
const ATOM_NAMES: [&str; 1] = ["WM_COLORMAP_WINDOWS"];
|
||||||
|
|
||||||
|
/// Interned [`xcb::x::Atom`]s for the `icccm` protocol
|
||||||
|
///
|
||||||
|
/// The ids for these atoms are created when the [`crate::icccm::Connection`] is established. Hence,
|
||||||
|
/// are bound to the [`crate::icccm::Connection`] and can only be retrieved from there.
|
||||||
|
///
|
||||||
|
/// See also: [`icccm spec`](https://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html)
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub struct Atoms {
|
||||||
|
pub WM_COLORMAP_WINDOWS: xcb::x::Atom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Atoms {
|
||||||
|
pub(crate) fn intern(con: &xcb::Connection) -> Atoms {
|
||||||
|
let mut cookies: HashMap<&'static str, xcb::x::InternAtomCookie> = HashMap::new();
|
||||||
|
|
||||||
|
for atom in ATOM_NAMES {
|
||||||
|
let intern_atom = xcb::x::InternAtom {
|
||||||
|
only_if_exists: false,
|
||||||
|
name: atom.as_bytes(),
|
||||||
|
};
|
||||||
|
|
||||||
|
cookies.insert(atom, con.send_request(&intern_atom));
|
||||||
|
}
|
||||||
|
|
||||||
|
let interned_atoms: HashMap<&'static str, xcb::x::Atom> = cookies
|
||||||
|
.into_iter()
|
||||||
|
.map(|(atom_name, cookie)| (atom_name, con.wait_for_reply(cookie).unwrap()))
|
||||||
|
.map(|(atom_name, reply)| (atom_name, reply.atom()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Atoms::from_interned_atoms(interned_atoms)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_interned_atoms(mut atoms: HashMap<&'static str, xcb::x::Atom>) -> Atoms {
|
||||||
|
Atoms {
|
||||||
|
WM_COLORMAP_WINDOWS: atoms.remove("WM_COLORMAP_WINDOWS").unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
src/icccm/connection.rs
Normal file
111
src/icccm/connection.rs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
use crate::icccm::atoms::Atoms;
|
||||||
|
use crate::icccm::traits::{
|
||||||
|
IcccmPropertyCookieChecked, IcccmPropertyCookieUnchecked, IcccmPropertyRequestUnchecked,
|
||||||
|
IcccmRequest, IcccmVoidRequestChecked,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The main `icccm` entry point
|
||||||
|
///
|
||||||
|
/// `Connection` is a thin wrapper around [`xcb::Connection`]. It provides a
|
||||||
|
/// subset of the [`xcb::Connection`] API covering the functionality needed for
|
||||||
|
/// `icccm`.
|
||||||
|
///
|
||||||
|
/// 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 not strictly needed for `icccm` (ICCCM Atoms are pre-defined
|
||||||
|
/// in the core protocol [1]). However, to align with `ewmh` we provide the same
|
||||||
|
/// usage pattern/API as for `ewmh`.
|
||||||
|
///
|
||||||
|
/// [1] <https://www.x.org/releases/current/doc/xproto/x11protocol.html#Predefined_Atoms>
|
||||||
|
///
|
||||||
|
pub struct Connection<'a> {
|
||||||
|
pub(crate) con: &'a xcb::Connection,
|
||||||
|
|
||||||
|
/// Interned [`Atoms`] for the `icccm` protocol
|
||||||
|
pub atoms: Atoms,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl<'a> Connection<'a> {
|
||||||
|
pub fn connect(xcb_con: &'a xcb::Connection) -> Connection<'a> {
|
||||||
|
Connection {
|
||||||
|
con: xcb_con,
|
||||||
|
atoms: Atoms::intern(xcb_con),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_request<'b, R>(&self, request: &'b R) -> R::IcccmCookie
|
||||||
|
where
|
||||||
|
R: IcccmRequest<'b>,
|
||||||
|
{
|
||||||
|
request.send(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_request_checked<'b, R>(&self, request: &'b R) -> xcb::VoidCookieChecked
|
||||||
|
where
|
||||||
|
R: IcccmVoidRequestChecked<'b>,
|
||||||
|
{
|
||||||
|
request.send(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_request_unchecked<R>(&self, request: &R) -> R::IcccmCookie
|
||||||
|
where
|
||||||
|
R: IcccmPropertyRequestUnchecked,
|
||||||
|
{
|
||||||
|
request.send(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_reply<C>(&self, cookie: C) -> C::Reply
|
||||||
|
where
|
||||||
|
C: IcccmPropertyCookieChecked,
|
||||||
|
{
|
||||||
|
let xcb_reply = self.con.wait_for_reply(cookie.inner());
|
||||||
|
xcb_reply.unwrap().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_reply_unchecked<C>(&self, cookie: C) -> C::Reply
|
||||||
|
where
|
||||||
|
C: IcccmPropertyCookieUnchecked,
|
||||||
|
{
|
||||||
|
let xcb_reply = self.con.wait_for_reply_unchecked(cookie.inner());
|
||||||
|
xcb_reply.unwrap().unwrap().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::icccm::proto::SetWmName;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_of_desktops() {
|
||||||
|
let xcb_con = xcb::Connection::connect(Option::None).unwrap().0;
|
||||||
|
let icccm_con = crate::icccm::Connection::connect(&xcb_con);
|
||||||
|
|
||||||
|
use xcb::XidNew;
|
||||||
|
|
||||||
|
let window = unsafe { xcb::x::Window::new(0x2e0013e) };
|
||||||
|
|
||||||
|
let request = crate::icccm::proto::GetWmName::new(window);
|
||||||
|
let cookie = icccm_con.send_request(&request);
|
||||||
|
let reply = icccm_con.wait_for_reply(cookie);
|
||||||
|
println!("{:?}", reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_number_of_desktops() {
|
||||||
|
let xcb_con = xcb::Connection::connect(Option::None).unwrap().0;
|
||||||
|
let icccm_con = crate::icccm::Connection::connect(&xcb_con);
|
||||||
|
|
||||||
|
use xcb::XidNew;
|
||||||
|
|
||||||
|
let window = unsafe { xcb::x::Window::new(0x4600003) };
|
||||||
|
let name = String::from("NEW NAME").into_bytes();
|
||||||
|
|
||||||
|
let request = SetWmName::new(window, xcb::x::ATOM_STRING, name);
|
||||||
|
let cookie = icccm_con.send_request_checked(&request);
|
||||||
|
let reply = xcb_con.check_request(cookie);
|
||||||
|
println!("{:?}", reply);
|
||||||
|
}
|
||||||
|
}
|
12
src/icccm/mod.rs
Normal file
12
src/icccm/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//! Request and response definitions for the icccm protocol
|
||||||
|
//!
|
||||||
|
//! see: <https://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html>
|
||||||
|
|
||||||
|
mod traits;
|
||||||
|
|
||||||
|
mod atoms;
|
||||||
|
|
||||||
|
mod connection;
|
||||||
|
pub use connection::Connection;
|
||||||
|
|
||||||
|
pub mod proto;
|
259
src/icccm/proto/client_props.rs
Normal file
259
src/icccm/proto/client_props.rs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
//! Client Properties
|
||||||
|
//!
|
||||||
|
//! see: <https://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html#Client_Properties>
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use xcb::{Xid, XidNew};
|
||||||
|
|
||||||
|
use crate::icccm::traits::*;
|
||||||
|
use crate::icccm::Connection;
|
||||||
|
|
||||||
|
use paste::paste; // Needed for macros
|
||||||
|
|
||||||
|
// WM_NAME, TEXT
|
||||||
|
// {{{
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GetWmNameReply {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<xcb::x::GetPropertyReply> for GetWmNameReply {
|
||||||
|
fn from(reply: xcb::x::GetPropertyReply) -> Self {
|
||||||
|
let x = reply.value::<u8>().into();
|
||||||
|
GetWmNameReply {
|
||||||
|
// TODO Compound string
|
||||||
|
name: String::from_utf8(x).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_get_property! {
|
||||||
|
request=GetWmName{
|
||||||
|
window: client,
|
||||||
|
property: ATOM_WM_NAME,
|
||||||
|
xtype: ATOM_ANY
|
||||||
|
},
|
||||||
|
reply=GetWmNameReply
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetWmName<T: xcb::x::PropEl> {
|
||||||
|
window: xcb::x::Window,
|
||||||
|
encoding: xcb::x::Atom,
|
||||||
|
data: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: xcb::x::PropEl> SetWmName<T> {
|
||||||
|
pub fn new(window: xcb::x::Window, encoding: xcb::x::Atom, name: Vec<T>) -> SetWmName<T> {
|
||||||
|
SetWmName {
|
||||||
|
window: window,
|
||||||
|
encoding: encoding,
|
||||||
|
data: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_set_text_property! {
|
||||||
|
request=SetWmName{
|
||||||
|
property: ATOM_WM_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// WM_ICON_NAME, TEXT
|
||||||
|
// {{{
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GetWmIconNameReply {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<xcb::x::GetPropertyReply> for GetWmIconNameReply {
|
||||||
|
fn from(reply: xcb::x::GetPropertyReply) -> Self {
|
||||||
|
let x = reply.value::<u8>().into();
|
||||||
|
GetWmIconNameReply {
|
||||||
|
// TODO Compound string
|
||||||
|
name: String::from_utf8(x).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_get_property! {
|
||||||
|
request=GetWmIconName{
|
||||||
|
window: client,
|
||||||
|
property: ATOM_WM_ICON_NAME,
|
||||||
|
xtype: ATOM_ANY
|
||||||
|
},
|
||||||
|
reply=GetWmIconNameReply
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetWmIconName<T: xcb::x::PropEl> {
|
||||||
|
window: xcb::x::Window,
|
||||||
|
encoding: xcb::x::Atom,
|
||||||
|
data: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: xcb::x::PropEl> SetWmIconName<T> {
|
||||||
|
pub fn new(window: xcb::x::Window, encoding: xcb::x::Atom, name: Vec<T>) -> SetWmIconName<T> {
|
||||||
|
SetWmIconName {
|
||||||
|
window: window,
|
||||||
|
encoding: encoding,
|
||||||
|
data: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_set_text_property! {
|
||||||
|
request=SetWmIconName{
|
||||||
|
property: ATOM_WM_ICON_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// WM_COLORMAP_WINDOWS, WINDOW[]/32
|
||||||
|
// {{{
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GetWmColorMapWindowsReply {
|
||||||
|
pub windows: Vec<xcb::x::Window>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<xcb::x::GetPropertyReply> for GetWmColorMapWindowsReply {
|
||||||
|
fn from(reply: xcb::x::GetPropertyReply) -> Self {
|
||||||
|
GetWmColorMapWindowsReply {
|
||||||
|
windows: reply.value().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_get_property! {
|
||||||
|
request=GetWmColorMapWindows {
|
||||||
|
window: client,
|
||||||
|
property: WM_COLORMAP_WINDOWS,
|
||||||
|
xtype: ATOM_WINDOW
|
||||||
|
},
|
||||||
|
reply=GetWmIconNameReply
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetWmColorMapWindows {
|
||||||
|
window: xcb::x::Window,
|
||||||
|
data: Vec<xcb::x::Window>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetWmColorMapWindows {
|
||||||
|
pub fn new(
|
||||||
|
window: xcb::x::Window,
|
||||||
|
colormap_windows: Vec<xcb::x::Window>,
|
||||||
|
) -> SetWmColorMapWindows {
|
||||||
|
SetWmColorMapWindows {
|
||||||
|
window: window,
|
||||||
|
data: colormap_windows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_set_property! {
|
||||||
|
request=SetWmColorMapWindows{
|
||||||
|
property: con.WM_COLORMAP_WINDOWS,
|
||||||
|
xtype: ATOM_WINDOW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// WM_CLIENT_MACHINE, TEXT
|
||||||
|
// {{{
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GetWmClientMachineReply {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<xcb::x::GetPropertyReply> for GetWmClientMachineReply {
|
||||||
|
fn from(reply: xcb::x::GetPropertyReply) -> Self {
|
||||||
|
let x = reply.value::<u8>().into();
|
||||||
|
GetWmClientMachineReply {
|
||||||
|
// TODO Compound string
|
||||||
|
name: String::from_utf8(x).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_get_property! {
|
||||||
|
request=GetWmClientMachine{
|
||||||
|
window: client,
|
||||||
|
property: ATOM_WM_CLIENT_MACHINE,
|
||||||
|
xtype: ATOM_ANY
|
||||||
|
},
|
||||||
|
reply=GetWmClientMachineReply
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetWmClientMachine<T: xcb::x::PropEl> {
|
||||||
|
window: xcb::x::Window,
|
||||||
|
encoding: xcb::x::Atom,
|
||||||
|
data: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: xcb::x::PropEl> SetWmClientMachine<T> {
|
||||||
|
pub fn new(
|
||||||
|
window: xcb::x::Window,
|
||||||
|
encoding: xcb::x::Atom,
|
||||||
|
name: Vec<T>,
|
||||||
|
) -> SetWmClientMachine<T> {
|
||||||
|
SetWmClientMachine {
|
||||||
|
window: window,
|
||||||
|
encoding: encoding,
|
||||||
|
data: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_set_text_property! {
|
||||||
|
request=SetWmClientMachine{
|
||||||
|
property: ATOM_WM_CLIENT_MACHINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// WM_TRANSIENT_FOR, WINDOW/32
|
||||||
|
// {{{
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GetWmTransientForReply {
|
||||||
|
pub window: xcb::x::Window,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<xcb::x::GetPropertyReply> for GetWmTransientForReply {
|
||||||
|
fn from(reply: xcb::x::GetPropertyReply) -> Self {
|
||||||
|
GetWmTransientForReply {
|
||||||
|
window: unsafe { xcb::x::Window::new(reply.value::<u32>()[0]) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_get_property! {
|
||||||
|
request=GetWmTransientFor{
|
||||||
|
window: client,
|
||||||
|
property: ATOM_WM_TRANSIENT_FOR,
|
||||||
|
xtype: ATOM_WINDOW
|
||||||
|
},
|
||||||
|
reply=GetWmTransientForReply
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetWmTransientFor {
|
||||||
|
window: xcb::x::Window,
|
||||||
|
data: Vec<xcb::x::Window>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetWmTransientFor {
|
||||||
|
// TODO better name for second window
|
||||||
|
pub fn new(window: xcb::x::Window, window2: xcb::x::Window) -> SetWmTransientFor {
|
||||||
|
SetWmTransientFor {
|
||||||
|
window: window,
|
||||||
|
data: vec![window2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icccm_set_property! {
|
||||||
|
request=SetWmTransientFor{
|
||||||
|
property: ATOM_WM_CLIENT_MACHINE,
|
||||||
|
xtype: ATOM_WINDOW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
99
src/icccm/proto/macros/get_property.rs
Normal file
99
src/icccm/proto/macros/get_property.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
macro_rules! _get_property_cookies {
|
||||||
|
(@icccm_priv $request:ident, $reply:ident) => {
|
||||||
|
paste! {
|
||||||
|
impl IcccmCookie for [<$request Cookie>] {
|
||||||
|
type XcbCookie = xcb::x::GetPropertyCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IcccmPropertyCookieChecked for [<$request Cookie>] {
|
||||||
|
type Reply = $reply;
|
||||||
|
|
||||||
|
fn inner(self) -> xcb::x::GetPropertyCookie {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IcccmPropertyCookieUnchecked for [<$request CookieUnchecked>] {
|
||||||
|
type Reply = $reply;
|
||||||
|
|
||||||
|
fn inner(self) -> xcb::x::GetPropertyCookieUnchecked {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! _get_property_request {
|
||||||
|
//TODO do we need the `client` here? Probs just c&p
|
||||||
|
(@icccm_priv client, WM_COLORMAP_WINDOWS, $xtype:ident) => {
|
||||||
|
fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty {
|
||||||
|
xcb::x::GetProperty {
|
||||||
|
delete: false,
|
||||||
|
window: self.0,
|
||||||
|
property: con.atoms.WM_COLORMAP_WINDOWS,
|
||||||
|
r#type: xcb::x::$xtype,
|
||||||
|
long_offset: 0,
|
||||||
|
long_length: u32::MAX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(@icccm_priv client, $property:ident, $xtype:ident) => {
|
||||||
|
fn xcb_request(&self, con: &Connection) -> xcb::x::GetProperty {
|
||||||
|
xcb::x::GetProperty {
|
||||||
|
delete: false,
|
||||||
|
window: self.0,
|
||||||
|
property: xcb::x::$property,
|
||||||
|
r#type: xcb::x::$xtype,
|
||||||
|
long_offset: 0,
|
||||||
|
long_length: u32::MAX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! icccm_get_property {
|
||||||
|
(request=$request:ident{
|
||||||
|
window: $window:ident,
|
||||||
|
property: $property:ident,
|
||||||
|
xtype: $xtype: ident
|
||||||
|
},
|
||||||
|
reply=$reply:ident) => {
|
||||||
|
paste!{
|
||||||
|
pub struct $request(xcb::x::Window);
|
||||||
|
pub struct [<$request Cookie>](xcb::x::GetPropertyCookie);
|
||||||
|
pub struct [<$request CookieUnchecked>](xcb::x::GetPropertyCookieUnchecked);
|
||||||
|
|
||||||
|
impl $request {
|
||||||
|
pub fn new(window: xcb::x::Window) -> $request {
|
||||||
|
$request(window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IcccmRequest<'a> for $request {
|
||||||
|
type XcbRequest = xcb::x::GetProperty;
|
||||||
|
type IcccmCookie = [<$request Cookie>];
|
||||||
|
|
||||||
|
_get_property_request! {@icccm_priv $window, $property, $xtype}
|
||||||
|
|
||||||
|
fn convert_cookie(&'a self, xcb_cookie: xcb::x::GetPropertyCookie) -> Self::IcccmCookie {
|
||||||
|
[<$request Cookie>](xcb_cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IcccmPropertyRequestUnchecked for $request {
|
||||||
|
paste!{type IcccmCookie = [<$request CookieUnchecked>];}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn convert_cookie(&self, xcb_cookie: xcb::x::GetPropertyCookieUnchecked) -> Self::IcccmCookie {
|
||||||
|
[<$request CookieUnchecked>](xcb_cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_property_request! {@icccm_priv $window, $property, $xtype}
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_property_cookies! {@icccm_priv $request, $reply}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
5
src/icccm/proto/macros/mod.rs
Normal file
5
src/icccm/proto/macros/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#[macro_use]
|
||||||
|
mod get_property;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod set_property;
|
128
src/icccm/proto/macros/set_property.rs
Normal file
128
src/icccm/proto/macros/set_property.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
macro_rules! icccm_set_text_property {
|
||||||
|
(request=$request:ident{
|
||||||
|
property: $property:ident
|
||||||
|
}) => {
|
||||||
|
impl<'a, T: 'a + xcb::x::PropEl> IcccmRequest<'a> for $request<T> {
|
||||||
|
type XcbRequest = xcb::x::ChangeProperty<'a, T>;
|
||||||
|
type IcccmCookie = xcb::VoidCookie;
|
||||||
|
|
||||||
|
fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, T> {
|
||||||
|
xcb::x::ChangeProperty {
|
||||||
|
mode: xcb::x::PropMode::Replace,
|
||||||
|
window: self.window,
|
||||||
|
property: xcb::x::$property,
|
||||||
|
r#type: self.encoding,
|
||||||
|
data: &self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::IcccmCookie {
|
||||||
|
xcb_cookie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: 'a + xcb::x::PropEl> IcccmVoidRequestChecked<'a> for $request<T> {
|
||||||
|
type XcbRequest = xcb::x::ChangeProperty<'a, T>;
|
||||||
|
|
||||||
|
fn xcb_request(&'a self, con: &Connection) -> xcb::x::ChangeProperty<'a, T> {
|
||||||
|
xcb::x::ChangeProperty {
|
||||||
|
mode: xcb::x::PropMode::Replace,
|
||||||
|
window: self.window,
|
||||||
|
property: xcb::x::$property,
|
||||||
|
r#type: self.encoding,
|
||||||
|
data: &self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! icccm_set_property {
|
||||||
|
(request=$request:ident{
|
||||||
|
property: con.$property:ident,
|
||||||
|
xtype: $type:ident
|
||||||
|
}) => {
|
||||||
|
impl<'a> IcccmRequest<'a> for $request {
|
||||||
|
type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>;
|
||||||
|
type IcccmCookie = xcb::VoidCookie;
|
||||||
|
|
||||||
|
fn xcb_request(
|
||||||
|
&'a self,
|
||||||
|
con: &Connection,
|
||||||
|
) -> xcb::x::ChangeProperty<'a, xcb::x::Window> {
|
||||||
|
xcb::x::ChangeProperty {
|
||||||
|
mode: xcb::x::PropMode::Replace,
|
||||||
|
window: self.window,
|
||||||
|
property: con.atoms.$property,
|
||||||
|
r#type: xcb::x::$type,
|
||||||
|
data: &self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::IcccmCookie {
|
||||||
|
xcb_cookie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IcccmVoidRequestChecked<'a> for $request {
|
||||||
|
type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>;
|
||||||
|
|
||||||
|
fn xcb_request(
|
||||||
|
&'a self,
|
||||||
|
con: &Connection,
|
||||||
|
) -> xcb::x::ChangeProperty<'a, xcb::x::Window> {
|
||||||
|
xcb::x::ChangeProperty {
|
||||||
|
mode: xcb::x::PropMode::Replace,
|
||||||
|
window: self.window,
|
||||||
|
property: con.atoms.$property,
|
||||||
|
r#type: xcb::x::$type,
|
||||||
|
data: &self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(request=$request:ident{
|
||||||
|
property: $property:ident,
|
||||||
|
xtype: $type:ident
|
||||||
|
}) => {
|
||||||
|
impl<'a> IcccmRequest<'a> for $request {
|
||||||
|
type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>;
|
||||||
|
type IcccmCookie = xcb::VoidCookie;
|
||||||
|
|
||||||
|
fn xcb_request(
|
||||||
|
&'a self,
|
||||||
|
con: &Connection,
|
||||||
|
) -> xcb::x::ChangeProperty<'a, xcb::x::Window> {
|
||||||
|
xcb::x::ChangeProperty {
|
||||||
|
mode: xcb::x::PropMode::Replace,
|
||||||
|
window: self.window,
|
||||||
|
property: xcb::x::$property,
|
||||||
|
r#type: xcb::x::$type,
|
||||||
|
data: &self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_cookie(&'a self, xcb_cookie: xcb::VoidCookie) -> Self::IcccmCookie {
|
||||||
|
xcb_cookie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IcccmVoidRequestChecked<'a> for $request {
|
||||||
|
type XcbRequest = xcb::x::ChangeProperty<'a, xcb::x::Window>;
|
||||||
|
|
||||||
|
fn xcb_request(
|
||||||
|
&'a self,
|
||||||
|
con: &Connection,
|
||||||
|
) -> xcb::x::ChangeProperty<'a, xcb::x::Window> {
|
||||||
|
xcb::x::ChangeProperty {
|
||||||
|
mode: xcb::x::PropMode::Replace,
|
||||||
|
window: self.window,
|
||||||
|
property: xcb::x::$property,
|
||||||
|
r#type: xcb::x::$type,
|
||||||
|
data: &self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
21
src/icccm/proto/mod.rs
Normal file
21
src/icccm/proto/mod.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//! Request and response definitions for the icccm protocol
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//! ```
|
||||||
|
//! let connection = xcb_wm::icccm::Connection();
|
||||||
|
//! let request = xcb_wm::ewmh::proto::GetSupported;
|
||||||
|
//! let cookie = connection.send_request(request);
|
||||||
|
//! let response = connection.wait_for_reply(cookie);
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
// mod application_props;
|
||||||
|
// #[allow(unused)]
|
||||||
|
// mod root_props;
|
||||||
|
//
|
||||||
|
// pub use application_props::*;
|
||||||
|
// pub use root_props::*;
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
mod client_props;
|
||||||
|
pub use client_props::*;
|
156
src/icccm/traits.rs
Normal file
156
src/icccm/traits.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
use crate::icccm::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 trait IcccmRequest<'a> {
|
||||||
|
/// The underlying [`xcb::Request`] used to do the actual heavy lifting
|
||||||
|
type XcbRequest: xcb::Request;
|
||||||
|
|
||||||
|
/// The icccm wrapper for the [`xcb::Request::Cookie`]
|
||||||
|
type IcccmCookie: IcccmCookie<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 icccm cookie wrapper
|
||||||
|
fn convert_cookie(
|
||||||
|
&'a self,
|
||||||
|
xcb_cookie: <Self::XcbRequest as xcb::Request>::Cookie,
|
||||||
|
) -> Self::IcccmCookie;
|
||||||
|
|
||||||
|
/// Default implementation. Delegate the request to the [`xcb::Connection`] and wrap the
|
||||||
|
/// response in the icccm cookie wrapper.
|
||||||
|
///
|
||||||
|
/// There is usually no need to override the provided default implementation.
|
||||||
|
fn send(&'a self, con: &Connection) -> Self::IcccmCookie {
|
||||||
|
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 trait IcccmVoidRequestChecked<'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 trait IcccmPropertyRequestUnchecked {
|
||||||
|
type IcccmCookie: IcccmPropertyCookieUnchecked;
|
||||||
|
|
||||||
|
/// 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::IcccmCookie;
|
||||||
|
|
||||||
|
/// Default implementation. Delegate the request to the [`xcb::Connection`].
|
||||||
|
///
|
||||||
|
/// Returns a wrapped cookie that tracks the IcccmReply 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::IcccmCookie {
|
||||||
|
let xcb_request = self.xcb_request(con);
|
||||||
|
let xcb_cookie = con.con.send_request_unchecked(&xcb_request);
|
||||||
|
self.convert_cookie(xcb_cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Most generic icccm wrapper for an [`xcb::Cookie`]
|
||||||
|
///
|
||||||
|
/// This is needed for [`IcccmRequest`] 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 trait IcccmCookie {
|
||||||
|
/// 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 icccm cookie wrapper
|
||||||
|
/// for SetProperty and ClientMessage requests needed. [`xcb::VoidCookie`] satisfies
|
||||||
|
/// all that is needed for [`icccm`]. In order to use it with [`IcccmRequest`] we need
|
||||||
|
/// this impl.
|
||||||
|
impl IcccmCookie for xcb::VoidCookie {
|
||||||
|
type XcbCookie = xcb::VoidCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// icccm 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 trait IcccmPropertyCookieChecked {
|
||||||
|
type Reply: IcccmPropertyReply;
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// icccm 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 trait IcccmPropertyCookieUnchecked {
|
||||||
|
type Reply: IcccmPropertyReply;
|
||||||
|
|
||||||
|
/// 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 icccm property reply trait is used to convert a generic reply to a [`xcb::x::GetProperty`]
|
||||||
|
/// request to a specific icccm reply struct.
|
||||||
|
///
|
||||||
|
/// The connection between a icccm request and the reply struct is made via icccm property cookies
|
||||||
|
/// ([`IcccmPropertyCookieChecked`] and [`IcccmPropertyCookieUnchecked`]
|
||||||
|
pub trait IcccmPropertyReply: From<xcb::x::GetPropertyReply> {}
|
||||||
|
impl<T> IcccmPropertyReply for T where T: From<xcb::x::GetPropertyReply> {}
|
|
@ -4,6 +4,9 @@
|
||||||
#[cfg(feature = "ewmh")]
|
#[cfg(feature = "ewmh")]
|
||||||
pub mod ewmh;
|
pub mod ewmh;
|
||||||
|
|
||||||
|
#[cfg(feature = "icccm")]
|
||||||
|
pub mod icccm;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue