Public Access
Fn+p saves the screen as it is to /screenshots, as the Shell's `screenshot` does, from anywhere: text fields, dialogs and the help panel included. The key never reaches an App. It refuses on Settings > Debug Console, which shows the token: a picture of that page is a copy of the token in a file (App::showsSecret). `key shot` presses it over the consoles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
136 lines
4.6 KiB
C++
136 lines
4.6 KiB
C++
#include "key_mapper.h"
|
|
|
|
#include <algorithm>
|
|
|
|
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<KeyEvent> KeyMapper::update(const RawKeys& keys) {
|
|
std::vector<KeyEvent> 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<KeyEvent>& 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<unsigned char>(accent), keys));
|
|
out.push_back(charEvent(static_cast<unsigned char>(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 'p':
|
|
case 'P':
|
|
if (keys.fn) { // Fn+p: a screenshot, while typing too
|
|
out.push_back(KeyEvent::of(Key::Screenshot));
|
|
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<unsigned char>(c), keys));
|
|
}
|
|
|
|
} // namespace roro
|