diff options
author | Kirill Chibisov <contact@kchibisov.com> | 2020-07-11 20:03:09 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-11 20:03:09 +0300 |
commit | 18cf806a27f06185b1ceb2d63f3b9bc2dd3dc80e (patch) | |
tree | 6609ca3aec4fe8da171de474a4a8e8d9b572f0e5 /alacritty | |
parent | 5f039cee49b9c817177c6feecc5e7d97fb0a57e1 (diff) | |
download | r-alacritty-18cf806a27f06185b1ceb2d63f3b9bc2dd3dc80e.tar.gz r-alacritty-18cf806a27f06185b1ceb2d63f3b9bc2dd3dc80e.tar.bz2 r-alacritty-18cf806a27f06185b1ceb2d63f3b9bc2dd3dc80e.zip |
Remove gui dependencies from alacritty_terminal
This commit removes font dependency from alacritty_terminal,
so it'll simplify the usage of alacritty_terminal as a library,
since you won't link to system's libraries anymore. It also
moves many alacritty related config options from it.
Fixes #3393.
Diffstat (limited to 'alacritty')
-rw-r--r-- | alacritty/Cargo.toml | 3 | ||||
-rw-r--r-- | alacritty/src/cli.rs | 51 | ||||
-rw-r--r-- | alacritty/src/config/debug.rs | 64 | ||||
-rw-r--r-- | alacritty/src/config/font.rs | 218 | ||||
-rw-r--r-- | alacritty/src/config/mod.rs | 33 | ||||
-rw-r--r-- | alacritty/src/config/ui_config.rs | 97 | ||||
-rw-r--r-- | alacritty/src/config/window.rs | 220 | ||||
-rw-r--r-- | alacritty/src/display.rs | 73 | ||||
-rw-r--r-- | alacritty/src/event.rs | 46 | ||||
-rw-r--r-- | alacritty/src/input.rs | 6 | ||||
-rw-r--r-- | alacritty/src/locale.rs | 100 | ||||
-rw-r--r-- | alacritty/src/logging.rs | 2 | ||||
-rw-r--r-- | alacritty/src/main.rs | 27 | ||||
-rw-r--r-- | alacritty/src/message_bar.rs | 433 | ||||
-rw-r--r-- | alacritty/src/meter.rs | 97 | ||||
-rw-r--r-- | alacritty/src/panic.rs | 24 | ||||
-rw-r--r-- | alacritty/src/renderer/mod.rs | 94 | ||||
-rw-r--r-- | alacritty/src/window.rs | 13 |
18 files changed, 1446 insertions, 155 deletions
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index b5dc924f..c30bc370 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -36,6 +36,9 @@ xdg = "2" [target.'cfg(not(target_os = "macos"))'.dependencies] image = { version = "0.23.3", default-features = false, features = ["ico"] } +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2.2" + [target.'cfg(any(target_os = "macos", windows))'.dependencies] dirs = "2.0.2" diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index 89db20e1..1f677488 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -4,9 +4,11 @@ use std::path::PathBuf; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; use log::{self, error, LevelFilter}; -use alacritty_terminal::config::{Delta, Dimensions, Program, DEFAULT_NAME}; +use alacritty_terminal::config::Program; use alacritty_terminal::index::{Column, Line}; +use crate::config::ui_config::Delta; +use crate::config::window::{Dimensions, DEFAULT_NAME}; use crate::config::Config; #[cfg(not(any(target_os = "macos", windows)))] @@ -263,35 +265,42 @@ impl Options { } if let Some(lcr) = self.live_config_reload { - config.set_live_config_reload(lcr); + config.ui_config.set_live_config_reload(lcr); } config.shell = self.command.or(config.shell); config.hold = self.hold; - config.set_dynamic_title(config.dynamic_title() && self.title.is_none()); - config.window.dimensions = self.dimensions.unwrap_or(config.window.dimensions); - config.window.title = self.title.unwrap_or(config.window.title); - config.window.position = self.position.or(config.window.position); - config.window.embed = self.embed.and_then(|embed| embed.parse().ok()); + let dynamic_title = config.ui_config.dynamic_title() && self.title.is_none(); + config.ui_config.set_dynamic_title(dynamic_title); - config.window.class.instance = self.class_instance.unwrap_or(config.window.class.instance); - config.window.class.general = self.class_general.unwrap_or(config.window.class.general); + replace_if_some(&mut config.ui_config.window.dimensions, self.dimensions); + replace_if_some(&mut config.ui_config.window.title, self.title); + config.ui_config.window.position = self.position.or(config.ui_config.window.position); + config.ui_config.window.embed = self.embed.and_then(|embed| embed.parse().ok()); + replace_if_some(&mut config.ui_config.window.class.instance, self.class_instance); + replace_if_some(&mut config.ui_config.window.class.general, self.class_general); - config.debug.print_events = self.print_events || config.debug.print_events; - config.debug.log_level = max(config.debug.log_level, self.log_level); - config.debug.ref_test = self.ref_test || config.debug.ref_test; - config.debug.persistent_logging = - self.persistent_logging || config.debug.persistent_logging; + config.ui_config.debug.print_events |= self.print_events; + config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level); + config.ui_config.debug.ref_test |= self.ref_test; + config.ui_config.debug.persistent_logging |= self.persistent_logging; - if config.debug.print_events { - config.debug.log_level = max(config.debug.log_level, LevelFilter::Info); + if config.ui_config.debug.print_events { + config.ui_config.debug.log_level = + max(config.ui_config.debug.log_level, LevelFilter::Info); } config } } +fn replace_if_some<T>(option: &mut T, value: Option<T>) { + if let Some(value) = value { + *option = value; + } +} + #[cfg(test)] mod tests { use crate::cli::Options; @@ -300,11 +309,11 @@ mod tests { #[test] fn dynamic_title_ignoring_options_by_default() { let config = Config::default(); - let old_dynamic_title = config.dynamic_title(); + let old_dynamic_title = config.ui_config.dynamic_title(); let config = Options::default().into_config(config); - assert_eq!(old_dynamic_title, config.dynamic_title()); + assert_eq!(old_dynamic_title, config.ui_config.dynamic_title()); } #[test] @@ -315,16 +324,16 @@ mod tests { options.title = Some("foo".to_owned()); let config = options.into_config(config); - assert!(!config.dynamic_title()); + assert!(!config.ui_config.dynamic_title()); } #[test] fn dynamic_title_not_overridden_by_config() { let mut config = Config::default(); - config.window.title = "foo".to_owned(); + config.ui_config.window.title = "foo".to_owned(); let config = Options::default().into_config(config); - assert!(config.dynamic_title()); + assert!(config.ui_config.dynamic_title()); } } diff --git a/alacritty/src/config/debug.rs b/alacritty/src/config/debug.rs new file mode 100644 index 00000000..62de0500 --- /dev/null +++ b/alacritty/src/config/debug.rs @@ -0,0 +1,64 @@ +use log::{error, LevelFilter}; +use serde::{Deserialize, Deserializer}; + +use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG}; + +/// Debugging options. +#[serde(default)] +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Debug { + #[serde(default = "default_log_level", deserialize_with = "deserialize_log_level")] + pub log_level: LevelFilter, + + #[serde(deserialize_with = "failure_default")] + pub print_events: bool, + + /// Keep the log file after quitting. + #[serde(deserialize_with = "failure_default")] + pub persistent_logging: bool, + + /// Should show render timer. + #[serde(deserialize_with = "failure_default")] + pub render_timer: bool, + + /// Record ref test. + #[serde(skip)] + pub ref_test: bool, +} + +impl Default for Debug { + fn default() -> Self { + Self { + log_level: default_log_level(), + print_events: Default::default(), + persistent_logging: Default::default(), + render_timer: Default::default(), + ref_test: Default::default(), + } + } +} + +fn default_log_level() -> LevelFilter { + LevelFilter::Warn +} + +fn deserialize_log_level<'a, D>(deserializer: D) -> Result<LevelFilter, D::Error> +where + D: Deserializer<'a>, +{ + Ok(match failure_default::<D, String>(deserializer)?.to_lowercase().as_str() { + "off" | "none" => LevelFilter::Off, + "error" => LevelFilter::Error, + "warn" => LevelFilter::Warn, + "info" => LevelFilter::Info, + "debug" => LevelFilter::Debug, + "trace" => LevelFilter::Trace, + level => { + error!( + target: LOG_TARGET_CONFIG, + "Problem with config: invalid log level {}; using level Warn", level + ); + default_log_level() + }, + }) +} diff --git a/alacritty/src/config/font.rs b/alacritty/src/config/font.rs new file mode 100644 index 00000000..f718587c --- /dev/null +++ b/alacritty/src/config/font.rs @@ -0,0 +1,218 @@ +use std::fmt; + +use font::Size; +use log::error; +use serde::de::Visitor; +use serde::{Deserialize, Deserializer}; + +use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG}; + +#[cfg(target_os = "macos")] +use crate::config::ui_config::DefaultTrueBool; +use crate::config::ui_config::Delta; + +/// Font config. +/// +/// Defaults are provided at the level of this struct per platform, but not per +/// field in this struct. It might be nice in the future to have defaults for +/// each value independently. Alternatively, maybe erroring when the user +/// doesn't provide complete config is Ok. +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct Font { + /// Normal font face. + #[serde(deserialize_with = "failure_default")] + normal: FontDescription, + + /// Bold font face. + #[serde(deserialize_with = "failure_default")] + bold: SecondaryFontDescription, + + /// Italic font face. + #[serde(deserialize_with = "failure_default")] + italic: SecondaryFontDescription, + + /// Bold italic font face. + #[serde(deserialize_with = "failure_default")] + bold_italic: SecondaryFontDescription, + + /// Font size in points. + #[serde(deserialize_with = "DeserializeSize::deserialize")] + pub size: Size, + + /// Extra spacing per character. + #[serde(deserialize_with = "failure_default")] + pub offset: Delta<i8>, + + /// Glyph offset within character cell. + #[serde(deserialize_with = "failure_default")] + pub glyph_offset: Delta<i8>, + + #[cfg(target_os = "macos")] + #[serde(deserialize_with = "failure_default")] + use_thin_strokes: DefaultTrueBool, +} + +impl Default for Font { + fn default() -> Font { + Font { + size: default_font_size(), + normal: Default::default(), + bold: Default::default(), + italic: Default::default(), + bold_italic: Default::default(), + glyph_offset: Default::default(), + offset: Default::default(), + #[cfg(target_os = "macos")] + use_thin_strokes: Default::default(), + } + } +} + +impl Font { + /// Get a font clone with a size modification. + pub fn with_size(self, size: Size) -> Font { + Font { size, ..self } + } + + /// Get normal font description. + pub fn normal(&self) -> &FontDescription { + &self.normal + } + + /// Get bold font description. + pub fn bold(&self) -> FontDescription { + self.bold.desc(&self.normal) + } + + /// Get italic font description. + pub fn italic(&self) -> FontDescription { + self.italic.desc(&self.normal) + } + + /// Get bold italic font description. + pub fn bold_italic(&self) -> FontDescription { + self.bold_italic.desc(&self.normal) + } + + #[cfg(target_os = "macos")] + pub fn use_thin_strokes(&self) -> bool { + self.use_thin_strokes.0 + } + + #[cfg(not(target_os = "macos"))] + pub fn use_thin_strokes(&self) -> bool { + false + } +} + +fn default_font_size() -> Size { + Size::new(11.) +} + +/// Description of the normal font. +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct FontDescription { + #[serde(deserialize_with = "failure_default")] + pub family: String, + #[serde(deserialize_with = "failure_default")] + pub style: Option<String>, +} + +impl Default for FontDescription { + fn default() -> FontDescription { + FontDescription { + #[cfg(not(any(target_os = "macos", windows)))] + family: "monospace".into(), + #[cfg(target_os = "macos")] + family: "Menlo".into(), + #[cfg(windows)] + family: "Consolas".into(), + style: None, + } + } +} + +/// Description of the italic and bold font. +#[serde(default)] +#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] +pub struct SecondaryFontDescription { + #[serde(deserialize_with = "failure_default")] + family: Option<String>, + #[serde(deserialize_with = "failure_default")] + style: Option<String>, +} + +impl SecondaryFontDescription { + pub fn desc(&self, fallback: &FontDescription) -> FontDescription { + FontDescription { + family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), + style: self.style.clone(), + } + } +} + +trait DeserializeSize: Sized { + fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error> + where + D: serde::de::Deserializer<'a>; +} + +impl DeserializeSize for Size { + fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: serde::de::Deserializer<'a>, + { + use std::marker::PhantomData; + + struct NumVisitor<__D> { + _marker: PhantomData<__D>, + } + + impl<'a, __D> Visitor<'a> for NumVisitor<__D> + where + __D: serde::de::Deserializer<'a>, + { + type Value = f64; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("f64 or u64") + } + + fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(value) + } + + fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(value as f64) + } + } + + let value = serde_yaml::Value::deserialize(deserializer)?; + let size = value + .deserialize_any(NumVisitor::<D> { _marker: PhantomData }) + .map(|v| Size::new(v as _)); + + // Use default font size as fallback. + match size { + Ok(size) => Ok(size), + Err(err) => { + let size = default_font_size(); + error!( + target: LOG_TARGET_CONFIG, + "Problem with config: {}; using size {}", + err, + size.as_f32_pts() + ); + Ok(size) + }, + } + } +} diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index e6437d26..7fffcc39 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -11,9 +11,12 @@ use log::{error, warn}; use alacritty_terminal::config::{Config as TermConfig, LOG_TARGET_CONFIG}; mod bindings; +pub mod debug; +pub mod font; pub mod monitor; mod mouse; -mod ui_config; +pub mod ui_config; +pub mod window; pub use crate::config::bindings::{Action, Binding, Key, ViAction}; #[cfg(test)] @@ -172,27 +175,6 @@ fn parse_config(contents: &str) -> 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!( - 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" - ); - } - if config.scrolling.faux_multiplier().is_some() { warn!( target: LOG_TARGET_CONFIG, @@ -224,6 +206,13 @@ fn print_deprecation_warnings(config: &Config) { "Config visual_bell has been deprecated; please use bell instead" ) } + + if config.ui_config.dynamic_title.is_some() { + warn!( + target: LOG_TARGET_CONFIG, + "Config dynamic_title is deprecated; please use window.dynamic_title instead", + ) + } } #[cfg(test)] diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 49e54e05..a8b1749f 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -1,13 +1,24 @@ use log::error; use serde::{Deserialize, Deserializer}; -use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG}; +use alacritty_terminal::config::{failure_default, Percentage, LOG_TARGET_CONFIG}; use crate::config::bindings::{self, Binding, KeyBinding, MouseBinding}; +use crate::config::debug::Debug; +use crate::config::font::Font; use crate::config::mouse::Mouse; +use crate::config::window::WindowConfig; #[derive(Debug, PartialEq, Deserialize)] pub struct UIConfig { + /// Font configuration. + #[serde(default, deserialize_with = "failure_default")] + pub font: Font, + + /// Window configuration. + #[serde(default, deserialize_with = "failure_default")] + pub window: WindowConfig, + #[serde(default, deserialize_with = "failure_default")] pub mouse: Mouse, @@ -18,18 +29,79 @@ pub struct UIConfig { /// Bindings for the mouse. #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] pub mouse_bindings: Vec<MouseBinding>, + + /// Debug options. + #[serde(default, deserialize_with = "failure_default")] + pub debug: Debug, + + /// Send escape sequences using the alt key. + #[serde(default, deserialize_with = "failure_default")] + alt_send_esc: DefaultTrueBool, + + /// Live config reload. + #[serde(default, deserialize_with = "failure_default")] + live_config_reload: DefaultTrueBool, + + /// Background opacity from 0.0 to 1.0. + #[serde(default, deserialize_with = "failure_default")] + background_opacity: Percentage, + + // TODO: DEPRECATED + #[serde(default, deserialize_with = "failure_default")] + pub dynamic_title: Option<bool>, } impl Default for UIConfig { fn default() -> Self { UIConfig { - mouse: Mouse::default(), + font: Default::default(), + window: Default::default(), + mouse: Default::default(), key_bindings: default_key_bindings(), mouse_bindings: default_mouse_bindings(), + debug: Default::default(), + alt_send_esc: Default::default(), + background_opacity: Default::default(), + live_config_reload: Default::default(), + dynamic_title: Default::default(), } } } +impl UIConfig { + #[inline] + pub fn background_opacity(&self) -> f32 { + self.background_opacity.as_f32() + } + + #[inline] + pub fn dynamic_title(&self) -> bool { + self.dynamic_title.unwrap_or_else(|| self.window.dynamic_title()) + } + + #[inline] + pub fn set_dynamic_title(&mut self, dynamic_title: bool) { + self.window.set_dynamic_title(dynamic_title); + } + + /// Live config reload. + #[inline] + pub fn live_config_reload(&self) -> bool { + self.live_config_reload.0 + } + + #[inline] + pub fn set_live_config_reload(&mut self, live_config_reload: bool) { + self.live_config_reload.0 = live_config_reload; + } + + /// Send escape sequences using the alt key. + #[inline] + pub fn alt_send_esc(&self) -> bool { + self.alt_send_esc.0 + } +} + fn default_key_bindings() -> Vec<KeyBinding> { bindings::default_key_bindings() } @@ -83,3 +155,24 @@ where Ok(bindings) } + +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] +pub struct DefaultTrueBool(pub bool); + +impl Default for DefaultTrueBool { + fn default() -> Self { + DefaultTrueBool(true) + } +} + +/// A delta for a point in a 2 dimensional plane. +#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] +pub struct Delta<T: Default + PartialEq + Eq> { + /// Horizontal change. + #[serde(deserialize_with = "failure_default")] + pub x: T, + /// Vertical change. + #[serde(deserialize_with = "failure_default")] + pub y: T, +} diff --git a/alacritty/src/config/window.rs b/alacritty/src/config/window.rs new file mode 100644 index 00000000..f866e180 --- /dev/null +++ b/alacritty/src/config/window.rs @@ -0,0 +1,220 @@ +use std::os::raw::c_ulong; + +use log::error; +use serde::{Deserialize, Deserializer}; +use serde_yaml::Value; + +use alacritty_terminal::config::{failure_default, option_explicit_none, LOG_TARGET_CONFIG}; +use alacritty_terminal::index::{Column, Line}; + +use crate::config::ui_config::{DefaultTrueBool, Delta}; + +/// Default Alacritty name, used for window title and class. +pub const DEFAULT_NAME: &str = "Alacritty"; + +#[serde(default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct WindowConfig { + /// Initial dimensions. + #[serde(deserialize_with = "failure_default")] + pub dimensions: Dimensions, + + /// Initial position. + #[serde(deserialize_with = "failure_default")] + pub position: Option<Delta<i32>>, + + /// Pixel padding. + #[serde(deserialize_with = "failure_default")] + pub padding: Delta<u8>, + + /// Draw the window with title bar / borders. + #[serde(deserialize_with = "failure_default")] + pub decorations: Decorations, + + /// Spread out additional padding evenly. + #[serde(deserialize_with = "failure_default")] + pub dynamic_padding: bool, + + /// Startup mode. + #[serde(deserialize_with = "failure_default")] + pub startup_mode: StartupMode, + + /// Window title. + #[serde(default = "default_title")] + pub title: String, + + /// Window class. + #[serde(deserialize_with = "deserialize_class")] + pub class: Class, + + /// XEmbed parent. + #[serde(skip)] + pub embed: Option<c_ulong>, + + /// GTK theme variant. + #[serde(deserialize_with = "option_explicit_none")] + pub gtk_theme_variant: Option<String>, + + /// Use dynamic title. + #[serde(default, deserialize_with = "failure_default")] + dynamic_title: DefaultTrueBool, +} + +pub fn default_title() -> String { + DEFAULT_NAME.to_string() +} + +impl WindowConfig { + #[inline] + pub fn dynamic_title(&self) -> bool { + self.dynamic_title.0 + } + + #[inline] + pub fn set_dynamic_title(&mut self, dynamic_title: bool) { + self.dynamic_title.0 = dynamic_title; + } +} + +impl Default for WindowConfig { + fn default() -> WindowConfig { + WindowConfig { + dimensions: Default::default(), + position: Default::default(), + padding: Default::default(), + decorations: Default::default(), + dynamic_padding: Default::default(), + startup_mode: Default::default(), + class: Default::default(), + embed: Default::default(), + gtk_theme_variant: Default::default(), + title: default_title(), + dynamic_title: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] +pub enum StartupMode { + Windowed, + Maximized, + Fullscreen, + #[cfg(target_os = "macos")] + SimpleFullscreen, +} + +impl Default for StartupMode { + fn default() -> StartupMode { + StartupMode::Windowed + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +pub enum Decorations { + #[serde(rename = "full")] + Full, + #[cfg(target_os = "macos")] + #[serde(rename = "transparent")] + Transparent, + #[cfg(target_os = "macos")] + #[serde(rename = "buttonless")] + Buttonless, + #[serde(rename = "none")] + None, +} + +impl Default for Decorations { + fn default() -> Decorations { + Decorations::Full + } +} + +/// Window Dimensions. +/// +/// Newtype to avoid passing values incorrectly. +#[serde(default)] +#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] +pub struct Dimensions { + /// Window width in character columns. + #[serde(deserialize_with = "failure_default")] + columns: Column, + + /// Window Height in character lines. + #[serde(deserialize_with = "failure_default")] + lines: Line, +} + +impl Dimensions { + pub fn new(columns: Column, lines: Line) -> Self { + Dimensions { columns, lines } + } + + /// Get lines. + #[inline] + pub fn lines_u32(&self) -> u32 { + self.lines.0 as u32 + } + + /// Get columns. + #[inline] + pub fn columns_u32(&self) -> u32 { + self.columns.0 as u32 + } +} + +/// Window class hint. +#[serde(default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Class { + #[serde(deserialize_with = "deserialize_class_resource")] + pub instance: String, + + #[serde(deserialize_with = "deserialize_class_resource")] + pub general: String, +} + +impl Default for Class { + fn default() -> Self { + Class { instance: DEFAULT_NAME.into(), general: DEFAULT_NAME.into() } + } +} + +fn deserialize_class_resource<'a, D>(deserializer: D) -> Result<String, D::Error> +where + D: Deserializer<'a>, +{ + let value = Value::deserialize(deserializer)?; + match String::deserialize(value) { + Ok(value) => Ok(value), + Err(err) => { + error!( + target: LOG_TARGET_CONFIG, + "Problem with config: {}, using default value {}", err, DEFAULT_NAME, + ); + + Ok(DEFAULT_NAME.into()) + }, + } +} + +fn deserialize_class<'a, D>(deserializer: D) -> Result<Class, D::Error> +where + D: Deserializer<'a>, +{ + let value = Value::deserialize(deserializer)?; + + if let Value::String(instance) = value { + return Ok(Class { instance, general: DEFAULT_NAME.into() }); + } + + match Class::deserialize(value) { + Ok(value) => Ok(value), + Err(err) => { + error!( + target: LOG_TARGET_CONFIG, + "Problem with config: {}; using class {}", err, DEFAULT_NAME + ); + Ok(Class::default()) + }, + } +} diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 0a3bea34..ab6ed7e6 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -22,19 +22,23 @@ use wayland_client::{Display as WaylandDisplay, EventQueue}; #[cfg(target_os = "macos")] use font::set_font_smoothing; -use font::{self, Rasterize}; +use font::{self, Rasterize, Rasterizer}; -use alacritty_terminal::config::{Font, StartupMode}; use alacritty_terminal::event::{EventListener, OnResize}; +#[cfg(not(windows))] use alacritty_terminal::grid::Dimensions; -use alacritty_terminal::index::{Column, Line, Point}; -use alacritty_terminal::message_bar::MessageBuffer; -use alacritty_terminal::meter::Meter; +use alacritty_terminal::index::Line; +#[cfg(not(windows))] +use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::selection::Selection; use alacritty_terminal::term::{RenderableCell, SizeInfo, Term, TermMode}; +use crate::config::font::Font; +use crate::config::window::StartupMode; use crate::config::Config; use crate::event::Mouse; +use crate::message_bar::MessageBuffer; +use crate::meter::Meter; use crate::renderer::rects::{RenderLines, RenderRect}; use crate::renderer::{self, GlyphCache, QuadRenderer}; use crate::url::{Url, Urls}; @@ -167,10 +171,15 @@ impl Display { event_loop.available_monitors().next().map(|m| m.scale_factor()).unwrap_or(1.); // Guess the target window dimensions. - let metrics = GlyphCache::static_metrics(config.font.clone(), estimated_dpr)?; + let metrics = GlyphCache::static_metrics(config.ui_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); + + let dimensions = GlyphCache::calculate_dimensions( + &config.ui_config.window, + estimated_dpr, + cell_width, + cell_height, + ); debug!("Estimated DPR: {}", estimated_dpr); debug!("Estimated Cell Size: {} x {}", cell_width, cell_height); @@ -212,11 +221,12 @@ impl Display { 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; + let padding = config.ui_config.window.padding; + let mut padding_x = f32::from(padding.x) * dpr as f32; + let mut padding_y = f32::from(padding.y) * dpr as f32; if let Some((width, height)) = - GlyphCache::calculate_dimensions(config, dpr, cell_width, cell_height) + GlyphCache::calculate_dimensions(&config.ui_config.window, dpr, cell_width, cell_height) { let PhysicalSize { width: w, height: h } = window.inner_size(); if w == width && h == height { @@ -224,7 +234,7 @@ impl Display { } else { window.set_inner_size(PhysicalSize::new(width, height)); } - } else if config.window.dynamic_padding { + } else if config.ui_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); @@ -252,13 +262,13 @@ impl Display { // Clear screen. let background_color = config.colors.primary.background; - renderer.with_api(&config, &size_info, |api| { + renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { api.clear(background_color); }); // Set subpixel anti-aliasing. #[cfg(target_os = "macos")] - set_font_smoothing(config.font.use_thin_strokes()); + set_font_smoothing(config.ui_config.font.use_thin_strokes()); #[cfg(not(any(target_os = "macos", windows)))] let is_x11 = event_loop.is_x11(); @@ -269,7 +279,7 @@ impl Display { // actually draw something into it and commit those changes. if is_x11 { window.swap_buffers(); - renderer.with_api(&config, &size_info, |api| { + renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { api.finish(); }); } @@ -281,12 +291,12 @@ impl Display { // // TODO: replace `set_position` with `with_position` once available. // Upstream issue: https://github.com/rust-windowing/winit/issues/806. - if let Some(position) = config.window.position { + if let Some(position) = config.ui_config.window.position { window.set_outer_position(PhysicalPosition::from((position.x, position.y))); } #[allow(clippy::single_match)] - match config.window.startup_mode() { + match config.ui_config.window.startup_mode { StartupMode::Fullscreen => window.set_fullscreen(true), #[cfg(target_os = "macos")] StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), @@ -315,8 +325,8 @@ impl Display { 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())?; + let font = config.ui_config.font.clone(); + let rasterizer = Rasterizer::new(dpr as f32, config.ui_config.font.use_thin_strokes())?; // Initialize glyph cache. let glyph_cache = { @@ -387,8 +397,9 @@ impl Display { let cell_height = self.size_info.cell_height; // Recalculate padding. - let mut padding_x = f32::from(config.window.padding.x) * self.size_info.dpr as f32; - let mut padding_y = f32::from(config.window.padding.y) * self.size_info.dpr as f32; + let padding = config.ui_config.window.padding; + let mut padding_x = f32::from(padding.x) * self.size_info.dpr as f32; + let mut padding_y = f32::from(padding.y) * self.size_info.dpr as f32; // Update the window dimensions. if let Some(size) = update_pending.dimensions() { @@ -398,7 +409,7 @@ impl Display { } // Distribute excess padding equally on all sides. - if config.window.dynamic_padding { + if config.ui_config.window.dynamic_padding { padding_x = dynamic_padding(padding_x, self.size_info.width, cell_width); padding_y = dynamic_padding(padding_y, self.size_info.height, cell_height); } @@ -480,7 +491,7 @@ impl Display { // Drop terminal as early as possible to free lock. drop(terminal); - self.renderer.with_api(&config, &size_info, |api| { + self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { api.clear(background_color); }); @@ -491,7 +502,7 @@ impl Display { { let _sampler = self.meter.sampler(); - self.renderer.with_api(&config, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { // Iterate over all non-empty cells in the grid. for cell in grid_cells { // Update URL underlines. @@ -566,7 +577,7 @@ impl Display { // Relay messages to the user. let fg = config.colors.primary.background; for (i, message_text) in text.iter().rev().enumerate() { - self.renderer.with_api(&config, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { api.render_string( glyph_cache, Line(size_info.lines().saturating_sub(i + 1)), @@ -597,7 +608,7 @@ impl Display { // On X11 `swap_buffers` does not block for vsync. However the next OpenGl command // will block to synchronize (this is `glClear` in Alacritty), which causes a // permanent one frame delay. - self.renderer.with_api(&config, &size_info, |api| { + self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |api| { api.finish(); }); } @@ -650,14 +661,14 @@ impl Display { let fg = config.colors.search_bar_foreground(); let bg = config.colors.search_bar_background(); let line = size_info.lines() - message_bar_lines - 1; - self.renderer.with_api(&config, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { api.render_string(glyph_cache, line, &text, fg, Some(bg)); }); } /// Draw render timer. fn draw_render_timer(&mut self, config: &Config, size_info: &SizeInfo) { - if !config.render_timer() { + if !config.ui_config.debug.render_timer { return; } let glyph_cache = &mut self.glyph_cache; @@ -666,7 +677,7 @@ impl Display { let fg = config.colors.normal().black; let bg = config.colors.normal().red; - self.renderer.with_api(&config, &size_info, |mut api| { + self.renderer.with_api(&config.ui_config, config.cursor, &size_info, |mut api| { api.render_string(glyph_cache, size_info.lines() - 2, &timing[..], fg, Some(bg)); }); } @@ -701,8 +712,8 @@ fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 { /// 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); + let offset_x = f64::from(config.ui_config.font.offset.x); + let offset_y = f64::from(config.ui_config.font.offset.y); ( ((metrics.average_advance + offset_x) as f32).floor().max(1.), ((metrics.line_height + offset_y) as f32).floor().max(1.), diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index c651fda3..f90ebdd3 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -32,7 +32,6 @@ use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify, OnResize}; use alacritty_terminal::grid::{Dimensions, Scroll}; use alacritty_terminal::index::{Column, Direction, Line, Point, Side}; -use alacritty_terminal::message_bar::{Message, MessageBuffer}; use alacritty_terminal::selection::{Selection, SelectionType}; use alacritty_terminal::sync::FairMutex; use alacritty_terminal::term::cell::Cell; @@ -47,6 +46,7 @@ use crate::config::Config; use crate::daemon::start_daemon; use crate::display::{Display, DisplayUpdate}; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; +use crate::message_bar::{Message, MessageBuffer}; use crate::scheduler::{Scheduler, TimerId}; use crate::url::{Url, Urls}; use crate::window::Window; @@ -312,14 +312,14 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon fn change_font_size(&mut self, delta: f32) { *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP)); - let font = self.config.font.clone().with_size(*self.font_size); + let font = self.config.ui_config.font.clone().with_size(*self.font_size); self.display_update_pending.set_font(font); self.terminal.dirty = true; } fn reset_font_size(&mut self) { - *self.font_size = self.config.font.size; - self.display_update_pending.set_font(self.config.font.clone()); + *self.font_size = self.config.ui_config.font.size; + self.display_update_pending.set_font(self.config.ui_config.font.clone()); self.terminal.dirty = true; } @@ -636,7 +636,7 @@ impl<N: Notify + OnResize> Processor<N> { received_count: 0, suppress_chars: false, modifiers: Default::default(), - font_size: config.font.size, + font_size: config.ui_config.font.size, config, message_buffer, display, @@ -679,7 +679,7 @@ impl<N: Notify + OnResize> Processor<N> { let mut scheduler = Scheduler::new(); event_loop.run_return(|event, event_loop, control_flow| { - if self.config.debug.print_events { + if self.config.ui_config.debug.print_events { info!("glutin event: {:?}", event); } @@ -791,7 +791,7 @@ impl<N: Notify + OnResize> Processor<N> { }); // Write ref tests to disk. - if self.config.debug.ref_test { + if self.config.ui_config.debug.ref_test { self.write_ref_test_results(&terminal.lock()); } } @@ -811,7 +811,7 @@ impl<N: Notify + OnResize> Processor<N> { let display_update_pending = &mut processor.ctx.display_update_pending; // Push current font to update its DPR. - let font = processor.ctx.config.font.clone(); + let font = processor.ctx.config.ui_config.font.clone(); display_update_pending.set_font(font.with_size(*processor.ctx.font_size)); // Resize to event's dimensions, since no resize event is emitted on Wayland. @@ -829,7 +829,18 @@ impl<N: Notify + OnResize> Processor<N> { Event::ConfigReload(path) => Self::reload_config(&path, processor), Event::Scroll(scroll) => processor.ctx.scroll(scroll), Event::TerminalEvent(event) => match event { - TerminalEvent::Title(title) => processor.ctx.window.set_title(&title), + TerminalEvent::Title(title) => { + let ui_config = &processor.ctx.config.ui_config; + if ui_config.dynamic_title() { + processor.ctx.window.set_title(&title); + } + }, + TerminalEvent::ResetTitle => { + let ui_config = &processor.ctx.config.ui_config; + if ui_config.dynamic_title() { + processor.ctx.window.set_title(&ui_config.window.title); + } + }, TerminalEvent::Wakeup => processor.ctx.terminal.dirty = true, TerminalEvent::Bell => { let bell_command = processor.ctx.config.bell().command.as_ref(); @@ -983,16 +994,23 @@ impl<N: Notify + OnResize> Processor<N> { processor.ctx.display_update_pending.set_cursor_dirty(); } - if processor.ctx.config.font != config.font { + if processor.ctx.config.ui_config.font != config.ui_config.font { // Do not update font size if it has been changed at runtime. - if *processor.ctx.font_size == processor.ctx.config.font.size { - *processor.ctx.font_size = config.font.size; + if *processor.ctx.font_size == processor.ctx.config.ui_config.font.size { + *processor.ctx.font_size = config.ui_config.font.size; } - let font = config.font.clone().with_size(*processor.ctx.font_size); + let font = config.ui_config.font.clone().with_size(*processor.ctx.font_size); processor.ctx.display_update_pending.set_font(font); } + // Live title reload. + if !config.ui_config.dynamic_title() + || processor.ctx.config.ui_config.window.title != config.ui_config.window.title + { + processor.ctx.window.set_title(&config.ui_config.window.title); + } + #[cfg(not(any(target_os = "macos", windows)))] { if processor.ctx.event_loop.is_wayland() { @@ -1002,7 +1020,7 @@ impl<N: Notify + OnResize> Processor<N> { // Set subpixel anti-aliasing. #[cfg(target_os = "macos")] - set_font_smoothing(config.font.use_thin_strokes()); + set_font_smoothing(config.ui_config.font.use_thin_strokes()); *processor.ctx.config = config; diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 64b79002..2d865990 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -26,7 +26,6 @@ use alacritty_terminal::ansi::{ClearMode, Handler}; use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Scroll}; use alacritty_terminal::index::{Column, Direction, Line, Point, Side}; -use alacritty_terminal::message_bar::{self, Message}; use alacritty_terminal::selection::SelectionType; use alacritty_terminal::term::mode::TermMode; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term}; @@ -36,6 +35,7 @@ use crate::clipboard::Clipboard; use crate::config::{Action, Binding, Config, Key, ViAction}; use crate::daemon::start_daemon; use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY}; +use crate::message_bar::{self, Message}; use crate::scheduler::{Scheduler, TimerId}; use crate::url::{Url, Urls}; use crate::window::Window; @@ -894,7 +894,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { c.encode_utf8(&mut bytes[..]); } - if self.ctx.config().alt_send_esc() + if self.ctx.config().ui_config.alt_send_esc() && *self.ctx.received_count() == 0 && self.ctx.modifiers().alt() && utf8_len == 1 @@ -1088,10 +1088,10 @@ mod tests { use glutin::event::{Event as GlutinEvent, VirtualKeyCode, WindowEvent}; use alacritty_terminal::event::Event as TerminalEvent; - use alacritty_terminal::message_bar::MessageBuffer; use alacritty_terminal::selection::Selection; use crate::config::ClickHandler; + use crate::message_bar::MessageBuffer; const KEY: VirtualKeyCode = VirtualKeyCode::Key0; diff --git a/alacritty/src/locale.rs b/alacritty/src/locale.rs new file mode 100644 index 00000000..6c02c12c --- /dev/null +++ b/alacritty/src/locale.rs @@ -0,0 +1,100 @@ +#![allow(clippy::let_unit_value)] + +use std::env; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::slice; +use std::str; + +use libc::{setlocale, LC_ALL, LC_CTYPE}; +use log::debug; +use objc::runtime::{Class, Object}; +use objc::{msg_send, sel, sel_impl}; + +const FALLBACK_LOCALE: &str = "UTF-8"; + +pub fn set_locale_environment() { + let env_locale_c = CString::new("").unwrap(); + let env_locale_ptr = unsafe { setlocale(LC_ALL, env_locale_c.as_ptr()) }; + if !env_locale_ptr.is_null() { + let env_locale = unsafe { CStr::from_ptr(env_locale_ptr).to_string_lossy() }; + + // Assume `C` locale means unchanged, since it is the default anyways. + if env_locale != "C" { + debug!("Using environment locale: {}", env_locale); + return; + } + } + + let system_locale = system_locale(); + + // Set locale to system locale. + let system_locale_c = CString::new(system_locale.clone()).expect("nul byte in system locale"); + let lc_all = unsafe { setlocale(LC_ALL, system_locale_c.as_ptr()) }; + + // Check if system locale was valid or not. + if lc_all.is_null() { + // Use fallback locale. + debug!("Using fallback locale: {}", FALLBACK_LOCALE); + + let fallback_locale_c = CString::new(FALLBACK_LOCALE).unwrap(); + unsafe { setlocale(LC_CTYPE, fallback_locale_c.as_ptr()) }; + + env::set_var("LC_CTYPE", FALLBACK_LOCALE); + } else { + // Use system locale. + debug!("Using system locale: {}", system_locale); + + env::set_var("LC_ALL", system_locale); + } +} + +/// Determine system locale based on language and country code. +fn system_locale() -> String { + unsafe { + let locale_class = Class::get("NSLocale").unwrap(); + let locale: *const Object = msg_send![locale_class, currentLocale]; + let _: () = msg_send![locale_class, release]; + + // `localeIdentifier` returns extra metadata with the locale (including currency and + // collator) on newer versions of macOS. This is not a valid locale, so we use + // `languageCode` and `countryCode`, if they're available (macOS 10.12+): + // + // https://developer.apple.com/documentation/foundation/nslocale/1416263-localeidentifier?language=objc + // https://developer.apple.com/documentation/foundation/nslocale/1643060-countrycode?language=objc + // https://developer.apple.com/documentation/foundation/nslocale/1643026-languagecode?language=objc + let is_language_code_supported: bool = + msg_send![locale, respondsToSelector: sel!(languageCode)]; + let is_country_code_supported: bool = + msg_send![locale, respondsToSelector: sel!(countryCode)]; + let locale_id = if is_language_code_supported && is_country_code_supported { + let language_code: *const Object = msg_send![locale, languageCode]; + let language_code_str = nsstring_as_str(language_code).to_owned(); + let _: () = msg_send![language_code, release]; + + let country_code: *const Object = msg_send![locale, countryCode]; + let country_code_str = nsstring_as_str(country_code).to_owned(); + let _: () = msg_send![country_code, release]; + + format!("{}_{}.UTF-8", &language_code_str, &country_code_str) + } else { + let identifier: *const Object = msg_send![locale, localeIdentifier]; + let identifier_str = nsstring_as_str(identifier).to_owned(); + let _: () = msg_send![identifier, release]; + + identifier_str + ".UTF-8" + }; + + let _: () = msg_send![locale, release]; + + locale_id + } +} + +const UTF8_ENCODING: usize = 4; + +unsafe fn nsstring_as_str<'a>(nsstring: *const Object) -> &'a str { + let cstr: *const c_char = msg_send![nsstring, UTF8String]; + let len: usize = msg_send![nsstring, lengthOfBytesUsingEncoding: UTF8_ENCODING]; + str::from_utf8(slice::from_raw_parts(cstr as *const u8, len)).unwrap() +} diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index a6fcf3fb..5d702999 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -15,11 +15,11 @@ use std::sync::{Arc, Mutex}; use glutin::event_loop::EventLoopProxy; use log::{self, Level}; -use alacritty_terminal::message_bar::Message; use alacritty_terminal::term::color; use crate::cli::Options; use crate::event::Event; +use crate::message_bar::Message; const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 9fbcab55..ae3ef346 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -1,7 +1,6 @@ //! Alacritty - The GPU Enhanced Terminal. #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![cfg_attr(all(test, feature = "bench"), feature(test))] // With the default subsystem, 'console', windows creates an additional console // window for the program. @@ -24,10 +23,6 @@ use log::{error, info}; use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS}; 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::Term; use alacritty_terminal::tty; @@ -40,7 +35,13 @@ mod daemon; mod display; mod event; mod input; +#[cfg(target_os = "macos")] +mod locale; mod logging; +mod message_bar; +mod meter; +#[cfg(windows)] +mod panic; mod renderer; mod scheduler; mod url; @@ -59,8 +60,10 @@ use crate::config::monitor::Monitor; use crate::config::Config; use crate::display::Display; use crate::event::{Event, EventProxy, Processor}; +use crate::message_bar::MessageBuffer; fn main() { + #[cfg(windows)] panic::attach_handler(); // When linked with the windows subsystem windows won't automatically attach @@ -87,7 +90,7 @@ fn main() { let config = options.into_config(config); // Update the log level from config. - log::set_max_level(config.debug.log_level); + log::set_max_level(config.ui_config.debug.log_level); // Switch to home directory. #[cfg(target_os = "macos")] @@ -97,7 +100,7 @@ fn main() { locale::set_locale_environment(); // Store if log file should be deleted before moving config. - let persistent_logging = config.persistent_logging(); + let persistent_logging = config.ui_config.debug.persistent_logging; // Run Alacritty. if let Err(err) = run(window_event_loop, config) { @@ -161,7 +164,13 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), // renderer and input processing. Note that access to the terminal state is // synchronized since the I/O loop updates the state, and the display // consumes it periodically. - let event_loop = EventLoop::new(Arc::clone(&terminal), event_proxy.clone(), pty, &config); + let event_loop = EventLoop::new( + Arc::clone(&terminal), + event_proxy.clone(), + pty, + config.hold, + config.ui_config.debug.ref_test, + ); // The event loop channel allows write requests from the event processor // to be sent to the pty loop and ultimately written to the pty. @@ -171,7 +180,7 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), // // The monitor watches the config file for changes and reloads it. Pending // config changes are processed in the main loop. - if config.live_config_reload() { + if config.ui_config.live_config_reload() { config.config_path.as_ref().map(|path| Monitor::new(path, event_proxy.clone())); } diff --git a/alacritty/src/message_bar.rs b/alacritty/src/message_bar.rs new file mode 100644 index 00000000..175f7f81 --- /dev/null +++ b/alacritty/src/message_bar.rs @@ -0,0 +1,433 @@ +use std::collections::VecDeque; + +use alacritty_terminal::term::color::Rgb; +use alacritty_terminal::term::SizeInfo; + +pub const CLOSE_BUTTON_TEXT: &str = "[X]"; +const CLOSE_BUTTON_PADDING: usize = 1; +const MIN_FREE_LINES: usize = 3; +const TRUNCATED_MESSAGE: &str = "[MESSAGE TRUNCATED]"; + +/// Message for display in the MessageBuffer. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Message { + text: String, + color: Rgb, + target: Option<String>, +} + +impl Message { + /// Create a new message. + pub fn new(text: String, color: Rgb) -> Message { + Message { text, color, target: None } + } + + /// 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); + let button_len = CLOSE_BUTTON_TEXT.len(); + + // Split line to fit the screen. + let mut lines = Vec::new(); + let mut line = String::new(); + for c in self.text.trim().chars() { + if c == '\n' + || line.len() == num_cols + // Keep space in first line for button. + || (lines.is_empty() + && num_cols >= button_len + && line.len() == num_cols.saturating_sub(button_len + CLOSE_BUTTON_PADDING)) + { + // Attempt to wrap on word boundaries. + if let (Some(index), true) = (line.rfind(char::is_whitespace), c != '\n') { + let split = line.split_off(index + 1); + line.pop(); + lines.push(Self::pad_text(line, num_cols)); + line = split + } else { + lines.push(Self::pad_text(line, num_cols)); + line = String::new(); + } + } + + if c != '\n' { + line.push(c); + } + } + lines.push(Self::pad_text(line, num_cols)); + + // Truncate output if it's too long. + if lines.len() > max_lines { + lines.truncate(max_lines); + if TRUNCATED_MESSAGE.len() <= num_cols { + if let Some(line) = lines.iter_mut().last() { + *line = Self::pad_text(TRUNCATED_MESSAGE.into(), num_cols); + } + } + } + + // Append close button to first line. + if button_len <= num_cols { + if let Some(line) = lines.get_mut(0) { + line.truncate(num_cols - button_len); + line.push_str(CLOSE_BUTTON_TEXT); + } + } + + lines + } + + /// Message color. + #[inline] + pub fn color(&self) -> Rgb { + self.color + } + + /// Message target. + #[inline] + pub fn target(&self) -> Option<&String> { + self.target.as_ref() + } + + /// Update the message target. + #[inline] + pub fn set_target(&mut self, target: String) { + self.target = Some(target); + } + + /// 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()); + text.extend(vec![' '; padding_len]); + text + } +} + +/// Storage for message bar. +#[derive(Debug, Default)] +pub struct MessageBuffer { + messages: VecDeque<Message>, +} + +impl MessageBuffer { + /// Create new message buffer. + pub fn new() -> MessageBuffer { + MessageBuffer { messages: VecDeque::new() } + } + + /// Check if there are any messages queued. + #[inline] + pub fn is_empty(&self) -> bool { + self.messages.is_empty() + } + + /// Current message. + #[inline] + pub fn message(&self) -> Option<&Message> { + self.messages.front() + } + + /// Remove the currently visible message. + #[inline] + pub fn pop(&mut self) { + // Remove the message itself. + let msg = self.messages.pop_front(); + + // Remove all duplicates. + if let Some(msg) = msg { + self.messages = self.messages.drain(..).filter(|m| m != &msg).collect(); + } + } + + /// 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); + } +} + +#[cfg(test)] +mod tests { + use super::{Message, MessageBuffer, MIN_FREE_LINES}; + use alacritty_terminal::term::{color, SizeInfo}; + + #[test] + fn appends_close_button() { + let input = "a"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 7., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("a [X]")]); + } + + #[test] + fn multiline_close_button_first_line() { + let input = "fo\nbar"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 6., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("fo [X]"), String::from("bar ")]); + } + + #[test] + fn splits_on_newline() { + let input = "a\nb"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 6., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines.len(), 2); + } + + #[test] + fn splits_on_length() { + let input = "foobar1"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 6., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines.len(), 2); + } + + #[test] + fn empty_with_shortterm() { + let input = "foobar"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 6., + height: 0., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines.len(), 0); + } + + #[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.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 22., + height: (MIN_FREE_LINES + 2) as f32, + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![ + String::from("hahahahahahahahaha [X]"), + String::from("[MESSAGE TRUNCATED] ") + ]); + } + + #[test] + fn hide_button_when_too_narrow() { + let input = "ha"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 2., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("ha")]); + } + + #[test] + fn hide_truncated_when_too_narrow() { + let input = "hahahahahahahahaha"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 2., + height: (MIN_FREE_LINES + 2) as f32, + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("ha"), String::from("ha")]); + } + + #[test] + fn add_newline_for_button() { + let input = "test"; + let mut message_buffer = MessageBuffer::new(); + message_buffer.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 5., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![String::from("t [X]"), String::from("est ")]); + } + + #[test] + 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_target("target".into()); + } + message_buffer.push(msg); + } + + message_buffer.remove_target("target"); + + // Count number of messages. + let mut num_messages = 0; + while message_buffer.message().is_some() { + num_messages += 1; + message_buffer.pop(); + } + + assert_eq!(num_messages, 7); + } + + #[test] + fn pop() { + let mut message_buffer = MessageBuffer::new(); + let one = Message::new(String::from("one"), color::RED); + message_buffer.push(one.clone()); + let two = Message::new(String::from("two"), color::YELLOW); + message_buffer.push(two.clone()); + + assert_eq!(message_buffer.message(), Some(&one)); + + message_buffer.pop(); + + 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.push(Message::new(input.into(), color::RED)); + let size = SizeInfo { + width: 5., + height: 10., + cell_width: 1., + cell_height: 1., + padding_x: 0., + padding_y: 0., + dpr: 0., + }; + + let lines = message_buffer.message().unwrap().text(&size); + + assert_eq!(lines, vec![ + String::from("a [X]"), + String::from("bc "), + String::from("defg ") + ]); + } + + #[test] + fn remove_duplicates() { + let mut message_buffer = MessageBuffer::new(); + for _ in 0..10 { + let msg = Message::new(String::from("test"), color::RED); + message_buffer.push(msg); + } + 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(); + + // Count number of messages. + let mut num_messages = 0; + while message_buffer.message().is_some() { + num_messages += 1; + message_buffer.pop(); + } + + assert_eq!(num_messages, 2); + } +} diff --git a/alacritty/src/meter.rs b/alacritty/src/meter.rs new file mode 100644 index 00000000..c07d901f --- /dev/null +++ b/alacritty/src/meter.rs @@ -0,0 +1,97 @@ +//! Rendering time meter. +//! +//! Used to track rendering times and provide moving averages. +//! +//! # Examples +//! +//! ```rust +//! // create a meter +//! let mut meter = alacritty_terminal::meter::Meter::new(); +//! +//! // Sample something. +//! { +//! let _sampler = meter.sampler(); +//! } +//! +//! // 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}; + +const NUM_SAMPLES: usize = 10; + +/// The meter. +#[derive(Default)] +pub struct Meter { + /// Track last 60 timestamps. + times: [f64; NUM_SAMPLES], + + /// Average sample time in microseconds. + avg: f64, + + /// Index of next time to update.. + index: usize, +} + +/// Sampler. +/// +/// Samplers record how long they are "alive" for and update the meter on drop.. +pub struct Sampler<'a> { + /// Reference to meter that created the sampler. + meter: &'a mut Meter, + + /// When the sampler was created. + created_at: Instant, +} + +impl<'a> Sampler<'a> { + fn new(meter: &'a mut Meter) -> Sampler<'a> { + Sampler { meter, created_at: Instant::now() } + } + + #[inline] + fn alive_duration(&self) -> Duration { + self.created_at.elapsed() + } +} + +impl<'a> Drop for Sampler<'a> { + fn drop(&mut self) { + self.meter.add_sample(self.alive_duration()); + } +} + +impl Meter { + /// Create a meter. + pub fn new() -> Meter { + Default::default() + } + + /// Get a sampler. + pub fn sampler(&mut self) -> Sampler<'_> { + Sampler::new(self) + } + + /// Get the current average sample duration in microseconds. + pub fn average(&self) -> f64 { + self.avg + } + + /// Add a sample. + /// + /// Used by Sampler::drop. + fn add_sample(&mut self, sample: Duration) { + let mut usec = 0f64; + + usec += f64::from(sample.subsec_nanos()) / 1e3; + usec += (sample.as_secs() as f64) * 1e6; + + let prev = self.times[self.index]; + self.times[self.index] = usec; + self.avg -= prev / NUM_SAMPLES as f64; + self.avg += usec / NUM_SAMPLES as f64; + self.index = (self.index + 1) % NUM_SAMPLES; + } +} diff --git a/alacritty/src/panic.rs b/alacritty/src/panic.rs new file mode 100644 index 00000000..9a76df72 --- /dev/null +++ b/alacritty/src/panic.rs @@ -0,0 +1,24 @@ +use alacritty_terminal::tty::windows::win32_string; + +// Install a panic handler that renders the panic in a classical Windows error +// dialog box as well as writes the panic to STDERR. +pub fn attach_handler() { + use std::{io, io::Write, panic, ptr}; + use winapi::um::winuser; + + panic::set_hook(Box::new(|panic_info| { + let _ = writeln!(io::stderr(), "{}", panic_info); + let msg = format!("{}\n\nPress Ctrl-C to Copy", panic_info); + unsafe { + winuser::MessageBoxW( + ptr::null_mut(), + win32_string(&msg).as_ptr(), + win32_string("Alacritty: Runtime Error").as_ptr(), + winuser::MB_ICONERROR + | winuser::MB_OK + | winuser::MB_SETFOREGROUND + | winuser::MB_TASKMODAL, + ); + } + })); +} diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 99e4afbc..09682e6e 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::fmt::{self, Display, Formatter}; use std::fs; use std::hash::BuildHasherDefault; use std::io; @@ -10,7 +11,8 @@ use std::time::Duration; use fnv::FnvHasher; use font::{ - self, BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer, Size, + BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer, Size, Slant, + Style, Weight, }; use log::{error, info}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; @@ -19,16 +21,19 @@ use crate::cursor; use crate::gl; use crate::gl::types::*; use crate::renderer::rects::RenderRect; -use alacritty_terminal::config::{self, Config, Delta, Font, StartupMode}; +use alacritty_terminal::config::Cursor; use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::term::cell::{self, Flags}; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::{self, CursorKey, RenderableCell, RenderableCellContent, SizeInfo}; +use alacritty_terminal::term::{CursorKey, RenderableCell, RenderableCellContent, SizeInfo}; use alacritty_terminal::thread; -use std::fmt::{self, Display, Formatter}; pub mod rects; +use crate::config::font::{Font, FontDescription}; +use crate::config::ui_config::{Delta, UIConfig}; +use crate::config::window::{StartupMode, WindowConfig}; + // Shader paths for live reload. static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"); static TEXT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl"); @@ -173,7 +178,7 @@ pub struct GlyphCache { impl GlyphCache { pub fn new<L>( mut rasterizer: Rasterizer, - font: &config::Font, + font: &Font, loader: &mut L, ) -> Result<GlyphCache, font::Error> where @@ -215,14 +220,13 @@ impl GlyphCache { /// Computes font keys for (Regular, Bold, Italic, Bold Italic). fn compute_font_keys( - font: &config::Font, + font: &Font, rasterizer: &mut Rasterizer, ) -> Result<(FontKey, FontKey, FontKey, FontKey), font::Error> { let size = font.size; // Load regular font. - let regular_desc = - Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); + let regular_desc = Self::make_desc(&font.normal(), Slant::Normal, Weight::Normal); let regular = Self::load_regular_font(rasterizer, ®ular_desc, size)?; @@ -236,19 +240,17 @@ impl GlyphCache { }; // Load bold font. - let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold); + let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold); let bold = load_or_regular(bold_desc); // Load italic font. - let italic_desc = - Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal); + let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal); let italic = load_or_regular(italic_desc); // Load bold italic font. - let bold_italic_desc = - Self::make_desc(&font.bold_italic(), font::Slant::Italic, font::Weight::Bold); + let bold_italic_desc = Self::make_desc(&font.bold_italic(), Slant::Italic, Weight::Bold); let bold_italic = load_or_regular(bold_italic_desc); @@ -265,25 +267,18 @@ impl GlyphCache { Err(err) => { error!("{}", err); - let fallback_desc = Self::make_desc( - &Font::default().normal(), - font::Slant::Normal, - font::Weight::Normal, - ); + let fallback_desc = + Self::make_desc(&Font::default().normal(), Slant::Normal, Weight::Normal); rasterizer.load_font(&fallback_desc, size) }, } } - fn make_desc( - desc: &config::FontDescription, - slant: font::Slant, - weight: font::Weight, - ) -> FontDesc { + fn make_desc(desc: &FontDescription, slant: Slant, weight: Weight) -> FontDesc { let style = if let Some(ref spec) = desc.style { - font::Style::Specific(spec.to_owned()) + Style::Specific(spec.to_owned()) } else { - font::Style::Description { slant, weight } + Style::Description { slant, weight } }; FontDesc::new(desc.family.clone(), style) } @@ -318,7 +313,7 @@ impl GlyphCache { pub fn update_font_size<L: LoadGlyph>( &mut self, - font: &config::Font, + font: &Font, dpr: f64, loader: &mut L, ) -> Result<(), font::Error> { @@ -361,31 +356,30 @@ impl GlyphCache { /// 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_desc = GlyphCache::make_desc(&font.normal(), Slant::Normal, Weight::Normal); let regular = Self::load_regular_font(&mut rasterizer, ®ular_desc, font.size)?; rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; rasterizer.metrics(regular, font.size) } - pub fn calculate_dimensions<C>( - config: &Config<C>, + pub fn calculate_dimensions( + window_config: &WindowConfig, dpr: f64, cell_width: f32, cell_height: f32, ) -> Option<(u32, u32)> { - let dimensions = config.window.dimensions; + let dimensions = window_config.dimensions; if dimensions.columns_u32() == 0 || dimensions.lines_u32() == 0 - || config.window.startup_mode() != StartupMode::Windowed + || window_config.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; + let padding_x = f64::from(window_config.padding.x) * dpr; + let padding_y = f64::from(window_config.padding.y) * dpr; // Calculate new size based on cols/lines specified in config. let grid_width = cell_width as u32 * dimensions.columns_u32(); @@ -446,13 +440,14 @@ pub struct QuadRenderer { } #[derive(Debug)] -pub struct RenderApi<'a, C> { +pub struct RenderApi<'a> { 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<C>, + config: &'a UIConfig, + cursor_config: Cursor, } #[derive(Debug)] @@ -730,7 +725,7 @@ impl QuadRenderer { } /// Draw all rectangles simultaneously to prevent excessive program swaps. - pub fn draw_rects(&mut self, props: &term::SizeInfo, rects: Vec<RenderRect>) { + pub fn draw_rects(&mut self, props: &SizeInfo, rects: Vec<RenderRect>) { // Swap to rectangle rendering program. unsafe { // Swap program. @@ -783,9 +778,15 @@ impl QuadRenderer { } } - pub fn with_api<F, T, C>(&mut self, config: &Config<C>, props: &term::SizeInfo, func: F) -> T + pub fn with_api<F, T>( + &mut self, + config: &UIConfig, + cursor_config: Cursor, + props: &SizeInfo, + func: F, + ) -> T where - F: FnOnce(RenderApi<'_, C>) -> T, + F: FnOnce(RenderApi<'_>) -> T, { // Flush message queue. if let Ok(Msg::ShaderReload) = self.rx.try_recv() { @@ -810,6 +811,7 @@ impl QuadRenderer { current_atlas: &mut self.current_atlas, program: &mut self.program, config, + cursor_config, }); unsafe { @@ -838,7 +840,7 @@ impl QuadRenderer { }) } - pub fn reload_shaders(&mut self, props: &term::SizeInfo) { + pub fn reload_shaders(&mut self, props: &SizeInfo) { info!("Reloading shaders..."); let result = (TextShaderProgram::new(), RectShaderProgram::new()); let (program, rect_program) = match result { @@ -888,7 +890,7 @@ impl QuadRenderer { /// Render a rectangle. /// /// This requires the rectangle program to be activated. - fn render_rect(&mut self, rect: &RenderRect, size: &term::SizeInfo) { + fn render_rect(&mut self, rect: &RenderRect, size: &SizeInfo) { // Do nothing when alpha is fully transparent. if rect.alpha == 0. { return; @@ -923,7 +925,7 @@ impl QuadRenderer { } } -impl<'a, C> RenderApi<'a, C> { +impl<'a> RenderApi<'a> { pub fn clear(&self, color: Rgb) { unsafe { let alpha = self.config.background_opacity(); @@ -1046,7 +1048,7 @@ impl<'a, C> RenderApi<'a, C> { self.config.font.offset.x, self.config.font.offset.y, cursor_key.is_wide, - self.config.cursor.thickness(), + self.cursor_config.thickness(), )) }); self.add_render_item(cell, glyph); @@ -1154,7 +1156,7 @@ impl<'a> LoadGlyph for LoaderApi<'a> { } } -impl<'a, C> LoadGlyph for RenderApi<'a, C> { +impl<'a> LoadGlyph for RenderApi<'a> { fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) } @@ -1164,7 +1166,7 @@ impl<'a, C> LoadGlyph for RenderApi<'a, C> { } } -impl<'a, C> Drop for RenderApi<'a, C> { +impl<'a> Drop for RenderApi<'a> { fn drop(&mut self) { if !self.batch.is_empty() { self.render_batch(); @@ -1251,7 +1253,7 @@ impl TextShaderProgram { } } - fn set_term_uniforms(&self, props: &term::SizeInfo) { + fn set_term_uniforms(&self, props: &SizeInfo) { unsafe { gl::Uniform2f(self.u_cell_dim, props.cell_width, props.cell_height); } diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs index 450329d4..89f16b7a 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/window.rs @@ -33,12 +33,12 @@ use glutin::{self, ContextBuilder, PossiblyCurrent, WindowedContext}; #[cfg(windows)] use winapi::shared::minwindef::WORD; -use alacritty_terminal::config::{Decorations, StartupMode, WindowConfig}; #[cfg(not(windows))] use alacritty_terminal::index::Point; #[cfg(not(windows))] use alacritty_terminal::term::SizeInfo; +use crate::config::window::{Decorations, StartupMode, WindowConfig}; use crate::config::Config; use crate::gl; @@ -154,7 +154,8 @@ impl Window { size: Option<PhysicalSize<u32>>, #[cfg(not(any(target_os = "macos", windows)))] wayland_event_queue: Option<&EventQueue>, ) -> Result<Window> { - let window_builder = Window::get_platform_window(&config.window.title, &config.window); + let window_config = &config.ui_config.window; + let window_builder = Window::get_platform_window(&window_config.title, &window_config); // Disable vsync on Wayland. #[cfg(not(any(target_os = "macos", windows)))] @@ -180,7 +181,7 @@ impl Window { { if event_loop.is_x11() { // On X11, embed the window inside another if the parent ID has been set. - if let Some(parent_window_id) = config.window.embed { + if let Some(parent_window_id) = window_config.embed { x_embed_window(windowed_context.window(), parent_window_id); } } else { @@ -265,7 +266,7 @@ impl Window { .with_visible(false) .with_transparent(true) .with_decorations(decorations) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized) + .with_maximized(window_config.startup_mode == StartupMode::Maximized) .with_window_icon(icon.ok()) // X11. .with_class(class.instance.clone(), class.general.clone()) @@ -293,7 +294,7 @@ impl Window { .with_visible(false) .with_decorations(decorations) .with_transparent(true) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized) + .with_maximized(window_config.startup_mode == StartupMode::Maximized) .with_window_icon(icon.ok()) } @@ -303,7 +304,7 @@ impl Window { .with_title(title) .with_visible(false) .with_transparent(true) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized); + .with_maximized(window_config.startup_mode == StartupMode::Maximized); match window_config.decorations { Decorations::Full => window, |