diff options
Diffstat (limited to 'src/keymap.rs')
| -rw-r--r-- | src/keymap.rs | 253 |
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 @@ | |||
| 1 | use crate::layout; | ||
| 2 | use crate::x11; | ||
| 3 | |||
| 4 | use xkbcommon::xkb; | ||
| 5 | |||
| 6 | #[derive(Debug, Clone)] | ||
| 7 | pub enum KeyKind { | ||
| 8 | Normal, | ||
| 9 | Shift, | ||
| 10 | Level3, | ||
| 11 | Lock, | ||
| 12 | Control, | ||
| 13 | Alt, | ||
| 14 | Super, | ||
| 15 | } | ||
| 16 | |||
| 17 | #[derive(Debug, Clone)] | ||
| 18 | pub 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)] | ||
| 26 | pub struct KeyData { | ||
| 27 | pub keys: Vec<KeyEntry>, | ||
| 28 | pub layouts: Vec<String>, | ||
| 29 | pub is_iso: bool, | ||
| 30 | } | ||
| 31 | |||
| 32 | fn 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 | |||
| 58 | fn 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 | |||
| 104 | fn 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 | |||
| 119 | fn is_keysym_dead(sym: xkb::Keysym) -> bool { | ||
| 120 | xkb::keysym_get_name(sym).to_lowercase().starts_with("dead_") | ||
| 121 | } | ||
| 122 | |||
| 123 | pub 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 | |||
| 134 | struct RawKey { | ||
| 135 | name: String, | ||
| 136 | labels: [[String; 4]; 4], | ||
| 137 | dead: [[bool; 4]; 4], | ||
| 138 | kind: KeyKind, | ||
| 139 | } | ||
| 140 | |||
| 141 | fn 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 | |||
| 151 | pub 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 | } | ||
