1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 21:32:13 +03:00

allow capturing the clipboard impl from the terminal host

This should allow asynchronous access to the clipboard, which
in turn will allow the server to send the clipboard to the client
unilaterally.
This commit is contained in:
Wez Furlong 2019-06-20 07:55:03 -07:00
parent 0b9c953446
commit e993e5a625
7 changed files with 101 additions and 68 deletions

View File

@ -0,0 +1,58 @@
use clipboard::{ClipboardContext, ClipboardProvider};
use failure::{format_err, Fallible};
use std::sync::Mutex;
use term::terminal::Clipboard;
pub struct SystemClipboard {
inner: Mutex<Inner>,
}
struct Inner {
/// macOS gets unhappy if we set up the clipboard too early,
/// so we use an Option to defer it until we use it
clipboard: Option<ClipboardContext>,
}
impl Inner {
fn new() -> Self {
Self { clipboard: None }
}
fn clipboard(&mut self) -> Fallible<&mut ClipboardContext> {
if self.clipboard.is_none() {
self.clipboard = Some(ClipboardContext::new().map_err(|e| format_err!("{}", e))?);
}
Ok(self.clipboard.as_mut().unwrap())
}
}
impl SystemClipboard {
pub fn new() -> Self {
Self {
inner: Mutex::new(Inner::new()),
}
}
}
impl Clipboard for SystemClipboard {
fn get_contents(&self) -> Fallible<String> {
let mut inner = self.inner.lock().unwrap();
inner
.clipboard()?
.get_contents()
.map_err(|e| format_err!("{}", e))
}
fn set_contents(&self, data: Option<String>) -> Fallible<()> {
let mut inner = self.inner.lock().unwrap();
let clip = inner.clipboard()?;
clip.set_contents(data.unwrap_or_else(|| "".into()))
.map_err(|e| format_err!("{}", e))?;
// Request the clipboard contents we just set; on some systems
// if we copy and paste in wezterm, the clipboard isn't visible
// to us again until the second call to get_clipboard.
clip.get_contents()
.map(|_| ())
.map_err(|e| format_err!("{}", e))
}
}

View File

@ -1,12 +1,12 @@
use super::window::TerminalWindow;
use crate::font::{FontConfiguration, FontSystemSelection};
use crate::frontend::guicommon::clipboard::SystemClipboard;
use crate::frontend::guicommon::window::SpawnTabDomain;
use crate::frontend::{front_end, gui_executor};
use crate::mux::tab::{Tab, TabId};
use crate::mux::Mux;
use clipboard::{ClipboardContext, ClipboardProvider};
use failure::Error;
use failure::Fallible;
use failure::{format_err, Error};
use log::error;
use portable_pty::PtySize;
use promise::Future;
@ -14,6 +14,7 @@ use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use term::terminal::Clipboard;
use term::{KeyCode, KeyModifiers};
use termwiz::hyperlink::Hyperlink;
@ -49,9 +50,7 @@ pub trait HostHelper {
pub struct HostImpl<H: HostHelper> {
helper: H,
/// macOS gets unhappy if we set up the clipboard too early,
/// so we use an Option to defer it until we use it
clipboard: Option<ClipboardContext>,
clipboard: Arc<Clipboard>,
keys: KeyMap,
}
@ -174,32 +173,13 @@ impl<H: HostHelper> HostImpl<H> {
pub fn new(helper: H) -> Self {
Self {
helper,
clipboard: None,
clipboard: Arc::new(SystemClipboard::new()),
keys: key_bindings(),
}
}
fn clipboard(&mut self) -> Result<&mut ClipboardContext, Error> {
if self.clipboard.is_none() {
self.clipboard = Some(ClipboardContext::new().map_err(|e| format_err!("{}", e))?);
}
Ok(self.clipboard.as_mut().unwrap())
}
pub fn get_clipboard(&mut self) -> Result<String, Error> {
self.clipboard()?
.get_contents()
.map_err(|e| format_err!("{}", e))
}
pub fn set_clipboard(&mut self, clip: Option<String>) -> Result<(), Error> {
self.clipboard()?
.set_contents(clip.unwrap_or_else(|| "".into()))
.map_err(|e| format_err!("{}", e))?;
// Request the clipboard contents we just set; on some systems
// if we copy and paste in wezterm, the clipboard isn't visible
// to us again until the second call to get_clipboard.
self.get_clipboard().map(|_| ())
pub fn get_clipboard(&mut self) -> Fallible<Arc<Clipboard>> {
Ok(Arc::clone(&self.clipboard))
}
pub fn spawn_new_window(&mut self) {
@ -238,7 +218,7 @@ impl<H: HostHelper> HostImpl<H> {
// Nominally copy, but that is implicit, so NOP
}
Paste => {
let text = self.get_clipboard()?;
let text = self.get_clipboard()?.get_contents()?;
if text.len() <= PASTE_CHUNK_SIZE {
// Send it all now
tab.send_paste(&text)?;
@ -375,14 +355,10 @@ impl<'a, H: HostHelper> term::TerminalHost for TabHost<'a, H> {
}
}
fn get_clipboard(&mut self) -> Result<String, Error> {
fn get_clipboard(&mut self) -> Fallible<Arc<Clipboard>> {
self.host.get_clipboard()
}
fn set_clipboard(&mut self, clip: Option<String>) -> Result<(), Error> {
self.host.set_clipboard(clip)
}
fn set_title(&mut self, _title: &str) {
self.host.with_window(move |win| {
win.update_title();

View File

@ -1,3 +1,4 @@
pub mod clipboard;
pub mod host;
pub mod localtab;
pub mod window;

View File

@ -4,9 +4,9 @@ use crate::mux::tab::{Tab, TabId};
use crate::mux::window::{Window, WindowId};
use crate::server::pollable::{pollable_channel, PollableReceiver, PollableSender};
use domain::{Domain, DomainId};
use failure::{format_err, Error, Fallible};
use failure::{bail, format_err, Error, Fallible};
use failure_derive::*;
use log::{debug, error, warn};
use log::{debug, error};
use portable_pty::ExitStatus;
use promise::{Executor, Future};
use std::cell::{Ref, RefCell, RefMut};
@ -16,6 +16,7 @@ use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use term::terminal::Clipboard;
use term::TerminalHost;
use termwiz::hyperlink::Hyperlink;
@ -101,13 +102,8 @@ impl<'a> TerminalHost for Host<'a> {
}
}
fn get_clipboard(&mut self) -> Result<String, Error> {
warn!("peer requested clipboard; ignoring");
Ok("".into())
}
fn set_clipboard(&mut self, _clip: Option<String>) -> Result<(), Error> {
Ok(())
fn get_clipboard(&mut self) -> Fallible<Arc<Clipboard>> {
bail!("peer requested clipboard; ignoring");
}
fn set_title(&mut self, _title: &str) {}

View File

@ -8,7 +8,7 @@ use crossbeam_channel::TryRecvError;
use failure::{bail, err_msg, format_err, Error, Fallible};
#[cfg(unix)]
use libc::{mode_t, umask};
use log::{debug, error, warn};
use log::{debug, error};
use native_tls::{Identity, TlsAcceptor};
use promise::{Executor, Future};
use std::collections::HashMap;
@ -23,6 +23,7 @@ use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Instant;
use term::terminal::Clipboard;
use termwiz::surface::{Change, Position, SequenceNo, Surface};
struct LocalListener {
@ -312,16 +313,8 @@ impl<'a> term::TerminalHost for BufferedTerminalHost<'a> {
error!("ignoring url open of {:?}", link.uri());
}
fn get_clipboard(&mut self) -> Result<String, Error> {
warn!("peer requested clipboard; ignoring");
Ok("".into())
}
fn set_clipboard(&mut self, clip: Option<String>) -> Result<(), Error> {
if let Some(clip) = clip {
self.clipboard.replace(clip);
}
Ok(())
fn get_clipboard(&mut self) -> Fallible<Arc<Clipboard>> {
bail!("peer requested clipboard; ignoring");
}
fn set_title(&mut self, title: &str) {

View File

@ -1,8 +1,14 @@
use super::*;
use failure::Fallible;
use std::sync::Arc;
use termwiz::escape::parser::Parser;
use termwiz::hyperlink::Rule as HyperlinkRule;
pub trait Clipboard {
fn get_contents(&self) -> Fallible<String>;
fn set_contents(&self, data: Option<String>) -> Fallible<()>;
}
/// Represents the host of the terminal.
/// Provides a means for sending data to the connected pty,
/// and for operating on the clipboard
@ -11,11 +17,8 @@ pub trait TerminalHost {
/// slave end of the associated pty.
fn writer(&mut self) -> &mut std::io::Write;
/// Returns the current clipboard contents
fn get_clipboard(&mut self) -> Result<String, Error>;
/// Adjust the contents of the clipboard
fn set_clipboard(&mut self, clip: Option<String>) -> Result<(), Error>;
/// Returns the clipboard manager
fn get_clipboard(&mut self) -> Fallible<Arc<Clipboard>>;
/// Change the title of the window
fn set_title(&mut self, title: &str);

View File

@ -429,7 +429,7 @@ impl TerminalState {
y: event.y as ScrollbackOrVisibleRowIndex
- self.viewport_offset as ScrollbackOrVisibleRowIndex,
});
host.set_clipboard(None)
host.get_clipboard()?.set_contents(None)
}
/// Double click to select a word on the current line
@ -506,7 +506,7 @@ impl TerminalState {
"finish 2click selection {:?} '{}'",
self.selection_range, text
);
host.set_clipboard(Some(text))
host.get_clipboard()?.set_contents(Some(text))
}
/// triple click to select the current line
@ -531,7 +531,7 @@ impl TerminalState {
"finish 3click selection {:?} '{}'",
self.selection_range, text
);
host.set_clipboard(Some(text))
host.get_clipboard()?.set_contents(Some(text))
}
fn mouse_press_left(
@ -555,7 +555,7 @@ impl TerminalState {
_ => {
self.selection_range = None;
self.selection_start = None;
host.set_clipboard(None)?;
host.get_clipboard()?.set_contents(None)?;
}
}
@ -578,7 +578,7 @@ impl TerminalState {
"finish drag selection {:?} '{}'",
self.selection_range, text
);
host.set_clipboard(Some(text))?;
host.get_clipboard()?.set_contents(Some(text))?;
} else if let Some(link) = self.current_highlight() {
// If the button release wasn't a drag, consider
// whether it was a click on a hyperlink
@ -650,7 +650,7 @@ impl TerminalState {
format!("\x1b[<{};{};{}M", button, event.x + 1, event.y + 1).as_bytes(),
)?;
} else if event.button == MouseButton::Middle {
let clip = host.get_clipboard()?;
let clip = host.get_clipboard()?.get_contents()?;
self.send_paste(&clip, host.writer())?
}
}
@ -2077,13 +2077,19 @@ impl<'a> Performer<'a> {
}
OperatingSystemCommand::ClearSelection(_) => {
self.host.set_clipboard(None).ok();
if let Ok(clip) = self.host.get_clipboard() {
clip.set_contents(None).ok();
}
}
OperatingSystemCommand::QuerySelection(_) => {}
OperatingSystemCommand::SetSelection(_, selection_data) => {
match self.host.set_clipboard(Some(selection_data)) {
Ok(_) => (),
Err(err) => error!("failed to set clipboard in response to OSC 52: {:?}", err),
if let Ok(clip) = self.host.get_clipboard() {
match clip.set_contents(Some(selection_data)) {
Ok(_) => (),
Err(err) => {
error!("failed to set clipboard in response to OSC 52: {:?}", err)
}
}
}
}
OperatingSystemCommand::ITermProprietary(iterm) => match iterm {