diff options
| author | srdusr <[email protected]> | 2024-04-02 00:58:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-04-02 00:58:00 +0200 |
| commit | 8110bb2773b6c841029a51eca7971f42a36f480c (patch) | |
| tree | b3b364a6c0231fe408229e94dc9e25ce4562ad02 /crates/windows | |
| parent | d1f856edc516047575cf46f12fad2b59006363c8 (diff) | |
| download | srdwm-8110bb2773b6c841029a51eca7971f42a36f480c.tar.gz srdwm-8110bb2773b6c841029a51eca7971f42a36f480c.zip | |
Rewrite srdwm in Rust: working X11 and Wayland backends, Lua config
The C++ prototype (moved to legacy-cpp/) was mostly a design skeleton:
X11 and Windows backends were partially real, Wayland created the
wlroots object graph but never wired a single event listener, macOS
was stub except monitor enumeration, and the Lua engine's srd.bind()
stored a key-combo string but never the actual closure. See
docs/PRIOR_ART.md for the full audit.
This replaces it with a Cargo workspace:
- srdwm-core: platform-independent window/workspace/monitor state,
a real master-stack tiling layout, and SmartPlacement grid/cascade/
snap-to-edge placement - fixing several bugs in the C++ version
(hardcoded 2-column grid, cascade that never cascaded, snap-to-edge
that always returned a fixed rect). 35 unit tests.
- srdwm-config: the srd Lua API via mlua, implementing the surface
docs/DEFAULTS.md always documented but the C++ engine never actually
built (srd.window.close()/focus(direction), srd.workspace.next(),
real keybinding closures, require("srd") support). 10 unit tests.
- srdwm-x11: a real reparenting WM with a drawn title bar (buttons,
drag, resize), verified live under Xephyr - frame placement and
client offset match srdwm-core's computed geometry exactly, and the
decoration renders correctly on screen.
- srdwm-wayland: a from-scratch smithay compositor (the C++ version
had nothing working to port from) - runs via the winit backend,
tracks xdg-shell toplevels through the same WindowManager and
hit-testing code X11 uses, verified to start/render/run without
crashing. Decorations are solid-color (no text yet); see
docs/IMPLEMENTATION_STATUS.md for exact scope.
- srdwm-windows / srdwm-macos: structured, cfg-gated designs informed
by komorebi/glazewm and yabai/AeroSpace respectively (see
docs/PRIOR_ART.md), honestly marked as unbuilt/unverified since this
sandbox has no Windows or macOS target.
Diffstat (limited to 'crates/windows')
| -rw-r--r-- | crates/windows/Cargo.toml | 21 | ||||
| -rw-r--r-- | crates/windows/src/lib.rs | 250 |
2 files changed, 271 insertions, 0 deletions
diff --git a/crates/windows/Cargo.toml b/crates/windows/Cargo.toml new file mode 100644 index 0000000..53c01eb --- /dev/null +++ b/crates/windows/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "srdwm-windows" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Windows backend for srdwm (DWM border API + Win32 window ops)" + +[dependencies] +srdwm-core.workspace = true +srdwm-platform.workspace = true +log.workspace = true + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.58", features = [ + "Win32_Foundation", + "Win32_UI_WindowsAndMessaging", + "Win32_Graphics_Dwm", + "Win32_Graphics_Gdi", + "Win32_System_Threading", + "Win32_UI_Input_KeyboardAndMouse", +] } diff --git a/crates/windows/src/lib.rs b/crates/windows/src/lib.rs new file mode 100644 index 0000000..b876eb0 --- /dev/null +++ b/crates/windows/src/lib.rs @@ -0,0 +1,250 @@ +//! Windows backend for srdwm. +//! +//! Only the `cfg(windows)` code paths are real; this crate is still included +//! in the workspace on Linux/macOS so it type-checks continuously, but on +//! non-Windows targets every `Platform` method returns +//! `PlatformError::Unsupported` rather than pretending to work - this crate +//! has never been built or run on an actual Windows machine, unlike the X11 +//! backend. +//! +//! Porting notes (see docs/PRIOR_ART.md for the full legacy-C++ digest this +//! is based on): the legacy `windows_platform.cc` had real, working +//! `DwmSetWindowAttribute(DWMWA_BORDER_COLOR, ...)` border tinting (Windows +//! 11+ only), low-level `WH_KEYBOARD_LL`/`WH_MOUSE_LL` hooks, and +//! `EnumDisplayMonitors` monitor enumeration - those are the pieces +//! reimplemented for real below (behind `cfg(windows)`). It had no window +//! subclassing, no virtual-desktop support (would need the undocumented COM +//! `IVirtualDesktopManager` interface), and no custom title bar - DWM does +//! not support custom border widths or a fully custom frame without +//! disabling the native one, so for feature parity with the X11/Wayland +//! "full title bar" look, srdwm on Windows should render its own decoration +//! via a borderless (`WS_POPUP`) window plus manual hit-testing (same +//! `srdwm_core::window::ResizeEdge::hit_test` used by the other backends), +//! rather than trying to theme the native frame. That's flagged as TODO here +//! rather than implemented, since it can't be visually verified without a +//! Windows machine. + +use srdwm_core::{Monitor, Rect, Window, WindowId}; +use srdwm_platform::{Platform, PlatformError, PlatformKind, Result}; + +pub struct WindowsPlatform { + #[cfg(windows)] + windows: std::collections::HashMap<WindowId, windows::Win32::Foundation::HWND>, +} + +impl WindowsPlatform { + pub fn new() -> Result<Self> { + #[cfg(windows)] + { + Ok(Self { windows: Default::default() }) + } + #[cfg(not(windows))] + { + Err(PlatformError::Unsupported("srdwm-windows was not compiled for a Windows target")) + } + } +} + +impl Platform for WindowsPlatform { + fn kind(&self) -> PlatformKind { + PlatformKind::Windows + } + + #[cfg(windows)] + fn poll_events(&mut self) -> Result<Vec<srdwm_core::Event>> { + // TODO: real PeekMessageW/TranslateMessage/DispatchMessage pump, + // mapping WM_* to srdwm_core::Event as the legacy backend did. + Ok(Vec::new()) + } + #[cfg(not(windows))] + fn poll_events(&mut self) -> Result<Vec<srdwm_core::Event>> { + Err(PlatformError::Unsupported("poll_events")) + } + + #[cfg(windows)] + fn monitors(&mut self) -> Result<Vec<Monitor>> { + use windows::Win32::Graphics::Gdi::{ + EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFO, + }; + use windows::Win32::Foundation::{BOOL, LPARAM, RECT}; + + unsafe extern "system" fn callback( + hmonitor: HMONITOR, + _hdc: HDC, + _rect: *mut RECT, + lparam: LPARAM, + ) -> BOOL { + let out = &mut *(lparam.0 as *mut Vec<Monitor>); + let mut info = MONITORINFO { cbSize: std::mem::size_of::<MONITORINFO>() as u32, ..Default::default() }; + if GetMonitorInfoW(hmonitor, &mut info).as_bool() { + let r = info.rcMonitor; + out.push(Monitor::new( + out.len() as u32, + format!("monitor-{}", out.len()), + Rect::new(r.left, r.top, (r.right - r.left) as u32, (r.bottom - r.top) as u32), + )); + } + BOOL(1) + } + + let mut monitors: Vec<Monitor> = Vec::new(); + unsafe { + let _ = EnumDisplayMonitors(None, None, Some(callback), LPARAM(&mut monitors as *mut _ as isize)); + } + Ok(monitors) + } + #[cfg(not(windows))] + fn monitors(&mut self) -> Result<Vec<Monitor>> { + Err(PlatformError::Unsupported("monitors")) + } + + #[cfg(windows)] + fn apply_geometry(&mut self, window: WindowId, geometry: Rect) -> Result<()> { + use windows::Win32::UI::WindowsAndMessaging::{SetWindowPos, SWP_NOZORDER}; + if let Some(&hwnd) = self.windows.get(&window) { + unsafe { + let _ = SetWindowPos( + hwnd, + None, + geometry.x, + geometry.y, + geometry.width as i32, + geometry.height as i32, + SWP_NOZORDER, + ); + } + } + Ok(()) + } + #[cfg(not(windows))] + fn apply_geometry(&mut self, _window: WindowId, _geometry: Rect) -> Result<()> { + Err(PlatformError::Unsupported("apply_geometry")) + } + + #[cfg(windows)] + fn set_title(&mut self, window: WindowId, title: &str) -> Result<()> { + use windows::core::HSTRING; + use windows::Win32::UI::WindowsAndMessaging::SetWindowTextW; + if let Some(&hwnd) = self.windows.get(&window) { + unsafe { + let _ = SetWindowTextW(hwnd, &HSTRING::from(title)); + } + } + Ok(()) + } + #[cfg(not(windows))] + fn set_title(&mut self, _window: WindowId, _title: &str) -> Result<()> { + Err(PlatformError::Unsupported("set_title")) + } + + #[cfg(windows)] + fn focus(&mut self, window: WindowId) -> Result<()> { + use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus; + use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow; + if let Some(&hwnd) = self.windows.get(&window) { + unsafe { + let _ = SetForegroundWindow(hwnd); + let _ = SetFocus(Some(hwnd)); + } + } + Ok(()) + } + #[cfg(not(windows))] + fn focus(&mut self, _window: WindowId) -> Result<()> { + Err(PlatformError::Unsupported("focus")) + } + + #[cfg(windows)] + fn minimize(&mut self, window: WindowId) -> Result<()> { + use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_MINIMIZE}; + if let Some(&hwnd) = self.windows.get(&window) { + unsafe { + let _ = ShowWindow(hwnd, SW_MINIMIZE); + } + } + Ok(()) + } + #[cfg(not(windows))] + fn minimize(&mut self, _window: WindowId) -> Result<()> { + Err(PlatformError::Unsupported("minimize")) + } + + #[cfg(windows)] + fn restore(&mut self, window: WindowId) -> Result<()> { + use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_RESTORE}; + if let Some(&hwnd) = self.windows.get(&window) { + unsafe { + let _ = ShowWindow(hwnd, SW_RESTORE); + } + } + Ok(()) + } + #[cfg(not(windows))] + fn restore(&mut self, _window: WindowId) -> Result<()> { + Err(PlatformError::Unsupported("restore")) + } + + #[cfg(windows)] + fn close(&mut self, window: WindowId) -> Result<()> { + use windows::Win32::UI::WindowsAndMessaging::{PostMessageW, WM_CLOSE}; + if let Some(&hwnd) = self.windows.get(&window) { + unsafe { + let _ = PostMessageW(Some(hwnd), WM_CLOSE, windows::Win32::Foundation::WPARAM(0), windows::Win32::Foundation::LPARAM(0)); + } + } + Ok(()) + } + #[cfg(not(windows))] + fn close(&mut self, _window: WindowId) -> Result<()> { + Err(PlatformError::Unsupported("close")) + } + + fn set_decorated(&mut self, _window: WindowId, _decorated: bool) -> Result<()> { + // TODO: toggle WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_MAXIMIZEBOX via + // GetWindowLong/SetWindowLong + SWP_FRAMECHANGED, as the legacy + // backend did. + Err(PlatformError::Unsupported("set_decorated")) + } + + #[cfg(windows)] + fn set_border_color(&mut self, window: WindowId, rgb: (u8, u8, u8)) -> Result<()> { + use windows::Win32::Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_BORDER_COLOR}; + if let Some(&hwnd) = self.windows.get(&window) { + let colorref = (rgb.0 as u32) | ((rgb.1 as u32) << 8) | ((rgb.2 as u32) << 16); + unsafe { + let _ = DwmSetWindowAttribute( + hwnd, + DWMWA_BORDER_COLOR, + &colorref as *const _ as *const std::ffi::c_void, + std::mem::size_of::<u32>() as u32, + ); + } + } + Ok(()) + } + #[cfg(not(windows))] + fn set_border_color(&mut self, _window: WindowId, _rgb: (u8, u8, u8)) -> Result<()> { + Err(PlatformError::Unsupported("set_border_color")) + } + + fn set_border_width(&mut self, _window: WindowId, _width: u32) -> Result<()> { + // DWM does not expose a border-width knob without replacing the + // native frame entirely (see module docs). + Err(PlatformError::Unsupported("set_border_width (DWM has no custom-width API)")) + } + + fn redraw_decoration(&mut self, _window: WindowId, _win: &Window, _focused: bool) -> Result<()> { + // Native DWM frame is used for now; see module docs for the + // borderless-window plan to get X11/Wayland decoration parity. + Ok(()) + } + + fn grab_keyboard(&mut self) -> Result<()> { + // TODO: SetWindowsHookExW(WH_KEYBOARD_LL, ...) as the legacy backend did. + Err(PlatformError::Unsupported("grab_keyboard")) + } + + fn ungrab_keyboard(&mut self) -> Result<()> { + Err(PlatformError::Unsupported("ungrab_keyboard")) + } +} |