aboutsummaryrefslogtreecommitdiff
path: root/alacritty
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty')
-rw-r--r--alacritty/Cargo.toml3
-rw-r--r--alacritty/src/cli.rs51
-rw-r--r--alacritty/src/config/debug.rs64
-rw-r--r--alacritty/src/config/font.rs218
-rw-r--r--alacritty/src/config/mod.rs33
-rw-r--r--alacritty/src/config/ui_config.rs97
-rw-r--r--alacritty/src/config/window.rs220
-rw-r--r--alacritty/src/display.rs73
-rw-r--r--alacritty/src/event.rs46
-rw-r--r--alacritty/src/input.rs6
-rw-r--r--alacritty/src/locale.rs100
-rw-r--r--alacritty/src/logging.rs2
-rw-r--r--alacritty/src/main.rs27
-rw-r--r--alacritty/src/message_bar.rs433
-rw-r--r--alacritty/src/meter.rs97
-rw-r--r--alacritty/src/panic.rs24
-rw-r--r--alacritty/src/renderer/mod.rs94
-rw-r--r--alacritty/src/window.rs13
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, &regular_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, &regular_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,