From 36add615d9f9e248068d8cb1a751789f65a22140 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 8 May 2022 16:18:29 +0200 Subject: [PATCH] Beginning of `rust-xcb-wm` Initial structure and first basic working EWMH requests --- .gitignore | 3 + Cargo.toml | 20 +++ src/ewmh/atoms.rs | 282 +++++++++++++++++++++++++++++++++++++++ src/ewmh/ewmh.rs | 133 ++++++++++++++++++ src/ewmh/mod.rs | 4 + src/ewmh/proto.rs | 228 +++++++++++++++++++++++++++++++ src/ewmh/proto_traits.rs | 93 +++++++++++++ src/lib.rs | 10 ++ 8 files changed, 773 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/ewmh/atoms.rs create mode 100644 src/ewmh/ewmh.rs create mode 100644 src/ewmh/mod.rs create mode 100644 src/ewmh/proto.rs create mode 100644 src/ewmh/proto_traits.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ab5292 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +.idea diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f55c807 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rust-xcb-wm" +version = "0.1.0" +authors = [ "Armin Friedl " ] +description = "Rust implementation of xcb-wm - icccm and ewmh extensions for xcb" +readme = "README.md" +keyworks = ["icccm", "ewmh", "xcb-wm", "xcb", "window", "xlib", "x11"] +license = "MIT" +edition = "2018" +repository = "https://git.friedl.net/incubator/rust-xcb-wm" +homepage = "https://git.friedl.net/incubator/rust-xcb-wm" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +xcb = "1" + +[features] +icccm = [] +ewmh = [] diff --git a/src/ewmh/atoms.rs b/src/ewmh/atoms.rs new file mode 100644 index 0000000..a6cc0fb --- /dev/null +++ b/src/ewmh/atoms.rs @@ -0,0 +1,282 @@ +use std::collections::HashMap; + +const ATOM_NAMES: [&'static str; 82] = [ + "_NET_SUPPORTED", + "_NET_CLIENT_LIST", + "_NET_CLIENT_LIST_STACKING", + "_NET_NUMBER_OF_DESKTOPS", + "_NET_DESKTOP_GEOMETRY", + "_NET_DESKTOP_VIEWPORT", + "_NET_CURRENT_DESKTOP", + "_NET_DESKTOP_NAMES", + "_NET_ACTIVE_WINDOW", + "_NET_WORKAREA", + "_NET_SUPPORTING_WM_CHECK", + "_NET_VIRTUAL_ROOTS", + "_NET_DESKTOP_LAYOUT", + "_NET_SHOWING_DESKTOP", + "_NET_CLOSE_WINDOW", + "_NET_MOVERESIZE_WINDOW", + "_NET_WM_MOVERESIZE", + "_NET_RESTACK_WINDOW", + "_NET_REQUEST_FRAME_EXTENTS", + "_NET_WM_NAME", + "_NET_WM_VISIBLE_NAME", + "_NET_WM_ICON_NAME", + "_NET_WM_VISIBLE_ICON_NAME", + "_NET_WM_DESKTOP", + "_NET_WM_WINDOW_TYPE", + "_NET_WM_STATE", + "_NET_WM_ALLOWED_ACTIONS", + "_NET_WM_STRUT", + "_NET_WM_STRUT_PARTIAL", + "_NET_WM_ICON_GEOMETRY", + "_NET_WM_ICON", + "_NET_WM_PID", + "_NET_WM_HANDLED_ICONS", + "_NET_WM_USER_TIME", + "_NET_WM_USER_TIME_WINDOW", + "_NET_FRAME_EXTENTS", + "_NET_WM_PING", + "_NET_WM_SYNC_REQUEST", + "_NET_WM_SYNC_REQUEST_COUNTER", + "_NET_WM_FULLSCREEN_MONITORS", + "_NET_WM_FULL_PLACEMENT", + "UTF8_STRING", + "WM_PROTOCOLS", + "MANAGER", + "_NET_WM_WINDOW_TYPE_DESKTOP", + "_NET_WM_WINDOW_TYPE_DOCK", + "_NET_WM_WINDOW_TYPE_TOOLBAR", + "_NET_WM_WINDOW_TYPE_MENU", + "_NET_WM_WINDOW_TYPE_UTILITY", + "_NET_WM_WINDOW_TYPE_SPLASH", + "_NET_WM_WINDOW_TYPE_DIALOG", + "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", + "_NET_WM_WINDOW_TYPE_POPUP_MENU", + "_NET_WM_WINDOW_TYPE_TOOLTIP", + "_NET_WM_WINDOW_TYPE_NOTIFICATION", + "_NET_WM_WINDOW_TYPE_COMBO", + "_NET_WM_WINDOW_TYPE_DND", + "_NET_WM_WINDOW_TYPE_NORMAL", + "_NET_WM_STATE_MODAL", + "_NET_WM_STATE_STICKY", + "_NET_WM_STATE_MAXIMIZED_VERT", + "_NET_WM_STATE_MAXIMIZED_HORZ", + "_NET_WM_STATE_SHADED", + "_NET_WM_STATE_SKIP_TASKBAR", + "_NET_WM_STATE_SKIP_PAGER", + "_NET_WM_STATE_HIDDEN", + "_NET_WM_STATE_FULLSCREEN", + "_NET_WM_STATE_ABOVE", + "_NET_WM_STATE_BELOW", + "_NET_WM_STATE_DEMANDS_ATTENTION", + "_NET_WM_ACTION_MOVE", + "_NET_WM_ACTION_RESIZE", + "_NET_WM_ACTION_MINIMIZE", + "_NET_WM_ACTION_SHADE", + "_NET_WM_ACTION_STICK", + "_NET_WM_ACTION_MAXIMIZE_HORZ", + "_NET_WM_ACTION_MAXIMIZE_VERT", + "_NET_WM_ACTION_FULLSCREEN", + "_NET_WM_ACTION_CHANGE_DESKTOP", + "_NET_WM_ACTION_CLOSE", + "_NET_WM_ACTION_ABOVE", + "_NET_WM_ACTION_BELOW" +]; + +pub struct Atoms { + // TODO _NET_WM_CM_Sn atoms + pub _NET_SUPPORTED: xcb::x::Atom, + pub _NET_CLIENT_LIST: xcb::x::Atom, + pub _NET_CLIENT_LIST_STACKING: xcb::x::Atom, + pub _NET_NUMBER_OF_DESKTOPS: xcb::x::Atom, + pub _NET_DESKTOP_GEOMETRY: xcb::x::Atom, + pub _NET_DESKTOP_VIEWPORT: xcb::x::Atom, + pub _NET_CURRENT_DESKTOP: xcb::x::Atom, + pub _NET_DESKTOP_NAMES: xcb::x::Atom, + pub _NET_ACTIVE_WINDOW: xcb::x::Atom, + pub _NET_WORKAREA: xcb::x::Atom, + pub _NET_SUPPORTING_WM_CHECK: xcb::x::Atom, + pub _NET_VIRTUAL_ROOTS: xcb::x::Atom, + pub _NET_DESKTOP_LAYOUT: xcb::x::Atom, + pub _NET_SHOWING_DESKTOP: xcb::x::Atom, + pub _NET_CLOSE_WINDOW: xcb::x::Atom, + pub _NET_MOVERESIZE_WINDOW: xcb::x::Atom, + pub _NET_WM_MOVERESIZE: xcb::x::Atom, + pub _NET_RESTACK_WINDOW: xcb::x::Atom, + pub _NET_REQUEST_FRAME_EXTENTS: xcb::x::Atom, + pub _NET_WM_NAME: xcb::x::Atom, + pub _NET_WM_VISIBLE_NAME: xcb::x::Atom, + pub _NET_WM_ICON_NAME: xcb::x::Atom, + pub _NET_WM_VISIBLE_ICON_NAME: xcb::x::Atom, + pub _NET_WM_DESKTOP: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE: xcb::x::Atom, + pub _NET_WM_STATE: xcb::x::Atom, + pub _NET_WM_ALLOWED_ACTIONS: xcb::x::Atom, + pub _NET_WM_STRUT: xcb::x::Atom, + pub _NET_WM_STRUT_PARTIAL: xcb::x::Atom, + pub _NET_WM_ICON_GEOMETRY: xcb::x::Atom, + pub _NET_WM_ICON: xcb::x::Atom, + pub _NET_WM_PID: xcb::x::Atom, + pub _NET_WM_HANDLED_ICONS: xcb::x::Atom, + pub _NET_WM_USER_TIME: xcb::x::Atom, + pub _NET_WM_USER_TIME_WINDOW: xcb::x::Atom, + pub _NET_FRAME_EXTENTS: xcb::x::Atom, + pub _NET_WM_PING: xcb::x::Atom, + pub _NET_WM_SYNC_REQUEST: xcb::x::Atom, + pub _NET_WM_SYNC_REQUEST_COUNTER: xcb::x::Atom, + pub _NET_WM_FULLSCREEN_MONITORS: xcb::x::Atom, + pub _NET_WM_FULL_PLACEMENT: xcb::x::Atom, + pub UTF8_STRING: xcb::x::Atom, + pub WM_PROTOCOLS: xcb::x::Atom, + pub MANAGER: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_DESKTOP: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_DOCK: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_TOOLBAR: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_MENU: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_UTILITY: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_SPLASH: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_DIALOG: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_DROPDOWN_MENU: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_POPUP_MENU: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_TOOLTIP: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_NOTIFICATION: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_COMBO: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_DND: xcb::x::Atom, + pub _NET_WM_WINDOW_TYPE_NORMAL: xcb::x::Atom, + pub _NET_WM_STATE_MODAL: xcb::x::Atom, + pub _NET_WM_STATE_STICKY: xcb::x::Atom, + pub _NET_WM_STATE_MAXIMIZED_VERT: xcb::x::Atom, + pub _NET_WM_STATE_MAXIMIZED_HORZ: xcb::x::Atom, + pub _NET_WM_STATE_SHADED: xcb::x::Atom, + pub _NET_WM_STATE_SKIP_TASKBAR: xcb::x::Atom, + pub _NET_WM_STATE_SKIP_PAGER: xcb::x::Atom, + pub _NET_WM_STATE_HIDDEN: xcb::x::Atom, + pub _NET_WM_STATE_FULLSCREEN: xcb::x::Atom, + pub _NET_WM_STATE_ABOVE: xcb::x::Atom, + pub _NET_WM_STATE_BELOW: xcb::x::Atom, + pub _NET_WM_STATE_DEMANDS_ATTENTION: xcb::x::Atom, + pub _NET_WM_ACTION_MOVE: xcb::x::Atom, + pub _NET_WM_ACTION_RESIZE: xcb::x::Atom, + pub _NET_WM_ACTION_MINIMIZE: xcb::x::Atom, + pub _NET_WM_ACTION_SHADE: xcb::x::Atom, + pub _NET_WM_ACTION_STICK: xcb::x::Atom, + pub _NET_WM_ACTION_MAXIMIZE_HORZ: xcb::x::Atom, + pub _NET_WM_ACTION_MAXIMIZE_VERT: xcb::x::Atom, + pub _NET_WM_ACTION_FULLSCREEN: xcb::x::Atom, + pub _NET_WM_ACTION_CHANGE_DESKTOP: xcb::x::Atom, + pub _NET_WM_ACTION_CLOSE: xcb::x::Atom, + pub _NET_WM_ACTION_ABOVE: xcb::x::Atom, + pub _NET_WM_ACTION_BELOW: 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 { + _NET_SUPPORTED: atoms.remove("_NET_SUPPORTED").unwrap(), + _NET_CLIENT_LIST: atoms.remove("_NET_CLIENT_LIST").unwrap(), + _NET_CLIENT_LIST_STACKING: atoms.remove("_NET_CLIENT_LIST_STACKING").unwrap(), + _NET_NUMBER_OF_DESKTOPS: atoms.remove("_NET_NUMBER_OF_DESKTOPS").unwrap(), + _NET_DESKTOP_GEOMETRY: atoms.remove("_NET_DESKTOP_GEOMETRY").unwrap(), + _NET_DESKTOP_VIEWPORT: atoms.remove("_NET_DESKTOP_VIEWPORT").unwrap(), + _NET_CURRENT_DESKTOP: atoms.remove("_NET_CURRENT_DESKTOP").unwrap(), + _NET_DESKTOP_NAMES: atoms.remove("_NET_DESKTOP_NAMES").unwrap(), + _NET_ACTIVE_WINDOW: atoms.remove("_NET_ACTIVE_WINDOW").unwrap(), + _NET_WORKAREA: atoms.remove("_NET_WORKAREA").unwrap(), + _NET_SUPPORTING_WM_CHECK: atoms.remove("_NET_SUPPORTING_WM_CHECK").unwrap(), + _NET_VIRTUAL_ROOTS: atoms.remove("_NET_VIRTUAL_ROOTS").unwrap(), + _NET_DESKTOP_LAYOUT: atoms.remove("_NET_DESKTOP_LAYOUT").unwrap(), + _NET_SHOWING_DESKTOP: atoms.remove("_NET_SHOWING_DESKTOP").unwrap(), + _NET_CLOSE_WINDOW: atoms.remove("_NET_CLOSE_WINDOW").unwrap(), + _NET_MOVERESIZE_WINDOW: atoms.remove("_NET_MOVERESIZE_WINDOW").unwrap(), + _NET_WM_MOVERESIZE: atoms.remove("_NET_WM_MOVERESIZE").unwrap(), + _NET_RESTACK_WINDOW: atoms.remove("_NET_RESTACK_WINDOW").unwrap(), + _NET_REQUEST_FRAME_EXTENTS: atoms.remove("_NET_REQUEST_FRAME_EXTENTS").unwrap(), + _NET_WM_NAME: atoms.remove("_NET_WM_NAME").unwrap(), + _NET_WM_VISIBLE_NAME: atoms.remove("_NET_WM_VISIBLE_NAME").unwrap(), + _NET_WM_ICON_NAME: atoms.remove("_NET_WM_ICON_NAME").unwrap(), + _NET_WM_VISIBLE_ICON_NAME: atoms.remove("_NET_WM_VISIBLE_ICON_NAME").unwrap(), + _NET_WM_DESKTOP: atoms.remove("_NET_WM_DESKTOP").unwrap(), + _NET_WM_WINDOW_TYPE: atoms.remove("_NET_WM_WINDOW_TYPE").unwrap(), + _NET_WM_STATE: atoms.remove("_NET_WM_STATE").unwrap(), + _NET_WM_ALLOWED_ACTIONS: atoms.remove("_NET_WM_ALLOWED_ACTIONS").unwrap(), + _NET_WM_STRUT: atoms.remove("_NET_WM_STRUT").unwrap(), + _NET_WM_STRUT_PARTIAL: atoms.remove("_NET_WM_STRUT_PARTIAL").unwrap(), + _NET_WM_ICON_GEOMETRY: atoms.remove("_NET_WM_ICON_GEOMETRY").unwrap(), + _NET_WM_ICON: atoms.remove("_NET_WM_ICON").unwrap(), + _NET_WM_PID: atoms.remove("_NET_WM_PID").unwrap(), + _NET_WM_HANDLED_ICONS: atoms.remove("_NET_WM_HANDLED_ICONS").unwrap(), + _NET_WM_USER_TIME: atoms.remove("_NET_WM_USER_TIME").unwrap(), + _NET_WM_USER_TIME_WINDOW: atoms.remove("_NET_WM_USER_TIME_WINDOW").unwrap(), + _NET_FRAME_EXTENTS: atoms.remove("_NET_FRAME_EXTENTS").unwrap(), + _NET_WM_PING: atoms.remove("_NET_WM_PING").unwrap(), + _NET_WM_SYNC_REQUEST: atoms.remove("_NET_WM_SYNC_REQUEST").unwrap(), + _NET_WM_SYNC_REQUEST_COUNTER: atoms.remove("_NET_WM_SYNC_REQUEST_COUNTER").unwrap(), + _NET_WM_FULLSCREEN_MONITORS: atoms.remove("_NET_WM_FULLSCREEN_MONITORS").unwrap(), + _NET_WM_FULL_PLACEMENT: atoms.remove("_NET_WM_FULL_PLACEMENT").unwrap(), + UTF8_STRING: atoms.remove("UTF8_STRING").unwrap(), + WM_PROTOCOLS: atoms.remove("WM_PROTOCOLS").unwrap(), + MANAGER: atoms.remove("MANAGER").unwrap(), + _NET_WM_WINDOW_TYPE_DESKTOP: atoms.remove("_NET_WM_WINDOW_TYPE_DESKTOP").unwrap(), + _NET_WM_WINDOW_TYPE_DOCK: atoms.remove("_NET_WM_WINDOW_TYPE_DOCK").unwrap(), + _NET_WM_WINDOW_TYPE_TOOLBAR: atoms.remove("_NET_WM_WINDOW_TYPE_TOOLBAR").unwrap(), + _NET_WM_WINDOW_TYPE_MENU: atoms.remove("_NET_WM_WINDOW_TYPE_MENU").unwrap(), + _NET_WM_WINDOW_TYPE_UTILITY: atoms.remove("_NET_WM_WINDOW_TYPE_UTILITY").unwrap(), + _NET_WM_WINDOW_TYPE_SPLASH: atoms.remove("_NET_WM_WINDOW_TYPE_SPLASH").unwrap(), + _NET_WM_WINDOW_TYPE_DIALOG: atoms.remove("_NET_WM_WINDOW_TYPE_DIALOG").unwrap(), + _NET_WM_WINDOW_TYPE_DROPDOWN_MENU: atoms.remove("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU").unwrap(), + _NET_WM_WINDOW_TYPE_POPUP_MENU: atoms.remove("_NET_WM_WINDOW_TYPE_POPUP_MENU").unwrap(), + _NET_WM_WINDOW_TYPE_TOOLTIP: atoms.remove("_NET_WM_WINDOW_TYPE_TOOLTIP").unwrap(), + _NET_WM_WINDOW_TYPE_NOTIFICATION: atoms.remove("_NET_WM_WINDOW_TYPE_NOTIFICATION").unwrap(), + _NET_WM_WINDOW_TYPE_COMBO: atoms.remove("_NET_WM_WINDOW_TYPE_COMBO").unwrap(), + _NET_WM_WINDOW_TYPE_DND: atoms.remove("_NET_WM_WINDOW_TYPE_DND").unwrap(), + _NET_WM_WINDOW_TYPE_NORMAL: atoms.remove("_NET_WM_WINDOW_TYPE_NORMAL").unwrap(), + _NET_WM_STATE_MODAL: atoms.remove("_NET_WM_STATE_MODAL").unwrap(), + _NET_WM_STATE_STICKY: atoms.remove("_NET_WM_STATE_STICKY").unwrap(), + _NET_WM_STATE_MAXIMIZED_VERT: atoms.remove("_NET_WM_STATE_MAXIMIZED_VERT").unwrap(), + _NET_WM_STATE_MAXIMIZED_HORZ: atoms.remove("_NET_WM_STATE_MAXIMIZED_HORZ").unwrap(), + _NET_WM_STATE_SHADED: atoms.remove("_NET_WM_STATE_SHADED").unwrap(), + _NET_WM_STATE_SKIP_TASKBAR: atoms.remove("_NET_WM_STATE_SKIP_TASKBAR").unwrap(), + _NET_WM_STATE_SKIP_PAGER: atoms.remove("_NET_WM_STATE_SKIP_PAGER").unwrap(), + _NET_WM_STATE_HIDDEN: atoms.remove("_NET_WM_STATE_HIDDEN").unwrap(), + _NET_WM_STATE_FULLSCREEN: atoms.remove("_NET_WM_STATE_FULLSCREEN").unwrap(), + _NET_WM_STATE_ABOVE: atoms.remove("_NET_WM_STATE_ABOVE").unwrap(), + _NET_WM_STATE_BELOW: atoms.remove("_NET_WM_STATE_BELOW").unwrap(), + _NET_WM_STATE_DEMANDS_ATTENTION: atoms.remove("_NET_WM_STATE_DEMANDS_ATTENTION").unwrap(), + _NET_WM_ACTION_MOVE: atoms.remove("_NET_WM_ACTION_MOVE").unwrap(), + _NET_WM_ACTION_RESIZE: atoms.remove("_NET_WM_ACTION_RESIZE").unwrap(), + _NET_WM_ACTION_MINIMIZE: atoms.remove("_NET_WM_ACTION_MINIMIZE").unwrap(), + _NET_WM_ACTION_SHADE: atoms.remove("_NET_WM_ACTION_SHADE").unwrap(), + _NET_WM_ACTION_STICK: atoms.remove("_NET_WM_ACTION_STICK").unwrap(), + _NET_WM_ACTION_MAXIMIZE_HORZ: atoms.remove("_NET_WM_ACTION_MAXIMIZE_HORZ").unwrap(), + _NET_WM_ACTION_MAXIMIZE_VERT: atoms.remove("_NET_WM_ACTION_MAXIMIZE_VERT").unwrap(), + _NET_WM_ACTION_FULLSCREEN: atoms.remove("_NET_WM_ACTION_FULLSCREEN").unwrap(), + _NET_WM_ACTION_CHANGE_DESKTOP: atoms.remove("_NET_WM_ACTION_CHANGE_DESKTOP").unwrap(), + _NET_WM_ACTION_CLOSE: atoms.remove("_NET_WM_ACTION_CLOSE").unwrap(), + _NET_WM_ACTION_ABOVE: atoms.remove("_NET_WM_ACTION_ABOVE").unwrap(), + _NET_WM_ACTION_BELOW: atoms.remove("_NET_WM_ACTION_BELOW").unwrap(), + } + } +} diff --git a/src/ewmh/ewmh.rs b/src/ewmh/ewmh.rs new file mode 100644 index 0000000..4778a40 --- /dev/null +++ b/src/ewmh/ewmh.rs @@ -0,0 +1,133 @@ +use xcb::x::{Atom, GetPropertyReply}; + +use crate::ewmh::proto_traits::{EwmhCookie, EwmhRequest}; + +use super::atoms::Atoms; + +pub struct Connection<'a> { + pub(crate) con: &'a xcb::Connection, + pub atoms: Atoms, +} + +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::GetNetSupportedRequest {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + + for atom in reply.value { + 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::GetNetClientListRequest {}; + 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::GetNetNumberOfDesktopsRequest {}; + 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::GetNetDesktopGeometryRequest {}; + let cookie = ewmh_con.send_request(request); + let reply = ewmh_con.wait_for_reply(cookie); + println!("{:?}", reply); + } + + #[test] + fn desktop_viewport() { + let xcb_con = xcb::Connection::connect(Option::None).unwrap().0; + let ewmh_con = crate::ewmh::ewmh::Connection::connect(&xcb_con); + + let request = crate::ewmh::proto::GetNetDesktopViewportRequest {}; + let 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::GetNetCurrentDesktopRequest {}; + 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::GetNetDesktopNamesRequest {}; + 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 new file mode 100644 index 0000000..9a6a832 --- /dev/null +++ b/src/ewmh/mod.rs @@ -0,0 +1,4 @@ +mod ewmh; +mod atoms; +mod proto; +mod proto_traits; \ No newline at end of file diff --git a/src/ewmh/proto.rs b/src/ewmh/proto.rs new file mode 100644 index 0000000..2efd3d7 --- /dev/null +++ b/src/ewmh/proto.rs @@ -0,0 +1,228 @@ +use crate::ewmh::ewmh::Connection; +use crate::ewmh::proto_traits::{EwmhCookie, EwmhCookieUnchecked, EwmhRequest, EwmhRequestData}; +use crate::{request, root_request}; + +root_request! { + GetNetSupportedRequest, + _NET_SUPPORTED, + ATOM_ATOM, + GetNetSupportedCookie, + GetNetSupportedCookieUnchecked, + GetNetSupportedReply +} + +#[derive(Debug)] +pub struct GetNetSupportedReply { + pub value: Vec, +} + +impl From for GetNetSupportedReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetSupportedReply { + value: reply.value().into(), + } + } +} + +root_request! { + GetNetClientListRequest, + _NET_CLIENT_LIST, + ATOM_WINDOW, + GetNetClientListCookie, + GetNetClientListCookieUnchecked, + GetNetClientListReply +} + +#[derive(Debug)] +pub struct GetNetClientListReply { + pub value: Vec, +} + +impl From for GetNetClientListReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetClientListReply { + value: reply.value().into(), + } + } +} + +root_request! { + GetNetClientListStackingRequest, + _NET_CLIENT_LIST_STACKING, + ATOM_WINDOW, + GetNetClientListStackingCookie, + GetNetClientListStackingCookieUnchecked, + GetNetClientListStackingReply +} + +#[derive(Debug)] +pub struct GetNetClientListStackingReply { + pub value: Vec, +} + +impl From for GetNetClientListStackingReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetClientListStackingReply { + value: reply.value().into(), + } + } +} + +root_request! { + GetNetNumberOfDesktopsRequest, + _NET_NUMBER_OF_DESKTOPS, + ATOM_CARDINAL, + GetNetNumberOfDesktopsCookie, + GetNetNumberOfDesktopsCookieUnchecked, + GetNetNumberOfDesktopsReply +} + +#[derive(Debug)] +pub struct GetNetNumberOfDesktopsReply { + pub value: u32, +} + +impl From for GetNetNumberOfDesktopsReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetNumberOfDesktopsReply { + value: reply.value::()[0], + } + } +} + +root_request! { + GetNetDesktopGeometryRequest, + _NET_DESKTOP_GEOMETRY, + ATOM_CARDINAL, + GetNetDesktopGeometryCookie, + GetNetDesktopGeometryCookieUnchecked, + GetNetDesktopGeometryReply +} + +#[derive(Debug)] +pub struct GetNetDesktopGeometryReply { + pub width: u32, + pub height: u32, +} + +impl From for GetNetDesktopGeometryReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetDesktopGeometryReply { + width: reply.value::()[0], + height: reply.value::()[1], + } + } +} + +root_request! { + GetNetDesktopViewportRequest, + _NET_DESKTOP_VIEWPORT, + ATOM_CARDINAL, + GetNetDesktopViewportCookie, + GetNetDesktopViewportCookieUnchecked, + GetNetDesktopViewportReply +} + +#[derive(Debug)] +pub struct GetNetDesktopViewportReply { + pub x: u32, + pub y: u32, +} + +impl From for GetNetDesktopViewportReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetDesktopViewportReply { + x: reply.value::()[0], + y: reply.value::()[1], + } + } +} + +root_request! { + GetNetCurrentDesktopRequest, + _NET_CURRENT_DESKTOP, + ATOM_CARDINAL, + GetNetCurrentDesktopCookie, + GetNetCurrentDesktopCookieUnchecked, + GetNetCurrentDesktopReply +} + +#[derive(Debug)] +pub struct GetNetCurrentDesktopReply { + pub desktop: u32, +} + +impl From for GetNetCurrentDesktopReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetCurrentDesktopReply { + desktop: reply.value::()[0], + } + } +} + +request! { + GetNetDesktopNamesRequest, + GetNetDesktopNamesCookie, + GetNetDesktopNamesCookieUnchecked, + GetNetDesktopNamesReply +} + +#[derive(Debug)] +pub struct GetNetDesktopNamesReply { + pub value: Vec, +} + +impl EwmhRequestData for GetNetDesktopNamesRequest { + fn get_request_data(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms._NET_DESKTOP_NAMES, + r#type: con.atoms.UTF8_STRING, + long_offset: 0, + long_length: u32::MAX, + } + } +} + +impl From for GetNetDesktopNamesReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + let mut vals = vec![]; + let mut buf = vec![]; + + for b in reply.value::() { + if *b != 0x00 { + buf.push(*b) + } else if !buf.is_empty() { + vals.push(String::from_utf8(buf.clone()).unwrap()); + buf.clear(); + } else { + buf.clear(); + } + } + + GetNetDesktopNamesReply { value: vals } + } +} + +root_request! { + GetNetActiveWindowRequest, + _NET_ACTIVE_WINDOW, + ATOM_WINDOW, + GetNetActiveWindowCookie, + GetNetActiveWindowCookieUnchecked, + GetNetActiveWindowReply +} + +#[derive(Debug)] +pub struct GetNetActiveWindowReply { + pub value: xcb::x::Window, +} + +impl From for GetNetActiveWindowReply { + fn from(reply: xcb::x::GetPropertyReply) -> Self { + GetNetActiveWindowReply { + value: reply.value::()[0], + } + } +} diff --git a/src/ewmh/proto_traits.rs b/src/ewmh/proto_traits.rs new file mode 100644 index 0000000..c803569 --- /dev/null +++ b/src/ewmh/proto_traits.rs @@ -0,0 +1,93 @@ +use crate::ewmh::ewmh::Connection; + +pub(crate) trait EwmhRequest: EwmhRequestData { + 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 { + fn get_request_data(&self, con: &Connection) -> xcb::x::GetProperty; +} + +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_export] +macro_rules! 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 EwmhRequest 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_export] +macro_rules! root_request { + ($req: ident, $prop: ident, $type: ident, $cookie: ident, $cookie_unchecked: ident, $reply: ident) => { + request! {$req, $cookie, $cookie_unchecked, $reply} + + impl EwmhRequestData for $req { + fn get_request_data(&self, con: &Connection) -> xcb::x::GetProperty { + xcb::x::GetProperty { + delete: false, + window: con.con.get_setup().roots().next().unwrap().root(), + property: con.atoms.$prop, + r#type: xcb::x::$type, + long_offset: 0, + long_length: u32::MAX, + } + } + } + }; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..742e157 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +mod ewmh; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +}