diff --git a/alacritty.yml b/alacritty.yml index 686cb387..203b8fa1 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -36,6 +36,10 @@ dpi: # Display tabs using this many cells (changes require restart) tabspaces: 8 +# Cursor blinking +cursor_blink_interval: 300 +cursor_blink: true + # When true, bold text is drawn using the bright variant of colors. draw_bold_text_with_bright_colors: true diff --git a/alacritty_macos.yml b/alacritty_macos.yml index 007d3b42..57cc59e3 100644 --- a/alacritty_macos.yml +++ b/alacritty_macos.yml @@ -35,6 +35,10 @@ dpi: # Display tabs using this many cells (changes require restart) tabspaces: 8 +# Cursor blinking +cursor_blink_interval: 300 +cursor_blink: true + # When true, bold text is drawn using the bright variant of colors. draw_bold_text_with_bright_colors: true diff --git a/src/config.rs b/src/config.rs index 7734e7af..0e241b51 100644 --- a/src/config.rs +++ b/src/config.rs @@ -171,6 +171,7 @@ impl<'a> Shell<'a> { } } + /// Top-level config type #[derive(Debug, Deserialize)] pub struct Config { @@ -202,6 +203,11 @@ pub struct Config { #[serde(default)] custom_cursor_colors: bool, + // TODO these should be optional + #[serde(deserialize_with="deserialize_duration_ms")] + cursor_blink_interval: Duration, + cursor_blink: bool, + /// Should draw bold text with brighter colors intead of bold font #[serde(default="true_bool")] draw_bold_text_with_bright_colors: bool, @@ -278,6 +284,8 @@ impl Default for Config { font: Default::default(), render_timer: Default::default(), custom_cursor_colors: false, + cursor_blink: true, + cursor_blink_interval: Duration::from_millis(300), colors: Default::default(), key_bindings: Vec::new(), mouse_bindings: Vec::new(), @@ -1142,6 +1150,14 @@ impl Config { Ok(contents) } + + pub fn cursor_blink_interval(&self) -> Duration { + self.cursor_blink_interval + } + + pub fn blink_enabled(&self) -> bool { + self.cursor_blink + } } /// Window Dimensions diff --git a/src/event_loop.rs b/src/event_loop.rs index c744dda0..4ccf4248 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -7,11 +7,13 @@ use std::os::unix::io::AsRawFd; use std::sync::Arc; use mio::{self, Events, PollOpt, Ready}; +use mio::timer::{self, Timer, Timeout}; use mio::unix::EventedFd; use ansi; use display; use event; +use config::Config; use term::Term; use util::thread; use sync::FairMutex; @@ -23,7 +25,11 @@ pub enum Msg { Input(Cow<'static, [u8]>), /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down - Shutdown + Shutdown, + + /// Enable or disable blinking events. Passing true will enable them, and + /// false will disable them. + Blink(bool), } /// The main event!.. loop. @@ -35,6 +41,8 @@ pub struct EventLoop { pty: Io, rx: mio::channel::Receiver, tx: mio::channel::Sender, + timer: Timer<()>, + blink_timeout: Option, terminal: Arc>, display: display::Notifier, ref_test: bool, @@ -171,20 +179,41 @@ const CHANNEL: mio::Token = mio::Token(0); /// `mio::Token` for the pty file descriptor const PTY: mio::Token = mio::Token(1); +/// `mio::Token` for timers +const TIMER: mio::Token = mio::Token(2); + impl EventLoop where Io: io::Read + io::Write + Send + AsRawFd + 'static { /// Create a new event loop pub fn new( terminal: Arc>, + config: &Config, display: display::Notifier, pty: Io, ref_test: bool, ) -> EventLoop { let (tx, rx) = ::mio::channel::channel(); + let mut timer = timer::Builder::default() + .capacity(2) + .num_slots(2) + .build(); + let timeout = { + let term = terminal.lock(); + if term.mode().contains(::term::mode::CURSOR_BLINK) { + println!("setup blink"); + let timeout = timer.set_timeout(term.cursor_blink_interval, ()).unwrap(); + Some(timeout) + } else { + println!("no setup blink"); + None + } + }; EventLoop { poll: mio::Poll::new().expect("create mio Poll"), pty: pty, + timer: timer, + blink_timeout: timeout, tx: tx, rx: rx, terminal: terminal, @@ -201,7 +230,7 @@ impl EventLoop // // Returns a `DrainResult` indicating the result of receiving from the channe; // - fn drain_recv_channel(&self, state: &mut State) -> DrainResult { + fn drain_recv_channel(&mut self, state: &mut State) -> DrainResult { let mut received_item = false; while let Ok(msg) = self.rx.try_recv() { received_item = true; @@ -209,6 +238,20 @@ impl EventLoop Msg::Input(input) => { state.write_list.push_back(input); }, + Msg::Blink(true) => { + // Set timeout if it's not running + if self.blink_timeout.is_none() { + let mut terminal = self.terminal.lock(); + let interval = terminal.cursor_blink_interval; + self.blink_timeout = Some(self.timer.set_timeout(interval, ()).unwrap()); + } + }, + Msg::Blink(false) => { + // Cancel timeout if it's running + if let Some(timeout) = self.blink_timeout.take() { + self.timer.cancel_timeout(&timeout); + } + }, Msg::Shutdown => { return DrainResult::Shutdown; } @@ -342,6 +385,25 @@ impl EventLoop Ok(()) } + fn handle_timers(&mut self) { + if self.timer.poll().is_some() { + println!("timers!"); + // Dispatch blink + let mut terminal = self.terminal.lock(); + terminal.toggle_blink_state(); + if !terminal.dirty { + self.display.notify(); + terminal.dirty = true; + } + + // Reregister timer + println!("set_timeout {:?}", terminal.cursor_blink_interval); + self.timer.set_timeout(terminal.cursor_blink_interval, ()).unwrap(); + } else { + println!("timers :("); + } + } + pub fn spawn( mut self, state: Option @@ -357,6 +419,7 @@ impl EventLoop self.poll.register(&self.rx, CHANNEL, Ready::readable(), poll_opts).unwrap(); self.poll.register(&fd, PTY, Ready::readable(), poll_opts).unwrap(); + self.poll.register(&self.timer, TIMER, Ready::readable(), poll_opts).unwrap(); let mut events = Events::with_capacity(1024); @@ -383,6 +446,13 @@ impl EventLoop break 'event_loop; } }, + TIMER => { + self.handle_timers(); + println!("reregister timer"); + self.poll + .reregister(&self.timer, TIMER, Ready::readable(), poll_opts) + .expect("reregister timer"); + }, PTY => { let kind = event.kind(); diff --git a/src/main.rs b/src/main.rs index 9db5ce58..c61cfd6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,6 +123,7 @@ fn run(mut config: Config, options: cli::Options) -> Result<(), Box> { // consumes it periodically. let event_loop = EventLoop::new( terminal.clone(), + &config, display.notifier(), pty.reader(), options.ref_test, diff --git a/src/term/mod.rs b/src/term/mod.rs index d898fe2f..70741387 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -114,6 +114,7 @@ impl<'a> RenderableCellsIter<'a> { cursor: &'b Point, colors: &'b color::List, mode: TermMode, + cursor_blink: bool, config: &'b Config, selection: Option>, cursor_style: CursorStyle, @@ -131,7 +132,7 @@ impl<'a> RenderableCellsIter<'a> { config: config, colors: colors, cursor_cells: ArrayDeque::new(), - }.initialize(cursor_style) + }.initialize(cursor_style, cursor_blink) } fn populate_block_cursor(&mut self) { @@ -242,8 +243,8 @@ impl<'a> RenderableCellsIter<'a> { }); } - fn initialize(mut self, cursor_style: CursorStyle) -> Self { - if self.cursor_is_visible() { + fn initialize(mut self, cursor_style: CursorStyle, blink: bool) -> Self { + if !blink && self.cursor_is_visible() { match cursor_style { CursorStyle::Block => { self.populate_block_cursor(); @@ -391,19 +392,20 @@ impl<'a> Iterator for RenderableCellsIter<'a> { pub mod mode { bitflags! { pub flags TermMode: u16 { - const SHOW_CURSOR = 0b000000000001, - const APP_CURSOR = 0b000000000010, - const APP_KEYPAD = 0b000000000100, - const MOUSE_REPORT_CLICK = 0b000000001000, - const BRACKETED_PASTE = 0b000000010000, - const SGR_MOUSE = 0b000000100000, - const MOUSE_MOTION = 0b000001000000, - const LINE_WRAP = 0b000010000000, - const LINE_FEED_NEW_LINE = 0b000100000000, - const ORIGIN = 0b001000000000, - const INSERT = 0b010000000000, - const FOCUS_IN_OUT = 0b100000000000, - const ANY = 0b111111111111, + const SHOW_CURSOR = 0b0000000000001, + const APP_CURSOR = 0b0000000000010, + const APP_KEYPAD = 0b0000000000100, + const MOUSE_REPORT_CLICK = 0b0000000001000, + const BRACKETED_PASTE = 0b0000000010000, + const SGR_MOUSE = 0b0000000100000, + const MOUSE_MOTION = 0b0000001000000, + const LINE_WRAP = 0b0000010000000, + const LINE_FEED_NEW_LINE = 0b0000100000000, + const ORIGIN = 0b0001000000000, + const INSERT = 0b0010000000000, + const FOCUS_IN_OUT = 0b0100000000000, + const CURSOR_BLINK = 0b1000000000000, + const ANY = 0b1111111111111, const NONE = 0, } } @@ -677,6 +679,9 @@ pub struct Term { colors: color::List, cursor_style: CursorStyle, + + pub cursor_blink_interval: Duration, + pub cursor_blink: bool, } /// Terminal size info @@ -741,6 +746,10 @@ impl Term { self.next_title.take() } + pub fn toggle_blink_state(&mut self) { + self.cursor_blink = !self.cursor_blink; + } + pub fn new(config : &Config, size: SizeInfo) -> Term { let template = Cell::default(); @@ -756,11 +765,19 @@ impl Term { let alt = grid.clone(); let scroll_region = Line(0)..grid.num_lines(); + let mut mode = TermMode::default(); + if config.blink_enabled() { + println!("enable blink from term"); + mode.insert(mode::CURSOR_BLINK); + } + Term { next_title: None, dirty: false, visual_bell: VisualBell::new(config), input_needs_wrap: false, + cursor_blink_interval: config.cursor_blink_interval(), + cursor_blink: false, grid: grid, alt_grid: alt, alt: false, @@ -769,7 +786,7 @@ impl Term { cursor_save: Default::default(), cursor_save_alt: Default::default(), tabs: tabs, - mode: Default::default(), + mode: mode, scroll_region: scroll_region, size_info: size, empty_cell: template, @@ -949,6 +966,7 @@ impl Term { &self.cursor.point, &self.colors, self.mode, + self.cursor_blink, config, selection, self.cursor_style