From b45e33819709f92f9ea6e547445d47ab311f91cb Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Fri, 22 Jul 2022 17:00:41 -0400 Subject: [PATCH] Emit focus events --- examples/event-read.rs | 11 +++++---- src/event.rs | 45 +++++++++++++++++++++++++++++++++++++ src/event/source/windows.rs | 8 +++++++ src/event/sys/unix/parse.rs | 10 +++++++++ src/style.rs | 1 - 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/examples/event-read.rs b/examples/event-read.rs index 7de5f42ca..2a32cefba 100644 --- a/examples/event-read.rs +++ b/examples/event-read.rs @@ -7,7 +7,10 @@ use std::io::stdout; use crossterm::event::poll; use crossterm::{ cursor::position, - event::{read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, + event::{ + read, DisableFocusChange, DisableMouseCapture, EnableFocusChange, EnableMouseCapture, + Event, KeyCode, + }, execute, terminal::{disable_raw_mode, enable_raw_mode}, Result, @@ -15,7 +18,7 @@ use crossterm::{ use std::time::Duration; const HELP: &str = r#"Blocking read() - - Keyboard, mouse and terminal resize events enabled + - Keyboard, mouse, focus and terminal resize events enabled - Hit "c" to print current cursor position - Use Esc to quit "#; @@ -67,13 +70,13 @@ fn main() -> Result<()> { enable_raw_mode()?; let mut stdout = stdout(); - execute!(stdout, EnableMouseCapture)?; + execute!(stdout, EnableFocusChange, EnableMouseCapture)?; if let Err(e) = print_events() { println!("Error: {:?}\r", e); } - execute!(stdout, DisableMouseCapture)?; + execute!(stdout, DisableFocusChange, DisableMouseCapture)?; disable_raw_mode() } diff --git a/src/event.rs b/src/event.rs index accc19c1d..070e6e2c6 100644 --- a/src/event.rs +++ b/src/event.rs @@ -34,6 +34,8 @@ //! loop { //! // `read()` blocks until an `Event` is available //! match read()? { +//! Event::FocusGained => println!("FocusGained"), +//! Event::FocusLost => println!("FocusLost"), //! Event::Key(event) => println!("{:?}", event), //! Event::Mouse(event) => println!("{:?}", event), //! Event::Resize(width, height) => println!("New size {}x{}", width, height), @@ -57,6 +59,8 @@ //! // It's guaranteed that the `read()` won't block when the `poll()` //! // function returns `true` //! match read()? { +//! Event::FocusGained => println!("FocusGained"), +//! Event::FocusLost => println!("FocusLost"), //! Event::Key(event) => println!("{:?}", event), //! Event::Mouse(event) => println!("{:?}", event), //! Event::Resize(width, height) => println!("New size {}x{}", width, height), @@ -74,6 +78,7 @@ use std::fmt; use std::hash::{Hash, Hasher}; +#[cfg(windows)] use std::io; use std::time::Duration; @@ -409,10 +414,50 @@ impl Command for PopKeyboardEnhancementFlags { } } +/// A command that enables focus event emission. +/// +/// Focus events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EnableFocusChange; + +impl Command for EnableFocusChange { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(csi!("?1004h")) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + // Focus events are always enabled on Windows + Ok(()) + } +} + +/// A command that disables focus event emission. +/// +/// Focus events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DisableFocusChange; + +impl Command for DisableFocusChange { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + f.write_str(csi!("?1004l")) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + // Focus events can't be disabled on Windows + Ok(()) + } +} + /// Represents an event. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] pub enum Event { + /// The terminal gained focus + FocusGained, + /// The terminal lost focus + FocusLost, /// A single key event with additional pressed modifiers. Key(KeyEvent), /// A single mouse event with additional pressed modifiers. diff --git a/src/event/source/windows.rs b/src/event/source/windows.rs index 669272d48..752f75347 100644 --- a/src/event/source/windows.rs +++ b/src/event/source/windows.rs @@ -66,6 +66,14 @@ impl EventSource for WindowsEventSource { InputRecord::WindowBufferSizeEvent(record) => { Some(Event::Resize(record.size.x as u16, record.size.y as u16)) } + InputRecord::FocusEvent(record) => { + let event = if record.set_focus { + Event::FocusGained + } else { + Event::FocusLost + }; + Some(event) + } _ => None, }; diff --git a/src/event/sys/unix/parse.rs b/src/event/sys/unix/parse.rs index 4e127cac8..89cfeb537 100644 --- a/src/event/sys/unix/parse.rs +++ b/src/event/sys/unix/parse.rs @@ -167,6 +167,8 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result> { })), b'M' => return parse_csi_normal_mouse(buffer), b'<' => return parse_csi_sgr_mouse(buffer), + b'I' => Some(Event::FocusGained), + b'O' => Some(Event::FocusLost), b'0'..=b'9' => { // Numbered escape code. if buffer.len() == 3 { @@ -745,6 +747,14 @@ mod tests { ); } + #[test] + fn test_parse_csi_focus() { + assert_eq!( + parse_csi(b"\x1B[O").unwrap(), + Some(InternalEvent::Event(Event::FocusLost)) + ); + } + #[test] fn test_parse_csi_rxvt_mouse() { assert_eq!( diff --git a/src/style.rs b/src/style.rs index f4cd55433..c8865243a 100644 --- a/src/style.rs +++ b/src/style.rs @@ -350,7 +350,6 @@ pub struct SetStyle(pub ContentStyle); impl Command for SetStyle { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - if let Some(bg) = self.0.background_color { execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?; }