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:
Christian Duerr 2019-10-05 02:29:26 +02:00 committed by GitHub
parent b0c6fdff76
commit 729eef0c93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 2757 additions and 3033 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"]}

View File

@ -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() {

View File

@ -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));
}
}

View File

@ -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"
);
}

View File

@ -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,
}
}
}

View File

@ -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())
},
}

View File

@ -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]

View 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
View 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
View 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

View File

@ -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));
}
}
}

View File

@ -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");
}

View File

@ -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);
}

View File

@ -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"

View File

@ -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);
}

View File

@ -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;

View File

@ -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 {

View File

@ -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()
},
})

View File

@ -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)
},
}

View File

@ -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()
}

View File

@ -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())
},
}

View File

@ -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")]

View File

@ -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)]

View File

@ -16,6 +16,8 @@
use std::cmp;
use serde::Deserialize;
use font::{Metrics, RasterizedGlyph};
use crate::ansi::CursorStyle;

View File

@ -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()
}
}

View File

@ -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);
}

View File

@ -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;
}
},

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)
}
}

View File

@ -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;

View File

@ -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);
}}
}

View File

@ -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();

View File

@ -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};

View File

@ -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(&regular_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();

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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.

View File

@ -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();

View File

@ -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;
}

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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 {

View File

@ -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);
}

View File

@ -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,

View File

@ -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)
}
}