Skip to content

Commit

Permalink
Add blinking cursor support
Browse files Browse the repository at this point in the history
This adds support for blinking the terminal cursor. This can be
controlled either using the configuration file, or using escape
sequences.

The supported control sequences for changing the blinking state are
`CSI Ps SP q` and private mode 12.
  • Loading branch information
Dettorer authored Nov 23, 2020
1 parent 07cfe8b commit 2fd2db4
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 71 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Wide characters sometimes being cut off
- Preserve vi mode across terminal `reset`

### Added

- New `cursor.style.blinking` option to set the default blinking state
- New `cursor.blink_interval` option to configure the blinking frequency
- Support for cursor blinking escapes (`CSI ? 12 h`, `CSI ? 12 l` and `CSI Ps SP q`)

## 0.6.0

### Packaging
Expand Down
26 changes: 20 additions & 6 deletions alacritty.yml
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,23 @@

#cursor:
# Cursor style
#
# Values for `style`:
# - ▇ Block
# - _ Underline
# - | Beam
#style: Block
#style:
# Cursor shape
#
# Values for `shape`:
# - ▇ Block
# - _ Underline
# - | Beam
#shape: Block

# Cursor blinking state
#
# Values for `blinking`:
# - Never: Prevent the cursor from ever blinking
# - Off: Disable blinking by default
# - On: Enable blinking by default
# - Always: Force the cursor to always blink
#blinking: Off

# Vi mode cursor style
#
Expand All @@ -356,6 +367,9 @@
# See `cursor.style` for available options.
#vi_mode_style: None

# Cursor blinking interval in milliseconds.
#blink_interval: 750

# If this is `true`, the cursor will be rendered as a hollow box when the
# window is not focused.
#unfocused_hollow: true
Expand Down
14 changes: 7 additions & 7 deletions alacritty/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
use crossfont::{BitmapBuffer, Metrics, RasterizedGlyph};

use alacritty_terminal::ansi::CursorStyle;
use alacritty_terminal::ansi::CursorShape;

pub fn get_cursor_glyph(
cursor: CursorStyle,
cursor: CursorShape,
metrics: Metrics,
offset_x: i8,
offset_y: i8,
Expand All @@ -26,11 +26,11 @@ pub fn get_cursor_glyph(
}

match cursor {
CursorStyle::HollowBlock => get_box_cursor_glyph(height, width, line_width),
CursorStyle::Underline => get_underline_cursor_glyph(width, line_width),
CursorStyle::Beam => get_beam_cursor_glyph(height, line_width),
CursorStyle::Block => get_block_cursor_glyph(height, width),
CursorStyle::Hidden => RasterizedGlyph::default(),
CursorShape::HollowBlock => get_box_cursor_glyph(height, width, line_width),
CursorShape::Underline => get_underline_cursor_glyph(width, line_width),
CursorShape::Beam => get_beam_cursor_glyph(height, line_width),
CursorShape::Block => get_block_cursor_glyph(height, width),
CursorShape::Hidden => RasterizedGlyph::default(),
}
}

Expand Down
7 changes: 6 additions & 1 deletion alacritty/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ pub struct Display {
#[cfg(not(any(target_os = "macos", windows)))]
pub is_x11: bool,

/// UI cursor visibility for blinking.
pub cursor_hidden: bool,

renderer: QuadRenderer,
glyph_cache: GlyphCache,
meter: Meter,
Expand Down Expand Up @@ -300,6 +303,7 @@ impl Display {
is_x11,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
wayland_event_queue,
cursor_hidden: false,
})
}

Expand Down Expand Up @@ -442,8 +446,9 @@ impl Display {
let viewport_match = search_state
.focused_match()
.and_then(|focused_match| terminal.grid().clamp_buffer_range_to_visible(focused_match));
let cursor_hidden = self.cursor_hidden || search_state.regex().is_some();

let grid_cells = terminal.renderable_cells(config, !search_active).collect::<Vec<_>>();
let grid_cells = terminal.renderable_cells(config, !cursor_hidden).collect::<Vec<_>>();
let visual_bell_intensity = terminal.visual_bell.intensity();
let background_color = terminal.background_color();
let cursor_point = terminal.grid().cursor.point;
Expand Down
71 changes: 70 additions & 1 deletion alacritty/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub enum Event {
Scroll(Scroll),
ConfigReload(PathBuf),
Message(Message),
BlinkCursor,
SearchNext,
}

Expand Down Expand Up @@ -150,6 +151,7 @@ pub struct ActionContext<'a, N, T> {
pub urls: &'a Urls,
pub scheduler: &'a mut Scheduler,
pub search_state: &'a mut SearchState,
cursor_hidden: &'a mut bool,
cli_options: &'a CLIOptions,
font_size: &'a mut Size,
}
Expand Down Expand Up @@ -495,6 +497,28 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
}

/// Handle keyboard typing start.
///
/// This will temporarily disable some features like terminal cursor blinking or the mouse
/// cursor.
///
/// All features are re-enabled again automatically.
#[inline]
fn on_typing_start(&mut self) {
// Disable cursor blinking.
let blink_interval = self.config.cursor.blink_interval();
if let Some(timer) = self.scheduler.get_mut(TimerId::BlinkCursor) {
timer.deadline = Instant::now() + Duration::from_millis(blink_interval);
*self.cursor_hidden = false;
self.terminal.dirty = true;
}

// Hide mouse cursor.
if self.config.ui_config.mouse.hide_when_typing {
self.window.set_mouse_visible(false);
}
}

#[inline]
fn search_direction(&self) -> Direction {
self.search_state.direction
Expand Down Expand Up @@ -667,6 +691,33 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize;
origin
}

/// Update the cursor blinking state.
fn update_cursor_blinking(&mut self) {
// Get config cursor style.
let mut cursor_style = self.config.cursor.style;
if self.terminal.mode().contains(TermMode::VI) {
cursor_style = self.config.cursor.vi_mode_style.unwrap_or(cursor_style);
};

// Check terminal cursor style.
let terminal_blinking = self.terminal.cursor_style().blinking;
let blinking = cursor_style.blinking_override().unwrap_or(terminal_blinking);

// Update cursor blinking state.
self.scheduler.unschedule(TimerId::BlinkCursor);
if blinking && self.terminal.is_focused {
self.scheduler.schedule(
GlutinEvent::UserEvent(Event::BlinkCursor),
Duration::from_millis(self.config.cursor.blink_interval()),
true,
TimerId::BlinkCursor,
)
} else {
*self.cursor_hidden = false;
self.terminal.dirty = true;
}
}
}

#[derive(Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -804,6 +855,12 @@ impl<N: Notify + OnResize> Processor<N> {
{
let mut scheduler = Scheduler::new();

// Start the initial cursor blinking timer.
if self.config.cursor.style().blinking {
let event: Event = TerminalEvent::CursorBlinkingChange(true).into();
self.event_queue.push(event.into());
}

event_loop.run_return(|event, event_loop, control_flow| {
if self.config.ui_config.debug.print_events {
info!("glutin event: {:?}", event);
Expand Down Expand Up @@ -873,6 +930,7 @@ impl<N: Notify + OnResize> Processor<N> {
scheduler: &mut scheduler,
search_state: &mut self.search_state,
cli_options: &self.cli_options,
cursor_hidden: &mut self.display.cursor_hidden,
event_loop,
};
let mut processor = input::Processor::new(context, &self.display.highlighted_url);
Expand Down Expand Up @@ -953,6 +1011,10 @@ impl<N: Notify + OnResize> Processor<N> {
Event::SearchNext => processor.ctx.goto_match(None),
Event::ConfigReload(path) => Self::reload_config(&path, processor),
Event::Scroll(scroll) => processor.ctx.scroll(scroll),
Event::BlinkCursor => {
*processor.ctx.cursor_hidden ^= true;
processor.ctx.terminal.dirty = true;
},
Event::TerminalEvent(event) => match event {
TerminalEvent::Title(title) => {
let ui_config = &processor.ctx.config.ui_config;
Expand Down Expand Up @@ -983,6 +1045,9 @@ impl<N: Notify + OnResize> Processor<N> {
},
TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(),
TerminalEvent::Exit => (),
TerminalEvent::CursorBlinkingChange(_) => {
processor.ctx.update_cursor_blinking();
},
},
},
GlutinEvent::RedrawRequested(_) => processor.ctx.terminal.dirty = true,
Expand Down Expand Up @@ -1033,6 +1098,7 @@ impl<N: Notify + OnResize> Processor<N> {
processor.ctx.window.set_mouse_visible(true);
}

processor.ctx.update_cursor_blinking();
processor.on_focus_change(is_focused);
}
},
Expand Down Expand Up @@ -1111,7 +1177,7 @@ impl<N: Notify + OnResize> Processor<N> {

processor.ctx.terminal.update_config(&config);

// Reload cursor if we've changed its thickness.
// Reload cursor if its thickness has changed.
if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs()
> std::f64::EPSILON
{
Expand Down Expand Up @@ -1154,6 +1220,9 @@ impl<N: Notify + OnResize> Processor<N> {

*processor.ctx.config = config;

// Update cursor blinking.
processor.ctx.update_cursor_blinking();

processor.ctx.terminal.dirty = true;
}

Expand Down
32 changes: 15 additions & 17 deletions alacritty/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub trait ActionContext<T: EventListener> {
fn advance_search_origin(&mut self, direction: Direction);
fn search_direction(&self) -> Direction;
fn search_active(&self) -> bool;
fn on_typing_start(&mut self);
}

trait Execute<T: EventListener> {
Expand Down Expand Up @@ -138,9 +139,7 @@ impl<T: EventListener> Execute<T> for Action {
fn execute<A: ActionContext<T>>(&self, ctx: &mut A) {
match *self {
Action::Esc(ref s) => {
if ctx.config().ui_config.mouse.hide_when_typing {
ctx.window_mut().set_mouse_visible(false);
}
ctx.on_typing_start();

ctx.clear_selection();
ctx.scroll(Scroll::Bottom);
Expand All @@ -167,10 +166,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::ClearSelection => ctx.clear_selection(),
Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(),
Action::ViMotion(motion) => {
if ctx.config().ui_config.mouse.hide_when_typing {
ctx.window_mut().set_mouse_visible(false);
}

ctx.on_typing_start();
ctx.terminal_mut().vi_motion(motion)
},
Action::ViAction(ViAction::ToggleNormalSelection) => {
Expand Down Expand Up @@ -870,6 +866,13 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
}

/// Reset mouse cursor based on modifier and terminal state.
#[inline]
pub fn reset_mouse_cursor(&mut self) {
let mouse_state = self.mouse_state();
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
}

/// Process a received character.
pub fn received_char(&mut self, c: char) {
let suppress_chars = *self.ctx.suppress_chars();
Expand All @@ -890,9 +893,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
return;
}

if self.ctx.config().ui_config.mouse.hide_when_typing {
self.ctx.window_mut().set_mouse_visible(false);
}
self.ctx.on_typing_start();

self.ctx.scroll(Scroll::Bottom);
self.ctx.clear_selection();
Expand All @@ -917,13 +918,6 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
*self.ctx.received_count() += 1;
}

/// Reset mouse cursor based on modifier and terminal state.
#[inline]
pub fn reset_mouse_cursor(&mut self) {
let mouse_state = self.mouse_state();
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
}

/// Attempt to find a binding and execute its action.
///
/// The provided mode, mods, and key must match what is allowed by a binding
Expand Down Expand Up @@ -1270,6 +1264,10 @@ mod tests {
fn scheduler_mut(&mut self) -> &mut Scheduler {
unimplemented!();
}

fn on_typing_start(&mut self) {
unimplemented!();
}
}

macro_rules! test_clickstate {
Expand Down
2 changes: 1 addition & 1 deletion alacritty/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ impl<'a> RenderApi<'a> {
let metrics = glyph_cache.metrics;
let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| {
self.load_glyph(&cursor::get_cursor_glyph(
cursor_key.style,
cursor_key.shape,
metrics,
self.config.font.offset.x,
self.config.font.offset.y,
Expand Down
1 change: 1 addition & 0 deletions alacritty/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Event = GlutinEvent<'static, AlacrittyEvent>;
pub enum TimerId {
SelectionScrolling,
DelayedSearch,
BlinkCursor,
}

/// Event scheduled to be emitted at a specific time.
Expand Down
Loading

0 comments on commit 2fd2db4

Please sign in to comment.