* parse kitty keys from STDIN * work * work * replace internal Key representation with the new KeyWithModifier in all the places * work * work * allow disabling with config * adjust ordering * handle enabling/disabling properly on the client * support multiple modifiers without kitty too * normalize uppercase keys * get tests to pass * various cleanups * style(fmt): rustfmt
168 lines
6.8 KiB
Rust
168 lines
6.8 KiB
Rust
use crate::keyboard_parser::KittyKeyboardParser;
|
|
use crate::os_input_output::ClientOsApi;
|
|
use crate::stdin_ansi_parser::StdinAnsiParser;
|
|
use crate::InputInstruction;
|
|
use std::sync::{Arc, Mutex};
|
|
use zellij_utils::channels::SenderWithContext;
|
|
use zellij_utils::termwiz::input::{InputEvent, InputParser, MouseButtons};
|
|
|
|
fn send_done_parsing_after_query_timeout(
|
|
send_input_instructions: SenderWithContext<InputInstruction>,
|
|
query_duration: u64,
|
|
) {
|
|
std::thread::spawn({
|
|
move || {
|
|
std::thread::sleep(std::time::Duration::from_millis(query_duration));
|
|
send_input_instructions
|
|
.send(InputInstruction::DoneParsing)
|
|
.unwrap();
|
|
}
|
|
});
|
|
}
|
|
|
|
pub(crate) fn stdin_loop(
|
|
mut os_input: Box<dyn ClientOsApi>,
|
|
send_input_instructions: SenderWithContext<InputInstruction>,
|
|
stdin_ansi_parser: Arc<Mutex<StdinAnsiParser>>,
|
|
explicitly_disable_kitty_keyboard_protocol: bool,
|
|
) {
|
|
let mut holding_mouse = false;
|
|
let mut input_parser = InputParser::new();
|
|
let mut current_buffer = vec![];
|
|
{
|
|
// on startup we send a query to the terminal emulator for stuff like the pixel size and colors
|
|
// we get a response through STDIN, so it makes sense to do this here
|
|
let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
|
|
match stdin_ansi_parser.read_cache() {
|
|
Some(events) => {
|
|
let _ =
|
|
send_input_instructions.send(InputInstruction::AnsiStdinInstructions(events));
|
|
let _ = send_input_instructions
|
|
.send(InputInstruction::DoneParsing)
|
|
.unwrap();
|
|
},
|
|
None => {
|
|
send_input_instructions
|
|
.send(InputInstruction::StartedParsing)
|
|
.unwrap();
|
|
let terminal_emulator_query_string =
|
|
stdin_ansi_parser.terminal_emulator_query_string();
|
|
let _ = os_input
|
|
.get_stdout_writer()
|
|
.write(terminal_emulator_query_string.as_bytes())
|
|
.unwrap();
|
|
let query_duration = stdin_ansi_parser.startup_query_duration();
|
|
send_done_parsing_after_query_timeout(
|
|
send_input_instructions.clone(),
|
|
query_duration,
|
|
);
|
|
},
|
|
}
|
|
}
|
|
let mut ansi_stdin_events = vec![];
|
|
loop {
|
|
match os_input.read_from_stdin() {
|
|
Ok(buf) => {
|
|
{
|
|
// here we check if we need to parse specialized ANSI instructions sent over STDIN
|
|
// this happens either on startup (see above) or on SIGWINCH
|
|
//
|
|
// if we need to parse them, we do so with an internal timeout - anything else we
|
|
// receive on STDIN during that timeout is unceremoniously dropped
|
|
let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
|
|
if stdin_ansi_parser.should_parse() {
|
|
let events = stdin_ansi_parser.parse(buf);
|
|
if !events.is_empty() {
|
|
ansi_stdin_events.append(&mut events.clone());
|
|
let _ = send_input_instructions
|
|
.send(InputInstruction::AnsiStdinInstructions(events));
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
if !ansi_stdin_events.is_empty() {
|
|
stdin_ansi_parser
|
|
.lock()
|
|
.unwrap()
|
|
.write_cache(ansi_stdin_events.drain(..).collect());
|
|
}
|
|
current_buffer.append(&mut buf.to_vec());
|
|
|
|
if !explicitly_disable_kitty_keyboard_protocol {
|
|
// first we try to parse with the KittyKeyboardParser
|
|
// if we fail, we try to parse normally
|
|
match KittyKeyboardParser::new().parse(&buf) {
|
|
Some(key_with_modifier) => {
|
|
send_input_instructions
|
|
.send(InputInstruction::KeyWithModifierEvent(
|
|
key_with_modifier,
|
|
current_buffer.drain(..).collect(),
|
|
))
|
|
.unwrap();
|
|
continue;
|
|
},
|
|
None => {},
|
|
}
|
|
}
|
|
|
|
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
|
|
let mut events = vec![];
|
|
input_parser.parse(
|
|
&buf,
|
|
|input_event: InputEvent| {
|
|
events.push(input_event);
|
|
},
|
|
maybe_more,
|
|
);
|
|
|
|
let event_count = events.len();
|
|
for (i, input_event) in events.into_iter().enumerate() {
|
|
if holding_mouse && is_mouse_press_or_hold(&input_event) && i == event_count - 1
|
|
{
|
|
let mut poller = os_input.stdin_poller();
|
|
loop {
|
|
if poller.ready() {
|
|
break;
|
|
}
|
|
send_input_instructions
|
|
.send(InputInstruction::KeyEvent(
|
|
input_event.clone(),
|
|
current_buffer.clone(),
|
|
))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
holding_mouse = is_mouse_press_or_hold(&input_event);
|
|
|
|
send_input_instructions
|
|
.send(InputInstruction::KeyEvent(
|
|
input_event,
|
|
current_buffer.drain(..).collect(),
|
|
))
|
|
.unwrap();
|
|
}
|
|
},
|
|
Err(e) => {
|
|
if e == "Session ended" {
|
|
log::debug!("Switched sessions, signing this thread off...");
|
|
} else {
|
|
log::error!("Failed to read from STDIN: {}", e);
|
|
}
|
|
let _ = send_input_instructions.send(InputInstruction::Exit);
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_mouse_press_or_hold(input_event: &InputEvent) -> bool {
|
|
if let InputEvent::Mouse(mouse_event) = input_event {
|
|
if mouse_event.mouse_buttons.contains(MouseButtons::LEFT)
|
|
|| mouse_event.mouse_buttons.contains(MouseButtons::RIGHT)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|