Skip to content

Commit 3c1e35e

Browse files
naseschwarzNaseschwarzextrawurst
authored
Copy text using OSC52 (#2548)
* Copy text using OSC52 if X/Wayland methods fail * Move Wayland/X string copying out of copy_string Copying logic seems too nested to comprehend with the introcution of two paths towards OSC52 otherwise. --------- Co-authored-by: Naseschwarz <[email protected]> Co-authored-by: extrawurst <[email protected]>
1 parent 381ab45 commit 3c1e35e

File tree

4 files changed

+66
-14
lines changed

4 files changed

+66
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Changed
1111
* improve syntax highlighting file detection [[@acuteenvy](https://github.com/acuteenvy)] ([#2524](https://github.com/extrawurst/gitui/pull/2524))
1212
* After commit: jump back to unstaged area [[@tommady](https://github.com/tommady)] ([#2476](https://github.com/extrawurst/gitui/issues/2476))
13+
* use OSC52 copying in case other methods fail [[@naseschwarz](https://github.com/naseschwarz)] ([#2366](https://github.com/gitui-org/gitui/issues/2366))
1314

1415
## [0.27.0] - 2024-01-14
1516

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ build = "build.rs"
1818
anyhow = "1.0"
1919
asyncgit = { path = "./asyncgit", version = "0.27.0", default-features = false }
2020
backtrace = "0.3"
21+
base64 = "0.21"
2122
bitflags = "2.8"
2223
bugreport = "0.5.1"
2324
bwrap = { version = "1.3", features = ["use_std"] }

src/clipboard.rs

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,33 +63,68 @@ fn is_wsl() -> bool {
6363
false
6464
}
6565

66+
// Copy text using escape sequence Ps = 5 2.
67+
// This enables copying even if there is no Wayland or X socket available,
68+
// e.g. via SSH, as long as it supported by the terminal.
69+
// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
70+
#[cfg(any(
71+
all(target_family = "unix", not(target_os = "macos")),
72+
test
73+
))]
74+
fn copy_string_osc52(text: &str, out: &mut impl Write) -> Result<()> {
75+
use base64::prelude::{Engine, BASE64_STANDARD};
76+
const OSC52_DESTINATION_CLIPBOARD: char = 'c';
77+
write!(
78+
out,
79+
"\x1b]52;{destination};{encoded_text}\x07",
80+
destination = OSC52_DESTINATION_CLIPBOARD,
81+
encoded_text = BASE64_STANDARD.encode(text)
82+
)?;
83+
Ok(())
84+
}
85+
6686
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
67-
pub fn copy_string(text: &str) -> Result<()> {
68-
if std::env::var("WAYLAND_DISPLAY").is_ok() {
69-
return exec_copy_with_args("wl-copy", &[], text, false);
87+
fn copy_string_wayland(text: &str) -> Result<()> {
88+
if exec_copy_with_args("wl-copy", &[], text, false).is_ok() {
89+
return Ok(());
7090
}
7191

72-
if is_wsl() {
73-
return exec_copy_with_args("clip.exe", &[], text, false);
74-
}
92+
copy_string_osc52(text, &mut std::io::stdout())
93+
}
7594

95+
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
96+
fn copy_string_x(text: &str) -> Result<()> {
7697
if exec_copy_with_args(
7798
"xclip",
7899
&["-selection", "clipboard"],
79100
text,
80101
false,
81102
)
82-
.is_err()
103+
.is_ok()
83104
{
84-
return exec_copy_with_args(
85-
"xsel",
86-
&["--clipboard"],
87-
text,
88-
true,
89-
);
105+
return Ok(());
90106
}
91107

92-
Ok(())
108+
if exec_copy_with_args("xsel", &["--clipboard"], text, true)
109+
.is_ok()
110+
{
111+
return Ok(());
112+
}
113+
114+
copy_string_osc52(text, &mut std::io::stdout())
115+
}
116+
117+
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
118+
pub fn copy_string(text: &str) -> Result<()> {
119+
if std::env::var("WAYLAND_DISPLAY").is_ok() {
120+
return copy_string_wayland(text);
121+
}
122+
123+
if is_wsl() {
124+
return exec_copy_with_args("clip.exe", &[], text, false);
125+
}
126+
127+
copy_string_x(text)
93128
}
94129

95130
#[cfg(any(target_os = "macos", windows))]
@@ -106,3 +141,17 @@ pub fn copy_string(text: &str) -> Result<()> {
106141
pub fn copy_string(text: &str) -> Result<()> {
107142
exec_copy("clip", text)
108143
}
144+
145+
#[cfg(test)]
146+
mod tests {
147+
#[test]
148+
fn test_copy_string_osc52() {
149+
let mut buffer = Vec::<u8>::new();
150+
{
151+
let mut cursor = std::io::Cursor::new(&mut buffer);
152+
super::copy_string_osc52("foo", &mut cursor).unwrap();
153+
}
154+
let output = String::from_utf8(buffer).unwrap();
155+
assert_eq!(output, "\x1b]52;c;Zm9v\x07");
156+
}
157+
}

0 commit comments

Comments
 (0)