copypasta_ext/osc52.rs
1//! OSC 52 escape sequence to set clipboard contents.
2//!
3//! This provider can set clipboard contents by outputting a sequence to stdout in supported
4//! terminals. It uses Xterm escape sequences, OSC 52 to be exact.
5//!
6//! Getting clipboard contents is not supported through this context and will error.
7//!
8//! ## Benefits
9//!
10//! - Keeps contents in clipboard for the terminal lifetime even after your application exists.
11//!
12//! ## Drawbacks
13//!
14//! - Requires terminal that supports these escape codes.
15//! - Doesn't catch errors while setting clipboard contents.
16//! - Cannot get clipboard contents.
17//!
18//! # Examples
19//!
20//! ```rust,no_run
21//! use copypasta_ext::prelude::*;
22//! use copypasta_ext::x11_bin::X11BinClipboardContext;
23//!
24//! let mut ctx = X11BinClipboardContext::new().unwrap();
25//! ctx.set_contents("some string".into()).unwrap();
26//! ```
27//!
28//! Use `new_with` to combine with another context such as [`X11ClipboardContext`][X11ClipboardContext] to support getting clipboard contents as well.
29//!
30//! ```rust,no_run
31//! use copypasta_ext::prelude::*;
32//! use copypasta_ext::osc52::Osc52ClipboardContext;
33//! use copypasta_ext::x11_bin::X11BinClipboardContext;
34//!
35//! let mut ctx = Osc52ClipboardContext::new_with(X11BinClipboardContext::new().unwrap()).unwrap();
36//! println!("{:?}", ctx.get_contents());
37//! ctx.set_contents("some string".into()).unwrap();
38//! ```
39//!
40//! [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html
41
42use std::error::Error as StdError;
43use std::fmt;
44
45use base64::engine::Engine;
46
47use crate::combined::CombinedClipboardContext;
48use crate::display::DisplayServer;
49use crate::prelude::*;
50
51/// Platform specific context.
52///
53/// Alias for `Osc52ClipboardContext` on supported platforms, aliases to standard
54/// `ClipboardContext` provided by `rust-clipboard` on other platforms.
55pub type ClipboardContext = Osc52ClipboardContext;
56
57/// OSC 52 escape sequence to set clipboard contents.
58///
59/// See module documentation for more information.
60pub struct Osc52ClipboardContext;
61
62impl Osc52ClipboardContext {
63 pub fn new() -> Result<Self, Box<dyn StdError>> {
64 Ok(Self)
65 }
66
67 /// Construct combined with another context for getting the clipboard.
68 ///
69 /// This clipboard context only supports setting the clipboard contents.
70 /// You can combine this with the given context to support getting clipboard contents as well
71 /// to get the best of both worlds.
72 pub fn new_with<G>(get: G) -> Result<CombinedClipboardContext<G, Self>, Box<dyn StdError>>
73 where
74 G: ClipboardProvider,
75 {
76 Self::new()?.with(get)
77 }
78
79 /// Combine this context with [`X11ClipboardContext`][X11ClipboardContext].
80 ///
81 /// This clipboard context only supports setting the clipboard contents.
82 /// You can combine this with the given context to support getting clipboard contents as well
83 /// to get the best of both worlds.
84 pub fn with<G>(self, get: G) -> Result<CombinedClipboardContext<G, Self>, Box<dyn StdError>>
85 where
86 G: ClipboardProvider,
87 {
88 Ok(CombinedClipboardContext(get, self))
89 }
90}
91
92impl ClipboardProvider for Osc52ClipboardContext {
93 fn get_contents(&mut self) -> crate::ClipResult<String> {
94 Err(Error::Unsupported.into())
95 }
96
97 fn set_contents(&mut self, contents: String) -> crate::ClipResult<()> {
98 // Use OSC 52 escape sequence to set clipboard through stdout
99 print!(
100 "\x1B]52;c;{}\x07",
101 base64::engine::general_purpose::STANDARD.encode(&contents)
102 );
103 Ok(())
104 }
105}
106
107impl ClipboardProviderExt for Osc52ClipboardContext {
108 fn display_server(&self) -> Option<DisplayServer> {
109 Some(DisplayServer::Tty)
110 }
111
112 fn has_bin_lifetime(&self) -> bool {
113 false
114 }
115}
116
117/// Represents OSC 52 clipboard related error.
118#[derive(Debug)]
119#[non_exhaustive]
120pub enum Error {
121 /// Getting clipboard contents is not supported.
122 Unsupported,
123}
124
125impl fmt::Display for Error {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 match self {
128 Error::Unsupported => write!(
129 f,
130 "Getting clipboard contents is not supported through this context"
131 ),
132 }
133 }
134}
135
136impl StdError for Error {
137 fn source(&self) -> Option<&(dyn StdError + 'static)> {
138 None
139 }
140}