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

Don't hang when using middle mouse button to paste

A while back I moved the clipboard related processing mostly out
of the term processing code, but since I mostly use the keyboard
for pasting, I'd overlooked the middle mouse button paste flow.

This is problematic because fetching the clipboard requires a
degree of inter process communication and needs the event loop
to be pumped.  Since the terminal callbacks are dispatched from
an event loop callback, attempting to block on the clipboard
future causes a deadlock.

This commit resolves that issue by anticipating that we'll need
the clipboard contents for the majority of middle mouse button
clicks and scheduling a fetch and cache ahead of passing that
event down to the terminal layer.

Why don't we simply use the same technique as the Paste key
assignment?  If the terminal is currently using SGR mouse
tracking mode then the application on the other end of the
pty will want the raw button press.   Our layering doesn't
allow for passing up the concept of whether a middle button
does paste or sends the raw event.

tricksy!

Refs: https://github.com/wez/wezterm/issues/87
This commit is contained in:
Wez Furlong 2019-12-28 09:15:24 -08:00
parent 682bb0d3fd
commit fa7a007a9f

View File

@ -24,6 +24,7 @@ use std::ops::Range;
use std::ops::{Add, Sub};
use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::{Duration, Instant};
use term::color::ColorPalette;
use term::{CursorPosition, Line, Underline, VisibleRowIndex};
@ -40,11 +41,25 @@ struct RowsAndCols {
/// manipulation and the term crate clipboard interface
struct ClipboardHelper {
window: Window,
clipboard_contents: Arc<Mutex<Option<String>>>,
}
impl term::Clipboard for ClipboardHelper {
fn get_contents(&self) -> anyhow::Result<String> {
self.window.get_clipboard().wait()
// Even though we could request the clipboard contents using a call
// like `self.window.get_clipboard().wait()` here, that requires
// that the event loop be processed to do its work.
// Since we are typically called in a blocking fashion on the
// event loop, we have to manually arrange to populate the
// clipboard_contents cache prior to calling the code that
// might call us.
Ok(self
.clipboard_contents
.lock()
.unwrap()
.as_ref()
.cloned()
.unwrap_or_else(String::new))
}
fn set_contents(&self, data: Option<String>) -> anyhow::Result<()> {
@ -72,6 +87,10 @@ pub struct TermWindow {
config_generation: usize,
created_instant: Instant,
last_scroll_info: (VisibleRowIndex, usize),
/// Gross workaround for managing async keyboard fetching
/// just for middle mouse button paste function
clipboard_contents: Arc<Mutex<Option<String>>>,
}
struct Host<'a> {
@ -255,54 +274,94 @@ impl WindowCallbacks for TermWindow {
} else {
let y = y.saturating_sub(first_line_offset);
tab.mouse_event(
term::MouseEvent {
kind: match event.kind {
WMEK::Move => TMEK::Move,
WMEK::VertWheel(_)
| WMEK::HorzWheel(_)
| WMEK::DoubleClick(_)
| WMEK::Press(_) => TMEK::Press,
WMEK::Release(_) => TMEK::Release,
},
button: match event.kind {
WMEK::Release(ref press)
| WMEK::Press(ref press)
| WMEK::DoubleClick(ref press) => match press {
MousePress::Left => TMB::Left,
MousePress::Middle => TMB::Middle,
MousePress::Right => TMB::Right,
},
WMEK::Move => {
if event.mouse_buttons == WMB::LEFT {
TMB::Left
} else if event.mouse_buttons == WMB::RIGHT {
TMB::Right
} else if event.mouse_buttons == WMB::MIDDLE {
TMB::Middle
} else {
TMB::None
}
}
WMEK::VertWheel(amount) => {
if amount > 0 {
TMB::WheelUp(amount as usize)
} else {
TMB::WheelDown((-amount) as usize)
}
}
WMEK::HorzWheel(_) => TMB::None,
},
x,
y,
modifiers: window_mods_to_termwiz_mods(event.modifiers),
let mouse_event = term::MouseEvent {
kind: match event.kind {
WMEK::Move => TMEK::Move,
WMEK::VertWheel(_)
| WMEK::HorzWheel(_)
| WMEK::DoubleClick(_)
| WMEK::Press(_) => TMEK::Press,
WMEK::Release(_) => TMEK::Release,
},
&mut Host {
writer: &mut *tab.writer(),
context,
button: match event.kind {
WMEK::Release(ref press)
| WMEK::Press(ref press)
| WMEK::DoubleClick(ref press) => match press {
MousePress::Left => TMB::Left,
MousePress::Middle => TMB::Middle,
MousePress::Right => TMB::Right,
},
WMEK::Move => {
if event.mouse_buttons == WMB::LEFT {
TMB::Left
} else if event.mouse_buttons == WMB::RIGHT {
TMB::Right
} else if event.mouse_buttons == WMB::MIDDLE {
TMB::Middle
} else {
TMB::None
}
}
WMEK::VertWheel(amount) => {
if amount > 0 {
TMB::WheelUp(amount as usize)
} else {
TMB::WheelDown((-amount) as usize)
}
}
WMEK::HorzWheel(_) => TMB::None,
},
)
.ok();
x,
y,
modifiers: window_mods_to_termwiz_mods(event.modifiers),
};
if let WMEK::Press(MousePress::Middle) = event.kind {
// The middle button will typically want to paste the clipboard but
// obtaining the contents is an async operation that requires the
// event loop to pump.
// So we schedule that work and continue with dispatching the middle
// button once we have it.
// want to paste the clipboard,
let tab_id = tab.tab_id();
let window_clone = self.window.as_ref().cloned().unwrap();
let future = self.window.as_ref().unwrap().get_clipboard();
Connection::get().unwrap().spawn_task(async move {
if let Ok(clip) = future.await {
window_clone.apply(move |myself, context| {
if let Some(myself) = myself.downcast_mut::<Self>() {
myself
.clipboard_contents
.lock()
.unwrap()
.replace(clip.clone());
let mux = Mux::get().unwrap();
if let Some(tab) = mux.get_tab(tab_id) {
tab.mouse_event(
mouse_event,
&mut Host {
writer: &mut *tab.writer(),
context,
},
)
.ok();
}
}
Ok(())
});
}
});
} else {
tab.mouse_event(
mouse_event,
&mut Host {
writer: &mut *tab.writer(),
context,
},
)
.ok();
}
}
match event.kind {
@ -519,6 +578,8 @@ impl TermWindow {
ATLAS_SIZE,
)?);
let clipboard_contents = Arc::new(Mutex::new(None));
let window = Window::new_window(
"wezterm",
"wezterm",
@ -541,6 +602,7 @@ impl TermWindow {
config_generation: config.generation(),
created_instant: Instant::now(),
last_scroll_info: (0, 0),
clipboard_contents: Arc::clone(&clipboard_contents),
}),
)?;
@ -605,6 +667,7 @@ impl TermWindow {
let clipboard: Arc<dyn term::Clipboard> = Arc::new(ClipboardHelper {
window: window.clone(),
clipboard_contents,
});
tab.set_clipboard(&clipboard);
Mux::get()
@ -941,6 +1004,7 @@ impl TermWindow {
let clipboard: Arc<dyn term::Clipboard> = Arc::new(ClipboardHelper {
window: self.window.as_ref().unwrap().clone(),
clipboard_contents: Arc::clone(&self.clipboard_contents),
});
tab.set_clipboard(&clipboard);