init commit

This commit is contained in:
Luxferre
2026-10-19 09:23:54 +03:00
commit 6942acf5cf
307 changed files with 35003 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
cmake_minimum_required(VERSION 3.21)
project(fcitx5-t9 VERSION 0.0.1)
find_package(Fcitx5Core REQUIRED)
# Setup some compiler option that is generally useful and compatible with Fcitx 5 (C++17)
# include("${FCITX_INSTALL_CMAKECONFIG_DIR}/Fcitx5Utils/Fcitx5CompilerSettings.cmake")
add_subdirectory(src)
+12
View File
@@ -0,0 +1,12 @@
add_library(t9 SHARED t9.cpp)
target_link_libraries(t9 PRIVATE Fcitx5::Core)
set_target_properties(t9 PROPERTIES PREFIX "")
install(TARGETS t9 DESTINATION "${FCITX_INSTALL_LIBDIR}/fcitx5")
# Addon config file
configure_file(t9-addon.conf t9-addon.conf)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/t9-addon.conf" RENAME t9.conf DESTINATION "${FCITX_INSTALL_PKGDATADIR}/addon")
# Input Method registration file
configure_file(t9-inputmethod.conf t9-inputmethod.conf)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/t9-inputmethod.conf" RENAME t9.conf DESTINATION "${FCITX_INSTALL_PKGDATADIR}/inputmethod")
+12
View File
@@ -0,0 +1,12 @@
[Addon]
Name=t9
Category=InputMethod
Enabled=True
Library=t9
Type=SharedLibrary
OnDemand=False
RegisterInputMethod=True
Configurable=False
[Addon/Dependencies]
0=core
+7
View File
@@ -0,0 +1,7 @@
[InputMethod]
Name=T9
Icon=input-keyboard
Label=T9
LangCode=en
Addon=t9
Configurable=False
+922
View File
@@ -0,0 +1,922 @@
#include "t9.h"
#include <fcitx-utils/event.h>
#include <fcitx/inputcontext.h>
#include <fcitx/inputpanel.h>
#include <fcitx/text.h>
#include <cctype>
#include <cstdint>
#include <dlfcn.h>
#include <string_view>
#include <typeinfo>
namespace {
struct wl_proxy;
struct wl_interface;
using WlProxyGetVersionFn = uint32_t (*)(struct wl_proxy*);
using WlProxyMarshalFlagsFn = struct wl_proxy* (*)(
struct wl_proxy*, uint32_t, const struct wl_interface*, uint32_t, uint32_t,
...);
void sendWaylandVkModifiers(fcitx::InputContext* ic, uint32_t modsDepressed) {
if (!ic || ic->frontendName() != std::string_view("wayland_v2")) {
return;
}
static void* wlHandle = dlopen("libwayland-client.so.0", RTLD_LAZY);
static auto getVer =
wlHandle ? reinterpret_cast<WlProxyGetVersionFn>(
dlsym(wlHandle, "wl_proxy_get_version"))
: nullptr;
static auto marshalFlags =
wlHandle ? reinterpret_cast<WlProxyMarshalFlagsFn>(
dlsym(wlHandle, "wl_proxy_marshal_flags"))
: nullptr;
if (!getVer || !marshalFlags) {
return;
}
auto* raw = reinterpret_cast<uint8_t*>(ic);
std::string_view typeName(typeid(*ic).name());
if (typeName.find("VirtualInputContext") != std::string_view::npos) {
raw = *reinterpret_cast<uint8_t**>(raw + 3 * sizeof(void*));
if (!raw) {
return;
}
}
if (!raw[54]) {
return;
}
void* vkObj = *reinterpret_cast<void**>(raw + 40);
if (!vkObj) {
return;
}
auto* proxy = *reinterpret_cast<struct wl_proxy**>(
reinterpret_cast<uint8_t*>(vkObj) + 2 * sizeof(void*));
if (!proxy) {
return;
}
constexpr uint32_t kZwpVirtualKeyboardV1ModifiersOpcode = 2;
uint32_t ver = getVer(proxy);
marshalFlags(proxy, kZwpVirtualKeyboardV1ModifiersOpcode, nullptr, ver, 0,
modsDepressed, 0u, 0u, 0u);
}
constexpr int kKey1 = 1;
constexpr int kKey2 = 2;
constexpr int kKey3 = 3;
constexpr int kKey4 = 4;
constexpr int kKey5 = 5;
constexpr int kKey6 = 6;
constexpr int kKey7 = 7;
constexpr int kKey8 = 8;
constexpr int kKey9 = 9;
constexpr int kKey0 = 10;
constexpr int kKeyStar = 11;
constexpr int kKeyPound = 12;
T9Item makeChar(char c) {
std::string s(1, c);
std::string lbl = (c == ' ') ? "SPC" : s;
return T9Item{lbl, s, FcitxKey_None, fcitx::KeyState::NoState, 0};
}
std::vector<T9Item> makeCharItems(const std::string& chars) {
std::vector<T9Item> out;
out.reserve(chars.size());
for (char c : chars) {
out.push_back(makeChar(c));
}
return out;
}
T9Item makeKey(const std::string& label, fcitx::KeySym sym, uint32_t xkbCode,
fcitx::KeyStates states = fcitx::KeyState::NoState) {
return T9Item{label, "", sym, states, xkbCode};
}
T9Item makeCtrlKey(const std::string& label, fcitx::KeySym sym,
uint32_t xkbCode) {
return T9Item{label, "", sym, fcitx::KeyState::Ctrl, xkbCode};
}
bool charToCtrlKey(char ch, fcitx::KeySym* symOut, uint32_t* xkbCodeOut) {
char lower = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
switch (lower) {
case 'a':
*symOut = FcitxKey_a;
*xkbCodeOut = 38;
return true;
case 'b':
*symOut = FcitxKey_b;
*xkbCodeOut = 56;
return true;
case 'c':
*symOut = FcitxKey_c;
*xkbCodeOut = 54;
return true;
case 'd':
*symOut = FcitxKey_d;
*xkbCodeOut = 40;
return true;
case 'e':
*symOut = FcitxKey_e;
*xkbCodeOut = 26;
return true;
case 'f':
*symOut = FcitxKey_f;
*xkbCodeOut = 41;
return true;
case 'g':
*symOut = FcitxKey_g;
*xkbCodeOut = 42;
return true;
case 'h':
*symOut = FcitxKey_h;
*xkbCodeOut = 43;
return true;
case 'i':
*symOut = FcitxKey_i;
*xkbCodeOut = 31;
return true;
case 'j':
*symOut = FcitxKey_j;
*xkbCodeOut = 44;
return true;
case 'k':
*symOut = FcitxKey_k;
*xkbCodeOut = 45;
return true;
case 'l':
*symOut = FcitxKey_l;
*xkbCodeOut = 46;
return true;
case 'm':
*symOut = FcitxKey_m;
*xkbCodeOut = 58;
return true;
case 'n':
*symOut = FcitxKey_n;
*xkbCodeOut = 57;
return true;
case 'o':
*symOut = FcitxKey_o;
*xkbCodeOut = 32;
return true;
case 'p':
*symOut = FcitxKey_p;
*xkbCodeOut = 33;
return true;
case 'q':
*symOut = FcitxKey_q;
*xkbCodeOut = 24;
return true;
case 'r':
*symOut = FcitxKey_r;
*xkbCodeOut = 27;
return true;
case 's':
*symOut = FcitxKey_s;
*xkbCodeOut = 39;
return true;
case 't':
*symOut = FcitxKey_t;
*xkbCodeOut = 28;
return true;
case 'u':
*symOut = FcitxKey_u;
*xkbCodeOut = 30;
return true;
case 'v':
*symOut = FcitxKey_v;
*xkbCodeOut = 55;
return true;
case 'w':
*symOut = FcitxKey_w;
*xkbCodeOut = 25;
return true;
case 'x':
*symOut = FcitxKey_x;
*xkbCodeOut = 53;
return true;
case 'y':
*symOut = FcitxKey_y;
*xkbCodeOut = 29;
return true;
case 'z':
*symOut = FcitxKey_z;
*xkbCodeOut = 52;
return true;
case '[':
*symOut = FcitxKey_bracketleft;
*xkbCodeOut = 34;
return true;
case '\\':
*symOut = FcitxKey_backslash;
*xkbCodeOut = 51;
return true;
case ']':
*symOut = FcitxKey_bracketright;
*xkbCodeOut = 35;
return true;
case '^':
case '6':
*symOut = FcitxKey_6;
*xkbCodeOut = 15;
return true;
case '_':
case '-':
*symOut = FcitxKey_minus;
*xkbCodeOut = 20;
return true;
default:
return false;
}
}
int mapHardwareKey(const fcitx::Key& key) {
uint32_t code = key.code();
// XKB keycodes: KEY_1..KEY_9 = 10..18, KEY_0 = 19
if (code >= 10 && code <= 19) {
return static_cast<int>(code - 9);
}
// KEY_NUMERIC_STAR (evdev 522 -> XKB 530)
if (code == 530 || key.sym() == FcitxKey_asterisk ||
static_cast<uint32_t>(key.sym()) == 0x1008ffaa) {
return kKeyStar;
}
// KEY_NUMERIC_POUND (evdev 523 -> XKB 531)
if (code == 531 || key.sym() == FcitxKey_numbersign ||
static_cast<uint32_t>(key.sym()) == 0x1008ffab) {
return kKeyPound;
}
return -1;
}
} // namespace
T9Engine::T9Engine(fcitx::Instance* instance)
: instance(instance), factory([this](fcitx::InputContext& ic) {
return new T9State(this, &ic);
}) {
instance->inputContextManager().registerProperty("t9State", &factory);
}
std::vector<fcitx::InputMethodEntry> T9Engine::listInputMethods() {
std::vector<fcitx::InputMethodEntry> result;
result.emplace_back("t9", "T9", "en", "t9")
.setLabel("T9")
.setIcon("input-keyboard")
.setConfigurable(false);
return result;
}
void T9Engine::keyEvent(const fcitx::InputMethodEntry& entry,
fcitx::KeyEvent& keyEvent) {
FCITX_UNUSED(entry);
FCITX_INFO() << "T9 keyEvent code=" << keyEvent.key().code()
<< " sym=" << keyEvent.key().sym()
<< " isRelease=" << keyEvent.isRelease();
auto* ic = keyEvent.inputContext();
auto* state = ic->propertyFor(&factory);
if (state->handleKeyEvent(keyEvent)) {
keyEvent.filterAndAccept();
}
}
void T9Engine::reset(const fcitx::InputMethodEntry& entry,
fcitx::InputContextEvent& event) {
FCITX_UNUSED(entry);
auto* ic = event.inputContext();
auto* state = ic->propertyFor(&factory);
state->reset();
}
FCITX_ADDON_FACTORY(T9EngineFactory);
std::string T9State::modePrefix() const {
std::string p;
switch (mode) {
case T9Mode::Lower:
p = "abc";
break;
case T9Mode::Shift:
p = "Abc";
break;
case T9Mode::Upper:
p = "ABC";
break;
case T9Mode::Sym:
p = "Sym";
break;
case T9Mode::Ctrl:
p = "Ctl";
break;
case T9Mode::Num:
p = "123";
break;
}
if (ctrlLatch) {
p += "+^";
}
return p;
}
std::vector<T9Item> T9State::getItemsForKey(int keyId) const {
if (mode == T9Mode::Sym) {
switch (keyId) {
case kKey1:
return makeCharItems(".,!?:;-_1");
case kKey2:
return makeCharItems("@#$%^&2");
case kKey3:
return makeCharItems("()[]{}<>3");
case kKey4:
return makeCharItems("+-*/=_4");
case kKey5:
return makeCharItems("|\\~`'\"5");
case kKey6:
return makeCharItems("'\"`~^6");
case kKey7:
return makeCharItems("/\\|_-+7");
case kKey8:
return makeCharItems("<>!=?&8");
case kKey9:
return makeCharItems("$%^&*#@9");
case kKey0: {
auto v = makeCharItems(" 0");
v.push_back(makeKey("Tab", FcitxKey_Tab, 23));
v.push_back(makeKey("Ent", FcitxKey_Return, 36));
v.push_back(makeKey("Esc", FcitxKey_Escape, 9));
return v;
}
case kKeyStar:
return makeCharItems("*#+-=_/\\|~`@$%^&()[]{}<>'\".,!?:;");
default:
return {};
}
}
if (mode == T9Mode::Ctrl) {
switch (keyId) {
case kKey1:
return {
makeKey("Esc", FcitxKey_Escape, 9),
makeKey("Tab", FcitxKey_Tab, 23),
makeKey("Del", FcitxKey_Delete, 119),
makeKey("Ins", FcitxKey_Insert, 118),
makeKey("Bksp", FcitxKey_BackSpace, 22),
};
case kKey2:
return {
makeCtrlKey("^C", FcitxKey_c, 54),
makeCtrlKey("^D", FcitxKey_d, 40),
makeCtrlKey("^Z", FcitxKey_z, 52),
makeCtrlKey("^X", FcitxKey_x, 53),
makeCtrlKey("^\\", FcitxKey_backslash, 51),
};
case kKey3:
return {
makeCtrlKey("^A", FcitxKey_a, 38),
makeCtrlKey("^E", FcitxKey_e, 26),
makeCtrlKey("^U", FcitxKey_u, 30),
makeCtrlKey("^K", FcitxKey_k, 45),
makeCtrlKey("^W", FcitxKey_w, 25),
};
case kKey4:
return {
makeCtrlKey("^R", FcitxKey_r, 27),
makeCtrlKey("^L", FcitxKey_l, 46),
makeCtrlKey("^P", FcitxKey_p, 33),
makeCtrlKey("^N", FcitxKey_n, 57),
makeCtrlKey("^G", FcitxKey_g, 42),
};
case kKey5:
return {
makeCtrlKey("^S", FcitxKey_s, 39),
makeCtrlKey("^Q", FcitxKey_q, 24),
makeCtrlKey("^O", FcitxKey_o, 32),
makeCtrlKey("^T", FcitxKey_t, 28),
makeCtrlKey("^Y", FcitxKey_y, 29),
};
case kKey6:
return {
makeCtrlKey("^B", FcitxKey_b, 56),
makeCtrlKey("^F", FcitxKey_f, 41),
makeCtrlKey("^V", FcitxKey_v, 55),
makeCtrlKey("^H", FcitxKey_h, 43),
makeCtrlKey("^[", FcitxKey_bracketleft, 34),
makeCtrlKey("^]", FcitxKey_bracketright, 35),
makeCtrlKey("^^", FcitxKey_6, 15),
makeCtrlKey("^_", FcitxKey_minus, 20),
};
case kKey7:
return {
makeKey("PgUp", FcitxKey_Page_Up, 112),
makeKey("PgDn", FcitxKey_Page_Down, 117),
makeKey("Home", FcitxKey_Home, 110),
makeKey("End", FcitxKey_End, 115),
};
case kKey8:
return {
makeKey("Up", FcitxKey_Up, 111),
makeKey("Down", FcitxKey_Down, 116),
makeKey("Left", FcitxKey_Left, 113),
makeKey("Right", FcitxKey_Right, 114),
};
case kKey9:
return {
makeKey("F1", FcitxKey_F1, 67), makeKey("F2", FcitxKey_F2, 68),
makeKey("F3", FcitxKey_F3, 69), makeKey("F4", FcitxKey_F4, 70),
makeKey("F5", FcitxKey_F5, 71), makeKey("F6", FcitxKey_F6, 72),
makeKey("F7", FcitxKey_F7, 73), makeKey("F8", FcitxKey_F8, 74),
makeKey("F9", FcitxKey_F9, 75), makeKey("F10", FcitxKey_F10, 76),
makeKey("F11", FcitxKey_F11, 95), makeKey("F12", FcitxKey_F12, 96),
};
case kKey0:
return {
makeChar(' '),
makeKey("Tab", FcitxKey_Tab, 23),
makeKey("Ent", FcitxKey_Return, 36),
makeKey("Esc", FcitxKey_Escape, 9),
};
case kKeyStar:
return {
makeKey("Esc", FcitxKey_Escape, 9),
makeKey("Tab", FcitxKey_Tab, 23),
makeCtrlKey("^C", FcitxKey_c, 54),
makeCtrlKey("^D", FcitxKey_d, 40),
makeCtrlKey("^Z", FcitxKey_z, 52),
makeCtrlKey("^X", FcitxKey_x, 53),
makeCtrlKey("^L", FcitxKey_l, 46),
makeCtrlKey("^R", FcitxKey_r, 27),
makeCtrlKey("^W", FcitxKey_w, 25),
};
default:
return {};
}
}
bool upperFirst = (mode == T9Mode::Shift || mode == T9Mode::Upper);
switch (keyId) {
case kKey1:
return makeCharItems(".,-_!?/@:;'\"+=*#$%^&()[]{}<>|\\~`1");
case kKey2:
return makeCharItems(upperFirst ? "ABC2abc" : "abc2ABC");
case kKey3:
return makeCharItems(upperFirst ? "DEF3def" : "def3DEF");
case kKey4:
return makeCharItems(upperFirst ? "GHI4ghi" : "ghi4GHI");
case kKey5:
return makeCharItems(upperFirst ? "JKL5jkl" : "jkl5JKL");
case kKey6:
return makeCharItems(upperFirst ? "MNO6mno" : "mno6MNO");
case kKey7:
return makeCharItems(upperFirst ? "PQRS7pqrs" : "pqrs7PQRS");
case kKey8:
return makeCharItems(upperFirst ? "TUV8tuv" : "tuv8TUV");
case kKey9:
return makeCharItems(upperFirst ? "WXYZ9wxyz" : "wxyz9WXYZ");
case kKey0: {
auto v = makeCharItems(" 0");
v.push_back(makeKey("Ent", FcitxKey_Return, 36));
v.push_back(makeKey("Tab", FcitxKey_Tab, 23));
return v;
}
case kKeyStar: {
auto v = makeCharItems("*#+-=_/\\|~`@$%^&()[]{}<>'\".,!?:;");
v.push_back(makeKey("Tab", FcitxKey_Tab, 23));
v.push_back(makeKey("Esc", FcitxKey_Escape, 9));
v.push_back(makeCtrlKey("^C", FcitxKey_c, 54));
v.push_back(makeCtrlKey("^D", FcitxKey_d, 40));
v.push_back(makeCtrlKey("^Z", FcitxKey_z, 52));
return v;
}
default:
return {};
}
}
void T9State::cycleMode() {
switch (mode) {
case T9Mode::Lower:
mode = T9Mode::Shift;
break;
case T9Mode::Shift:
mode = T9Mode::Upper;
break;
case T9Mode::Upper:
mode = T9Mode::Sym;
break;
case T9Mode::Sym:
mode = T9Mode::Ctrl;
break;
case T9Mode::Ctrl:
mode = T9Mode::Num;
break;
case T9Mode::Num:
mode = T9Mode::Lower;
break;
}
showStatusBanner("[Mode: " + modePrefix() + "]");
}
void T9State::toggleCurrentCase() {
if (activeItems.empty() || currentIndex < 0 ||
static_cast<size_t>(currentIndex) >= activeItems.size()) {
return;
}
const std::string& cur = activeItems[currentIndex].commitText;
if (cur.size() == 1 && std::isalpha(static_cast<unsigned char>(cur[0]))) {
char target = std::isupper(static_cast<unsigned char>(cur[0]))
? static_cast<char>(
std::tolower(static_cast<unsigned char>(cur[0])))
: static_cast<char>(
std::toupper(static_cast<unsigned char>(cur[0])));
for (size_t i = 0; i < activeItems.size(); ++i) {
if (activeItems[i].commitText.size() == 1 &&
activeItems[i].commitText[0] == target) {
currentIndex = static_cast<int>(i);
break;
}
}
scheduleCommit();
showPreview(activeItems, currentIndex);
} else {
stepCandidate(4);
}
}
void T9State::stepCandidate(int delta) {
if (activeItems.empty()) {
return;
}
int n = static_cast<int>(activeItems.size());
currentIndex = ((currentIndex + delta) % n + n) % n;
scheduleCommit();
showPreview(activeItems, currentIndex);
}
bool T9State::handleKeyEvent(fcitx::KeyEvent& keyEvent) {
fcitx::Key key = keyEvent.key();
uint32_t code = key.code();
fcitx::KeySym sym = key.sym();
// Handle preedit navigation when an uncommitted character is active
if (!activeItems.empty()) {
if (sym == FcitxKey_BackSpace || code == 22) {
if (!keyEvent.isRelease()) {
cancelCommit();
lastKeyId = -1;
currentIndex = 0;
activeItems.clear();
}
return true;
}
if (sym == FcitxKey_Left || code == 113) {
if (!keyEvent.isRelease()) {
stepCandidate(-1);
}
return true;
}
if (sym == FcitxKey_Right || code == 114 || sym == FcitxKey_Return ||
code == 36) {
if (!keyEvent.isRelease()) {
commit();
}
return true;
}
if (sym == FcitxKey_Up || code == 111) {
if (!keyEvent.isRelease()) {
toggleCurrentCase();
}
return true;
}
if (sym == FcitxKey_Down || code == 116) {
if (!keyEvent.isRelease()) {
if (activeItems[currentIndex].commitText.size() == 1 &&
std::isalpha(static_cast<unsigned char>(
activeItems[currentIndex].commitText[0]))) {
toggleCurrentCase();
} else {
stepCandidate(-4);
}
}
return true;
}
}
if (mode == T9Mode::Ctrl) {
if (sym == FcitxKey_Up || code == 111) {
if (!keyEvent.isRelease()) {
forwardKeyTap(FcitxKey_Page_Up, fcitx::KeyState::NoState, 112);
}
return true;
}
if (sym == FcitxKey_Down || code == 116) {
if (!keyEvent.isRelease()) {
forwardKeyTap(FcitxKey_Page_Down, fcitx::KeyState::NoState, 117);
}
return true;
}
if (sym == FcitxKey_Left || code == 113) {
if (!keyEvent.isRelease()) {
forwardKeyTap(FcitxKey_Home, fcitx::KeyState::NoState, 110);
}
return true;
}
if (sym == FcitxKey_Right || code == 114) {
if (!keyEvent.isRelease()) {
forwardKeyTap(FcitxKey_End, fcitx::KeyState::NoState, 115);
}
return true;
}
}
int keyId = mapHardwareKey(key);
if (keyId < 0) {
if (!keyEvent.isRelease()) {
commit();
}
return false;
}
if (keyEvent.isRelease()) {
cancelLongPressTimer();
int releasedId = pressedKeyId;
pressedKeyId = -1;
if (isLongPressHandled) {
isLongPressHandled = false;
return true;
}
if (releasedId != keyId && releasedId != -1) {
return true;
}
if (keyId == kKeyPound) {
if (!activeItems.empty()) {
toggleCurrentCase();
} else {
cycleMode();
}
return true;
}
if (mode == T9Mode::Num) {
if (bannerTimer) {
bannerTimer.reset();
resetUI();
}
if (keyId >= kKey1 && keyId <= kKey9) {
sendItem(makeChar(static_cast<char>('0' + keyId)));
} else if (keyId == kKey0) {
sendItem(makeChar('0'));
} else if (keyId == kKeyStar) {
sendItem(makeChar('*'));
}
return true;
}
handleMultiTap(keyId);
return true;
}
// Key press event
if (pressedKeyId != keyId) {
pressedKeyId = keyId;
isLongPressHandled = false;
startLongPressTimer(keyId);
}
return true;
}
void T9State::startLongPressTimer(int keyId) {
cancelLongPressTimer();
longPressTimer = engine->instance->eventLoop().addTimeEvent(
CLOCK_MONOTONIC, fcitx::now(CLOCK_MONOTONIC) + 450000, 0,
[this, keyId](fcitx::EventSourceTime*, uint64_t) {
onLongPress(keyId);
return true;
});
}
void T9State::cancelLongPressTimer() {
if (longPressTimer) {
longPressTimer.reset();
}
}
void T9State::onLongPress(int keyId) {
isLongPressHandled = true;
if (!activeItems.empty()) {
commit();
}
if (keyId >= kKey1 && keyId <= kKey9) {
sendItem(makeChar(static_cast<char>('0' + keyId)));
resetUI();
} else if (keyId == kKey0) {
sendItem(makeChar('0'));
resetUI();
} else if (keyId == kKeyStar) {
if (mode == T9Mode::Num) {
sendItem(makeChar('.'));
resetUI();
} else {
ctrlLatch = !ctrlLatch;
showStatusBanner(ctrlLatch ? "[Ctrl+ LATCH ON]" : "[Ctrl+ LATCH OFF]");
}
} else if (keyId == kKeyPound) {
sendItem(makeChar('#'));
resetUI();
}
}
void T9State::handleMultiTap(int keyId) {
if (bannerTimer) {
bannerTimer.reset();
}
if (lastKeyId != -1 && keyId != lastKeyId) {
commit();
}
if (keyId == lastKeyId && !activeItems.empty()) {
currentIndex = (currentIndex + 1) % static_cast<int>(activeItems.size());
} else {
activeItems = getItemsForKey(keyId);
currentIndex = 0;
lastKeyId = keyId;
}
if (activeItems.empty()) {
return;
}
uint64_t delayUs = (activeItems.size() > 10) ? 750000 : 600000;
scheduleCommit(delayUs);
showPreview(activeItems, currentIndex);
}
void T9State::reset() {
cancelLongPressTimer();
cancelCommit();
if (bannerTimer) {
bannerTimer.reset();
}
lastKeyId = -1;
pressedKeyId = -1;
currentIndex = 0;
activeItems.clear();
isLongPressHandled = false;
ctrlLatch = false;
}
void T9State::forwardKeyTap(fcitx::KeySym sym, fcitx::KeyStates states,
uint32_t xkbCode) {
if (states.test(fcitx::KeyState::Ctrl)) {
constexpr uint32_t kXkbModCtrl = (1u << 2);
sendWaylandVkModifiers(ic, kXkbModCtrl);
ic->forwardKey(fcitx::Key(sym, fcitx::KeyState::Ctrl, xkbCode), false);
sendWaylandVkModifiers(ic, 0u);
} else {
ic->forwardKey(fcitx::Key(sym, states, xkbCode), false);
}
}
void T9State::sendItem(const T9Item& item) {
if (ctrlLatch && !item.commitText.empty()) {
fcitx::KeySym cSym = FcitxKey_None;
uint32_t cCode = 0;
ctrlLatch = false;
if (charToCtrlKey(item.commitText[0], &cSym, &cCode)) {
forwardKeyTap(cSym, fcitx::KeyState::Ctrl, cCode);
return;
}
}
ctrlLatch = false;
if (item.sym != FcitxKey_None) {
forwardKeyTap(item.sym, item.states, item.xkbCode);
} else if (!item.commitText.empty()) {
ic->commitString(item.commitText);
if (mode == T9Mode::Shift &&
std::isalpha(static_cast<unsigned char>(item.commitText[0]))) {
mode = T9Mode::Lower;
}
}
}
void T9State::commit() {
if (commitTimer) {
commitTimer.reset();
}
if (!activeItems.empty() && currentIndex >= 0 &&
static_cast<size_t>(currentIndex) < activeItems.size()) {
T9Item chosen = activeItems[currentIndex];
activeItems.clear();
lastKeyId = -1;
currentIndex = 0;
resetUI();
sendItem(chosen);
} else {
activeItems.clear();
lastKeyId = -1;
currentIndex = 0;
resetUI();
}
}
void T9State::scheduleCommit(uint64_t delayUs) {
if (commitTimer) {
commitTimer.reset();
}
commitTimer = engine->instance->eventLoop().addTimeEvent(
CLOCK_MONOTONIC, fcitx::now(CLOCK_MONOTONIC) + delayUs, 0,
[this](fcitx::EventSourceTime*, uint64_t) {
commit();
return true;
});
}
void T9State::cancelCommit() {
if (commitTimer) {
commitTimer.reset();
}
resetUI();
}
void T9State::resetUI() {
ic->inputPanel().reset();
ic->updatePreedit();
ic->updateUserInterface(fcitx::UserInterfaceComponent::InputPanel);
}
void T9State::showStatusBanner(const std::string& msg) {
if (bannerTimer) {
bannerTimer.reset();
}
auto& inputPanel = ic->inputPanel();
inputPanel.reset();
fcitx::Text text;
text.append(msg);
if (ic->capabilityFlags().test(fcitx::CapabilityFlag::Preedit)) {
inputPanel.setClientPreedit(text);
ic->updatePreedit();
} else {
inputPanel.setPreedit(text);
}
ic->updateUserInterface(fcitx::UserInterfaceComponent::InputPanel);
bannerTimer = engine->instance->eventLoop().addTimeEvent(
CLOCK_MONOTONIC, fcitx::now(CLOCK_MONOTONIC) + 900000, 0,
[this](fcitx::EventSourceTime*, uint64_t) {
resetUI();
return true;
});
}
void T9State::showPreview(const std::vector<T9Item>& items, int index) {
int total = static_cast<int>(items.size());
if (total == 0) {
return;
}
constexpr int kMaxVisible = 7;
int start = 0;
int end = total;
if (total > kMaxVisible) {
start = index - (kMaxVisible / 2);
if (start < 0) {
start = 0;
}
end = start + kMaxVisible;
if (end > total) {
end = total;
start = end - kMaxVisible;
}
}
std::string preedit = modePrefix() + ": ";
if (start > 0) {
preedit += "< ";
}
for (int i = start; i < end; ++i) {
if (i == index) {
preedit += "[";
preedit += items[i].label;
preedit += "]";
} else {
preedit += items[i].label;
}
if (i < end - 1) {
preedit += " ";
}
}
if (end < total) {
preedit += " >";
}
auto& inputPanel = ic->inputPanel();
inputPanel.reset();
fcitx::Text text;
text.append(preedit);
if (ic->capabilityFlags().test(fcitx::CapabilityFlag::Preedit)) {
inputPanel.setClientPreedit(text);
ic->updatePreedit();
} else {
inputPanel.setPreedit(text);
}
ic->updateUserInterface(fcitx::UserInterfaceComponent::InputPanel);
}
+101
View File
@@ -0,0 +1,101 @@
#ifndef _FCITX5_T9_T9_H_
#define _FCITX5_T9_T9_H_
#include <fcitx-utils/event.h>
#include <fcitx-utils/key.h>
#include <fcitx-utils/keysym.h>
#include <fcitx/addonfactory.h>
#include <fcitx/addonmanager.h>
#include <fcitx/inputcontext.h>
#include <fcitx/inputmethodengine.h>
#include <fcitx/instance.h>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>
class T9Engine;
enum class T9Mode {
Lower = 0, // abc: lowercase first, then digit, then uppercase
Shift = 1, // Abc: uppercase for next character, then auto-reverts to Lower
Upper = 2, // ABC: uppercase first, then digit, then lowercase
Sym = 3, // Sym: full ASCII punctuation & symbols grouped across 1-9, 0, *
Ctrl = 4, // Ctl: terminal control chars (^A-^Z) & navigation/function keys
Num = 5 // 123: instant single-tap numeric entry
};
struct T9Item {
std::string label;
std::string commitText;
fcitx::KeySym sym = FcitxKey_None;
fcitx::KeyStates states = fcitx::KeyState::NoState;
uint32_t xkbCode = 0;
};
class T9State : public fcitx::InputContextProperty {
public:
T9State(T9Engine* engine, fcitx::InputContext* ic) : engine(engine), ic(ic) {}
bool handleKeyEvent(fcitx::KeyEvent& keyEvent);
void cancelCommit();
void reset();
private:
std::vector<T9Item> getItemsForKey(int keyId) const;
std::string modePrefix() const;
void cycleMode();
void toggleCurrentCase();
void stepCandidate(int delta);
void startLongPressTimer(int keyId);
void cancelLongPressTimer();
void onLongPress(int keyId);
void scheduleCommit(uint64_t delayUs = 600000);
void commit();
void sendItem(const T9Item& item);
void forwardKeyTap(fcitx::KeySym sym, fcitx::KeyStates states,
uint32_t xkbCode);
void resetUI();
void showPreview(const std::vector<T9Item>& items, int index);
void showStatusBanner(const std::string& msg);
void handleMultiTap(int keyId);
T9Engine* engine;
fcitx::InputContext* ic;
T9Mode mode = T9Mode::Lower;
bool ctrlLatch = false;
int lastKeyId = -1;
int pressedKeyId = -1;
int currentIndex = 0;
std::vector<T9Item> activeItems;
std::unique_ptr<fcitx::EventSourceTime> commitTimer;
std::unique_ptr<fcitx::EventSourceTime> longPressTimer;
std::unique_ptr<fcitx::EventSourceTime> bannerTimer;
bool isLongPressHandled = false;
};
class T9Engine : public fcitx::InputMethodEngineV2 {
public:
explicit T9Engine(fcitx::Instance* instance);
std::vector<fcitx::InputMethodEntry> listInputMethods() override;
void keyEvent(const fcitx::InputMethodEntry& entry,
fcitx::KeyEvent& keyEvent) override;
void reset(const fcitx::InputMethodEntry& entry,
fcitx::InputContextEvent& event) override;
fcitx::Instance* instance;
private:
fcitx::FactoryFor<T9State> factory;
};
class T9EngineFactory : public fcitx::AddonFactory {
public:
fcitx::AddonInstance* create(fcitx::AddonManager* manager) override {
FCITX_UNUSED(manager);
return new T9Engine(manager->instance());
}
};
#endif // _FCITX5_T9_T9_H_