#include "key_mapper.h" #include namespace roro { namespace { constexpr char kArmed = '*'; bool isAccent(char c) { return c == '\'' || c == '`' || c == '^' || c == '"' || c == ',' || c == '~'; } // Latin-1 code point for accent + base letter, or 0. uint32_t composeLatin1(char accent, char base) { struct Entry { char accent; const char* bases; const uint32_t* codes; }; static const uint32_t acute[] = {0xE1, 0xE9, 0xED, 0xF3, 0xFA, 0xFD, 0xC1, 0xC9, 0xCD, 0xD3, 0xDA, 0xDD}; static const uint32_t grave[] = {0xE0, 0xE8, 0xEC, 0xF2, 0xF9, 0xC0, 0xC8, 0xCC, 0xD2, 0xD9}; static const uint32_t circ[] = {0xE2, 0xEA, 0xEE, 0xF4, 0xFB, 0xC2, 0xCA, 0xCE, 0xD4, 0xDB}; static const uint32_t diaer[] = {0xE4, 0xEB, 0xEF, 0xF6, 0xFC, 0xFF, 0xC4, 0xCB, 0xCF, 0xD6, 0xDC}; static const uint32_t cedil[] = {0xE7, 0xC7}; static const uint32_t tilde[] = {0xE3, 0xF1, 0xF5, 0xC3, 0xD1, 0xD5}; static const Entry table[] = { {'\'', "aeiouyAEIOUY", acute}, {'`', "aeiouAEIOU", grave}, {'^', "aeiouAEIOU", circ}, {'"', "aeiouyAEIOU", diaer}, {',', "cC", cedil}, {'~', "anoANO", tilde}, }; for (auto& e : table) { if (e.accent != accent) continue; for (int i = 0; e.bases[i]; i++) if (e.bases[i] == base) return e.codes[i]; } return 0; } KeyEvent charEvent(uint32_t cp, const RawKeys& keys) { KeyEvent e = KeyEvent::character(cp); e.shift = keys.shift; e.ctrl = keys.ctrl; e.alt = keys.alt; return e; } } // namespace std::vector KeyMapper::update(const RawKeys& keys) { std::vector out; if (keys.opt && !previous_.opt && keys.chars.empty()) { compose_ = compose_ ? 0 : kArmed; // a second opt cancels } if (keys.enter && !previous_.enter) out.push_back(KeyEvent::of(Key::Select)); if (keys.del && !previous_.del) out.push_back(KeyEvent::of(Key::Delete)); if (keys.tab && !previous_.tab) out.push_back(KeyEvent::of(Key::Tab)); for (char c : keys.chars) { bool wasHeld = std::find(previous_.chars.begin(), previous_.chars.end(), c) != previous_.chars.end(); if (!wasHeld) onChar(c, keys, out); } previous_ = keys; return out; } void KeyMapper::onChar(char c, const RawKeys& keys, std::vector& out) { // Holding opt while typing the accent is the same as pressing opt first. if (keys.opt && !compose_) compose_ = kArmed; if (compose_ == kArmed) { if (isAccent(c)) { compose_ = c; return; } compose_ = 0; // not an accent: type it normally below } else if (compose_) { char accent = compose_; compose_ = 0; uint32_t composed = composeLatin1(accent, c); if (composed) { out.push_back(charEvent(composed, keys)); } else { out.push_back(charEvent(static_cast(accent), keys)); out.push_back(charEvent(static_cast(c), keys)); } return; } if (keys.fn || !textEntry_) { switch (c) { case ';': out.push_back(KeyEvent::of(Key::Up)); return; case '.': out.push_back(KeyEvent::of(Key::Down)); return; case ',': out.push_back(KeyEvent::of(Key::Left)); return; case '/': out.push_back(KeyEvent::of(Key::Right)); return; case '`': if (keys.fn) { out.push_back(KeyEvent::of(Key::Home)); return; } break; case 'h': case 'H': if (keys.fn) { // Fn+h: help, while typing too out.push_back(KeyEvent::of(Key::Help)); return; } break; case '?': if (!textEntry_ && !keys.fn) { // ? alone, when it wouldn't be typed out.push_back(KeyEvent::of(Key::Help)); return; } if (keys.fn) return; break; default: if (keys.fn) return; // other Fn combos are unassigned break; } } if (c == '`') { out.push_back(KeyEvent::of(Key::Back)); return; } out.push_back(charEvent(static_cast(c), keys)); } } // namespace roro