srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/x11/src/platform/mod.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-07-31 16:22:00 +0200
committersrdusr <[email protected]>2024-07-31 16:22:00 +0200
commitc180bfc34e4c74ba7cac9e0220e9c172c239fe71 (patch)
tree45550c7eb7ae819a9c3a415acb9cfa5be1cfd03b /crates/x11/src/platform/mod.rs
parent38683f069a039f9da6d7ddc64167850560fd07ed (diff)
downloadsrdwm-c180bfc34e4c74ba7cac9e0220e9c172c239fe71.tar.gz
srdwm-c180bfc34e4c74ba7cac9e0220e9c172c239fe71.zip
Split crates/x11/src/lib.rs (925 lines) into platform/
Pure reorganization, no behavior change - verified by diffing the function-name and struct/trait-name sets before/after (both identical) plus a full cargo test pass. lib.rs is now a thin shim (mod declaration + pub use), same pattern crates/config used, since a crate root can't itself become a directory. platform/mod.rs keeps the atom table, Frame/X11Platform's struct definitions, the small free-function helpers (err, modmask_for_keycode_in_mod_slots, rgb_to_pixel), and the ClonedForRender trait+impl. The rest splits by concern: - connect.rs: connect, keymap/modifier helpers, grab_keybindings. - window.rs: manage_new_window and the other per-client lifecycle methods (window_title/class, supports_wm_delete, unmanage, frame_for). - events.rs: handle_event, the X11 event-dispatch loop. - actions.rs: raise_and_focus/request_close/sync_geometry/ redraw_all_decorations. - trait_impl.rs: `impl Platform for X11Platform` - named to avoid clippy's module_inception lint, since the containing directory is already named `platform`. - tests.rs: unsplit, same reasoning as every other split this pass. A handful of X11Platform methods (frame_for, manage_new_window, unmanage, raise_and_focus, request_close, sync_geometry, keycode_to_keysym, modifiers_from_state, handle_event) went from private to pub(super): called across what are now sibling submodules, which Rust's privacy model doesn't let see each other's private items.
Diffstat (limited to 'crates/x11/src/platform/mod.rs')
-rw-r--r--crates/x11/src/platform/mod.rs146
1 files changed, 146 insertions, 0 deletions
diff --git a/crates/x11/src/platform/mod.rs b/crates/x11/src/platform/mod.rs
new file mode 100644
index 0000000..eea936f
--- /dev/null
+++ b/crates/x11/src/platform/mod.rs
@@ -0,0 +1,146 @@
+//! X11 backend for srdwm: a classic reparenting window manager that draws
+//! its own title bar (close/maximize/minimize buttons, drag-to-move,
+//! edge/corner resize) rather than relying on any toolkit's decorations -
+//! this is what gives "full title bar support" parity with Windows/macOS on
+//! X11.
+//!
+//! Compared to the legacy C++ `x11_platform.cc` (see docs/PRIOR_ART.md),
+//! this fixes several real bugs rather than porting them:
+//! - the titlebar was drawn at a hardcoded 800px width; here it's sized to
+//! the actual frame width every time (see `redraw_decoration`).
+//! - there was no drag/resize/button hit-testing at all; here it's the
+//! shared `srdwm_core::window::ResizeEdge::hit_test` used by every backend.
+//! - `check_for_other_wm` always returned `true` because its error handler
+//! discarded errors; here we do a *checked* `change_window_attributes`
+//! with `SUBSTRUCTURE_REDIRECT` and propagate a real `BadAccess` as
+//! [`PlatformError::AnotherWmRunning`].
+//! - RandR monitor geometry used the output's physical size in
+//! millimeters instead of the CRTC's pixel mode; here it reads the CRTC.
+//!
+//! Not implemented (documented rather than faked): XKB-level keymaps (only
+//! a hand-maintained keysym table covering common keys, shared with the
+//! Wayland backend via `srdwm_core::keysyms`), ICCCM `WM_HINTS`/urgency, and
+//! EWMH pager/taskbar hints beyond
+//! `_NET_SUPPORTED`/`_NET_CLIENT_LIST`/`_NET_WM_STATE` maximize.
+
+use srdwm_core::keysyms;
+use srdwm_core::{Event, Modifiers, MouseButton, TitlebarHit, Window as CoreWindow, WindowId, TITLEBAR_HEIGHT};
+use srdwm_core::{Monitor, Rect, WindowManager};
+use srdwm_platform::{Platform, PlatformError, PlatformKind, Result as PlatformResult};
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::os::unix::io::AsRawFd;
+use std::rc::Rc;
+use x11rb::connection::Connection;
+use x11rb::protocol::randr::ConnectionExt as _;
+use x11rb::protocol::xproto::{
+ ButtonIndex, ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt as _, CreateGCAux, CreateWindowAux,
+ EventMask, GrabMode, ModMask, Rectangle, StackMode, Window as XWindow, WindowClass,
+};
+use x11rb::protocol::Event as XEvent;
+use x11rb::rust_connection::RustConnection;
+use x11rb::wrapper::ConnectionExt as _;
+use x11rb::COPY_DEPTH_FROM_PARENT;
+
+x11rb::atom_manager! {
+ pub Atoms: AtomsCookie {
+ WM_PROTOCOLS,
+ WM_DELETE_WINDOW,
+ WM_STATE,
+ _NET_SUPPORTED,
+ _NET_WM_NAME,
+ _NET_WM_STATE,
+ _NET_WM_STATE_MAXIMIZED_VERT,
+ _NET_WM_STATE_MAXIMIZED_HORZ,
+ _NET_CLIENT_LIST,
+ _NET_ACTIVE_WINDOW,
+ UTF8_STRING,
+ }
+}
+
+struct Frame {
+ frame: XWindow,
+ client: XWindow,
+ supports_delete: bool,
+}
+
+fn err(e: impl std::fmt::Display) -> PlatformError {
+ PlatformError::Other(e.to_string())
+}
+
+/// Finds which of `ModMask::M1`..`M5` a keycode is bound to, given a
+/// `GetModifierMappingReply`'s flattened `keycodes` list (8 fixed slots --
+/// Shift, Lock, Control, Mod1..Mod5 - each `keycodes_per_modifier` long,
+/// zero-padded). Only scans the Mod1..Mod5 slots (indices 3..8): Shift/
+/// Lock/Control are never where Num Lock lands in practice, and this is
+/// only ever called looking for it. Returns an empty mask if the keycode
+/// isn't bound to any modifier at all (a keyboard with no Num Lock key, or
+/// a keycode of `0` from a lookup that found nothing).
+fn modmask_for_keycode_in_mod_slots(keycode: u8, keycodes_per_modifier: usize, keycodes: &[u8]) -> ModMask {
+ if keycode == 0 || keycodes_per_modifier == 0 {
+ return ModMask::from(0u16);
+ }
+ (3..8usize)
+ .find(|&slot| {
+ let start = slot * keycodes_per_modifier;
+ keycodes.get(start..start + keycodes_per_modifier).is_some_and(|ks| ks.contains(&keycode))
+ })
+ .map(|slot| ModMask::from(1u16 << slot))
+ .unwrap_or(ModMask::from(0u16))
+}
+
+/// Packs an RGB triple into the `0x00RRGGBB` pixel value X11's
+/// `border_pixel`/GC `foreground` etc. expect on a TrueColor visual --
+/// matching the format the hardcoded titlebar colour constants in
+/// `redraw_decoration` already use.
+fn rgb_to_pixel((r, g, b): (u8, u8, u8)) -> u32 {
+ ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
+}
+
+pub struct X11Platform {
+ conn: RustConnection,
+ root: XWindow,
+ atoms: Atoms,
+ gc: x11rb::protocol::xproto::Gcontext,
+ font: x11rb::protocol::xproto::Font,
+ wm: Rc<RefCell<WindowManager>>,
+ xid_to_core: HashMap<XWindow, WindowId>,
+ frames: HashMap<WindowId, Frame>,
+ min_keycode: u8,
+ max_keycode: u8,
+ keysyms_per_keycode: u8,
+ keyboard_mapping: Vec<u32>,
+ /// Whichever of `ModMask::M1`..`M5` the server has Num Lock bound to --
+ /// see `grab_keybindings`'s doc comment for why this needs grabbing
+ /// alongside every binding, not just the modifiers a config actually
+ /// asked for.
+ numlock_mask: ModMask,
+ /// `srd`'s control socket - see `srdwm_platform::IpcServer`'s module
+ /// doc comment. `None` if binding it failed (a stale socket from a
+ /// still-running instance, an unwritable runtime dir): the compositor
+ /// itself still starts either way, matching how the Wayland backends
+ /// already treat this as non-fatal.
+ ipc: Option<srdwm_platform::IpcServer>,
+}
+
+
+
+/// Small helper so we can grab an owned snapshot of a `&Window` out of a
+/// `Ref<WindowManager>` borrow without holding the borrow across the redraw call.
+trait ClonedForRender {
+ fn cloned_for_render(self) -> Option<CoreWindow>;
+}
+impl ClonedForRender for Option<&CoreWindow> {
+ fn cloned_for_render(self) -> Option<CoreWindow> {
+ self.cloned()
+ }
+}
+
+mod actions;
+mod connect;
+mod events;
+mod trait_impl;
+mod window;
+
+#[cfg(test)]
+mod tests;