fix(linux): add QA and translation modifier hotkey support to fcitx5 plugin
Extend the fcitx5 plugin with SetQaHotkeyRaw/SetTranslationHotkeyRaw DBus methods and corresponding signals, so that QA panel toggle and translation modifier hotkeys work on Linux (not just the main dictation hotkey). Built-in Shift key translation modifier also handled by the plugin. Document DBus security model in plugin header. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
b349a4dcba
commit
323dbed401
3 files changed
+234
-60
No files matched your search
@@ -7,13 +7,18 @@
|
||||
*
|
||||
* DBus 接口: org.fcitx.Fcitx.OpenLess1 (对象路径 /openless)
|
||||
* 方法:
|
||||
* CommitText(s: text) — 将文字提交到当前焦点输入上下文
|
||||
* SetHotkey(as: keys) — 设置听写触发快捷键 (Key::parse 格式)
|
||||
* SetHotkeyRaw(uu: sym, states) — 直接设 sym+states (不走 parse)
|
||||
* CommitText(s: text) — 将文字提交到当前焦点输入上下文
|
||||
* 安全性:本接口在会话总线(session bus)上对同用户
|
||||
* 所有进程开放,此为 fcitx5/IBus 体系的标准安全模型
|
||||
* (非特权进程隔离)。
|
||||
* SetHotkey(as: keys) — 设置听写触发快捷键 (Key::parse 格式)
|
||||
* SetHotkeyRaw(uu: sym, states) — 直接设听写触发 sym+states (不走 parse)
|
||||
* SetQaHotkeyRaw(uu: sym, states) — 直接设 QA 面板触发 sym+states
|
||||
* SetTranslationHotkeyRaw(uu: sym, states) — 直接设翻译模式触发 sym+states
|
||||
* 信号:
|
||||
* DictationKeyEvent(uu: sym, states) — 热键被按下
|
||||
*
|
||||
* 后续: 当需要 IBus 引擎兼容时 (GNOME),另行实现 org.freedesktop.IBus.Engine。
|
||||
* DictationKeyEvent(uub: sym, states, isPress) — 听写热键按下/抬起
|
||||
* QaShortcutEvent(uub: sym, states, isPress) — QA 快捷键按下/抬起
|
||||
* TranslationModifierEvent(uub: sym, states, isPress) — 翻译修饰键按下/抬起
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
@@ -57,6 +62,10 @@ public:
|
||||
: instance_(instance),
|
||||
triggerRawSym_(0),
|
||||
triggerRawStates_(0),
|
||||
qaRawSym_(0),
|
||||
qaRawStates_(0),
|
||||
translationRawSym_(0),
|
||||
translationRawStates_(0),
|
||||
savedIc_(nullptr) {
|
||||
|
||||
// 1. 读取配置
|
||||
@@ -91,34 +100,73 @@ public:
|
||||
auto &keyEvent = static_cast<KeyEvent &>(event);
|
||||
// 保存当前输入上下文:快捷键按下时用户在目标 app 中,
|
||||
// 此后胶囊窗口可能抢走焦点,但 commitText 仍能用此 IC 提交文字。
|
||||
// IC 销毁时通过 InputContextDestroyed 事件自动清空指针(见下方)。
|
||||
if (!keyEvent.isRelease()) {
|
||||
savedIc_ = keyEvent.inputContext();
|
||||
}
|
||||
// 先检查 raw sym/states(修饰键专用路径,绕过 Key::parse 限制)
|
||||
|
||||
auto sym = static_cast<uint32_t>(keyEvent.key().sym());
|
||||
auto states = static_cast<uint32_t>(keyEvent.key().states());
|
||||
bool isPress = !keyEvent.isRelease();
|
||||
|
||||
// 检查听写触发键(raw + keylist 双路径)
|
||||
bool dictationMatched = false;
|
||||
if ((triggerRawSym_ != 0 &&
|
||||
keyEvent.key().sym() == static_cast<KeySym>(triggerRawSym_) &&
|
||||
keyEvent.key().states() == static_cast<KeyStates>(triggerRawStates_)) ||
|
||||
sym == triggerRawSym_ &&
|
||||
states == triggerRawStates_) ||
|
||||
(triggerRawSym_ == 0 && [&]() {
|
||||
for (const auto &hk : triggerKeyList_) {
|
||||
if (keyEvent.key().sym() == hk.sym() &&
|
||||
keyEvent.key().states() == hk.states())
|
||||
if (sym == static_cast<uint32_t>(hk.sym()) &&
|
||||
states == static_cast<uint32_t>(hk.states()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}())) {
|
||||
auto sym = triggerRawSym_ != 0
|
||||
dictationMatched = true;
|
||||
auto dsym = triggerRawSym_ != 0
|
||||
? triggerRawSym_
|
||||
: static_cast<uint32_t>(triggerKeyList_[0].sym());
|
||||
auto states = triggerRawStates_ != 0
|
||||
auto dstates = triggerRawStates_ != 0
|
||||
? triggerRawStates_
|
||||
: static_cast<uint32_t>(triggerKeyList_[0].states());
|
||||
bool isPress = !keyEvent.isRelease();
|
||||
FCITX_LOGC(openless, Debug)
|
||||
<< "Dictation hotkey: sym="
|
||||
<< sym << " states=" << states
|
||||
<< "Dictation hotkey: sym=" << dsym
|
||||
<< " states=" << dstates
|
||||
<< " isPress=" << isPress;
|
||||
dictationKeyEvent(sym, states, isPress);
|
||||
dictationKeyEvent(dsym, dstates, isPress);
|
||||
keyEvent.filterAndAccept();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 QA 快捷键
|
||||
if (qaRawSym_ != 0 &&
|
||||
sym == qaRawSym_ &&
|
||||
states == qaRawStates_) {
|
||||
FCITX_LOGC(openless, Debug)
|
||||
<< "QA shortcut: sym=" << qaRawSym_
|
||||
<< " states=" << qaRawStates_
|
||||
<< " isPress=" << isPress;
|
||||
qaShortcutEvent(qaRawSym_, qaRawStates_, isPress);
|
||||
keyEvent.filterAndAccept();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查翻译模式修饰键(自定义 + 内置 Shift)
|
||||
bool translationMatched = false;
|
||||
if (translationRawSym_ != 0 &&
|
||||
sym == translationRawSym_ &&
|
||||
states == translationRawStates_) {
|
||||
translationMatched = true;
|
||||
}
|
||||
// 内置 Shift 修饰键
|
||||
if (sym == 0xffe1 || sym == 0xffe2) {
|
||||
translationMatched = true;
|
||||
}
|
||||
if (translationMatched) {
|
||||
FCITX_LOGC(openless, Debug)
|
||||
<< "Translation modifier: sym=" << sym
|
||||
<< " states=" << states
|
||||
<< " isPress=" << isPress;
|
||||
translationModifierEvent(sym, states, isPress);
|
||||
keyEvent.filterAndAccept();
|
||||
return;
|
||||
}
|
||||
@@ -217,16 +265,44 @@ public:
|
||||
rebuildTriggerKeys();
|
||||
}
|
||||
|
||||
void setQaHotkeyRaw(uint32_t sym, uint32_t states) {
|
||||
qaRawSym_ = sym;
|
||||
qaRawStates_ = states;
|
||||
RawConfig raw;
|
||||
readAsIni(raw, configFile());
|
||||
raw.setValueByPath("QaRawSym", std::to_string(sym));
|
||||
raw.setValueByPath("QaRawStates", std::to_string(states));
|
||||
safeSaveAsIni(raw, configFile());
|
||||
FCITX_LOGC(openless, Info)
|
||||
<< "SetQaHotkeyRaw: sym=" << sym << " states=" << states;
|
||||
}
|
||||
|
||||
void setTranslationHotkeyRaw(uint32_t sym, uint32_t states) {
|
||||
translationRawSym_ = sym;
|
||||
translationRawStates_ = states;
|
||||
RawConfig raw;
|
||||
readAsIni(raw, configFile());
|
||||
raw.setValueByPath("TranslationRawSym", std::to_string(sym));
|
||||
raw.setValueByPath("TranslationRawStates", std::to_string(states));
|
||||
safeSaveAsIni(raw, configFile());
|
||||
FCITX_LOGC(openless, Info)
|
||||
<< "SetTranslationHotkeyRaw: sym=" << sym << " states=" << states;
|
||||
}
|
||||
|
||||
FCITX_OBJECT_VTABLE_METHOD(commitText, "CommitText", "s", "");
|
||||
FCITX_OBJECT_VTABLE_METHOD(setHotkey, "SetHotkey", "as", "");
|
||||
FCITX_OBJECT_VTABLE_METHOD(setHotkeyRaw, "SetHotkeyRaw", "uu", "");
|
||||
FCITX_OBJECT_VTABLE_METHOD(setQaHotkeyRaw, "SetQaHotkeyRaw", "uu", "");
|
||||
FCITX_OBJECT_VTABLE_METHOD(setTranslationHotkeyRaw, "SetTranslationHotkeyRaw", "uu", "");
|
||||
FCITX_OBJECT_VTABLE_SIGNAL(dictationKeyEvent, "DictationKeyEvent", "uub");
|
||||
FCITX_OBJECT_VTABLE_SIGNAL(qaShortcutEvent, "QaShortcutEvent", "uub");
|
||||
FCITX_OBJECT_VTABLE_SIGNAL(translationModifierEvent, "TranslationModifierEvent", "uub");
|
||||
|
||||
Instance *instance() { return instance_; }
|
||||
|
||||
void reloadConfig() override {
|
||||
readAsIni(config_, configFile());
|
||||
// 加载原始 sym/states(由 SetHotkeyRaw 写入的持久化键值)
|
||||
// 加载原始 sym/states(由 SetHotkeyRaw / SetQaHotkeyRaw / SetTranslationHotkeyRaw 写入的持久化键值)
|
||||
RawConfig raw;
|
||||
readAsIni(raw, configFile());
|
||||
{
|
||||
@@ -237,6 +313,22 @@ public:
|
||||
auto *v = raw.valueByPath("TriggerRawStates");
|
||||
triggerRawStates_ = v ? std::stoul(*v, nullptr, 0) : 0;
|
||||
}
|
||||
{
|
||||
auto *v = raw.valueByPath("QaRawSym");
|
||||
qaRawSym_ = v ? std::stoul(*v, nullptr, 0) : 0;
|
||||
}
|
||||
{
|
||||
auto *v = raw.valueByPath("QaRawStates");
|
||||
qaRawStates_ = v ? std::stoul(*v, nullptr, 0) : 0;
|
||||
}
|
||||
{
|
||||
auto *v = raw.valueByPath("TranslationRawSym");
|
||||
translationRawSym_ = v ? std::stoul(*v, nullptr, 0) : 0;
|
||||
}
|
||||
{
|
||||
auto *v = raw.valueByPath("TranslationRawStates");
|
||||
translationRawStates_ = v ? std::stoul(*v, nullptr, 0) : 0;
|
||||
}
|
||||
rebuildTriggerKeys();
|
||||
}
|
||||
|
||||
@@ -264,6 +356,10 @@ private:
|
||||
KeyList triggerKeyList_;
|
||||
uint32_t triggerRawSym_;
|
||||
uint32_t triggerRawStates_;
|
||||
uint32_t qaRawSym_;
|
||||
uint32_t qaRawStates_;
|
||||
uint32_t translationRawSym_;
|
||||
uint32_t translationRawStates_;
|
||||
/// 快捷键按下时保存的输入上下文指针,用于 commitText 在失焦后仍能提交文字。
|
||||
/// 事件处理线程和 DBus 处理线程都是 fcitx5 主事件循环,无竞态。
|
||||
/// 通过 InputContextDestroyed 事件监听 IC 销毁时自动清空指针。
|
||||
|
||||
Reference in new issue
Block a user