mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-23 04:51:16 -03:00
Adopt TtyHandoff in remaining places
This adopts the tty handoff in remaining places. The idea is to rationalize when we enable and disable tty protocols (such as CSI-U). In particular this removes the tty protocol disabling in Parser::eval_node - that is intended to execute pure fish script and should not be talking to the tty.
This commit is contained in:
@@ -139,12 +139,13 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
||||
|
||||
// Note if tty transfer fails, we still try running the job.
|
||||
parser.job_promote_at(job_pos);
|
||||
let mut handoff = TtyHandoff::new();
|
||||
let _ = make_fd_blocking(STDIN_FILENO);
|
||||
{
|
||||
let job_group = job.group();
|
||||
job_group.set_is_foreground(true);
|
||||
if job.entitled_to_terminal() {
|
||||
crate::input_common::terminal_protocols_disable_ifn();
|
||||
handoff.disable_tty_protocols();
|
||||
}
|
||||
let tmodes = job_group.tmodes.borrow();
|
||||
if job_group.wants_terminal() && tmodes.is_some() {
|
||||
@@ -155,7 +156,6 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut handoff = TtyHandoff::new();
|
||||
handoff.to_job_group(job.group.as_ref().unwrap());
|
||||
let resumed = job.resume();
|
||||
if resumed {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//!
|
||||
//! Type "exit" or "quit" to terminate the program.
|
||||
|
||||
use std::{cell::RefCell, ops::ControlFlow, os::unix::prelude::OsStrExt, sync::atomic::Ordering};
|
||||
use std::{cell::RefCell, ops::ControlFlow, os::unix::prelude::OsStrExt};
|
||||
|
||||
use libc::{STDIN_FILENO, TCSANOW, VEOF, VINTR};
|
||||
use once_cell::unsync::OnceCell;
|
||||
@@ -30,10 +30,13 @@
|
||||
proc::set_interactive_session,
|
||||
reader::{check_exit_loop_maybe_warning, initial_query, reader_init},
|
||||
signal::signal_set_handlers,
|
||||
terminal::{Capability, KITTY_KEYBOARD_SUPPORTED},
|
||||
terminal::Capability,
|
||||
threads,
|
||||
topic_monitor::topic_monitor_init,
|
||||
tty_handoff::{initialize_tty_metadata, TtyHandoff},
|
||||
tty_handoff::{
|
||||
get_kitty_keyboard_capability, initialize_tty_metadata, set_kitty_keyboard_capability,
|
||||
TtyHandoff,
|
||||
},
|
||||
wchar::prelude::*,
|
||||
wgetopt::{wopt, ArgType, WGetopter, WOption},
|
||||
};
|
||||
@@ -96,9 +99,8 @@ fn process_input(streams: &mut IoStreams, continuous_mode: bool, verbose: bool)
|
||||
CharEvent::Key(kevt) => kevt,
|
||||
CharEvent::Readline(_) | CharEvent::Command(_) | CharEvent::Implicit(_) => continue,
|
||||
CharEvent::QueryResponse(QueryResponseEvent::PrimaryDeviceAttribute) => {
|
||||
if KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed) == Capability::Unknown as _ {
|
||||
KITTY_KEYBOARD_SUPPORTED
|
||||
.store(Capability::NotSupported as _, Ordering::Release);
|
||||
if get_kitty_keyboard_capability() == Capability::Unknown {
|
||||
set_kitty_keyboard_capability(Capability::NotSupported);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
use crate::env::READ_BYTE_LIMIT;
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::input_common::decode_input_byte;
|
||||
use crate::input_common::terminal_protocols_disable_ifn;
|
||||
use crate::input_common::DecodeState;
|
||||
use crate::input_common::InvalidPolicy;
|
||||
use crate::nix::isatty;
|
||||
@@ -22,6 +21,7 @@
|
||||
use crate::tokenizer::Tokenizer;
|
||||
use crate::tokenizer::TOK_ACCEPT_UNFINISHED;
|
||||
use crate::tokenizer::TOK_ARGUMENT_LIST;
|
||||
use crate::tty_handoff::TtyHandoff;
|
||||
use crate::wcstringutil::split_about;
|
||||
use crate::wcstringutil::split_string_tok;
|
||||
use crate::wutil;
|
||||
@@ -244,9 +244,10 @@ fn read_interactive(
|
||||
|
||||
let mline = {
|
||||
let _interactive = parser.push_scope(|s| s.is_interactive = true);
|
||||
let mut scoped_handoff = TtyHandoff::new();
|
||||
scoped_handoff.enable_tty_protocols();
|
||||
reader_readline(parser, NonZeroUsize::try_from(nchars).ok())
|
||||
};
|
||||
terminal_protocols_disable_ifn();
|
||||
if let Some(line) = mline {
|
||||
*buff = line;
|
||||
if nchars > 0 && nchars < buff.len() {
|
||||
|
||||
@@ -9,13 +9,10 @@
|
||||
self, alt, canonicalize_control_char, canonicalize_keyed_control_char, char_to_symbol, ctrl,
|
||||
function_key, shift, Key, Modifiers, ViewportPosition,
|
||||
};
|
||||
use crate::reader::reader_current_data;
|
||||
use crate::reader::reader_test_and_clear_interrupted;
|
||||
use crate::terminal::{
|
||||
Capability, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE,
|
||||
};
|
||||
use crate::terminal::{Capability, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE};
|
||||
use crate::threads::iothread_port;
|
||||
use crate::tty_handoff::set_tty_protocols_active;
|
||||
use crate::tty_handoff::set_kitty_keyboard_capability;
|
||||
use crate::universal_notifier::default_notifier;
|
||||
use crate::wchar::{encode_byte_to_char, prelude::*};
|
||||
use crate::wutil::encoding::{mbrtowc, mbstate_t, zero_mbstate};
|
||||
@@ -648,22 +645,6 @@ fn parse_mask(mask: u32) -> (Modifiers, bool) {
|
||||
(modifiers, caps_lock)
|
||||
}
|
||||
|
||||
// Trampolines to enable or disable terminal protocols.
|
||||
pub fn terminal_protocols_enable_ifn() {
|
||||
if set_tty_protocols_active(true) {
|
||||
// Our tty has been modified, so save the current timestamps so that
|
||||
// the reader doesn't believe it has to repaint the prompt.
|
||||
reader_current_data().map(|data| data.save_screen_state());
|
||||
}
|
||||
}
|
||||
pub fn terminal_protocols_disable_ifn() {
|
||||
if set_tty_protocols_active(false) {
|
||||
// Our tty has been modified, so save the current timestamps so that
|
||||
// the reader doesn't believe it has to repaint the prompt.
|
||||
reader_current_data().map(|data| data.save_screen_state());
|
||||
}
|
||||
}
|
||||
|
||||
// A data type used by the input machinery.
|
||||
pub struct InputData {
|
||||
// The file descriptor from which we read input, often stdin.
|
||||
@@ -1187,7 +1168,7 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
|
||||
reader,
|
||||
"Received kitty progressive enhancement flags, marking as supported"
|
||||
);
|
||||
KITTY_KEYBOARD_SUPPORTED.store(Capability::Supported as _, Ordering::Release);
|
||||
set_kitty_keyboard_capability(Capability::Supported);
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1421,7 +1402,6 @@ fn readch_timed(&mut self, wait_time_ms: usize) -> Option<CharEvent> {
|
||||
if let Some(evt) = self.try_pop() {
|
||||
return Some(evt);
|
||||
}
|
||||
terminal_protocols_enable_ifn();
|
||||
|
||||
// We are not prepared to handle a signal immediately; we only want to know if we get input on
|
||||
// our fd before the timeout. Use pselect to block all signals; we will handle signals
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
};
|
||||
use crate::fds::{open_dir, BEST_O_SEARCH};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input_common::{terminal_protocols_disable_ifn, TerminalQuery};
|
||||
use crate::input_common::TerminalQuery;
|
||||
use crate::io::IoChain;
|
||||
use crate::job_group::MaybeJobId;
|
||||
use crate::operation_context::{OperationContext, EXPANSION_LIMIT_DEFAULT};
|
||||
@@ -681,8 +681,6 @@ pub fn eval_node<T: Node>(
|
||||
// Create a new execution context.
|
||||
let mut execution_context = ExecutionContext::new(ps, block_io.clone(), &self.line_counter);
|
||||
|
||||
terminal_protocols_disable_ifn();
|
||||
|
||||
// Check the exec count so we know if anything got executed.
|
||||
let prev_exec_count = self.libdata().exec_count;
|
||||
let prev_status_count = self.libdata().status_count;
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
//! When the user searches forward, i.e. presses Alt-down, the list is consulted for previous search
|
||||
//! result, and subsequent backwards searches are also handled by consulting the list up until the
|
||||
//! end of the list is reached, at which point regular searching will commence.
|
||||
//!
|
||||
//! In general interactive reads work with the tty protocols (CSI-U, etc) enabled; these are disabled
|
||||
//! before calling out to fish script, wildcards, or completions. Note CSI-U protocol prevents
|
||||
//! control-C from generating SIGINT, so failing to disable these would prevent cancellation of wildcard
|
||||
//! expansion, etc.
|
||||
|
||||
use libc::{
|
||||
c_char, ECHO, EINTR, EIO, EISDIR, ENOTTY, EPERM, ESRCH, ICANON, ICRNL, IEXTEN, INLCR, IXOFF,
|
||||
@@ -81,14 +86,9 @@
|
||||
SearchType,
|
||||
};
|
||||
use crate::input::init_input;
|
||||
use crate::input_common::stop_query;
|
||||
use crate::input_common::terminal_protocols_disable_ifn;
|
||||
use crate::input_common::CursorPositionQuery;
|
||||
use crate::input_common::ImplicitEvent;
|
||||
use crate::input_common::QueryResponseEvent;
|
||||
use crate::input_common::TerminalQuery;
|
||||
use crate::input_common::{
|
||||
terminal_protocols_enable_ifn, CharEvent, CharInputStyle, InputData, ReadlineCmd,
|
||||
stop_query, CharEvent, CharInputStyle, CursorPositionQuery, ImplicitEvent, InputData,
|
||||
QueryResponseEvent, ReadlineCmd, TerminalQuery,
|
||||
};
|
||||
use crate::io::IoChain;
|
||||
use crate::key::ViewportPosition;
|
||||
@@ -131,9 +131,7 @@
|
||||
QueryCursorPosition, QueryKittyKeyboardProgressiveEnhancements, QueryPrimaryDeviceAttribute,
|
||||
QueryXtgettcap, QueryXtversion,
|
||||
};
|
||||
use crate::terminal::{
|
||||
Capability, KITTY_KEYBOARD_SUPPORTED, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE,
|
||||
};
|
||||
use crate::terminal::{Capability, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE};
|
||||
use crate::termsize::{termsize_invalidate_tty, termsize_last, termsize_update};
|
||||
use crate::text_face::parse_text_face;
|
||||
use crate::text_face::TextFace;
|
||||
@@ -147,7 +145,10 @@
|
||||
tok_command, MoveWordStateMachine, MoveWordStyle, TokenType, Tokenizer, TOK_ACCEPT_UNFINISHED,
|
||||
TOK_SHOW_COMMENTS,
|
||||
};
|
||||
use crate::tty_handoff::{initialize_tty_metadata, tty_metadata};
|
||||
use crate::tty_handoff::{
|
||||
get_kitty_keyboard_capability, get_tty_protocols_active, initialize_tty_metadata,
|
||||
safe_deactivate_tty_protocols, set_kitty_keyboard_capability, tty_metadata, TtyHandoff,
|
||||
};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wcstringutil::string_prefixes_string_maybe_case_insensitive;
|
||||
use crate::wcstringutil::{
|
||||
@@ -712,6 +713,11 @@ fn read_i(parser: &Parser) {
|
||||
let mut data = reader_push(parser, &history_session_id(parser.vars()), conf);
|
||||
data.import_history_if_necessary();
|
||||
|
||||
// Set up tty protocols. These should be enabled while we're reading interactively,
|
||||
// and disabled before we run fish script, wildcards, or completions. This is scoped.
|
||||
// Note this may be disabled within the loop, e.g. when running fish script bound to keys.
|
||||
let mut tty = TtyHandoff::new();
|
||||
|
||||
while !check_exit_loop_maybe_warning(Some(&mut data)) {
|
||||
RUN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
@@ -723,6 +729,8 @@ fn read_i(parser: &Parser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Got a command. Disable tty protocols while we execute it.
|
||||
tty.disable_tty_protocols();
|
||||
data.clear(EditableLineTag::Commandline);
|
||||
data.update_buff_pos(EditableLineTag::Commandline, None);
|
||||
BufferedOutputter::new(Outputter::stdoutput()).write_command(Osc133CommandStart(&command));
|
||||
@@ -758,7 +766,8 @@ fn read_i(parser: &Parser) {
|
||||
}
|
||||
reader_pop();
|
||||
|
||||
// If we got SIGHUP, ensure the tty is redirected.
|
||||
// If we got SIGHUP, ensure the tty is redirected and release tty handoff without
|
||||
// trying to muck with protocols.
|
||||
if reader_received_sighup() {
|
||||
// If we are the top-level reader, then we translate SIGHUP into exit_forced.
|
||||
redirect_tty_after_sighup();
|
||||
@@ -892,11 +901,9 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pca): this is run in our "AT_EXIT" handler from a SIGTERM handler.
|
||||
// It must be made async-signal-safe (or not invoked).
|
||||
pub fn reader_deinit(restore_foreground_pgroup: bool) {
|
||||
safe_restore_term_mode();
|
||||
crate::input_common::terminal_protocols_disable_ifn();
|
||||
safe_deactivate_tty_protocols();
|
||||
if restore_foreground_pgroup {
|
||||
restore_term_foreground_process_group_for_exit();
|
||||
}
|
||||
@@ -2175,6 +2182,8 @@ impl<'a> Reader<'a> {
|
||||
/// Read a command to execute, respecting input bindings.
|
||||
/// Return the command, or none if we were asked to cancel (e.g. SIGHUP).
|
||||
fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
let mut tty = TtyHandoff::new();
|
||||
|
||||
self.rls = Some(ReadlineLoopState::new());
|
||||
|
||||
// Suppress fish_trace during executing key bindings.
|
||||
@@ -2242,11 +2251,18 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
self.force_exec_prompt_and_repaint = true;
|
||||
|
||||
while !self.rls().finished && !check_exit_loop_maybe_warning(Some(self)) {
|
||||
// Enable tty protocols while we read input.
|
||||
tty.enable_tty_protocols();
|
||||
if self.handle_char_event(None).is_break() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable tty protocols now that we're going to execute a command.
|
||||
if tty.disable_tty_protocols() {
|
||||
self.save_screen_state();
|
||||
}
|
||||
|
||||
if self.conf.transient_prompt {
|
||||
self.exec_prompt(true, true);
|
||||
}
|
||||
@@ -2306,10 +2322,16 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
fn eval_bind_cmd(&mut self, cmd: &wstr) {
|
||||
let last_statuses = self.parser.vars().get_last_statuses();
|
||||
let prev_exec_external_count = self.parser.libdata().exec_external_count;
|
||||
// Disable TTY protocols while we run a bind command, because it may call out.
|
||||
let mut scoped_tty = TtyHandoff::new();
|
||||
let mut modified_tty = scoped_tty.disable_tty_protocols();
|
||||
|
||||
self.parser.eval(cmd, &IoChain::new());
|
||||
self.parser.set_last_statuses(last_statuses);
|
||||
if self.parser.libdata().exec_external_count != prev_exec_external_count
|
||||
&& self.data.left_prompt_buff.contains('\n')
|
||||
modified_tty |= scoped_tty.reclaim();
|
||||
if modified_tty
|
||||
|| (self.parser.libdata().exec_external_count != prev_exec_external_count
|
||||
&& self.data.left_prompt_buff.contains('\n'))
|
||||
{
|
||||
self.save_screen_state();
|
||||
}
|
||||
@@ -2351,7 +2373,6 @@ fn read_normal_chars(&mut self) -> Option<CharEvent> {
|
||||
let mut accumulated_chars = WString::new();
|
||||
|
||||
while accumulated_chars.len() < limit {
|
||||
terminal_protocols_enable_ifn();
|
||||
let evt = self.read_char();
|
||||
let CharEvent::Key(kevt) = &evt else {
|
||||
event_needing_handling = Some(evt);
|
||||
@@ -2545,11 +2566,11 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
// Rogue reply.
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
if KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed)
|
||||
== Capability::Unknown as _
|
||||
{
|
||||
KITTY_KEYBOARD_SUPPORTED
|
||||
.store(Capability::NotSupported as _, Ordering::Release);
|
||||
if get_kitty_keyboard_capability() == Capability::Unknown {
|
||||
set_kitty_keyboard_capability(Capability::NotSupported);
|
||||
// We may have written to the tty, so save the screen state
|
||||
// so we don't repaint.
|
||||
self.screen.save_status();
|
||||
}
|
||||
}
|
||||
QueryResponseEvent::CursorPositionReport(cursor_pos) => {
|
||||
@@ -2796,7 +2817,12 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
}
|
||||
} else {
|
||||
// Either the user hit tab only once, or we had no visible completion list.
|
||||
// Disable tty protocols while we compute completions, so that control-C
|
||||
// triggers SIGINT (suppressed by CSI-U).
|
||||
let mut tty = TtyHandoff::new();
|
||||
tty.disable_tty_protocols();
|
||||
self.compute_and_apply_completions(c);
|
||||
tty.reclaim();
|
||||
}
|
||||
}
|
||||
rl::PagerToggleSearch => {
|
||||
@@ -4587,6 +4613,10 @@ fn exec_prompt(&mut self, full_prompt: bool, final_prompt: bool) {
|
||||
// Prompts must be run non-interactively.
|
||||
let _noninteractive = self.parser.push_scope(|s| s.is_interactive = false);
|
||||
|
||||
// Suppress TTY protocols in a scoped way so that e.g. control-C can cancel the prompt.
|
||||
let mut scoped_tty = TtyHandoff::new();
|
||||
scoped_tty.disable_tty_protocols();
|
||||
|
||||
// Update the termsize now.
|
||||
// This allows prompts to react to $COLUMNS.
|
||||
self.update_termsize();
|
||||
@@ -5680,6 +5710,10 @@ fn check_for_orphaned_process(loop_count: usize, shell_pgid: libc::pid_t) -> boo
|
||||
/// Run the specified command with the correct terminal modes, and while taking care to perform job
|
||||
/// notification, set the title, etc.
|
||||
fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes {
|
||||
assert!(
|
||||
!get_tty_protocols_active(),
|
||||
"TTY protocols should not be active"
|
||||
);
|
||||
let ft = tok_command(cmd);
|
||||
|
||||
// Provide values for `status current-command` and `status current-commandline`
|
||||
@@ -6232,6 +6266,10 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
||||
c,
|
||||
ReadlineCmd::Complete | ReadlineCmd::CompleteAndSearch
|
||||
));
|
||||
assert!(
|
||||
!get_tty_protocols_active(),
|
||||
"should not be called with TTY protocols active"
|
||||
);
|
||||
|
||||
// Remove a trailing backslash. This may trigger an extra repaint, but this is
|
||||
// rare.
|
||||
@@ -6261,9 +6299,6 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
|
||||
token_range.start += cmdsub_range.start;
|
||||
token_range.end += cmdsub_range.start;
|
||||
|
||||
// Wildcard expansion and completion below check for cancellation.
|
||||
terminal_protocols_disable_ifn();
|
||||
|
||||
// Check if we have a wildcard within this string; if so we first attempt to expand the
|
||||
// wildcard; if that succeeds we don't then apply user completions (#8593).
|
||||
let mut wc_expanded = WString::new();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use crate::reader::{reader_handle_sigint, reader_sighup, safe_restore_term_mode};
|
||||
use crate::termsize::TermsizeContainer;
|
||||
use crate::topic_monitor::{topic_monitor_principal, Generation, GenerationsList, Topic};
|
||||
use crate::tty_handoff::{safe_deactivate_tty_protocols, safe_mark_tty_invalid};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wutil::{fish_wcstoi, perror};
|
||||
use errno::{errno, set_errno};
|
||||
@@ -83,13 +84,15 @@ extern "C" fn fish_signal_handler(
|
||||
// Exit unless the signal was trapped.
|
||||
if !observed {
|
||||
reader_sighup();
|
||||
safe_mark_tty_invalid();
|
||||
}
|
||||
topic_monitor_principal().post(Topic::sighupint);
|
||||
}
|
||||
libc::SIGTERM => {
|
||||
// Handle sigterm. The only thing we do is restore the front process ID, then die.
|
||||
// Handle sigterm. The only thing we do is restore the front process ID and disable protocols, then die.
|
||||
if !observed {
|
||||
safe_restore_term_mode();
|
||||
safe_deactivate_tty_protocols();
|
||||
// Safety: signal() and raise() are async-signal-safe.
|
||||
unsafe {
|
||||
libc::signal(libc::SIGTERM, libc::SIG_DFL);
|
||||
|
||||
@@ -216,15 +216,14 @@ fn maybe_terminfo(
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum Capability {
|
||||
pub enum Capability {
|
||||
Unknown,
|
||||
Supported,
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
pub(crate) static KITTY_KEYBOARD_SUPPORTED: AtomicU8 = AtomicU8::new(Capability::Unknown as _);
|
||||
|
||||
pub(crate) static SCROLL_FORWARD_SUPPORTED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
pub(crate) static SCROLL_FORWARD_TERMINFO_CODE: &str = "indn";
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
KittyKeyboardProgressiveEnhancementsDisable, KittyKeyboardProgressiveEnhancementsEnable,
|
||||
ModifyOtherKeysDisable, ModifyOtherKeysEnable,
|
||||
};
|
||||
use crate::terminal::{Capability, Output, Outputter, KITTY_KEYBOARD_SUPPORTED};
|
||||
use crate::terminal::{Capability, Output, Outputter};
|
||||
use crate::threads::assert_is_main_thread;
|
||||
use crate::wchar_ext::ToWString;
|
||||
use crate::wutil::perror;
|
||||
use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||
use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering};
|
||||
|
||||
// Facts about our environment, which inform how we handle the tty.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -60,6 +60,35 @@ fn detect() -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
// Whether CSI-U ("Kitty") support is present in the TTY.
|
||||
static KITTY_KEYBOARD_SUPPORTED: AtomicU8 = AtomicU8::new(Capability::Unknown as _);
|
||||
|
||||
// Get the support capability for CSI-U ("Kitty") protocols.
|
||||
pub fn get_kitty_keyboard_capability() -> Capability {
|
||||
let cap = KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed);
|
||||
match cap {
|
||||
x if x == Capability::Supported as _ => Capability::Supported,
|
||||
x if x == Capability::NotSupported as _ => Capability::NotSupported,
|
||||
_ => Capability::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
// Set CSI-U ("Kitty") support capability.
|
||||
// This correctly handles the case where we think protocols are already enabled.
|
||||
pub fn set_kitty_keyboard_capability(cap: Capability) {
|
||||
assert_is_main_thread();
|
||||
// Disable and renable protocols around capabilities.
|
||||
let mut tty = TtyHandoff::new();
|
||||
tty.disable_tty_protocols();
|
||||
KITTY_KEYBOARD_SUPPORTED.store(cap as _, Ordering::Relaxed);
|
||||
FLOG!(
|
||||
term_protocols,
|
||||
"Set Kitty keyboard capability to",
|
||||
format!("{:?}", cap)
|
||||
);
|
||||
tty.reclaim();
|
||||
}
|
||||
|
||||
// Helper to determine which keyboard protocols to enable.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum ProtocolKind {
|
||||
@@ -215,10 +244,13 @@ pub fn initialize_tty_metadata() {
|
||||
// A marker of the current state of the tty protocols.
|
||||
static TTY_PROTOCOLS_ACTIVE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
// A marker that the tty has been closed (SIGHUP, etc) and so we should not try to write to it.
|
||||
static TTY_INVALID: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
// Enable or disable TTY protocols by writing the appropriate commands to the tty.
|
||||
// Return true if we emitted any bytes to the tty.
|
||||
// Note this does NOT intialize the TTY protocls if not already initialized.
|
||||
pub fn set_tty_protocols_active(enable: bool) -> bool {
|
||||
fn set_tty_protocols_active(enable: bool) -> bool {
|
||||
assert_is_main_thread();
|
||||
// Have protocols at all? We require someone else to have initialized them.
|
||||
let Some(protocols) = tty_protocols() else {
|
||||
@@ -226,11 +258,17 @@ pub fn set_tty_protocols_active(enable: bool) -> bool {
|
||||
};
|
||||
// Already set?
|
||||
// Note we don't need atomic swaps as this is only called on the main thread.
|
||||
// Also note we (logically) set and clear this even if we got SIGHUP.
|
||||
if TTY_PROTOCOLS_ACTIVE.load() == enable {
|
||||
return false;
|
||||
}
|
||||
TTY_PROTOCOLS_ACTIVE.store(enable);
|
||||
|
||||
// Did we get SIGHUP?
|
||||
if TTY_INVALID.load() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the commands to the tty, ignoring errors.
|
||||
let commands = protocols.safe_get_commands(enable);
|
||||
let _ = common::write_loop(&libc::STDOUT_FILENO, commands);
|
||||
@@ -259,10 +297,15 @@ pub fn safe_deactivate_tty_protocols() {
|
||||
// No protocols set, nothing to do.
|
||||
return;
|
||||
};
|
||||
|
||||
if !TTY_PROTOCOLS_ACTIVE.load() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Did we get SIGHUP?
|
||||
if TTY_INVALID.load() {
|
||||
return;
|
||||
}
|
||||
|
||||
TTY_PROTOCOLS_ACTIVE.store(false);
|
||||
let commands = protocols.safe_get_commands(false);
|
||||
// Safety: just writing data to stdout.
|
||||
@@ -270,6 +313,12 @@ pub fn safe_deactivate_tty_protocols() {
|
||||
let _ = nix::unistd::write(stdout_fd, commands);
|
||||
}
|
||||
|
||||
// Called from a signal handler to mark the tty as invalid (e.g. SIGHUP).
|
||||
// This suppresses any further attempts to write protocols to the tty,
|
||||
pub fn safe_mark_tty_invalid() {
|
||||
TTY_INVALID.store(true);
|
||||
}
|
||||
|
||||
// Allows transferring the tty to a job group, while it runs, in a scoped fashion.
|
||||
// This has several responsibilities:
|
||||
// - Invoking tcsetpgrp() to transfer the tty to the job group.
|
||||
|
||||
Reference in New Issue
Block a user