srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/platform/src/lib.rs
blob: 4be9cc47a541968997ca8addcef1e399f3291c7d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Platform abstraction: the trait every backend (X11, Wayland, Windows,
//! macOS) implements, plus auto-detection of which backend to use.
//!
//! This mirrors the intent of the legacy C++ `Platform` interface
//! (see docs/PRIOR_ART.md) with one deliberate change: `poll_events` is the
//! only place backends need to bridge their native event model (X11's
//! blocking `XNextEvent`, Wayland's callback-driven dispatch, Win32's message
//! pump, macOS's event taps) into the common [`srdwm_core::Event`] queue -
//! everything downstream of that is platform-independent.

mod ipc;
pub use ipc::IpcServer;

use srdwm_core::{Monitor, Rect, Window, WindowId};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlatformKind {
    X11,
    Wayland,
    Windows,
    MacOS,
}

impl PlatformKind {
    pub fn name(self) -> &'static str {
        match self {
            PlatformKind::X11 => "x11",
            PlatformKind::Wayland => "wayland",
            PlatformKind::Windows => "windows",
            PlatformKind::MacOS => "macos",
        }
    }
}

/// Chooses a backend the way the legacy `PlatformFactory` did: prefer
/// Wayland when a compositor is reachable, fall back to X11, otherwise use
/// the compile-time native backend on Windows/macOS.
///
/// X11 is only chosen when there's actual evidence of a running X server
/// (`DISPLAY` set) and no Wayland evidence - `srdwm_x11::X11Platform`
/// only ever *connects to* an existing server (Xephyr, or the real system
/// Xorg started separately), it never spawns one itself. Every other case,
/// including a bare TTY with neither env var set, resolves to Wayland:
/// `srdwm_wayland::connect` is the only backend that can run standalone
/// there, via its udev/DRM backend.
pub fn detect() -> PlatformKind {
    #[cfg(target_os = "windows")]
    {
        return PlatformKind::Windows;
    }
    #[cfg(target_os = "macos")]
    {
        return PlatformKind::MacOS;
    }
    #[cfg(all(unix, not(target_os = "macos")))]
    {
        detect_unix(
            std::env::var_os("WAYLAND_DISPLAY").is_some(),
            std::env::var("XDG_SESSION_TYPE").map(|v| v == "wayland").unwrap_or(false),
            std::env::var_os("DISPLAY").is_some(),
        )
    }
}

/// The env-var decision logic behind [`detect`]'s unix branch, pulled out
/// as a pure function so it's testable without mutating real process env
/// vars (which would be racy across parallel test threads).
#[cfg(all(unix, not(target_os = "macos")))]
fn detect_unix(wayland_display: bool, xdg_session_type_wayland: bool, display: bool) -> PlatformKind {
    let wayland_evidence = wayland_display || xdg_session_type_wayland;
    if display && !wayland_evidence {
        PlatformKind::X11
    } else {
        PlatformKind::Wayland
    }
}

#[cfg(all(unix, not(target_os = "macos")))]
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn display_set_alone_picks_x11() {
        assert_eq!(detect_unix(false, false, true), PlatformKind::X11);
    }

    #[test]
    fn bare_tty_with_nothing_set_picks_wayland() {
        assert_eq!(detect_unix(false, false, false), PlatformKind::Wayland);
    }

    #[test]
    fn wayland_display_set_picks_wayland_even_if_display_also_set() {
        // XWayland-style setups often have both DISPLAY and WAYLAND_DISPLAY
        // set; Wayland should win.
        assert_eq!(detect_unix(true, false, true), PlatformKind::Wayland);
    }

    #[test]
    fn xdg_session_type_wayland_picks_wayland() {
        assert_eq!(detect_unix(false, true, false), PlatformKind::Wayland);
    }
}

#[derive(thiserror::Error, Debug)]
pub enum PlatformError {
    #[error("failed to connect to display server: {0}")]
    ConnectionFailed(String),
    #[error("another window manager is already running")]
    AnotherWmRunning,
    #[error("unsupported operation on this platform: {0}")]
    Unsupported(&'static str),
    #[error("{0}")]
    Other(String),
}

pub type Result<T> = std::result::Result<T, PlatformError>;

/// A platform backend: owns the connection to the display server and
/// translates between real surfaces/windows and srdwm-core's `Window` state.
pub trait Platform {
    fn kind(&self) -> PlatformKind;

    /// Blocks until at least one event is available (or a short timeout
    /// elapses), then drains everything currently pending.
    fn poll_events(&mut self) -> Result<Vec<srdwm_core::Event>>;

    fn monitors(&mut self) -> Result<Vec<Monitor>>;

    fn apply_geometry(&mut self, window: WindowId, geometry: Rect) -> Result<()>;
    fn set_title(&mut self, window: WindowId, title: &str) -> Result<()>;
    fn focus(&mut self, window: WindowId) -> Result<()>;
    fn minimize(&mut self, window: WindowId) -> Result<()>;
    fn restore(&mut self, window: WindowId) -> Result<()>;
    fn close(&mut self, window: WindowId) -> Result<()>;

    fn set_decorated(&mut self, window: WindowId, decorated: bool) -> Result<()>;
    fn set_border_color(&mut self, window: WindowId, rgb: (u8, u8, u8)) -> Result<()>;
    fn set_border_width(&mut self, window: WindowId, width: u32) -> Result<()>;

    /// Redraws the decoration (titlebar + buttons) for `window`, e.g. after
    /// a focus change or resize. No-op on platforms with native
    /// decorations (Windows/macOS).
    fn redraw_decoration(&mut self, window: WindowId, win: &Window, focused: bool) -> Result<()>;

    fn grab_keyboard(&mut self) -> Result<()>;
    fn ungrab_keyboard(&mut self) -> Result<()>;
}