summaryrefslogtreecommitdiffstats
path: root/src/keymap.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/keymap.rs')
-rw-r--r--src/keymap.rs253
1 files changed, 253 insertions, 0 deletions
diff --git a/src/keymap.rs b/src/keymap.rs
new file mode 100644
index 0000000..d14f9ab
--- /dev/null
+++ b/src/keymap.rs
@@ -0,0 +1,253 @@
1use crate::layout;
2use crate::x11;
3
4use xkbcommon::xkb;
5
6#[derive(Debug, Clone)]
7pub enum KeyKind {
8 Normal,
9 Shift,
10 Level3,
11 Lock,
12 Control,
13 Alt,
14 Super,
15}
16
17#[derive(Debug, Clone)]
18pub struct KeyEntry {
19 pub labels: [[String; 4]; 4],
20 pub kind: KeyKind,
21 pub dead: [[bool; 4]; 4],
22 pub pos: layout::KeyPos,
23}
24
25#[derive(Debug, Clone)]
26pub struct KeyData {
27 pub keys: Vec<KeyEntry>,
28 pub layouts: Vec<String>,
29 pub is_iso: bool,
30}
31
32fn dead_display(name: &str) -> &'static str {
33 match name.to_lowercase().as_str() {
34 "dead_grave" => "`",
35 "dead_acute" => "'",
36 "dead_circumflex" => "^",
37 "dead_tilde" => "~",
38 "dead_macron" => "-",
39 "dead_breve" => "u",
40 "dead_abovedot" => ".",
41 "dead_diaeresis" => "\"",
42 "dead_caron" => "v",
43 "dead_cedilla" => ",",
44 "dead_ogonek" => "~",
45 "dead_doubleacute" => "\"",
46 "dead_stroke" => "/",
47 "dead_hook" => "?",
48 "dead_horn" => "?",
49 "dead_psili" => "'",
50 "dead_dasia" => "'",
51 "dead_iota" => "i",
52 "dead_voiced_sound" => "\"",
53 "dead_semivoiced_sound" => "o",
54 _ => "?",
55 }
56}
57
58fn keysym_to_display(sym: xkb::Keysym) -> String {
59 let utf8 = xkb::keysym_to_utf8(sym);
60 let trimmed = utf8.trim().to_string();
61 if !trimmed.is_empty() {
62 let name = xkb::keysym_get_name(sym);
63 if name.to_lowercase().starts_with("dead_") {
64 return dead_display(&name).to_string();
65 }
66 return trimmed;
67 }
68 let name = xkb::keysym_get_name(sym);
69 if name.to_lowercase().starts_with("dead_") {
70 return dead_display(&name).to_string();
71 }
72 match sym {
73 xkb::Keysym::Shift_L | xkb::Keysym::Shift_R => "Shift".into(),
74 xkb::Keysym::ISO_Level3_Shift => "AltGr".into(),
75 xkb::Keysym::Control_L | xkb::Keysym::Control_R => "Ctrl".into(),
76 xkb::Keysym::Alt_L | xkb::Keysym::Alt_R => "Alt".into(),
77 xkb::Keysym::Super_L | xkb::Keysym::Super_R => "Super".into(),
78 xkb::Keysym::Menu => "Menu".into(),
79 xkb::Keysym::Caps_Lock => "Caps".into(),
80 xkb::Keysym::Tab => "Tab".into(),
81 xkb::Keysym::Return => "Enter".into(),
82 xkb::Keysym::Escape => "Esc".into(),
83 xkb::Keysym::BackSpace => "Bksp".into(),
84 xkb::Keysym::space => " ".into(),
85 xkb::Keysym::Delete => "Del".into(),
86 xkb::Keysym::Insert => "Ins".into(),
87 xkb::Keysym::Home => "Home".into(),
88 xkb::Keysym::End => "End".into(),
89 xkb::Keysym::Page_Up => "PgUp".into(),
90 xkb::Keysym::Page_Down => "PgDn".into(),
91 xkb::Keysym::Up => "^".into(),
92 xkb::Keysym::Down => "v".into(),
93 xkb::Keysym::Left => "<".into(),
94 xkb::Keysym::Right => ">".into(),
95 xkb::Keysym::KP_Enter => "Enter".into(),
96 xkb::Keysym::Num_Lock => "Num".into(),
97 xkb::Keysym::Scroll_Lock => "Scrl".into(),
98 xkb::Keysym::Pause => "Pause".into(),
99 xkb::Keysym::Print => "PrtSc".into(),
100 _ => name,
101 }
102}
103
104fn detect_key_kind(syms: &[xkb::Keysym]) -> KeyKind {
105 for &sym in syms {
106 match sym {
107 xkb::Keysym::Shift_L | xkb::Keysym::Shift_R => return KeyKind::Shift,
108 xkb::Keysym::ISO_Level3_Shift => return KeyKind::Level3,
109 xkb::Keysym::Caps_Lock => return KeyKind::Lock,
110 xkb::Keysym::Control_L | xkb::Keysym::Control_R => return KeyKind::Control,
111 xkb::Keysym::Alt_L | xkb::Keysym::Alt_R => return KeyKind::Alt,
112 xkb::Keysym::Super_L | xkb::Keysym::Super_R => return KeyKind::Super,
113 _ => {}
114 }
115 }
116 KeyKind::Normal
117}
118
119fn is_keysym_dead(sym: xkb::Keysym) -> bool {
120 xkb::keysym_get_name(sym).to_lowercase().starts_with("dead_")
121}
122
123pub fn level_from_x11_modmask(mask: u16) -> usize {
124 let shift = mask & 0x01 != 0;
125 let altgr = mask & 0x80 != 0;
126 match (shift, altgr) {
127 (false, false) => 0,
128 (true, false) => 1,
129 (false, true) => 2,
130 (true, true) => 3,
131 }
132}
133
134struct RawKey {
135 name: String,
136 labels: [[String; 4]; 4],
137 dead: [[bool; 4]; 4],
138 kind: KeyKind,
139}
140
141fn detect_is_iso(model: &str, keymap_has_lsgt: bool) -> bool {
142 if model.contains("105") || model.contains("102") || model.contains("98") {
143 return true;
144 }
145 if model.contains("101") || model.contains("104") || model.contains("106") {
146 return false;
147 }
148 keymap_has_lsgt
149}
150
151pub fn compile_keymap(names: &x11::RuleNames) -> Result<KeyData, String> {
152 let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
153
154 let options = Some(names.options.clone());
155
156 let keymap = xkb::Keymap::new_from_names(
157 &context,
158 &names.rules,
159 &names.model,
160 &names.layout,
161 &names.variant,
162 options,
163 xkb::KEYMAP_COMPILE_NO_FLAGS,
164 )
165 .ok_or_else(|| "failed to compile keymap".to_string())?;
166
167 let num_groups = keymap.num_layouts() as usize;
168
169 let layouts: Vec<String> = if names.layout.is_empty() || num_groups <= 1 {
170 vec![if names.layout.is_empty() {
171 "default".into()
172 } else {
173 names.layout.clone()
174 }]
175 } else {
176 let layout_parts: Vec<&str> = names.layout.split(',').collect();
177 let variant_parts: Vec<&str> = if names.variant.is_empty() {
178 vec![]
179 } else {
180 names.variant.split(',').collect()
181 };
182 layout_parts
183 .iter()
184 .enumerate()
185 .map(|(i, l)| {
186 let v = variant_parts.get(i).filter(|v| !v.is_empty());
187 match v {
188 Some(v) => format!("{}({})", l, v),
189 None => l.to_string(),
190 }
191 })
192 .collect()
193 };
194
195 layout::reset_default_pos();
196
197 let mut raw = Vec::new();
198 let mut has_lsgt = false;
199
200 keymap.key_for_each(|km, kc| {
201 let name = match km.key_get_name(kc) {
202 Some(n) => n.to_string(),
203 None => return,
204 };
205
206 if name == "LSGT" {
207 has_lsgt = true;
208 }
209
210 let mut labels: [[String; 4]; 4] = Default::default();
211 let mut dead = [[false; 4]; 4];
212 let mut all_syms = Vec::new();
213
214 for group in 0..num_groups.min(4) {
215 for level in 0..4_u32 {
216 let syms = km.key_get_syms_by_level(kc, group as u32, level);
217 let li = level as usize;
218 if syms.is_empty() {
219 labels[group][li] = String::new();
220 } else {
221 let display: Vec<String> =
222 syms.iter().map(|&s| keysym_to_display(s)).collect();
223 labels[group][li] = display.join(" ");
224 dead[group][li] = syms.iter().any(|&s| is_keysym_dead(s));
225 all_syms.extend_from_slice(syms);
226 }
227 }
228 }
229
230 let kind = detect_key_kind(&all_syms);
231
232 raw.push(RawKey { name, labels, dead, kind });
233 });
234
235 let is_iso = detect_is_iso(&names.model, has_lsgt);
236
237 let keys: Vec<KeyEntry> = raw
238 .into_iter()
239 .filter(|k| !(k.name == "LSGT" && !is_iso))
240 .map(|k| KeyEntry {
241 pos: layout::resolve_position(&k.name, is_iso),
242 labels: k.labels,
243 dead: k.dead,
244 kind: k.kind,
245 })
246 .collect();
247
248 Ok(KeyData {
249 keys,
250 layouts,
251 is_iso,
252 })
253}