Skip to content

Commit dfae0c2

Browse files
committed
Support querying the state of all VT extension features at once
Querying for each feature individually can cause some latency. This change introduces a struct with fields for all VT extension features and a function to query all at once.
1 parent 2d837eb commit dfae0c2

File tree

6 files changed

+117
-5
lines changed

6 files changed

+117
-5
lines changed

src/event.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,6 +1573,17 @@ pub enum ThemeMode {
15731573
Dark,
15741574
}
15751575

1576+
/// The current state of features supported by the terminal.
1577+
///
1578+
/// This can be queried with [terminal::terminal_features].
1579+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1580+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1581+
pub struct TerminalFeatures {
1582+
pub keyboard_enhancement_flags: Option<KeyboardEnhancementFlags>,
1583+
pub synchronized_output_mode: Option<SynchronizedOutputMode>,
1584+
pub theme_mode: Option<ThemeMode>,
1585+
}
1586+
15761587
#[cfg(test)]
15771588
mod tests {
15781589
use std::collections::hash_map::DefaultHasher;

src/event/filter.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ impl Filter for SynchronizedOutputModeFilter {
8181
}
8282
}
8383

84+
#[cfg(unix)]
85+
#[derive(Debug, Clone)]
86+
pub(crate) struct TerminalFeaturesFilter;
87+
88+
#[cfg(unix)]
89+
impl Filter for TerminalFeaturesFilter {
90+
fn eval(&self, event: &InternalEvent) -> bool {
91+
// See `KeyboardEnhancementFlagsFilter` above: `PrimaryDeviceAttributes` is
92+
// used to elicit a response from the terminal even if it doesn't support the
93+
// synchronized output mode query.
94+
matches!(
95+
*event,
96+
InternalEvent::KeyboardEnhancementFlags(_)
97+
| InternalEvent::Event(Event::ThemeModeChanged(_))
98+
| InternalEvent::SynchronizedOutputMode(_)
99+
| InternalEvent::PrimaryDeviceAttributes
100+
)
101+
}
102+
}
103+
84104
#[derive(Debug, Clone)]
85105
pub(crate) struct EventFilter;
86106

src/terminal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub(crate) mod sys;
101101
#[cfg(feature = "events")]
102102
pub use sys::{
103103
query_keyboard_enhancement_flags, query_terminal_theme_mode, supports_keyboard_enhancement,
104-
supports_synchronized_output,
104+
supports_synchronized_output, terminal_features,
105105
};
106106

107107
/// Tells whether the raw mode is enabled.

src/terminal/sys.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub(crate) use self::unix::{
88
#[cfg(feature = "events")]
99
pub use self::unix::{
1010
query_keyboard_enhancement_flags, query_terminal_theme_mode, supports_keyboard_enhancement,
11-
supports_synchronized_output,
11+
supports_synchronized_output, terminal_features,
1212
};
1313
#[cfg(all(windows, test))]
1414
pub(crate) use self::windows::temp_screen_buffer;
@@ -21,7 +21,7 @@ pub(crate) use self::windows::{
2121
#[cfg(feature = "events")]
2222
pub use self::windows::{
2323
query_keyboard_enhancement_flags, query_terminal_theme_mode, supports_keyboard_enhancement,
24-
supports_synchronized_output,
24+
supports_synchronized_output, terminal_features,
2525
};
2626

2727
#[cfg(windows)]

src/terminal/sys/unix.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! UNIX related logic for terminal manipulation.
22
33
#[cfg(feature = "events")]
4-
use crate::event::{KeyboardEnhancementFlags, ThemeMode};
4+
use crate::event::{KeyboardEnhancementFlags, TerminalFeatures, ThemeMode};
55
use crate::terminal::{
66
sys::file_descriptor::{tty_fd, FileDesc},
77
WindowSize,
@@ -401,6 +401,82 @@ fn query_keyboard_enhancement_flags_raw() -> io::Result<Option<KeyboardEnhanceme
401401
}
402402
}
403403

404+
/// Queries information about features that the terminal supports.
405+
///
406+
/// On unix systems, this function will block and possibly time out while
407+
/// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
408+
#[cfg(feature = "events")]
409+
pub fn terminal_features() -> io::Result<TerminalFeatures> {
410+
if is_raw_mode_enabled() {
411+
terminal_features_raw()
412+
} else {
413+
terminal_features_nonraw()
414+
}
415+
}
416+
417+
#[cfg(feature = "events")]
418+
fn terminal_features_nonraw() -> io::Result<TerminalFeatures> {
419+
enable_raw_mode()?;
420+
let features = terminal_features_raw();
421+
disable_raw_mode()?;
422+
features
423+
}
424+
425+
#[cfg(feature = "events")]
426+
fn terminal_features_raw() -> io::Result<TerminalFeatures> {
427+
use crate::event::{
428+
filter::TerminalFeaturesFilter, poll_internal, read_internal, Event, InternalEvent,
429+
};
430+
use std::io::Write;
431+
use std::time::Duration;
432+
433+
// This is the recommended method for testing support for the keyboard enhancement protocol.
434+
// We send a query for the flags supported by the terminal and then the primary device attributes
435+
// query. If we receive the primary device attributes response but not the keyboard enhancement
436+
// flags, none of the flags are supported.
437+
//
438+
// See <https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol>
439+
440+
// ESC [ ? u Query progressive keyboard enhancement flags (kitty protocol).
441+
// ESC [ c Query primary device attributes.
442+
const QUERY: &[u8] = b"\x1B[?u\x1B[c";
443+
444+
let result = File::open("/dev/tty").and_then(|mut file| {
445+
file.write_all(QUERY)?;
446+
file.flush()
447+
});
448+
if result.is_err() {
449+
let mut stdout = io::stdout();
450+
stdout.write_all(QUERY)?;
451+
stdout.flush()?;
452+
}
453+
454+
let mut features = TerminalFeatures::default();
455+
loop {
456+
match poll_internal(Some(Duration::from_millis(2000)), &TerminalFeaturesFilter) {
457+
Ok(true) => match read_internal(&TerminalFeaturesFilter) {
458+
Ok(InternalEvent::KeyboardEnhancementFlags(flags)) => {
459+
features.keyboard_enhancement_flags = Some(flags);
460+
}
461+
Ok(InternalEvent::SynchronizedOutputMode(mode)) => {
462+
features.synchronized_output_mode = Some(mode);
463+
}
464+
Ok(InternalEvent::Event(Event::ThemeModeChanged(theme_mode))) => {
465+
features.theme_mode = Some(theme_mode);
466+
}
467+
_ => return Ok(features),
468+
},
469+
Ok(false) => {
470+
return Err(io::Error::new(
471+
io::ErrorKind::Other,
472+
"Terminal information could not be read within a normal duration",
473+
));
474+
}
475+
Err(_) => {}
476+
}
477+
}
478+
}
479+
404480
/// execute tput with the given argument and parse
405481
/// the output as a u16.
406482
///

src/terminal/sys/windows.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use winapi::{
1010
};
1111

1212
#[cfg(feature = "events")]
13-
use crate::event::{KeyboardEnhancementFlags, ThemeMode};
13+
use crate::event::{KeyboardEnhancementFlags, TerminalFeatures, ThemeMode};
1414
use crate::{
1515
cursor,
1616
terminal::{ClearType, WindowSize},
@@ -102,6 +102,11 @@ pub fn query_terminal_theme_mode() -> io::Result<Option<ThemeMode>> {
102102
Ok(None)
103103
}
104104

105+
#[cfg(feature = "events")]
106+
pub fn terminal_features() -> io::Result<TerminalFeatures> {
107+
Ok(TerminalFeatures::default())
108+
}
109+
105110
pub(crate) fn clear(clear_type: ClearType) -> std::io::Result<()> {
106111
let screen_buffer = ScreenBuffer::current()?;
107112
let csbi = screen_buffer.info()?;

0 commit comments

Comments
 (0)