hotfix(stdin): poll for mouse hold in the stdin thread (#752)
* hotfix(stdin): poll for mouse hold in the stdin thread * add missing dont panic * style(fmt): make rustfmt happy
This commit is contained in:
parent
f74af9c7bf
commit
d7e4ec65db
8 changed files with 96 additions and 28 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -2716,7 +2716,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij"
|
name = "zellij"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"insta",
|
"insta",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -2730,7 +2730,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-client"
|
name = "zellij-client"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"insta",
|
"insta",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -2742,7 +2742,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-server"
|
name = "zellij-server"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.12.1",
|
"ansi_term 0.12.1",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|
@ -2765,7 +2765,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-tile"
|
name = "zellij-tile"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -2775,14 +2775,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-tile-utils"
|
name = "zellij-tile-utils"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.12.1",
|
"ansi_term 0.12.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-utils"
|
name = "zellij-utils"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
|
|
||||||
|
|
@ -926,6 +926,7 @@ pub fn mirrored_sessions() {
|
||||||
// if no test timed out, we break the loop and assert the snapshot
|
// if no test timed out, we break the loop and assert the snapshot
|
||||||
let mut first_runner =
|
let mut first_runner =
|
||||||
RemoteRunner::new_with_session_name("mirrored_sessions", fake_win_size, session_name)
|
RemoteRunner::new_with_session_name("mirrored_sessions", fake_win_size, session_name)
|
||||||
|
.dont_panic()
|
||||||
.add_step(Step {
|
.add_step(Step {
|
||||||
name: "Split pane to the right",
|
name: "Split pane to the right",
|
||||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||||
|
|
@ -959,6 +960,7 @@ pub fn mirrored_sessions() {
|
||||||
|
|
||||||
let mut second_runner =
|
let mut second_runner =
|
||||||
RemoteRunner::new_existing_session("mirrored_sessions", fake_win_size, session_name)
|
RemoteRunner::new_existing_session("mirrored_sessions", fake_win_size, session_name)
|
||||||
|
.dont_panic()
|
||||||
.add_step(Step {
|
.add_step(Step {
|
||||||
name: "Make sure session appears correctly",
|
name: "Make sure session appears correctly",
|
||||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,7 @@ pub struct RemoteRunner {
|
||||||
without_frames: bool,
|
without_frames: bool,
|
||||||
session_name: Option<String>,
|
session_name: Option<String>,
|
||||||
attach_to_existing: bool,
|
attach_to_existing: bool,
|
||||||
|
panic_on_no_retries_left: bool,
|
||||||
pub test_timed_out: bool,
|
pub test_timed_out: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,6 +248,7 @@ impl RemoteRunner {
|
||||||
session_name: None,
|
session_name: None,
|
||||||
attach_to_existing: false,
|
attach_to_existing: false,
|
||||||
test_timed_out: false,
|
test_timed_out: false,
|
||||||
|
panic_on_no_retries_left: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_with_session_name(
|
pub fn new_with_session_name(
|
||||||
|
|
@ -285,6 +287,7 @@ impl RemoteRunner {
|
||||||
session_name: Some(String::from(session_name)),
|
session_name: Some(String::from(session_name)),
|
||||||
attach_to_existing: false,
|
attach_to_existing: false,
|
||||||
test_timed_out: false,
|
test_timed_out: false,
|
||||||
|
panic_on_no_retries_left: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_existing_session(
|
pub fn new_existing_session(
|
||||||
|
|
@ -323,6 +326,7 @@ impl RemoteRunner {
|
||||||
session_name: Some(String::from(session_name)),
|
session_name: Some(String::from(session_name)),
|
||||||
attach_to_existing: true,
|
attach_to_existing: true,
|
||||||
test_timed_out: false,
|
test_timed_out: false,
|
||||||
|
panic_on_no_retries_left: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self {
|
pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self {
|
||||||
|
|
@ -357,6 +361,7 @@ impl RemoteRunner {
|
||||||
session_name: None,
|
session_name: None,
|
||||||
attach_to_existing: false,
|
attach_to_existing: false,
|
||||||
test_timed_out: false,
|
test_timed_out: false,
|
||||||
|
panic_on_no_retries_left: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_with_layout(
|
pub fn new_with_layout(
|
||||||
|
|
@ -396,8 +401,13 @@ impl RemoteRunner {
|
||||||
session_name: None,
|
session_name: None,
|
||||||
attach_to_existing: false,
|
attach_to_existing: false,
|
||||||
test_timed_out: false,
|
test_timed_out: false,
|
||||||
|
panic_on_no_retries_left: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn dont_panic(mut self) -> Self {
|
||||||
|
self.panic_on_no_retries_left = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn add_step(mut self, step: Step) -> Self {
|
pub fn add_step(mut self, step: Step) -> Self {
|
||||||
self.steps.push(step);
|
self.steps.push(step);
|
||||||
self
|
self
|
||||||
|
|
@ -405,6 +415,35 @@ impl RemoteRunner {
|
||||||
pub fn replace_steps(&mut self, steps: Vec<Step>) {
|
pub fn replace_steps(&mut self, steps: Vec<Step>) {
|
||||||
self.steps = steps;
|
self.steps = steps;
|
||||||
}
|
}
|
||||||
|
fn display_informative_error(&mut self) {
|
||||||
|
let test_name = self.test_name;
|
||||||
|
let current_step_name = self.currently_running_step.as_ref().cloned();
|
||||||
|
match current_step_name {
|
||||||
|
Some(current_step) => {
|
||||||
|
let remote_terminal = self.current_remote_terminal_state();
|
||||||
|
eprintln!("Timed out waiting for data on the SSH channel for test {}. Was waiting for step: {}", test_name, current_step);
|
||||||
|
eprintln!("{:?}", remote_terminal);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let remote_terminal = self.current_remote_terminal_state();
|
||||||
|
eprintln!("Timed out waiting for data on the SSH channel for test {}. Haven't begun running steps yet.", test_name);
|
||||||
|
eprintln!("{:?}", remote_terminal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_current_snapshot(&mut self) -> String {
|
||||||
|
take_snapshot(&mut self.terminal_output)
|
||||||
|
}
|
||||||
|
fn current_remote_terminal_state(&mut self) -> RemoteTerminal {
|
||||||
|
let current_snapshot = self.get_current_snapshot();
|
||||||
|
let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
|
||||||
|
RemoteTerminal {
|
||||||
|
cursor_x,
|
||||||
|
cursor_y,
|
||||||
|
current_snapshot,
|
||||||
|
channel: &mut self.channel,
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn run_next_step(&mut self) {
|
pub fn run_next_step(&mut self) {
|
||||||
if let Some(next_step) = self.steps.get(self.current_step_index) {
|
if let Some(next_step) = self.steps.get(self.current_step_index) {
|
||||||
let current_snapshot = take_snapshot(&mut self.terminal_output);
|
let current_snapshot = take_snapshot(&mut self.terminal_output);
|
||||||
|
|
@ -482,11 +521,15 @@ impl RemoteRunner {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
if self.retries_left > 0 {
|
if self.retries_left > 0 {
|
||||||
return self.restart_test();
|
return self.restart_test();
|
||||||
}
|
}
|
||||||
self.test_timed_out = true;
|
self.test_timed_out = true;
|
||||||
|
if self.panic_on_no_retries_left {
|
||||||
|
self.display_informative_error();
|
||||||
|
panic!("timed out waiting for test: {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,6 @@ impl InputHandler {
|
||||||
}
|
}
|
||||||
MouseEvent::Hold(point) => {
|
MouseEvent::Hold(point) => {
|
||||||
self.dispatch_action(Action::MouseHold(point));
|
self.dispatch_action(Action::MouseHold(point));
|
||||||
self.os_input
|
|
||||||
.start_action_repeater(Action::MouseHold(point));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use zellij_utils::{
|
||||||
channels::{self, ChannelWithContext, SenderWithContext},
|
channels::{self, ChannelWithContext, SenderWithContext},
|
||||||
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
|
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
|
||||||
errors::{ClientContext, ContextType, ErrorInstruction},
|
errors::{ClientContext, ContextType, ErrorInstruction},
|
||||||
input::{actions::Action, config::Config, options::Options},
|
input::{actions::Action, config::Config, mouse::MouseEvent, options::Options},
|
||||||
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
|
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
|
||||||
termion,
|
termion,
|
||||||
};
|
};
|
||||||
|
|
@ -197,6 +197,35 @@ pub fn start_client(
|
||||||
let stdin_buffer = os_input.read_from_stdin();
|
let stdin_buffer = os_input.read_from_stdin();
|
||||||
for key_result in stdin_buffer.events_and_raw() {
|
for key_result in stdin_buffer.events_and_raw() {
|
||||||
let (key_event, raw_bytes) = key_result.unwrap();
|
let (key_event, raw_bytes) = key_result.unwrap();
|
||||||
|
if let termion::event::Event::Mouse(me) = key_event {
|
||||||
|
let mouse_event = zellij_utils::input::mouse::MouseEvent::from(me);
|
||||||
|
if let MouseEvent::Hold(_) = mouse_event {
|
||||||
|
// as long as the user is holding the mouse down (no other stdin, eg.
|
||||||
|
// MouseRelease) we need to keep sending this instruction to the app,
|
||||||
|
// because the app itself doesn't have an event loop in the proper
|
||||||
|
// place
|
||||||
|
let mut poller = os_input.stdin_poller();
|
||||||
|
send_input_instructions
|
||||||
|
.send(InputInstruction::KeyEvent(
|
||||||
|
key_event.clone(),
|
||||||
|
raw_bytes.clone(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
loop {
|
||||||
|
let ready = poller.ready();
|
||||||
|
if ready {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
send_input_instructions
|
||||||
|
.send(InputInstruction::KeyEvent(
|
||||||
|
key_event.clone(),
|
||||||
|
raw_bytes.clone(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
send_input_instructions
|
send_input_instructions
|
||||||
.send(InputInstruction::KeyEvent(key_event, raw_bytes))
|
.send(InputInstruction::KeyEvent(key_event, raw_bytes))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use zellij_utils::input::actions::Action;
|
|
||||||
use zellij_utils::pane_size::Size;
|
use zellij_utils::pane_size::Size;
|
||||||
use zellij_utils::{interprocess, libc, nix, signal_hook, termion, zellij_tile};
|
use zellij_utils::{interprocess, libc, nix, signal_hook, termion, zellij_tile};
|
||||||
|
|
||||||
|
|
@ -97,7 +96,7 @@ pub trait ClientOsApi: Send + Sync {
|
||||||
fn enable_mouse(&self);
|
fn enable_mouse(&self);
|
||||||
fn disable_mouse(&self);
|
fn disable_mouse(&self);
|
||||||
// Repeatedly send action, until stdin is readable again
|
// Repeatedly send action, until stdin is readable again
|
||||||
fn start_action_repeater(&mut self, action: Action);
|
fn stdin_poller(&self) -> StdinPoller;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientOsApi for ClientOsInputOutput {
|
impl ClientOsApi for ClientOsInputOutput {
|
||||||
|
|
@ -204,16 +203,8 @@ impl ClientOsApi for ClientOsInputOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_action_repeater(&mut self, action: Action) {
|
fn stdin_poller(&self) -> StdinPoller {
|
||||||
let mut poller = StdinPoller::default();
|
StdinPoller::default()
|
||||||
|
|
||||||
loop {
|
|
||||||
let ready = poller.ready();
|
|
||||||
if ready {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.send_to_server(ClientToServerMsg::Action(action.clone()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,7 +228,7 @@ pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
|
||||||
|
|
||||||
pub const DEFAULT_STDIN_POLL_TIMEOUT_MS: u64 = 10;
|
pub const DEFAULT_STDIN_POLL_TIMEOUT_MS: u64 = 10;
|
||||||
|
|
||||||
struct StdinPoller {
|
pub struct StdinPoller {
|
||||||
poll: Poll,
|
poll: Poll,
|
||||||
events: Events,
|
events: Events,
|
||||||
timeout: time::Duration,
|
timeout: time::Duration,
|
||||||
|
|
@ -245,7 +236,7 @@ struct StdinPoller {
|
||||||
|
|
||||||
impl StdinPoller {
|
impl StdinPoller {
|
||||||
// use mio poll to check if stdin is readable without blocking
|
// use mio poll to check if stdin is readable without blocking
|
||||||
fn ready(&mut self) -> bool {
|
pub fn ready(&mut self) -> bool {
|
||||||
self.poll
|
self.poll
|
||||||
.poll(&mut self.events, Some(self.timeout))
|
.poll(&mut self.events, Some(self.timeout))
|
||||||
.expect("could not poll stdin for readiness");
|
.expect("could not poll stdin for readiness");
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ use zellij_utils::termion::event::Key;
|
||||||
use zellij_utils::zellij_tile::data::Palette;
|
use zellij_utils::zellij_tile::data::Palette;
|
||||||
|
|
||||||
use crate::InputInstruction;
|
use crate::InputInstruction;
|
||||||
use crate::{os_input_output::ClientOsApi, ClientInstruction, CommandIsExecuting};
|
use crate::{
|
||||||
|
os_input_output::{ClientOsApi, StdinPoller},
|
||||||
|
ClientInstruction, CommandIsExecuting,
|
||||||
|
};
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
@ -133,7 +136,9 @@ impl ClientOsApi for FakeClientOsApi {
|
||||||
}
|
}
|
||||||
fn enable_mouse(&self) {}
|
fn enable_mouse(&self) {}
|
||||||
fn disable_mouse(&self) {}
|
fn disable_mouse(&self) {}
|
||||||
fn start_action_repeater(&mut self, _action: Action) {}
|
fn stdin_poller(&self) -> StdinPoller {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_actions_sent_to_server(
|
fn extract_actions_sent_to_server(
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ pub fn get_current_ctx() -> ErrorContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A representation of the call stack.
|
/// A representation of the call stack.
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
pub struct ErrorContext {
|
pub struct ErrorContext {
|
||||||
calls: [ContextType; MAX_THREAD_CALL_STACK],
|
calls: [ContextType; MAX_THREAD_CALL_STACK],
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +161,7 @@ impl Display for ErrorContext {
|
||||||
/// Complex variants store a variant of a related enum, whose variants can be built from
|
/// Complex variants store a variant of a related enum, whose variants can be built from
|
||||||
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
|
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
|
||||||
/// [`PtyInstruction`], [`ClientInstruction`], etc).
|
/// [`PtyInstruction`], [`ClientInstruction`], etc).
|
||||||
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||||
pub enum ContextType {
|
pub enum ContextType {
|
||||||
/// A screen-related call.
|
/// A screen-related call.
|
||||||
Screen(ScreenContext),
|
Screen(ScreenContext),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue