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}