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

allow collapsing mouse events in the mux protocol

Repeated moves or wheel events are collapsed so that we don't clog up
the queue.  The queue size doesn't matter as much as the latency of
processing a large queue.  For fast or repeated moves the queue can grow
rather quickly, and with what is currently ~25-50 ms round trip per
entry for a remote session, that is a poor UX.
This commit is contained in:
Wez Furlong 2019-06-15 21:33:10 -07:00
parent 302db2c976
commit 764597851c
8 changed files with 179 additions and 67 deletions

View File

@ -380,19 +380,19 @@ impl GliumTerminalWindow {
// We currently only care about vertical scrolling so the code // We currently only care about vertical scrolling so the code
// below will return early if all we have is horizontal scroll // below will return early if all we have is horizontal scroll
// components. // components.
let (button, times) = match delta { let button = match delta {
glutin::MouseScrollDelta::LineDelta(_, lines) if lines > 0.0 => { glutin::MouseScrollDelta::LineDelta(_, lines) if lines > 0.0 => {
(MouseButton::WheelUp, lines.abs().ceil() as usize) MouseButton::WheelUp(lines.abs().ceil() as usize)
} }
glutin::MouseScrollDelta::LineDelta(_, lines) if lines < 0.0 => { glutin::MouseScrollDelta::LineDelta(_, lines) if lines < 0.0 => {
(MouseButton::WheelDown, lines.abs().ceil() as usize) MouseButton::WheelDown(lines.abs().ceil() as usize)
} }
glutin::MouseScrollDelta::PixelDelta(position) => { glutin::MouseScrollDelta::PixelDelta(position) => {
let lines = position.y / self.cell_height as f64; let lines = position.y / self.cell_height as f64;
if lines > 0.0 { if lines > 0.0 {
(MouseButton::WheelUp, lines.abs().ceil() as usize) MouseButton::WheelUp(lines.abs().ceil() as usize)
} else if lines < 0.0 { } else if lines < 0.0 {
(MouseButton::WheelDown, lines.abs().ceil() as usize) MouseButton::WheelDown(lines.abs().ceil() as usize)
} else { } else {
return Ok(()); return Ok(());
} }
@ -405,7 +405,6 @@ impl GliumTerminalWindow {
Some(tab) => tab, Some(tab) => tab,
None => return Ok(()), None => return Ok(()),
}; };
for _ in 0..times {
tab.mouse_event( tab.mouse_event(
term::MouseEvent { term::MouseEvent {
kind: MouseEventKind::Press, kind: MouseEventKind::Press,
@ -416,7 +415,6 @@ impl GliumTerminalWindow {
}, },
&mut TabHost::new(&mut *tab.writer(), &mut self.host), &mut TabHost::new(&mut *tab.writer(), &mut self.host),
)?; )?;
}
self.paint_if_needed()?; self.paint_if_needed()?;
Ok(()) Ok(())

View File

@ -236,8 +236,8 @@ impl X11TerminalWindow {
1 => MouseButton::Left, 1 => MouseButton::Left,
2 => MouseButton::Middle, 2 => MouseButton::Middle,
3 => MouseButton::Right, 3 => MouseButton::Right,
4 => MouseButton::WheelUp, 4 => MouseButton::WheelUp(1),
5 => MouseButton::WheelDown, 5 => MouseButton::WheelDown(1),
_ => { _ => {
error!("button {} is not implemented", button_press.detail()); error!("button {} is not implemented", button_press.detail());
return Ok(()); return Ok(());

View File

@ -210,7 +210,7 @@ fn main() -> Result<(), Error> {
run_terminal_gui(config, &start) run_terminal_gui(config, &start)
} }
SubCommand::Cli(cli) => { SubCommand::Cli(cli) => {
let mut client = Client::new_unix_domain(&config)?; let client = Client::new_unix_domain(&config)?;
match cli.sub { match cli.sub {
CliSubCommand::List => { CliSubCommand::List => {
let cols = vec![ let cols = vec![

View File

@ -23,13 +23,14 @@ enum ReaderMessage {
SendPdu { pdu: Pdu, promise: Promise<Pdu> }, SendPdu { pdu: Pdu, promise: Promise<Pdu> },
} }
#[derive(Clone)]
pub struct Client { pub struct Client {
sender: Sender<ReaderMessage>, sender: Sender<ReaderMessage>,
} }
macro_rules! rpc { macro_rules! rpc {
($method_name:ident, $request_type:ident, $response_type:ident) => { ($method_name:ident, $request_type:ident, $response_type:ident) => {
pub fn $method_name(&mut self, pdu: $request_type) -> Future<$response_type> { pub fn $method_name(&self, pdu: $request_type) -> Future<$response_type> {
self.send_pdu(Pdu::$request_type(pdu)).then(|result| { self.send_pdu(Pdu::$request_type(pdu)).then(|result| {
match result { match result {
Ok(Pdu::$response_type(res)) => Ok(res), Ok(Pdu::$response_type(res)) => Ok(res),
@ -44,7 +45,7 @@ macro_rules! rpc {
// in the case where the struct is empty and present only for the purpose // in the case where the struct is empty and present only for the purpose
// of typing the request. // of typing the request.
($method_name:ident, $request_type:ident=(), $response_type:ident) => { ($method_name:ident, $request_type:ident=(), $response_type:ident) => {
pub fn $method_name(&mut self) -> Future<$response_type> { pub fn $method_name(&self) -> Future<$response_type> {
self.send_pdu(Pdu::$request_type($request_type{})).then(|result| { self.send_pdu(Pdu::$request_type($request_type{})).then(|result| {
match result { match result {
Ok(Pdu::$response_type(res)) => Ok(res), Ok(Pdu::$response_type(res)) => Ok(res),
@ -173,7 +174,7 @@ impl Client {
Ok(Self::new(stream)) Ok(Self::new(stream))
} }
pub fn send_pdu(&mut self, pdu: Pdu) -> Future<Pdu> { pub fn send_pdu(&self, pdu: Pdu) -> Future<Pdu> {
let mut promise = Promise::new(); let mut promise = Promise::new();
let future = promise.get_future().expect("future already taken!?"); let future = promise.get_future().expect("future already taken!?");
match self.sender.send(ReaderMessage::SendPdu { pdu, promise }) { match self.sender.send(ReaderMessage::SendPdu { pdu, promise }) {

View File

@ -14,7 +14,7 @@ use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
pub struct ClientInner { pub struct ClientInner {
pub client: Mutex<Client>, pub client: Client,
pub local_domain_id: DomainId, pub local_domain_id: DomainId,
pub remote_domain_id: DomainId, pub remote_domain_id: DomainId,
remote_to_local_window: Mutex<HashMap<WindowId, WindowId>>, remote_to_local_window: Mutex<HashMap<WindowId, WindowId>>,
@ -64,7 +64,7 @@ impl ClientInner {
// this a bit rigorously. // this a bit rigorously.
let remote_domain_id = 0; let remote_domain_id = 0;
Self { Self {
client: Mutex::new(client), client,
local_domain_id, local_domain_id,
remote_domain_id, remote_domain_id,
remote_to_local_window: Mutex::new(HashMap::new()), remote_to_local_window: Mutex::new(HashMap::new()),
@ -91,9 +91,9 @@ impl Domain for ClientDomain {
window: WindowId, window: WindowId,
) -> Fallible<Rc<dyn Tab>> { ) -> Fallible<Rc<dyn Tab>> {
let remote_tab_id = { let remote_tab_id = {
let mut client = self.inner.client.lock().unwrap(); let result = self
.inner
let result = client .client
.spawn(Spawn { .spawn(Spawn {
domain_id: self.inner.remote_domain_id, domain_id: self.inner.remote_domain_id,
window_id: self.inner.local_to_remote_window(window), window_id: self.inner.local_to_remote_window(window),
@ -117,8 +117,7 @@ impl Domain for ClientDomain {
fn attach(&self) -> Fallible<()> { fn attach(&self) -> Fallible<()> {
let mux = Mux::get().unwrap(); let mux = Mux::get().unwrap();
let mut client = self.inner.client.lock().unwrap(); let tabs = self.inner.client.list_tabs().wait()?;
let tabs = client.list_tabs().wait()?;
log::error!("ListTabs result {:#?}", tabs); log::error!("ListTabs result {:#?}", tabs);
for entry in tabs.tabs.iter() { for entry in tabs.tabs.iter() {

View File

@ -1,6 +1,8 @@
use crate::frontend::gui_executor;
use crate::mux::domain::DomainId; use crate::mux::domain::DomainId;
use crate::mux::renderable::Renderable; use crate::mux::renderable::Renderable;
use crate::mux::tab::{alloc_tab_id, Tab, TabId}; use crate::mux::tab::{alloc_tab_id, Tab, TabId};
use crate::server::client::Client;
use crate::server::codec::*; use crate::server::codec::*;
use crate::server::domain::ClientInner; use crate::server::domain::ClientInner;
use failure::Fallible; use failure::Fallible;
@ -10,17 +12,113 @@ use portable_pty::PtySize;
use promise::Future; use promise::Future;
use std::cell::RefCell; use std::cell::RefCell;
use std::cell::RefMut; use std::cell::RefMut;
use std::collections::VecDeque;
use std::ops::Range; use std::ops::Range;
use std::sync::Arc; use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use term::color::ColorPalette; use term::color::ColorPalette;
use term::selection::SelectionRange; use term::selection::SelectionRange;
use term::{CursorPosition, Line}; use term::{CursorPosition, Line};
use term::{KeyCode, KeyModifiers, MouseEvent, TerminalHost}; use term::{KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind, TerminalHost};
use termwiz::hyperlink::Hyperlink; use termwiz::hyperlink::Hyperlink;
use termwiz::input::KeyEvent; use termwiz::input::KeyEvent;
use termwiz::surface::{Change, SequenceNo, Surface}; use termwiz::surface::{Change, SequenceNo, Surface};
struct MouseState {
future: Option<Future<()>>,
queue: VecDeque<MouseEvent>,
selection_range: Arc<Mutex<Option<SelectionRange>>>,
something_changed: Arc<Mutex<bool>>,
client: Client,
remote_tab_id: TabId,
}
impl MouseState {
fn append(&mut self, event: MouseEvent) {
if let Some(last) = self.queue.back_mut() {
if last.modifiers == event.modifiers {
if last.kind == MouseEventKind::Move
&& event.kind == MouseEventKind::Move
&& last.button == event.button
{
// Collapse any interim moves and just buffer up
// the last of them
*last = event;
return;
}
// Similarly, for repeated wheel scrolls, add up the deltas
// rather than swamping the queue
match (&last.button, &event.button) {
(MouseButton::WheelUp(a), MouseButton::WheelUp(b)) => {
last.button = MouseButton::WheelUp(a + b);
return;
}
(MouseButton::WheelDown(a), MouseButton::WheelDown(b)) => {
last.button = MouseButton::WheelDown(a + b);
return;
}
_ => {}
}
}
}
self.queue.push_back(event);
log::trace!("MouseEvent {}: queued", self.queue.len());
}
fn pop(&mut self) -> Fallible<Option<MouseEvent>> {
if self.can_send()? {
Ok(self.queue.pop_front())
} else {
Ok(None)
}
}
fn can_send(&mut self) -> Fallible<bool> {
if self.future.is_none() {
Ok(true)
} else {
let ready = self.future.as_ref().map(Future::is_ready).unwrap_or(false);
if ready {
self.future.take().unwrap().wait()?;
Ok(true)
} else {
Ok(false)
}
}
}
fn next(state: &Arc<Mutex<Self>>) -> Fallible<()> {
let mut mouse = state.lock().unwrap();
if let Some(event) = mouse.pop()? {
let selection_range = Arc::clone(&mouse.selection_range);
let something_changed = Arc::clone(&mouse.something_changed);
let state = Arc::clone(state);
mouse.future = Some(
mouse
.client
.mouse_event(SendMouseEvent {
tab_id: mouse.remote_tab_id,
event,
})
.then(move |resp| {
if let Ok(r) = resp.as_ref() {
*selection_range.lock().unwrap() = r.selection_range.clone();
*something_changed.lock().unwrap() = true;
}
Future::with_executor(gui_executor().unwrap(), move || {
Self::next(&state)?;
Ok(())
});
Ok(())
}),
);
}
Ok(())
}
}
pub struct ClientTab { pub struct ClientTab {
client: Arc<ClientInner>, client: Arc<ClientInner>,
local_tab_id: TabId, local_tab_id: TabId,
@ -28,6 +126,7 @@ pub struct ClientTab {
renderable: RefCell<RenderableState>, renderable: RefCell<RenderableState>,
writer: RefCell<TabWriter>, writer: RefCell<TabWriter>,
reader: Pipe, reader: Pipe,
mouse: Arc<Mutex<MouseState>>,
} }
impl ClientTab { impl ClientTab {
@ -37,6 +136,16 @@ impl ClientTab {
client: Arc::clone(client), client: Arc::clone(client),
remote_tab_id, remote_tab_id,
}; };
let selection_range = Arc::new(Mutex::new(None));
let something_changed = Arc::new(Mutex::new(true));
let mouse = Arc::new(Mutex::new(MouseState {
selection_range: Arc::clone(&selection_range),
something_changed: Arc::clone(&something_changed),
remote_tab_id,
client: client.client.clone(),
future: None,
queue: VecDeque::new(),
}));
let render = RenderableState { let render = RenderableState {
client: Arc::clone(client), client: Arc::clone(client),
remote_tab_id, remote_tab_id,
@ -46,13 +155,15 @@ impl ClientTab {
surface: RefCell::new(Surface::new(80, 24)), surface: RefCell::new(Surface::new(80, 24)),
remote_sequence: RefCell::new(0), remote_sequence: RefCell::new(0),
local_sequence: RefCell::new(0), local_sequence: RefCell::new(0),
selection_range: RefCell::new(None), selection_range,
something_changed,
}; };
let reader = Pipe::new().expect("Pipe::new failed"); let reader = Pipe::new().expect("Pipe::new failed");
Self { Self {
client: Arc::clone(client), client: Arc::clone(client),
mouse,
remote_tab_id, remote_tab_id,
local_tab_id, local_tab_id,
renderable: RefCell::new(render), renderable: RefCell::new(render),
@ -77,8 +188,7 @@ impl Tab for ClientTab {
} }
fn send_paste(&self, text: &str) -> Fallible<()> { fn send_paste(&self, text: &str) -> Fallible<()> {
let mut client = self.client.client.lock().unwrap(); self.client.client.send_paste(SendPaste {
client.send_paste(SendPaste {
tab_id: self.remote_tab_id, tab_id: self.remote_tab_id,
data: text.to_owned(), data: text.to_owned(),
}); });
@ -100,8 +210,7 @@ impl Tab for ClientTab {
.surface .surface
.borrow_mut() .borrow_mut()
.resize(size.cols as usize, size.rows as usize); .resize(size.cols as usize, size.rows as usize);
let mut client = self.client.client.lock().unwrap(); self.client.client.resize(Resize {
client.resize(Resize {
tab_id: self.remote_tab_id, tab_id: self.remote_tab_id,
size, size,
}); });
@ -109,8 +218,7 @@ impl Tab for ClientTab {
} }
fn key_down(&self, key: KeyCode, mods: KeyModifiers) -> Fallible<()> { fn key_down(&self, key: KeyCode, mods: KeyModifiers) -> Fallible<()> {
let mut client = self.client.client.lock().unwrap(); self.client.client.key_down(SendKeyDown {
client.key_down(SendKeyDown {
tab_id: self.remote_tab_id, tab_id: self.remote_tab_id,
event: KeyEvent { event: KeyEvent {
key, key,
@ -120,21 +228,17 @@ impl Tab for ClientTab {
Ok(()) Ok(())
} }
fn mouse_event(&self, event: MouseEvent, host: &mut dyn TerminalHost) -> Fallible<()> { fn mouse_event(&self, event: MouseEvent, _host: &mut dyn TerminalHost) -> Fallible<()> {
let mut client = self.client.client.lock().unwrap(); self.mouse.lock().unwrap().append(event);
let resp = client MouseState::next(&self.mouse)?;
.mouse_event(SendMouseEvent { Ok(())
tab_id: self.remote_tab_id,
event,
})
.wait()?;
/*
if resp.clipboard.is_some() { if resp.clipboard.is_some() {
host.set_clipboard(resp.clipboard)?; host.set_clipboard(resp.clipboard)?;
} }
*self.renderable.borrow().selection_range.borrow_mut() = resp.selection_range; *self.renderable.borrow().selection_range.lock().unwrap() = resp.selection_range;
*/
Ok(())
} }
fn advance_bytes(&self, _buf: &[u8], _host: &mut dyn TerminalHost) { fn advance_bytes(&self, _buf: &[u8], _host: &mut dyn TerminalHost) {
@ -158,7 +262,12 @@ impl Tab for ClientTab {
} }
fn selection_range(&self) -> Option<SelectionRange> { fn selection_range(&self) -> Option<SelectionRange> {
self.renderable.borrow().selection_range.borrow().clone() self.renderable
.borrow()
.selection_range
.lock()
.unwrap()
.clone()
} }
} }
@ -171,7 +280,8 @@ struct RenderableState {
surface: RefCell<Surface>, surface: RefCell<Surface>,
remote_sequence: RefCell<SequenceNo>, remote_sequence: RefCell<SequenceNo>,
local_sequence: RefCell<SequenceNo>, local_sequence: RefCell<SequenceNo>,
selection_range: RefCell<Option<SelectionRange>>, selection_range: Arc<Mutex<Option<SelectionRange>>>,
something_changed: Arc<Mutex<bool>>,
} }
const POLL_INTERVAL: Duration = Duration::from_millis(50); const POLL_INTERVAL: Duration = Duration::from_millis(50);
@ -214,13 +324,13 @@ impl RenderableState {
} }
{ {
let mut client = self.client.client.lock().unwrap();
*self.last_poll.borrow_mut() = Instant::now(); *self.last_poll.borrow_mut() = Instant::now();
*self.poll_future.borrow_mut() = *self.poll_future.borrow_mut() = Some(self.client.client.get_tab_render_changes(
Some(client.get_tab_render_changes(GetTabRenderChanges { GetTabRenderChanges {
tab_id: self.remote_tab_id, tab_id: self.remote_tab_id,
sequence_no: *self.remote_sequence.borrow(), sequence_no: *self.remote_sequence.borrow(),
})); },
));
} }
Ok(()) Ok(())
} }
@ -236,7 +346,8 @@ impl Renderable for RenderableState {
let mut surface = self.surface.borrow_mut(); let mut surface = self.surface.borrow_mut();
let seq = surface.current_seqno(); let seq = surface.current_seqno();
surface.flush_changes_older_than(seq); surface.flush_changes_older_than(seq);
let selection = *self.selection_range.borrow(); let selection = *self.selection_range.lock().unwrap();
*self.something_changed.lock().unwrap() = false;
surface surface
.screen_lines() .screen_lines()
.into_iter() .into_iter()
@ -255,6 +366,9 @@ impl Renderable for RenderableState {
if self.poll().is_err() { if self.poll().is_err() {
*self.dead.borrow_mut() = true; *self.dead.borrow_mut() = true;
} }
if *self.something_changed.lock().unwrap() {
return true;
}
self.surface self.surface
.borrow() .borrow()
.has_changes(*self.local_sequence.borrow()) .has_changes(*self.local_sequence.borrow())
@ -281,8 +395,8 @@ struct TabWriter {
impl std::io::Write for TabWriter { impl std::io::Write for TabWriter {
fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> { fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
let mut client = self.client.client.lock().unwrap(); self.client
client .client
.write_to_tab(WriteToTab { .write_to_tab(WriteToTab {
tab_id: self.remote_tab_id, tab_id: self.remote_tab_id,
data: data.to_vec(), data: data.to_vec(),

View File

@ -13,8 +13,8 @@ pub enum MouseButton {
Left, Left,
Middle, Middle,
Right, Right,
WheelUp, WheelUp(usize),
WheelDown, WheelDown(usize),
None, None,
} }

View File

@ -614,10 +614,10 @@ impl TerminalState {
} }
fn mouse_wheel(&mut self, event: MouseEvent, writer: &mut std::io::Write) -> Result<(), Error> { fn mouse_wheel(&mut self, event: MouseEvent, writer: &mut std::io::Write) -> Result<(), Error> {
let (report_button, scroll_delta, key) = if event.button == MouseButton::WheelUp { let (report_button, scroll_delta, key) = match event.button {
(64, -1, KeyCode::UpArrow) MouseButton::WheelUp(amount) => (64, -(amount as i64), KeyCode::UpArrow),
} else { MouseButton::WheelDown(amount) => (65, amount as i64, KeyCode::DownArrow),
(65, 1, KeyCode::DownArrow) _ => bail!("unexpected mouse event {:?}", event),
}; };
if self.sgr_mouse { if self.sgr_mouse {
@ -763,12 +763,12 @@ impl TerminalState {
match event { match event {
MouseEvent { MouseEvent {
kind: MouseEventKind::Press, kind: MouseEventKind::Press,
button: MouseButton::WheelUp, button: MouseButton::WheelUp(_),
.. ..
} }
| MouseEvent { | MouseEvent {
kind: MouseEventKind::Press, kind: MouseEventKind::Press,
button: MouseButton::WheelDown, button: MouseButton::WheelDown(_),
.. ..
} => self.mouse_wheel(event, host.writer()), } => self.mouse_wheel(event, host.writer()),
MouseEvent { MouseEvent {