diff --git a/Cargo.lock b/Cargo.lock index ec79f5fc..269d5191 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,9 @@ dependencies = [ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "notify 2.6.1 (git+https://github.com/passcod/rsnotify)", "parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_macros 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -18,6 +21,11 @@ name = "android_glue" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "aster" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "0.3.3" @@ -561,6 +569,27 @@ name = "pkg-config" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "quasi" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quasi_codegen" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aster 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quasi_macros" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quasi_codegen 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.14" @@ -592,6 +621,39 @@ name = "serde" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "serde_codegen" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aster 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quasi 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quasi_macros 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_item 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_item" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_macros" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_codegen 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_yaml" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "servo-fontconfig" version = "0.2.0" @@ -767,3 +829,8 @@ dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "yaml-rust" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/Cargo.toml b/Cargo.toml index d069daf9..22db5f00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ bitflags = "*" font = { path = "./font" } errno = "0.1.6" parking_lot = "0.2.6" +serde = "0.7" +serde_yaml = "0.2" +serde_macros = "0.7" [build-dependencies] gl_generator = "0.5" diff --git a/alacritty.yml b/alacritty.yml new file mode 100644 index 00000000..e0d1d3fa --- /dev/null +++ b/alacritty.yml @@ -0,0 +1,21 @@ +# Configuration for Alacritty, the GPU enhanced terminal emulator + +# The FreeType rasterizer needs to know the device DPI for best results +dpi: + x: 96.0 + y: 96.0 + +# Font configuration +font: + family: DejaVu Sans Mono + style: Book + # Point size of the font + size: 11.0 + # Offset is the extra space around each character. offset.y can be thought of + # as modifying the linespacing, and offset.x as modifying the letter spacing. + offset: + x: 2.0 + y: -7.0 + +# Should display the render timer +render_timer: false diff --git a/src/ansi.rs b/src/ansi.rs index 2fc3e4f3..5d4d9e4c 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -31,7 +31,6 @@ //! should be, feel free to add it. Please try not to become overzealous and adding support for //! sequences only used by folks trapped in 1988. -use std::io::Write; use ::Rgb; /// A CSI Escape sequence diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..426a3381 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,300 @@ +//! Configuration definitions and file loading +//! +//! Alacritty reads from a config file at startup to determine various runtime +//! parameters including font family and style, font size, etc. In the future, +//! the config file will also hold user and platform specific keybindings. +use std::env; +use std::fs; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; + +use serde_yaml; + +/// Top-level config type +#[derive(Debug, Deserialize, Default)] +pub struct Config { + /// Pixels per inch + #[serde(default)] + dpi: Dpi, + + /// Font configuration + #[serde(default)] + font: Font, + + /// Should show render timer + #[serde(default)] + render_timer: bool, +} + +/// Errors occurring during config loading +#[derive(Debug)] +pub enum Error { + /// Config file not found + NotFound, + + /// Couldn't read $HOME environment variable + ReadingEnvHome(env::VarError), + + /// io error reading file + Io(io::Error), + + /// Not valid yaml or missing parameters + Yaml(serde_yaml::Error), +} + +impl ::std::error::Error for Error { + fn cause(&self) -> Option<&::std::error::Error> { + match *self { + Error::NotFound => None, + Error::ReadingEnvHome(ref err) => Some(err), + Error::Io(ref err) => Some(err), + Error::Yaml(ref err) => Some(err), + } + } + + fn description(&self) -> &str { + match *self { + Error::NotFound => "could not locate config file", + Error::ReadingEnvHome(ref err) => err.description(), + Error::Io(ref err) => err.description(), + Error::Yaml(ref err) => err.description(), + } + } +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match *self { + Error::NotFound => write!(f, "{}", ::std::error::Error::description(self)), + Error::ReadingEnvHome(ref err) => { + write!(f, "could not read $HOME environment variable: {}", err) + }, + Error::Io(ref err) => write!(f, "error reading config file: {}", err), + Error::Yaml(ref err) => write!(f, "problem with config: {}", err), + } + } +} + +impl From for Error { + fn from(val: env::VarError) -> Error { + Error::ReadingEnvHome(val) + } +} + +impl From for Error { + fn from(val: io::Error) -> Error { + if val.kind() == io::ErrorKind::NotFound { + Error::NotFound + } else { + Error::Io(val) + } + } +} + +impl From for Error { + fn from(val: serde_yaml::Error) -> Error { + Error::Yaml(val) + } +} + +/// Result from config loading +pub type Result = ::std::result::Result; + +impl Config { + /// Attempt to load the config file + /// + /// The config file is loaded from the first file it finds in this list of paths + /// + /// 1. `$HOME/.config/alacritty.yml` + /// 2. `$HOME/.alacritty.yml` + pub fn load() -> Result { + let home = env::var("HOME")?; + + // First path + let mut path = PathBuf::from(&home); + path.push(".config"); + path.push("alacritty.yml"); + + // Fallback path + let mut alt_path = PathBuf::from(&home); + alt_path.push(".alacritty.yml"); + + match Config::load_from(&path) { + Ok(c) => Ok(c), + Err(e) => { + match e { + Error::NotFound => Config::load_from(&alt_path), + _ => Err(e), + } + } + } + } + + /// Get font config + #[inline] + pub fn font(&self) -> &Font { + &self.font + } + + /// Get dpi config + #[inline] + pub fn dpi(&self) -> &Dpi { + &self.dpi + } + + /// Should show render timer + #[inline] + pub fn render_timer(&self) -> bool { + self.render_timer + } + + fn load_from>(path: P) -> Result { + let raw = Config::read_file(path)?; + Ok(serde_yaml::from_str(&raw[..])?) + } + + fn read_file>(path: P) -> Result { + let mut f = fs::File::open(path)?; + let mut contents = String::new(); + f.read_to_string(&mut contents)?; + + Ok(contents) + } +} + +/// Pixels per inch +/// +/// This is only used on FreeType systems +#[derive(Debug, Deserialize)] +pub struct Dpi { + /// Horizontal dpi + x: f32, + + /// Vertical dpi + y: f32, +} + +impl Default for Dpi { + fn default() -> Dpi { + Dpi { x: 96.0, y: 96.0 } + } +} + +impl Dpi { + /// Get horizontal dpi + #[inline] + pub fn x(&self) -> f32 { + self.x + } + + /// Get vertical dpi + #[inline] + pub fn y(&self) -> f32 { + self.y + } +} + +/// Modifications to font spacing +/// +/// The way Alacritty calculates vertical and horizontal cell sizes may not be +/// ideal for all fonts. This gives the user a way to tweak those values. +#[derive(Debug, Deserialize)] +pub struct FontOffset { + /// Extra horizontal spacing between letters + x: f32, + /// Extra vertical spacing between lines + y: f32, +} + +impl FontOffset { + /// Get letter spacing + #[inline] + pub fn x(&self) -> f32 { + self.x + } + + /// Get line spacing + #[inline] + pub fn y(&self) -> f32 { + self.y + } +} + +/// 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. +#[derive(Debug, Deserialize)] +pub struct Font { + /// Font family + family: String, + + /// Font style + style: String, + + /// Font size in points + size: f32, + + /// Extra spacing per character + offset: FontOffset, +} + +impl Font { + /// Get the font family + #[inline] + pub fn family(&self) -> &str { + &self.family[..] + } + + /// Get the font style + #[inline] + pub fn style(&self) -> &str { + &self.style[..] + } + + /// Get the font size in points + #[inline] + pub fn size(&self) -> f32 { + self.size + } + + /// Get offsets to font metrics + #[inline] + pub fn offset(&self) -> &FontOffset { + &self.offset + } +} + +#[cfg(target_os = "macos")] +impl Default for Font { + fn default() -> Font { + Font { + family: String::from("Menlo"), + style: String::from("Regular"), + size: 11.0, + offset: FontOffset { + x: 0.0, + y: 0.0 + } + } + } +} + +#[cfg(target_os = "linux")] +impl Default for Font { + fn default() -> Font { + Font { + family: String::from("DejaVu Sans Mono"), + style: String::from("Book"), + size: 11.0, + offset: FontOffset { + // TODO should improve freetype metrics... shouldn't need such + // drastic offsets for the default! + x: 2.0, + y: -7.0 + } + } + } +} diff --git a/src/macros.rs b/src/macros.rs index e35eeb90..8abad78d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -14,17 +14,16 @@ #[macro_export] macro_rules! die { - ($($arg:tt)*) => { - println!($($arg)*); + ($($arg:tt)*) => {{ + err_println!($($arg)*); ::std::process::exit(1); - } + }} } #[macro_export] macro_rules! err_println { - ($($arg:tt)*) => { - if let Err(_) = writeln!(&mut ::std::io::stderr(), $($arg)*) { - die!("Cannot reach stderr"); - } - } + ($($arg:tt)*) => {{ + use std::io::Write; + (writeln!(&mut ::std::io::stderr(), $($arg)*)).expect("stderr"); + }} } diff --git a/src/main.rs b/src/main.rs index ec68ebf6..b699fe44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,14 +19,18 @@ #![feature(io)] #![feature(drop_types_in_const)] #![feature(unicode)] +#![feature(custom_derive, plugin)] +#![plugin(serde_macros)] -extern crate font; -extern crate libc; -extern crate glutin; extern crate cgmath; -extern crate notify; extern crate errno; +extern crate font; +extern crate glutin; +extern crate libc; +extern crate notify; extern crate parking_lot; +extern crate serde; +extern crate serde_yaml; #[macro_use] extern crate bitflags; @@ -37,6 +41,7 @@ mod macros; mod renderer; pub mod grid; mod meter; +pub mod config; mod input; mod tty; pub mod ansi; @@ -49,6 +54,7 @@ use std::sync::{mpsc, Arc}; use parking_lot::Mutex; +use config::Config; use font::FontDesc; use meter::Meter; use renderer::{QuadRenderer, GlyphCache}; @@ -131,18 +137,21 @@ mod gl { include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); } -#[cfg(target_os = "linux")] -static FONT: &'static str = "DejaVu Sans Mono"; -#[cfg(target_os = "linux")] -static FONT_STYLE: &'static str = "Book"; - -#[cfg(target_os = "macos")] -static FONT: &'static str = "Menlo"; -#[cfg(target_os = "macos")] -static FONT_STYLE: &'static str = "Regular"; - - fn main() { + // Load configuration + let config = match Config::load() { + Err(err) => match err { + // Use default config when not found + config::Error::NotFound => Config::default(), + // Exit when there's a problem with it + _ => die!("{}", err), + }, + Ok(config) => config, + }; + + let font = config.font(); + let dpi = config.dpi(); + let render_timer = config.render_timer(); let mut window = glutin::WindowBuilder::new() .with_vsync() @@ -156,17 +165,12 @@ fn main() { println!("device_pixel_ratio: {}", dpr); - let font_size = 11.; + let desc = FontDesc::new(font.family(), font.style()); + let mut rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr); - let sep_x = 0.0; - let sep_y = 0.0; - - let desc = FontDesc::new(FONT, FONT_STYLE); - let mut rasterizer = font::Rasterizer::new(96., 96., dpr); - - let metrics = rasterizer.metrics(&desc, font_size); - let cell_width = (metrics.average_advance + sep_x) as u32; - let cell_height = (metrics.line_height + sep_y) as u32; + let metrics = rasterizer.metrics(&desc, font.size()); + let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32; + let cell_height = (metrics.line_height + font.offset().y() as f64) as u32; println!("Cell Size: ({} x {})", cell_width, cell_height); @@ -175,7 +179,7 @@ fn main() { let reader = terminal.tty().reader(); let writer = terminal.tty().writer(); - let mut glyph_cache = GlyphCache::new(rasterizer, desc, font_size); + let mut glyph_cache = GlyphCache::new(rasterizer, desc, font.size()); let needs_render = Arc::new(AtomicBool::new(true)); let needs_render2 = needs_render.clone(); @@ -322,11 +326,13 @@ fn main() { } // Draw render timer - let timing = format!("{:.3} usec", meter.average()); - let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - renderer.with_api(terminal.size_info(), |mut api| { - api.render_string(&timing[..], &mut glyph_cache, &color); - }); + if render_timer { + let timing = format!("{:.3} usec", meter.average()); + let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; + renderer.with_api(terminal.size_info(), |mut api| { + api.render_string(&timing[..], &mut glyph_cache, &color); + }); + } } window.swap_buffers().unwrap();