copypasta_ext/
display.rs

1//! Display server management.
2//!
3//! Provides functionality to select used display server based on the runtime environment.
4
5use std::env;
6
7use crate::prelude::ClipboardProviderExt;
8
9/// A display server type.
10#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
11#[non_exhaustive]
12pub enum DisplayServer {
13    /// The X11 display server.
14    X11,
15
16    /// The Wayland display server.
17    Wayland,
18
19    /// The default macOS display server.
20    MacOs,
21
22    /// The default Windows display server.
23    Windows,
24
25    /// For TTYs.
26    /// Not an actual display server, but something with a clipboard context to fall back to.
27    Tty,
28}
29
30impl DisplayServer {
31    /// Select current used display server.
32    ///
33    /// This selection is made at runtime. This uses a best effort approach and does not reliably
34    /// select the current display server. Selects any recognized display server regardless of
35    /// compiler feature flag configuration. Defaults to `X11` on Unix if display server could not
36    /// be determined.
37    #[allow(unreachable_code)]
38    pub fn select() -> DisplayServer {
39        #[cfg(target_os = "macos")]
40        return DisplayServer::MacOs;
41        #[cfg(windows)]
42        return DisplayServer::Windows;
43
44        // Runtime check on Unix
45        if is_wayland() {
46            DisplayServer::Wayland
47        } else if is_x11() {
48            DisplayServer::X11
49        } else if is_tty() {
50            DisplayServer::Tty
51        } else {
52            // TODO: return Option::None if this isn't X11 either.
53            DisplayServer::X11
54        }
55    }
56
57    /// Build clipboard context for display server.
58    ///
59    /// This attempts to build a clipboard context for the selected display server based on what
60    /// contexts are available.
61    ///
62    /// If no compatible context is available or if no compatible context could be initialized,
63    /// `None` is returned.
64    pub fn try_context(self) -> Option<Box<dyn ClipboardProviderExt>> {
65        match self {
66            DisplayServer::X11 => {
67                #[cfg(feature = "x11-fork")]
68                {
69                    let context = crate::x11_fork::ClipboardContext::new();
70                    if let Ok(context) = context {
71                        return Some(Box::new(context));
72                    }
73                }
74                #[cfg(feature = "x11-bin")]
75                {
76                    let context = crate::x11_bin::ClipboardContext::new();
77                    if let Ok(context) = context {
78                        return Some(Box::new(context));
79                    }
80                }
81                #[cfg(all(
82                    unix,
83                    not(any(
84                        target_os = "macos",
85                        target_os = "android",
86                        target_os = "ios",
87                        target_os = "emscripten"
88                    ))
89                ))]
90                {
91                    let context = copypasta::x11_clipboard::X11ClipboardContext::new();
92                    if let Ok(context) = context {
93                        return Some(Box::new(context));
94                    }
95                }
96                None
97            }
98            DisplayServer::Wayland => {
99                #[cfg(feature = "wayland-bin")]
100                {
101                    let context = crate::wayland_bin::ClipboardContext::new();
102                    if let Ok(context) = context {
103                        return Some(Box::new(context));
104                    }
105                }
106                // TODO: this correct?
107                copypasta::ClipboardContext::new()
108                    .ok()
109                    .map(|c| -> Box<dyn ClipboardProviderExt> { Box::new(c) })
110            }
111            DisplayServer::MacOs | DisplayServer::Windows => copypasta::ClipboardContext::new()
112                .ok()
113                .map(|c| -> Box<dyn ClipboardProviderExt> { Box::new(c) }),
114            DisplayServer::Tty => {
115                #[cfg(feature = "osc52")]
116                {
117                    let context = crate::osc52::ClipboardContext::new();
118                    if let Ok(context) = context {
119                        return Some(Box::new(context));
120                    }
121                }
122                None
123            }
124        }
125    }
126}
127
128/// Check whether we're in an X11 environment.
129///
130/// This is a best effort, may be unreliable.
131/// Checks the `XDG_SESSION_TYPE` and `DISPLAY` environment variables.
132/// Always returns false on unsupported platforms such as Windows/macOS.
133///
134/// Available regardless of the `x11-*` compiler feature flags.
135pub fn is_x11() -> bool {
136    if !cfg!(all(unix, not(all(target_os = "macos", target_os = "ios")))) {
137        return false;
138    }
139
140    match env::var("XDG_SESSION_TYPE").ok().as_deref() {
141        Some("x11") => true,
142        Some("wayland") => false,
143        _ => has_non_empty_env("DISPLAY"),
144    }
145}
146
147/// Check whether we're in a Wayland environment.
148///
149/// This is a best effort, may be unreliable.
150/// Checks the `XDG_SESSION_TYPE` and `WAYLAND_DISPLAY` environment variables.
151/// Always returns false on Windows/macOS.
152///
153/// Available regardless of the `wayland-*` compiler feature flags.
154pub fn is_wayland() -> bool {
155    if !cfg!(all(unix, not(all(target_os = "macos", target_os = "ios")))) {
156        return false;
157    }
158
159    match env::var("XDG_SESSION_TYPE").ok().as_deref() {
160        Some("wayland") => true,
161        Some("x11") => false,
162        _ => has_non_empty_env("WAYLAND_DISPLAY"),
163    }
164}
165
166/// Check whether we're in a TTY environment.
167///
168/// This is a basic check and only returns true if `XDG_SESSION_TYPE` is set to `tty` explicitly.
169pub fn is_tty() -> bool {
170    env::var("XDG_SESSION_TYPE").as_deref() == Ok("tty")
171}
172
173/// Check if an environment variable is set and is not empty.
174#[inline]
175fn has_non_empty_env(env: &str) -> bool {
176    env::var_os(env).map(|v| !v.is_empty()).unwrap_or(false)
177}