Skip to content

Commit 2d837eb

Browse files
committed
Add terminal::supports_synchronized_output
This checks whether the terminal supports the synchronized outputs feature according to https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036#feature-detection
1 parent bb11b97 commit 2d837eb

File tree

7 files changed

+148
-1
lines changed

7 files changed

+148
-1
lines changed

src/event.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,20 @@ bitflags! {
312312
}
313313
}
314314

315+
/// The terminal's current mode of the synchronized output feature.
316+
///
317+
/// <https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036#feature-detection>
318+
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
319+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
320+
pub enum SynchronizedOutputMode {
321+
/// Screen updates are not shown to the user until mode is disabled.
322+
Set,
323+
/// Screen updates are shown as usual (e.g. as soon as they arrive).
324+
Reset,
325+
/// The terminal does not support synchronized output sequences.
326+
NotSupported,
327+
}
328+
315329
/// A command that enables mouse event capturing.
316330
///
317331
/// Mouse events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html).
@@ -1538,6 +1552,9 @@ pub(crate) enum InternalEvent {
15381552
/// The progressive keyboard enhancement flags enabled by the terminal.
15391553
#[cfg(unix)]
15401554
KeyboardEnhancementFlags(KeyboardEnhancementFlags),
1555+
/// The terminal's current mode of the synchronized output feature.
1556+
#[cfg(unix)]
1557+
SynchronizedOutputMode(SynchronizedOutputMode),
15411558
/// Attributes and architectural class of the terminal.
15421559
#[cfg(unix)]
15431560
PrimaryDeviceAttributes,

src/event/filter.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ impl Filter for ThemeModeFilter {
6464
}
6565
}
6666

67+
#[cfg(unix)]
68+
#[derive(Debug, Clone)]
69+
pub(crate) struct SynchronizedOutputModeFilter;
70+
71+
#[cfg(unix)]
72+
impl Filter for SynchronizedOutputModeFilter {
73+
fn eval(&self, event: &InternalEvent) -> bool {
74+
// See `KeyboardEnhancementFlagsFilter` above: `PrimaryDeviceAttributes` is
75+
// used to elicit a response from the terminal even if it doesn't support the
76+
// synchronized output mode query.
77+
matches!(
78+
*event,
79+
InternalEvent::SynchronizedOutputMode(_) | InternalEvent::PrimaryDeviceAttributes
80+
)
81+
}
82+
}
83+
6784
#[derive(Debug, Clone)]
6885
pub(crate) struct EventFilter;
6986

src/event/sys/unix/parse.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use std::io;
22

33
use crate::event::{
44
Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyboardEnhancementFlags,
5-
MediaKeyCode, ModifierKeyCode, MouseButton, MouseEvent, MouseEventKind, ThemeMode,
5+
MediaKeyCode, ModifierKeyCode, MouseButton, MouseEvent, MouseEventKind, SynchronizedOutputMode,
6+
ThemeMode,
67
};
78

89
use super::super::super::InternalEvent;
@@ -181,6 +182,7 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> io::Result<Option<InternalEvent>> {
181182
b'u' => return parse_csi_keyboard_enhancement_flags(buffer),
182183
b'c' => return parse_csi_primary_device_attributes(buffer),
183184
b'n' => return parse_csi_theme_mode(buffer),
185+
b'y' => return parse_csi_synchronized_output_mode(buffer),
184186
_ => None,
185187
},
186188
b'0'..=b'9' => {
@@ -327,6 +329,36 @@ fn parse_csi_theme_mode(buffer: &[u8]) -> io::Result<Option<InternalEvent>> {
327329
))))
328330
}
329331

332+
fn parse_csi_synchronized_output_mode(buffer: &[u8]) -> io::Result<Option<InternalEvent>> {
333+
// ESC [ ? 2026 ; 0 $ y
334+
assert!(buffer.starts_with(b"\x1B[?"));
335+
assert!(buffer.ends_with(b"y"));
336+
337+
let s = std::str::from_utf8(&buffer[3..buffer.len() - 1])
338+
.map_err(|_| could_not_parse_event_error())?;
339+
let s = match s.strip_suffix('$') {
340+
Some(s) => s,
341+
None => return Ok(None),
342+
};
343+
344+
let mut split = s.split(';');
345+
346+
if next_parsed::<u16>(&mut split)? != 2026 {
347+
return Ok(None);
348+
}
349+
350+
let synchronized_output_mode = match next_parsed::<u8>(&mut split)? {
351+
1 => SynchronizedOutputMode::Set,
352+
2 => SynchronizedOutputMode::Reset,
353+
0 | 4 => SynchronizedOutputMode::NotSupported,
354+
_ => return Ok(None),
355+
};
356+
357+
Ok(Some(InternalEvent::SynchronizedOutputMode(
358+
synchronized_output_mode,
359+
)))
360+
}
361+
330362
fn parse_modifiers(mask: u8) -> KeyModifiers {
331363
let modifier_mask = mask.saturating_sub(1);
332364
let mut modifiers = KeyModifiers::empty();

src/terminal.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +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,
104105
};
105106

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

src/terminal/sys.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +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,
1112
};
1213
#[cfg(all(windows, test))]
1314
pub(crate) use self::windows::temp_screen_buffer;
@@ -20,6 +21,7 @@ pub(crate) use self::windows::{
2021
#[cfg(feature = "events")]
2122
pub use self::windows::{
2223
query_keyboard_enhancement_flags, query_terminal_theme_mode, supports_keyboard_enhancement,
24+
supports_synchronized_output,
2325
};
2426

2527
#[cfg(windows)]

src/terminal/sys/unix.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,76 @@ fn query_terminal_theme_mode_raw() -> io::Result<Option<ThemeMode>> {
245245
}
246246
}
247247

248+
#[cfg(feature = "events")]
249+
fn supports_synchronized_output_raw() -> io::Result<bool> {
250+
use crate::event::{
251+
filter::{PrimaryDeviceAttributesFilter, SynchronizedOutputModeFilter},
252+
poll_internal, read_internal, InternalEvent, SynchronizedOutputMode,
253+
};
254+
use std::io::Write;
255+
use std::time::Duration;
256+
257+
// ESC [ ? 2026 $ p DECRQM request for synchronized output state
258+
// ESC [ c Query primary device attributes (widely supported)
259+
const QUERY: &[u8] = b"\x1B[?2026$p\x1B[c";
260+
261+
let result = File::open("/dev/tty").and_then(|mut file| {
262+
file.write_all(QUERY)?;
263+
file.flush()
264+
});
265+
if result.is_err() {
266+
let mut stdout = io::stdout();
267+
stdout.write_all(QUERY)?;
268+
stdout.flush()?;
269+
}
270+
271+
loop {
272+
match poll_internal(
273+
Some(Duration::from_millis(2000)),
274+
&SynchronizedOutputModeFilter,
275+
) {
276+
Ok(true) => match read_internal(&SynchronizedOutputModeFilter) {
277+
Ok(InternalEvent::SynchronizedOutputMode(
278+
SynchronizedOutputMode::Set | SynchronizedOutputMode::Reset,
279+
)) => {
280+
// Flush the PrimaryDeviceAttributes out of the event queue.
281+
read_internal(&PrimaryDeviceAttributesFilter).ok();
282+
return Ok(true);
283+
}
284+
_ => return Ok(false),
285+
},
286+
Ok(false) => {
287+
return Err(io::Error::new(
288+
io::ErrorKind::Other,
289+
"Synchronized output mode could not be read in a normal duration",
290+
));
291+
}
292+
Err(_) => {}
293+
}
294+
}
295+
}
296+
297+
#[cfg(feature = "events")]
298+
fn supports_synchronized_output_nonraw() -> io::Result<bool> {
299+
enable_raw_mode()?;
300+
let is_supported = supports_synchronized_output_raw();
301+
disable_raw_mode()?;
302+
is_supported
303+
}
304+
305+
/// Queries the terminal's support for synchronized output sequences.
306+
///
307+
/// On unix systems, this function will block and possibly time out while
308+
/// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
309+
#[cfg(feature = "events")]
310+
pub fn supports_synchronized_output() -> io::Result<bool> {
311+
if is_raw_mode_enabled() {
312+
supports_synchronized_output_raw()
313+
} else {
314+
supports_synchronized_output_nonraw()
315+
}
316+
}
317+
248318
/// Queries the terminal's support for progressive keyboard enhancement.
249319
///
250320
/// On unix systems, this function will block and possibly time out while

src/terminal/sys/windows.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ pub(crate) fn window_size() -> io::Result<WindowSize> {
7070
))
7171
}
7272

73+
/// Queries the terminal's support for synchronized output sequences.
74+
///
75+
/// This always returns `Ok(false)` on Windows.
76+
#[cfg(feature = "events")]
77+
pub fn supports_synchronized_output() -> io::Result<bool> {
78+
Ok(false)
79+
}
80+
7381
/// Queries the terminal's support for progressive keyboard enhancement.
7482
///
7583
/// This always returns `Ok(false)` on Windows.

0 commit comments

Comments
 (0)