| //! 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::borrow::Cow; |
| use std::{env, fmt}; |
| use std::fs::{self, File}; |
| use std::io::{self, Read, Write}; |
| use std::path::{Path, PathBuf}; |
| use std::str::FromStr; |
| use std::sync::mpsc; |
| use std::time::Duration; |
| use std::collections::HashMap; |
| |
| use ::Rgb; |
| use font::Size; |
| use serde_yaml; |
| use serde::{self, de, Deserialize}; |
| use serde::de::Error as SerdeError; |
| use serde::de::{Visitor, MapVisitor, Unexpected}; |
| use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op}; |
| |
| use input::{Action, Binding, MouseBinding, KeyBinding}; |
| use index::{Line, Column}; |
| |
| use util::fmt::Yellow; |
| |
| /// Function that returns true for serde default |
| fn true_bool() -> bool { |
| true |
| } |
| |
| #[derive(Clone, Debug, Deserialize)] |
| pub struct Selection { |
| pub semantic_escape_chars: String, |
| } |
| |
| impl Default for Selection { |
| fn default() -> Selection { |
| Selection { |
| semantic_escape_chars: String::new() |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug, Deserialize)] |
| pub struct ClickHandler { |
| #[serde(deserialize_with="deserialize_duration_ms")] |
| pub threshold: Duration, |
| } |
| |
| fn deserialize_duration_ms<D>(deserializer: D) -> ::std::result::Result<Duration, D::Error> |
| where D: de::Deserializer |
| { |
| let threshold_ms = u64::deserialize(deserializer)?; |
| Ok(Duration::from_millis(threshold_ms)) |
| } |
| |
| |
| #[derive(Clone, Debug, Deserialize)] |
| pub struct Mouse { |
| pub double_click: ClickHandler, |
| pub triple_click: ClickHandler, |
| } |
| |
| impl Default for Mouse { |
| fn default() -> Mouse { |
| Mouse { |
| double_click: ClickHandler { |
| threshold: Duration::from_millis(300), |
| }, |
| triple_click: ClickHandler { |
| threshold: Duration::from_millis(300), |
| } |
| } |
| } |
| } |
| |
| /// VisulBellAnimations are modeled after a subset of CSS transitions and Robert |
| /// Penner's Easing Functions. |
| #[derive(Clone, Copy, Debug, Deserialize)] |
| pub enum VisualBellAnimation { |
| Ease, // CSS |
| EaseOut, // CSS |
| EaseOutSine, // Penner |
| EaseOutQuad, // Penner |
| EaseOutCubic, // Penner |
| EaseOutQuart, // Penner |
| EaseOutQuint, // Penner |
| EaseOutExpo, // Penner |
| EaseOutCirc, // Penner |
| Linear, |
| } |
| |
| #[derive(Debug, Deserialize)] |
| pub struct VisualBellConfig { |
| /// Visual bell animation function |
| #[serde(default="default_visual_bell_animation")] |
| animation: VisualBellAnimation, |
| |
| /// Visual bell duration in milliseconds |
| #[serde(default="default_visual_bell_duration")] |
| duration: u16, |
| } |
| |
| fn default_visual_bell_animation() -> VisualBellAnimation { |
| VisualBellAnimation::EaseOutExpo |
| } |
| |
| fn default_visual_bell_duration() -> u16 { |
| 150 |
| } |
| |
| impl VisualBellConfig { |
| /// Visual bell animation |
| #[inline] |
| pub fn animation(&self) -> VisualBellAnimation { |
| self.animation |
| } |
| |
| /// Visual bell duration in milliseconds |
| #[inline] |
| pub fn duration(&self) -> Duration { |
| Duration::from_millis(self.duration as u64) |
| } |
| } |
| |
| impl Default for VisualBellConfig { |
| fn default() -> VisualBellConfig { |
| VisualBellConfig { |
| animation: default_visual_bell_animation(), |
| duration: default_visual_bell_duration(), |
| } |
| } |
| } |
| |
| #[derive(Debug, Deserialize)] |
| pub struct Shell<'a> { |
| program: Cow<'a, str>, |
| |
| #[serde(default)] |
| args: Vec<String>, |
| } |
| |
| impl<'a> Shell<'a> { |
| pub fn new<S>(program: S) -> Shell<'a> |
| where S: Into<Cow<'a, str>> |
| { |
| Shell { |
| program: program.into(), |
| args: Vec::new(), |
| } |
| } |
| |
| pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a> |
| where S: Into<Cow<'a, str>> |
| { |
| Shell { |
| program: program.into(), |
| args: args |
| } |
| } |
| |
| pub fn program(&self) -> &str { |
| &*self.program |
| } |
| |
| pub fn args(&self) -> &[String] { |
| self.args.as_slice() |
| } |
| } |
| |
| /// Top-level config type |
| #[derive(Debug, Deserialize)] |
| pub struct Config { |
| /// TERM env variable |
| #[serde(default)] |
| env: HashMap<String, String>, |
| |
| /// Initial dimensions |
| #[serde(default)] |
| dimensions: Dimensions, |
| |
| /// Pixel padding |
| #[serde(default="default_padding")] |
| padding: Delta, |
| |
| /// Pixels per inch |
| #[serde(default)] |
| dpi: Dpi, |
| |
| /// Font configuration |
| #[serde(default)] |
| font: Font, |
| |
| /// Should show render timer |
| #[serde(default)] |
| render_timer: bool, |
| |
| /// Should use custom cursor colors |
| #[serde(default)] |
| custom_cursor_colors: bool, |
| |
| /// Should draw bold text with brighter colors intead of bold font |
| #[serde(default="true_bool")] |
| draw_bold_text_with_bright_colors: bool, |
| |
| #[serde(default)] |
| colors: Colors, |
| |
| /// Keybindings |
| #[serde(default="default_key_bindings")] |
| key_bindings: Vec<KeyBinding>, |
| |
| /// Bindings for the mouse |
| #[serde(default="default_mouse_bindings")] |
| mouse_bindings: Vec<MouseBinding>, |
| |
| #[serde(default="default_selection")] |
| selection: Selection, |
| |
| #[serde(default="default_mouse")] |
| mouse: Mouse, |
| |
| /// Path to a shell program to run on startup |
| #[serde(default)] |
| shell: Option<Shell<'static>>, |
| |
| /// Path where config was loaded from |
| config_path: Option<PathBuf>, |
| |
| /// Visual bell configuration |
| #[serde(default)] |
| visual_bell: VisualBellConfig, |
| |
| /// Hide cursor when typing |
| #[serde(default)] |
| hide_cursor_when_typing: bool, |
| } |
| |
| fn default_padding() -> Delta { |
| Delta { x: 2., y: 2. } |
| } |
| |
| #[cfg(not(target_os="macos"))] |
| static DEFAULT_CONFIG: &'static str = include_str!("../alacritty.yml"); |
| #[cfg(target_os="macos")] |
| static DEFAULT_CONFIG: &'static str = include_str!("../alacritty_macos.yml"); |
| |
| fn default_config() -> Config { |
| serde_yaml::from_str(DEFAULT_CONFIG) |
| .expect("default config is valid") |
| } |
| |
| fn default_selection() -> Selection { |
| default_config().selection |
| } |
| |
| fn default_key_bindings() -> Vec<KeyBinding> { |
| default_config().key_bindings |
| } |
| |
| fn default_mouse_bindings() -> Vec<MouseBinding> { |
| default_config().mouse_bindings |
| } |
| |
| fn default_mouse() -> Mouse { |
| default_config().mouse |
| } |
| |
| impl Default for Config { |
| fn default() -> Config { |
| Config { |
| draw_bold_text_with_bright_colors: true, |
| dimensions: Default::default(), |
| dpi: Default::default(), |
| font: Default::default(), |
| render_timer: Default::default(), |
| custom_cursor_colors: false, |
| colors: Default::default(), |
| key_bindings: Vec::new(), |
| mouse_bindings: Vec::new(), |
| selection: Default::default(), |
| mouse: Default::default(), |
| shell: None, |
| config_path: None, |
| visual_bell: Default::default(), |
| env: Default::default(), |
| hide_cursor_when_typing: Default::default(), |
| padding: default_padding(), |
| } |
| } |
| } |
| |
| /// Newtype for implementing deserialize on glutin Mods |
| /// |
| /// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the |
| /// impl below. |
| struct ModsWrapper(::glutin::Mods); |
| |
| impl ModsWrapper { |
| fn into_inner(self) -> ::glutin::Mods { |
| self.0 |
| } |
| } |
| |
| impl de::Deserialize for ModsWrapper { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: de::Deserializer |
| { |
| struct ModsVisitor; |
| |
| impl Visitor for ModsVisitor { |
| type Value = ModsWrapper; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") |
| } |
| |
| fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E> |
| where E: de::Error, |
| { |
| use ::glutin::{mods, Mods}; |
| let mut res = Mods::empty(); |
| for modifier in value.split('|') { |
| match modifier.trim() { |
| "Command" | "Super" => res |= mods::SUPER, |
| "Shift" => res |= mods::SHIFT, |
| "Alt" | "Option" => res |= mods::ALT, |
| "Control" => res |= mods::CONTROL, |
| _ => err_println!("unknown modifier {:?}", modifier), |
| } |
| } |
| |
| Ok(ModsWrapper(res)) |
| } |
| } |
| |
| deserializer.deserialize_str(ModsVisitor) |
| } |
| } |
| |
| struct ActionWrapper(::input::Action); |
| |
| impl ActionWrapper { |
| fn into_inner(self) -> ::input::Action { |
| self.0 |
| } |
| } |
| |
| impl de::Deserialize for ActionWrapper { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: de::Deserializer |
| { |
| struct ActionVisitor; |
| |
| impl Visitor for ActionVisitor { |
| type Value = ActionWrapper; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("Paste, Copy, PasteSelection, or Quit") |
| } |
| |
| fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E> |
| where E: de::Error, |
| { |
| Ok(ActionWrapper(match value { |
| "Paste" => Action::Paste, |
| "Copy" => Action::Copy, |
| "PasteSelection" => Action::PasteSelection, |
| "Quit" => Action::Quit, |
| _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), |
| })) |
| } |
| } |
| deserializer.deserialize_str(ActionVisitor) |
| } |
| } |
| |
| #[derive(Debug, Deserialize)] |
| #[serde(untagged)] |
| enum CommandWrapper { |
| Just(String), |
| WithArgs { |
| program: String, |
| #[serde(default)] |
| args: Vec<String>, |
| }, |
| } |
| |
| use ::term::{mode, TermMode}; |
| |
| struct ModeWrapper { |
| pub mode: TermMode, |
| pub not_mode: TermMode, |
| } |
| |
| impl de::Deserialize for ModeWrapper { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: de::Deserializer |
| { |
| struct ModeVisitor; |
| |
| impl Visitor for ModeVisitor { |
| type Value = ModeWrapper; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") |
| } |
| |
| fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E> |
| where E: de::Error, |
| { |
| let mut res = ModeWrapper { |
| mode: TermMode::empty(), |
| not_mode: TermMode::empty() |
| }; |
| |
| for modifier in value.split('|') { |
| match modifier.trim() { |
| "AppCursor" => res.mode |= mode::APP_CURSOR, |
| "~AppCursor" => res.not_mode |= mode::APP_CURSOR, |
| "AppKeypad" => res.mode |= mode::APP_KEYPAD, |
| "~AppKeypad" => res.not_mode |= mode::APP_KEYPAD, |
| _ => err_println!("unknown omde {:?}", modifier), |
| } |
| } |
| |
| Ok(res) |
| } |
| } |
| deserializer.deserialize_str(ModeVisitor) |
| } |
| } |
| |
| struct MouseButton(::glutin::MouseButton); |
| |
| impl MouseButton { |
| fn into_inner(self) -> ::glutin::MouseButton { |
| self.0 |
| } |
| } |
| |
| impl de::Deserialize for MouseButton { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: de::Deserializer |
| { |
| struct MouseButtonVisitor; |
| |
| impl Visitor for MouseButtonVisitor { |
| type Value = MouseButton; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("Left, Right, Middle, or a number") |
| } |
| |
| fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E> |
| where E: de::Error, |
| { |
| match value { |
| "Left" => Ok(MouseButton(::glutin::MouseButton::Left)), |
| "Right" => Ok(MouseButton(::glutin::MouseButton::Right)), |
| "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)), |
| _ => { |
| if let Ok(index) = u8::from_str(value) { |
| Ok(MouseButton(::glutin::MouseButton::Other(index))) |
| } else { |
| Err(E::invalid_value(Unexpected::Str(value), &self)) |
| } |
| } |
| } |
| } |
| } |
| |
| deserializer.deserialize_str(MouseButtonVisitor) |
| } |
| } |
| |
| /// Bindings are deserialized into a `RawBinding` before being parsed as a |
| /// `KeyBinding` or `MouseBinding`. |
| struct RawBinding { |
| key: Option<::glutin::VirtualKeyCode>, |
| mouse: Option<::glutin::MouseButton>, |
| mods: ::glutin::Mods, |
| mode: TermMode, |
| notmode: TermMode, |
| action: Action, |
| } |
| |
| impl RawBinding { |
| fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { |
| if self.mouse.is_some() { |
| Ok(Binding { |
| trigger: self.mouse.unwrap(), |
| mods: self.mods, |
| action: self.action, |
| mode: self.mode, |
| notmode: self.notmode, |
| }) |
| } else { |
| Err(self) |
| } |
| } |
| |
| fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> { |
| if self.key.is_some() { |
| Ok(KeyBinding { |
| trigger: self.key.unwrap(), |
| mods: self.mods, |
| action: self.action, |
| mode: self.mode, |
| notmode: self.notmode, |
| }) |
| } else { |
| Err(self) |
| } |
| } |
| } |
| |
| impl de::Deserialize for RawBinding { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: de::Deserializer |
| { |
| enum Field { |
| Key, |
| Mods, |
| Mode, |
| Action, |
| Chars, |
| Mouse, |
| Command, |
| } |
| |
| impl de::Deserialize for Field { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error> |
| where D: de::Deserializer |
| { |
| struct FieldVisitor; |
| |
| static FIELDS: &'static [&'static str] = &[ |
| "key", "mods", "mode", "action", "chars", "mouse", "command", |
| ]; |
| |
| impl Visitor for FieldVisitor { |
| type Value = Field; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("binding fields") |
| } |
| |
| fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E> |
| where E: de::Error, |
| { |
| match value { |
| "key" => Ok(Field::Key), |
| "mods" => Ok(Field::Mods), |
| "mode" => Ok(Field::Mode), |
| "action" => Ok(Field::Action), |
| "chars" => Ok(Field::Chars), |
| "mouse" => Ok(Field::Mouse), |
| "command" => Ok(Field::Command), |
| _ => Err(E::unknown_field(value, FIELDS)), |
| } |
| } |
| } |
| |
| deserializer.deserialize_struct_field(FieldVisitor) |
| } |
| } |
| |
| struct RawBindingVisitor; |
| impl Visitor for RawBindingVisitor { |
| type Value = RawBinding; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("binding specification") |
| } |
| |
| fn visit_map<V>( |
| self, |
| mut visitor: V |
| ) -> ::std::result::Result<RawBinding, V::Error> |
| where V: MapVisitor, |
| { |
| let mut mods: Option<::glutin::Mods> = None; |
| let mut key: Option<::glutin::VirtualKeyCode> = None; |
| let mut chars: Option<String> = None; |
| let mut action: Option<::input::Action> = None; |
| let mut mode: Option<TermMode> = None; |
| let mut not_mode: Option<TermMode> = None; |
| let mut mouse: Option<::glutin::MouseButton> = None; |
| let mut command: Option<CommandWrapper> = None; |
| |
| use ::serde::de::Error; |
| |
| while let Some(struct_key) = visitor.visit_key::<Field>()? { |
| match struct_key { |
| Field::Key => { |
| if key.is_some() { |
| return Err(<V::Error as Error>::duplicate_field("key")); |
| } |
| |
| let coherent_key = visitor.visit_value::<Key>()?; |
| key = Some(coherent_key.to_glutin_key()); |
| }, |
| Field::Mods => { |
| if mods.is_some() { |
| return Err(<V::Error as Error>::duplicate_field("mods")); |
| } |
| |
| mods = Some(visitor.visit_value::<ModsWrapper>()?.into_inner()); |
| }, |
| Field::Mode => { |
| if mode.is_some() { |
| return Err(<V::Error as Error>::duplicate_field("mode")); |
| } |
| |
| let mode_deserializer = visitor.visit_value::<ModeWrapper>()?; |
| mode = Some(mode_deserializer.mode); |
| not_mode = Some(mode_deserializer.not_mode); |
| }, |
| Field::Action => { |
| if action.is_some() { |
| return Err(<V::Error as Error>::duplicate_field("action")); |
| } |
| |
| action = Some(visitor.visit_value::<ActionWrapper>()?.into_inner()); |
| }, |
| Field::Chars => { |
| if chars.is_some() { |
| return Err(<V::Error as Error>::duplicate_field("chars")); |
| } |
| |
| chars = Some(visitor.visit_value()?); |
| }, |
| Field::Mouse => { |
| if chars.is_some() { |
| return Err(<V::Error as Error>::duplicate_field("mouse")); |
| } |
| |
| mouse = Some(visitor.visit_value::<MouseButton>()?.into_inner()); |
| }, |
| Field::Command => { |
| if command.is_some() { |
| return Err(<V::Error as Error>::duplicate_field("command")); |
| } |
| |
| command = Some(visitor.visit_value::<CommandWrapper>()?); |
| }, |
| } |
| } |
| |
| let action = match (action, chars, command) { |
| (Some(action), None, None) => action, |
| (None, Some(chars), None) => Action::Esc(chars), |
| (None, None, Some(cmd)) => { |
| match cmd { |
| CommandWrapper::Just(program) => { |
| Action::Command(program, vec![]) |
| }, |
| CommandWrapper::WithArgs { program, args } => { |
| Action::Command(program, args) |
| }, |
| } |
| }, |
| (None, None, None) => return Err(V::Error::custom("must specify chars, action or command")), |
| _ => return Err(V::Error::custom("must specify only chars, action or command")), |
| }; |
| |
| let mode = mode.unwrap_or_else(TermMode::empty); |
| let not_mode = not_mode.unwrap_or_else(TermMode::empty); |
| let mods = mods.unwrap_or_else(::glutin::Mods::empty); |
| |
| if mouse.is_none() && key.is_none() { |
| return Err(V::Error::custom("bindings require mouse button or key")); |
| } |
| |
| Ok(RawBinding { |
| mode: mode, |
| notmode: not_mode, |
| action: action, |
| key: key, |
| mouse: mouse, |
| mods: mods, |
| }) |
| } |
| } |
| |
| const FIELDS: &'static [&'static str] = &[ |
| "key", "mods", "mode", "action", "chars", "mouse", "command", |
| ]; |
| |
| deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) |
| } |
| } |
| |
| impl de::Deserialize for MouseBinding { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: de::Deserializer |
| { |
| let raw = RawBinding::deserialize(deserializer)?; |
| raw.into_mouse_binding() |
| .map_err(|_| D::Error::custom("expected mouse binding")) |
| } |
| } |
| |
| impl de::Deserialize for KeyBinding { |
| fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: de::Deserializer |
| { |
| let raw = RawBinding::deserialize(deserializer)?; |
| raw.into_key_binding() |
| .map_err(|_| D::Error::custom("expected key binding")) |
| } |
| } |
| |
| /// Errors occurring during config loading |
| #[derive(Debug)] |
| pub enum Error { |
| /// Config file not found |
| NotFound, |
| |
| /// Config file empty |
| Empty, |
| |
| /// 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), |
| } |
| |
| #[derive(Debug, Deserialize)] |
| pub struct Colors { |
| pub primary: PrimaryColors, |
| #[serde(deserialize_with="deserialize_cursor_colors", default="default_cursor_colors")] |
| pub cursor: CursorColors, |
| pub normal: AnsiColors, |
| pub bright: AnsiColors, |
| pub dim: Option<AnsiColors>, |
| } |
| |
| fn deserialize_cursor_colors<D>(deserializer: D) -> ::std::result::Result<CursorColors, D::Error> |
| where D: de::Deserializer |
| { |
| let either = CursorOrPrimaryColors::deserialize(deserializer)?; |
| Ok(either.into_cursor_colors()) |
| } |
| |
| #[derive(Deserialize)] |
| #[serde(untagged)] |
| pub enum CursorOrPrimaryColors { |
| Cursor { |
| #[serde(deserialize_with = "rgb_from_hex")] |
| text: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| cursor: Rgb, |
| }, |
| Primary { |
| #[serde(deserialize_with = "rgb_from_hex")] |
| foreground: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| background: Rgb, |
| } |
| } |
| |
| impl CursorOrPrimaryColors { |
| fn into_cursor_colors(self) -> CursorColors { |
| match self { |
| CursorOrPrimaryColors::Cursor { text, cursor } => CursorColors { |
| text: text, |
| cursor: cursor |
| }, |
| CursorOrPrimaryColors::Primary { foreground, background } => { |
| // Must print in config since logger isn't setup yet. |
| println!("{}", |
| Yellow("You're using a deprecated form of cursor color config. Please update \ |
| your config to use `text` and `cursor` properties instead of `foreground` \ |
| and `background`. This will become an error in a future release.") |
| ); |
| CursorColors { |
| text: foreground, |
| cursor: background |
| } |
| } |
| } |
| } |
| } |
| |
| fn default_cursor_colors() -> CursorColors { |
| CursorColors { |
| text: Rgb { r: 0, g: 0, b: 0 }, |
| cursor: Rgb { r: 0xff, g: 0xff, b: 0xff }, |
| } |
| } |
| |
| #[derive(Debug)] |
| pub struct CursorColors { |
| pub text: Rgb, |
| pub cursor: Rgb, |
| } |
| |
| #[derive(Debug, Deserialize)] |
| pub struct PrimaryColors { |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub background: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub foreground: Rgb, |
| } |
| |
| impl Default for Colors { |
| fn default() -> Colors { |
| Colors { |
| primary: PrimaryColors { |
| background: Rgb { r: 0, g: 0, b: 0 }, |
| foreground: Rgb { r: 0xea, g: 0xea, b: 0xea }, |
| }, |
| cursor: default_cursor_colors(), |
| normal: AnsiColors { |
| black: Rgb {r: 0x00, g: 0x00, b: 0x00}, |
| red: Rgb {r: 0xd5, g: 0x4e, b: 0x53}, |
| green: Rgb {r: 0xb9, g: 0xca, b: 0x4a}, |
| yellow: Rgb {r: 0xe6, g: 0xc5, b: 0x47}, |
| blue: Rgb {r: 0x7a, g: 0xa6, b: 0xda}, |
| magenta: Rgb {r: 0xc3, g: 0x97, b: 0xd8}, |
| cyan: Rgb {r: 0x70, g: 0xc0, b: 0xba}, |
| white: Rgb {r: 0xea, g: 0xea, b: 0xea}, |
| }, |
| bright: AnsiColors { |
| black: Rgb {r: 0x66, g: 0x66, b: 0x66}, |
| red: Rgb {r: 0xff, g: 0x33, b: 0x34}, |
| green: Rgb {r: 0x9e, g: 0xc4, b: 0x00}, |
| yellow: Rgb {r: 0xe7, g: 0xc5, b: 0x47}, |
| blue: Rgb {r: 0x7a, g: 0xa6, b: 0xda}, |
| magenta: Rgb {r: 0xb7, g: 0x7e, b: 0xe0}, |
| cyan: Rgb {r: 0x54, g: 0xce, b: 0xd6}, |
| white: Rgb {r: 0xff, g: 0xff, b: 0xff}, |
| }, |
| dim: None, |
| } |
| } |
| } |
| |
| /// The 8-colors sections of config |
| #[derive(Debug, Deserialize)] |
| pub struct AnsiColors { |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub black: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub red: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub green: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub yellow: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub blue: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub magenta: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub cyan: Rgb, |
| #[serde(deserialize_with = "rgb_from_hex")] |
| pub white: Rgb, |
| } |
| |
| /// Deserialize an Rgb from a hex string |
| /// |
| /// This is *not* the deserialize impl for Rgb since we want a symmetric |
| /// serialize/deserialize impl for ref tests. |
| fn rgb_from_hex<D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error> |
| where D: de::Deserializer |
| { |
| struct RgbVisitor; |
| |
| impl ::serde::de::Visitor for RgbVisitor { |
| type Value = Rgb; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("Hex colors spec like 'ffaabb'") |
| } |
| |
| fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E> |
| where E: ::serde::de::Error |
| { |
| Rgb::from_str(&value[..]) |
| .map_err(|_| E::custom("failed to parse rgb; expect 0xrrggbb")) |
| } |
| } |
| |
| deserializer.deserialize_str(RgbVisitor) |
| } |
| |
| impl FromStr for Rgb { |
| type Err = (); |
| fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> { |
| let mut chars = s.chars(); |
| let mut rgb = Rgb::default(); |
| |
| macro_rules! component { |
| ($($c:ident),*) => { |
| $( |
| match chars.next().unwrap().to_digit(16) { |
| Some(val) => rgb.$c = (val as u8) << 4, |
| None => return Err(()) |
| } |
| |
| match chars.next().unwrap().to_digit(16) { |
| Some(val) => rgb.$c |= val as u8, |
| None => return Err(()) |
| } |
| )* |
| } |
| } |
| |
| if chars.next().unwrap() != '0' { return Err(()); } |
| if chars.next().unwrap() != 'x' { return Err(()); } |
| |
| component!(r, g, b); |
| |
| Ok(rgb) |
| } |
| } |
| |
| impl ::std::error::Error for Error { |
| fn cause(&self) -> Option<&::std::error::Error> { |
| match *self { |
| Error::NotFound => None, |
| Error::Empty => 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::Empty => "empty 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::Empty => 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<env::VarError> for Error { |
| fn from(val: env::VarError) -> Error { |
| Error::ReadingEnvHome(val) |
| } |
| } |
| |
| impl From<io::Error> for Error { |
| fn from(val: io::Error) -> Error { |
| if val.kind() == io::ErrorKind::NotFound { |
| Error::NotFound |
| } else { |
| Error::Io(val) |
| } |
| } |
| } |
| |
| impl From<serde_yaml::Error> for Error { |
| fn from(val: serde_yaml::Error) -> Error { |
| Error::Yaml(val) |
| } |
| } |
| |
| /// Result from config loading |
| pub type Result<T> = ::std::result::Result<T, Error>; |
| |
| impl Config { |
| /// Get the location of the first found default config file paths |
| /// according to the following order: |
| /// |
| /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml |
| /// 2. $XDG_CONFIG_HOME/alacritty.yml |
| /// 3. $HOME/.config/alacritty/alacritty.yml |
| /// 4. $HOME/.alacritty.yml |
| pub fn existing_path() -> Option<Cow<'static, Path>> { |
| // Try using XDG location by default |
| ::xdg::BaseDirectories::with_prefix("alacritty") |
| .ok() |
| // $XDG_CONFIG_HOME/alacritty/alacritty.yml |
| .and_then(|xdg| xdg.find_config_file("alacritty.yml")) |
| // $XDG_CONFIG_HOME/alacritty.yml |
| .or_else(|| { |
| ::xdg::BaseDirectories::new().ok().and_then(|fallback| { |
| fallback.find_config_file("alacritty.yml") |
| }) |
| }) |
| .or_else(|| { |
| if let Ok(home) = env::var("HOME") { |
| // Fallback path: $HOME/.config/alacritty/alacritty.yml |
| let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); |
| if fallback.exists() { |
| return Some(fallback); |
| } |
| // Fallback path: $HOME/.alacritty.yml |
| let fallback = PathBuf::from(&home).join(".alacritty.yml"); |
| if fallback.exists() { |
| return Some(fallback); |
| } |
| } |
| None |
| }) |
| .map(|path| path.into()) |
| } |
| |
| pub fn write_defaults() -> io::Result<Cow<'static, Path>> { |
| let path = ::xdg::BaseDirectories::with_prefix("alacritty") |
| .map_err(|err| io::Error::new(io::ErrorKind::NotFound, Box::new(err))) |
| .and_then(|p| p.place_config_file("alacritty.yml"))?; |
| |
| File::create(&path)? |
| .write_all(DEFAULT_CONFIG.as_bytes())?; |
| warn!("Wrote default config to {}", path.display()); |
| |
| Ok(path.into()) |
| } |
| |
| /// Get list of colors |
| /// |
| /// The ordering returned here is expected by the terminal. Colors are simply indexed in this |
| /// array for performance. |
| pub fn colors(&self) -> &Colors { |
| &self.colors |
| } |
| |
| pub fn key_bindings(&self) -> &[KeyBinding] { |
| &self.key_bindings[..] |
| } |
| |
| pub fn mouse_bindings(&self) -> &[MouseBinding] { |
| &self.mouse_bindings[..] |
| } |
| |
| pub fn mouse(&self) -> &Mouse { |
| &self.mouse |
| } |
| |
| pub fn selection(&self) -> &Selection { |
| &self.selection |
| } |
| |
| pub fn padding(&self) -> &Delta { |
| &self.padding |
| } |
| |
| #[inline] |
| pub fn draw_bold_text_with_bright_colors(&self) -> bool { |
| self.draw_bold_text_with_bright_colors |
| } |
| |
| /// Get font config |
| #[inline] |
| pub fn font(&self) -> &Font { |
| &self.font |
| } |
| |
| /// Get window dimensions |
| #[inline] |
| pub fn dimensions(&self) -> Dimensions { |
| self.dimensions |
| } |
| |
| /// Get dpi config |
| #[inline] |
| pub fn dpi(&self) -> &Dpi { |
| &self.dpi |
| } |
| |
| /// Get visual bell config |
| #[inline] |
| pub fn visual_bell(&self) -> &VisualBellConfig { |
| &self.visual_bell |
| } |
| |
| /// Should show render timer |
| #[inline] |
| pub fn render_timer(&self) -> bool { |
| self.render_timer |
| } |
| |
| #[inline] |
| pub fn use_thin_strokes(&self) -> bool { |
| self.font.use_thin_strokes |
| } |
| |
| /// show cursor as inverted |
| #[inline] |
| pub fn custom_cursor_colors(&self) -> bool { |
| self.custom_cursor_colors |
| } |
| |
| pub fn path(&self) -> Option<&Path> { |
| self.config_path |
| .as_ref() |
| .map(|p| p.as_path()) |
| } |
| |
| pub fn shell(&self) -> Option<&Shell> { |
| self.shell.as_ref() |
| } |
| |
| pub fn env(&self) -> &HashMap<String, String> { |
| &self.env |
| } |
| |
| /// Should hide cursor when typing |
| #[inline] |
| pub fn hide_cursor_when_typing(&self) -> bool { |
| self.hide_cursor_when_typing |
| } |
| |
| /// Load configuration |
| /// |
| /// If a configuration file is given as a command line argument we don't |
| /// generate a default file. If an empty configuration file is given, i.e. |
| /// /dev/null, we load the compiled-in defaults. |
| pub fn load(options: &::cli::Options) -> Config { |
| // Get config path |
| let config_path = options.config_path() |
| .or_else(|| Config::existing_path()) |
| .unwrap_or_else(|| { |
| Config::write_defaults() |
| .unwrap_or_else(|err| die!("Write defaults config failure: {}", err)) |
| }); |
| |
| // Load config from path |
| Config::load_from(&*config_path) |
| .map(|config| { |
| if let Some(path) = config.path().as_ref() { |
| info!("Config loaded from {}", path.display()); |
| } |
| |
| config |
| }) |
| .unwrap_or_else(|err| { |
| use self::Error::*; |
| match err { |
| NotFound | Empty => { |
| die!("Config file empty or not found at: {}", config_path.display()); |
| }, |
| _ => die!("{}", err), |
| } |
| }) |
| } |
| |
| fn load_from<P: Into<PathBuf>>(path: P) -> Result<Config> { |
| let path = path.into(); |
| let raw = Config::read_file(path.as_path())?; |
| let mut config: Config = serde_yaml::from_str(&raw)?; |
| config.config_path = Some(path); |
| |
| Ok(config) |
| } |
| |
| fn read_file<P: AsRef<Path>>(path: P) -> Result<String> { |
| let mut f = fs::File::open(path)?; |
| let mut contents = String::new(); |
| f.read_to_string(&mut contents)?; |
| if contents.len() == 0 { |
| return Err(Error::Empty); |
| } |
| |
| Ok(contents) |
| } |
| } |
| |
| /// Window Dimensions |
| /// |
| /// Newtype to avoid passing values incorrectly |
| #[derive(Debug, Copy, Clone, Deserialize)] |
| pub struct Dimensions { |
| /// Window width in character columns |
| columns: Column, |
| |
| /// Window Height in character lines |
| lines: Line, |
| } |
| |
| impl Default for Dimensions { |
| fn default() -> Dimensions { |
| Dimensions::new(Column(80), Line(24)) |
| } |
| } |
| |
| impl Dimensions { |
| pub fn new(columns: Column, lines: Line) -> Self { |
| Dimensions { |
| columns: columns, |
| lines: 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 |
| } |
| } |
| |
| /// 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 |
| } |
| } |
| |
| /// A delta for a point in a 2 dimensional plane |
| #[derive(Clone, Copy, Debug, Deserialize)] |
| pub struct Delta { |
| /// Horizontal change |
| pub x: f32, |
| /// Vertical change |
| pub y: f32, |
| } |
| |
| impl Default for Delta { |
| fn default() -> Delta { |
| Delta { x: 0.0, y: 0.0 } |
| } |
| } |
| |
| trait DeserializeFromF32 : Sized { |
| fn deserialize_from_f32<D>(D) -> ::std::result::Result<Self, D::Error> |
| where D: serde::de::Deserializer; |
| } |
| |
| impl DeserializeFromF32 for Size { |
| fn deserialize_from_f32<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> |
| where D: serde::de::Deserializer |
| { |
| use std::marker::PhantomData; |
| |
| struct FloatVisitor<__D> { |
| _marker: PhantomData<__D>, |
| } |
| |
| impl<__D> ::serde::de::Visitor for FloatVisitor<__D> |
| where __D: ::serde::de::Deserializer |
| { |
| type Value = f64; |
| |
| fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str("f64") |
| } |
| |
| fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E> |
| where E: ::serde::de::Error |
| { |
| Ok(value) |
| } |
| } |
| |
| deserializer |
| .deserialize_f64(FloatVisitor::<D>{ _marker: PhantomData }) |
| .map(|v| Size::new(v as _)) |
| } |
| } |
| |
| /// 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 |
| pub normal: FontDescription, |
| |
| #[serde(default="default_italic_desc")] |
| pub italic: FontDescription, |
| |
| #[serde(default="default_bold_desc")] |
| pub bold: FontDescription, |
| |
| // Font size in points |
| #[serde(deserialize_with="DeserializeFromF32::deserialize_from_f32")] |
| size: Size, |
| |
| /// Extra spacing per character |
| offset: Delta, |
| |
| /// Glyph offset within character cell |
| #[serde(default)] |
| glyph_offset: Delta, |
| |
| #[serde(default="true_bool")] |
| use_thin_strokes: bool |
| } |
| |
| fn default_bold_desc() -> FontDescription { |
| Font::default().bold |
| } |
| |
| fn default_italic_desc() -> FontDescription { |
| Font::default().italic |
| } |
| |
| /// Description of a single font |
| #[derive(Debug, Deserialize)] |
| pub struct FontDescription { |
| pub family: String, |
| pub style: Option<String>, |
| } |
| |
| impl FontDescription { |
| fn new_with_family<S: Into<String>>(family: S) -> FontDescription { |
| FontDescription { |
| family: family.into(), |
| style: None, |
| } |
| } |
| } |
| |
| impl Font { |
| /// Get the font size in points |
| #[inline] |
| pub fn size(&self) -> Size { |
| self.size |
| } |
| |
| /// Get offsets to font metrics |
| #[inline] |
| pub fn offset(&self) -> &Delta { |
| &self.offset |
| } |
| |
| /// Get cell offsets for glyphs |
| #[inline] |
| pub fn glyph_offset(&self) -> &Delta { |
| &self.glyph_offset |
| } |
| } |
| |
| #[cfg(target_os = "macos")] |
| impl Default for Font { |
| fn default() -> Font { |
| Font { |
| normal: FontDescription::new_with_family("Menlo"), |
| bold: FontDescription::new_with_family("Menlo"), |
| italic: FontDescription::new_with_family("Menlo"), |
| size: Size::new(11.0), |
| use_thin_strokes: true, |
| offset: Default::default(), |
| glyph_offset: Default::default() |
| } |
| } |
| } |
| |
| #[cfg(any(target_os = "linux",target_os = "freebsd"))] |
| impl Default for Font { |
| fn default() -> Font { |
| Font { |
| normal: FontDescription::new_with_family("monospace"), |
| bold: FontDescription::new_with_family("monospace"), |
| italic: FontDescription::new_with_family("monospace"), |
| size: Size::new(11.0), |
| use_thin_strokes: false, |
| offset: Default::default(), |
| glyph_offset: Default::default() |
| } |
| } |
| } |
| |
| pub struct Monitor { |
| _thread: ::std::thread::JoinHandle<()>, |
| rx: mpsc::Receiver<Config>, |
| } |
| |
| pub trait OnConfigReload { |
| fn on_config_reload(&mut self); |
| } |
| |
| impl OnConfigReload for ::display::Notifier { |
| fn on_config_reload(&mut self) { |
| self.notify(); |
| } |
| } |
| |
| impl Monitor { |
| /// Get pending config changes |
| pub fn pending_config(&self) -> Option<Config> { |
| let mut config = None; |
| while let Ok(new) = self.rx.try_recv() { |
| config = Some(new); |
| } |
| |
| config |
| } |
| pub fn new<H, P>(path: P, mut handler: H) -> Monitor |
| where H: OnConfigReload + Send + 'static, |
| P: Into<PathBuf> |
| { |
| let path = path.into(); |
| |
| let (config_tx, config_rx) = mpsc::channel(); |
| |
| Monitor { |
| _thread: ::util::thread::spawn_named("config watcher", move || { |
| let (tx, rx) = mpsc::channel(); |
| let mut watcher = FileWatcher::new(tx).unwrap(); |
| watcher.watch(&path).expect("watch alacritty yml"); |
| |
| let config_path = path.as_path(); |
| |
| loop { |
| let event = rx.recv().expect("watcher event"); |
| let ::notify::Event { path, op } = event; |
| |
| if let Ok(op) = op { |
| // Skip events that are just a rename |
| if op.contains(op::RENAME) && !op.contains(op::WRITE) { |
| continue; |
| } |
| |
| // Need to handle ignore for linux |
| if op.contains(op::IGNORED) { |
| if let Some(path) = path.as_ref() { |
| if let Err(err) = watcher.watch(&path) { |
| err_println!("failed to establish watch on {:?}: {:?}", |
| path, err); |
| } |
| } |
| } |
| |
| // Reload file |
| path.map(|path| { |
| if path == config_path { |
| match Config::load_from(path) { |
| Ok(config) => { |
| let _ = config_tx.send(config); |
| handler.on_config_reload(); |
| }, |
| Err(err) => err_println!("Ignoring invalid config: {}", err), |
| } |
| } |
| }); |
| } |
| } |
| }), |
| rx: config_rx, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::Config; |
| |
| static ALACRITTY_YML: &'static str = |
| include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); |
| |
| #[test] |
| fn parse_config() { |
| let config: Config = ::serde_yaml::from_str(ALACRITTY_YML) |
| .expect("deserialize config"); |
| |
| // Sanity check that mouse bindings are being parsed |
| assert!(config.mouse_bindings.len() >= 1); |
| |
| // Sanity check that key bindings are being parsed |
| assert!(config.key_bindings.len() >= 1); |
| } |
| |
| #[test] |
| fn defaults_are_ok() { |
| super::default_key_bindings(); |
| super::default_mouse_bindings(); |
| } |
| } |
| |
| #[cfg_attr(feature = "clippy", allow(enum_variant_names))] |
| #[derive(Deserialize, Copy, Clone)] |
| enum Key { |
| Key1, |
| Key2, |
| Key3, |
| Key4, |
| Key5, |
| Key6, |
| Key7, |
| Key8, |
| Key9, |
| Key0, |
| |
| A, |
| B, |
| C, |
| D, |
| E, |
| F, |
| G, |
| H, |
| I, |
| J, |
| K, |
| L, |
| M, |
| N, |
| O, |
| P, |
| Q, |
| R, |
| S, |
| T, |
| U, |
| V, |
| W, |
| X, |
| Y, |
| Z, |
| |
| Escape, |
| |
| F1, |
| F2, |
| F3, |
| F4, |
| F5, |
| F6, |
| F7, |
| F8, |
| F9, |
| F10, |
| F11, |
| F12, |
| F13, |
| F14, |
| F15, |
| |
| Snapshot, |
| Scroll, |
| Pause, |
| Insert, |
| Home, |
| Delete, |
| End, |
| PageDown, |
| PageUp, |
| |
| Left, |
| Up, |
| Right, |
| Down, |
| Back, |
| Return, |
| Space, |
| Compose, |
| Numlock, |
| Numpad0, |
| Numpad1, |
| Numpad2, |
| Numpad3, |
| Numpad4, |
| Numpad5, |
| Numpad6, |
| Numpad7, |
| Numpad8, |
| Numpad9, |
| |
| AbntC1, |
| AbntC2, |
| Add, |
| Apostrophe, |
| Apps, |
| At, |
| Ax, |
| Backslash, |
| Calculator, |
| Capital, |
| Colon, |
| Comma, |
| Convert, |
| Decimal, |
| Divide, |
| Equals, |
| Grave, |
| Kana, |
| Kanji, |
| LAlt, |
| LBracket, |
| LControl, |
| LMenu, |
| LShift, |
| LWin, |
| Mail, |
| MediaSelect, |
| MediaStop, |
| Minus, |
| Multiply, |
| Mute, |
| MyComputer, |
| NavigateForward, |
| NavigateBackward, |
| NextTrack, |
| NoConvert, |
| NumpadComma, |
| NumpadEnter, |
| NumpadEquals, |
| OEM102, |
| Period, |
| PlayPause, |
| Power, |
| PrevTrack, |
| RAlt, |
| RBracket, |
| RControl, |
| RMenu, |
| RShift, |
| RWin, |
| Semicolon, |
| Slash, |
| Sleep, |
| Stop, |
| Subtract, |
| Sysrq, |
| Tab, |
| Underline, |
| Unlabeled, |
| VolumeDown, |
| VolumeUp, |
| Wake, |
| WebBack, |
| WebFavorites, |
| WebForward, |
| WebHome, |
| WebRefresh, |
| WebSearch, |
| WebStop, |
| Yen, |
| } |
| |
| impl Key { |
| fn to_glutin_key(&self) -> ::glutin::VirtualKeyCode { |
| // Thank you, vim macros! |
| match *self { |
| Key::Key1 => ::glutin::VirtualKeyCode::Key1, |
| Key::Key2 => ::glutin::VirtualKeyCode::Key2, |
| Key::Key3 => ::glutin::VirtualKeyCode::Key3, |
| Key::Key4 => ::glutin::VirtualKeyCode::Key4, |
| Key::Key5 => ::glutin::VirtualKeyCode::Key5, |
| Key::Key6 => ::glutin::VirtualKeyCode::Key6, |
| Key::Key7 => ::glutin::VirtualKeyCode::Key7, |
| Key::Key8 => ::glutin::VirtualKeyCode::Key8, |
| Key::Key9 => ::glutin::VirtualKeyCode::Key9, |
| Key::Key0 => ::glutin::VirtualKeyCode::Key0, |
| Key::A => ::glutin::VirtualKeyCode::A, |
| Key::B => ::glutin::VirtualKeyCode::B, |
| Key::C => ::glutin::VirtualKeyCode::C, |
| Key::D => ::glutin::VirtualKeyCode::D, |
| Key::E => ::glutin::VirtualKeyCode::E, |
| Key::F => ::glutin::VirtualKeyCode::F, |
| Key::G => ::glutin::VirtualKeyCode::G, |
| Key::H => ::glutin::VirtualKeyCode::H, |
| Key::I => ::glutin::VirtualKeyCode::I, |
| Key::J => ::glutin::VirtualKeyCode::J, |
| Key::K => ::glutin::VirtualKeyCode::K, |
| Key::L => ::glutin::VirtualKeyCode::L, |
| Key::M => ::glutin::VirtualKeyCode::M, |
| Key::N => ::glutin::VirtualKeyCode::N, |
| Key::O => ::glutin::VirtualKeyCode::O, |
| Key::P => ::glutin::VirtualKeyCode::P, |
| Key::Q => ::glutin::VirtualKeyCode::Q, |
| Key::R => ::glutin::VirtualKeyCode::R, |
| Key::S => ::glutin::VirtualKeyCode::S, |
| Key::T => ::glutin::VirtualKeyCode::T, |
| Key::U => ::glutin::VirtualKeyCode::U, |
| Key::V => ::glutin::VirtualKeyCode::V, |
| Key::W => ::glutin::VirtualKeyCode::W, |
| Key::X => ::glutin::VirtualKeyCode::X, |
| Key::Y => ::glutin::VirtualKeyCode::Y, |
| Key::Z => ::glutin::VirtualKeyCode::Z, |
| Key::Escape => ::glutin::VirtualKeyCode::Escape, |
| Key::F1 => ::glutin::VirtualKeyCode::F1, |
| Key::F2 => ::glutin::VirtualKeyCode::F2, |
| Key::F3 => ::glutin::VirtualKeyCode::F3, |
| Key::F4 => ::glutin::VirtualKeyCode::F4, |
| Key::F5 => ::glutin::VirtualKeyCode::F5, |
| Key::F6 => ::glutin::VirtualKeyCode::F6, |
| Key::F7 => ::glutin::VirtualKeyCode::F7, |
| Key::F8 => ::glutin::VirtualKeyCode::F8, |
| Key::F9 => ::glutin::VirtualKeyCode::F9, |
| Key::F10 => ::glutin::VirtualKeyCode::F10, |
| Key::F11 => ::glutin::VirtualKeyCode::F11, |
| Key::F12 => ::glutin::VirtualKeyCode::F12, |
| Key::F13 => ::glutin::VirtualKeyCode::F13, |
| Key::F14 => ::glutin::VirtualKeyCode::F14, |
| Key::F15 => ::glutin::VirtualKeyCode::F15, |
| Key::Snapshot => ::glutin::VirtualKeyCode::Snapshot, |
| Key::Scroll => ::glutin::VirtualKeyCode::Scroll, |
| Key::Pause => ::glutin::VirtualKeyCode::Pause, |
| Key::Insert => ::glutin::VirtualKeyCode::Insert, |
| Key::Home => ::glutin::VirtualKeyCode::Home, |
| Key::Delete => ::glutin::VirtualKeyCode::Delete, |
| Key::End => ::glutin::VirtualKeyCode::End, |
| Key::PageDown => ::glutin::VirtualKeyCode::PageDown, |
| Key::PageUp => ::glutin::VirtualKeyCode::PageUp, |
| Key::Left => ::glutin::VirtualKeyCode::Left, |
| Key::Up => ::glutin::VirtualKeyCode::Up, |
| Key::Right => ::glutin::VirtualKeyCode::Right, |
| Key::Down => ::glutin::VirtualKeyCode::Down, |
| Key::Back => ::glutin::VirtualKeyCode::Back, |
| Key::Return => ::glutin::VirtualKeyCode::Return, |
| Key::Space => ::glutin::VirtualKeyCode::Space, |
| Key::Compose => ::glutin::VirtualKeyCode::Compose, |
| Key::Numlock => ::glutin::VirtualKeyCode::Numlock, |
| Key::Numpad0 => ::glutin::VirtualKeyCode::Numpad0, |
| Key::Numpad1 => ::glutin::VirtualKeyCode::Numpad1, |
| Key::Numpad2 => ::glutin::VirtualKeyCode::Numpad2, |
| Key::Numpad3 => ::glutin::VirtualKeyCode::Numpad3, |
| Key::Numpad4 => ::glutin::VirtualKeyCode::Numpad4, |
| Key::Numpad5 => ::glutin::VirtualKeyCode::Numpad5, |
| Key::Numpad6 => ::glutin::VirtualKeyCode::Numpad6, |
| Key::Numpad7 => ::glutin::VirtualKeyCode::Numpad7, |
| Key::Numpad8 => ::glutin::VirtualKeyCode::Numpad8, |
| Key::Numpad9 => ::glutin::VirtualKeyCode::Numpad9, |
| Key::AbntC1 => ::glutin::VirtualKeyCode::AbntC1, |
| Key::AbntC2 => ::glutin::VirtualKeyCode::AbntC2, |
| Key::Add => ::glutin::VirtualKeyCode::Add, |
| Key::Apostrophe => ::glutin::VirtualKeyCode::Apostrophe, |
| Key::Apps => ::glutin::VirtualKeyCode::Apps, |
| Key::At => ::glutin::VirtualKeyCode::At, |
| Key::Ax => ::glutin::VirtualKeyCode::Ax, |
| Key::Backslash => ::glutin::VirtualKeyCode::Backslash, |
| Key::Calculator => ::glutin::VirtualKeyCode::Calculator, |
| Key::Capital => ::glutin::VirtualKeyCode::Capital, |
| Key::Colon => ::glutin::VirtualKeyCode::Colon, |
| Key::Comma => ::glutin::VirtualKeyCode::Comma, |
| Key::Convert => ::glutin::VirtualKeyCode::Convert, |
| Key::Decimal => ::glutin::VirtualKeyCode::Decimal, |
| Key::Divide => ::glutin::VirtualKeyCode::Divide, |
| Key::Equals => ::glutin::VirtualKeyCode::Equals, |
| Key::Grave => ::glutin::VirtualKeyCode::Grave, |
| Key::Kana => ::glutin::VirtualKeyCode::Kana, |
| Key::Kanji => ::glutin::VirtualKeyCode::Kanji, |
| Key::LAlt => ::glutin::VirtualKeyCode::LAlt, |
| Key::LBracket => ::glutin::VirtualKeyCode::LBracket, |
| Key::LControl => ::glutin::VirtualKeyCode::LControl, |
| Key::LMenu => ::glutin::VirtualKeyCode::LMenu, |
| Key::LShift => ::glutin::VirtualKeyCode::LShift, |
| Key::LWin => ::glutin::VirtualKeyCode::LWin, |
| Key::Mail => ::glutin::VirtualKeyCode::Mail, |
| Key::MediaSelect => ::glutin::VirtualKeyCode::MediaSelect, |
| Key::MediaStop => ::glutin::VirtualKeyCode::MediaStop, |
| Key::Minus => ::glutin::VirtualKeyCode::Minus, |
| Key::Multiply => ::glutin::VirtualKeyCode::Multiply, |
| Key::Mute => ::glutin::VirtualKeyCode::Mute, |
| Key::MyComputer => ::glutin::VirtualKeyCode::MyComputer, |
| Key::NavigateForward => ::glutin::VirtualKeyCode::NavigateForward, |
| Key::NavigateBackward => ::glutin::VirtualKeyCode::NavigateBackward, |
| Key::NextTrack => ::glutin::VirtualKeyCode::NextTrack, |
| Key::NoConvert => ::glutin::VirtualKeyCode::NoConvert, |
| Key::NumpadComma => ::glutin::VirtualKeyCode::NumpadComma, |
| Key::NumpadEnter => ::glutin::VirtualKeyCode::NumpadEnter, |
| Key::NumpadEquals => ::glutin::VirtualKeyCode::NumpadEquals, |
| Key::OEM102 => ::glutin::VirtualKeyCode::OEM102, |
| Key::Period => ::glutin::VirtualKeyCode::Period, |
| Key::PlayPause => ::glutin::VirtualKeyCode::PlayPause, |
| Key::Power => ::glutin::VirtualKeyCode::Power, |
| Key::PrevTrack => ::glutin::VirtualKeyCode::PrevTrack, |
| Key::RAlt => ::glutin::VirtualKeyCode::RAlt, |
| Key::RBracket => ::glutin::VirtualKeyCode::RBracket, |
| Key::RControl => ::glutin::VirtualKeyCode::RControl, |
| Key::RMenu => ::glutin::VirtualKeyCode::RMenu, |
| Key::RShift => ::glutin::VirtualKeyCode::RShift, |
| Key::RWin => ::glutin::VirtualKeyCode::RWin, |
| Key::Semicolon => ::glutin::VirtualKeyCode::Semicolon, |
| Key::Slash => ::glutin::VirtualKeyCode::Slash, |
| Key::Sleep => ::glutin::VirtualKeyCode::Sleep, |
| Key::Stop => ::glutin::VirtualKeyCode::Stop, |
| Key::Subtract => ::glutin::VirtualKeyCode::Subtract, |
| Key::Sysrq => ::glutin::VirtualKeyCode::Sysrq, |
| Key::Tab => ::glutin::VirtualKeyCode::Tab, |
| Key::Underline => ::glutin::VirtualKeyCode::Underline, |
| Key::Unlabeled => ::glutin::VirtualKeyCode::Unlabeled, |
| Key::VolumeDown => ::glutin::VirtualKeyCode::VolumeDown, |
| Key::VolumeUp => ::glutin::VirtualKeyCode::VolumeUp, |
| Key::Wake => ::glutin::VirtualKeyCode::Wake, |
| Key::WebBack => ::glutin::VirtualKeyCode::WebBack, |
| Key::WebFavorites => ::glutin::VirtualKeyCode::WebFavorites, |
| Key::WebForward => ::glutin::VirtualKeyCode::WebForward, |
| Key::WebHome => ::glutin::VirtualKeyCode::WebHome, |
| Key::WebRefresh => ::glutin::VirtualKeyCode::WebRefresh, |
| Key::WebSearch => ::glutin::VirtualKeyCode::WebSearch, |
| Key::WebStop => ::glutin::VirtualKeyCode::WebStop, |
| Key::Yen => ::glutin::VirtualKeyCode::Yen, |
| } |
| } |
| } |