mirror of
https://github.com/alacritty/alacritty.git
synced 2025-04-14 17:53:03 -04:00
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.
This commit is contained in:
parent
b0c6fdff76
commit
729eef0c93
54 changed files with 2757 additions and 3033 deletions
CHANGELOG.mdCargo.lock
alacritty
Cargo.toml
src
alacritty_terminal
Cargo.toml
src
ansi.rsclipboard.rs
config
cursor.rsdisplay.rsevent.rsevent_loop.rsgrid
index.rslib.rsmacros.rsmessage_bar.rsmeter.rsrenderer
selection.rsterm
tty
url.rstests
font/src
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -53,6 +53,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Discard scrolling region escape with bottom above top
|
||||
- Opacity always applying to cells with their background color matching the teriminal background
|
||||
- Allow semicolons when setting titles using an OSC
|
||||
- Background always opaque on X11
|
||||
- Skipping redraws on PTY update
|
||||
- Not redrawing while resizing on Windows/macOS
|
||||
- Decorations `none` launching an invisible window on Windows
|
||||
- Alacritty turning transparent when opening another window on macOS with chunkwm
|
||||
- Startup mode `Maximized` having no effect on Windows
|
||||
- Inserting Emojis using `Super+.` or compose sequences on Windows
|
||||
- Change mouse cursor depending on mode with Wayland
|
||||
- Hide mouse cursor when typing if the `mouse.hide_when_typing` option is set on Wayland
|
||||
- Glitches when DPI changes on Windows
|
||||
- Crash when resuming after suspension
|
||||
- Crash when trying to start on X11 with a Wayland compositor running
|
||||
- Crash with a virtual display connected on X11
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
461
Cargo.lock
generated
461
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -14,9 +14,15 @@ clap = "2"
|
|||
log = "0.4"
|
||||
time = "0.1.40"
|
||||
env_logger = "0.6.0"
|
||||
crossbeam-channel = "0.3.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "1"
|
||||
glutin = { git = "https://github.com/chrisduerr/glutin" }
|
||||
notify = "4"
|
||||
libc = "0.2"
|
||||
unicode-width = "0.1"
|
||||
parking_lot = "0.9"
|
||||
font = { path = "../font" }
|
||||
|
||||
[build-dependencies]
|
||||
rustc_tools_util = "0.2.0"
|
||||
|
@ -24,9 +30,15 @@ rustc_tools_util = "0.2.0"
|
|||
[target.'cfg(not(windows))'.dependencies]
|
||||
xdg = "2"
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
image = "0.21.0"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", windows))'.dependencies]
|
||||
dirs = "1.0.2"
|
||||
|
||||
[target.'cfg(not(any(target_os="windows", target_os="macos")))'.dependencies]
|
||||
x11-dl = "2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["impl-default", "winuser", "synchapi", "roerrorapi", "winerror", "wincon", "wincontypes"]}
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@ use std::path::{Path, PathBuf};
|
|||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
|
||||
use log::{self, LevelFilter};
|
||||
|
||||
use alacritty_terminal::config::{Config, Delta, Dimensions, Shell};
|
||||
use alacritty_terminal::config::{Delta, Dimensions, Shell, DEFAULT_NAME};
|
||||
use alacritty_terminal::index::{Column, Line};
|
||||
use alacritty_terminal::window::DEFAULT_NAME;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
/// Options specified on the command line
|
||||
pub struct Options {
|
||||
|
@ -283,9 +284,10 @@ impl Options {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use alacritty_terminal::config::{Config, DEFAULT_ALACRITTY_CONFIG};
|
||||
use alacritty_terminal::config::DEFAULT_ALACRITTY_CONFIG;
|
||||
|
||||
use crate::cli::Options;
|
||||
use crate::config::Config;
|
||||
|
||||
#[test]
|
||||
fn dynamic_title_ignoring_options_by_default() {
|
||||
|
|
|
@ -15,13 +15,207 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use glutin::{ModifiersState, MouseButton};
|
||||
use glutin::event::{ModifiersState, MouseButton};
|
||||
use log::error;
|
||||
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;
|
||||
use alacritty_terminal::config::LOG_TARGET_CONFIG;
|
||||
use alacritty_terminal::term::TermMode;
|
||||
|
||||
/// 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]
|
||||
pub 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 == mods || (relaxed && self.mods.relaxed_eq(mods)))
|
||||
}
|
||||
|
||||
#[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))
|
||||
}
|
||||
}
|
||||
|
||||
#[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 From<&'static str> for Action {
|
||||
fn from(s: &'static str) -> Action {
|
||||
Action::Esc(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub 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)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bindings {
|
||||
(
|
||||
|
@ -444,8 +638,8 @@ pub enum Key {
|
|||
}
|
||||
|
||||
impl Key {
|
||||
pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self {
|
||||
use glutin::VirtualKeyCode::*;
|
||||
pub fn from_glutin_input(key: glutin::event::VirtualKeyCode) -> Self {
|
||||
use glutin::event::VirtualKeyCode::*;
|
||||
// Thank you, vim macros and regex!
|
||||
match key {
|
||||
Key1 => Key::Key1,
|
||||
|
@ -646,7 +840,7 @@ impl<'a> Deserialize<'a> for ModeWrapper {
|
|||
"~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD,
|
||||
"~alt" => res.not_mode |= TermMode::ALT_SCREEN,
|
||||
"alt" => res.mode |= TermMode::ALT_SCREEN,
|
||||
_ => error!("Unknown mode {:?}", modifier),
|
||||
_ => error!(target: LOG_TARGET_CONFIG, "Unknown mode {:?}", modifier),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -812,7 +1006,7 @@ impl<'a> Deserialize<'a> for RawBinding {
|
|||
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 action: Option<Action> = None;
|
||||
let mut mode: Option<TermMode> = None;
|
||||
let mut not_mode: Option<TermMode> = None;
|
||||
let mut mouse: Option<MouseButton> = None;
|
||||
|
@ -1010,7 +1204,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper {
|
|||
"alt" | "option" => res.alt = true,
|
||||
"control" => res.ctrl = true,
|
||||
"none" => (),
|
||||
_ => error!("Unknown modifier {:?}", modifier),
|
||||
_ => error!(target: LOG_TARGET_CONFIG, "Unknown modifier {:?}", modifier),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1021,3 +1215,195 @@ impl<'a> de::Deserialize<'a> for ModsWrapper {
|
|||
deserializer.deserialize_str(ModsVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use glutin::event::ModifiersState;
|
||||
|
||||
use alacritty_terminal::term::TermMode;
|
||||
|
||||
use crate::config::{Action, Binding};
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -11,9 +11,23 @@ use serde_yaml;
|
|||
#[cfg(not(windows))]
|
||||
use xdg;
|
||||
|
||||
use alacritty_terminal::config::{Config, DEFAULT_ALACRITTY_CONFIG};
|
||||
use alacritty_terminal::config::{
|
||||
Config as TermConfig, DEFAULT_ALACRITTY_CONFIG, LOG_TARGET_CONFIG,
|
||||
};
|
||||
|
||||
pub const SOURCE_FILE_PATH: &str = file!();
|
||||
mod bindings;
|
||||
pub mod monitor;
|
||||
mod mouse;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod ui_config;
|
||||
|
||||
pub use crate::config::bindings::{Action, Binding, Key, RelaxedEq};
|
||||
#[cfg(test)]
|
||||
pub use crate::config::mouse::{ClickHandler, Mouse};
|
||||
use crate::config::ui_config::UIConfig;
|
||||
|
||||
pub type Config = TermConfig<UIConfig>;
|
||||
|
||||
/// Result from config loading
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
@ -169,7 +183,7 @@ pub fn reload_from(path: &PathBuf) -> Result<Config> {
|
|||
match read_config(path) {
|
||||
Ok(config) => Ok(config),
|
||||
Err(err) => {
|
||||
error!("Unable to load config {:?}: {}", path, err);
|
||||
error!(target: LOG_TARGET_CONFIG, "Unable to load config {:?}: {}", path, err);
|
||||
Err(err)
|
||||
},
|
||||
}
|
||||
|
@ -199,16 +213,21 @@ fn read_config(path: &PathBuf) -> Result<Config> {
|
|||
fn print_deprecation_warnings(config: &Config) {
|
||||
if config.window.start_maximized.is_some() {
|
||||
warn!(
|
||||
target: LOG_TARGET_CONFIG,
|
||||
"Config window.start_maximized is deprecated; please use window.startup_mode instead"
|
||||
);
|
||||
}
|
||||
|
||||
if config.render_timer.is_some() {
|
||||
warn!("Config render_timer is deprecated; please use debug.render_timer instead");
|
||||
warn!(
|
||||
target: LOG_TARGET_CONFIG,
|
||||
"Config render_timer is deprecated; please use debug.render_timer instead"
|
||||
);
|
||||
}
|
||||
|
||||
if config.persistent_logging.is_some() {
|
||||
warn!(
|
||||
target: LOG_TARGET_CONFIG,
|
||||
"Config persistent_logging is deprecated; please use debug.persistent_logging instead"
|
||||
);
|
||||
}
|
|
@ -4,43 +4,24 @@ use std::time::Duration;
|
|||
|
||||
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||
|
||||
use alacritty_terminal::event::{Event, EventListener};
|
||||
use alacritty_terminal::util;
|
||||
|
||||
use crate::event::EventProxy;
|
||||
|
||||
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
|
||||
pub fn new<P>(path: P, event_proxy: EventProxy) -> 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 || {
|
||||
_thread: util::thread::spawn_named("config watcher", move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
// The Duration argument is a debouncing period.
|
||||
let mut watcher =
|
||||
|
@ -66,14 +47,12 @@ impl Monitor {
|
|||
continue;
|
||||
}
|
||||
|
||||
let _ = config_tx.send(path);
|
||||
handler.on_config_reload();
|
||||
event_proxy.send_event(Event::ConfigReload(path));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}),
|
||||
rx: config_rx,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use glutin::ModifiersState;
|
||||
use glutin::event::ModifiersState;
|
||||
use log::error;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG};
|
||||
|
||||
use crate::config::bindings::{CommandWrapper, ModsWrapper};
|
||||
use crate::config::failure_default;
|
||||
|
||||
#[serde(default)]
|
||||
#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
|
@ -56,7 +58,12 @@ where
|
|||
match <Option<CommandWrapper>>::deserialize(val) {
|
||||
Ok(launcher) => Ok(launcher),
|
||||
Err(err) => {
|
||||
error!("Problem with config: {}; using {}", err, default.clone().unwrap().program());
|
||||
error!(
|
||||
target: LOG_TARGET_CONFIG,
|
||||
"Problem with config: {}; using {}",
|
||||
err,
|
||||
default.clone().unwrap().program()
|
||||
);
|
||||
Ok(default)
|
||||
},
|
||||
}
|
||||
|
@ -101,7 +108,7 @@ where
|
|||
match u64::deserialize(value) {
|
||||
Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)),
|
||||
Err(err) => {
|
||||
error!("Problem with config: {}; using default value", err);
|
||||
error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; using default value", err);
|
||||
Ok(default_threshold_ms())
|
||||
},
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use crate::config::{Config, DEFAULT_ALACRITTY_CONFIG};
|
||||
use alacritty_terminal::config::DEFAULT_ALACRITTY_CONFIG;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
#[test]
|
||||
fn parse_config() {
|
||||
|
@ -6,10 +8,10 @@ fn parse_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());
|
||||
assert!(!config.ui_config.mouse_bindings.is_empty());
|
||||
|
||||
// Sanity check that key bindings are being parsed
|
||||
assert!(!config.key_bindings.is_empty());
|
||||
assert!(!config.ui_config.key_bindings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
63
alacritty/src/config/ui_config.rs
Normal file
63
alacritty/src/config/ui_config.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use alacritty_terminal::config::failure_default;
|
||||
|
||||
use crate::config::bindings::{self, Binding, KeyBinding, MouseBinding};
|
||||
use crate::config::mouse::Mouse;
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
pub struct UIConfig {
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub mouse: Mouse,
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
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,
|
||||
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)
|
||||
}
|
476
alacritty/src/display.rs
Normal file
476
alacritty/src/display.rs
Normal file
|
@ -0,0 +1,476 @@
|
|||
// 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::cmp::max;
|
||||
use std::f64;
|
||||
use std::fmt;
|
||||
use std::time::Instant;
|
||||
|
||||
use glutin::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use glutin::event_loop::EventLoop;
|
||||
use log::{debug, info};
|
||||
use parking_lot::MutexGuard;
|
||||
|
||||
use font::{self, Rasterize, Size};
|
||||
|
||||
use alacritty_terminal::config::StartupMode;
|
||||
use alacritty_terminal::event::{Event, OnResize};
|
||||
use alacritty_terminal::index::Line;
|
||||
use alacritty_terminal::message_bar::MessageBuffer;
|
||||
use alacritty_terminal::meter::Meter;
|
||||
use alacritty_terminal::renderer::rects::{RenderLines, RenderRect};
|
||||
use alacritty_terminal::renderer::{self, GlyphCache, QuadRenderer};
|
||||
use alacritty_terminal::term::color::Rgb;
|
||||
use alacritty_terminal::term::{RenderableCell, SizeInfo, Term};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::event::{FontResize, Resize};
|
||||
use crate::window::{self, Window};
|
||||
|
||||
/// Font size change interval
|
||||
pub const FONT_SIZE_STEP: f32 = 0.5;
|
||||
|
||||
#[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),
|
||||
|
||||
/// Error during buffer swap
|
||||
ContextError(glutin::ContextError),
|
||||
}
|
||||
|
||||
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),
|
||||
Error::ContextError(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(),
|
||||
Error::ContextError(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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),
|
||||
Error::ContextError(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)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<glutin::ContextError> for Error {
|
||||
fn from(val: glutin::ContextError) -> Error {
|
||||
Error::ContextError(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// The display wraps a window, font rasterizer, and GPU renderer
|
||||
pub struct Display {
|
||||
pub size_info: SizeInfo,
|
||||
pub font_size: Size,
|
||||
pub window: Window,
|
||||
|
||||
renderer: QuadRenderer,
|
||||
glyph_cache: GlyphCache,
|
||||
meter: Meter,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn new(config: &Config, event_loop: &EventLoop<Event>) -> Result<Display, Error> {
|
||||
// Guess DPR based on first monitor
|
||||
let estimated_dpr =
|
||||
event_loop.available_monitors().next().map(|m| m.hidpi_factor()).unwrap_or(1.);
|
||||
|
||||
// Guess the target window dimensions
|
||||
let metrics = GlyphCache::static_metrics(config.font.clone(), estimated_dpr)?;
|
||||
let (cell_width, cell_height) = compute_cell_size(config, &metrics);
|
||||
let dimensions =
|
||||
GlyphCache::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));
|
||||
|
||||
// Spawn window
|
||||
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().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 = f32::from(config.window.padding.x) * dpr as f32;
|
||||
let mut padding_y = f32::from(config.window.padding.y) * dpr as f32;
|
||||
|
||||
if let Some((width, height)) =
|
||||
GlyphCache::calculate_dimensions(config, dpr, cell_width, cell_height)
|
||||
{
|
||||
let PhysicalSize { width: w, height: h } = window.inner_size().to_physical(dpr);
|
||||
if (w - width).abs() < f64::EPSILON && (h - height).abs() < f64::EPSILON {
|
||||
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
|
||||
padding_x = dynamic_padding(padding_x, viewport_size.width as f32, cell_width);
|
||||
padding_y = dynamic_padding(padding_y, viewport_size.height as f32, cell_height);
|
||||
}
|
||||
|
||||
padding_x = padding_x.floor();
|
||||
padding_y = padding_y.floor();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
// Update OpenGL projection
|
||||
renderer.resize(&size_info);
|
||||
|
||||
// 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.set_visible(true);
|
||||
|
||||
// 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(dpr);
|
||||
window.set_outer_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 => window.set_maximized(true),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(Display {
|
||||
window,
|
||||
renderer,
|
||||
glyph_cache,
|
||||
meter: Meter::new(),
|
||||
size_info,
|
||||
font_size: config.font.size,
|
||||
})
|
||||
}
|
||||
|
||||
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 = 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) = compute_cell_size(config, &glyph_cache.font_metrics());
|
||||
|
||||
Ok((glyph_cache, cw, ch))
|
||||
}
|
||||
|
||||
/// Update font size and cell dimensions
|
||||
fn update_glyph_cache(&mut self, config: &Config, size: Size) {
|
||||
let size_info = &mut self.size_info;
|
||||
let cache = &mut self.glyph_cache;
|
||||
|
||||
let font = config.font.clone().with_size(size);
|
||||
|
||||
self.renderer.with_loader(|mut api| {
|
||||
let _ = cache.update_font_size(font, size_info.dpr, &mut api);
|
||||
});
|
||||
|
||||
// Update cell size
|
||||
let (cell_width, cell_height) = compute_cell_size(config, &self.glyph_cache.font_metrics());
|
||||
size_info.cell_width = cell_width;
|
||||
size_info.cell_height = cell_height;
|
||||
}
|
||||
|
||||
/// Process resize events
|
||||
pub fn handle_resize<T>(
|
||||
&mut self,
|
||||
terminal: &mut Term<T>,
|
||||
pty_resize_handle: &mut dyn OnResize,
|
||||
message_buffer: &MessageBuffer,
|
||||
config: &Config,
|
||||
resize_pending: Resize,
|
||||
) {
|
||||
// Update font size and cell dimensions
|
||||
if let Some(resize) = resize_pending.font_size {
|
||||
self.font_size = match resize {
|
||||
FontResize::Delta(delta) => max(self.font_size + delta, FONT_SIZE_STEP.into()),
|
||||
FontResize::Reset => config.font.size,
|
||||
};
|
||||
|
||||
self.update_glyph_cache(config, self.font_size);
|
||||
}
|
||||
|
||||
// Update the window dimensions
|
||||
if let Some(size) = resize_pending.dimensions {
|
||||
self.size_info.width = size.width as f32;
|
||||
self.size_info.height = size.height as f32;
|
||||
}
|
||||
|
||||
let dpr = self.size_info.dpr;
|
||||
let width = self.size_info.width;
|
||||
let height = self.size_info.height;
|
||||
let cell_width = self.size_info.cell_width;
|
||||
let cell_height = self.size_info.cell_height;
|
||||
|
||||
// Recalculate padding
|
||||
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 = dynamic_padding(padding_x, width, cell_width);
|
||||
padding_y = dynamic_padding(padding_y, height, cell_height);
|
||||
}
|
||||
|
||||
self.size_info.padding_x = padding_x.floor() as f32;
|
||||
self.size_info.padding_y = padding_y.floor() as f32;
|
||||
|
||||
let mut pty_size = self.size_info;
|
||||
|
||||
// Subtract message bar lines from pty size
|
||||
if resize_pending.message_buffer.is_some() {
|
||||
let lines =
|
||||
message_buffer.message().map(|m| m.text(&self.size_info).len()).unwrap_or(0);
|
||||
pty_size.height -= pty_size.cell_height * lines as f32;
|
||||
}
|
||||
|
||||
// Resize PTY
|
||||
pty_resize_handle.on_resize(&pty_size);
|
||||
|
||||
// Resize terminal
|
||||
terminal.resize(&pty_size);
|
||||
|
||||
// Resize renderer
|
||||
let physical =
|
||||
PhysicalSize::new(f64::from(self.size_info.width), f64::from(self.size_info.height));
|
||||
self.renderer.resize(&self.size_info);
|
||||
self.window.resize(physical);
|
||||
}
|
||||
|
||||
/// 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<T>(
|
||||
&mut self,
|
||||
terminal: MutexGuard<'_, Term<T>>,
|
||||
message_buffer: &MessageBuffer,
|
||||
config: &Config,
|
||||
) {
|
||||
let grid_cells: Vec<RenderableCell> = terminal.renderable_cells(config).collect();
|
||||
let visual_bell_intensity = terminal.visual_bell.intensity();
|
||||
let background_color = terminal.background_color();
|
||||
let metrics = self.glyph_cache.font_metrics();
|
||||
let glyph_cache = &mut self.glyph_cache;
|
||||
let size_info = self.size_info;
|
||||
|
||||
// Update IME position
|
||||
#[cfg(not(windows))]
|
||||
self.window.update_ime_position(&terminal, &self.size_info);
|
||||
|
||||
// Drop terminal as early as possible to free lock
|
||||
drop(terminal);
|
||||
|
||||
self.renderer.with_api(&config, &size_info, |api| {
|
||||
api.clear(background_color);
|
||||
});
|
||||
|
||||
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.message() {
|
||||
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(
|
||||
&size_info,
|
||||
config.visual_bell.color,
|
||||
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(
|
||||
&size_info,
|
||||
config.visual_bell.color,
|
||||
visual_bell_intensity,
|
||||
rects,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw render timer
|
||||
if config.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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate padding to spread it evenly around the terminal content
|
||||
#[inline]
|
||||
fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
|
||||
padding + ((dimension - 2. * padding) % cell_dimension) / 2.
|
||||
}
|
||||
|
||||
/// Calculate the cell dimensions based on font metrics.
|
||||
#[inline]
|
||||
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()),
|
||||
)
|
||||
}
|
651
alacritty/src/event.rs
Normal file
651
alacritty/src/event.rs
Normal file
|
@ -0,0 +1,651 @@
|
|||
//! Process window events
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
#[cfg(unix)]
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use glutin::dpi::PhysicalSize;
|
||||
use glutin::event::{ElementState, Event as GlutinEvent, MouseButton};
|
||||
use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy};
|
||||
use glutin::platform::desktop::EventLoopExtDesktop;
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
use glutin::platform::unix::EventLoopWindowTargetExtUnix;
|
||||
use log::{debug, info, warn};
|
||||
use serde_json as json;
|
||||
|
||||
use font::Size;
|
||||
|
||||
use alacritty_terminal::clipboard::ClipboardType;
|
||||
use alacritty_terminal::config::LOG_TARGET_CONFIG;
|
||||
use alacritty_terminal::event::OnResize;
|
||||
use alacritty_terminal::event::{Event, EventListener, Notify};
|
||||
use alacritty_terminal::grid::Scroll;
|
||||
use alacritty_terminal::index::{Column, Line, Point, Side};
|
||||
use alacritty_terminal::message_bar::{Message, MessageBuffer};
|
||||
use alacritty_terminal::selection::Selection;
|
||||
use alacritty_terminal::sync::FairMutex;
|
||||
use alacritty_terminal::term::cell::Cell;
|
||||
use alacritty_terminal::term::{SizeInfo, Term};
|
||||
use alacritty_terminal::tty;
|
||||
use alacritty_terminal::util::{limit, start_daemon};
|
||||
|
||||
use crate::config;
|
||||
use crate::config::Config;
|
||||
use crate::display::Display;
|
||||
use crate::input::{self, ActionContext as _, Modifiers};
|
||||
use crate::window::Window;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum FontResize {
|
||||
Delta(f32),
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Resize {
|
||||
pub dimensions: Option<PhysicalSize>,
|
||||
pub message_buffer: Option<()>,
|
||||
pub font_size: Option<FontResize>,
|
||||
}
|
||||
|
||||
impl Resize {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.dimensions.is_none() && self.font_size.is_none() && self.message_buffer.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActionContext<'a, N, T> {
|
||||
pub notifier: &'a mut N,
|
||||
pub terminal: &'a mut Term<T>,
|
||||
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: &'a mut Window,
|
||||
pub message_buffer: &'a mut MessageBuffer,
|
||||
pub resize_pending: &'a mut Resize,
|
||||
pub font_size: &'a Size,
|
||||
}
|
||||
|
||||
impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionContext<'a, N, T> {
|
||||
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> {
|
||||
let x = self.mouse.x as usize;
|
||||
let y = self.mouse.y as usize;
|
||||
|
||||
if self.size_info.contains_point(x, y, true) {
|
||||
Some(self.size_info.pixels_to_coords(x, y))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[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 window(&self) -> &Window {
|
||||
self.window
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn window_mut(&mut self) -> &mut Window {
|
||||
self.window
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn terminal(&self) -> &Term<T> {
|
||||
self.terminal
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn terminal_mut(&mut self) -> &mut Term<T> {
|
||||
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 change_font_size(&mut self, delta: f32) {
|
||||
self.resize_pending.font_size = Some(FontResize::Delta(delta));
|
||||
self.terminal.dirty = true;
|
||||
}
|
||||
|
||||
fn reset_font_size(&mut self) {
|
||||
self.resize_pending.font_size = Some(FontResize::Reset);
|
||||
self.terminal.dirty = true;
|
||||
}
|
||||
|
||||
fn pop_message(&mut self) {
|
||||
self.resize_pending.message_buffer = Some(());
|
||||
self.message_buffer.pop();
|
||||
}
|
||||
|
||||
fn message(&self) -> Option<&Message> {
|
||||
self.message_buffer.message()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
notifier: N,
|
||||
mouse: Mouse,
|
||||
received_count: usize,
|
||||
suppress_chars: bool,
|
||||
modifiers: Modifiers,
|
||||
config: Config,
|
||||
pty_resize_handle: Box<dyn OnResize>,
|
||||
message_buffer: MessageBuffer,
|
||||
display: Display,
|
||||
}
|
||||
|
||||
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,
|
||||
pty_resize_handle: Box<dyn OnResize>,
|
||||
message_buffer: MessageBuffer,
|
||||
config: Config,
|
||||
display: Display,
|
||||
) -> Processor<N> {
|
||||
Processor {
|
||||
notifier,
|
||||
mouse: Default::default(),
|
||||
received_count: 0,
|
||||
suppress_chars: false,
|
||||
modifiers: Default::default(),
|
||||
config,
|
||||
pty_resize_handle,
|
||||
message_buffer,
|
||||
display,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the event loop.
|
||||
pub fn run<T>(&mut self, terminal: Arc<FairMutex<Term<T>>>, mut event_loop: EventLoop<Event>)
|
||||
where
|
||||
T: EventListener,
|
||||
{
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
let mut dpr_initialized = false;
|
||||
|
||||
let mut event_queue = Vec::new();
|
||||
|
||||
event_loop.run_return(|event, _event_loop, control_flow| {
|
||||
if self.config.debug.print_events {
|
||||
info!("glutin event: {:?}", event);
|
||||
}
|
||||
|
||||
match (&event, tty::process_should_exit()) {
|
||||
// Check for shutdown
|
||||
(GlutinEvent::UserEvent(Event::Exit), _) | (_, true) => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
},
|
||||
// Process events
|
||||
(GlutinEvent::EventsCleared, _) => {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if event_queue.is_empty() {
|
||||
return;
|
||||
}
|
||||
},
|
||||
// Buffer events
|
||||
_ => {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
if !Self::skip_event(&event) {
|
||||
event_queue.push(event);
|
||||
}
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
let mut terminal = terminal.lock();
|
||||
|
||||
let mut resize_pending = Resize::default();
|
||||
|
||||
let context = ActionContext {
|
||||
terminal: &mut terminal,
|
||||
notifier: &mut self.notifier,
|
||||
mouse: &mut self.mouse,
|
||||
size_info: &mut self.display.size_info,
|
||||
received_count: &mut self.received_count,
|
||||
suppress_chars: &mut self.suppress_chars,
|
||||
modifiers: &mut self.modifiers,
|
||||
message_buffer: &mut self.message_buffer,
|
||||
resize_pending: &mut resize_pending,
|
||||
window: &mut self.display.window,
|
||||
font_size: &self.display.font_size,
|
||||
};
|
||||
let mut processor = input::Processor::new(context, &mut self.config);
|
||||
|
||||
for event in event_queue.drain(..) {
|
||||
Processor::handle_event(event, &mut processor);
|
||||
}
|
||||
|
||||
// TODO: Workaround for incorrect startup DPI on X11
|
||||
// https://github.com/rust-windowing/winit/issues/998
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
{
|
||||
if !dpr_initialized && _event_loop.is_x11() {
|
||||
dpr_initialized = true;
|
||||
|
||||
let dpr = self.display.window.hidpi_factor();
|
||||
self.display.size_info.dpr = dpr;
|
||||
|
||||
let size = self.display.window.inner_size().to_physical(dpr);
|
||||
|
||||
resize_pending.font_size = Some(FontResize::Delta(0.));
|
||||
resize_pending.dimensions = Some(size);
|
||||
|
||||
terminal.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process resize events
|
||||
if !resize_pending.is_empty() {
|
||||
self.display.handle_resize(
|
||||
&mut terminal,
|
||||
self.pty_resize_handle.as_mut(),
|
||||
&self.message_buffer,
|
||||
&self.config,
|
||||
resize_pending,
|
||||
);
|
||||
}
|
||||
|
||||
if terminal.dirty {
|
||||
// Clear dirty flag
|
||||
terminal.dirty = !terminal.visual_bell.completed();
|
||||
|
||||
// Redraw screen
|
||||
self.display.draw(terminal, &self.message_buffer, &self.config);
|
||||
}
|
||||
});
|
||||
|
||||
// Write ref tests to disk
|
||||
self.write_ref_test_results(&terminal.lock());
|
||||
}
|
||||
|
||||
/// Handle events from glutin
|
||||
///
|
||||
/// Doesn't take self mutably due to borrow checking. Kinda uggo but w/e.
|
||||
fn handle_event<T>(
|
||||
event: GlutinEvent<Event>,
|
||||
processor: &mut input::Processor<T, ActionContext<N, T>>,
|
||||
) where
|
||||
T: EventListener,
|
||||
{
|
||||
match event {
|
||||
GlutinEvent::UserEvent(event) => match event {
|
||||
Event::Title(title) => processor.ctx.window.set_title(&title),
|
||||
Event::Wakeup => processor.ctx.terminal.dirty = true,
|
||||
Event::Urgent => {
|
||||
processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused)
|
||||
},
|
||||
Event::ConfigReload(path) => {
|
||||
processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG);
|
||||
processor.ctx.resize_pending.message_buffer = Some(());
|
||||
|
||||
if let Ok(config) = config::reload_from(&path) {
|
||||
processor.ctx.terminal.update_config(&config);
|
||||
|
||||
if *processor.ctx.font_size == processor.config.font.size {
|
||||
processor.ctx.resize_pending.font_size = Some(FontResize::Reset);
|
||||
}
|
||||
|
||||
*processor.config = config;
|
||||
|
||||
processor.ctx.terminal.dirty = true;
|
||||
}
|
||||
},
|
||||
Event::Message(message) => {
|
||||
processor.ctx.message_buffer.push(message);
|
||||
processor.ctx.resize_pending.message_buffer = Some(());
|
||||
processor.ctx.terminal.dirty = true;
|
||||
},
|
||||
Event::MouseCursorDirty => processor.reset_mouse_cursor(),
|
||||
Event::Exit => (),
|
||||
},
|
||||
GlutinEvent::WindowEvent { event, window_id, .. } => {
|
||||
use glutin::event::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => processor.ctx.terminal.exit(),
|
||||
Resized(lsize) => {
|
||||
let psize = lsize.to_physical(processor.ctx.size_info.dpr);
|
||||
processor.ctx.resize_pending.dimensions = Some(psize);
|
||||
processor.ctx.terminal.dirty = true;
|
||||
},
|
||||
KeyboardInput { input, .. } => {
|
||||
processor.process_key(input);
|
||||
if input.state == ElementState::Pressed {
|
||||
// Hide cursor while typing
|
||||
if processor.config.ui_config.mouse.hide_when_typing {
|
||||
processor.ctx.window.set_mouse_visible(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
ReceivedCharacter(c) => processor.received_char(c),
|
||||
MouseInput { state, button, modifiers, .. } => {
|
||||
if !cfg!(target_os = "macos") || processor.ctx.terminal.is_focused {
|
||||
processor.ctx.window.set_mouse_visible(true);
|
||||
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);
|
||||
|
||||
processor.ctx.window.set_mouse_visible(true);
|
||||
processor.mouse_moved(x as usize, y as usize, modifiers);
|
||||
},
|
||||
MouseWheel { delta, phase, modifiers, .. } => {
|
||||
processor.ctx.window.set_mouse_visible(true);
|
||||
processor.on_mouse_wheel(delta, phase, modifiers);
|
||||
},
|
||||
Focused(is_focused) => {
|
||||
if window_id == processor.ctx.window.window_id() {
|
||||
processor.ctx.terminal.is_focused = is_focused;
|
||||
processor.ctx.terminal.dirty = true;
|
||||
|
||||
if is_focused {
|
||||
processor.ctx.window.set_urgent(false);
|
||||
} else {
|
||||
processor.ctx.window.set_mouse_visible(true);
|
||||
}
|
||||
|
||||
processor.on_focus_change(is_focused);
|
||||
}
|
||||
},
|
||||
DroppedFile(path) => {
|
||||
let path: String = path.to_string_lossy().into();
|
||||
processor.ctx.write_to_pty(path.into_bytes());
|
||||
},
|
||||
HiDpiFactorChanged(dpr) => {
|
||||
let dpr_change = (dpr / processor.ctx.size_info.dpr) as f32;
|
||||
let resize_pending = &mut processor.ctx.resize_pending;
|
||||
|
||||
// Push current font to update its DPR
|
||||
resize_pending.font_size = Some(FontResize::Delta(0.));
|
||||
|
||||
// Scale window dimensions with new DPR
|
||||
let old_width = processor.ctx.size_info.width;
|
||||
let old_height = processor.ctx.size_info.height;
|
||||
let dimensions = resize_pending.dimensions.get_or_insert_with(|| {
|
||||
PhysicalSize::new(f64::from(old_width), f64::from(old_height))
|
||||
});
|
||||
dimensions.width *= f64::from(dpr_change);
|
||||
dimensions.height *= f64::from(dpr_change);
|
||||
|
||||
processor.ctx.terminal.dirty = true;
|
||||
processor.ctx.size_info.dpr = dpr;
|
||||
},
|
||||
RedrawRequested => processor.ctx.terminal.dirty = true,
|
||||
TouchpadPressure { .. }
|
||||
| CursorEntered { .. }
|
||||
| CursorLeft { .. }
|
||||
| AxisMotion { .. }
|
||||
| HoveredFileCancelled
|
||||
| Destroyed
|
||||
| HoveredFile(_)
|
||||
| Touch(_)
|
||||
| Moved(_) => (),
|
||||
// TODO: Add support for proper modifier handling
|
||||
ModifiersChanged { .. } => (),
|
||||
}
|
||||
},
|
||||
GlutinEvent::DeviceEvent { .. }
|
||||
| GlutinEvent::Suspended { .. }
|
||||
| GlutinEvent::NewEvents { .. }
|
||||
| GlutinEvent::EventsCleared
|
||||
| GlutinEvent::Resumed
|
||||
| GlutinEvent::LoopDestroyed => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an event is irrelevant and can be skipped
|
||||
fn skip_event(event: &GlutinEvent<Event>) -> bool {
|
||||
match event {
|
||||
GlutinEvent::UserEvent(Event::Exit) => true,
|
||||
GlutinEvent::WindowEvent { event, .. } => {
|
||||
use glutin::event::WindowEvent::*;
|
||||
match event {
|
||||
TouchpadPressure { .. }
|
||||
| CursorEntered { .. }
|
||||
| CursorLeft { .. }
|
||||
| AxisMotion { .. }
|
||||
| HoveredFileCancelled
|
||||
| Destroyed
|
||||
| HoveredFile(_)
|
||||
| Touch(_)
|
||||
| Moved(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
GlutinEvent::DeviceEvent { .. }
|
||||
| GlutinEvent::Suspended { .. }
|
||||
| GlutinEvent::NewEvents { .. }
|
||||
| GlutinEvent::EventsCleared
|
||||
| GlutinEvent::LoopDestroyed => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Write the ref test results to the disk
|
||||
pub fn write_ref_test_results<T>(&self, terminal: &Term<T>) {
|
||||
if !self.config.debug.ref_test {
|
||||
return;
|
||||
}
|
||||
|
||||
// dump grid state
|
||||
let mut grid = terminal.grid().clone();
|
||||
grid.initialize_all(&Cell::default());
|
||||
grid.truncate();
|
||||
|
||||
let serialized_grid = json::to_string(&grid).expect("serialize grid");
|
||||
|
||||
let serialized_size = json::to_string(&self.display.size_info).expect("serialize size");
|
||||
|
||||
let serialized_config = format!("{{\"history_size\":{}}}", grid.history_size());
|
||||
|
||||
File::create("./grid.json")
|
||||
.and_then(|mut f| f.write_all(serialized_grid.as_bytes()))
|
||||
.expect("write grid.json");
|
||||
|
||||
File::create("./size.json")
|
||||
.and_then(|mut f| f.write_all(serialized_size.as_bytes()))
|
||||
.expect("write size.json");
|
||||
|
||||
File::create("./config.json")
|
||||
.and_then(|mut f| f.write_all(serialized_config.as_bytes()))
|
||||
.expect("write config.json");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventProxy(EventLoopProxy<Event>);
|
||||
|
||||
impl EventProxy {
|
||||
pub fn new(proxy: EventLoopProxy<Event>) -> Self {
|
||||
EventProxy(proxy)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventListener for EventProxy {
|
||||
fn send_event(&self, event: Event) {
|
||||
let _ = self.0.send_event(event);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -25,10 +25,11 @@ use std::process;
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
use glutin::event_loop::EventLoopProxy;
|
||||
use log::{self, Level};
|
||||
use time;
|
||||
|
||||
use alacritty_terminal::event::Event;
|
||||
use alacritty_terminal::message_bar::Message;
|
||||
use alacritty_terminal::term::color;
|
||||
|
||||
|
@ -38,7 +39,7 @@ const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG";
|
|||
|
||||
pub fn initialize(
|
||||
options: &Options,
|
||||
message_tx: Sender<Message>,
|
||||
event_proxy: EventLoopProxy<Event>,
|
||||
) -> Result<Option<PathBuf>, log::SetLoggerError> {
|
||||
log::set_max_level(options.log_level);
|
||||
|
||||
|
@ -48,7 +49,7 @@ pub fn initialize(
|
|||
::env_logger::try_init()?;
|
||||
Ok(None)
|
||||
} else {
|
||||
let logger = Logger::new(message_tx);
|
||||
let logger = Logger::new(event_proxy);
|
||||
let path = logger.file_path();
|
||||
log::set_boxed_logger(Box::new(logger))?;
|
||||
Ok(path)
|
||||
|
@ -58,15 +59,15 @@ pub fn initialize(
|
|||
pub struct Logger {
|
||||
logfile: Mutex<OnDemandLogFile>,
|
||||
stdout: Mutex<LineWriter<Stdout>>,
|
||||
message_tx: Sender<Message>,
|
||||
event_proxy: Mutex<EventLoopProxy<Event>>,
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
fn new(message_tx: Sender<Message>) -> Self {
|
||||
fn new(event_proxy: EventLoopProxy<Event>) -> Self {
|
||||
let logfile = Mutex::new(OnDemandLogFile::new());
|
||||
let stdout = Mutex::new(LineWriter::new(io::stdout()));
|
||||
|
||||
Logger { logfile, stdout, message_tx }
|
||||
Logger { logfile, stdout, event_proxy: Mutex::new(event_proxy) }
|
||||
}
|
||||
|
||||
fn file_path(&self) -> Option<PathBuf> {
|
||||
|
@ -122,9 +123,12 @@ impl log::Log for Logger {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut message = Message::new(msg, color);
|
||||
message.set_topic(record.file().unwrap_or("?").into());
|
||||
let _ = self.message_tx.send(message);
|
||||
if let Ok(event_proxy) = self.event_proxy.lock() {
|
||||
let mut message = Message::new(msg, color);
|
||||
message.set_target(record.target().to_owned());
|
||||
|
||||
let _ = event_proxy.send_event(Event::Message(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#[cfg(target_os = "macos")]
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::fs::{self, File};
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
@ -33,29 +33,35 @@ use std::sync::Arc;
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
use dirs;
|
||||
use glutin::event_loop::EventLoop as GlutinEventLoop;
|
||||
use log::{error, info};
|
||||
use serde_json as json;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS};
|
||||
|
||||
use alacritty_terminal::clipboard::Clipboard;
|
||||
use alacritty_terminal::config::{Config, Monitor};
|
||||
use alacritty_terminal::display::Display;
|
||||
use alacritty_terminal::event::Event;
|
||||
use alacritty_terminal::event_loop::{self, EventLoop, Msg};
|
||||
#[cfg(target_os = "macos")]
|
||||
use alacritty_terminal::locale;
|
||||
use alacritty_terminal::message_bar::MessageBuffer;
|
||||
use alacritty_terminal::panic;
|
||||
use alacritty_terminal::sync::FairMutex;
|
||||
use alacritty_terminal::term::{cell::Cell, Term};
|
||||
use alacritty_terminal::term::Term;
|
||||
use alacritty_terminal::tty;
|
||||
use alacritty_terminal::{die, event};
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
mod display;
|
||||
mod event;
|
||||
mod input;
|
||||
mod logging;
|
||||
mod window;
|
||||
|
||||
use crate::cli::Options;
|
||||
use crate::config::monitor::Monitor;
|
||||
use crate::config::Config;
|
||||
use crate::display::Display;
|
||||
use crate::event::{EventProxy, Processor};
|
||||
|
||||
fn main() {
|
||||
panic::attach_handler();
|
||||
|
@ -71,12 +77,12 @@ fn main() {
|
|||
// Load command line options
|
||||
let options = Options::new();
|
||||
|
||||
// Setup storage for message UI
|
||||
let message_buffer = MessageBuffer::new();
|
||||
// Setup glutin event loop
|
||||
let window_event_loop = GlutinEventLoop::<Event>::with_user_event();
|
||||
|
||||
// Initialize the logger as soon as possible as to capture output from other subsystems
|
||||
let log_file =
|
||||
logging::initialize(&options, message_buffer.tx()).expect("Unable to initialize logger");
|
||||
let log_file = logging::initialize(&options, window_event_loop.create_proxy())
|
||||
.expect("Unable to initialize logger");
|
||||
|
||||
// Load configuration file
|
||||
// If the file is a command line argument, we won't write a generated default file
|
||||
|
@ -107,8 +113,9 @@ fn main() {
|
|||
let persistent_logging = config.persistent_logging();
|
||||
|
||||
// Run alacritty
|
||||
if let Err(err) = run(config, message_buffer) {
|
||||
die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", err);
|
||||
if let Err(err) = run(window_event_loop, config) {
|
||||
println!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Clean up logfile
|
||||
|
@ -123,7 +130,7 @@ fn main() {
|
|||
///
|
||||
/// Creates a window, the terminal state, pty, I/O event loop, input processor,
|
||||
/// config change monitor, and runs the main display loop.
|
||||
fn run(config: Config, message_buffer: MessageBuffer) -> Result<(), Box<dyn Error>> {
|
||||
fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), Box<dyn Error>> {
|
||||
info!("Welcome to Alacritty");
|
||||
if let Some(config_path) = &config.config_path {
|
||||
info!("Configuration loaded from {:?}", config_path.display());
|
||||
|
@ -132,17 +139,19 @@ fn run(config: Config, message_buffer: MessageBuffer) -> Result<(), Box<dyn Erro
|
|||
// Set environment variables
|
||||
tty::setup_env(&config);
|
||||
|
||||
// Create a display.
|
||||
//
|
||||
// The display manages a window and can draw the terminal
|
||||
let mut display = Display::new(&config)?;
|
||||
let event_proxy = EventProxy::new(window_event_loop.create_proxy());
|
||||
|
||||
info!("PTY Dimensions: {:?} x {:?}", display.size().lines(), display.size().cols());
|
||||
// Create a display
|
||||
//
|
||||
// The display manages a window and can draw the terminal.
|
||||
let display = Display::new(&config, &window_event_loop)?;
|
||||
|
||||
info!("PTY Dimensions: {:?} x {:?}", display.size_info.lines(), display.size_info.cols());
|
||||
|
||||
// Create new native clipboard
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||
let clipboard = Clipboard::new(display.get_wayland_display());
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
let clipboard = Clipboard::new(display.window.wayland_display());
|
||||
#[cfg(any(target_os = "macos", windows))]
|
||||
let clipboard = Clipboard::new();
|
||||
|
||||
// Create the terminal
|
||||
|
@ -150,28 +159,28 @@ fn run(config: Config, message_buffer: MessageBuffer) -> Result<(), Box<dyn Erro
|
|||
// This object contains all of the state about what's being displayed. It's
|
||||
// wrapped in a clonable mutex since both the I/O loop and display need to
|
||||
// access it.
|
||||
let terminal = Term::new(&config, display.size().to_owned(), message_buffer, clipboard);
|
||||
let terminal = Term::new(&config, &display.size_info, clipboard, event_proxy.clone());
|
||||
let terminal = Arc::new(FairMutex::new(terminal));
|
||||
|
||||
// Find the window ID for setting $WINDOWID
|
||||
let window_id = display.get_window_id();
|
||||
|
||||
// Create the pty
|
||||
//
|
||||
// The pty forks a process to run the shell on the slave side of the
|
||||
// pseudoterminal. A file descriptor for the master side is retained for
|
||||
// reading/writing to the shell.
|
||||
let pty = tty::new(&config, &display.size(), window_id);
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
let pty = tty::new(&config, &display.size_info, display.window.x11_window_id());
|
||||
#[cfg(any(target_os = "macos", windows))]
|
||||
let pty = tty::new(&config, &display.size_info, None);
|
||||
|
||||
// Get a reference to something that we can resize
|
||||
// Create PTY resize handle
|
||||
//
|
||||
// This exists because rust doesn't know the interface is thread-safe
|
||||
// and we need to be able to resize the PTY from the main thread while the IO
|
||||
// thread owns the EventedRW object.
|
||||
#[cfg(windows)]
|
||||
let mut resize_handle = pty.resize_handle();
|
||||
let resize_handle = pty.resize_handle();
|
||||
#[cfg(not(windows))]
|
||||
let mut resize_handle = pty.fd.as_raw_fd();
|
||||
let resize_handle = pty.fd.as_raw_fd();
|
||||
|
||||
// Create the pseudoterminal I/O loop
|
||||
//
|
||||
|
@ -180,86 +189,45 @@ fn run(config: Config, message_buffer: MessageBuffer) -> Result<(), Box<dyn Erro
|
|||
// synchronized since the I/O loop updates the state, and the display
|
||||
// consumes it periodically.
|
||||
let event_loop =
|
||||
EventLoop::new(Arc::clone(&terminal), display.notifier(), pty, config.debug.ref_test);
|
||||
EventLoop::new(Arc::clone(&terminal), event_proxy.clone(), pty, config.debug.ref_test);
|
||||
|
||||
// The event loop channel allows write requests from the event processor
|
||||
// to be sent to the loop and ultimately written to the pty.
|
||||
// to be sent to the pty loop and ultimately written to the pty.
|
||||
let loop_tx = event_loop.channel();
|
||||
|
||||
// Event processor
|
||||
//
|
||||
// Need the Rc<RefCell<_>> here since a ref is shared in the resize callback
|
||||
let mut processor = event::Processor::new(
|
||||
event_loop::Notifier(event_loop.channel()),
|
||||
display.resize_channel(),
|
||||
&config,
|
||||
display.size().to_owned(),
|
||||
);
|
||||
|
||||
// Create a config monitor when config was loaded from path
|
||||
//
|
||||
// The monitor watches the config file for changes and reloads it. Pending
|
||||
// config changes are processed in the main loop.
|
||||
let config_monitor = if config.live_config_reload() {
|
||||
config.config_path.as_ref().map(|path| Monitor::new(path, display.notifier()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if config.live_config_reload() {
|
||||
config.config_path.as_ref().map(|path| Monitor::new(path, event_proxy.clone()));
|
||||
}
|
||||
|
||||
// Setup storage for message UI
|
||||
let message_buffer = MessageBuffer::new();
|
||||
|
||||
// Event processor
|
||||
//
|
||||
// Need the Rc<RefCell<_>> here since a ref is shared in the resize callback
|
||||
let mut processor = Processor::new(
|
||||
event_loop::Notifier(loop_tx.clone()),
|
||||
Box::new(resize_handle),
|
||||
message_buffer,
|
||||
config,
|
||||
display,
|
||||
);
|
||||
|
||||
// Kick off the I/O thread
|
||||
let _io_thread = event_loop.spawn(None);
|
||||
let io_thread = event_loop.spawn();
|
||||
|
||||
info!("Initialisation complete");
|
||||
|
||||
// Main display loop
|
||||
loop {
|
||||
// Process input and window events
|
||||
let mut terminal_lock = processor.process_events(&terminal, display.window());
|
||||
// Start event loop and block until shutdown
|
||||
processor.run(terminal, window_event_loop);
|
||||
|
||||
// Handle config reloads
|
||||
if let Some(ref path) = config_monitor.as_ref().and_then(Monitor::pending) {
|
||||
// Clear old config messages from bar
|
||||
terminal_lock.message_buffer_mut().remove_topic(config::SOURCE_FILE_PATH);
|
||||
|
||||
if let Ok(config) = config::reload_from(path) {
|
||||
display.update_config(&config);
|
||||
processor.update_config(&config);
|
||||
terminal_lock.update_config(&config);
|
||||
}
|
||||
|
||||
terminal_lock.dirty = true;
|
||||
}
|
||||
|
||||
// Begin shutdown if the flag was raised
|
||||
if terminal_lock.should_exit() || tty::process_should_exit() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Maybe draw the terminal
|
||||
if terminal_lock.needs_draw() {
|
||||
// Try to update the position of the input method editor
|
||||
#[cfg(not(windows))]
|
||||
display.update_ime_position(&terminal_lock);
|
||||
|
||||
// Handle pending resize events
|
||||
//
|
||||
// The second argument is a list of types that want to be notified
|
||||
// of display size changes.
|
||||
display.handle_resize(&mut terminal_lock, &config, &mut resize_handle, &mut processor);
|
||||
|
||||
drop(terminal_lock);
|
||||
|
||||
// Draw the current state of the terminal
|
||||
display.draw(&terminal, &config);
|
||||
}
|
||||
}
|
||||
|
||||
// Write ref tests to disk
|
||||
if config.debug.ref_test {
|
||||
write_ref_test_results(&terminal.lock());
|
||||
}
|
||||
|
||||
loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to event loop");
|
||||
// Shutdown PTY parser event loop
|
||||
loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to pty event loop");
|
||||
io_thread.join().expect("join io thread");
|
||||
|
||||
// FIXME patch notify library to have a shutdown method
|
||||
// config_reloader.join().ok();
|
||||
|
@ -274,29 +242,3 @@ fn run(config: Config, message_buffer: MessageBuffer) -> Result<(), Box<dyn Erro
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Write the ref test results to the disk
|
||||
fn write_ref_test_results(terminal: &Term) {
|
||||
// dump grid state
|
||||
let mut grid = terminal.grid().clone();
|
||||
grid.initialize_all(&Cell::default());
|
||||
grid.truncate();
|
||||
|
||||
let serialized_grid = json::to_string(&grid).expect("serialize grid");
|
||||
|
||||
let serialized_size = json::to_string(terminal.size_info()).expect("serialize size");
|
||||
|
||||
let serialized_config = format!("{{\"history_size\":{}}}", grid.history_size());
|
||||
|
||||
File::create("./grid.json")
|
||||
.and_then(|mut f| f.write_all(serialized_grid.as_bytes()))
|
||||
.expect("write grid.json");
|
||||
|
||||
File::create("./size.json")
|
||||
.and_then(|mut f| f.write_all(serialized_size.as_bytes()))
|
||||
.expect("write size.json");
|
||||
|
||||
File::create("./config.json")
|
||||
.and_then(|mut f| f.write_all(serialized_config.as_bytes()))
|
||||
.expect("write config.json");
|
||||
}
|
||||
|
|
|
@ -14,42 +14,43 @@
|
|||
use std::convert::From;
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
use std::ffi::c_void;
|
||||
use std::fmt::Display;
|
||||
use std::fmt;
|
||||
|
||||
use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
||||
use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use glutin::event_loop::EventLoop;
|
||||
#[cfg(target_os = "macos")]
|
||||
use glutin::os::macos::WindowExt;
|
||||
use glutin::platform::macos::{RequestUserAttentionType, WindowBuilderExtMacOS, WindowExtMacOS};
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
use glutin::os::unix::{EventsLoopExt, WindowExt};
|
||||
use glutin::platform::unix::{EventLoopWindowTargetExtUnix, WindowBuilderExtUnix, WindowExtUnix};
|
||||
#[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,
|
||||
};
|
||||
use glutin::window::Icon;
|
||||
use glutin::window::{CursorIcon, Fullscreen, Window as GlutinWindow, WindowBuilder, WindowId};
|
||||
use glutin::{self, ContextBuilder, PossiblyCurrent, WindowedContext};
|
||||
#[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;
|
||||
use alacritty_terminal::config::{Decorations, StartupMode, WindowConfig, DEFAULT_NAME};
|
||||
use alacritty_terminal::event::Event;
|
||||
use alacritty_terminal::gl;
|
||||
use alacritty_terminal::term::{SizeInfo, Term};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
// 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 dealing with fonts
|
||||
Font(font::Error),
|
||||
|
||||
/// Error manipulating the rendering context
|
||||
Context(glutin::ContextError),
|
||||
}
|
||||
|
@ -57,43 +58,12 @@ pub enum Error {
|
|||
/// 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),
|
||||
Error::Font(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,15 +71,17 @@ impl ::std::error::Error for Error {
|
|||
match *self {
|
||||
Error::ContextCreation(ref _err) => "Error creating gl context",
|
||||
Error::Context(ref _err) => "Error operating on render context",
|
||||
Error::Font(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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),
|
||||
Error::Font(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,14 +98,20 @@ impl From<glutin::ContextError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<font::Error> for Error {
|
||||
fn from(val: font::Error) -> Error {
|
||||
Error::Font(val)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_gl_window(
|
||||
mut window: WindowBuilder,
|
||||
event_loop: &EventsLoop,
|
||||
event_loop: &EventLoop<Event>,
|
||||
srgb: bool,
|
||||
dimensions: Option<LogicalSize>,
|
||||
) -> Result<glutin::WindowedContext<PossiblyCurrent>> {
|
||||
) -> Result<WindowedContext<PossiblyCurrent>> {
|
||||
if let Some(dimensions) = dimensions {
|
||||
window = window.with_dimensions(dimensions);
|
||||
window = window.with_inner_size(dimensions);
|
||||
}
|
||||
|
||||
let windowed_context = ContextBuilder::new()
|
||||
|
@ -148,25 +126,33 @@ fn create_gl_window(
|
|||
Ok(windowed_context)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
windowed_context: WindowedContext<PossiblyCurrent>,
|
||||
current_mouse_cursor: CursorIcon,
|
||||
mouse_visible: bool,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
/// Create a new window
|
||||
///
|
||||
/// This creates a window and fully initializes a window.
|
||||
pub fn new(
|
||||
event_loop: EventsLoop,
|
||||
event_loop: &EventLoop<Event>,
|
||||
config: &Config,
|
||||
dimensions: Option<LogicalSize>,
|
||||
logical: 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();
|
||||
create_gl_window(window_builder.clone(), &event_loop, false, logical)
|
||||
.or_else(|_| create_gl_window(window_builder, &event_loop, true, logical))?;
|
||||
|
||||
// Text cursor
|
||||
window.set_cursor(MouseCursor::Text);
|
||||
windowed_context.window().set_cursor_icon(CursorIcon::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 _);
|
||||
|
@ -176,80 +162,33 @@ impl Window {
|
|||
{
|
||||
if event_loop.is_x11() {
|
||||
if let Some(parent_window_id) = config.window.embed {
|
||||
x_embed_window(window, parent_window_id);
|
||||
x_embed_window(windowed_context.window(), parent_window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let window = Window {
|
||||
event_loop,
|
||||
current_mouse_cursor: MouseCursor::Default,
|
||||
windowed_context,
|
||||
Ok(Window {
|
||||
current_mouse_cursor: CursorIcon::Default,
|
||||
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()
|
||||
windowed_context,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_inner_size(&mut self, size: LogicalSize) {
|
||||
self.window().set_inner_size(size);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> LogicalSize {
|
||||
self.window().inner_size()
|
||||
}
|
||||
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
self.window().get_hidpi_factor()
|
||||
self.window().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);
|
||||
pub fn set_visible(&self, visibility: bool) {
|
||||
self.window().set_visible(visibility);
|
||||
}
|
||||
|
||||
/// Set the window title
|
||||
|
@ -259,10 +198,10 @@ impl Window {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
|
||||
pub fn set_mouse_cursor(&mut self, cursor: CursorIcon) {
|
||||
if cursor != self.current_mouse_cursor {
|
||||
self.current_mouse_cursor = cursor;
|
||||
self.window().set_cursor(cursor);
|
||||
self.window().set_cursor_icon(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,27 +209,29 @@ impl Window {
|
|||
pub fn set_mouse_visible(&mut self, visible: bool) {
|
||||
if visible != self.mouse_visible {
|
||||
self.mouse_visible = visible;
|
||||
self.window().hide_cursor(!visible);
|
||||
self.window().set_cursor_visible(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 image = image::load_from_memory_with_format(WINDOW_ICON, ImageFormat::ICO)
|
||||
.expect("loading icon")
|
||||
.to_rgba();
|
||||
let (width, height) = image.dimensions();
|
||||
let icon = Icon::from_rgba(image.into_raw(), width, height);
|
||||
|
||||
let class = &window_config.class;
|
||||
|
||||
let mut builder = WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.with_visibility(false)
|
||||
.with_transparency(true)
|
||||
.with_visible(false)
|
||||
.with_transparent(true)
|
||||
.with_decorations(decorations)
|
||||
.with_maximized(window_config.startup_mode() == StartupMode::Maximized)
|
||||
.with_window_icon(icon.ok())
|
||||
|
@ -313,25 +254,27 @@ impl Window {
|
|||
_ => true,
|
||||
};
|
||||
|
||||
let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO);
|
||||
let image = image::load_from_memory_with_format(WINDOW_ICON, ImageFormat::ICO)
|
||||
.expect("loading icon")
|
||||
.to_rgba();
|
||||
let (width, height) = image.dimensions();
|
||||
let icon = Icon::from_rgba(image.into_raw(), width, height);
|
||||
|
||||
WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.with_visibility(cfg!(windows))
|
||||
.with_visible(true)
|
||||
.with_decorations(decorations)
|
||||
.with_transparency(true)
|
||||
.with_transparent(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_visible(false)
|
||||
.with_transparent(true)
|
||||
.with_maximized(window_config.startup_mode() == StartupMode::Maximized);
|
||||
|
||||
match window_config.decorations {
|
||||
|
@ -356,76 +299,103 @@ impl Window {
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_urgent(&self, is_urgent: bool) {
|
||||
self.window().request_user_attention(is_urgent);
|
||||
if !is_urgent {
|
||||
return;
|
||||
}
|
||||
|
||||
self.window().request_user_attention(RequestUserAttentionType::Critical);
|
||||
}
|
||||
|
||||
#[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_outer_position(&self, pos: LogicalPosition) {
|
||||
self.window().set_outer_position(pos);
|
||||
}
|
||||
|
||||
pub fn set_position(&self, pos: LogicalPosition) {
|
||||
self.window().set_position(pos);
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
pub fn x11_window_id(&self) -> Option<usize> {
|
||||
self.window().xlib_window().map(|xlib_window| xlib_window as usize)
|
||||
}
|
||||
|
||||
#[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 window_id(&self) -> WindowId {
|
||||
self.window().id()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
self.window().set_maximized(maximized);
|
||||
}
|
||||
|
||||
/// Toggle the window's fullscreen state
|
||||
pub fn toggle_fullscreen(&mut self) {
|
||||
self.set_fullscreen(self.window().fullscreen().is_none());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_simple_fullscreen(&self, fullscreen: bool) {
|
||||
self.window().set_simple_fullscreen(fullscreen);
|
||||
pub fn toggle_simple_fullscreen(&mut self) {
|
||||
self.set_simple_fullscreen(!self.window().simple_fullscreen());
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&mut self, fullscreen: bool) {
|
||||
#[cfg(macos)]
|
||||
{
|
||||
if self.window().simple_fullscreen() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if fullscreen {
|
||||
let current_monitor = self.window().current_monitor();
|
||||
self.window().set_fullscreen(Some(Fullscreen::Borderless(current_monitor)));
|
||||
} else {
|
||||
self.window().set_fullscreen(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_simple_fullscreen(&mut self, simple_fullscreen: bool) {
|
||||
if self.window().fullscreen().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.window().set_simple_fullscreen(simple_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()
|
||||
pub fn wayland_display(&self) -> Option<*mut c_void> {
|
||||
self.window().wayland_display()
|
||||
}
|
||||
|
||||
fn window(&self) -> &glutin::Window {
|
||||
/// Adjust the IME editor position according to the new location of the cursor
|
||||
#[cfg(not(windows))]
|
||||
pub fn update_ime_position<T>(&mut self, terminal: &Term<T>, size_info: &SizeInfo) {
|
||||
let point = terminal.cursor().point;
|
||||
let SizeInfo { cell_width: cw, cell_height: ch, padding_x: px, padding_y: py, dpr, .. } =
|
||||
size_info;
|
||||
|
||||
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_position(PhysicalPosition::from((nspot_x, nspot_y)).to_logical(*dpr));
|
||||
}
|
||||
|
||||
pub fn swap_buffers(&self) {
|
||||
self.windowed_context.swap_buffers().expect("swap buffers");
|
||||
}
|
||||
|
||||
pub fn resize(&self, size: PhysicalSize) {
|
||||
self.windowed_context.resize(size);
|
||||
}
|
||||
|
||||
fn window(&self) -> &GlutinWindow {
|
||||
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()) {
|
||||
let (xlib_display, xlib_window) = match (window.xlib_display(), window.xlib_window()) {
|
||||
(Some(display), Some(window)) => (display, window),
|
||||
_ => return,
|
||||
};
|
||||
|
@ -459,15 +429,6 @@ fn x_embed_window(window: &GlutinWindow, parent_id: u64) {
|
|||
|
||||
#[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();
|
||||
}
|
||||
println!("Could not embed into specified window.");
|
||||
std::process::exit(1);
|
||||
}
|
|
@ -15,8 +15,7 @@ notify = "4"
|
|||
bitflags = "1"
|
||||
font = { path = "../font" }
|
||||
parking_lot = "0.9"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_yaml = "0.8"
|
||||
vte = "0.3"
|
||||
mio = "0.6"
|
||||
|
@ -24,12 +23,10 @@ mio-extras = "2"
|
|||
log = "0.4"
|
||||
fnv = "1"
|
||||
unicode-width = "0.1"
|
||||
glutin = { version = "0.21.0", features = ["icon_loading"] }
|
||||
base64 = "0.10.0"
|
||||
static_assertions = "0.3.0"
|
||||
terminfo = "0.6.1"
|
||||
url = "2"
|
||||
crossbeam-channel = "0.3.8"
|
||||
copypasta = { path = "../copypasta" }
|
||||
rfind_url = "0.4.0"
|
||||
|
||||
|
@ -37,9 +34,6 @@ rfind_url = "0.4.0"
|
|||
nix = "0.14.1"
|
||||
signal-hook = { version = "0.1", features = ["mio-support"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "macos", windows)))'.dependencies]
|
||||
x11-dl = "2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winpty = { path = "../winpty" }
|
||||
mio-named-pipes = "0.1"
|
||||
|
@ -49,9 +43,6 @@ winapi = { version = "0.3.7", features = ["impl-default", "winuser", "synchapi",
|
|||
widestring = "0.4"
|
||||
mio-anonymous-pipes = "0.1"
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
image = "0.21.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.2"
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
use std::cmp;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use font::{Metrics, RasterizedGlyph};
|
||||
|
||||
use crate::ansi::CursorStyle;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -6,20 +6,22 @@ use std::io::{self, ErrorKind, Read, Write};
|
|||
use std::marker::Send;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::error;
|
||||
#[cfg(not(windows))]
|
||||
use mio::unix::UnixReady;
|
||||
use mio::{self, Events, PollOpt, Ready};
|
||||
use mio_extras::channel::{self, Receiver, Sender};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use mio::unix::UnixReady;
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
pub fn message(&self) -> Option<&Message> {
|
||||
self.messages.front()
|
||||
}
|
||||
|
||||
/// Channel for adding new messages
|
||||
#[inline]
|
||||
pub fn tx(&self) -> Sender<Message> {
|
||||
self.tx.clone()
|
||||
}
|
||||
|
||||
/// Remove the currently visible message
|
||||
/// Remove the currently visible message.
|
||||
#[inline]
|
||||
pub fn pop(&mut self) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Remove the message itself
|
||||
self.current = self.messages.try_recv().ok();
|
||||
}
|
||||
let msg = self.messages.pop_front();
|
||||
|
||||
/// Remove all messages with a specific topic
|
||||
#[inline]
|
||||
pub fn remove_topic(&mut self, topic: &str) {
|
||||
// Filter messages currently pending
|
||||
for msg in self
|
||||
.messages
|
||||
.try_iter()
|
||||
.take(self.messages.len())
|
||||
.filter(|m| m.topic().map(String::as_str) != Some(topic))
|
||||
{
|
||||
let _ = self.tx.send(msg);
|
||||
// Remove all duplicates
|
||||
if let Some(msg) = msg {
|
||||
self.messages = self.messages.drain(..).filter(|m| m != &msg).collect();
|
||||
}
|
||||
|
||||
// Remove the currently active message
|
||||
self.current = self.messages.try_recv().ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MessageBuffer {
|
||||
fn default() -> MessageBuffer {
|
||||
MessageBuffer::new()
|
||||
/// Remove all messages with a specific target.
|
||||
#[inline]
|
||||
pub fn remove_target(&mut self, target: &str) {
|
||||
self.messages = self
|
||||
.messages
|
||||
.drain(..)
|
||||
.filter(|m| m.target().map(String::as_str) != Some(target))
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -1210,11 +1140,6 @@ impl Term {
|
|||
self.tabs = TabStops::new(self.grid.num_cols(), self.tabspaces);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
use serde::Deserialize;
|
||||
use serde_json as json;
|
||||
|
||||
use std::fs::File;
|
||||
|
@ -8,9 +7,9 @@ use std::path::Path;
|
|||
|
||||
use alacritty_terminal::ansi;
|
||||
use alacritty_terminal::clipboard::Clipboard;
|
||||
use alacritty_terminal::config::Config;
|
||||
use alacritty_terminal::config::MockConfig;
|
||||
use alacritty_terminal::event::{Event, EventListener};
|
||||
use alacritty_terminal::index::Column;
|
||||
use alacritty_terminal::message_bar::MessageBuffer;
|
||||
use alacritty_terminal::term::cell::Cell;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
use alacritty_terminal::Grid;
|
||||
|
@ -81,6 +80,11 @@ struct RefConfig {
|
|||
history_size: u32,
|
||||
}
|
||||
|
||||
struct Mock;
|
||||
impl EventListener for Mock {
|
||||
fn send_event(&self, _event: Event) {}
|
||||
}
|
||||
|
||||
fn ref_test(dir: &Path) {
|
||||
let recording = read_u8(dir.join("alacritty.recording"));
|
||||
let serialized_size = read_string(dir.join("size.json")).unwrap();
|
||||
|
@ -91,10 +95,10 @@ fn ref_test(dir: &Path) {
|
|||
let grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap();
|
||||
let ref_config: RefConfig = json::from_str(&serialized_cfg).unwrap_or_default();
|
||||
|
||||
let mut config: Config = Default::default();
|
||||
let mut config = MockConfig::default();
|
||||
config.scrolling.set_history(ref_config.history_size);
|
||||
|
||||
let mut terminal = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop());
|
||||
let mut terminal = Term::new(&config, &size, Clipboard::new_nop(), Mock);
|
||||
let mut parser = ansi::Processor::new();
|
||||
|
||||
for byte in recording {
|
||||
|
|
|
@ -591,12 +591,11 @@ mod tests {
|
|||
let index = ((glyph.width * 3 * row) + (col * 3)) as usize;
|
||||
let value = glyph.buf[index];
|
||||
let c = match value {
|
||||
0...50 => ' ',
|
||||
51...100 => '.',
|
||||
101...150 => '~',
|
||||
151...200 => '*',
|
||||
201...255 => '#',
|
||||
_ => unreachable!(),
|
||||
0..=50 => ' ',
|
||||
51..=100 => '.',
|
||||
101..=150 => '~',
|
||||
151..=200 => '*',
|
||||
201..=255 => '#',
|
||||
};
|
||||
print!("{}", c);
|
||||
}
|
||||
|
|
|
@ -546,7 +546,7 @@ pub enum Error {
|
|||
}
|
||||
|
||||
impl ::std::error::Error for Error {
|
||||
fn cause(&self) -> Option<&dyn (::std::error::Error)> {
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
match *self {
|
||||
Error::FreeType(ref err) => Some(err),
|
||||
_ => None,
|
||||
|
|
|
@ -47,6 +47,7 @@ extern crate log;
|
|||
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, Mul};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
// If target isn't macos or windows, reexport everything from ft
|
||||
|
@ -173,28 +174,42 @@ impl PartialEq for GlyphKey {
|
|||
pub struct Size(i16);
|
||||
|
||||
impl Size {
|
||||
/// Create a new `Size` from a f32 size in points
|
||||
pub fn new(size: f32) -> Size {
|
||||
Size((size * Size::factor()) as i16)
|
||||
}
|
||||
|
||||
/// Scale factor between font "Size" type and point size
|
||||
#[inline]
|
||||
pub fn factor() -> f32 {
|
||||
2.0
|
||||
}
|
||||
|
||||
/// Create a new `Size` from a f32 size in points
|
||||
pub fn new(size: f32) -> Size {
|
||||
Size((size * Size::factor()) as i16)
|
||||
}
|
||||
|
||||
/// Get the f32 size in points
|
||||
pub fn as_f32_pts(self) -> f32 {
|
||||
f32::from(self.0) / Size::factor()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::Add for Size {
|
||||
impl<T: Into<Size>> Add<T> for Size {
|
||||
type Output = Size;
|
||||
|
||||
fn add(self, other: Size) -> Size {
|
||||
Size(self.0.saturating_add(other.0))
|
||||
fn add(self, other: T) -> Size {
|
||||
Size(self.0.saturating_add(other.into().0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Size>> Mul<T> for Size {
|
||||
type Output = Size;
|
||||
|
||||
fn mul(self, other: T) -> Size {
|
||||
Size(self.0 * other.into().0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Size {
|
||||
fn from(float: f32) -> Size {
|
||||
Size::new(float)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue