diff options
author | Christian Duerr <contact@christianduerr.com> | 2019-10-05 02:29:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-05 02:29:26 +0200 |
commit | 729eef0c933831bccfeac6a355bdb410787fbe5f (patch) | |
tree | 35cdf2e6427ad18bc53efbab4cab34a0af2054d7 /alacritty_terminal/src | |
parent | b0c6fdff763f7271506d26d7e768e6377fdc691b (diff) | |
download | alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.tar.gz alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.zip |
Update to winit/glutin EventLoop 2.0
This takes the latest glutin master to port Alacritty to the EventLoop
2.0 rework.
This changes a big part of the event loop handling by pushing the event
loop in a separate thread from the renderer and running both in
parallel.
Fixes #2796.
Fixes #2694.
Fixes #2643.
Fixes #2625.
Fixes #2618.
Fixes #2601.
Fixes #2564.
Fixes #2456.
Fixes #2438.
Fixes #2334.
Fixes #2254.
Fixes #2217.
Fixes #1789.
Fixes #1750.
Fixes #1125.
Diffstat (limited to 'alacritty_terminal/src')
39 files changed, 431 insertions, 5046 deletions
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index 3bdef32d..b34c1cd9 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -16,11 +16,13 @@ use std::io; use std::str; -use crate::index::{Column, Contains, Line}; use base64; -use glutin::MouseCursor; +use log::{debug, trace}; +use serde::{Deserialize, Serialize}; + use vte; +use crate::index::{Column, Contains, Line}; use crate::term::color::Rgb; // Parse colors in XParseColor format @@ -104,7 +106,7 @@ struct ProcessorState { /// Processor creates a Performer when running advance and passes the Performer /// to `vte::Parser`. struct Performer<'a, H: Handler + TermInfo, W: io::Write> { - _state: &'a mut ProcessorState, + state: &'a mut ProcessorState, handler: &'a mut H, writer: &'a mut W, } @@ -117,7 +119,7 @@ impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> { handler: &'b mut H, writer: &'b mut W, ) -> Performer<'b, H, W> { - Performer { _state: state, handler, writer } + Performer { state, handler, writer } } } @@ -157,9 +159,6 @@ pub trait Handler { /// OSC to set window title fn set_title(&mut self, _: &str) {} - /// Set the window's mouse cursor - fn set_mouse_cursor(&mut self, _: MouseCursor) {} - /// Set the cursor style fn set_cursor_style(&mut self, _: Option<CursorStyle>) {} @@ -686,7 +685,7 @@ where #[inline] fn print(&mut self, c: char) { self.handler.input(c); - self._state.preceding_char = Some(c); + self.state.preceding_char = Some(c); } #[inline] @@ -919,7 +918,7 @@ where handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize)); }, ('b', None) => { - if let Some(c) = self._state.preceding_char { + if let Some(c) = self.state.preceding_char { for _ in 0..arg_or_default!(idx: 0, default: 1) { handler.input(c); } diff --git a/alacritty_terminal/src/clipboard.rs b/alacritty_terminal/src/clipboard.rs index dc826481..6f6a41be 100644 --- a/alacritty_terminal/src/clipboard.rs +++ b/alacritty_terminal/src/clipboard.rs @@ -15,6 +15,8 @@ #[cfg(not(any(target_os = "macos", target_os = "windows")))] use std::ffi::c_void; +use log::{debug, warn}; + use copypasta::nop_clipboard::NopClipboardContext; #[cfg(not(any(target_os = "macos", target_os = "windows")))] use copypasta::wayland_clipboard; diff --git a/alacritty_terminal/src/config/bindings.rs b/alacritty_terminal/src/config/bindings.rs deleted file mode 100644 index 00090bbf..00000000 --- a/alacritty_terminal/src/config/bindings.rs +++ /dev/null @@ -1,1023 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::fmt; -use std::str::FromStr; - -use glutin::{ModifiersState, MouseButton}; -use serde::de::Error as SerdeError; -use serde::de::{self, MapAccess, Unexpected, Visitor}; -use serde::{Deserialize, Deserializer}; - -use crate::input::{Action, Binding, KeyBinding, MouseBinding}; -use crate::term::TermMode; - -macro_rules! bindings { - ( - $ty:ident; - $( - $key:path - $(,[$($mod:ident: $enabled:expr),*])* - $(,+$mode:expr)* - $(,~$notmode:expr)* - ;$action:expr - );* - $(;)* - ) => {{ - let mut v = Vec::new(); - - $( - let mut _mods = ModifiersState { - $($($mod: $enabled),*,)* - ..Default::default() - }; - let mut _mode = TermMode::empty(); - $(_mode = $mode;)* - let mut _notmode = TermMode::empty(); - $(_notmode = $notmode;)* - - v.push($ty { - trigger: $key, - mods: _mods, - mode: _mode, - notmode: _notmode, - action: $action, - }); - )* - - v - }} -} - -pub fn default_mouse_bindings() -> Vec<MouseBinding> { - bindings!( - MouseBinding; - MouseButton::Middle; Action::PasteSelection; - ) -} - -pub fn default_key_bindings() -> Vec<KeyBinding> { - let mut bindings = bindings!( - KeyBinding; - Key::Paste; Action::Paste; - Key::Copy; Action::Copy; - Key::L, [ctrl: true]; Action::ClearLogNotice; - Key::L, [ctrl: true]; Action::Esc("\x0c".into()); - Key::PageUp, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageUp; - Key::PageDown, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageDown; - Key::Home, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollToTop; - Key::End, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollToBottom; - Key::Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into()); - Key::Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into()); - Key::Home, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2H".into()); - Key::End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into()); - Key::End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into()); - Key::End, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2F".into()); - Key::PageUp; Action::Esc("\x1b[5~".into()); - Key::PageUp, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into()); - Key::PageDown; Action::Esc("\x1b[6~".into()); - Key::PageDown, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into()); - Key::Tab, [shift: true]; Action::Esc("\x1b[Z".into()); - Key::Back; Action::Esc("\x7f".into()); - Key::Back, [alt: true]; Action::Esc("\x1b\x7f".into()); - Key::Insert; Action::Esc("\x1b[2~".into()); - Key::Delete; Action::Esc("\x1b[3~".into()); - Key::Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into()); - Key::Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into()); - Key::Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into()); - Key::Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into()); - Key::Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into()); - Key::Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into()); - Key::Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into()); - Key::Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into()); - Key::F1; Action::Esc("\x1bOP".into()); - Key::F2; Action::Esc("\x1bOQ".into()); - Key::F3; Action::Esc("\x1bOR".into()); - Key::F4; Action::Esc("\x1bOS".into()); - Key::F5; Action::Esc("\x1b[15~".into()); - Key::F6; Action::Esc("\x1b[17~".into()); - Key::F7; Action::Esc("\x1b[18~".into()); - Key::F8; Action::Esc("\x1b[19~".into()); - Key::F9; Action::Esc("\x1b[20~".into()); - Key::F10; Action::Esc("\x1b[21~".into()); - Key::F11; Action::Esc("\x1b[23~".into()); - Key::F12; Action::Esc("\x1b[24~".into()); - Key::F13; Action::Esc("\x1b[25~".into()); - Key::F14; Action::Esc("\x1b[26~".into()); - Key::F15; Action::Esc("\x1b[28~".into()); - Key::F16; Action::Esc("\x1b[29~".into()); - Key::F17; Action::Esc("\x1b[31~".into()); - Key::F18; Action::Esc("\x1b[32~".into()); - Key::F19; Action::Esc("\x1b[33~".into()); - Key::F20; Action::Esc("\x1b[34~".into()); - Key::NumpadEnter; Action::Esc("\n".into()); - ); - - // Code Modifiers - // ---------+--------------------------- - // 2 | Shift - // 3 | Alt - // 4 | Shift + Alt - // 5 | Control - // 6 | Shift + Control - // 7 | Alt + Control - // 8 | Shift + Alt + Control - // ---------+--------------------------- - // - // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys - let modifiers = vec![ - ModifiersState { shift: true, ..ModifiersState::default() }, - ModifiersState { alt: true, ..ModifiersState::default() }, - ModifiersState { shift: true, alt: true, ..ModifiersState::default() }, - ModifiersState { ctrl: true, ..ModifiersState::default() }, - ModifiersState { shift: true, ctrl: true, ..ModifiersState::default() }, - ModifiersState { alt: true, ctrl: true, ..ModifiersState::default() }, - ModifiersState { shift: true, alt: true, ctrl: true, ..ModifiersState::default() }, - ]; - - for (index, mods) in modifiers.iter().enumerate() { - let modifiers_code = index + 2; - bindings.extend(bindings!( - KeyBinding; - Key::Up, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}A", modifiers_code)); - Key::Down, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}B", modifiers_code)); - Key::Right, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}C", modifiers_code)); - Key::Left, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}D", modifiers_code)); - Key::F1, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}P", modifiers_code)); - Key::F2, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}Q", modifiers_code)); - Key::F3, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}R", modifiers_code)); - Key::F4, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}S", modifiers_code)); - Key::F5, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[15;{}~", modifiers_code)); - Key::F6, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[17;{}~", modifiers_code)); - Key::F7, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[18;{}~", modifiers_code)); - Key::F8, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[19;{}~", modifiers_code)); - Key::F9, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[20;{}~", modifiers_code)); - Key::F10, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[21;{}~", modifiers_code)); - Key::F11, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[23;{}~", modifiers_code)); - Key::F12, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[24;{}~", modifiers_code)); - Key::F13, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[25;{}~", modifiers_code)); - Key::F14, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[26;{}~", modifiers_code)); - Key::F15, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[28;{}~", modifiers_code)); - Key::F16, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[29;{}~", modifiers_code)); - Key::F17, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[31;{}~", modifiers_code)); - Key::F18, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[32;{}~", modifiers_code)); - Key::F19, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[33;{}~", modifiers_code)); - Key::F20, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[34;{}~", modifiers_code)); - )); - - // We're adding the following bindings with `Shift` manually above, so skipping them here - // modifiers_code != Shift - if modifiers_code != 2 { - bindings.extend(bindings!( - KeyBinding; - Key::PageUp, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[5;{}~", modifiers_code)); - Key::PageDown, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[6;{}~", modifiers_code)); - Key::End, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}F", modifiers_code)); - Key::Home, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; - Action::Esc(format!("\x1b[1;{}H", modifiers_code)); - )); - } - } - - bindings.extend(platform_key_bindings()); - - bindings -} - -#[cfg(not(any(target_os = "macos", test)))] -fn common_keybindings() -> Vec<KeyBinding> { - bindings!( - KeyBinding; - Key::V, [ctrl: true, shift: true]; Action::Paste; - Key::C, [ctrl: true, shift: true]; Action::Copy; - Key::Insert, [shift: true]; Action::PasteSelection; - Key::Key0, [ctrl: true]; Action::ResetFontSize; - Key::Equals, [ctrl: true]; Action::IncreaseFontSize; - Key::Add, [ctrl: true]; Action::IncreaseFontSize; - Key::Subtract, [ctrl: true]; Action::DecreaseFontSize; - Key::Minus, [ctrl: true]; Action::DecreaseFontSize; - ) -} - -#[cfg(not(any(target_os = "macos", target_os = "windows", test)))] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - common_keybindings() -} - -#[cfg(all(target_os = "windows", not(test)))] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - let mut bindings = bindings!( - KeyBinding; - Key::Return, [alt: true]; Action::ToggleFullscreen; - ); - bindings.extend(common_keybindings()); - bindings -} - -#[cfg(all(target_os = "macos", not(test)))] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - bindings!( - KeyBinding; - Key::Key0, [logo: true]; Action::ResetFontSize; - Key::Equals, [logo: true]; Action::IncreaseFontSize; - Key::Add, [logo: true]; Action::IncreaseFontSize; - Key::Minus, [logo: true]; Action::DecreaseFontSize; - Key::F, [ctrl: true, logo: true]; Action::ToggleFullscreen; - Key::K, [logo: true]; Action::ClearHistory; - Key::K, [logo: true]; Action::Esc("\x0c".into()); - Key::V, [logo: true]; Action::Paste; - Key::C, [logo: true]; Action::Copy; - Key::H, [logo: true]; Action::Hide; - Key::Q, [logo: true]; Action::Quit; - Key::W, [logo: true]; Action::Quit; - ) -} - -// Don't return any bindings for tests since they are commented-out by default -#[cfg(test)] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - vec![] -} - -#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub enum Key { - Scancode(u32), - Key1, - Key2, - Key3, - Key4, - Key5, - Key6, - Key7, - Key8, - Key9, - Key0, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - Escape, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - Snapshot, - Scroll, - Pause, - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - Left, - Up, - Right, - Down, - Back, - Return, - Space, - Compose, - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - AbntC1, - AbntC2, - Add, - Apostrophe, - Apps, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Decimal, - Divide, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Multiply, - Mute, - MyComputer, - NavigateForward, - NavigateBackward, - NextTrack, - NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, - OEM102, - Period, - PlayPause, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Subtract, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Caret, - Copy, - Paste, - Cut, -} - -impl Key { - pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { - use glutin::VirtualKeyCode::*; - // Thank you, vim macros and regex! - match key { - Key1 => Key::Key1, - Key2 => Key::Key2, - Key3 => Key::Key3, - Key4 => Key::Key4, - Key5 => Key::Key5, - Key6 => Key::Key6, - Key7 => Key::Key7, - Key8 => Key::Key8, - Key9 => Key::Key9, - Key0 => Key::Key0, - A => Key::A, - B => Key::B, - C => Key::C, - D => Key::D, - E => Key::E, - F => Key::F, - G => Key::G, - H => Key::H, - I => Key::I, - J => Key::J, - K => Key::K, - L => Key::L, - M => Key::M, - N => Key::N, - O => Key::O, - P => Key::P, - Q => Key::Q, - R => Key::R, - S => Key::S, - T => Key::T, - U => Key::U, - V => Key::V, - W => Key::W, - X => Key::X, - Y => Key::Y, - Z => Key::Z, - Escape => Key::Escape, - F1 => Key::F1, - F2 => Key::F2, - F3 => Key::F3, - F4 => Key::F4, - F5 => Key::F5, - F6 => Key::F6, - F7 => Key::F7, - F8 => Key::F8, - F9 => Key::F9, - F10 => Key::F10, - F11 => Key::F11, - F12 => Key::F12, - F13 => Key::F13, - F14 => Key::F14, - F15 => Key::F15, - F16 => Key::F16, - F17 => Key::F17, - F18 => Key::F18, - F19 => Key::F19, - F20 => Key::F20, - F21 => Key::F21, - F22 => Key::F22, - F23 => Key::F23, - F24 => Key::F24, - Snapshot => Key::Snapshot, - Scroll => Key::Scroll, - Pause => Key::Pause, - Insert => Key::Insert, - Home => Key::Home, - Delete => Key::Delete, - End => Key::End, - PageDown => Key::PageDown, - PageUp => Key::PageUp, - Left => Key::Left, - Up => Key::Up, - Right => Key::Right, - Down => Key::Down, - Back => Key::Back, - Return => Key::Return, - Space => Key::Space, - Compose => Key::Compose, - Numlock => Key::Numlock, - Numpad0 => Key::Numpad0, - Numpad1 => Key::Numpad1, - Numpad2 => Key::Numpad2, - Numpad3 => Key::Numpad3, - Numpad4 => Key::Numpad4, - Numpad5 => Key::Numpad5, - Numpad6 => Key::Numpad6, - Numpad7 => Key::Numpad7, - Numpad8 => Key::Numpad8, - Numpad9 => Key::Numpad9, - AbntC1 => Key::AbntC1, - AbntC2 => Key::AbntC2, - Add => Key::Add, - Apostrophe => Key::Apostrophe, - Apps => Key::Apps, - At => Key::At, - Ax => Key::Ax, - Backslash => Key::Backslash, - Calculator => Key::Calculator, - Capital => Key::Capital, - Colon => Key::Colon, - Comma => Key::Comma, - Convert => Key::Convert, - Decimal => Key::Decimal, - Divide => Key::Divide, - Equals => Key::Equals, - Grave => Key::Grave, - Kana => Key::Kana, - Kanji => Key::Kanji, - LAlt => Key::LAlt, - LBracket => Key::LBracket, - LControl => Key::LControl, - LShift => Key::LShift, - LWin => Key::LWin, - Mail => Key::Mail, - MediaSelect => Key::MediaSelect, - MediaStop => Key::MediaStop, - Minus => Key::Minus, - Multiply => Key::Multiply, - Mute => Key::Mute, - MyComputer => Key::MyComputer, - NavigateForward => Key::NavigateForward, - NavigateBackward => Key::NavigateBackward, - NextTrack => Key::NextTrack, - NoConvert => Key::NoConvert, - NumpadComma => Key::NumpadComma, - NumpadEnter => Key::NumpadEnter, - NumpadEquals => Key::NumpadEquals, - OEM102 => Key::OEM102, - Period => Key::Period, - PlayPause => Key::PlayPause, - Power => Key::Power, - PrevTrack => Key::PrevTrack, - RAlt => Key::RAlt, - RBracket => Key::RBracket, - RControl => Key::RControl, - RShift => Key::RShift, - RWin => Key::RWin, - Semicolon => Key::Semicolon, - Slash => Key::Slash, - Sleep => Key::Sleep, - Stop => Key::Stop, - Subtract => Key::Subtract, - Sysrq => Key::Sysrq, - Tab => Key::Tab, - Underline => Key::Underline, - Unlabeled => Key::Unlabeled, - VolumeDown => Key::VolumeDown, - VolumeUp => Key::VolumeUp, - Wake => Key::Wake, - WebBack => Key::WebBack, - WebFavorites => Key::WebFavorites, - WebForward => Key::WebForward, - WebHome => Key::WebHome, - WebRefresh => Key::WebRefresh, - WebSearch => Key::WebSearch, - WebStop => Key::WebStop, - Yen => Key::Yen, - Caret => Key::Caret, - Copy => Key::Copy, - Paste => Key::Paste, - Cut => Key::Cut, - } - } -} - -struct ModeWrapper { - pub mode: TermMode, - pub not_mode: TermMode, -} - -impl<'a> Deserialize<'a> for ModeWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: Deserializer<'a>, - { - struct ModeVisitor; - - impl<'a> Visitor<'a> for ModeVisitor { - type Value = ModeWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E> - where - E: de::Error, - { - let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; - - for modifier in value.split('|') { - match modifier.trim().to_lowercase().as_str() { - "appcursor" => res.mode |= TermMode::APP_CURSOR, - "~appcursor" => res.not_mode |= TermMode::APP_CURSOR, - "appkeypad" => res.mode |= TermMode::APP_KEYPAD, - "~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD, - "~alt" => res.not_mode |= TermMode::ALT_SCREEN, - "alt" => res.mode |= TermMode::ALT_SCREEN, - _ => error!("Unknown mode {:?}", modifier), - } - } - - Ok(res) - } - } - deserializer.deserialize_str(ModeVisitor) - } -} - -struct MouseButtonWrapper(MouseButton); - -impl MouseButtonWrapper { - fn into_inner(self) -> MouseButton { - self.0 - } -} - -impl<'a> Deserialize<'a> for MouseButtonWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: Deserializer<'a>, - { - struct MouseButtonVisitor; - - impl<'a> Visitor<'a> for MouseButtonVisitor { - type Value = MouseButtonWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Left, Right, Middle, or a number") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButtonWrapper, E> - where - E: de::Error, - { - match value { - "Left" => Ok(MouseButtonWrapper(MouseButton::Left)), - "Right" => Ok(MouseButtonWrapper(MouseButton::Right)), - "Middle" => Ok(MouseButtonWrapper(MouseButton::Middle)), - _ => { - if let Ok(index) = u8::from_str(value) { - Ok(MouseButtonWrapper(MouseButton::Other(index))) - } else { - Err(E::invalid_value(Unexpected::Str(value), &self)) - } - }, - } - } - } - - deserializer.deserialize_str(MouseButtonVisitor) - } -} - -/// Bindings are deserialized into a `RawBinding` before being parsed as a -/// `KeyBinding` or `MouseBinding`. -#[derive(PartialEq, Eq)] -struct RawBinding { - key: Option<Key>, - mouse: Option<MouseButton>, - mods: ModifiersState, - mode: TermMode, - notmode: TermMode, - action: Action, -} - -impl RawBinding { - fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { - if let Some(mouse) = self.mouse { - Ok(Binding { - trigger: mouse, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } - - fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> { - if let Some(key) = self.key { - Ok(KeyBinding { - trigger: key, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } -} - -impl<'a> Deserialize<'a> for RawBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: Deserializer<'a>, - { - enum Field { - Key, - Mods, - Mode, - Action, - Chars, - Mouse, - Command, - } - - impl<'a> Deserialize<'a> for Field { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error> - where - D: Deserializer<'a>, - { - struct FieldVisitor; - - static FIELDS: &[&str] = - &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - impl<'a> Visitor<'a> for FieldVisitor { - type Value = Field; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding fields") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E> - where - E: de::Error, - { - match value { - "key" => Ok(Field::Key), - "mods" => Ok(Field::Mods), - "mode" => Ok(Field::Mode), - "action" => Ok(Field::Action), - "chars" => Ok(Field::Chars), - "mouse" => Ok(Field::Mouse), - "command" => Ok(Field::Command), - _ => Err(E::unknown_field(value, FIELDS)), - } - } - } - - deserializer.deserialize_str(FieldVisitor) - } - } - - struct RawBindingVisitor; - impl<'a> Visitor<'a> for RawBindingVisitor { - type Value = RawBinding; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding specification") - } - - fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error> - where - V: MapAccess<'a>, - { - let mut mods: Option<ModifiersState> = None; - let mut key: Option<Key> = None; - let mut chars: Option<String> = None; - let mut action: Option<crate::input::Action> = None; - let mut mode: Option<TermMode> = None; - let mut not_mode: Option<TermMode> = None; - let mut mouse: Option<MouseButton> = None; - let mut command: Option<CommandWrapper> = None; - - use ::serde::de::Error; - - while let Some(struct_key) = map.next_key::<Field>()? { - match struct_key { - Field::Key => { - if key.is_some() { - return Err(<V::Error as Error>::duplicate_field("key")); - } - - let val = map.next_value::<serde_yaml::Value>()?; - if val.is_u64() { - let scancode = val.as_u64().unwrap(); - if scancode > u64::from(::std::u32::MAX) { - return Err(<V::Error as Error>::custom(format!( - "Invalid key binding, scancode too big: {}", - scancode - ))); - } - key = Some(Key::Scancode(scancode as u32)); - } else { - let k = Key::deserialize(val).map_err(V::Error::custom)?; - key = Some(k); - } - }, - Field::Mods => { - if mods.is_some() { - return Err(<V::Error as Error>::duplicate_field("mods")); - } - - mods = Some(map.next_value::<ModsWrapper>()?.into_inner()); - }, - Field::Mode => { - if mode.is_some() { - return Err(<V::Error as Error>::duplicate_field("mode")); - } - - let mode_deserializer = map.next_value::<ModeWrapper>()?; - mode = Some(mode_deserializer.mode); - not_mode = Some(mode_deserializer.not_mode); - }, - Field::Action => { - if action.is_some() { - return Err(<V::Error as Error>::duplicate_field("action")); - } - - action = Some(map.next_value::<Action>()?); - }, - Field::Chars => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("chars")); - } - - chars = Some(map.next_value()?); - }, - Field::Mouse => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("mouse")); - } - - mouse = Some(map.next_value::<MouseButtonWrapper>()?.into_inner()); - }, - Field::Command => { - if command.is_some() { - return Err(<V::Error as Error>::duplicate_field("command")); - } - - command = Some(map.next_value::<CommandWrapper>()?); - }, - } - } - - let action = match (action, chars, command) { - (Some(action), None, None) => action, - (None, Some(chars), None) => Action::Esc(chars), - (None, None, Some(cmd)) => match cmd { - CommandWrapper::Just(program) => Action::Command(program, vec![]), - CommandWrapper::WithArgs { program, args } => { - Action::Command(program, args) - }, - }, - (None, None, None) => { - return Err(V::Error::custom("must specify chars, action or command")); - }, - _ => { - return Err(V::Error::custom("must specify only chars, action or command")) - }, - }; - - let mode = mode.unwrap_or_else(TermMode::empty); - let not_mode = not_mode.unwrap_or_else(TermMode::empty); - let mods = mods.unwrap_or_else(ModifiersState::default); - - if mouse.is_none() && key.is_none() { - return Err(V::Error::custom("bindings require mouse button or key")); - } - - Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) - } - } - - const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) - } -} - -impl<'a> Deserialize<'a> for MouseBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) - } -} - -impl<'a> Deserialize<'a> for KeyBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) - } -} - -#[serde(untagged)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub enum CommandWrapper { - Just(String), - WithArgs { - program: String, - #[serde(default)] - args: Vec<String>, - }, -} - -impl CommandWrapper { - pub fn program(&self) -> &str { - match self { - CommandWrapper::Just(program) => program, - CommandWrapper::WithArgs { program, .. } => program, - } - } - - pub fn args(&self) -> &[String] { - match self { - CommandWrapper::Just(_) => &[], - CommandWrapper::WithArgs { args, .. } => args, - } - } -} - -/// Newtype for implementing deserialize on glutin Mods -/// -/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the -/// impl below. -#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] -pub struct ModsWrapper(ModifiersState); - -impl ModsWrapper { - pub fn into_inner(self) -> ModifiersState { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ModsWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ModsVisitor; - - impl<'a> Visitor<'a> for ModsVisitor { - type Value = ModsWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E> - where - E: de::Error, - { - let mut res = ModifiersState::default(); - for modifier in value.split('|') { - match modifier.trim().to_lowercase().as_str() { - "command" | "super" => res.logo = true, - "shift" => res.shift = true, - "alt" | "option" => res.alt = true, - "control" => res.ctrl = true, - "none" => (), - _ => error!("Unknown modifier {:?}", modifier), - } - } - - Ok(ModsWrapper(res)) - } - } - - deserializer.deserialize_str(ModsVisitor) - } -} diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs index a9e7a6de..35c03684 100644 --- a/alacritty_terminal/src/config/colors.rs +++ b/alacritty_terminal/src/config/colors.rs @@ -1,10 +1,11 @@ +use log::error; use serde::{Deserialize, Deserializer}; -use crate::config::failure_default; +use crate::config::{failure_default, LOG_TARGET_CONFIG}; use crate::term::color::Rgb; #[serde(default)] -#[derive(Deserialize, Debug, Default, PartialEq, Eq)] +#[derive(Deserialize, Clone, Debug, Default, PartialEq, Eq)] pub struct Colors { #[serde(deserialize_with = "failure_default")] pub primary: PrimaryColors, @@ -33,7 +34,7 @@ impl Colors { } #[serde(default)] -#[derive(Deserialize, Default, Debug, PartialEq, Eq)] +#[derive(Deserialize, Clone, Default, Debug, PartialEq, Eq)] pub struct IndexedColor { #[serde(deserialize_with = "deserialize_color_index")] pub index: u8, @@ -50,6 +51,7 @@ where Ok(index) => { if index < 16 { error!( + target: LOG_TARGET_CONFIG, "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ was expected; ignoring setting", index @@ -62,7 +64,7 @@ where } }, Err(err) => { - error!("Problem with config: {}; ignoring setting", err); + error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring setting", err); // Return value out of range to ignore this color Ok(0) @@ -89,7 +91,7 @@ pub struct SelectionColors { } #[serde(default)] -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub struct PrimaryColors { #[serde(default = "default_background", deserialize_with = "failure_default")] pub background: Rgb, @@ -121,7 +123,7 @@ fn default_foreground() -> Rgb { } /// The 8-colors sections of config -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub struct AnsiColors { #[serde(deserialize_with = "failure_default")] pub black: Rgb, @@ -141,7 +143,7 @@ pub struct AnsiColors { pub white: Rgb, } -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] struct NormalColors(AnsiColors); impl Default for NormalColors { @@ -159,7 +161,7 @@ impl Default for NormalColors { } } -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] struct BrightColors(AnsiColors); impl Default for BrightColors { diff --git a/alacritty_terminal/src/config/debug.rs b/alacritty_terminal/src/config/debug.rs index b7d1144f..f3489693 100644 --- a/alacritty_terminal/src/config/debug.rs +++ b/alacritty_terminal/src/config/debug.rs @@ -1,7 +1,7 @@ -use log::LevelFilter; -use serde::Deserializer; +use log::{error, LevelFilter}; +use serde::{Deserialize, Deserializer}; -use crate::config::failure_default; +use crate::config::{failure_default, LOG_TARGET_CONFIG}; /// Debugging options #[serde(default)] @@ -54,7 +54,10 @@ where "debug" => LevelFilter::Debug, "trace" => LevelFilter::Trace, level => { - error!("Problem with config: invalid log level {}; using level Warn", level); + error!( + target: LOG_TARGET_CONFIG, + "Problem with config: invalid log level {}; using level Warn", level + ); default_log_level() }, }) diff --git a/alacritty_terminal/src/config/font.rs b/alacritty_terminal/src/config/font.rs index 6148c982..a8a76c15 100644 --- a/alacritty_terminal/src/config/font.rs +++ b/alacritty_terminal/src/config/font.rs @@ -1,12 +1,13 @@ use std::fmt; use font::Size; +use log::error; use serde::de::Visitor; use serde::{Deserialize, Deserializer}; #[cfg(target_os = "macos")] use crate::config::DefaultTrueBool; -use crate::config::{failure_default, Delta}; +use crate::config::{failure_default, Delta, LOG_TARGET_CONFIG}; /// Font config /// @@ -202,7 +203,12 @@ impl DeserializeSize for Size { Ok(size) => Ok(size), Err(err) => { let size = default_font_size(); - error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); + error!( + target: LOG_TARGET_CONFIG, + "Problem with config: {}; using size {}", + err, + size.as_f32_pts() + ); Ok(size) }, } diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index ac945e9b..cd900373 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -17,42 +17,38 @@ use std::collections::HashMap; use std::fmt::Display; use std::path::PathBuf; +use log::error; +use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer}; use serde_yaml::Value; -mod bindings; mod colors; mod debug; mod font; -mod monitor; -mod mouse; mod scrolling; -#[cfg(test)] -mod test; mod visual_bell; mod window; use crate::ansi::{Color, CursorStyle, NamedColor}; -use crate::input::{Binding, KeyBinding, MouseBinding}; -pub use crate::config::bindings::Key; pub use crate::config::colors::Colors; pub use crate::config::debug::Debug; pub use crate::config::font::{Font, FontDescription}; -pub use crate::config::monitor::Monitor; -pub use crate::config::mouse::{ClickHandler, Mouse}; pub use crate::config::scrolling::Scrolling; pub use crate::config::visual_bell::{VisualBellAnimation, VisualBellConfig}; -pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig}; +pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig, DEFAULT_NAME}; use crate::term::color::Rgb; pub static DEFAULT_ALACRITTY_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml")); +pub const LOG_TARGET_CONFIG: &str = "alacritty_config"; const MAX_SCROLLBACK_LINES: u32 = 100_000; +pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>; + /// Top-level config type #[derive(Debug, PartialEq, Deserialize)] -pub struct Config { +pub struct Config<T> { /// Pixel padding #[serde(default, deserialize_with = "failure_default")] pub padding: Option<Delta<u8>>, @@ -80,20 +76,9 @@ pub struct Config { #[serde(default, deserialize_with = "failure_default")] pub window: WindowConfig, - /// Keybindings - #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] - pub key_bindings: Vec<KeyBinding>, - - /// Bindings for the mouse - #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] - pub mouse_bindings: Vec<MouseBinding>, - #[serde(default, deserialize_with = "failure_default")] pub selection: Selection, - #[serde(default, deserialize_with = "failure_default")] - pub mouse: Mouse, - /// Path to a shell program to run on startup #[serde(default, deserialize_with = "from_string_or_deserialize")] pub shell: Option<Shell<'static>>, @@ -144,6 +129,10 @@ pub struct Config { #[serde(default, deserialize_with = "failure_default")] pub debug: Debug, + /// Additional configuration options not directly required by the terminal + #[serde(flatten)] + pub ui_config: T, + // TODO: DEPRECATED #[serde(default, deserialize_with = "failure_default")] pub render_timer: Option<bool>, @@ -153,13 +142,13 @@ pub struct Config { pub persistent_logging: Option<bool>, } -impl Default for Config { +impl<T: DeserializeOwned> Default for Config<T> { fn default() -> Self { serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid") } } -impl Config { +impl<T> Config<T> { pub fn tabspaces(&self) -> usize { self.tabspaces.0 } @@ -236,49 +225,6 @@ impl Config { } } -fn default_key_bindings() -> Vec<KeyBinding> { - bindings::default_key_bindings() -} - -fn default_mouse_bindings() -> Vec<MouseBinding> { - bindings::default_mouse_bindings() -} - -fn deserialize_key_bindings<'a, D>(deserializer: D) -> Result<Vec<KeyBinding>, D::Error> -where - D: Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_key_bindings()) -} - -fn deserialize_mouse_bindings<'a, D>(deserializer: D) -> Result<Vec<MouseBinding>, D::Error> -where - D: Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_mouse_bindings()) -} - -fn deserialize_bindings<'a, D, T>( - deserializer: D, - mut default: Vec<Binding<T>>, -) -> Result<Vec<Binding<T>>, D::Error> -where - D: Deserializer<'a>, - T: Copy + Eq + std::hash::Hash + std::fmt::Debug, - Binding<T>: Deserialize<'a>, -{ - let mut bindings: Vec<Binding<T>> = failure_default(deserializer)?; - - // Remove matching default bindings - for binding in bindings.iter() { - default.retain(|b| !b.triggers_match(binding)); - } - - bindings.extend(default); - - Ok(bindings) -} - #[serde(default)] #[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)] pub struct Selection { @@ -324,7 +270,7 @@ impl Cursor { } } -#[derive(Debug, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] pub struct Shell<'a> { pub program: Cow<'a, str>, @@ -397,7 +343,7 @@ impl<'a> Deserialize<'a> for Alpha { } } -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] struct Tabspaces(usize); impl Default for Tabspaces { @@ -420,7 +366,7 @@ where T: Default, E: Display, { - error!("Problem with config: {}; using default value", err); + error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; using default value", err); T::default() } diff --git a/alacritty_terminal/src/config/monitor.rs b/alacritty_terminal/src/config/monitor.rs deleted file mode 100644 index 6d2ab41a..00000000 --- a/alacritty_terminal/src/config/monitor.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::path::PathBuf; -use std::sync::mpsc; -use std::time::Duration; - -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; - -pub struct Monitor { - _thread: ::std::thread::JoinHandle<()>, - rx: mpsc::Receiver<PathBuf>, -} - -pub trait OnConfigReload { - fn on_config_reload(&mut self); -} - -impl OnConfigReload for crate::display::Notifier { - fn on_config_reload(&mut self) { - self.notify(); - } -} - -impl Monitor { - /// Get pending config changes - pub fn pending(&self) -> Option<PathBuf> { - let mut config = None; - while let Ok(new) = self.rx.try_recv() { - config = Some(new); - } - - config - } - - pub fn new<H, P>(path: P, mut handler: H) -> Monitor - where - H: OnConfigReload + Send + 'static, - P: Into<PathBuf>, - { - let path = path.into(); - - let (config_tx, config_rx) = mpsc::channel(); - - Monitor { - _thread: crate::util::thread::spawn_named("config watcher", move || { - let (tx, rx) = mpsc::channel(); - // The Duration argument is a debouncing period. - let mut watcher = - watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); - let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); - - // Get directory of config - let mut parent = config_path.clone(); - parent.pop(); - - // Watch directory - watcher - .watch(&parent, RecursiveMode::NonRecursive) - .expect("watch alacritty.yml dir"); - - loop { - match rx.recv().expect("watcher event") { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Write(path) - | DebouncedEvent::Create(path) - | DebouncedEvent::Chmod(path) => { - if path != config_path { - continue; - } - - let _ = config_tx.send(path); - handler.on_config_reload(); - }, - _ => {}, - } - } - }), - rx: config_rx, - } - } -} diff --git a/alacritty_terminal/src/config/mouse.rs b/alacritty_terminal/src/config/mouse.rs deleted file mode 100644 index 7a04cbe7..00000000 --- a/alacritty_terminal/src/config/mouse.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::time::Duration; - -use glutin::ModifiersState; -use serde::{Deserialize, Deserializer}; - -use crate::config::bindings::{CommandWrapper, ModsWrapper}; -use crate::config::failure_default; - -#[serde(default)] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Mouse { - #[serde(deserialize_with = "failure_default")] - pub double_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub triple_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub hide_when_typing: bool, - #[serde(deserialize_with = "failure_default")] - pub url: Url, -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Url { - // Program for opening links - #[serde(deserialize_with = "deserialize_launcher")] - pub launcher: Option<CommandWrapper>, - - // Modifier used to open links - #[serde(deserialize_with = "failure_default")] - modifiers: ModsWrapper, -} - -impl Url { - pub fn mods(&self) -> ModifiersState { - self.modifiers.into_inner() - } -} - -fn deserialize_launcher<'a, D>( - deserializer: D, -) -> ::std::result::Result<Option<CommandWrapper>, D::Error> -where - D: Deserializer<'a>, -{ - let default = Url::default().launcher; - - // Deserialize to generic value - let val = serde_yaml::Value::deserialize(deserializer)?; - - // Accept `None` to disable the launcher - if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(None); - } - - match <Option<CommandWrapper>>::deserialize(val) { - Ok(launcher) => Ok(launcher), - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - Ok(default) - }, - } -} - -impl Default for Url { - fn default() -> Url { - Url { - #[cfg(not(any(target_os = "macos", windows)))] - launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), - #[cfg(target_os = "macos")] - launcher: Some(CommandWrapper::Just(String::from("open"))), - #[cfg(windows)] - launcher: Some(CommandWrapper::Just(String::from("explorer"))), - modifiers: Default::default(), - } - } -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct ClickHandler { - #[serde(deserialize_with = "deserialize_duration_ms")] - pub threshold: Duration, -} - -impl Default for ClickHandler { - fn default() -> Self { - ClickHandler { threshold: default_threshold_ms() } - } -} - -fn default_threshold_ms() -> Duration { - Duration::from_millis(300) -} - -fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error> -where - D: Deserializer<'a>, -{ - let value = serde_yaml::Value::deserialize(deserializer)?; - match u64::deserialize(value) { - Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_threshold_ms()) - }, - } -} diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs index d62b102f..8471fcd7 100644 --- a/alacritty_terminal/src/config/scrolling.rs +++ b/alacritty_terminal/src/config/scrolling.rs @@ -1,6 +1,7 @@ +use log::error; use serde::{Deserialize, Deserializer}; -use crate::config::{failure_default, MAX_SCROLLBACK_LINES}; +use crate::config::{failure_default, LOG_TARGET_CONFIG, MAX_SCROLLBACK_LINES}; /// Struct for scrolling related settings #[serde(default)] @@ -63,9 +64,11 @@ impl<'de> Deserialize<'de> for ScrollingHistory { Ok(lines) => { if lines > MAX_SCROLLBACK_LINES { error!( + target: LOG_TARGET_CONFIG, "Problem with config: scrollback size is {}, but expected a maximum of \ {}; using {1} instead", - lines, MAX_SCROLLBACK_LINES, + lines, + MAX_SCROLLBACK_LINES, ); Ok(ScrollingHistory(MAX_SCROLLBACK_LINES)) } else { @@ -73,7 +76,10 @@ impl<'de> Deserialize<'de> for ScrollingHistory { } }, Err(err) => { - error!("Problem with config: {}; using default value", err); + error!( + target: LOG_TARGET_CONFIG, + "Problem with config: {}; using default value", err + ); Ok(Default::default()) }, } diff --git a/alacritty_terminal/src/config/test.rs b/alacritty_terminal/src/config/test.rs deleted file mode 100644 index e7890922..00000000 --- a/alacritty_terminal/src/config/test.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::config::{Config, DEFAULT_ALACRITTY_CONFIG}; - -#[test] -fn parse_config() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - - // Sanity check that mouse bindings are being parsed - assert!(!config.mouse_bindings.is_empty()); - - // Sanity check that key bindings are being parsed - assert!(!config.key_bindings.is_empty()); -} - -#[test] -fn default_match_empty() { - let default = Config::default(); - - let empty = serde_yaml::from_str("key: val\n").unwrap(); - - assert_eq!(default, empty); -} diff --git a/alacritty_terminal/src/config/visual_bell.rs b/alacritty_terminal/src/config/visual_bell.rs index 3a31b24a..8981c929 100644 --- a/alacritty_terminal/src/config/visual_bell.rs +++ b/alacritty_terminal/src/config/visual_bell.rs @@ -1,10 +1,12 @@ use std::time::Duration; +use serde::Deserialize; + use crate::config::failure_default; use crate::term::color::Rgb; #[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub struct VisualBellConfig { /// Visual bell animation function #[serde(deserialize_with = "failure_default")] diff --git a/alacritty_terminal/src/config/window.rs b/alacritty_terminal/src/config/window.rs index 7ca90a5b..f470f936 100644 --- a/alacritty_terminal/src/config/window.rs +++ b/alacritty_terminal/src/config/window.rs @@ -1,8 +1,12 @@ +use serde::Deserialize; + use crate::config::{ failure_default, from_string_or_deserialize, option_explicit_none, Delta, FromString, }; use crate::index::{Column, Line}; -use crate::window::DEFAULT_NAME; + +/// Default Alacritty name, used for window title and class. +pub const DEFAULT_NAME: &str = "Alacritty"; #[serde(default)] #[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] diff --git a/alacritty_terminal/src/cursor.rs b/alacritty_terminal/src/cursor.rs index 93f2bd30..d1df14e2 100644 --- a/alacritty_terminal/src/cursor.rs +++ b/alacritty_terminal/src/cursor.rs @@ -16,6 +16,8 @@ use std::cmp; +use serde::Deserialize; + use font::{Metrics, RasterizedGlyph}; use crate::ansi::CursorStyle; diff --git a/alacritty_terminal/src/display.rs b/alacritty_terminal/src/display.rs deleted file mode 100644 index bdd34460..00000000 --- a/alacritty_terminal/src/display.rs +++ /dev/null @@ -1,603 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! The display subsystem including window management, font rasterization, and -//! GPU drawing. -use std::f64; -#[cfg(not(any(target_os = "macos", target_os = "windows")))] -use std::ffi::c_void; -use std::sync::mpsc; - -use glutin::dpi::{PhysicalPosition, PhysicalSize}; -use glutin::EventsLoop; -use parking_lot::MutexGuard; - -use crate::config::{Config, StartupMode}; -use crate::index::Line; -use crate::message_bar::Message; -use crate::meter::Meter; -use crate::renderer::rects::{RenderLines, RenderRect}; -use crate::renderer::{self, GlyphCache, QuadRenderer}; -use crate::sync::FairMutex; -use crate::term::color::Rgb; -use crate::term::{RenderableCell, SizeInfo, Term}; -use crate::window::{self, Window}; -use font::{self, Rasterize}; - -#[derive(Debug)] -pub enum Error { - /// Error with window management - Window(window::Error), - - /// Error dealing with fonts - Font(font::Error), - - /// Error in renderer - Render(renderer::Error), -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::Window(ref err) => Some(err), - Error::Font(ref err) => Some(err), - Error::Render(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::Window(ref err) => err.description(), - Error::Font(ref err) => err.description(), - Error::Render(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::Window(ref err) => err.fmt(f), - Error::Font(ref err) => err.fmt(f), - Error::Render(ref err) => err.fmt(f), - } - } -} - -impl From<window::Error> for Error { - fn from(val: window::Error) -> Error { - Error::Window(val) - } -} - -impl From<font::Error> for Error { - fn from(val: font::Error) -> Error { - Error::Font(val) - } -} - -impl From<renderer::Error> for Error { - fn from(val: renderer::Error) -> Error { - Error::Render(val) - } -} - -/// The display wraps a window, font rasterizer, and GPU renderer -pub struct Display { - window: Window, - renderer: QuadRenderer, - glyph_cache: GlyphCache, - render_timer: bool, - rx: mpsc::Receiver<PhysicalSize>, - tx: mpsc::Sender<PhysicalSize>, - meter: Meter, - font_size: font::Size, - size_info: SizeInfo, - last_message: Option<Message>, -} - -/// Can wakeup the render loop from other threads -pub struct Notifier(window::Proxy); - -/// Types that are interested in when the display is resized -pub trait OnResize { - fn on_resize(&mut self, size: &SizeInfo); -} - -impl Notifier { - pub fn notify(&self) { - self.0.wakeup_event_loop(); - } -} - -impl Display { - pub fn notifier(&self) -> Notifier { - Notifier(self.window.create_window_proxy()) - } - - pub fn update_config(&mut self, config: &Config) { - self.render_timer = config.render_timer(); - } - - /// Get size info about the display - pub fn size(&self) -> &SizeInfo { - &self.size_info - } - - pub fn new(config: &Config) -> Result<Display, Error> { - // Extract some properties from config - let render_timer = config.render_timer(); - - // Guess DPR based on first monitor - let event_loop = EventsLoop::new(); - let estimated_dpr = - event_loop.get_available_monitors().next().map(|m| m.get_hidpi_factor()).unwrap_or(1.); - - // Guess the target window dimensions - let metrics = GlyphCache::static_metrics(config, estimated_dpr as f32)?; - let (cell_width, cell_height) = Self::compute_cell_size(config, &metrics); - let dimensions = Self::calculate_dimensions(config, estimated_dpr, cell_width, cell_height); - - debug!("Estimated DPR: {}", estimated_dpr); - debug!("Estimated Cell Size: {} x {}", cell_width, cell_height); - debug!("Estimated Dimensions: {:?}", dimensions); - - // Create the window where Alacritty will be displayed - let logical = dimensions.map(|d| PhysicalSize::new(d.0, d.1).to_logical(estimated_dpr)); - let mut window = Window::new(event_loop, &config, logical)?; - - let dpr = window.hidpi_factor(); - info!("Device pixel ratio: {}", dpr); - - // get window properties for initializing the other subsystems - let mut viewport_size = - window.inner_size_pixels().expect("glutin returns window size").to_physical(dpr); - - // Create renderer - let mut renderer = QuadRenderer::new()?; - - let (glyph_cache, cell_width, cell_height) = - Self::new_glyph_cache(dpr, &mut renderer, config)?; - - let mut padding_x = f64::from(config.window.padding.x) * dpr; - let mut padding_y = f64::from(config.window.padding.y) * dpr; - - if let Some((width, height)) = - Self::calculate_dimensions(config, dpr, cell_width, cell_height) - { - if dimensions == Some((width, height)) { - info!("Estimated DPR correctly, skipping resize"); - } else { - viewport_size = PhysicalSize::new(width, height); - window.set_inner_size(viewport_size.to_logical(dpr)); - } - } else if config.window.dynamic_padding { - // Make sure additional padding is spread evenly - let cw = f64::from(cell_width); - let ch = f64::from(cell_height); - padding_x = padding_x + (viewport_size.width - 2. * padding_x) % cw / 2.; - padding_y = padding_y + (viewport_size.height - 2. * padding_y) % ch / 2.; - } - - padding_x = padding_x.floor(); - padding_y = padding_y.floor(); - - // Update OpenGL projection - renderer.resize(viewport_size, padding_x as f32, padding_y as f32); - - info!("Cell Size: {} x {}", cell_width, cell_height); - info!("Padding: {} x {}", padding_x, padding_y); - - let size_info = SizeInfo { - dpr, - width: viewport_size.width as f32, - height: viewport_size.height as f32, - cell_width: cell_width as f32, - cell_height: cell_height as f32, - padding_x: padding_x as f32, - padding_y: padding_y as f32, - }; - - // Channel for resize events - // - // macOS has a callback for getting resize events, the channel is used - // to queue resize events until the next draw call. Unfortunately, it - // seems that the event loop is blocked until the window is done - // resizing. If any drawing were to happen during a resize, it would - // need to be in the callback. - let (tx, rx) = mpsc::channel(); - - // Clear screen - let background_color = config.colors.primary.background; - renderer.with_api(config, &size_info, |api| { - api.clear(background_color); - }); - - // We should call `clear` when window is offscreen, so when `window.show()` happens it - // would be with background color instead of uninitialized surface. - window.swap_buffers()?; - - window.show(); - - // Set window position - // - // TODO: replace `set_position` with `with_position` once available - // Upstream issue: https://github.com/tomaka/winit/issues/806 - if let Some(position) = config.window.position { - let physical = PhysicalPosition::from((position.x, position.y)); - let logical = physical.to_logical(window.hidpi_factor()); - window.set_position(logical); - } - - #[allow(clippy::single_match)] - match config.window.startup_mode() { - StartupMode::Fullscreen => window.set_fullscreen(true), - #[cfg(target_os = "macos")] - StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), - #[cfg(not(any(target_os = "macos", windows)))] - StartupMode::Maximized if window.is_x11() => window.set_maximized(true), - _ => (), - } - - Ok(Display { - window, - renderer, - glyph_cache, - render_timer, - tx, - rx, - meter: Meter::new(), - font_size: config.font.size, - size_info, - last_message: None, - }) - } - - fn calculate_dimensions( - config: &Config, - dpr: f64, - cell_width: f32, - cell_height: f32, - ) -> Option<(f64, f64)> { - let dimensions = config.window.dimensions; - - if dimensions.columns_u32() == 0 - || dimensions.lines_u32() == 0 - || config.window.startup_mode() != StartupMode::Windowed - { - return None; - } - - let padding_x = f64::from(config.window.padding.x) * dpr; - let padding_y = f64::from(config.window.padding.y) * dpr; - - // Calculate new size based on cols/lines specified in config - let grid_width = cell_width as u32 * dimensions.columns_u32(); - let grid_height = cell_height as u32 * dimensions.lines_u32(); - - let width = (f64::from(grid_width) + 2. * padding_x).floor(); - let height = (f64::from(grid_height) + 2. * padding_y).floor(); - - Some((width, height)) - } - - fn new_glyph_cache( - dpr: f64, - renderer: &mut QuadRenderer, - config: &Config, - ) -> Result<(GlyphCache, f32, f32), Error> { - let font = config.font.clone(); - let rasterizer = font::Rasterizer::new(dpr as f32, config.font.use_thin_strokes())?; - - // Initialize glyph cache - let glyph_cache = { - info!("Initializing glyph cache..."); - let init_start = ::std::time::Instant::now(); - - let cache = - renderer.with_loader(|mut api| GlyphCache::new(rasterizer, &font, &mut api))?; - - let stop = init_start.elapsed(); - let stop_f = stop.as_secs() as f64 + f64::from(stop.subsec_nanos()) / 1_000_000_000f64; - info!("... finished initializing glyph cache in {}s", stop_f); - - cache - }; - - // Need font metrics to resize the window properly. This suggests to me the - // font metrics should be computed before creating the window in the first - // place so that a resize is not needed. - let (cw, ch) = Self::compute_cell_size(config, &glyph_cache.font_metrics()); - - Ok((glyph_cache, cw, ch)) - } - - pub fn update_glyph_cache(&mut self, config: &Config) { - let cache = &mut self.glyph_cache; - let dpr = self.size_info.dpr; - let size = self.font_size; - - self.renderer.with_loader(|mut api| { - let _ = cache.update_font_size(&config.font, size, dpr, &mut api); - }); - - let (cw, ch) = Self::compute_cell_size(config, &cache.font_metrics()); - self.size_info.cell_width = cw; - self.size_info.cell_height = ch; - } - - fn compute_cell_size(config: &Config, metrics: &font::Metrics) -> (f32, f32) { - let offset_x = f64::from(config.font.offset.x); - let offset_y = f64::from(config.font.offset.y); - ( - f32::max(1., ((metrics.average_advance + offset_x) as f32).floor()), - f32::max(1., ((metrics.line_height + offset_y) as f32).floor()), - ) - } - - #[inline] - pub fn resize_channel(&self) -> mpsc::Sender<PhysicalSize> { - self.tx.clone() - } - - pub fn window(&mut self) -> &mut Window { - &mut self.window - } - - /// Process pending resize events - pub fn handle_resize( - &mut self, - terminal: &mut MutexGuard<'_, Term>, - config: &Config, - pty_resize_handle: &mut dyn OnResize, - processor_resize_handle: &mut dyn OnResize, - ) { - let previous_cols = self.size_info.cols(); - let previous_lines = self.size_info.lines(); - - // Resize events new_size and are handled outside the poll_events - // iterator. This has the effect of coalescing multiple resize - // events into one. - let mut new_size = None; - - // Take most recent resize event, if any - while let Ok(size) = self.rx.try_recv() { - new_size = Some(size); - } - - // Update the DPR - let dpr = self.window.hidpi_factor(); - - // Font size/DPI factor modification detected - let font_changed = - terminal.font_size != self.font_size || (dpr - self.size_info.dpr).abs() > f64::EPSILON; - - // Skip resize if nothing changed - if let Some(new_size) = new_size { - if !font_changed - && (new_size.width - f64::from(self.size_info.width)).abs() < f64::EPSILON - && (new_size.height - f64::from(self.size_info.height)).abs() < f64::EPSILON - { - return; - } - } - - // Message bar update detected - let message_bar_changed = self.last_message != terminal.message_buffer_mut().message(); - - if font_changed || message_bar_changed { - if new_size == None { - // Force a resize to refresh things - new_size = Some(PhysicalSize::new( - f64::from(self.size_info.width) / self.size_info.dpr * dpr, - f64::from(self.size_info.height) / self.size_info.dpr * dpr, - )); - } - - self.font_size = terminal.font_size; - self.last_message = terminal.message_buffer_mut().message(); - self.size_info.dpr = dpr; - } - - if font_changed { - self.update_glyph_cache(config); - } - - if let Some(psize) = new_size.take() { - let width = psize.width as f32; - let height = psize.height as f32; - let cell_width = self.size_info.cell_width; - let cell_height = self.size_info.cell_height; - - self.size_info.width = width; - self.size_info.height = height; - - let mut padding_x = f32::from(config.window.padding.x) * dpr as f32; - let mut padding_y = f32::from(config.window.padding.y) * dpr as f32; - - if config.window.dynamic_padding { - padding_x = padding_x + ((width - 2. * padding_x) % cell_width) / 2.; - padding_y = padding_y + ((height - 2. * padding_y) % cell_height) / 2.; - } - - self.size_info.padding_x = padding_x.floor(); - self.size_info.padding_y = padding_y.floor(); - - let size = &self.size_info; - terminal.resize(size); - processor_resize_handle.on_resize(size); - - // Subtract message bar lines for pty size - let mut pty_size = *size; - if let Some(message) = terminal.message_buffer_mut().message() { - pty_size.height -= pty_size.cell_height * message.text(&size).len() as f32; - } - - if message_bar_changed - || previous_cols != pty_size.cols() - || previous_lines != pty_size.lines() - { - pty_resize_handle.on_resize(&pty_size); - } - - self.window.resize(psize); - self.renderer.resize(psize, self.size_info.padding_x, self.size_info.padding_y); - } - } - - /// Draw the screen - /// - /// A reference to Term whose state is being drawn must be provided. - /// - /// This call may block if vsync is enabled - pub fn draw(&mut self, terminal: &FairMutex<Term>, config: &Config) { - let mut terminal = terminal.lock(); - let size_info = *terminal.size_info(); - let visual_bell_intensity = terminal.visual_bell.intensity(); - let background_color = terminal.background_color(); - let metrics = self.glyph_cache.font_metrics(); - - let window_focused = self.window.is_focused; - let grid_cells: Vec<RenderableCell> = - terminal.renderable_cells(config, window_focused).collect(); - - // Get message from terminal to ignore modifications after lock is dropped - let message_buffer = terminal.message_buffer_mut().message(); - - // Clear dirty flag - terminal.dirty = !terminal.visual_bell.completed(); - - if let Some(title) = terminal.get_next_title() { - self.window.set_title(&title); - } - - if let Some(mouse_cursor) = terminal.get_next_mouse_cursor() { - self.window.set_mouse_cursor(mouse_cursor); - } - - if let Some(is_urgent) = terminal.next_is_urgent.take() { - // We don't need to set the urgent flag if we already have the - // user's attention. - if !is_urgent || !self.window.is_focused { - self.window.set_urgent(is_urgent); - } - } - - // Clear when terminal mutex isn't held. Mesa for - // some reason takes a long time to call glClear(). The driver descends - // into xcb_connect_to_fd() which ends up calling __poll_nocancel() - // which blocks for a while. - // - // By keeping this outside of the critical region, the Mesa bug is - // worked around to some extent. Since this doesn't actually address the - // issue of glClear being slow, less time is available for input - // handling and rendering. - drop(terminal); - - self.renderer.with_api(config, &size_info, |api| { - api.clear(background_color); - }); - - { - let glyph_cache = &mut self.glyph_cache; - let mut lines = RenderLines::new(); - - // Draw grid - { - let _sampler = self.meter.sampler(); - - self.renderer.with_api(config, &size_info, |mut api| { - // Iterate over all non-empty cells in the grid - for cell in grid_cells { - // Update underline/strikeout - lines.update(&cell); - - // Draw the cell - api.render_cell(cell, glyph_cache); - } - }); - } - - let mut rects = lines.into_rects(&metrics, &size_info); - - if let Some(message) = message_buffer { - let text = message.text(&size_info); - - // Create a new rectangle for the background - let start_line = size_info.lines().0 - text.len(); - let y = size_info.padding_y + size_info.cell_height * start_line as f32; - rects.push(RenderRect::new( - 0., - y, - size_info.width, - size_info.height - y, - message.color(), - )); - - // Draw rectangles including the new background - self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); - - // Relay messages to the user - let mut offset = 1; - for message_text in text.iter().rev() { - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string( - &message_text, - Line(size_info.lines().saturating_sub(offset)), - glyph_cache, - None, - ); - }); - offset += 1; - } - } else { - // Draw rectangles - self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); - } - - // Draw render timer - if self.render_timer { - let timing = format!("{:.3} usec", self.meter.average()); - let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, Some(color)); - }); - } - } - - self.window.swap_buffers().expect("swap buffers"); - } - - pub fn get_window_id(&self) -> Option<usize> { - self.window.get_window_id() - } - - /// Adjust the IME editor position according to the new location of the cursor - pub fn update_ime_position(&mut self, terminal: &Term) { - let point = terminal.cursor().point; - let SizeInfo { cell_width: cw, cell_height: ch, padding_x: px, padding_y: py, .. } = - *terminal.size_info(); - - let dpr = self.window().hidpi_factor(); - let nspot_x = f64::from(px + point.col.0 as f32 * cw); - let nspot_y = f64::from(py + (point.line.0 + 1) as f32 * ch); - - self.window().set_ime_spot(PhysicalPosition::from((nspot_x, nspot_y)).to_logical(dpr)); - } - - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - pub fn get_wayland_display(&self) -> Option<*mut c_void> { - self.window.get_wayland_display() - } -} diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs index 9d2aa78c..2d43e9dd 100644 --- a/alacritty_terminal/src/event.rs +++ b/alacritty_terminal/src/event.rs @@ -1,28 +1,19 @@ -//! Process window events use std::borrow::Cow; -use std::env; -#[cfg(unix)] -use std::fs; -use std::sync::mpsc; -use std::time::Instant; +use std::path::PathBuf; -use glutin::dpi::PhysicalSize; -use glutin::{self, ElementState, Event, MouseButton}; -use parking_lot::MutexGuard; +use crate::message_bar::Message; +use crate::term::SizeInfo; -use crate::clipboard::ClipboardType; -use crate::config::{self, Config, StartupMode}; -use crate::display::OnResize; -use crate::grid::Scroll; -use crate::index::{Column, Line, Point, Side}; -use crate::input::{self, KeyBinding, Modifiers, MouseBinding}; -use crate::selection::Selection; -use crate::sync::FairMutex; -use crate::term::{SizeInfo, Term}; -#[cfg(unix)] -use crate::tty; -use crate::util::{limit, start_daemon}; -use crate::window::Window; +#[derive(Clone, Debug, PartialEq)] +pub enum Event { + ConfigReload(PathBuf), + MouseCursorDirty, + Message(Message), + Title(String), + Wakeup, + Urgent, + Exit, +} /// Byte sequences are sent to a `Notify` in response to some events pub trait Notify { @@ -32,526 +23,12 @@ pub trait Notify { fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, _: B); } -pub struct ActionContext<'a, N> { - pub notifier: &'a mut N, - pub terminal: &'a mut Term, - pub size_info: &'a mut SizeInfo, - pub mouse: &'a mut Mouse, - pub received_count: &'a mut usize, - pub suppress_chars: &'a mut bool, - pub modifiers: &'a mut Modifiers, - pub window_changes: &'a mut WindowChanges, +/// Types that are interested in when the display is resized +pub trait OnResize { + fn on_resize(&mut self, size: &SizeInfo); } -impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { - fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, val: B) { - self.notifier.notify(val); - } - - fn size_info(&self) -> SizeInfo { - *self.size_info - } - - fn scroll(&mut self, scroll: Scroll) { - self.terminal.scroll_display(scroll); - - if let ElementState::Pressed = self.mouse().left_button_state { - let (x, y) = (self.mouse().x, self.mouse().y); - let size_info = self.size_info(); - let point = size_info.pixels_to_coords(x, y); - let cell_side = self.mouse().cell_side; - self.update_selection(Point { line: point.line, col: point.col }, cell_side); - } - } - - fn copy_selection(&mut self, ty: ClipboardType) { - if let Some(selected) = self.terminal.selection_to_string() { - if !selected.is_empty() { - self.terminal.clipboard().store(ty, selected); - } - } - } - - fn selection_is_empty(&self) -> bool { - self.terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true) - } - - fn clear_selection(&mut self) { - *self.terminal.selection_mut() = None; - self.terminal.dirty = true; - } - - fn update_selection(&mut self, point: Point, side: Side) { - let point = self.terminal.visible_to_buffer(point); - - // Update selection if one exists - if let Some(ref mut selection) = self.terminal.selection_mut() { - selection.update(point, side); - } - - self.terminal.dirty = true; - } - - fn simple_selection(&mut self, point: Point, side: Side) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::simple(point, side)); - self.terminal.dirty = true; - } - - fn block_selection(&mut self, point: Point, side: Side) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::block(point, side)); - self.terminal.dirty = true; - } - - fn semantic_selection(&mut self, point: Point) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::semantic(point)); - self.terminal.dirty = true; - } - - fn line_selection(&mut self, point: Point) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::lines(point)); - self.terminal.dirty = true; - } - - fn mouse_coords(&self) -> Option<Point> { - self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) - } - - #[inline] - fn mouse_mut(&mut self) -> &mut Mouse { - self.mouse - } - - #[inline] - fn mouse(&self) -> &Mouse { - self.mouse - } - - #[inline] - fn received_count(&mut self) -> &mut usize { - &mut self.received_count - } - - #[inline] - fn suppress_chars(&mut self) -> &mut bool { - &mut self.suppress_chars - } - - #[inline] - fn modifiers(&mut self) -> &mut Modifiers { - &mut self.modifiers - } - - #[inline] - fn hide_window(&mut self) { - self.window_changes.hide = true; - } - - #[inline] - fn terminal(&self) -> &Term { - self.terminal - } - - #[inline] - fn terminal_mut(&mut self) -> &mut Term { - self.terminal - } - - fn spawn_new_instance(&mut self) { - let alacritty = env::args().next().unwrap(); - - #[cfg(unix)] - let args = { - #[cfg(not(target_os = "freebsd"))] - let proc_prefix = ""; - #[cfg(target_os = "freebsd")] - let proc_prefix = "/compat/linux"; - let link_path = format!("{}/proc/{}/cwd", proc_prefix, tty::child_pid()); - if let Ok(path) = fs::read_link(link_path) { - vec!["--working-directory".into(), path] - } else { - Vec::new() - } - }; - #[cfg(not(unix))] - let args: Vec<String> = Vec::new(); - - match start_daemon(&alacritty, &args) { - Ok(_) => debug!("Started new Alacritty process: {} {:?}", alacritty, args), - Err(_) => warn!("Unable to start new Alacritty process: {} {:?}", alacritty, args), - } - } - - fn toggle_fullscreen(&mut self) { - self.window_changes.toggle_fullscreen(); - } - - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self) { - self.window_changes.toggle_simple_fullscreen() - } -} - -/// The ActionContext can't really have direct access to the Window -/// with the current design. Event handlers that want to change the -/// window must set these flags instead. The processor will trigger -/// the actual changes. -#[derive(Default)] -pub struct WindowChanges { - pub hide: bool, - pub toggle_fullscreen: bool, - #[cfg(target_os = "macos")] - pub toggle_simple_fullscreen: bool, -} - -impl WindowChanges { - fn clear(&mut self) { - *self = WindowChanges::default(); - } - - fn toggle_fullscreen(&mut self) { - self.toggle_fullscreen = !self.toggle_fullscreen; - } - - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self) { - self.toggle_simple_fullscreen = !self.toggle_simple_fullscreen; - } -} - -pub enum ClickState { - None, - Click, - DoubleClick, - TripleClick, -} - -/// State of the mouse -pub struct Mouse { - pub x: usize, - pub y: usize, - pub left_button_state: ElementState, - pub middle_button_state: ElementState, - pub right_button_state: ElementState, - pub last_click_timestamp: Instant, - pub click_state: ClickState, - pub scroll_px: i32, - pub line: Line, - pub column: Column, - pub cell_side: Side, - pub lines_scrolled: f32, - pub block_url_launcher: bool, - pub last_button: MouseButton, -} - -impl Default for Mouse { - fn default() -> Mouse { - Mouse { - x: 0, - y: 0, - last_click_timestamp: Instant::now(), - left_button_state: ElementState::Released, - middle_button_state: ElementState::Released, - right_button_state: ElementState::Released, - click_state: ClickState::None, - scroll_px: 0, - line: Line(0), - column: Column(0), - cell_side: Side::Left, - lines_scrolled: 0.0, - block_url_launcher: false, - last_button: MouseButton::Other(0), - } - } -} - -/// The event processor -/// -/// Stores some state from received events and dispatches actions when they are -/// triggered. -pub struct Processor<N> { - key_bindings: Vec<KeyBinding>, - mouse_bindings: Vec<MouseBinding>, - mouse_config: config::Mouse, - scrolling_config: config::Scrolling, - print_events: bool, - wait_for_event: bool, - notifier: N, - mouse: Mouse, - resize_tx: mpsc::Sender<PhysicalSize>, - size_info: SizeInfo, - hide_mouse_when_typing: bool, - hide_mouse: bool, - received_count: usize, - suppress_chars: bool, - modifiers: Modifiers, - pending_events: Vec<Event>, - window_changes: WindowChanges, - save_to_clipboard: bool, - alt_send_esc: bool, - is_fullscreen: bool, - is_simple_fullscreen: bool, -} - -/// Notify that the terminal was resized -/// -/// Currently this just forwards the notice to the input processor. -impl<N> OnResize for Processor<N> { - fn on_resize(&mut self, size: &SizeInfo) { - self.size_info = size.to_owned(); - } -} - -impl<N: Notify> Processor<N> { - /// Create a new event processor - /// - /// Takes a writer which is expected to be hooked up to the write end of a - /// pty. - pub fn new( - notifier: N, - resize_tx: mpsc::Sender<PhysicalSize>, - config: &Config, - size_info: SizeInfo, - ) -> Processor<N> { - Processor { - key_bindings: config.key_bindings.to_vec(), - mouse_bindings: config.mouse_bindings.to_vec(), - mouse_config: config.mouse.to_owned(), - scrolling_config: config.scrolling, - print_events: config.debug.print_events, - wait_for_event: true, - notifier, - resize_tx, - mouse: Default::default(), - size_info, - hide_mouse_when_typing: config.mouse.hide_when_typing, - hide_mouse: false, - received_count: 0, - suppress_chars: false, - modifiers: Default::default(), - pending_events: Vec::with_capacity(4), - window_changes: Default::default(), - save_to_clipboard: config.selection.save_to_clipboard, - alt_send_esc: config.alt_send_esc(), - is_fullscreen: config.window.startup_mode() == StartupMode::Fullscreen, - #[cfg(target_os = "macos")] - is_simple_fullscreen: config.window.startup_mode() == StartupMode::SimpleFullscreen, - #[cfg(not(target_os = "macos"))] - is_simple_fullscreen: false, - } - } - - /// Handle events from glutin - /// - /// Doesn't take self mutably due to borrow checking. Kinda uggo but w/e. - fn handle_event<'a>( - processor: &mut input::Processor<'a, ActionContext<'a, N>>, - event: Event, - resize_tx: &mpsc::Sender<PhysicalSize>, - hide_mouse: &mut bool, - window_is_focused: &mut bool, - ) { - match event { - // Pass on device events - Event::DeviceEvent { .. } | Event::Suspended { .. } => (), - Event::WindowEvent { event, .. } => { - use glutin::WindowEvent::*; - match event { - CloseRequested => processor.ctx.terminal.exit(), - Resized(lsize) => { - // Resize events are emitted via glutin/winit with logical sizes - // However the terminal, window and renderer use physical sizes - // so a conversion must be done here - resize_tx - .send(lsize.to_physical(processor.ctx.size_info.dpr)) - .expect("send new size"); - processor.ctx.terminal.dirty = true; - }, - KeyboardInput { input, .. } => { - processor.process_key(input); - if input.state == ElementState::Pressed { - // Hide cursor while typing - *hide_mouse = true; - } - }, - ReceivedCharacter(c) => { - processor.received_char(c); - }, - MouseInput { state, button, modifiers, .. } => { - if !cfg!(target_os = "macos") || *window_is_focused { - *hide_mouse = false; - processor.mouse_input(state, button, modifiers); - processor.ctx.terminal.dirty = true; - } - }, - CursorMoved { position: lpos, modifiers, .. } => { - let (x, y) = lpos.to_physical(processor.ctx.size_info.dpr).into(); - let x: i32 = limit(x, 0, processor.ctx.size_info.width as i32); - let y: i32 = limit(y, 0, processor.ctx.size_info.height as i32); - - *hide_mouse = false; - processor.mouse_moved(x as usize, y as usize, modifiers); - }, - MouseWheel { delta, phase, modifiers, .. } => { - *hide_mouse = false; - processor.on_mouse_wheel(delta, phase, modifiers); - }, - Refresh => { - processor.ctx.terminal.dirty = true; - }, - Focused(is_focused) => { - *window_is_focused = is_focused; - - if is_focused { - processor.ctx.terminal.next_is_urgent = Some(false); - processor.ctx.terminal.dirty = true; - } else { - processor.ctx.terminal.dirty = true; - *hide_mouse = false; - } - - processor.on_focus_change(is_focused); - }, - DroppedFile(path) => { - use crate::input::ActionContext; - let path: String = path.to_string_lossy().into(); - processor.ctx.write_to_pty(path.into_bytes()); - }, - HiDpiFactorChanged(new_dpr) => { - processor.ctx.size_info.dpr = new_dpr; - processor.ctx.terminal.dirty = true; - }, - CursorLeft { .. } => processor.ctx.terminal.reset_url_highlight(), - _ => (), - } - }, - Event::Awakened => { - processor.ctx.terminal.dirty = true; - }, - } - } - - /// Process events. When `wait_for_event` is set, this method is guaranteed - /// to process at least one event. - pub fn process_events<'a>( - &mut self, - term: &'a FairMutex<Term>, - window: &mut Window, - ) -> MutexGuard<'a, Term> { - // Terminal is lazily initialized the first time an event is returned - // from the blocking WaitEventsIterator. Otherwise, the pty reader would - // be blocked the entire time we wait for input! - let mut terminal; - - self.pending_events.clear(); - - { - // Ditto on lazy initialization for context and processor. - let context; - let mut processor: input::Processor<'_, ActionContext<'_, N>>; - - let print_events = self.print_events; - - let resize_tx = &self.resize_tx; - - if self.wait_for_event { - // A Vec is used here since wait_events can potentially yield - // multiple events before the interrupt is handled. For example, - // Resize and Moved events. - let pending_events = &mut self.pending_events; - window.wait_events(|e| { - pending_events.push(e); - glutin::ControlFlow::Break - }); - } - - terminal = term.lock(); - - context = ActionContext { - terminal: &mut terminal, - notifier: &mut self.notifier, - mouse: &mut self.mouse, - size_info: &mut self.size_info, - received_count: &mut self.received_count, - suppress_chars: &mut self.suppress_chars, - modifiers: &mut self.modifiers, - window_changes: &mut self.window_changes, - }; - - processor = input::Processor { - ctx: context, - scrolling_config: &self.scrolling_config, - mouse_config: &self.mouse_config, - key_bindings: &self.key_bindings[..], - mouse_bindings: &self.mouse_bindings[..], - save_to_clipboard: self.save_to_clipboard, - alt_send_esc: self.alt_send_esc, - }; - - let mut window_is_focused = window.is_focused; - - // Scope needed to that hide_mouse isn't borrowed after the scope - // ends. - { - let hide_mouse = &mut self.hide_mouse; - let mut process = |event| { - if print_events { - info!("glutin event: {:?}", event); - } - Processor::handle_event( - &mut processor, - event, - resize_tx, - hide_mouse, - &mut window_is_focused, - ); - }; - - for event in self.pending_events.drain(..) { - process(event); - } - - window.poll_events(process); - } - - if self.hide_mouse_when_typing { - window.set_mouse_visible(!self.hide_mouse); - } - - window.is_focused = window_is_focused; - } - - if self.window_changes.hide { - window.hide(); - } - - #[cfg(target_os = "macos")] - { - if self.window_changes.toggle_simple_fullscreen && !self.is_fullscreen { - window.set_simple_fullscreen(!self.is_simple_fullscreen); - self.is_simple_fullscreen = !self.is_simple_fullscreen; - } - } - - if self.window_changes.toggle_fullscreen && !self.is_simple_fullscreen { - window.set_fullscreen(!self.is_fullscreen); - self.is_fullscreen = !self.is_fullscreen; - } - - self.window_changes.clear(); - self.wait_for_event = !terminal.dirty; - - terminal - } - - pub fn update_config(&mut self, config: &Config) { - self.key_bindings = config.key_bindings.to_vec(); - self.mouse_bindings = config.mouse_bindings.to_vec(); - self.mouse_config = config.mouse.to_owned(); - self.save_to_clipboard = config.selection.save_to_clipboard; - self.alt_send_esc = config.alt_send_esc(); - } +/// Event Loop for notifying the renderer about terminal events +pub trait EventListener { + fn send_event(&self, event: Event); } diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs index 275d752c..ed78d79e 100644 --- a/alacritty_terminal/src/event_loop.rs +++ b/alacritty_terminal/src/event_loop.rs @@ -6,20 +6,22 @@ use std::io::{self, ErrorKind, Read, Write}; use std::marker::Send; use std::sync::Arc; -use mio::{self, Events, PollOpt, Ready}; -use mio_extras::channel::{self, Receiver, Sender}; - +use log::error; #[cfg(not(windows))] use mio::unix::UnixReady; +use mio::{self, Events, PollOpt, Ready}; +use mio_extras::channel::{self, Receiver, Sender}; use crate::ansi; -use crate::display; -use crate::event; +use crate::event::{self, Event, EventListener}; use crate::sync::FairMutex; use crate::term::Term; use crate::tty; use crate::util::thread; +/// Max bytes to read from the PTY +const MAX_READ: usize = 0x10_000; + /// Messages that may be sent to the `EventLoop` #[derive(Debug)] pub enum Msg { @@ -34,13 +36,13 @@ pub enum Msg { /// /// Handles all the pty I/O and runs the pty parser which updates terminal /// state. -pub struct EventLoop<T: tty::EventedPty> { +pub struct EventLoop<T: tty::EventedPty, U: EventListener> { poll: mio::Poll, pty: T, rx: Receiver<Msg>, tx: Sender<Msg>, - terminal: Arc<FairMutex<Term>>, - display: display::Notifier, + terminal: Arc<FairMutex<Term<U>>>, + event_proxy: U, ref_test: bool, } @@ -50,26 +52,6 @@ struct Writing { written: usize, } -/// Indicates the result of draining the mio channel -#[derive(Debug)] -enum DrainResult { - /// At least one new item was received - ReceivedItem, - /// Nothing was available to receive - Empty, - /// A shutdown message was received - Shutdown, -} - -impl DrainResult { - pub fn is_shutdown(&self) -> bool { - match *self { - DrainResult::Shutdown => true, - _ => false, - } - } -} - /// All of the mutable state needed to run the event loop /// /// Contains list of items to write, current write state, etc. Anything that @@ -155,17 +137,18 @@ impl Writing { } } -impl<T> EventLoop<T> +impl<T, U> EventLoop<T, U> where T: tty::EventedPty + Send + 'static, + U: EventListener + Send + 'static, { /// Create a new event loop pub fn new( - terminal: Arc<FairMutex<Term>>, - display: display::Notifier, + terminal: Arc<FairMutex<Term<U>>>, + event_proxy: U, pty: T, ref_test: bool, - ) -> EventLoop<T> { + ) -> EventLoop<T, U> { let (tx, rx) = channel::channel(); EventLoop { poll: mio::Poll::new().expect("create mio Poll"), @@ -173,7 +156,7 @@ where tx, rx, terminal, - display, + event_proxy, ref_test, } } @@ -184,33 +167,22 @@ where // Drain the channel // - // Returns a `DrainResult` indicating the result of receiving from the channel - // - fn drain_recv_channel(&self, state: &mut State) -> DrainResult { - let mut received_item = false; + // Returns `false` when a shutdown message was received. + fn drain_recv_channel(&self, state: &mut State) -> bool { while let Ok(msg) = self.rx.try_recv() { - received_item = true; match msg { - Msg::Input(input) => { - state.write_list.push_back(input); - }, - Msg::Shutdown => { - return DrainResult::Shutdown; - }, + Msg::Input(input) => state.write_list.push_back(input), + Msg::Shutdown => return false, } } - if received_item { - DrainResult::ReceivedItem - } else { - DrainResult::Empty - } + true } // Returns a `bool` indicating whether or not the event loop should continue running #[inline] fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool { - if self.drain_recv_channel(state).is_shutdown() { + if !self.drain_recv_channel(state) { return false; } @@ -231,13 +203,9 @@ where where X: Write, { - const MAX_READ: usize = 0x1_0000; let mut processed = 0; let mut terminal = None; - // Flag to keep track if wakeup has already been sent - let mut send_wakeup = false; - loop { match self.pty.reader().read(&mut buf[..]) { Ok(0) => break, @@ -255,14 +223,10 @@ where // Get reference to terminal. Lock is acquired on initial // iteration and held until there's no bytes left to parse // or we've reached MAX_READ. - let terminal = if terminal.is_none() { + if terminal.is_none() { terminal = Some(self.terminal.lock()); - let terminal = terminal.as_mut().unwrap(); - send_wakeup = !terminal.dirty; - terminal - } else { - terminal.as_mut().unwrap() - }; + } + let terminal = terminal.as_mut().unwrap(); // Run the parser for byte in &buf[..got] { @@ -283,13 +247,8 @@ where } } - // Only request a draw if one hasn't already been requested. - if let Some(mut terminal) = terminal { - if send_wakeup { - self.display.notify(); - terminal.dirty = true; - } - } + // Queue terminal redraw + self.event_proxy.send_event(Event::Wakeup); Ok(()) } @@ -326,10 +285,10 @@ where Ok(()) } - pub fn spawn(mut self, state: Option<State>) -> thread::JoinHandle<(Self, State)> { + pub fn spawn(mut self) -> thread::JoinHandle<(Self, State)> { thread::spawn_named("pty reader", move || { - let mut state = state.unwrap_or_else(Default::default); - let mut buf = [0u8; 0x1000]; + let mut state = State::default(); + let mut buf = [0u8; MAX_READ]; let mut tokens = (0..).map(Into::into); @@ -369,7 +328,7 @@ where token if token == self.pty.child_event_token() => { if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() { self.terminal.lock().exit(); - self.display.notify(); + self.event_proxy.send_event(Event::Wakeup); break 'event_loop; } }, diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 01c17152..5f5b84fe 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -17,6 +17,8 @@ use std::cmp::{max, min, Ordering}; use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo}; +use serde::{Deserialize, Serialize}; + use crate::index::{self, Column, IndexRange, Line, Point}; use crate::selection::Selection; diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs index f82e7eaa..058583f2 100644 --- a/alacritty_terminal/src/grid/row.rs +++ b/alacritty_terminal/src/grid/row.rs @@ -19,6 +19,8 @@ use std::ops::{Index, IndexMut}; use std::ops::{Range, RangeFrom, RangeFull, RangeTo, RangeToInclusive}; use std::slice; +use serde::{Deserialize, Serialize}; + use crate::grid::GridCell; use crate::index::Column; diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs index e04d01cd..2004cece 100644 --- a/alacritty_terminal/src/grid/storage.rs +++ b/alacritty_terminal/src/grid/storage.rs @@ -14,6 +14,7 @@ use std::ops::{Index, IndexMut}; use std::vec::Drain; +use serde::{Deserialize, Serialize}; use static_assertions::assert_eq_size; use super::Row; diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs index 7d1a8e91..0a100e18 100644 --- a/alacritty_terminal/src/index.rs +++ b/alacritty_terminal/src/index.rs @@ -19,6 +19,8 @@ use std::cmp::{Ord, Ordering}; use std::fmt; use std::ops::{self, Add, AddAssign, Deref, Range, RangeInclusive, Sub, SubAssign}; +use serde::{Deserialize, Serialize}; + use crate::term::RenderableCell; /// The side of a cell @@ -77,8 +79,8 @@ impl From<Point> for Point<usize> { } } -impl From<&RenderableCell> for Point<Line> { - fn from(cell: &RenderableCell) -> Self { +impl From<RenderableCell> for Point<Line> { + fn from(cell: RenderableCell) -> Self { Point::new(cell.line, cell.column) } } diff --git a/alacritty_terminal/src/input.rs b/alacritty_terminal/src/input.rs deleted file mode 100644 index e268bf62..00000000 --- a/alacritty_terminal/src/input.rs +++ /dev/null @@ -1,1538 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! Handle input from glutin -//! -//! Certain key combinations should send some escape sequence back to the pty. -//! In order to figure that out, state about which modifier keys are pressed -//! needs to be tracked. Additionally, we need a bit of a state machine to -//! determine what to do when a non-modifier key is pressed. -use crate::url::Url; -use std::borrow::Cow; -use std::mem; -use std::time::Instant; - -use glutin::{ - ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta, - TouchPhase, VirtualKeyCode, -}; - -use crate::ansi::{ClearMode, Handler}; -use crate::clipboard::ClipboardType; -use crate::config::{self, Key}; -use crate::event::{ClickState, Mouse}; -use crate::grid::Scroll; -use crate::index::{Column, Line, Point, Side}; -use crate::message_bar::{self, Message}; -use crate::term::mode::TermMode; -use crate::term::{SizeInfo, Term}; -use crate::util::start_daemon; - -pub const FONT_SIZE_STEP: f32 = 0.5; - -/// Processes input from glutin. -/// -/// An escape sequence may be emitted in case specific keys or key combinations -/// are activated. -pub struct Processor<'a, A: 'a> { - pub key_bindings: &'a [KeyBinding], - pub mouse_bindings: &'a [MouseBinding], - pub mouse_config: &'a config::Mouse, - pub scrolling_config: &'a config::Scrolling, - pub ctx: A, - pub save_to_clipboard: bool, - pub alt_send_esc: bool, -} - -pub trait ActionContext { - fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _: B); - fn size_info(&self) -> SizeInfo; - fn copy_selection(&mut self, _: ClipboardType); - fn clear_selection(&mut self); - fn update_selection(&mut self, point: Point, side: Side); - fn simple_selection(&mut self, point: Point, side: Side); - fn block_selection(&mut self, point: Point, side: Side); - fn semantic_selection(&mut self, point: Point); - fn line_selection(&mut self, point: Point); - fn selection_is_empty(&self) -> bool; - fn mouse_mut(&mut self) -> &mut Mouse; - fn mouse(&self) -> &Mouse; - fn mouse_coords(&self) -> Option<Point>; - fn received_count(&mut self) -> &mut usize; - fn suppress_chars(&mut self) -> &mut bool; - fn modifiers(&mut self) -> &mut Modifiers; - fn scroll(&mut self, scroll: Scroll); - fn hide_window(&mut self); - fn terminal(&self) -> &Term; - fn terminal_mut(&mut self) -> &mut Term; - fn spawn_new_instance(&mut self); - fn toggle_fullscreen(&mut self); - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self); -} - -#[derive(Debug, Default, Copy, Clone)] -pub struct Modifiers { - mods: ModifiersState, - lshift: bool, - rshift: bool, -} - -impl Modifiers { - pub fn update(&mut self, input: KeyboardInput) { - match input.virtual_keycode { - Some(VirtualKeyCode::LShift) => self.lshift = input.state == ElementState::Pressed, - Some(VirtualKeyCode::RShift) => self.rshift = input.state == ElementState::Pressed, - _ => (), - } - - self.mods = input.modifiers; - } - - pub fn shift(self) -> bool { - self.lshift || self.rshift - } - - pub fn ctrl(self) -> bool { - self.mods.ctrl - } - - pub fn logo(self) -> bool { - self.mods.logo - } - - pub fn alt(self) -> bool { - self.mods.alt - } -} - -impl From<&mut Modifiers> for ModifiersState { - fn from(mods: &mut Modifiers) -> ModifiersState { - ModifiersState { shift: mods.shift(), ..mods.mods } - } -} - -/// Describes a state and action to take in that state -/// -/// This is the shared component of `MouseBinding` and `KeyBinding` -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Binding<T> { - /// Modifier keys required to activate binding - pub mods: ModifiersState, - - /// String to send to pty if mods and mode match - pub action: Action, - - /// Terminal mode required to activate binding - pub mode: TermMode, - - /// excluded terminal modes where the binding won't be activated - pub notmode: TermMode, - - /// This property is used as part of the trigger detection code. - /// - /// For example, this might be a key like "G", or a mouse button. - pub trigger: T, -} - -/// Bindings that are triggered by a keyboard key -pub type KeyBinding = Binding<Key>; - -/// Bindings that are triggered by a mouse button -pub type MouseBinding = Binding<MouseButton>; - -impl Default for KeyBinding { - fn default() -> KeyBinding { - KeyBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: Key::A, - } - } -} - -impl Default for MouseBinding { - fn default() -> MouseBinding { - MouseBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: MouseButton::Left, - } - } -} - -impl<T: Eq> Binding<T> { - #[inline] - fn is_triggered_by( - &self, - mode: TermMode, - mods: ModifiersState, - input: &T, - relaxed: bool, - ) -> bool { - // Check input first since bindings are stored in one big list. This is - // the most likely item to fail so prioritizing it here allows more - // checks to be short circuited. - self.trigger == *input - && mode.contains(self.mode) - && !mode.intersects(self.notmode) - && self.mods_match(mods, relaxed) - } - - #[inline] - pub fn triggers_match(&self, binding: &Binding<T>) -> bool { - // Check the binding's key and modifiers - if self.trigger != binding.trigger || self.mods != binding.mods { - return false; - } - - // Completely empty modes match all modes - if (self.mode.is_empty() && self.notmode.is_empty()) - || (binding.mode.is_empty() && binding.notmode.is_empty()) - { - return true; - } - - // Check for intersection (equality is required since empty does not intersect itself) - (self.mode == binding.mode || self.mode.intersects(binding.mode)) - && (self.notmode == binding.notmode || self.notmode.intersects(binding.notmode)) - } -} - -impl<T> Binding<T> { - /// Execute the action associate with this binding - #[inline] - fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) { - self.action.execute(ctx, mouse_mode) - } - - /// Check that two mods descriptions for equivalence - #[inline] - fn mods_match(&self, mods: ModifiersState, relaxed: bool) -> bool { - if relaxed { - self.mods.relaxed_eq(mods) - } else { - self.mods == mods - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub enum Action { - /// Write an escape sequence. - #[serde(skip)] - Esc(String), - - /// Paste contents of system clipboard. - Paste, - - /// Store current selection into clipboard. - Copy, - - /// Paste contents of selection buffer. - PasteSelection, - - /// Increase font size. - IncreaseFontSize, - - /// Decrease font size. - DecreaseFontSize, - - /// Reset font size to the config value. - ResetFontSize, - - /// Scroll exactly one page up. - ScrollPageUp, - - /// Scroll exactly one page down. - ScrollPageDown, - - /// Scroll one line up. - ScrollLineUp, - - /// Scroll one line down. - ScrollLineDown, - - /// Scroll all the way to the top. - ScrollToTop, - - /// Scroll all the way to the bottom. - ScrollToBottom, - - /// Clear the display buffer(s) to remove history. - ClearHistory, - - /// Run given command. - #[serde(skip)] - Command(String, Vec<String>), - - /// Hide the Alacritty window. - Hide, - - /// Quit Alacritty. - Quit, - - /// Clear warning and error notices. - ClearLogNotice, - - /// Spawn a new instance of Alacritty. - SpawnNewInstance, - - /// Toggle fullscreen. - ToggleFullscreen, - - /// Toggle simple fullscreen on macos. - #[cfg(target_os = "macos")] - ToggleSimpleFullscreen, - - /// Allow receiving char input. - ReceiveChar, - - /// No action. - None, -} - -impl Default for Action { - fn default() -> Action { - Action::None - } -} - -impl Action { - #[inline] - fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) { - match *self { - Action::Esc(ref s) => { - ctx.scroll(Scroll::Bottom); - ctx.write_to_pty(s.clone().into_bytes()) - }, - Action::Copy => { - ctx.copy_selection(ClipboardType::Clipboard); - }, - Action::Paste => { - let text = ctx.terminal_mut().clipboard().load(ClipboardType::Clipboard); - self.paste(ctx, &text); - }, - Action::PasteSelection => { - // Only paste if mouse events are not captured by an application - if !mouse_mode { - let text = ctx.terminal_mut().clipboard().load(ClipboardType::Selection); - self.paste(ctx, &text); - } - }, - Action::Command(ref program, ref args) => { - trace!("Running command {} with args {:?}", program, args); - - match start_daemon(program, args) { - Ok(_) => debug!("Spawned new proc"), - Err(err) => warn!("Couldn't run command {}", err), - } - }, - Action::ToggleFullscreen => { - ctx.toggle_fullscreen(); - }, - #[cfg(target_os = "macos")] - Action::ToggleSimpleFullscreen => { - ctx.toggle_simple_fullscreen(); - }, - Action::Hide => { - ctx.hide_window(); - }, - Action::Quit => { - ctx.terminal_mut().exit(); - }, - Action::IncreaseFontSize => { - ctx.terminal_mut().change_font_size(FONT_SIZE_STEP); - }, - Action::DecreaseFontSize => { - ctx.terminal_mut().change_font_size(-FONT_SIZE_STEP); - }, - Action::ResetFontSize => { - ctx.terminal_mut().reset_font_size(); - }, - Action::ScrollPageUp => { - ctx.scroll(Scroll::PageUp); - }, - Action::ScrollPageDown => { - ctx.scroll(Scroll::PageDown); - }, - Action::ScrollLineUp => { - ctx.scroll(Scroll::Lines(1)); - }, - Action::ScrollLineDown => { - ctx.scroll(Scroll::Lines(-1)); - }, - Action::ScrollToTop => { - ctx.scroll(Scroll::Top); - }, - Action::ScrollToBottom => { - ctx.scroll(Scroll::Bottom); - }, - Action::ClearHistory => { - ctx.terminal_mut().clear_screen(ClearMode::Saved); - }, - Action::ClearLogNotice => { - ctx.terminal_mut().message_buffer_mut().pop(); - }, - Action::SpawnNewInstance => { - ctx.spawn_new_instance(); - }, - Action::ReceiveChar | Action::None => (), - } - } - - fn paste<A: ActionContext>(&self, ctx: &mut A, contents: &str) { - if ctx.terminal().mode().contains(TermMode::BRACKETED_PASTE) { - ctx.write_to_pty(&b"\x1b[200~"[..]); - ctx.write_to_pty(contents.replace("\x1b", "").into_bytes()); - ctx.write_to_pty(&b"\x1b[201~"[..]); - } else { - // In non-bracketed (ie: normal) mode, terminal applications cannot distinguish - // pasted data from keystrokes. - // In theory, we should construct the keystrokes needed to produce the data we are - // pasting... since that's neither practical nor sensible (and probably an impossible - // task to solve in a general way), we'll just replace line breaks (windows and unix - // style) with a single carriage return (\r, which is what the Enter key produces). - ctx.write_to_pty(contents.replace("\r\n", "\r").replace("\n", "\r").into_bytes()); - } - } -} - -trait RelaxedEq<T: ?Sized = Self> { - fn relaxed_eq(&self, other: T) -> bool; -} - -impl RelaxedEq for ModifiersState { - // Make sure that modifiers in the config are always present, - // but ignore surplus modifiers. - fn relaxed_eq(&self, other: Self) -> bool { - (!self.logo || other.logo) - && (!self.alt || other.alt) - && (!self.ctrl || other.ctrl) - && (!self.shift || other.shift) - } -} - -impl From<&'static str> for Action { - fn from(s: &'static str) -> Action { - Action::Esc(s.into()) - } -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum MouseState { - Url(Url), - MessageBar, - MessageBarButton, - Mouse, - Text, -} - -impl<'a, A: ActionContext + 'a> Processor<'a, A> { - fn mouse_state(&mut self, point: Point, mods: ModifiersState) -> MouseState { - let mouse_mode = - TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; - - // Check message bar before URL to ignore URLs in the message bar - if let Some(message) = self.message_at_point(Some(point)) { - if self.message_close_at_point(point, message) { - return MouseState::MessageBarButton; - } else { - return MouseState::MessageBar; - } - } - - // Check for URL at point with required modifiers held - if self.mouse_config.url.mods().relaxed_eq(mods) - && (!self.ctx.terminal().mode().intersects(mouse_mode) || mods.shift) - && self.mouse_config.url.launcher.is_some() - && self.ctx.selection_is_empty() - && self.ctx.mouse().left_button_state != ElementState::Pressed - { - let buffer_point = self.ctx.terminal().visible_to_buffer(point); - if let Some(url) = - self.ctx.terminal().urls().drain(..).find(|url| url.contains(buffer_point)) - { - return MouseState::Url(url); - } - } - - if self.ctx.terminal().mode().intersects(mouse_mode) && !self.ctx.modifiers().shift() { - MouseState::Mouse - } else { - MouseState::Text - } - } - - #[inline] - pub fn mouse_moved(&mut self, x: usize, y: usize, modifiers: ModifiersState) { - self.ctx.mouse_mut().x = x; - self.ctx.mouse_mut().y = y; - - let size_info = self.ctx.size_info(); - let point = size_info.pixels_to_coords(x, y); - - let cell_side = self.get_mouse_side(); - let prev_side = mem::replace(&mut self.ctx.mouse_mut().cell_side, cell_side); - let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line); - let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col); - - let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; - let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode; - - let cell_changed = - prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column; - - // If the mouse hasn't changed cells, do nothing - if !cell_changed && prev_side == cell_side { - return; - } - - // Don't launch URLs if mouse has moved - self.ctx.mouse_mut().block_url_launcher = true; - - let mouse_state = self.mouse_state(point, modifiers); - self.update_mouse_cursor(mouse_state); - match mouse_state { - MouseState::Url(url) => { - let url_bounds = url.linear_bounds(self.ctx.terminal()); - self.ctx.terminal_mut().set_url_highlight(url_bounds); - }, - MouseState::MessageBar | MouseState::MessageBarButton => { - self.ctx.terminal_mut().reset_url_highlight(); - return; - }, - _ => self.ctx.terminal_mut().reset_url_highlight(), - } - - if self.ctx.mouse().left_button_state == ElementState::Pressed - && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode)) - { - self.ctx.update_selection(Point { line: point.line, col: point.col }, cell_side); - } else if self.ctx.terminal().mode().intersects(motion_mode) - && size_info.contains_point(x, y, false) - && cell_changed - { - if self.ctx.mouse().left_button_state == ElementState::Pressed { - self.mouse_report(32, ElementState::Pressed, modifiers); - } else if self.ctx.mouse().middle_button_state == ElementState::Pressed { - self.mouse_report(33, ElementState::Pressed, modifiers); - } else if self.ctx.mouse().right_button_state == ElementState::Pressed { - self.mouse_report(34, ElementState::Pressed, modifiers); - } else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) { - self.mouse_report(35, ElementState::Pressed, modifiers); - } - } - } - - fn get_mouse_side(&self) -> Side { - let size_info = self.ctx.size_info(); - let x = self.ctx.mouse().x; - - let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize; - let half_cell_width = (size_info.cell_width / 2.0) as usize; - - let additional_padding = - (size_info.width - size_info.padding_x * 2.) % size_info.cell_width; - let end_of_grid = size_info.width - size_info.padding_x - additional_padding; - - if cell_x > half_cell_width - // Edge case when mouse leaves the window - || x as f32 >= end_of_grid - { - Side::Right - } else { - Side::Left - } - } - - pub fn normal_mouse_report(&mut self, button: u8) { - let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column); - - if line < Line(223) && column < Column(223) { - let msg = vec![ - b'\x1b', - b'[', - b'M', - 32 + button, - 32 + 1 + column.0 as u8, - 32 + 1 + line.0 as u8, - ]; - - self.ctx.write_to_pty(msg); - } - } - - pub fn sgr_mouse_report(&mut self, button: u8, state: ElementState) { - let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column); - let c = match state { - ElementState::Pressed => 'M', - ElementState::Released => 'm', - }; - - let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c); - self.ctx.write_to_pty(msg.into_bytes()); - } - - pub fn mouse_report(&mut self, button: u8, state: ElementState, modifiers: ModifiersState) { - // Calculate modifiers value - let mut mods = 0; - if modifiers.shift { - mods += 4; - } - if modifiers.alt { - mods += 8; - } - if modifiers.ctrl { - mods += 16; - } - - // Report mouse events - if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) { - self.sgr_mouse_report(button + mods, state); - } else if let ElementState::Released = state { - self.normal_mouse_report(3 + mods); - } else { - self.normal_mouse_report(button + mods); - } - } - - pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Option<Point>) { - if let (Some(point), true) = (point, button == MouseButton::Left) { - self.ctx.semantic_selection(point); - } - } - - pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Option<Point>) { - if let (Some(point), true) = (point, button == MouseButton::Left) { - self.ctx.line_selection(point); - } - } - - pub fn on_mouse_press( - &mut self, - button: MouseButton, - modifiers: ModifiersState, - point: Option<Point>, - ) { - let now = Instant::now(); - let elapsed = self.ctx.mouse().last_click_timestamp.elapsed(); - self.ctx.mouse_mut().last_click_timestamp = now; - - let button_changed = self.ctx.mouse().last_button != button; - - self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state { - ClickState::Click - if !button_changed && elapsed < self.mouse_config.double_click.threshold => - { - self.ctx.mouse_mut().block_url_launcher = true; - self.on_mouse_double_click(button, point); - ClickState::DoubleClick - } - ClickState::DoubleClick - if !button_changed && elapsed < self.mouse_config.triple_click.threshold => - { - self.ctx.mouse_mut().block_url_launcher = true; - self.on_mouse_triple_click(button, point); - ClickState::TripleClick - } - _ => { - // Don't launch URLs if this click cleared the selection - self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty(); - - self.ctx.clear_selection(); - - // Start new empty selection - let side = self.ctx.mouse().cell_side; - if let Some(point) = point { - if modifiers.ctrl { - self.ctx.block_selection(point, side); - } else { - self.ctx.simple_selection(point, side); - } - } - - let report_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { - let code = match button { - MouseButton::Left => 0, - MouseButton::Middle => 1, - MouseButton::Right => 2, - // Can't properly report more than three buttons. - MouseButton::Other(_) => return, - }; - self.mouse_report(code, ElementState::Pressed, modifiers); - return; - } - - ClickState::Click - }, - }; - } - - pub fn on_mouse_release( - &mut self, - button: MouseButton, - modifiers: ModifiersState, - point: Option<Point>, - ) { - let report_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { - let code = match button { - MouseButton::Left => 0, - MouseButton::Middle => 1, - MouseButton::Right => 2, - // Can't properly report more than three buttons. - MouseButton::Other(_) => return, - }; - self.mouse_report(code, ElementState::Released, modifiers); - return; - } else if let (Some(point), true) = (point, button == MouseButton::Left) { - let mouse_state = self.mouse_state(point, modifiers); - self.update_mouse_cursor(mouse_state); - if let MouseState::Url(url) = mouse_state { - let url_bounds = url.linear_bounds(self.ctx.terminal()); - self.ctx.terminal_mut().set_url_highlight(url_bounds); - self.launch_url(url); - } - } - - self.copy_selection(); - } - - /// Spawn URL launcher when clicking on URLs. - fn launch_url(&self, url: Url) { - if self.ctx.mouse().block_url_launcher { - return; - } - - if let Some(ref launcher) = self.mouse_config.url.launcher { - let mut args = launcher.args().to_vec(); - args.push(self.ctx.terminal().url_to_string(url)); - - match start_daemon(launcher.program(), &args) { - Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args), - Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args), - } - } - } - - pub fn on_mouse_wheel( - &mut self, - delta: MouseScrollDelta, - phase: TouchPhase, - modifiers: ModifiersState, - ) { - match delta { - MouseScrollDelta::LineDelta(_columns, lines) => { - let new_scroll_px = lines * self.ctx.size_info().cell_height; - self.scroll_terminal(modifiers, new_scroll_px as i32); - }, - MouseScrollDelta::PixelDelta(lpos) => { - match phase { - TouchPhase::Started => { - // Reset offset to zero - self.ctx.mouse_mut().scroll_px = 0; - }, - TouchPhase::Moved => { - self.scroll_terminal(modifiers, lpos.y as i32); - }, - _ => (), - } - }, - } - } - - fn scroll_terminal(&mut self, modifiers: ModifiersState, new_scroll_px: i32) { - let mouse_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - let height = self.ctx.size_info().cell_height as i32; - - // Make sure the new and deprecated setting are both allowed - let faux_multiplier = self.scrolling_config.faux_multiplier() as usize; - - if self.ctx.terminal().mode().intersects(mouse_modes) { - self.ctx.mouse_mut().scroll_px += new_scroll_px; - - let code = if new_scroll_px > 0 { 64 } else { 65 }; - let lines = (self.ctx.mouse().scroll_px / height).abs(); - - for _ in 0..lines { - self.mouse_report(code, ElementState::Pressed, modifiers); - } - } else if self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN) - && faux_multiplier > 0 - && !modifiers.shift - { - self.ctx.mouse_mut().scroll_px += new_scroll_px * faux_multiplier as i32; - - let cmd = if new_scroll_px > 0 { b'A' } else { b'B' }; - let lines = (self.ctx.mouse().scroll_px / height).abs(); - - let mut content = Vec::with_capacity(lines as usize * 3); - for _ in 0..lines { - content.push(0x1b); - content.push(b'O'); - content.push(cmd); - } - self.ctx.write_to_pty(content); - } else { - let multiplier = i32::from(self.scrolling_config.multiplier()); - self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; - - let lines = self.ctx.mouse().scroll_px / height; - - self.ctx.scroll(Scroll::Lines(lines as isize)); - } - - self.ctx.mouse_mut().scroll_px %= height; - } - - pub fn on_focus_change(&mut self, is_focused: bool) { - if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) { - let chr = if is_focused { "I" } else { "O" }; - - let msg = format!("\x1b[{}", chr); - self.ctx.write_to_pty(msg.into_bytes()); - } - } - - pub fn mouse_input( - &mut self, - state: ElementState, - button: MouseButton, - modifiers: ModifiersState, - ) { - match button { - MouseButton::Left => self.ctx.mouse_mut().left_button_state = state, - MouseButton::Middle => self.ctx.mouse_mut().middle_button_state = state, - MouseButton::Right => self.ctx.mouse_mut().right_button_state = state, - _ => (), - } - - let point = self.ctx.mouse_coords(); - - // Skip normal mouse events if the message bar has been clicked - if let Some(message) = self.message_at_point(point) { - // Message should never be `Some` if point is `None` - debug_assert!(point.is_some()); - self.on_message_bar_click(state, point.unwrap(), message, modifiers); - } else { - match state { - ElementState::Pressed => { - self.process_mouse_bindings(modifiers, button); - self.on_mouse_press(button, modifiers, point); - }, - ElementState::Released => self.on_mouse_release(button, modifiers, point), - } - } - - self.ctx.mouse_mut().last_button = button; - } - - /// Process key input. - pub fn process_key(&mut self, input: KeyboardInput) { - self.ctx.modifiers().update(input); - - // Update mouse cursor for temporarily disabling mouse mode - if input.virtual_keycode == Some(VirtualKeyCode::LShift) - || input.virtual_keycode == Some(VirtualKeyCode::RShift) - { - if let Some(point) = self.ctx.mouse_coords() { - let mods = self.ctx.modifiers().into(); - let mouse_state = self.mouse_state(point, mods); - self.update_mouse_cursor(mouse_state); - } - } - - match input.state { - ElementState::Pressed => { - *self.ctx.received_count() = 0; - self.process_key_bindings(input); - }, - ElementState::Released => *self.ctx.suppress_chars() = false, - } - } - - /// Process a received character. - pub fn received_char(&mut self, c: char) { - if *self.ctx.suppress_chars() { - return; - } - - self.ctx.scroll(Scroll::Bottom); - self.ctx.clear_selection(); - - let utf8_len = c.len_utf8(); - let mut bytes = Vec::with_capacity(utf8_len); - unsafe { - bytes.set_len(utf8_len); - c.encode_utf8(&mut bytes[..]); - } - - if self.alt_send_esc - && *self.ctx.received_count() == 0 - && self.ctx.modifiers().alt() - && utf8_len == 1 - { - bytes.insert(0, b'\x1b'); - } - - self.ctx.write_to_pty(bytes); - - *self.ctx.received_count() += 1; - } - - /// Attempt to find a binding and execute its action. - /// - /// The provided mode, mods, and key must match what is allowed by a binding - /// for its action to be executed. - fn process_key_bindings(&mut self, input: KeyboardInput) { - let mode = *self.ctx.terminal().mode(); - - *self.ctx.suppress_chars() = self - .key_bindings - .iter() - .filter(|binding| { - let key = match (binding.trigger, input.virtual_keycode) { - (Key::Scancode(_), _) => Key::Scancode(input.scancode), - (_, Some(key)) => Key::from_glutin_input(key), - _ => return false, - }; - - binding.is_triggered_by(mode, input.modifiers, &key, false) - }) - .fold(None, |suppress_chars, binding| { - // Binding was triggered; run the action - binding.execute(&mut self.ctx, false); - - // Don't suppress when there has been a `ReceiveChar` action - Some(suppress_chars.unwrap_or(true) && binding.action != Action::ReceiveChar) - }) - // Don't suppress char if no bindings were triggered - .unwrap_or(false); - } - - /// Attempt to find a binding and execute its action. - /// - /// The provided mode, mods, and key must match what is allowed by a binding - /// for its action to be executed. - fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) { - for binding in self.mouse_bindings { - if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) { - // binding was triggered; run the action - let mouse_mode = !mods.shift - && self.ctx.terminal().mode().intersects( - TermMode::MOUSE_REPORT_CLICK - | TermMode::MOUSE_DRAG - | TermMode::MOUSE_MOTION, - ); - binding.execute(&mut self.ctx, mouse_mode); - } - } - } - - /// Return the message bar's message if there is some at the specified point - fn message_at_point(&mut self, point: Option<Point>) -> Option<Message> { - if let (Some(point), Some(message)) = - (point, self.ctx.terminal_mut().message_buffer_mut().message()) - { - let size = self.ctx.size_info(); - if point.line.0 >= size.lines().saturating_sub(message.text(&size).len()) { - return Some(message); - } - } - - None - } - - /// Whether the point is over the message bar's close button - fn message_close_at_point(&self, point: Point, message: Message) -> bool { - let size = self.ctx.size_info(); - point.col + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols() - && point.line == size.lines() - message.text(&size).len() - } - - /// Handle clicks on the message bar. - fn on_message_bar_click( - &mut self, - button_state: ElementState, - point: Point, - message: Message, - mods: ModifiersState, - ) { - match button_state { - ElementState::Released => self.copy_selection(), - ElementState::Pressed => { - if self.message_close_at_point(point, message) { - let mouse_state = self.mouse_state(point, mods); - self.update_mouse_cursor(mouse_state); - self.ctx.terminal_mut().message_buffer_mut().pop(); - } - - self.ctx.clear_selection(); - }, - } - } - - /// Copy text selection. - fn copy_selection(&mut self) { - if self.save_to_clipboard { - self.ctx.copy_selection(ClipboardType::Clipboard); - } - self.ctx.copy_selection(ClipboardType::Selection); - } - - #[inline] - fn update_mouse_cursor(&mut self, mouse_state: MouseState) { - let mouse_cursor = match mouse_state { - MouseState::Url(_) | MouseState::MessageBarButton => MouseCursor::Hand, - MouseState::Text => MouseCursor::Text, - _ => MouseCursor::Default, - }; - - self.ctx.terminal_mut().set_mouse_cursor(mouse_cursor); - } -} - -#[cfg(test)] -mod tests { - use std::borrow::Cow; - use std::time::Duration; - - use glutin::{ElementState, Event, ModifiersState, MouseButton, VirtualKeyCode, WindowEvent}; - - use crate::clipboard::{Clipboard, ClipboardType}; - use crate::config::{self, ClickHandler, Config}; - use crate::event::{ClickState, Mouse, WindowChanges}; - use crate::grid::Scroll; - use crate::index::{Point, Side}; - use crate::message_bar::MessageBuffer; - use crate::selection::Selection; - use crate::term::{SizeInfo, Term, TermMode}; - - use super::{Action, Binding, Modifiers, Processor}; - - const KEY: VirtualKeyCode = VirtualKeyCode::Key0; - - type MockBinding = Binding<usize>; - - impl Default for MockBinding { - fn default() -> Self { - Self { - mods: Default::default(), - action: Default::default(), - mode: TermMode::empty(), - notmode: TermMode::empty(), - trigger: Default::default(), - } - } - } - - #[test] - fn binding_matches_itself() { - let binding = MockBinding::default(); - let identical_binding = MockBinding::default(); - - assert!(binding.triggers_match(&identical_binding)); - assert!(identical_binding.triggers_match(&binding)); - } - - #[test] - fn binding_matches_different_action() { - let binding = MockBinding::default(); - let mut different_action = MockBinding::default(); - different_action.action = Action::ClearHistory; - - assert!(binding.triggers_match(&different_action)); - assert!(different_action.triggers_match(&binding)); - } - - #[test] - fn mods_binding_requires_strict_match() { - let mut superset_mods = MockBinding::default(); - superset_mods.mods = ModifiersState { alt: true, logo: true, ctrl: true, shift: true }; - let mut subset_mods = MockBinding::default(); - subset_mods.mods = ModifiersState { alt: true, logo: false, ctrl: false, shift: false }; - - assert!(!superset_mods.triggers_match(&subset_mods)); - assert!(!subset_mods.triggers_match(&superset_mods)); - } - - #[test] - fn binding_matches_identical_mode() { - let mut b1 = MockBinding::default(); - b1.mode = TermMode::ALT_SCREEN; - let mut b2 = MockBinding::default(); - b2.mode = TermMode::ALT_SCREEN; - - assert!(b1.triggers_match(&b2)); - } - - #[test] - fn binding_without_mode_matches_any_mode() { - let b1 = MockBinding::default(); - let mut b2 = MockBinding::default(); - b2.mode = TermMode::APP_KEYPAD; - b2.notmode = TermMode::ALT_SCREEN; - - assert!(b1.triggers_match(&b2)); - } - - #[test] - fn binding_with_mode_matches_empty_mode() { - let mut b1 = MockBinding::default(); - b1.mode = TermMode::APP_KEYPAD; - b1.notmode = TermMode::ALT_SCREEN; - let b2 = MockBinding::default(); - - assert!(b1.triggers_match(&b2)); - } - - #[test] - fn binding_matches_superset_mode() { - let mut b1 = MockBinding::default(); - b1.mode = TermMode::APP_KEYPAD; - let mut b2 = MockBinding::default(); - b2.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD; - - assert!(b1.triggers_match(&b2)); - } - - #[test] - fn binding_matches_subset_mode() { - let mut b1 = MockBinding::default(); - b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD; - let mut b2 = MockBinding::default(); - b2.mode = TermMode::APP_KEYPAD; - - assert!(b1.triggers_match(&b2)); - } - - #[test] - fn binding_matches_partial_intersection() { - let mut b1 = MockBinding::default(); - b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD; - let mut b2 = MockBinding::default(); - b2.mode = TermMode::APP_KEYPAD | TermMode::APP_CURSOR; - - assert!(b1.triggers_match(&b2)); - } - - #[test] - fn binding_mismatches_notmode() { - let mut b1 = MockBinding::default(); - b1.mode = TermMode::ALT_SCREEN; - let mut b2 = MockBinding::default(); - b2.notmode = TermMode::ALT_SCREEN; - - assert!(!b1.triggers_match(&b2)); - } - - #[test] - fn binding_mismatches_unrelated() { - let mut b1 = MockBinding::default(); - b1.mode = TermMode::ALT_SCREEN; - let mut b2 = MockBinding::default(); - b2.mode = TermMode::APP_KEYPAD; - - assert!(!b1.triggers_match(&b2)); - } - - #[test] - fn binding_trigger_input() { - let mut binding = MockBinding::default(); - binding.trigger = 13; - - let mods = binding.mods; - let mode = binding.mode; - - assert!(binding.is_triggered_by(mode, mods, &13, true)); - assert!(!binding.is_triggered_by(mode, mods, &32, true)); - } - - #[test] - fn binding_trigger_mods() { - let mut binding = MockBinding::default(); - binding.mods = ModifiersState { alt: true, logo: true, ctrl: false, shift: false }; - - let superset_mods = ModifiersState { alt: true, logo: true, ctrl: true, shift: true }; - let subset_mods = ModifiersState { alt: false, logo: false, ctrl: false, shift: false }; - - let t = binding.trigger; - let mode = binding.mode; - - assert!(binding.is_triggered_by(mode, binding.mods, &t, true)); - assert!(binding.is_triggered_by(mode, binding.mods, &t, false)); - - assert!(binding.is_triggered_by(mode, superset_mods, &t, true)); - assert!(!binding.is_triggered_by(mode, superset_mods, &t, false)); - - assert!(!binding.is_triggered_by(mode, subset_mods, &t, true)); - assert!(!binding.is_triggered_by(mode, subset_mods, &t, false)); - } - - #[test] - fn binding_trigger_modes() { - let mut binding = MockBinding::default(); - binding.mode = TermMode::ALT_SCREEN; - - let t = binding.trigger; - let mods = binding.mods; - - assert!(!binding.is_triggered_by(TermMode::INSERT, mods, &t, true)); - assert!(binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true)); - assert!(binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true)); - } - - #[test] - fn binding_trigger_notmodes() { - let mut binding = MockBinding::default(); - binding.notmode = TermMode::ALT_SCREEN; - - let t = binding.trigger; - let mods = binding.mods; - - assert!(binding.is_triggered_by(TermMode::INSERT, mods, &t, true)); - assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true)); - assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true)); - } - - #[derive(PartialEq)] - enum MultiClick { - DoubleClick, - TripleClick, - None, - } - - struct ActionContext<'a> { - pub terminal: &'a mut Term, - pub selection: &'a mut Option<Selection>, - pub size_info: &'a SizeInfo, - pub mouse: &'a mut Mouse, - pub last_action: MultiClick, - pub received_count: usize, - pub suppress_chars: bool, - pub modifiers: Modifiers, - pub window_changes: &'a mut WindowChanges, - } - - impl<'a> super::ActionContext for ActionContext<'a> { - fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) {} - - fn update_selection(&mut self, _point: Point, _side: Side) {} - - fn simple_selection(&mut self, _point: Point, _side: Side) {} - - fn block_selection(&mut self, _point: Point, _side: Side) {} - - fn copy_selection(&mut self, _: ClipboardType) {} - - fn clear_selection(&mut self) {} - - fn hide_window(&mut self) {} - - fn spawn_new_instance(&mut self) {} - - fn toggle_fullscreen(&mut self) {} - - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self) {} - - fn terminal(&self) -> &Term { - &self.terminal - } - - fn terminal_mut(&mut self) -> &mut Term { - &mut self.terminal - } - - fn size_info(&self) -> SizeInfo { - *self.size_info - } - - fn semantic_selection(&mut self, _point: Point) { - // set something that we can check for here - self.last_action = MultiClick::DoubleClick; - } - - fn line_selection(&mut self, _point: Point) { - self.last_action = MultiClick::TripleClick; - } - - fn selection_is_empty(&self) -> bool { - true - } - - fn scroll(&mut self, scroll: Scroll) { - self.terminal.scroll_display(scroll); - } - - fn mouse_coords(&self) -> Option<Point> { - self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) - } - - #[inline] - fn mouse_mut(&mut self) -> &mut Mouse { - self.mouse - } - - #[inline] - fn mouse(&self) -> &Mouse { - self.mouse - } - - fn received_count(&mut self) -> &mut usize { - &mut self.received_count - } - - fn suppress_chars(&mut self) -> &mut bool { - &mut self.suppress_chars - } - - fn modifiers(&mut self) -> &mut Modifiers { - &mut self.modifiers - } - } - - macro_rules! test_clickstate { - { - name: $name:ident, - initial_state: $initial_state:expr, - initial_button: $initial_button:expr, - input: $input:expr, - end_state: $end_state:pat, - last_action: $last_action:expr - } => { - #[test] - fn $name() { - let config = Config::default(); - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - - let mut terminal = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); - - let mut mouse = Mouse::default(); - mouse.click_state = $initial_state; - mouse.last_button = $initial_button; - - let mut selection = None; - - let context = ActionContext { - terminal: &mut terminal, - selection: &mut selection, - mouse: &mut mouse, - size_info: &size, - last_action: MultiClick::None, - received_count: 0, - suppress_chars: false, - modifiers: Default::default(), - window_changes: &mut WindowChanges::default(), - }; - - let mut processor = Processor { - ctx: context, - mouse_config: &config::Mouse { - double_click: ClickHandler { - threshold: Duration::from_millis(1000), - }, - triple_click: ClickHandler { - threshold: Duration::from_millis(1000), - }, - hide_when_typing: false, - url: Default::default(), - }, - scrolling_config: &config::Scrolling::default(), - key_bindings: &config.key_bindings[..], - mouse_bindings: &config.mouse_bindings[..], - save_to_clipboard: config.selection.save_to_clipboard, - alt_send_esc: config.alt_send_esc(), - }; - - if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input { - processor.mouse_input(state, button, modifiers); - }; - - assert!(match processor.ctx.mouse.click_state { - $end_state => processor.ctx.last_action == $last_action, - _ => false - }); - } - } - } - - macro_rules! test_process_binding { - { - name: $name:ident, - binding: $binding:expr, - triggers: $triggers:expr, - mode: $mode:expr, - mods: $mods:expr - } => { - #[test] - fn $name() { - if $triggers { - assert!($binding.is_triggered_by($mode, $mods, &KEY, false)); - } else { - assert!(!$binding.is_triggered_by($mode, $mods, &KEY, false)); - } - } - } - } - - test_clickstate! { - name: single_click, - initial_state: ClickState::None, - initial_button: MouseButton::Other(0), - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::Click, - last_action: MultiClick::None - } - - test_clickstate! { - name: double_click, - initial_state: ClickState::Click, - initial_button: MouseButton::Left, - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::DoubleClick, - last_action: MultiClick::DoubleClick - } - - test_clickstate! { - name: triple_click, - initial_state: ClickState::DoubleClick, - initial_button: MouseButton::Left, - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::TripleClick, - last_action: MultiClick::TripleClick - } - - test_clickstate! { - name: multi_click_separate_buttons, - initial_state: ClickState::DoubleClick, - initial_button: MouseButton::Left, - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Right, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::Click, - last_action: MultiClick::None - } - - test_process_binding! { - name: process_binding_nomode_shiftmod_require_shift, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::NONE, - mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_nomod_require_shift, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: false, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_controlmod, - binding: Binding { trigger: KEY, mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }, action: Action::from("\x1b[1;5D"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::NONE, - mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_nomod_require_not_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[D"), mode: TermMode::NONE, notmode: TermMode::APP_CURSOR }, - triggers: true, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_appcursormode_nomod_require_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::APP_CURSOR, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_nomod_require_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE }, - triggers: false, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::APP_CURSOR | TermMode::APP_KEYPAD, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_fail_with_extra_mods, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: true }, action: Action::from("arst"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: false, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: true, logo: true } - } -} diff --git a/alacritty_terminal/src/lib.rs b/alacritty_terminal/src/lib.rs index 0bada535..dc26046d 100644 --- a/alacritty_terminal/src/lib.rs +++ b/alacritty_terminal/src/lib.rs @@ -17,27 +17,18 @@ #![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![cfg_attr(all(test, feature = "bench"), feature(test))] -#[macro_use] -extern crate log; -#[macro_use] -extern crate serde_derive; - #[cfg(target_os = "macos")] #[macro_use] extern crate objc; -#[macro_use] -pub mod macros; pub mod ansi; pub mod clipboard; pub mod config; mod cursor; -pub mod display; pub mod event; pub mod event_loop; pub mod grid; pub mod index; -pub mod input; pub mod locale; pub mod message_bar; pub mod meter; @@ -47,9 +38,8 @@ pub mod selection; pub mod sync; pub mod term; pub mod tty; -mod url; +pub mod url; pub mod util; -pub mod window; pub use crate::grid::Grid; pub use crate::term::Term; diff --git a/alacritty_terminal/src/macros.rs b/alacritty_terminal/src/macros.rs deleted file mode 100644 index 519f8b6a..00000000 --- a/alacritty_terminal/src/macros.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#[macro_export] -macro_rules! die { - ($($arg:tt)*) => {{ - error!($($arg)*); - ::std::process::exit(1); - }} -} diff --git a/alacritty_terminal/src/message_bar.rs b/alacritty_terminal/src/message_bar.rs index 8883dcb0..1382684d 100644 --- a/alacritty_terminal/src/message_bar.rs +++ b/alacritty_terminal/src/message_bar.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crossbeam_channel::{Receiver, Sender}; +use std::collections::VecDeque; use crate::term::color::Rgb; use crate::term::SizeInfo; @@ -22,21 +22,21 @@ const CLOSE_BUTTON_PADDING: usize = 1; const MIN_FREE_LINES: usize = 3; const TRUNCATED_MESSAGE: &str = "[MESSAGE TRUNCATED]"; -/// Message for display in the MessageBuffer +/// Message for display in the MessageBuffer. #[derive(Debug, Eq, PartialEq, Clone)] pub struct Message { text: String, color: Rgb, - topic: Option<String>, + target: Option<String>, } impl Message { - /// Create a new message + /// Create a new message. pub fn new(text: String, color: Rgb) -> Message { - Message { text, color, topic: None } + Message { text, color, target: None } } - /// Formatted message text lines + /// Formatted message text lines. pub fn text(&self, size_info: &SizeInfo) -> Vec<String> { let num_cols = size_info.cols().0; let max_lines = size_info.lines().saturating_sub(MIN_FREE_LINES); @@ -92,25 +92,25 @@ impl Message { lines } - /// Message color + /// Message color. #[inline] pub fn color(&self) -> Rgb { self.color } - /// Message topic + /// Message target. #[inline] - pub fn topic(&self) -> Option<&String> { - self.topic.as_ref() + pub fn target(&self) -> Option<&String> { + self.target.as_ref() } - /// Update the message topic + /// Update the message target. #[inline] - pub fn set_topic(&mut self, topic: String) { - self.topic = Some(topic); + pub fn set_target(&mut self, target: String) { + self.target = Some(target); } - /// Right-pad text to fit a specific number of columns + /// Right-pad text to fit a specific number of columns. #[inline] fn pad_text(mut text: String, num_cols: usize) -> String { let padding_len = num_cols.saturating_sub(text.len()); @@ -119,82 +119,56 @@ impl Message { } } -/// Storage for message bar -#[derive(Debug)] +/// Storage for message bar. +#[derive(Debug, Default)] pub struct MessageBuffer { - current: Option<Message>, - messages: Receiver<Message>, - tx: Sender<Message>, + messages: VecDeque<Message>, } impl MessageBuffer { - /// Create new message buffer + /// Create new message buffer. pub fn new() -> MessageBuffer { - let (tx, messages) = crossbeam_channel::unbounded(); - MessageBuffer { current: None, messages, tx } + MessageBuffer { messages: VecDeque::new() } } - /// Check if there are any messages queued + /// Check if there are any messages queued. #[inline] pub fn is_empty(&self) -> bool { - self.current.is_none() + self.messages.is_empty() } - /// Current message + /// Current message. #[inline] - pub fn message(&mut self) -> Option<Message> { - if let Some(current) = &self.current { - Some(current.clone()) - } else { - self.current = self.messages.try_recv().ok(); - self.current.clone() - } - } - - /// Channel for adding new messages - #[inline] - pub fn tx(&self) -> Sender<Message> { - self.tx.clone() + pub fn message(&self) -> Option<&Message> { + self.messages.front() } - /// Remove the currently visible message + /// Remove the currently visible message. #[inline] pub fn pop(&mut self) { + // Remove the message itself + let msg = self.messages.pop_front(); + // Remove all duplicates - for msg in self - .messages - .try_iter() - .take(self.messages.len()) - .filter(|m| Some(m) != self.current.as_ref()) - { - let _ = self.tx.send(msg); + if let Some(msg) = msg { + self.messages = self.messages.drain(..).filter(|m| m != &msg).collect(); } - - // Remove the message itself - self.current = self.messages.try_recv().ok(); } - /// Remove all messages with a specific topic + /// Remove all messages with a specific target. #[inline] - pub fn remove_topic(&mut self, topic: &str) { - // Filter messages currently pending - for msg in self + pub fn remove_target(&mut self, target: &str) { + self.messages = self .messages - .try_iter() - .take(self.messages.len()) - .filter(|m| m.topic().map(String::as_str) != Some(topic)) - { - let _ = self.tx.send(msg); - } - - // Remove the currently active message - self.current = self.messages.try_recv().ok(); + .drain(..) + .filter(|m| m.target().map(String::as_str) != Some(target)) + .collect(); } -} -impl Default for MessageBuffer { - fn default() -> MessageBuffer { - MessageBuffer::new() + /// Add a new message to the queue. + #[inline] + pub fn push(&mut self, message: Message) { + self.messages.push_back(message); } } @@ -207,7 +181,7 @@ mod test { fn appends_close_button() { let input = "a"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 7., height: 10., @@ -227,7 +201,7 @@ mod test { fn multiline_close_button_first_line() { let input = "fo\nbar"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 6., height: 10., @@ -247,7 +221,7 @@ mod test { fn splits_on_newline() { let input = "a\nb"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 6., height: 10., @@ -267,7 +241,7 @@ mod test { fn splits_on_length() { let input = "foobar1"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 6., height: 10., @@ -287,7 +261,7 @@ mod test { fn empty_with_shortterm() { let input = "foobar"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 6., height: 0., @@ -307,7 +281,7 @@ mod test { fn truncates_long_messages() { let input = "hahahahahahahahahahaha truncate this because it's too long for the term"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 22., height: (MIN_FREE_LINES + 2) as f32, @@ -330,7 +304,7 @@ mod test { fn hide_button_when_too_narrow() { let input = "ha"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 2., height: 10., @@ -350,7 +324,7 @@ mod test { fn hide_truncated_when_too_narrow() { let input = "hahahahahahahahaha"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 2., height: (MIN_FREE_LINES + 2) as f32, @@ -370,7 +344,7 @@ mod test { fn add_newline_for_button() { let input = "test"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 5., height: 10., @@ -387,17 +361,17 @@ mod test { } #[test] - fn remove_topic() { + fn remove_target() { let mut message_buffer = MessageBuffer::new(); for i in 0..10 { let mut msg = Message::new(i.to_string(), color::RED); if i % 2 == 0 && i < 5 { - msg.set_topic("topic".into()); + msg.set_target("target".into()); } - message_buffer.tx().send(msg).unwrap(); + message_buffer.push(msg); } - message_buffer.remove_topic("topic"); + message_buffer.remove_target("target"); // Count number of messages let mut num_messages = 0; @@ -413,22 +387,22 @@ mod test { fn pop() { let mut message_buffer = MessageBuffer::new(); let one = Message::new(String::from("one"), color::RED); - message_buffer.tx().send(one.clone()).unwrap(); + message_buffer.push(one.clone()); let two = Message::new(String::from("two"), color::YELLOW); - message_buffer.tx().send(two.clone()).unwrap(); + message_buffer.push(two.clone()); - assert_eq!(message_buffer.message(), Some(one)); + assert_eq!(message_buffer.message(), Some(&one)); message_buffer.pop(); - assert_eq!(message_buffer.message(), Some(two)); + assert_eq!(message_buffer.message(), Some(&two)); } #[test] fn wrap_on_words() { let input = "a\nbc defg"; let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); + message_buffer.push(Message::new(input.into(), color::RED)); let size = SizeInfo { width: 5., height: 10., @@ -453,10 +427,10 @@ mod test { let mut message_buffer = MessageBuffer::new(); for _ in 0..10 { let msg = Message::new(String::from("test"), color::RED); - message_buffer.tx().send(msg).unwrap(); + message_buffer.push(msg); } - message_buffer.tx().send(Message::new(String::from("other"), color::RED)).unwrap(); - message_buffer.tx().send(Message::new(String::from("test"), color::YELLOW)).unwrap(); + message_buffer.push(Message::new(String::from("other"), color::RED)); + message_buffer.push(Message::new(String::from("test"), color::YELLOW)); let _ = message_buffer.message(); message_buffer.pop(); diff --git a/alacritty_terminal/src/meter.rs b/alacritty_terminal/src/meter.rs index 19d7fe70..686dd859 100644 --- a/alacritty_terminal/src/meter.rs +++ b/alacritty_terminal/src/meter.rs @@ -30,6 +30,7 @@ //! // Get the moving average. The meter tracks a fixed number of samples, and //! // the average won't mean much until it's filled up at least once. //! println!("Average time: {}", meter.average()); +//! ``` use std::time::{Duration, Instant}; diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs index 4aae8536..51c0d0f0 100644 --- a/alacritty_terminal/src/renderer/mod.rs +++ b/alacritty_terminal/src/renderer/mod.rs @@ -23,10 +23,10 @@ use std::time::Duration; use fnv::FnvHasher; use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer}; -use glutin::dpi::PhysicalSize; +use log::{error, info}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; -use crate::config::{self, Config, Delta}; +use crate::config::{self, Config, Delta, Font, StartupMode}; use crate::cursor::{get_cursor_glyph, CursorKey}; use crate::gl; use crate::gl::types::*; @@ -34,7 +34,9 @@ use crate::index::{Column, Line}; use crate::renderer::rects::RenderRect; use crate::term::cell::{self, Flags}; use crate::term::color::Rgb; +use crate::term::SizeInfo; use crate::term::{self, RenderableCell, RenderableCellContent}; +use crate::util; pub mod rects; @@ -284,12 +286,6 @@ impl GlyphCache { FontDesc::new(desc.family.clone(), style) } - pub fn font_metrics(&self) -> font::Metrics { - self.rasterizer - .metrics(self.font_key, self.font_size) - .expect("metrics load since font is loaded at glyph cache creation") - } - pub fn get<'a, L>(&'a mut self, glyph_key: GlyphKey, loader: &mut L) -> &'a Glyph where L: LoadGlyph, @@ -311,8 +307,7 @@ impl GlyphCache { pub fn update_font_size<L: LoadGlyph>( &mut self, - font: &config::Font, - size: font::Size, + font: config::Font, dpr: f64, loader: &mut L, ) -> Result<(), font::Error> { @@ -325,12 +320,11 @@ impl GlyphCache { self.rasterizer.update_dpr(dpr as f32); // Recompute font keys - let font = font.to_owned().with_size(size); let (regular, bold, italic, bold_italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; - let metrics = self.rasterizer.metrics(regular, size)?; + let metrics = self.rasterizer.metrics(regular, font.size)?; info!("Font size changed to {:?} with DPR of {}", font.size, dpr); @@ -349,13 +343,15 @@ impl GlyphCache { Ok(()) } - // Calculate font metrics without access to a glyph cache - // - // This should only be used *before* OpenGL is initialized and the glyph cache can be filled. - pub fn static_metrics(config: &Config, dpr: f32) -> Result<font::Metrics, font::Error> { - let font = config.font.clone(); + pub fn font_metrics(&self) -> font::Metrics { + self.rasterizer + .metrics(self.font_key, self.font_size) + .expect("metrics load since font is loaded at glyph cache creation") + } - let mut rasterizer = font::Rasterizer::new(dpr, config.font.use_thin_strokes())?; + // Calculate font metrics without access to a glyph cache + pub fn static_metrics(font: Font, dpr: f64) -> Result<font::Metrics, font::Error> { + let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?; let regular_desc = GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); let regular = rasterizer.load_font(®ular_desc, font.size)?; @@ -363,6 +359,34 @@ impl GlyphCache { rasterizer.metrics(regular, font.size) } + + pub fn calculate_dimensions<C>( + config: &Config<C>, + dpr: f64, + cell_width: f32, + cell_height: f32, + ) -> Option<(f64, f64)> { + let dimensions = config.window.dimensions; + + if dimensions.columns_u32() == 0 + || dimensions.lines_u32() == 0 + || config.window.startup_mode() != StartupMode::Windowed + { + return None; + } + + let padding_x = f64::from(config.window.padding.x) * dpr; + let padding_y = f64::from(config.window.padding.y) * dpr; + + // Calculate new size based on cols/lines specified in config + let grid_width = cell_width as u32 * dimensions.columns_u32(); + let grid_height = cell_height as u32 * dimensions.lines_u32(); + + let width = (f64::from(grid_width) + 2. * padding_x).floor(); + let height = (f64::from(grid_height) + 2. * padding_y).floor(); + + Some((width, height)) + } } #[derive(Debug)] @@ -411,13 +435,13 @@ pub struct QuadRenderer { } #[derive(Debug)] -pub struct RenderApi<'a> { +pub struct RenderApi<'a, C> { active_tex: &'a mut GLuint, batch: &'a mut Batch, atlas: &'a mut Vec<Atlas>, current_atlas: &'a mut usize, program: &'a mut TextShaderProgram, - config: &'a Config, + config: &'a Config<C>, } #[derive(Debug)] @@ -445,7 +469,7 @@ impl Batch { Batch { tex: 0, instances: Vec::with_capacity(BATCH_MAX) } } - pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { + pub fn add_item(&mut self, cell: RenderableCell, glyph: &Glyph) { if self.is_empty() { self.tex = glyph.tex_id; } @@ -639,7 +663,7 @@ impl QuadRenderer { let (msg_tx, msg_rx) = mpsc::channel(); if cfg!(feature = "live-shader-reload") { - ::std::thread::spawn(move || { + util::thread::spawn_named("live shader reload", move || { let (tx, rx) = ::std::sync::mpsc::channel(); // The Duration argument is a debouncing period. let mut watcher = @@ -691,8 +715,8 @@ impl QuadRenderer { // Draw all rectangles simultaneously to prevent excessive program swaps pub fn draw_rects( &mut self, - config: &Config, props: &term::SizeInfo, + visual_bell_color: Rgb, visual_bell_intensity: f64, cell_line_rects: Vec<RenderRect>, ) { @@ -724,8 +748,7 @@ impl QuadRenderer { } // Draw visual bell - let color = config.visual_bell.color; - let rect = RenderRect::new(0., 0., props.width, props.height, color); + let rect = RenderRect::new(0., 0., props.width, props.height, visual_bell_color); self.render_rect(&rect, visual_bell_intensity as f32, props); // Draw underlines and strikeouts @@ -753,9 +776,9 @@ impl QuadRenderer { } } - pub fn with_api<F, T>(&mut self, config: &Config, props: &term::SizeInfo, func: F) -> T + pub fn with_api<F, T, C>(&mut self, config: &Config<C>, props: &term::SizeInfo, func: F) -> T where - F: FnOnce(RenderApi<'_>) -> T, + F: FnOnce(RenderApi<'_, C>) -> T, { // Flush message queue if let Ok(Msg::ShaderReload) = self.rx.try_recv() { @@ -838,25 +861,19 @@ impl QuadRenderer { self.rect_program = rect_program; } - pub fn resize(&mut self, size: PhysicalSize, padding_x: f32, padding_y: f32) { - let (width, height): (u32, u32) = size.into(); - + pub fn resize(&mut self, size: &SizeInfo) { // viewport unsafe { - let width = width as i32; - let height = height as i32; - let padding_x = padding_x as i32; - let padding_y = padding_y as i32; - gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); + gl::Viewport( + size.padding_x as i32, + size.padding_y as i32, + size.width as i32 - 2 * size.padding_x as i32, + size.height as i32 - 2 * size.padding_y as i32, + ); // update projection gl::UseProgram(self.program.id); - self.program.update_projection( - width as f32, - height as f32, - padding_x as f32, - padding_y as f32, - ); + self.program.update_projection(size.width, size.height, size.padding_x, size.padding_y); gl::UseProgram(0); } } @@ -899,10 +916,10 @@ impl QuadRenderer { } } -impl<'a> RenderApi<'a> { +impl<'a, C> RenderApi<'a, C> { pub fn clear(&self, color: Rgb) { - let alpha = self.config.background_opacity(); unsafe { + let alpha = self.config.background_opacity(); gl::ClearColor( (f32::from(color.r) / 255.0).min(1.0) * alpha, (f32::from(color.g) / 255.0).min(1.0) * alpha, @@ -989,7 +1006,7 @@ impl<'a> RenderApi<'a> { } #[inline] - fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { + fn add_render_item(&mut self, cell: RenderableCell, glyph: &Glyph) { // Flush batch if tex changing if !self.batch.is_empty() && self.batch.tex != glyph.tex_id { self.render_batch(); @@ -1009,18 +1026,15 @@ impl<'a> RenderApi<'a> { // Raw cell pixel buffers like cursors don't need to go through font lookup let metrics = glyph_cache.metrics; let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| { - let offset_x = self.config.font.offset.x; - let offset_y = self.config.font.offset.y; - self.load_glyph(&get_cursor_glyph( cursor_key.style, metrics, - offset_x, - offset_y, + self.config.font.offset.x, + self.config.font.offset.y, cursor_key.is_wide, )) }); - self.add_render_item(&cell, &glyph); + self.add_render_item(cell, &glyph); return; }, RenderableCellContent::Chars(chars) => chars, @@ -1050,7 +1064,7 @@ impl<'a> RenderApi<'a> { // Add cell to batch let glyph = glyph_cache.get(glyph_key, self); - self.add_render_item(&cell, glyph); + self.add_render_item(cell, glyph); // Render zero-width characters for c in (&chars[1..]).iter().filter(|c| **c != ' ') { @@ -1064,7 +1078,7 @@ impl<'a> RenderApi<'a> { // anchor has been moved to the right by one cell. glyph.left += glyph_cache.metrics.average_advance as f32; - self.add_render_item(&cell, &glyph); + self.add_render_item(cell, &glyph); } } } @@ -1124,7 +1138,7 @@ impl<'a> LoadGlyph for LoaderApi<'a> { } } -impl<'a> LoadGlyph for RenderApi<'a> { +impl<'a, C> LoadGlyph for RenderApi<'a, C> { fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) } @@ -1134,7 +1148,7 @@ impl<'a> LoadGlyph for RenderApi<'a> { } } -impl<'a> Drop for RenderApi<'a> { +impl<'a, C> Drop for RenderApi<'a, C> { fn drop(&mut self) { if !self.batch.is_empty() { self.render_batch(); diff --git a/alacritty_terminal/src/renderer/rects.rs b/alacritty_terminal/src/renderer/rects.rs index 72139e3d..dd72f673 100644 --- a/alacritty_terminal/src/renderer/rects.rs +++ b/alacritty_terminal/src/renderer/rects.rs @@ -91,7 +91,7 @@ impl RenderLines { } /// Update the stored lines with the next cell info. - pub fn update(&mut self, cell: &RenderableCell) { + pub fn update(&mut self, cell: RenderableCell) { for flag in &[Flags::UNDERLINE, Flags::STRIKEOUT] { if !cell.flags.contains(*flag) { continue; diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index ffe35e08..29934e5a 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -39,6 +39,7 @@ use crate::term::{Search, Term}; /// [`simple`]: enum.Selection.html#method.simple /// [`semantic`]: enum.Selection.html#method.semantic /// [`lines`]: enum.Selection.html#method.lines +/// [`update`]: enum.Selection.html#method.update #[derive(Debug, Clone, PartialEq)] pub enum Selection { Simple { @@ -164,7 +165,7 @@ impl Selection { } } - pub fn to_span(&self, term: &Term) -> Option<Span> { + pub fn to_span<T>(&self, term: &Term<T>) -> Option<Span> { // Get both sides of the selection let (mut start, mut end) = match *self { Selection::Simple { ref region } | Selection::Block { ref region } => { @@ -405,13 +406,19 @@ mod test { use super::{Selection, Span}; use crate::clipboard::Clipboard; + use crate::config::MockConfig; + use crate::event::{Event, EventListener}; use crate::grid::Grid; use crate::index::{Column, Line, Point, Side}; - use crate::message_bar::MessageBuffer; use crate::term::cell::{Cell, Flags}; use crate::term::{SizeInfo, Term}; - fn term(width: usize, height: usize) -> Term { + struct Mock; + impl EventListener for Mock { + fn send_event(&self, _event: Event) {} + } + + fn term(width: usize, height: usize) -> Term<Mock> { let size = SizeInfo { width: width as f32, height: height as f32, @@ -421,7 +428,7 @@ mod test { padding_y: 0.0, dpr: 1.0, }; - Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop()) + Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock) } /// Test case of single cell selection diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 7a759777..234805f9 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -13,6 +13,8 @@ // limitations under the License. use bitflags::bitflags; +use serde::{Deserialize, Serialize}; + use crate::ansi::{Color, NamedColor}; use crate::grid::{self, GridCell}; use crate::index::Column; diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs index f2db335a..e9f0a26d 100644 --- a/alacritty_terminal/src/term/color.rs +++ b/alacritty_terminal/src/term/color.rs @@ -2,8 +2,9 @@ use std::fmt; use std::ops::{Index, IndexMut, Mul}; use std::str::FromStr; +use log::{error, trace}; use serde::de::Visitor; -use serde::{Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize}; use crate::ansi; use crate::config::Colors; diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 58d06318..4e51d734 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -18,9 +18,9 @@ use std::ops::{Index, IndexMut, Range, RangeInclusive}; use std::time::{Duration, Instant}; use std::{io, mem, ptr}; -use font::{self, Size}; -use glutin::MouseCursor; +use log::{debug, trace}; use rfind_url::{Parser, ParserState}; +use serde::{Deserialize, Serialize}; use unicode_width::UnicodeWidthChar; use crate::ansi::{ @@ -29,19 +29,17 @@ use crate::ansi::{ use crate::clipboard::{Clipboard, ClipboardType}; use crate::config::{Config, VisualBellAnimation}; use crate::cursor::CursorKey; +use crate::event::{Event, EventListener}; use crate::grid::{ BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll, }; use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point}; -use crate::input::FONT_SIZE_STEP; -use crate::message_bar::MessageBuffer; use crate::selection::{self, Selection, SelectionRange, Span}; use crate::term::cell::{Cell, Flags, LineLength}; use crate::term::color::Rgb; -use crate::url::Url; - #[cfg(windows)] use crate::tty; +use crate::url::Url; pub mod cell; pub mod color; @@ -62,7 +60,7 @@ pub trait Search { fn bracket_search(&self, _: Point<usize>) -> Option<Point<usize>>; } -impl Search for Term { +impl<T> Search for Term<T> { fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> { // Limit the starting point to the last line in the history point.line = min(point.line, self.grid.len() - 1); @@ -151,7 +149,7 @@ impl Search for Term { } } -impl selection::Dimensions for Term { +impl<T> selection::Dimensions for Term<T> { fn dimensions(&self) -> Point { let line = if self.mode.contains(TermMode::ALT_SCREEN) { self.grid.num_lines() @@ -170,30 +168,30 @@ impl selection::Dimensions for Term { /// /// This manages the cursor during a render. The cursor location is inverted to /// draw it, and reverted after drawing to maintain state. -pub struct RenderableCellsIter<'a> { +pub struct RenderableCellsIter<'a, C> { inner: DisplayIter<'a, Cell>, grid: &'a Grid<Cell>, cursor: &'a Point, cursor_offset: usize, cursor_key: Option<CursorKey>, cursor_style: CursorStyle, - config: &'a Config, + config: &'a Config<C>, colors: &'a color::List, selection: Option<SelectionRange>, url_highlight: &'a Option<RangeInclusive<index::Linear>>, } -impl<'a> RenderableCellsIter<'a> { +impl<'a, C> RenderableCellsIter<'a, C> { /// Create the renderable cells iterator /// /// The cursor and terminal mode are required for properly displaying the /// cursor. - fn new<'b>( - term: &'b Term, - config: &'b Config, + fn new<'b, T>( + term: &'b Term<T>, + config: &'b Config<C>, selection: Option<Span>, mut cursor_style: CursorStyle, - ) -> RenderableCellsIter<'b> { + ) -> RenderableCellsIter<'b, C> { let grid = &term.grid; let cursor_offset = grid.line_to_offset(term.cursor.point.line); @@ -250,13 +248,13 @@ impl<'a> RenderableCellsIter<'a> { } } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub enum RenderableCellContent { Chars([char; cell::MAX_ZEROWIDTH_CHARS + 1]), Cursor(CursorKey), } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub struct RenderableCell { /// A _Display_ line (not necessarily an _Active_ line) pub line: Line, @@ -269,7 +267,12 @@ pub struct RenderableCell { } impl RenderableCell { - fn new(config: &Config, colors: &color::List, cell: Indexed<Cell>, selected: bool) -> Self { + fn new<C>( + config: &Config<C>, + colors: &color::List, + cell: Indexed<Cell>, + selected: bool, + ) -> Self { // Lookup RGB values let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags); let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg); @@ -309,7 +312,12 @@ impl RenderableCell { } } - fn compute_fg_rgb(config: &Config, colors: &color::List, fg: Color, flags: cell::Flags) -> Rgb { + fn compute_fg_rgb<C>( + config: &Config<C>, + colors: &color::List, + fg: Color, + flags: cell::Flags, + ) -> Rgb { match fg { Color::Spec(rgb) => rgb, Color::Named(ansi) => { @@ -365,7 +373,7 @@ impl RenderableCell { } } -impl<'a> Iterator for RenderableCellsIter<'a> { +impl<'a, C> Iterator for RenderableCellsIter<'a, C> { type Item = RenderableCell; /// Gets the next renderable cell @@ -573,7 +581,7 @@ fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 { } impl VisualBell { - pub fn new(config: &Config) -> VisualBell { + pub fn new<C>(config: &Config<C>) -> VisualBell { let visual_bell_config = &config.visual_bell; VisualBell { animation: visual_bell_config.animation, @@ -668,14 +676,14 @@ impl VisualBell { } } - pub fn update_config(&mut self, config: &Config) { + pub fn update_config<C>(&mut self, config: &Config<C>) { let visual_bell_config = &config.visual_bell; self.animation = visual_bell_config.animation; self.duration = visual_bell_config.duration(); } } -pub struct Term { +pub struct Term<T> { /// The grid grid: Grid<Cell>, @@ -686,14 +694,6 @@ pub struct Term { /// arrays. Without it we would have to sanitize cursor.col every time we used it. input_needs_wrap: bool, - /// Got a request to set title; it's buffered here until next draw. - /// - /// Would be nice to avoid the allocation... - next_title: Option<String>, - - /// Got a request to set the mouse cursor; it's buffered here until the next draw - next_mouse_cursor: Option<MouseCursor>, - /// Alternate grid alt_grid: Grid<Cell>, @@ -716,17 +716,9 @@ pub struct Term { /// Scroll region scroll_region: Range<Line>, - /// Font size - pub font_size: Size, - original_font_size: Size, - - /// Size - size_info: SizeInfo, - pub dirty: bool, pub visual_bell: VisualBell, - pub next_is_urgent: Option<bool>, /// Saved cursor from main grid cursor_save: Cursor, @@ -760,18 +752,18 @@ pub struct Term { /// Automatically scroll to bottom when new lines are added auto_scroll: bool, - /// Buffer to store messages for the message bar - message_buffer: MessageBuffer, - - /// Hint that Alacritty should be closed - should_exit: bool, - /// Clipboard access coupled to the active window clipboard: Clipboard, + + /// Proxy for sending events to the event loop + event_proxy: T, + + /// Terminal focus + pub is_focused: bool, } /// Terminal size info -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] pub struct SizeInfo { /// Terminal window width pub width: f32, @@ -829,7 +821,7 @@ impl SizeInfo { } } -impl Term { +impl<T> Term<T> { pub fn selection(&self) -> &Option<Selection> { &self.grid.selection } @@ -839,29 +831,22 @@ impl Term { } #[inline] - pub fn get_next_title(&mut self) -> Option<String> { - self.next_title.take() - } - - #[inline] - pub fn scroll_display(&mut self, scroll: Scroll) { - self.set_mouse_cursor(MouseCursor::Text); + pub fn scroll_display(&mut self, scroll: Scroll) + where + T: EventListener, + { + self.event_proxy.send_event(Event::MouseCursorDirty); self.grid.scroll_display(scroll); self.reset_url_highlight(); self.dirty = true; } - #[inline] - pub fn get_next_mouse_cursor(&mut self) -> Option<MouseCursor> { - self.next_mouse_cursor.take() - } - - pub fn new( - config: &Config, - size: SizeInfo, - message_buffer: MessageBuffer, + pub fn new<C>( + config: &Config<C>, + size: &SizeInfo, clipboard: Clipboard, - ) -> Term { + event_proxy: T, + ) -> Term<T> { let num_cols = size.cols(); let num_lines = size.lines(); @@ -877,17 +862,12 @@ impl Term { let colors = color::List::from(&config.colors); Term { - next_title: None, - next_mouse_cursor: None, dirty: false, visual_bell: VisualBell::new(config), - next_is_urgent: None, input_needs_wrap: false, grid, alt_grid: alt, alt: false, - font_size: config.font.size, - original_font_size: config.font.size, active_charset: Default::default(), cursor: Default::default(), cursor_save: Default::default(), @@ -895,7 +875,6 @@ impl Term { tabs, mode: Default::default(), scroll_region, - size_info: size, colors, color_modified: [false; color::COUNT], original_colors: colors, @@ -905,25 +884,13 @@ impl Term { dynamic_title: config.dynamic_title(), tabspaces, auto_scroll: config.scrolling.auto_scroll, - message_buffer, - should_exit: false, clipboard, + event_proxy, + is_focused: true, } } - pub fn change_font_size(&mut self, delta: f32) { - // Saturating addition with minimum font size FONT_SIZE_STEP - let new_size = self.font_size + Size::new(delta); - self.font_size = max(new_size, Size::new(FONT_SIZE_STEP)); - self.dirty = true; - } - - pub fn reset_font_size(&mut self) { - self.font_size = self.original_font_size; - self.dirty = true; - } - - pub fn update_config(&mut self, config: &Config) { + pub fn update_config<C>(&mut self, config: &Config<C>) { self.semantic_escape_chars = config.selection.semantic_escape_chars().to_owned(); self.original_colors.fill_named(&config.colors); self.original_colors.fill_cube(&config.colors); @@ -938,16 +905,6 @@ impl Term { self.dynamic_title = config.dynamic_title(); self.auto_scroll = config.scrolling.auto_scroll; self.grid.update_history(config.scrolling.history() as usize, &self.cursor.template); - - if self.original_font_size == self.font_size { - self.font_size = config.font.size; - } - self.original_font_size = config.font.size; - } - - #[inline] - pub fn needs_draw(&self) -> bool { - self.dirty } pub fn selection_to_string(&self) -> Option<String> { @@ -1072,21 +1029,6 @@ impl Term { self.grid.buffer_to_visible(point) } - /// Convert the given pixel values to a grid coordinate - /// - /// The mouse coordinates are expected to be relative to the top left. The - /// line and column returned are also relative to the top left. - /// - /// Returns None if the coordinates are outside the window, - /// padding pixels are considered inside the window - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> { - if self.size_info.contains_point(x, y, true) { - Some(self.size_info.pixels_to_coords(x, y)) - } else { - None - } - } - /// Access to the raw grid data structure /// /// This is a bit of a hack; when the window is closed, the event processor @@ -1106,14 +1048,10 @@ impl Term { /// A renderable cell is any cell which has content other than the default /// background color. Cells with an alternate background color are /// considered renderable as are cells with any text content. - pub fn renderable_cells<'b>( - &'b self, - config: &'b Config, - window_focused: bool, - ) -> RenderableCellsIter<'_> { + pub fn renderable_cells<'b, C>(&'b self, config: &'b Config<C>) -> RenderableCellsIter<'_, C> { let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self)); - let cursor = if window_focused || !config.cursor.unfocused_hollow() { + let cursor = if self.is_focused || !config.cursor.unfocused_hollow() { self.cursor_style.unwrap_or(self.default_cursor_style) } else { CursorStyle::HollowBlock @@ -1124,11 +1062,9 @@ impl Term { /// Resize terminal to new dimensions pub fn resize(&mut self, size: &SizeInfo) { - debug!("Resizing terminal"); - // Bounds check; lots of math assumes width and height are > 0 - if size.width as usize <= 2 * self.size_info.padding_x as usize - || size.height as usize <= 2 * self.size_info.padding_y as usize + if size.width as usize <= 2 * size.padding_x as usize + || size.height as usize <= 2 * size.padding_y as usize { return; } @@ -1138,12 +1074,6 @@ impl Term { let mut num_cols = size.cols(); let mut num_lines = size.lines(); - if let Some(message) = self.message_buffer.message() { - num_lines -= message.text(size).len(); - } - - self.size_info = *size; - if old_cols == num_cols && old_lines == num_lines { debug!("Term::resize dimensions unchanged"); return; @@ -1211,11 +1141,6 @@ impl Term { } #[inline] - pub fn size_info(&self) -> &SizeInfo { - &self.size_info - } - - #[inline] pub fn mode(&self) -> &TermMode { &self.mode } @@ -1266,7 +1191,10 @@ impl Term { self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &template); } - fn deccolm(&mut self) { + fn deccolm(&mut self) + where + T: EventListener, + { // Setting 132 column font makes no sense, but run the other side effects // Clear scrolling region self.set_scrolling_region(1, self.grid.num_lines().0); @@ -1282,23 +1210,11 @@ impl Term { } #[inline] - pub fn message_buffer_mut(&mut self) -> &mut MessageBuffer { - &mut self.message_buffer - } - - #[inline] - pub fn message_buffer(&self) -> &MessageBuffer { - &self.message_buffer - } - - #[inline] - pub fn exit(&mut self) { - self.should_exit = true; - } - - #[inline] - pub fn should_exit(&self) -> bool { - self.should_exit + pub fn exit(&mut self) + where + T: EventListener, + { + self.event_proxy.send_event(Event::Exit); } #[inline] @@ -1389,7 +1305,7 @@ impl Term { } } -impl TermInfo for Term { +impl<T> TermInfo for Term<T> { #[inline] fn lines(&self) -> Line { self.grid.num_lines() @@ -1401,33 +1317,33 @@ impl TermInfo for Term { } } -impl ansi::Handler for Term { - /// Set the window title +impl<T: EventListener> ansi::Handler for Term<T> { #[inline] + #[cfg(not(windows))] fn set_title(&mut self, title: &str) { if self.dynamic_title { - self.next_title = Some(title.to_owned()); - - #[cfg(windows)] - { - // cmd.exe in winpty: winpty incorrectly sets the title to ' ' instead of - // 'Alacritty' - thus we have to substitute this back to get equivalent - // behaviour as conpty. - // - // The starts_with check is necessary because other shells e.g. bash set a - // different title and don't need Alacritty prepended. - if !tty::is_conpty() && title.starts_with(' ') { - self.next_title = Some(format!("Alacritty {}", title.trim())); - } - } + self.event_proxy.send_event(Event::Title(title.to_owned())); } } - /// Set the mouse cursor #[inline] - fn set_mouse_cursor(&mut self, cursor: MouseCursor) { - self.next_mouse_cursor = Some(cursor); - self.dirty = true; + #[cfg(windows)] + fn set_title(&mut self, title: &str) { + if self.dynamic_title { + // cmd.exe in winpty: winpty incorrectly sets the title to ' ' instead of + // 'Alacritty' - thus we have to substitute this back to get equivalent + // behaviour as conpty. + // + // The starts_with check is necessary because other shells e.g. bash set a + // different title and don't need Alacritty prepended. + let title = if !tty::is_conpty() && title.starts_with(' ') { + format!("Alacritty {}", title.trim()) + } else { + title.to_owned() + }; + + self.event_proxy.send_event(Event::Title(title)); + } } /// A character to be displayed @@ -1554,11 +1470,11 @@ impl ansi::Handler for Term { fn insert_blank(&mut self, count: Column) { // Ensure inserting within terminal bounds - let count = min(count, self.size_info.cols() - self.cursor.point.col); + let count = min(count, self.grid.num_cols() - self.cursor.point.col); let source = self.cursor.point.col; let destination = self.cursor.point.col + count; - let num_cells = (self.size_info.cols() - destination).0; + let num_cells = (self.grid.num_cols() - destination).0; let line = &mut self.grid[self.cursor.point.line]; @@ -1703,7 +1619,7 @@ impl ansi::Handler for Term { fn bell(&mut self) { trace!("Bell"); self.visual_bell.ring(); - self.next_is_urgent = Some(true); + self.event_proxy.send_event(Event::Urgent); } #[inline] @@ -1794,12 +1710,14 @@ impl ansi::Handler for Term { #[inline] fn delete_chars(&mut self, count: Column) { + let cols = self.grid.num_cols(); + // Ensure deleting within terminal bounds - let count = min(count, self.size_info.cols()); + let count = min(count, cols); let start = self.cursor.point.col; - let end = min(start + count, self.grid.num_cols() - 1); - let n = (self.size_info.cols() - end).0; + let end = min(start + count, cols - 1); + let n = (cols - end).0; let line = &mut self.grid[self.cursor.point.line]; @@ -1813,7 +1731,7 @@ impl ansi::Handler for Term { // Clear last `count` cells in line. If deleting 1 char, need to delete // 1 cell. let template = self.cursor.template; - let end = self.size_info.cols() - count; + let end = cols - count; for c in &mut line[end..] { c.reset(&template); } @@ -1983,13 +1901,9 @@ impl ansi::Handler for Term { self.swap_alt(); } self.input_needs_wrap = false; - self.next_title = None; - self.next_mouse_cursor = None; self.cursor = Default::default(); self.active_charset = Default::default(); self.mode = Default::default(); - self.font_size = self.original_font_size; - self.next_is_urgent = None; self.cursor_save = Default::default(); self.cursor_save_alt = Default::default(); self.colors = self.original_colors; @@ -2061,15 +1975,15 @@ impl ansi::Handler for Term { ansi::Mode::CursorKeys => self.mode.insert(TermMode::APP_CURSOR), ansi::Mode::ReportMouseClicks => { self.mode.insert(TermMode::MOUSE_REPORT_CLICK); - self.set_mouse_cursor(MouseCursor::Default); + self.event_proxy.send_event(Event::MouseCursorDirty); }, ansi::Mode::ReportCellMouseMotion => { self.mode.insert(TermMode::MOUSE_DRAG); - self.set_mouse_cursor(MouseCursor::Default); + self.event_proxy.send_event(Event::MouseCursorDirty); }, ansi::Mode::ReportAllMouseMotion => { self.mode.insert(TermMode::MOUSE_MOTION); - self.set_mouse_cursor(MouseCursor::Default); + self.event_proxy.send_event(Event::MouseCursorDirty); }, ansi::Mode::ReportFocusInOut => self.mode.insert(TermMode::FOCUS_IN_OUT), ansi::Mode::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE), @@ -2101,15 +2015,15 @@ impl ansi::Handler for Term { ansi::Mode::CursorKeys => self.mode.remove(TermMode::APP_CURSOR), ansi::Mode::ReportMouseClicks => { self.mode.remove(TermMode::MOUSE_REPORT_CLICK); - self.set_mouse_cursor(MouseCursor::Text); + self.event_proxy.send_event(Event::MouseCursorDirty); }, ansi::Mode::ReportCellMouseMotion => { self.mode.remove(TermMode::MOUSE_DRAG); - self.set_mouse_cursor(MouseCursor::Text); + self.event_proxy.send_event(Event::MouseCursorDirty); }, ansi::Mode::ReportAllMouseMotion => { self.mode.remove(TermMode::MOUSE_MOTION); - self.set_mouse_cursor(MouseCursor::Text); + self.event_proxy.send_event(Event::MouseCursorDirty); }, ansi::Mode::ReportFocusInOut => self.mode.remove(TermMode::FOCUS_IN_OUT), ansi::Mode::BracketedPaste => self.mode.remove(TermMode::BRACKETED_PASTE), @@ -2215,19 +2129,22 @@ impl IndexMut<Column> for TabStops { mod tests { use std::mem; - use font::Size; use serde_json; use crate::ansi::{self, CharsetIndex, Handler, StandardCharset}; use crate::clipboard::Clipboard; - use crate::config::Config; + use crate::config::MockConfig; + use crate::event::{Event, EventListener}; use crate::grid::{Grid, Scroll}; use crate::index::{Column, Line, Point, Side}; - use crate::input::FONT_SIZE_STEP; - use crate::message_bar::MessageBuffer; use crate::selection::Selection; use crate::term::{cell, Cell, SizeInfo, Term}; + struct Mock; + impl EventListener for Mock { + fn send_event(&self, _event: Event) {} + } + #[test] fn semantic_selection_works() { let size = SizeInfo { @@ -2239,8 +2156,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = - Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop()); + let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0, Cell::default()); for i in 0..5 { for j in 0..2 { @@ -2284,8 +2200,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = - Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop()); + let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0, Cell::default()); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; @@ -2310,8 +2225,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = - Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop()); + let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut grid: Grid<Cell> = Grid::new(Line(3), Column(3), 0, Cell::default()); for l in 0..3 { if l != 1 { @@ -2355,8 +2269,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let mut term = - Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop()); + let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let cursor = Point::new(Line(0), Column(0)); term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing); term.input('a'); @@ -2364,75 +2277,6 @@ mod tests { assert_eq!(term.grid()[&cursor].c, '▒'); } - fn change_font_size_works(font_size: f32) { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); - term.change_font_size(font_size); - - let expected_font_size: Size = config.font.size + Size::new(font_size); - assert_eq!(term.font_size, expected_font_size); - } - - #[test] - fn increase_font_size_works() { - change_font_size_works(10.0); - } - - #[test] - fn decrease_font_size_works() { - change_font_size_works(-10.0); - } - - #[test] - fn prevent_font_below_threshold_works() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); - - term.change_font_size(-100.0); - - let expected_font_size: Size = Size::new(FONT_SIZE_STEP); - assert_eq!(term.font_size, expected_font_size); - } - - #[test] - fn reset_font_size_works() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); - - term.change_font_size(10.0); - term.reset_font_size(); - - let expected_font_size: Size = config.font.size; - assert_eq!(term.font_size, expected_font_size); - } - #[test] fn clear_saved_lines() { let size = SizeInfo { @@ -2444,8 +2288,7 @@ mod tests { padding_y: 0.0, dpr: 1.0, }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); + let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); // Add one line of scrollback term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default()); @@ -2471,13 +2314,18 @@ mod benches { use std::path::Path; use crate::clipboard::Clipboard; - use crate::config::Config; + use crate::config::MockConfig; + use crate::event::{Event, EventListener}; use crate::grid::Grid; - use crate::message_bar::MessageBuffer; use super::cell::Cell; use super::{SizeInfo, Term}; + struct Mock; + impl EventListener for Mock { + fn send_event(&self, _event: Event) {} + } + fn read_string<P>(path: P) -> String where P: AsRef<Path>, @@ -2512,13 +2360,13 @@ mod benches { let mut grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap(); let size: SizeInfo = json::from_str(&serialized_size).unwrap(); - let config = Config::default(); + let config = MockConfig::default(); - let mut terminal = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); + let mut terminal = Term::new(&config, &size, Clipboard::new_nop(), Mock); mem::swap(&mut terminal.grid, &mut grid); b.iter(|| { - let iter = terminal.renderable_cells(&config, false); + let iter = terminal.renderable_cells(&config); for cell in iter { test::black_box(cell); } diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs index 16ae96ce..7150a42d 100644 --- a/alacritty_terminal/src/tty/mod.rs +++ b/alacritty_terminal/src/tty/mod.rs @@ -77,7 +77,7 @@ pub trait EventedPty: EventedReadWrite { } // Setup environment variables -pub fn setup_env(config: &Config) { +pub fn setup_env<C>(config: &Config<C>) { // Default to 'alacritty' terminfo if it is available, otherwise // default to 'xterm-256color'. May be overridden by user's config // below. diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index 77b5db43..a2a277a6 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -15,12 +15,13 @@ //! tty related functionality use crate::config::{Config, Shell}; -use crate::display::OnResize; +use crate::event::OnResize; use crate::term::SizeInfo; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; use mio; use libc::{self, c_int, pid_t, winsize, TIOCSCTTY}; +use log::error; use nix::pty::openpty; use signal_hook::{self as sighook, iterator::Signals}; @@ -42,6 +43,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; /// Necessary to put this in static storage for `sigchld` to have access static PID: AtomicUsize = AtomicUsize::new(0); +macro_rules! die { + ($($arg:tt)*) => {{ + error!($($arg)*); + ::std::process::exit(1); + }} +} + pub fn child_pid() -> pid_t { PID.load(Ordering::Relaxed) as pid_t } @@ -133,24 +141,8 @@ pub struct Pty { signals_token: mio::Token, } -impl Pty { - /// Resize the pty - /// - /// Tells the kernel that the window size changed with the new pixel - /// dimensions and line/column counts. - pub fn resize<T: ToWinsize>(&self, size: &T) { - let win = size.to_winsize(); - - let res = unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) }; - - if res < 0 { - die!("ioctl TIOCSWINSZ failed: {}", io::Error::last_os_error()); - } - } -} - /// Create a new tty and return a handle to interact with it. -pub fn new<T: ToWinsize>(config: &Config, size: &T, window_id: Option<usize>) -> Pty { +pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty { let win_size = size.to_winsize(); let mut buf = [0; 1024]; let pw = get_pw_entry(&mut buf); @@ -241,12 +233,10 @@ pub fn new<T: ToWinsize>(config: &Config, size: &T, window_id: Option<usize>) -> signals, signals_token: mio::Token::from(0), }; - pty.resize(size); + pty.fd.as_raw_fd().on_resize(size); pty }, - Err(err) => { - die!("Failed to spawn command: {}", err); - }, + Err(err) => die!("Failed to spawn command: {}", err), } } @@ -365,6 +355,10 @@ impl<'a> ToWinsize for &'a SizeInfo { } impl OnResize for i32 { + /// Resize the pty + /// + /// Tells the kernel that the window size changed with the new pixel + /// dimensions and line/column counts. fn on_resize(&mut self, size: &SizeInfo) { let win = size.to_winsize(); diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index bd602c35..fe49b4dc 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -22,6 +22,7 @@ use std::ptr; use std::sync::Arc; use dunce::canonicalize; +use log::info; use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; use miow; use widestring::U16CString; @@ -38,7 +39,7 @@ use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTF_USESTDHANDLES, ST use winapi::um::wincontypes::{COORD, HPCON}; use crate::config::{Config, Shell}; -use crate::display::OnResize; +use crate::event::OnResize; use crate::term::SizeInfo; /// Dynamically-loaded Pseudoconsole API from kernel32.dll @@ -98,7 +99,11 @@ impl Drop for Conpty { unsafe impl Send for Conpty {} unsafe impl Sync for Conpty {} -pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option<usize>) -> Option<Pty<'a>> { +pub fn new<'a, C>( + config: &Config<C>, + size: &SizeInfo, + _window_id: Option<usize>, +) -> Option<Pty<'a>> { if !config.enable_experimental_conpty_backend { return None; } diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index 7537d331..922dde26 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -20,12 +20,13 @@ use mio::{self, Evented, Poll, PollOpt, Ready, Token}; use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; use mio_named_pipes::NamedPipe; +use log::info; use winapi::shared::winerror::WAIT_TIMEOUT; use winapi::um::synchapi::WaitForSingleObject; use winapi::um::winbase::WAIT_OBJECT_0; use crate::config::Config; -use crate::display::OnResize; +use crate::event::OnResize; use crate::term::SizeInfo; use crate::tty::{EventedPty, EventedReadWrite}; @@ -83,7 +84,7 @@ impl<'a> Pty<'a> { } } -pub fn new<'a>(config: &Config, size: &SizeInfo, window_id: Option<usize>) -> Pty<'a> { +pub fn new<'a, C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty<'a> { if let Some(pty) = conpty::new(config, size, window_id) { info!("Using Conpty agent"); IS_CONPTY.store(true, Ordering::Relaxed); diff --git a/alacritty_terminal/src/tty/windows/winpty.rs b/alacritty_terminal/src/tty/windows/winpty.rs index 8795ca6d..db397ad9 100644 --- a/alacritty_terminal/src/tty/windows/winpty.rs +++ b/alacritty_terminal/src/tty/windows/winpty.rs @@ -22,13 +22,13 @@ use std::sync::Arc; use std::u16; use dunce::canonicalize; +use log::info; use mio_named_pipes::NamedPipe; use winapi::um::winbase::FILE_FLAG_OVERLAPPED; -use winpty::Config as WinptyConfig; -use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty}; +use winpty::{Config as WinptyConfig, ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty}; use crate::config::{Config, Shell}; -use crate::display::OnResize; +use crate::event::OnResize; use crate::term::SizeInfo; // We store a raw pointer because we need mutable access to call @@ -75,7 +75,7 @@ impl<'a> Drop for Agent<'a> { /// This is a placeholder value until we see how often long responses happen const AGENT_TIMEOUT: u32 = 10000; -pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option<usize>) -> Pty<'a> { +pub fn new<'a, C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Pty<'a> { // Create config let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap(); diff --git a/alacritty_terminal/src/url.rs b/alacritty_terminal/src/url.rs index 292f8358..9e8ecd4b 100644 --- a/alacritty_terminal/src/url.rs +++ b/alacritty_terminal/src/url.rs @@ -29,7 +29,7 @@ impl Url { } /// Convert URLs bounding points to linear indices - pub fn linear_bounds(&self, terminal: &Term) -> RangeInclusive<Linear> { + pub fn linear_bounds<T>(&self, terminal: &Term<T>) -> RangeInclusive<Linear> { let mut start = self.start; let mut end = self.end; diff --git a/alacritty_terminal/src/window.rs b/alacritty_terminal/src/window.rs deleted file mode 100644 index f18fda3a..00000000 --- a/alacritty_terminal/src/window.rs +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use std::convert::From; -#[cfg(not(any(target_os = "macos", windows)))] -use std::ffi::c_void; -use std::fmt::Display; - -use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; -#[cfg(target_os = "macos")] -use glutin::os::macos::WindowExt; -#[cfg(not(any(target_os = "macos", windows)))] -use glutin::os::unix::{EventsLoopExt, WindowExt}; -#[cfg(not(target_os = "macos"))] -use glutin::Icon; -#[cfg(not(any(target_os = "macos", windows)))] -use glutin::Window as GlutinWindow; -use glutin::{ - self, ContextBuilder, ControlFlow, Event, EventsLoop, MouseCursor, PossiblyCurrent, - WindowBuilder, -}; -#[cfg(not(target_os = "macos"))] -use image::ImageFormat; -#[cfg(not(any(target_os = "macos", windows)))] -use x11_dl::xlib::{Display as XDisplay, PropModeReplace, XErrorEvent, Xlib}; - -use crate::config::{Config, Decorations, StartupMode, WindowConfig}; -use crate::gl; - -// It's required to be in this directory due to the `windows.rc` file -#[cfg(not(target_os = "macos"))] -static WINDOW_ICON: &[u8] = include_bytes!("../../extra/windows/alacritty.ico"); - -/// Default Alacritty name, used for window title and class. -pub const DEFAULT_NAME: &str = "Alacritty"; - -/// Window errors -#[derive(Debug)] -pub enum Error { - /// Error creating the window - ContextCreation(glutin::CreationError), - - /// Error manipulating the rendering context - Context(glutin::ContextError), -} - -/// Result of fallible operations concerning a Window. -type Result<T> = ::std::result::Result<T, Error>; - -/// A window which can be used for displaying the terminal -/// -/// Wraps the underlying windowing library to provide a stable API in Alacritty -pub struct Window { - event_loop: EventsLoop, - windowed_context: glutin::WindowedContext<PossiblyCurrent>, - mouse_visible: bool, - - /// Keep track of the current mouse cursor to avoid unnecessarily changing it - current_mouse_cursor: MouseCursor, - - /// Whether or not the window is the focused window. - pub is_focused: bool, -} - -/// Threadsafe APIs for the window -pub struct Proxy { - inner: glutin::EventsLoopProxy, -} - -/// Information about where the window is being displayed -/// -/// Useful for subsystems like the font rasterized which depend on DPI and scale -/// factor. -pub struct DeviceProperties { - /// Scale factor for pixels <-> points. - /// - /// This will be 1. on standard displays and may have a different value on - /// hidpi displays. - pub scale_factor: f64, -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::ContextCreation(ref err) => Some(err), - Error::Context(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::ContextCreation(ref _err) => "Error creating gl context", - Error::Context(ref _err) => "Error operating on render context", - } - } -} - -impl Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::ContextCreation(ref err) => write!(f, "Error creating GL context; {}", err), - Error::Context(ref err) => write!(f, "Error operating on render context; {}", err), - } - } -} - -impl From<glutin::CreationError> for Error { - fn from(val: glutin::CreationError) -> Error { - Error::ContextCreation(val) - } -} - -impl From<glutin::ContextError> for Error { - fn from(val: glutin::ContextError) -> Error { - Error::Context(val) - } -} - -fn create_gl_window( - mut window: WindowBuilder, - event_loop: &EventsLoop, - srgb: bool, - dimensions: Option<LogicalSize>, -) -> Result<glutin::WindowedContext<PossiblyCurrent>> { - if let Some(dimensions) = dimensions { - window = window.with_dimensions(dimensions); - } - - let windowed_context = ContextBuilder::new() - .with_srgb(srgb) - .with_vsync(true) - .with_hardware_acceleration(None) - .build_windowed(window, event_loop)?; - - // Make the context current so OpenGL operations can run - let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, e)| e)? }; - - Ok(windowed_context) -} - -impl Window { - /// Create a new window - /// - /// This creates a window and fully initializes a window. - pub fn new( - event_loop: EventsLoop, - config: &Config, - dimensions: Option<LogicalSize>, - ) -> Result<Window> { - let title = config.window.title.as_ref().map_or(DEFAULT_NAME, |t| t); - - let window_builder = Window::get_platform_window(title, &config.window); - let windowed_context = - create_gl_window(window_builder.clone(), &event_loop, false, dimensions) - .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?; - let window = windowed_context.window(); - - // Text cursor - window.set_cursor(MouseCursor::Text); - - // Set OpenGL symbol loader. This call MUST be after window.make_current on windows. - gl::load_with(|symbol| windowed_context.get_proc_address(symbol) as *const _); - - // On X11, embed the window inside another if the parent ID has been set - #[cfg(not(any(target_os = "macos", windows)))] - { - if event_loop.is_x11() { - if let Some(parent_window_id) = config.window.embed { - x_embed_window(window, parent_window_id); - } - } - } - - let window = Window { - event_loop, - current_mouse_cursor: MouseCursor::Default, - windowed_context, - mouse_visible: true, - is_focused: false, - }; - - Ok(window) - } - - /// Get some properties about the device - /// - /// Some window properties are provided since subsystems like font - /// rasterization depend on DPI and scale factor. - pub fn device_properties(&self) -> DeviceProperties { - DeviceProperties { scale_factor: self.window().get_hidpi_factor() } - } - - pub fn inner_size_pixels(&self) -> Option<LogicalSize> { - self.window().get_inner_size() - } - - pub fn set_inner_size(&mut self, size: LogicalSize) { - self.window().set_inner_size(size); - } - - #[inline] - pub fn hidpi_factor(&self) -> f64 { - self.window().get_hidpi_factor() - } - - #[inline] - pub fn create_window_proxy(&self) -> Proxy { - Proxy { inner: self.event_loop.create_proxy() } - } - - #[inline] - pub fn swap_buffers(&self) -> Result<()> { - self.windowed_context.swap_buffers().map_err(From::from) - } - - /// Poll for any available events - #[inline] - pub fn poll_events<F>(&mut self, func: F) - where - F: FnMut(Event), - { - self.event_loop.poll_events(func); - } - - #[inline] - pub fn resize(&self, size: PhysicalSize) { - self.windowed_context.resize(size); - } - - /// Show window - #[inline] - pub fn show(&self) { - self.window().show(); - } - - /// Block waiting for events - #[inline] - pub fn wait_events<F>(&mut self, func: F) - where - F: FnMut(Event) -> ControlFlow, - { - self.event_loop.run_forever(func); - } - - /// Set the window title - #[inline] - pub fn set_title(&self, title: &str) { - self.window().set_title(title); - } - - #[inline] - pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) { - if cursor != self.current_mouse_cursor { - self.current_mouse_cursor = cursor; - self.window().set_cursor(cursor); - } - } - - /// Set mouse cursor visible - pub fn set_mouse_visible(&mut self, visible: bool) { - if visible != self.mouse_visible { - self.mouse_visible = visible; - self.window().hide_cursor(!visible); - } - } - - #[cfg(not(any(target_os = "macos", windows)))] - pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder { - use glutin::os::unix::WindowBuilderExt; - - let decorations = match window_config.decorations { - Decorations::None => false, - _ => true, - }; - - let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO); - - let class = &window_config.class; - - let mut builder = WindowBuilder::new() - .with_title(title) - .with_visibility(false) - .with_transparency(true) - .with_decorations(decorations) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized) - .with_window_icon(icon.ok()) - // X11 - .with_class(class.instance.clone(), class.general.clone()) - // Wayland - .with_app_id(class.instance.clone()); - - if let Some(ref val) = window_config.gtk_theme_variant { - builder = builder.with_gtk_theme_variant(val.clone()) - } - - builder - } - - #[cfg(windows)] - pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder { - let decorations = match window_config.decorations { - Decorations::None => false, - _ => true, - }; - - let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO); - - WindowBuilder::new() - .with_title(title) - .with_visibility(cfg!(windows)) - .with_decorations(decorations) - .with_transparency(true) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized) - .with_window_icon(icon.ok()) - } - - #[cfg(target_os = "macos")] - pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder { - use glutin::os::macos::WindowBuilderExt; - - let window = WindowBuilder::new() - .with_title(title) - .with_visibility(false) - .with_transparency(true) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized); - - match window_config.decorations { - Decorations::Full => window, - Decorations::Transparent => window - .with_title_hidden(true) - .with_titlebar_transparent(true) - .with_fullsize_content_view(true), - Decorations::Buttonless => window - .with_title_hidden(true) - .with_titlebar_buttons_hidden(true) - .with_titlebar_transparent(true) - .with_fullsize_content_view(true), - Decorations::None => window.with_titlebar_hidden(true), - } - } - - #[cfg(not(any(target_os = "macos", windows)))] - pub fn set_urgent(&self, is_urgent: bool) { - self.window().set_urgent(is_urgent); - } - - #[cfg(target_os = "macos")] - pub fn set_urgent(&self, is_urgent: bool) { - self.window().request_user_attention(is_urgent); - } - - #[cfg(windows)] - pub fn set_urgent(&self, _is_urgent: bool) {} - - pub fn set_ime_spot(&self, pos: LogicalPosition) { - self.window().set_ime_spot(pos); - } - - pub fn set_position(&self, pos: LogicalPosition) { - self.window().set_position(pos); - } - - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - pub fn get_window_id(&self) -> Option<usize> { - match self.window().get_xlib_window() { - Some(xlib_window) => Some(xlib_window as usize), - None => None, - } - } - - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - pub fn is_x11(&self) -> bool { - self.event_loop.is_x11() - } - - #[cfg(any(target_os = "macos", target_os = "windows"))] - pub fn get_window_id(&self) -> Option<usize> { - None - } - - /// Hide the window - pub fn hide(&self) { - self.window().hide(); - } - - /// Fullscreens the window on the current monitor. - pub fn set_fullscreen(&self, fullscreen: bool) { - let glutin_window = self.window(); - if fullscreen { - let current_monitor = glutin_window.get_current_monitor(); - glutin_window.set_fullscreen(Some(current_monitor)); - } else { - glutin_window.set_fullscreen(None); - } - } - - pub fn set_maximized(&self, maximized: bool) { - self.window().set_maximized(maximized); - } - - #[cfg(target_os = "macos")] - pub fn set_simple_fullscreen(&self, fullscreen: bool) { - self.window().set_simple_fullscreen(fullscreen); - } - - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - pub fn get_wayland_display(&self) -> Option<*mut c_void> { - self.window().get_wayland_display() - } - - fn window(&self) -> &glutin::Window { - self.windowed_context.window() - } -} - -#[cfg(not(any(target_os = "macos", windows)))] -fn x_embed_window(window: &GlutinWindow, parent_id: u64) { - let (xlib_display, xlib_window) = match (window.get_xlib_display(), window.get_xlib_window()) { - (Some(display), Some(window)) => (display, window), - _ => return, - }; - - let xlib = Xlib::open().expect("get xlib"); - - unsafe { - let atom = (xlib.XInternAtom)(xlib_display as *mut _, "_XEMBED".as_ptr() as *const _, 0); - (xlib.XChangeProperty)( - xlib_display as _, - xlib_window as _, - atom, - atom, - 32, - PropModeReplace, - [0, 1].as_ptr(), - 2, - ); - - // Register new error handler - let old_handler = (xlib.XSetErrorHandler)(Some(xembed_error_handler)); - - // Check for the existence of the target before attempting reparenting - (xlib.XReparentWindow)(xlib_display as _, xlib_window as _, parent_id, 0, 0); - - // Drain errors and restore original error handler - (xlib.XSync)(xlib_display as _, 0); - (xlib.XSetErrorHandler)(old_handler); - } -} - -#[cfg(not(any(target_os = "macos", windows)))] -unsafe extern "C" fn xembed_error_handler(_: *mut XDisplay, _: *mut XErrorEvent) -> i32 { - die!("Could not embed into specified window."); -} - -impl Proxy { - /// Wakes up the event loop of the window - /// - /// This is useful for triggering a draw when the renderer would otherwise - /// be waiting on user input. - pub fn wakeup_event_loop(&self) { - self.inner.wakeup().unwrap(); - } -} |