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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Display server management.
//!
//! Provides functionality to select used display server based on the runtime environment.

use std::env;

use crate::prelude::ClipboardProviderExt;

/// A display server type.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum DisplayServer {
    /// The X11 display server.
    X11,

    /// The Wayland display server.
    Wayland,

    /// The default macOS display server.
    MacOs,

    /// The default Windows display server.
    Windows,

    /// For TTYs.
    /// Not an actual display server, but something with a clipboard context to fall back to.
    Tty,
}

impl DisplayServer {
    /// Select current used display server.
    ///
    /// This selection is made at runtime. This uses a best effort approach and does not reliably
    /// select the current display server. Selects any recognized display server regardless of
    /// compiler feature flag configuration. Defaults to `X11` on Unix if display server could not
    /// be determined.
    #[allow(unreachable_code)]
    pub fn select() -> DisplayServer {
        #[cfg(target_os = "macos")]
        return DisplayServer::MacOs;
        #[cfg(windows)]
        return DisplayServer::Windows;

        // Runtime check on Unix
        if is_wayland() {
            DisplayServer::Wayland
        } else if is_x11() {
            DisplayServer::X11
        } else if is_tty() {
            DisplayServer::Tty
        } else {
            // TODO: return Option::None if this isn't X11 either.
            DisplayServer::X11
        }
    }

    /// Build clipboard context for display server.
    ///
    /// This attempts to build a clipboard context for the selected display server based on what
    /// contexts are available.
    ///
    /// If no compatible context is available or if no compatible context could be initialized,
    /// `None` is returned.
    pub fn try_context(self) -> Option<Box<dyn ClipboardProviderExt>> {
        match self {
            DisplayServer::X11 => {
                #[cfg(feature = "x11-fork")]
                {
                    let context = crate::x11_fork::ClipboardContext::new();
                    if let Ok(context) = context {
                        return Some(Box::new(context));
                    }
                }
                #[cfg(feature = "x11-bin")]
                {
                    let context = crate::x11_bin::ClipboardContext::new();
                    if let Ok(context) = context {
                        return Some(Box::new(context));
                    }
                }
                #[cfg(all(
                    unix,
                    not(any(
                        target_os = "macos",
                        target_os = "android",
                        target_os = "ios",
                        target_os = "emscripten"
                    ))
                ))]
                {
                    let context = copypasta::x11_clipboard::X11ClipboardContext::new();
                    if let Ok(context) = context {
                        return Some(Box::new(context));
                    }
                }
                None
            }
            DisplayServer::Wayland => {
                #[cfg(feature = "wayland-bin")]
                {
                    let context = crate::wayland_bin::ClipboardContext::new();
                    if let Ok(context) = context {
                        return Some(Box::new(context));
                    }
                }
                // TODO: this correct?
                copypasta::ClipboardContext::new()
                    .ok()
                    .map(|c| -> Box<dyn ClipboardProviderExt> { Box::new(c) })
            }
            DisplayServer::MacOs | DisplayServer::Windows => copypasta::ClipboardContext::new()
                .ok()
                .map(|c| -> Box<dyn ClipboardProviderExt> { Box::new(c) }),
            DisplayServer::Tty => {
                #[cfg(feature = "osc52")]
                {
                    let context = crate::osc52::ClipboardContext::new();
                    if let Ok(context) = context {
                        return Some(Box::new(context));
                    }
                }
                None
            }
        }
    }
}

/// Check whether we're in an X11 environment.
///
/// This is a best effort, may be unreliable.
/// Checks the `XDG_SESSION_TYPE` and `DISPLAY` environment variables.
/// Always returns false on unsupported platforms such as Windows/macOS.
///
/// Available regardless of the `x11-*` compiler feature flags.
pub fn is_x11() -> bool {
    if !cfg!(all(unix, not(all(target_os = "macos", target_os = "ios")))) {
        return false;
    }

    match env::var("XDG_SESSION_TYPE").ok().as_deref() {
        Some("x11") => true,
        Some("wayland") => false,
        _ => has_non_empty_env("DISPLAY"),
    }
}

/// Check whether we're in a Wayland environment.
///
/// This is a best effort, may be unreliable.
/// Checks the `XDG_SESSION_TYPE` and `WAYLAND_DISPLAY` environment variables.
/// Always returns false on Windows/macOS.
///
/// Available regardless of the `wayland-*` compiler feature flags.
pub fn is_wayland() -> bool {
    if !cfg!(all(unix, not(all(target_os = "macos", target_os = "ios")))) {
        return false;
    }

    match env::var("XDG_SESSION_TYPE").ok().as_deref() {
        Some("wayland") => true,
        Some("x11") => false,
        _ => has_non_empty_env("WAYLAND_DISPLAY"),
    }
}

/// Check whether we're in a TTY environment.
///
/// This is a basic check and only returns true if `XDG_SESSION_TYPE` is set to `tty` explicitly.
pub fn is_tty() -> bool {
    env::var("XDG_SESSION_TYPE").as_deref() == Ok("tty")
}

/// Check if an environment variable is set and is not empty.
#[inline]
fn has_non_empty_env(env: &str) -> bool {
    env::var_os(env).map(|v| !v.is_empty()).unwrap_or(false)
}