#include "core.h" #include #include #include #include namespace ks { int HotkeyHook::update(UINT key, bool down, UINT modifiers, const std::vector> &bindings) { if (key >= 256) return 0; if (pressed[key]) { if (!down) pressed[key] = false; return -1; } if (down) for (const auto &[command, hotkey] : bindings) if (hotkey.key == key && hotkey.modifiers == modifiers) { pressed[key] = true; return command; } return 0; } bool letter(wchar_t c) { WORD type{}; GetStringTypeW(CT_CTYPE1, &c, 1, &type); return (type & C1_ALPHA) != 0; } std::wstring lower(std::wstring s) { if (!s.empty()) CharLowerBuffW(s.data(), (DWORD)s.size()); return s; } std::wstring upper(std::wstring s) { if (!s.empty()) CharUpperBuffW(s.data(), (DWORD)s.size()); return s; } int Layout::code(wchar_t c) const { auto p = normal.find(c); if (p != normal.npos && c) return (int)p; p = shifted.find(c); return p == shifted.npos || !c ? -1 : (int)(p + normal.size()); } wchar_t Layout::at(int c) const { if (c < 0) return 0; return c < (int)normal.size() ? normal[c] : c < (int)(normal.size() + shifted.size()) ? shifted[c - normal.size()] : 0; } bool Layout::hasLetter(wchar_t c) const { return letter(c) && code(c) >= 0; } std::wstring convert(const std::wstring &text, const Layout &from, const Layout &to, bool force) { std::wstring result; for (size_t i = 0; i < text.size();) { if (iswspace(text[i])) { result += text[i++]; continue; } size_t end = i; bool source = false, other = false; while (end < text.size() && !iswspace(text[end])) { wchar_t c = text[end++]; if (!letter(c)) continue; if (from.hasLetter(c) && !to.hasLetter(c)) source = true; else other = true; } for (; i < end; ++i) { wchar_t mapped = (!force && !source && other) ? 0 : to.at(from.code(text[i])); result += mapped ? mapped : text[i]; } } return result; } int detect(const std::wstring &text, const std::vector &layouts, int preferred) { int best = -1, score = 0; auto check = [&](int i) { if (i < 0 || i >= (int)layouts.size() || !layouts[i].convertible) return; int n = (int)std::count_if(text.begin(), text.end(), [&](wchar_t c) { return layouts[i].hasLetter(c); }); if (n > score) { score = n; best = i; } }; check(preferred); for (int i = 0; i < (int)layouts.size(); ++i) check(i); return best; } std::wstring convertTo(const std::wstring &text, const std::vector &layouts, int target, int hint) { if (target < 0 || target >= (int)layouts.size() || !layouts[target].convertible) return text; const auto &to = layouts[target]; if (hint >= 0 && hint < (int)layouts.size() && layouts[hint].convertible && std::all_of( text.begin(), text.end(), [&](wchar_t c) { return !letter(c) || layouts[hint].code(c) >= 0; })) return hint == target ? text : convert(text, layouts[hint], to, true); int best = -1, score = 0; for (int i = 0; i < (int)layouts.size(); ++i) if (i != target && layouts[i].convertible) { int n = (int)std::count_if(text.begin(), text.end(), [&](wchar_t c) { return layouts[i].hasLetter(c) && !to.hasLetter(c); }); if (n > score) { score = n; best = i; } } if (best < 0 && detect(text, layouts) < 0) { best = hint; if (best < 0) for (int i = 0; i < (int)layouts.size(); ++i) if (i != target && layouts[i].convertible) { best = i; break; } } return best < 0 ? text : convert(text, layouts[best], to); } std::wstring cycleCase(const std::wstring &text) { if (text == upper(text)) return lower(text); if (text != lower(text)) return upper(text); auto out = lower(text); bool start = true; for (auto &c : out) { if (letter(c)) { if (start) c = upper(std::wstring(1, c))[0]; start = false; } else start = !iswdigit(c) && c != L'\'' && c != L'’'; } return out; } std::wstring transliterate(const std::wstring &text) { static const std::wstring ru = L"абвгдеёжзийклмнопрстуфхцчшщъыьэюя"; static const std::vector lat = {L"a", L"b", L"v", L"g", L"d", L"e", L"yo", L"zh", L"z", L"i", L"y", L"k", L"l", L"m", L"n", L"o", L"p", L"r", L"s", L"t", L"u", L"f", L"kh", L"ts", L"ch", L"sh", L"shch", L"", L"y", L"", L"e", L"yu", L"ya"}; bool cyr = std::any_of(text.begin(), text.end(), [](wchar_t c) { return c >= 0x400 && c <= 0x4ff; }); std::wstring out, lo = lower(text); if (cyr) { bool uk = lo.find_first_of(L"ґіїє") != lo.npos; const std::map overrides = {{L'г', L"h"}, {L'ґ', L"g"}, {L'и', L"y"}, {L'і', L"i"}, {L'ї', L"yi"}, {L'є', L"ye"}, {L'\'', L""}, {L'’', L""}}; for (size_t i = 0; i < text.size(); ++i) { std::wstring m; auto p = ru.find(lo[i]); auto u = overrides.find(lo[i]); if (uk && u != overrides.end()) { m = u->second; if (i && letter(text[i - 1])) { if (lo[i] == L'ї') m = L"i"; if (lo[i] == L'є') m = L"ie"; } } else if (p != ru.npos) m = lat[p]; else { out += text[i]; continue; } if (text[i] != lo[i] && !m.empty()) { bool all = (i + 1 < text.size() && text[i + 1] != lo[i + 1]) || (i > 0 && text[i - 1] != lo[i - 1]); if (all) m = upper(m); else m[0] = upper(m.substr(0, 1))[0]; } out += m; } } else { static const std::vector> reverse = { {L"shch", L"щ"}, {L"zh", L"ж"}, {L"kh", L"х"}, {L"ts", L"ц"}, {L"ch", L"ч"}, {L"sh", L"ш"}, {L"yo", L"ё"}, {L"yu", L"ю"}, {L"ya", L"я"}, {L"ye", L"е"}, {L"a", L"а"}, {L"b", L"б"}, {L"v", L"в"}, {L"w", L"в"}, {L"g", L"г"}, {L"d", L"д"}, {L"e", L"е"}, {L"z", L"з"}, {L"i", L"и"}, {L"y", L"й"}, {L"k", L"к"}, {L"q", L"к"}, {L"l", L"л"}, {L"m", L"м"}, {L"n", L"н"}, {L"o", L"о"}, {L"p", L"п"}, {L"r", L"р"}, {L"s", L"с"}, {L"t", L"т"}, {L"u", L"у"}, {L"f", L"ф"}, {L"h", L"х"}, {L"c", L"к"}, {L"x", L"кс"}, {L"j", L"дж"}}; for (size_t i = 0; i < text.size();) { bool found = false; for (const auto &[a, b] : reverse) if (lo.compare(i, a.size(), a) == 0) { auto m = b; if (text[i] != lo[i]) { bool all = a.size() > 1 ? text[i + a.size() - 1] != lo[i + a.size() - 1] : i + 1 < text.size() && text[i + 1] != lo[i + 1]; if (all) m = upper(m); else m[0] = upper(m.substr(0, 1))[0]; } out += m; i += a.size(); found = true; break; } if (!found) out += text[i++]; } } return out; } Template parseTemplate(const std::wstring &text) { Template t; size_t cursor = std::wstring::npos; for (size_t i = 0; i < text.size();) { if (text.compare(i, 9, L"{{enter}}") == 0) { t.text += L"{enter}"; i += 9; } else if (text.compare(i, 7, L"{enter}") == 0) { ++t.enters; i += 7; } else if (text.compare(i, 10, L"{{cursor}}\0", 10) == 0) { t.text += L"{cursor}"; i += 10; } else if (text.compare(i, 8, L"{cursor}") == 0) { cursor = t.text.size(); ++t.cursors; i += 8; } else t.text += text[i++]; } if (cursor != text.npos) t.suffix = t.text.substr(cursor); return t; } namespace { const std::vector> &namedKeys() { static const std::vector> names = { {VK_SPACE, L"Space"}, {VK_TAB, L"Tab"}, {VK_RETURN, L"Enter"}, {VK_ESCAPE, L"Escape"}, {VK_BACK, L"Backspace"}, {VK_DELETE, L"Delete"}, {VK_INSERT, L"Insert"}, {VK_PAUSE, L"Pause"}, {VK_SCROLL, L"ScrollLock"}, {VK_HOME, L"Home"}, {VK_END, L"End"}, {VK_PRIOR, L"PageUp"}, {VK_NEXT, L"PageDown"}, {VK_LEFT, L"Left"}, {VK_RIGHT, L"Right"}, {VK_UP, L"Up"}, {VK_DOWN, L"Down"}, {VK_SNAPSHOT, L"PrintScreen"}, {VK_OEM_1, L"Semicolon"}, {VK_OEM_2, L"Slash"}, {VK_OEM_3, L"Backtick"}, {VK_OEM_4, L"LeftBracket"}, {VK_OEM_5, L"Backslash"}, {VK_OEM_6, L"RightBracket"}, {VK_OEM_7, L"Quote"}, {VK_OEM_PLUS, L"Equal"}, {VK_OEM_MINUS, L"Minus"}, {VK_OEM_COMMA, L"Comma"}, {VK_OEM_PERIOD, L"Period"}, {VK_MULTIPLY, L"Multiply"}, {VK_ADD, L"Add"}, {VK_SUBTRACT, L"Subtract"}, {VK_DIVIDE, L"Divide"}, {VK_DECIMAL, L"Decimal"}}; return names; } } // namespace std::wstring Hotkey::format() const { if (!key) return {}; std::wstring result; if (modifiers & MOD_CONTROL) result += L"Ctrl+"; if (modifiers & MOD_ALT) result += L"Alt+"; if (modifiers & MOD_SHIFT) result += L"Shift+"; if (modifiers & MOD_WIN) result += L"Win+"; if ((key >= L'A' && key <= L'Z') || (key >= L'0' && key <= L'9')) return result + wchar_t(key); if (key >= VK_F1 && key <= VK_F24) return result + L"F" + std::to_wstring(key - VK_F1 + 1); if (key >= VK_NUMPAD0 && key <= VK_NUMPAD9) return result + L"Num" + std::to_wstring(key - VK_NUMPAD0); for (auto &[vk, name] : namedKeys()) if (key == vk) return result + name; return {}; } std::optional Hotkey::parse(std::wstring s) { Hotkey h; if (s.empty()) return h; s = lower(s); size_t pos = 0; while (true) { size_t end = s.find(L'+', pos); auto token = s.substr(pos, end == s.npos ? s.npos : end - pos); if (token == L"ctrl") h.modifiers |= MOD_CONTROL; else if (token == L"alt") h.modifiers |= MOD_ALT; else if (token == L"shift") h.modifiers |= MOD_SHIFT; else if (token == L"win") h.modifiers |= MOD_WIN; else { if (h.key) return {}; if (token.size() == 1 && ((token[0] >= L'a' && token[0] <= L'z') || iswdigit(token[0]))) h.key = upper(token)[0]; else if (token.size() == 4 && token.substr(0, 3) == L"num" && token[3] >= L'0' && token[3] <= L'9') h.key = VK_NUMPAD0 + token[3] - L'0'; else if (token.size() >= 2 && token[0] == L'f') { wchar_t *tail{}; long n = wcstol(token.c_str() + 1, &tail, 10); if (*tail || n < 1 || n > 24) return {}; h.key = VK_F1 + (UINT)n - 1; } else { for (auto &[key, name] : namedKeys()) if (lower(name) == token) { h.key = key; break; } if (!h.key) return {}; } } if (end == s.npos) break; pos = end + 1; } return h.key ? std::optional(h) : std::nullopt; } std::string utf8(const std::wstring &s) { if (s.empty()) return {}; int n = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s.data(), (int)s.size(), nullptr, 0, nullptr, nullptr); if (!n) throw std::runtime_error("Invalid Unicode"); std::string out(n, 0); WideCharToMultiByte(CP_UTF8, 0, s.data(), (int)s.size(), out.data(), n, nullptr, nullptr); return out; } std::wstring wide(const std::string &s) { if (s.empty()) return {}; int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), (int)s.size(), nullptr, 0); if (!n) throw std::runtime_error("Invalid UTF-8"); std::wstring out(n, 0); MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), out.data(), n); return out; } void Run::reset() { keys.clear(); window = nullptr; layout = nullptr; overflow = false; } void Run::record(wchar_t c, HWND w, HKL l, ULONGLONG now) { if (!w || c < 32) { reset(); return; } if (window != w || layout != l || now - at >= 2000 || (!keys.empty() && iswspace(keys.back()) && !iswspace(c))) reset(); window = w; layout = l; at = now; if (overflow) { if (iswspace(c)) reset(); return; } if (keys.empty() && iswspace(c)) return; if (keys.size() >= 512) { keys.clear(); overflow = true; return; } keys += c; } void Run::backspace(HWND w, ULONGLONG now) { if (w != window || now - at >= 2000 || overflow) reset(); else if (!keys.empty()) keys.pop_back(); at = now; } int Run::match(const std::wstring &text, const std::vector &layouts, const Layout &physical) const { if (text.size() != keys.size() || overflow) return -1; for (int i = 0; i < (int)layouts.size(); ++i) if (layouts[i].convertible) { std::wstring expected; for (wchar_t c : keys) expected += c == L' ' ? L' ' : layouts[i].at(physical.code(c)); if (lower(expected) == lower(text)) return i; } return -1; } } // namespace ks