diff options
Diffstat (limited to 'src/config.rs')
-rw-r--r-- | src/config.rs | 726 |
1 files changed, 725 insertions, 1 deletions
diff --git a/src/config.rs b/src/config.rs index 33474ddb..b315fb86 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,14 +7,18 @@ use std::env; use std::fs; use std::io::{self, Read}; use std::path::{Path, PathBuf}; +use std::str::FromStr; use std::sync::mpsc; use ::Rgb; use font::Size; use serde_yaml; -use serde::{self, Error as SerdeError}; +use serde::{self, de, Error as SerdeError}; +use serde::de::{Visitor, MapVisitor}; use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op}; +use input::{Action, Binding, MouseBinding, KeyBinding}; + /// Function that returns true for serde default fn true_bool() -> bool { true @@ -42,6 +46,14 @@ pub struct Config { /// The standard ANSI colors to use #[serde(default)] colors: Colors, + + /// Keybindings + #[serde(default)] + key_bindings: Vec<KeyBinding>, + + /// Bindings for the mouse + #[serde(default)] + mouse_bindings: Vec<MouseBinding>, } impl Default for Config { @@ -52,7 +64,377 @@ impl Default for Config { font: Default::default(), render_timer: Default::default(), colors: Default::default(), + key_bindings: Vec::new(), + mouse_bindings: Vec::new(), + } + } +} + +/// 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: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct ModsVisitor; + + impl Visitor for ModsVisitor { + type Value = ModsWrapper; + + fn visit_str<E>(&mut 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 { + "Command" | "Super" => res = res | mods::SUPER, + "Shift" => res = res | mods::SHIFT, + "Alt" | "Option" => res = res | mods::ALT, + "Control" => res = 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: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct ActionVisitor; + + impl Visitor for ActionVisitor { + type Value = ActionWrapper; + + fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<ActionWrapper, E> + where E: de::Error, + { + Ok(ActionWrapper(match value { + "Paste" => Action::Paste, + "PasteSelection" => Action::PasteSelection, + _ => return Err(E::invalid_value("invalid value for Action")), + })) + } } + deserializer.deserialize_str(ActionVisitor) + } +} + +use ::term::{mode, TermMode}; + +struct ModeWrapper { + pub mode: TermMode, + pub not_mode: TermMode, +} + +impl de::Deserialize for ModeWrapper { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct ModeVisitor; + + impl Visitor for ModeVisitor { + type Value = ModeWrapper; + + fn visit_str<E>(&mut 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 { + "AppCursor" => res.mode = res.mode | mode::APP_CURSOR, + "~AppCursor" => res.not_mode = res.not_mode | mode::APP_CURSOR, + "AppKeypad" => res.mode = res.mode | mode::APP_KEYPAD, + "~AppKeypad" => res.not_mode = 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: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct MouseButtonVisitor; + + impl Visitor for MouseButtonVisitor { + type Value = MouseButton; + + fn visit_str<E>(&mut 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("mouse may be Left, Right, Middle, or u8")) + } + } + } + } + } + + 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(MouseBinding { + button: self.mouse.unwrap(), + binding: Binding { + 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 { + key: self.key.unwrap(), + binding: Binding { + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + } + }) + } else { + Err(self) + } + } +} + +impl de::Deserialize for RawBinding { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + enum Field { + Key, + Mods, + Mode, + Action, + Chars, + Mouse + } + + impl de::Deserialize for Field { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Field, D::Error> + where D: de::Deserializer + { + struct FieldVisitor; + + impl Visitor for FieldVisitor { + type Value = Field; + + fn visit_str<E>(&mut 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), + _ => Err(E::unknown_field(value)), + } + } + } + + deserializer.deserialize_struct_field(FieldVisitor) + } + } + + struct RawBindingVisitor; + impl Visitor for RawBindingVisitor { + type Value = RawBinding; + + fn visit_map<V>(&mut 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; + + use ::serde::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()); + } + } + } + visitor.end()?; + if key.is_none() { + return Err(V::Error::missing_field("key")); + } + + let action = match (action, chars) { + (Some(_), Some(_)) => { + return Err(V::Error::custom("must specify only chars or action")); + }, + (Some(action), _) => action, + (_, Some(chars)) => Action::Esc(chars), + _ => return Err(V::Error::custom("must specify chars or action")) + }; + + 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" + ]; + + deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) + } +} + +impl de::Deserialize for MouseBinding { + fn deserialize<D>(deserializer: &mut 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: &mut 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")) } } @@ -314,6 +696,14 @@ impl Config { ] } + pub fn key_bindings(&self) -> &[KeyBinding] { + &self.key_bindings[..] + } + + pub fn mouse_bindings(&self) -> &[MouseBinding] { + &self.mouse_bindings[..] + } + pub fn fg_color(&self) -> Rgb { self.colors.primary.foreground } @@ -605,3 +995,337 @@ impl Watcher { })) } } + +#[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"); + + println!("config: {:#?}", config); + } +} + +#[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, + } + } +} |