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:
parent
aad282cb81
commit
60e89f6328
@ -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"
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
|
@ -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]
|
||||||
|
Loading…
Reference in New Issue
Block a user