* fix(windows): 支持标点切换快捷键改绑与取消绑定 * fix(shortcut): 对齐虚拟键并处理语音与窗口快捷键冲突 * fix(ui-html): 绑定标点快捷键前提示应用快捷键占用 * fix(ui-html): 仅对已知快捷键冲突提示确认 * feat(shortcuts): 支持自定义输入状态切换快捷键 * fix: keep shortcut editor buttons on one row * fix(shortcuts): 限定输入状态快捷键为 Ctrl 组合 * fix: clarify shortcut override and punctuation lock behavior * perf(server,windows): 提前筛选输入状态快捷键 * fix(windows,server,ui-html): 修正输入状态快捷键的兼容与保存问题 - windows: 未协商 InputStateShortcuts 时回退到旧的 Ctrl+Shift+E KeyEvent, 新 DLL 配旧 Server 时英文候选快捷键不再失效 - windows: 全角快捷键与标点快捷键一样可在待处理按键后排队,不再漏给应用 - windows: Server 不可用时排队的英文候选切换按 FUNCTION_CANCEL 离线回放, 不再丢弃其后的按键 - server: 非字符串配置值不再以 "!invalid" 作为 binding 发给设置页 - server: 仅三个输入状态快捷键路径强制回传配置快照;去掉 worker 中与 reader 重复的协商检查;抽出 InputStateShortcutViews 去除重复转换 - ui-html: 保存后 5 秒未收到快照则恢复编辑器并提示重试 --------- Co-authored-by: fanlusky <1730976608@qq.com>
136 lines
5.5 KiB
C++
136 lines
5.5 KiB
C++
#include "../../engine/contracts/input_state_shortcuts.h"
|
|
#include "../../engine/contracts/ipc_negotiation.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#define CHECK(expression) \
|
|
do \
|
|
{ \
|
|
if (!(expression)) \
|
|
{ \
|
|
std::cerr << #expression << '\n'; \
|
|
return 1; \
|
|
} \
|
|
} while (false)
|
|
std::string Fixture()
|
|
{
|
|
using namespace FanyImeInputStateShortcuts;
|
|
std::ostringstream out;
|
|
const auto quote = [&out](std::string_view value) {
|
|
out << '"';
|
|
for (char c : value)
|
|
{
|
|
if (c == '"' || c == '\\')
|
|
out << '\\';
|
|
out << c;
|
|
}
|
|
out << '"';
|
|
};
|
|
out << "{\"actions\":[";
|
|
for (std::size_t i = 0; i < Definitions.size(); ++i)
|
|
{
|
|
const auto &d = Definitions[i];
|
|
if (i)
|
|
out << ',';
|
|
out << "{\"key\":";
|
|
quote(d.key);
|
|
out << ",\"label\":";
|
|
quote(d.label);
|
|
out << ",\"defaultBinding\":";
|
|
quote(d.defaultBinding);
|
|
out << ",\"editor\":";
|
|
quote(d.editor);
|
|
out << ",\"binding\":";
|
|
quote(d.defaultBinding);
|
|
out << ",\"problem\":\"\"}";
|
|
}
|
|
const auto rules = [&](std::string_view name, const auto &items) {
|
|
out << "],";
|
|
quote(name);
|
|
out << ":[";
|
|
bool first = true;
|
|
for (const auto &rule : items)
|
|
{
|
|
if (!first)
|
|
out << ',';
|
|
first = false;
|
|
out << "{\"binding\":";
|
|
quote(rule.binding);
|
|
out << ",\"label\":";
|
|
quote(rule.label);
|
|
out << '}';
|
|
}
|
|
};
|
|
rules("reserved", Reserved);
|
|
rules("warnings", Warnings);
|
|
out << "],\"keys\":[";
|
|
bool first = true;
|
|
for (const auto &key : FanyImeKeyChord::NamedKeys)
|
|
{
|
|
if (!first)
|
|
out << ',';
|
|
first = false;
|
|
out << "{\"code\":";
|
|
quote(key.name);
|
|
out << ",\"vk\":" << key.key << ",\"label\":";
|
|
quote(key.label);
|
|
out << '}';
|
|
}
|
|
out << "]}\n";
|
|
return out.str();
|
|
}
|
|
int main()
|
|
{
|
|
using namespace FanyImeInputStateShortcuts;
|
|
using namespace FanyImeKeyChord;
|
|
std::ifstream fixture(MSIME_SHORTCUT_FIXTURE_PATH);
|
|
CHECK(std::string((std::istreambuf_iterator<char>(fixture)), {}) == Fixture());
|
|
auto values = Defaults();
|
|
auto resolved = Resolve(values);
|
|
CHECK(resolved.Match('E', 3) == Action::EnglishCandidates);
|
|
CHECK(resolved.Match(0x20, 3) == Action::FullWidth);
|
|
CHECK(resolved.Match(0xbe, 2) == Action::Punctuation);
|
|
for (const auto &key : NamedKeys)
|
|
CHECK(KeyCode(key.name) == key.key);
|
|
Binding binding;
|
|
for (const auto value : {"Alt+KeyJ", "Alt+Shift+KeyJ", "Ctrl+Alt+Period", "Win+KeyA", "Shift+KeyA", "Ctrl+",
|
|
"Ctrl+F13", "Ctrl+Keya", "true"})
|
|
CHECK(!Parse(value, binding) && binding.key == 0);
|
|
for (const auto &rule : Reserved)
|
|
CHECK(!Validate(2, rule.binding, values).empty());
|
|
CHECK(!Validate(2, "Ctrl+Shift+KeyE", values).empty());
|
|
CHECK(!Validate(2, "Ctrl+Shift+Space", values).empty());
|
|
values[0] = "";
|
|
values[1] = "Ctrl+Shift+KeyG";
|
|
CHECK(Validate(2, "Ctrl+Shift+KeyE", values).empty());
|
|
CHECK(Validate(2, "Ctrl+Shift+Space", values).empty());
|
|
values[2] = "Ctrl+Shift+KeyG";
|
|
resolved = Resolve(values);
|
|
CHECK(resolved.bindings[1].key == 0 && resolved.bindings[2].key == 0);
|
|
for (const auto value : {"Alt+KeyJ", "Alt+Shift+KeyJ"})
|
|
{
|
|
CHECK(!Validate(1, value, values).empty());
|
|
values[1] = value;
|
|
CHECK(Resolve(values).bindings[1].key == 0);
|
|
CHECK(Resolve(values).Match('J', Alt) == static_cast<Action>(0));
|
|
CHECK(Resolve(values).Match('J', Alt | Shift) == static_cast<Action>(0));
|
|
}
|
|
values[1] = "Ctrl+Shift+KeyH";
|
|
resolved = Resolve(values);
|
|
for (unsigned modifiers = 0; modifiers < 16; ++modifiers)
|
|
CHECK(resolved.bindings[1].Matches('H', modifiers) == (modifiers == (Control | Shift)));
|
|
values[2] = "invalid";
|
|
CHECK(Resolve(values).bindings[2].key == 0);
|
|
CHECK(Resolve(values).bindings[1].key == 'H');
|
|
CHECK(TomlString(" 'Ctrl+Space' ") == "Ctrl+Space");
|
|
CHECK(TomlString("true") == "!invalid");
|
|
CHECK(Validate(2, "Ctrl+KeyC", values).empty());
|
|
auto hello = FanyImeProtocol::Hello(1, 2, FanyImeProtocol::Capabilities | FanyImeProtocol::InputStateShortcuts);
|
|
CHECK(!(FanyImeProtocol::Negotiate(hello).capabilities & FanyImeProtocol::InputStateShortcuts));
|
|
CHECK(FanyImeProtocol::Negotiate(hello, FanyImeProtocol::Capabilities | FanyImeProtocol::InputStateShortcuts)
|
|
.capabilities &
|
|
FanyImeProtocol::InputStateShortcuts);
|
|
CHECK(FanyImePipeEventType::InputStateAction == 17);
|
|
CHECK(static_cast<unsigned>(Action::EnglishCandidates) == 1);
|
|
}
|