use std::borrow::Cow;
use crate::keycode::Keycode;
use crate::keymod::KeyMod;
use crate::layout::{self, Layout, LayoutKey};
use crate::localizer::{self, KeyLocalizer, LabelStyle, Modifier, Platform};
use crate::named_key::NamedKey;
use crate::scancode::Scancode;
#[derive(Clone, Debug)]
pub struct Resolved {
pub scancode: Scancode,
pub keycode: Keycode,
pub glyph_en: Cow<'static, str>,
pub glyph_local: Cow<'static, str>,
pub character: Option<char>,
pub named_key: Option<NamedKey>,
pub layout: &'static str,
}
pub fn resolve<L: KeyLocalizer>(
scancode: Scancode,
mods: KeyMod,
layout: &str,
locale: &str,
style: LabelStyle,
localizer: &L,
) -> Resolved {
let layout_ref = layout::get_layout(layout)
.or_else(|| layout::get_layout("windows/en-t-k0-windows"))
.expect("windows/en-t-k0-windows always present in CLDR data");
resolve_in_layout(scancode, mods, layout_ref, locale, style, localizer)
}
fn resolve_in_layout<L: KeyLocalizer>(
scancode: Scancode,
mods: KeyMod,
layout: &'static Layout,
locale: &str,
style: LabelStyle,
localizer: &L,
) -> Resolved {
let key = match layout.key(scancode) {
Some(k) => k,
None => {
return Resolved {
scancode,
keycode: Keycode::UNKNOWN,
glyph_en: Cow::Borrowed(""),
glyph_local: Cow::Borrowed(""),
character: None,
named_key: None,
layout: layout.id,
};
}
};
if let Some(nk) = key.named {
if let Some((numlock_on_named, numlock_off_named, numlock_char)) = keypad_behavior(nk) {
if mods.num() {
let character = numlock_char;
return Resolved {
scancode,
keycode: layout::named_key_keycode(numlock_on_named),
glyph_en: character
.map(|c| Cow::Owned(c.to_string()))
.unwrap_or_else(|| {
named_key_label(numlock_on_named, "en", style, localizer)
}),
glyph_local: character.map(|c| Cow::Owned(c.to_string())).unwrap_or_else(
|| named_key_label(numlock_on_named, locale, style, localizer),
),
character,
named_key: Some(numlock_on_named),
layout: layout.id,
};
} else {
return Resolved {
scancode,
keycode: layout::named_key_keycode(numlock_off_named),
glyph_en: named_key_label(numlock_off_named, "en", style, localizer),
glyph_local: named_key_label(numlock_off_named, locale, style, localizer),
character: None,
named_key: Some(numlock_off_named),
layout: layout.id,
};
}
}
return Resolved {
scancode,
keycode: layout::named_key_keycode(nk),
glyph_en: named_key_label(nk, "en", style, localizer),
glyph_local: named_key_label(nk, locale, style, localizer),
character: None,
named_key: Some(nk),
layout: layout.id,
};
}
let effective_shift = effective_shift_state(key, mods);
let effective_altgr = mods.altgr();
let glyph = pick_glyph(key, effective_shift, effective_altgr);
let base_keycode = layout::layout_key_base_keycode(key);
let character = glyph;
let glyph_str = glyph.map(|c| c.to_string()).unwrap_or_default();
Resolved {
scancode,
keycode: base_keycode,
glyph_en: Cow::Owned(glyph_str.clone()),
glyph_local: Cow::Owned(glyph_str),
character,
named_key: None,
layout: layout.id,
}
}
fn pick_glyph(key: &LayoutKey, shift: bool, altgr: bool) -> Option<char> {
match (shift, altgr) {
(false, false) => key.base,
(true, false) => key.shift.or(key.base),
(false, true) => key.altgr.or(key.base),
(true, true) => key.shift_altgr.or(key.shift).or(key.base),
}
.and_then(|c| if c == '\0' { None } else { Some(c) })
}
fn effective_shift_state(key: &LayoutKey, mods: KeyMod) -> bool {
let shift = mods.shift();
let caps_applies = mods.caps() && is_letter_key(key);
shift ^ caps_applies
}
fn is_letter_key(key: &LayoutKey) -> bool {
key.base.map(|c| c.is_alphabetic()).unwrap_or(false)
}
fn keypad_behavior(nk: NamedKey) -> Option<(NamedKey, NamedKey, Option<char>)> {
use NamedKey::*;
Some(match nk {
Keypad0 => (Keypad0, Insert, Some('0')),
Keypad1 => (Keypad1, End, Some('1')),
Keypad2 => (Keypad2, ArrowDown, Some('2')),
Keypad3 => (Keypad3, PageDown, Some('3')),
Keypad4 => (Keypad4, ArrowLeft, Some('4')),
Keypad5 => (Keypad5, Keypad5, Some('5')), Keypad6 => (Keypad6, ArrowRight, Some('6')),
Keypad7 => (Keypad7, Home, Some('7')),
Keypad8 => (Keypad8, ArrowUp, Some('8')),
Keypad9 => (Keypad9, PageUp, Some('9')),
KeypadPeriod => (KeypadPeriod, Delete, Some('.')),
_ => return None,
})
}
fn named_key_label<L: KeyLocalizer>(
nk: NamedKey,
locale: &str,
style: LabelStyle,
localizer: &L,
) -> Cow<'static, str> {
match localizer::translate_for(nk.key_id(), locale, style, localizer) {
Some(s) => s,
None => Cow::Borrowed(nk.key_id()),
}
}
pub fn scancode_for(keycode: Keycode, layout: &str) -> Option<Scancode> {
let l = layout::get_layout(layout)?;
for k in l.printable_keys.iter() {
if layout::layout_key_base_keycode(k) == keycode {
return Some(k.scancode);
}
}
for k in l.named_keys.iter() {
if layout::layout_key_base_keycode(k) == keycode {
return Some(k.scancode);
}
}
None
}
pub fn modifier_label<L: KeyLocalizer>(
modifier: Modifier,
platform: Platform,
locale: &str,
style: LabelStyle,
localizer: &L,
) -> Cow<'static, str> {
let key = format!("{}_{}", modifier.key_id_prefix(), platform.id());
if let Some(s) = localizer::translate_for(&key, locale, style, localizer) {
return s;
}
let generic = modifier.key_id_prefix();
localizer::translate_for(generic, locale, style, localizer).unwrap_or(Cow::Borrowed(generic))
}
pub fn keycode_from_name(name: &str) -> Option<Keycode> {
let trimmed = name.trim();
if trimmed.is_empty() {
return None;
}
let mut chars = trimmed.chars();
if let (Some(c), None) = (chars.next(), chars.next()) {
if !c.is_control() {
let lower = c.to_lowercase().next().unwrap_or(c);
return Some(Keycode::from(lower));
}
}
let upper = trimmed.to_ascii_uppercase();
match upper.as_str() {
"RETURN" | "ENTER" => Some(Keycode::RETURN),
"ESCAPE" | "ESC" => Some(Keycode::ESCAPE),
"BACKSPACE" => Some(Keycode::BACKSPACE),
"TAB" => Some(Keycode::TAB),
"SPACE" => Some(Keycode::SPACE),
"CAPSLOCK" | "CAPS LOCK" => Some(Keycode::CAPSLOCK),
"NUMLOCK" | "NUM LOCK" | "NUMLOCKCLEAR" => Some(Keycode::NUM_LOCK_CLEAR),
"SCROLLLOCK" | "SCROLL LOCK" => Some(Keycode::SCROLL_LOCK),
"PRINTSCREEN" | "PRINT SCREEN" => Some(Keycode::PRINT_SCREEN),
"PAUSE" => Some(Keycode::PAUSE),
"INSERT" => Some(Keycode::INSERT),
"HOME" => Some(Keycode::HOME),
"PAGEUP" | "PAGE UP" => Some(Keycode::PAGE_UP),
"DELETE" => Some(Keycode::DELETE),
"END" => Some(Keycode::END),
"PAGEDOWN" | "PAGE DOWN" => Some(Keycode::PAGE_DOWN),
"RIGHT" => Some(Keycode::RIGHT),
"LEFT" => Some(Keycode::LEFT),
"DOWN" => Some(Keycode::DOWN),
"UP" => Some(Keycode::UP),
"APPLICATION" => Some(Keycode::APPLICATION),
"MENU" => Some(Keycode::MENU),
"LEFT CTRL" | "LCTRL" => Some(Keycode::LCTRL),
"RIGHT CTRL" | "RCTRL" => Some(Keycode::RCTRL),
"LEFT SHIFT" | "LSHIFT" => Some(Keycode::LSHIFT),
"RIGHT SHIFT" | "RSHIFT" => Some(Keycode::RSHIFT),
"LEFT ALT" | "LALT" => Some(Keycode::LALT),
"RIGHT ALT" | "RALT" | "ALTGR" => Some(Keycode::RALT),
"LEFT GUI" | "LGUI" => Some(Keycode::LGUI),
"RIGHT GUI" | "RGUI" => Some(Keycode::RGUI),
"F1" => Some(Keycode::F1),
"F2" => Some(Keycode::F2),
"F3" => Some(Keycode::F3),
"F4" => Some(Keycode::F4),
"F5" => Some(Keycode::F5),
"F6" => Some(Keycode::F6),
"F7" => Some(Keycode::F7),
"F8" => Some(Keycode::F8),
"F9" => Some(Keycode::F9),
"F10" => Some(Keycode::F10),
"F11" => Some(Keycode::F11),
"F12" => Some(Keycode::F12),
"F13" => Some(Keycode::F13),
"F14" => Some(Keycode::F14),
"F15" => Some(Keycode::F15),
"F16" => Some(Keycode::F16),
"F17" => Some(Keycode::F17),
"F18" => Some(Keycode::F18),
"F19" => Some(Keycode::F19),
"F20" => Some(Keycode::F20),
"F21" => Some(Keycode::F21),
"F22" => Some(Keycode::F22),
"F23" => Some(Keycode::F23),
"F24" => Some(Keycode::F24),
"KEYPAD 0" | "KP_0" => Some(Keycode::KP_0),
"KEYPAD 1" | "KP_1" => Some(Keycode::KP_1),
"KEYPAD 2" | "KP_2" => Some(Keycode::KP_2),
"KEYPAD 3" | "KP_3" => Some(Keycode::KP_3),
"KEYPAD 4" | "KP_4" => Some(Keycode::KP_4),
"KEYPAD 5" | "KP_5" => Some(Keycode::KP_5),
"KEYPAD 6" | "KP_6" => Some(Keycode::KP_6),
"KEYPAD 7" | "KP_7" => Some(Keycode::KP_7),
"KEYPAD 8" | "KP_8" => Some(Keycode::KP_8),
"KEYPAD 9" | "KP_9" => Some(Keycode::KP_9),
"KEYPAD ." | "KP_PERIOD" => Some(Keycode::KP_PERIOD),
"KEYPAD =" | "KP_EQUALS" => Some(Keycode::KP_EQUALS),
"KEYPAD ENTER" | "KP_ENTER" => Some(Keycode::KP_ENTER),
"KEYPAD /" | "KP_DIVIDE" => Some(Keycode::KP_DIVIDE),
"KEYPAD *" | "KP_MULTIPLY" => Some(Keycode::KP_MULTIPLY),
"KEYPAD -" | "KP_MINUS" => Some(Keycode::KP_MINUS),
"KEYPAD +" | "KP_PLUS" => Some(Keycode::KP_PLUS),
_ => None,
}
}