1use std::borrow::Cow;
5
6use crate::keycode::Keycode;
7use crate::keymod::KeyMod;
8use crate::layout::{self, Layout, LayoutKey};
9use crate::localizer::{self, KeyLocalizer, LabelStyle, Modifier, Platform};
10use crate::named_key::NamedKey;
11use crate::scancode::Scancode;
12
13#[derive(Clone, Debug)]
17pub struct Resolved {
18 pub scancode: Scancode,
19 pub keycode: Keycode,
20 pub glyph_en: Cow<'static, str>,
22 pub glyph_local: Cow<'static, str>,
24 pub character: Option<char>,
27 pub named_key: Option<NamedKey>,
30 pub layout: &'static str,
32}
33
34pub fn resolve<L: KeyLocalizer>(
49 scancode: Scancode,
50 mods: KeyMod,
51 layout: &str,
52 locale: &str,
53 style: LabelStyle,
54 localizer: &L,
55) -> Resolved {
56 let layout_ref = layout::get_layout(layout)
57 .or_else(|| layout::get_layout("windows/en-t-k0-windows"))
58 .expect("windows/en-t-k0-windows always present in CLDR data");
59
60 resolve_in_layout(scancode, mods, layout_ref, locale, style, localizer)
61}
62
63fn resolve_in_layout<L: KeyLocalizer>(
64 scancode: Scancode,
65 mods: KeyMod,
66 layout: &'static Layout,
67 locale: &str,
68 style: LabelStyle,
69 localizer: &L,
70) -> Resolved {
71 let key = match layout.key(scancode) {
72 Some(k) => k,
73 None => {
74 return Resolved {
75 scancode,
76 keycode: Keycode::UNKNOWN,
77 glyph_en: Cow::Borrowed(""),
78 glyph_local: Cow::Borrowed(""),
79 character: None,
80 named_key: None,
81 layout: layout.id,
82 };
83 }
84 };
85
86 if let Some(nk) = key.named {
88 if let Some((numlock_on_named, numlock_off_named, numlock_char)) = keypad_behavior(nk) {
89 if mods.num() {
90 let character = numlock_char;
92 return Resolved {
93 scancode,
94 keycode: layout::named_key_keycode(numlock_on_named),
95 glyph_en: character
96 .map(|c| Cow::Owned(c.to_string()))
97 .unwrap_or_else(|| {
98 named_key_label(numlock_on_named, "en", style, localizer)
99 }),
100 glyph_local: character.map(|c| Cow::Owned(c.to_string())).unwrap_or_else(
101 || named_key_label(numlock_on_named, locale, style, localizer),
102 ),
103 character,
104 named_key: Some(numlock_on_named),
105 layout: layout.id,
106 };
107 } else {
108 return Resolved {
110 scancode,
111 keycode: layout::named_key_keycode(numlock_off_named),
112 glyph_en: named_key_label(numlock_off_named, "en", style, localizer),
113 glyph_local: named_key_label(numlock_off_named, locale, style, localizer),
114 character: None,
115 named_key: Some(numlock_off_named),
116 layout: layout.id,
117 };
118 }
119 }
120
121 return Resolved {
123 scancode,
124 keycode: layout::named_key_keycode(nk),
125 glyph_en: named_key_label(nk, "en", style, localizer),
126 glyph_local: named_key_label(nk, locale, style, localizer),
127 character: None,
128 named_key: Some(nk),
129 layout: layout.id,
130 };
131 }
132
133 let effective_shift = effective_shift_state(key, mods);
135 let effective_altgr = mods.altgr();
136
137 let glyph = pick_glyph(key, effective_shift, effective_altgr);
138
139 let base_keycode = layout::layout_key_base_keycode(key);
142
143 let character = glyph;
144 let glyph_str = glyph.map(|c| c.to_string()).unwrap_or_default();
145
146 Resolved {
147 scancode,
148 keycode: base_keycode,
149 glyph_en: Cow::Owned(glyph_str.clone()),
150 glyph_local: Cow::Owned(glyph_str),
151 character,
152 named_key: None,
153 layout: layout.id,
154 }
155}
156
157fn pick_glyph(key: &LayoutKey, shift: bool, altgr: bool) -> Option<char> {
159 match (shift, altgr) {
160 (false, false) => key.base,
161 (true, false) => key.shift.or(key.base),
162 (false, true) => key.altgr.or(key.base),
163 (true, true) => key.shift_altgr.or(key.shift).or(key.base),
164 }
165 .and_then(|c| if c == '\0' { None } else { Some(c) })
166}
167
168fn effective_shift_state(key: &LayoutKey, mods: KeyMod) -> bool {
171 let shift = mods.shift();
172 let caps_applies = mods.caps() && is_letter_key(key);
173 shift ^ caps_applies
174}
175
176fn is_letter_key(key: &LayoutKey) -> bool {
179 key.base.map(|c| c.is_alphabetic()).unwrap_or(false)
180}
181
182fn keypad_behavior(nk: NamedKey) -> Option<(NamedKey, NamedKey, Option<char>)> {
189 use NamedKey::*;
190 Some(match nk {
191 Keypad0 => (Keypad0, Insert, Some('0')),
192 Keypad1 => (Keypad1, End, Some('1')),
193 Keypad2 => (Keypad2, ArrowDown, Some('2')),
194 Keypad3 => (Keypad3, PageDown, Some('3')),
195 Keypad4 => (Keypad4, ArrowLeft, Some('4')),
196 Keypad5 => (Keypad5, Keypad5, Some('5')), Keypad6 => (Keypad6, ArrowRight, Some('6')),
198 Keypad7 => (Keypad7, Home, Some('7')),
199 Keypad8 => (Keypad8, ArrowUp, Some('8')),
200 Keypad9 => (Keypad9, PageUp, Some('9')),
201 KeypadPeriod => (KeypadPeriod, Delete, Some('.')),
202 _ => return None,
203 })
204}
205
206fn named_key_label<L: KeyLocalizer>(
209 nk: NamedKey,
210 locale: &str,
211 style: LabelStyle,
212 localizer: &L,
213) -> Cow<'static, str> {
214 match localizer::translate_for(nk.key_id(), locale, style, localizer) {
215 Some(s) => s,
216 None => Cow::Borrowed(nk.key_id()),
217 }
218}
219
220pub fn scancode_for(keycode: Keycode, layout: &str) -> Option<Scancode> {
226 let l = layout::get_layout(layout)?;
227 for k in l.printable_keys.iter() {
228 if layout::layout_key_base_keycode(k) == keycode {
229 return Some(k.scancode);
230 }
231 }
232 for k in l.named_keys.iter() {
233 if layout::layout_key_base_keycode(k) == keycode {
234 return Some(k.scancode);
235 }
236 }
237 None
238}
239
240pub fn modifier_label<L: KeyLocalizer>(
249 modifier: Modifier,
250 platform: Platform,
251 locale: &str,
252 style: LabelStyle,
253 localizer: &L,
254) -> Cow<'static, str> {
255 let key = format!("{}_{}", modifier.key_id_prefix(), platform.id());
256 if let Some(s) = localizer::translate_for(&key, locale, style, localizer) {
257 return s;
258 }
259 let generic = modifier.key_id_prefix();
260 localizer::translate_for(generic, locale, style, localizer).unwrap_or(Cow::Borrowed(generic))
261}
262
263pub fn keycode_from_name(name: &str) -> Option<Keycode> {
270 let trimmed = name.trim();
271 if trimmed.is_empty() {
272 return None;
273 }
274
275 let mut chars = trimmed.chars();
277 if let (Some(c), None) = (chars.next(), chars.next()) {
278 if !c.is_control() {
279 let lower = c.to_lowercase().next().unwrap_or(c);
280 return Some(Keycode::from(lower));
281 }
282 }
283
284 let upper = trimmed.to_ascii_uppercase();
286 match upper.as_str() {
287 "RETURN" | "ENTER" => Some(Keycode::RETURN),
288 "ESCAPE" | "ESC" => Some(Keycode::ESCAPE),
289 "BACKSPACE" => Some(Keycode::BACKSPACE),
290 "TAB" => Some(Keycode::TAB),
291 "SPACE" => Some(Keycode::SPACE),
292 "CAPSLOCK" | "CAPS LOCK" => Some(Keycode::CAPSLOCK),
293 "NUMLOCK" | "NUM LOCK" | "NUMLOCKCLEAR" => Some(Keycode::NUM_LOCK_CLEAR),
294 "SCROLLLOCK" | "SCROLL LOCK" => Some(Keycode::SCROLL_LOCK),
295 "PRINTSCREEN" | "PRINT SCREEN" => Some(Keycode::PRINT_SCREEN),
296 "PAUSE" => Some(Keycode::PAUSE),
297 "INSERT" => Some(Keycode::INSERT),
298 "HOME" => Some(Keycode::HOME),
299 "PAGEUP" | "PAGE UP" => Some(Keycode::PAGE_UP),
300 "DELETE" => Some(Keycode::DELETE),
301 "END" => Some(Keycode::END),
302 "PAGEDOWN" | "PAGE DOWN" => Some(Keycode::PAGE_DOWN),
303 "RIGHT" => Some(Keycode::RIGHT),
304 "LEFT" => Some(Keycode::LEFT),
305 "DOWN" => Some(Keycode::DOWN),
306 "UP" => Some(Keycode::UP),
307 "APPLICATION" => Some(Keycode::APPLICATION),
308 "MENU" => Some(Keycode::MENU),
309 "LEFT CTRL" | "LCTRL" => Some(Keycode::LCTRL),
310 "RIGHT CTRL" | "RCTRL" => Some(Keycode::RCTRL),
311 "LEFT SHIFT" | "LSHIFT" => Some(Keycode::LSHIFT),
312 "RIGHT SHIFT" | "RSHIFT" => Some(Keycode::RSHIFT),
313 "LEFT ALT" | "LALT" => Some(Keycode::LALT),
314 "RIGHT ALT" | "RALT" | "ALTGR" => Some(Keycode::RALT),
315 "LEFT GUI" | "LGUI" => Some(Keycode::LGUI),
316 "RIGHT GUI" | "RGUI" => Some(Keycode::RGUI),
317 "F1" => Some(Keycode::F1),
318 "F2" => Some(Keycode::F2),
319 "F3" => Some(Keycode::F3),
320 "F4" => Some(Keycode::F4),
321 "F5" => Some(Keycode::F5),
322 "F6" => Some(Keycode::F6),
323 "F7" => Some(Keycode::F7),
324 "F8" => Some(Keycode::F8),
325 "F9" => Some(Keycode::F9),
326 "F10" => Some(Keycode::F10),
327 "F11" => Some(Keycode::F11),
328 "F12" => Some(Keycode::F12),
329 "F13" => Some(Keycode::F13),
330 "F14" => Some(Keycode::F14),
331 "F15" => Some(Keycode::F15),
332 "F16" => Some(Keycode::F16),
333 "F17" => Some(Keycode::F17),
334 "F18" => Some(Keycode::F18),
335 "F19" => Some(Keycode::F19),
336 "F20" => Some(Keycode::F20),
337 "F21" => Some(Keycode::F21),
338 "F22" => Some(Keycode::F22),
339 "F23" => Some(Keycode::F23),
340 "F24" => Some(Keycode::F24),
341 "KEYPAD 0" | "KP_0" => Some(Keycode::KP_0),
342 "KEYPAD 1" | "KP_1" => Some(Keycode::KP_1),
343 "KEYPAD 2" | "KP_2" => Some(Keycode::KP_2),
344 "KEYPAD 3" | "KP_3" => Some(Keycode::KP_3),
345 "KEYPAD 4" | "KP_4" => Some(Keycode::KP_4),
346 "KEYPAD 5" | "KP_5" => Some(Keycode::KP_5),
347 "KEYPAD 6" | "KP_6" => Some(Keycode::KP_6),
348 "KEYPAD 7" | "KP_7" => Some(Keycode::KP_7),
349 "KEYPAD 8" | "KP_8" => Some(Keycode::KP_8),
350 "KEYPAD 9" | "KP_9" => Some(Keycode::KP_9),
351 "KEYPAD ." | "KP_PERIOD" => Some(Keycode::KP_PERIOD),
352 "KEYPAD =" | "KP_EQUALS" => Some(Keycode::KP_EQUALS),
353 "KEYPAD ENTER" | "KP_ENTER" => Some(Keycode::KP_ENTER),
354 "KEYPAD /" | "KP_DIVIDE" => Some(Keycode::KP_DIVIDE),
355 "KEYPAD *" | "KP_MULTIPLY" => Some(Keycode::KP_MULTIPLY),
356 "KEYPAD -" | "KP_MINUS" => Some(Keycode::KP_MINUS),
357 "KEYPAD +" | "KP_PLUS" => Some(Keycode::KP_PLUS),
358 _ => None,
359 }
360}