1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 13:52:55 +03:00

Fix latency issue with clipboard/selection handling

We now use mio to select on both the xcb connection and the
channel that we're using to bridge the threads.
This commit is contained in:
Wez Furlong 2018-02-27 23:56:20 -08:00
parent aad282cb81
commit 60e89f6328
6 changed files with 68 additions and 41 deletions

View File

@ -14,6 +14,7 @@ glium = "0.20.0"
harfbuzz-sys = "0.1.15" harfbuzz-sys = "0.1.15"
libc = "0.2.36" libc = "0.2.36"
mio = "0.6.12" mio = "0.6.12"
mio-extras = "2.0.3"
palette = "0.2.1" palette = "0.2.1"
serde = "1.0.27" serde = "1.0.27"
serde_derive = "1.0.27" serde_derive = "1.0.27"

View File

@ -1,5 +1,4 @@
use failure::Error; use failure::Error;
use std::sync::mpsc::Receiver;
mod none; mod none;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -33,5 +32,5 @@ pub trait ClipboardImpl {
Self: Sized; Self: Sized;
fn set_clipboard(&self, text: Option<String>) -> Result<(), Error>; fn set_clipboard(&self, text: Option<String>) -> Result<(), Error>;
fn get_clipboard(&self) -> Result<String, Error>; fn get_clipboard(&self) -> Result<String, Error>;
fn receiver(&self) -> &Receiver<Paste>; fn try_get_paste(&self) -> Result<Option<Paste>, Error>;
} }

View File

@ -23,7 +23,8 @@ impl ClipboardImpl for NoClipboard {
fn get_clipboard(&self) -> Result<String, Error> { fn get_clipboard(&self) -> Result<String, Error> {
Ok("".into()) Ok("".into())
} }
fn receiver(&self) -> &Receiver<Paste> {
&self.receiver fn try_get_paste(&self) -> Result<Option<Paste>, Error> {
Ok(None)
} }
} }

View File

@ -2,7 +2,11 @@
//! Check out https://tronche.com/gui/x/icccm/sec-2.html for some deep and complex //! Check out https://tronche.com/gui/x/icccm/sec-2.html for some deep and complex
//! background on what's happening in here. //! background on what's happening in here.
use failure::{self, Error}; use failure::{self, Error};
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender, TryRecvError}; use mio::{Events, Poll, PollOpt, Ready, Token};
use mio::unix::EventedFd;
use mio_extras::channel::{channel as mio_channel, Receiver as MioReceiver, Sender as MioSender};
use std::os::unix::io::AsRawFd;
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
use std::thread::{self, JoinHandle}; use std::thread::{self, JoinHandle};
use std::time::Duration; use std::time::Duration;
use wakeup::{Wakeup, WakeupMsg}; use wakeup::{Wakeup, WakeupMsg};
@ -25,7 +29,7 @@ enum ClipRequest {
struct Inner { struct Inner {
/// if we own the clipboard, here's its string content /// if we own the clipboard, here's its string content
owned: Option<String>, owned: Option<String>,
receiver: Receiver<ClipRequest>, receiver: MioReceiver<ClipRequest>,
sender: Sender<Paste>, sender: Sender<Paste>,
conn: xcb::Connection, conn: xcb::Connection,
window_id: xcb::xproto::Window, window_id: xcb::xproto::Window,
@ -38,7 +42,7 @@ struct Inner {
impl Inner { impl Inner {
fn new( fn new(
receiver: Receiver<ClipRequest>, receiver: MioReceiver<ClipRequest>,
sender: Sender<Paste>, sender: Sender<Paste>,
wakeup: Wakeup, wakeup: Wakeup,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
@ -289,17 +293,10 @@ impl Inner {
} }
fn process_receiver(&mut self) -> Result<(), Error> { fn process_receiver(&mut self) -> Result<(), Error> {
match self.receiver.recv_timeout(Duration::from_millis(100)) { match self.receiver.try_recv() {
Err(RecvTimeoutError::Timeout) => return Ok(()), Err(TryRecvError::Empty) => Ok(()),
Err(err) => bail!("clipboard: Error while reading channel: {:?}", err), Err(err) => bail!("clipboard: Error while reading channel: {:?}", err),
Ok(request) => self.process_one_cliprequest(request)?, Ok(request) => self.process_one_cliprequest(request),
}
loop {
match self.receiver.try_recv() {
Err(TryRecvError::Empty) => return Ok(()),
Err(TryRecvError::Disconnected) => bail!("clipboard: receiver channel broken"),
Ok(request) => self.process_one_cliprequest(request)?,
}
} }
} }
@ -328,34 +325,63 @@ impl Inner {
/// Waits for events from either the X server or the main thread of the /// Waits for events from either the X server or the main thread of the
/// application. /// application.
fn clip_thread(&mut self) { fn clip_thread(&mut self) {
let poll = Poll::new().expect("mio Poll failed to init");
poll.register(
&EventedFd(&self.conn.as_raw_fd()),
Token(0),
Ready::readable(),
PollOpt::level(),
).expect("failed to register xcb conn for clipboard with mio");
poll.register(
&self.receiver,
Token(1),
Ready::readable(),
PollOpt::level(),
).expect("failed to register receiver for clipboard with mio");
let mut events = Events::with_capacity(2);
self.sender self.sender
.send(Paste::Running) .send(Paste::Running)
.expect("failed to send Running notice"); .expect("failed to send Running notice");
loop { loop {
match self.process_queued_xcb() { match poll.poll(&mut events, None) {
Ok(_) => for event in &events {
if event.token() == Token(0) {
match self.process_queued_xcb() {
Err(err) => {
eprintln!("clipboard: {:?}", err);
return;
}
_ => (),
}
}
if event.token() == Token(1) {
match self.process_receiver() {
Err(err) => {
eprintln!("clipboard: {:?}", err);
return;
}
_ => (),
}
self.conn.flush();
}
},
Err(err) => { Err(err) => {
eprintln!("clipboard: {:?}", err); eprintln!("clipboard: {:?}", err);
return; return;
} }
_ => (),
} }
match self.process_receiver() {
Err(err) => {
eprintln!("clipboard: {:?}", err);
return;
}
_ => (),
}
self.conn.flush();
} }
} }
} }
/// A clipboard client allows getting or setting the clipboard. /// A clipboard client allows getting or setting the clipboard.
pub struct Clipboard { pub struct Clipboard {
sender: Sender<ClipRequest>, sender: MioSender<ClipRequest>,
receiver: Receiver<Paste>, receiver: Receiver<Paste>,
/// This isn't really dead; we're keeping it alive until we Drop. /// This isn't really dead; we're keeping it alive until we Drop.
#[allow(dead_code)] #[allow(dead_code)]
@ -365,7 +391,7 @@ pub struct Clipboard {
impl ClipboardImpl for Clipboard { impl ClipboardImpl for Clipboard {
/// Create a new clipboard instance. `ping` is /// Create a new clipboard instance. `ping` is
fn new(wakeup: Wakeup) -> Result<Self, Error> { fn new(wakeup: Wakeup) -> Result<Self, Error> {
let (sender_clip, receiver_clip) = channel(); let (sender_clip, receiver_clip) = mio_channel();
let (sender_paste, receiver_paste) = channel(); let (sender_paste, receiver_paste) = channel();
let clip_thread = thread::spawn(move || { let clip_thread = thread::spawn(move || {
match Inner::new(receiver_clip, sender_paste, wakeup) { match Inner::new(receiver_clip, sender_paste, wakeup) {
@ -396,15 +422,19 @@ impl ClipboardImpl for Clipboard {
/// Blocks until the clipboard contents have been retrieved /// Blocks until the clipboard contents have been retrieved
fn get_clipboard(&self) -> Result<String, Error> { fn get_clipboard(&self) -> Result<String, Error> {
self.sender.send(ClipRequest::RequestClipboard)?; self.sender.send(ClipRequest::RequestClipboard)?;
match self.receiver().recv_timeout(Duration::from_secs(10)) { match self.receiver.recv_timeout(Duration::from_secs(10)) {
Ok(Paste::All(result)) => return Ok(result), Ok(Paste::All(result)) => return Ok(result),
Ok(Paste::Cleared) => return Ok("".into()), Ok(Paste::Cleared) => return Ok("".into()),
other @ _ => bail!("unexpected result while waiting for paste: {:?}", other), other @ _ => bail!("unexpected result while waiting for paste: {:?}", other),
} }
} }
fn receiver(&self) -> &Receiver<Paste> { fn try_get_paste(&self) -> Result<Option<Paste>, Error> {
&self.receiver match self.receiver.try_recv() {
Err(TryRecvError::Empty) => Ok(None),
Err(err) => bail!("{:?}", err),
Ok(paste) => Ok(Some(paste)),
}
} }
} }

View File

@ -15,7 +15,6 @@ use std::process::Child;
use std::process::Command; use std::process::Command;
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::sync::mpsc::TryRecvError;
use term::{self, Terminal}; use term::{self, Terminal};
use term::{MouseButton, MouseEventKind}; use term::{MouseButton, MouseEventKind};
use term::KeyCode; use term::KeyCode;
@ -578,16 +577,12 @@ impl TerminalWindow {
} }
fn process_clipboard(&mut self) -> Result<(), Error> { fn process_clipboard(&mut self) -> Result<(), Error> {
match self.host.clipboard.receiver().try_recv() { match self.host.clipboard.try_get_paste() {
Ok(Paste::Cleared) => { Ok(Some(Paste::Cleared)) => {
self.terminal.clear_selection(); self.terminal.clear_selection();
} }
Ok(_) => {} Ok(_) => {}
Err(TryRecvError::Empty) => { Err(err) => bail!("clipboard thread died? {:?}", err),
// Spurious wakeup. Most likely because Clipboard::get_clipboard
// already consumed the Paste::Cleared event.
}
Err(TryRecvError::Disconnected) => bail!("clipboard thread died"),
} }
self.paint_if_needed()?; self.paint_if_needed()?;
Ok(()) Ok(())

View File

@ -16,6 +16,7 @@ extern crate glium;
extern crate harfbuzz_sys; extern crate harfbuzz_sys;
extern crate libc; extern crate libc;
extern crate mio; extern crate mio;
extern crate mio_extras;
extern crate palette; extern crate palette;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]