Files
openless/openless-all/scripts/linux-fcitx5-plugin/input_target_contract.cpp
T
16c3f841b5 chore(release): prepare 2.0.0-Beta.2 build 20260924
Integrate the reviewed 2.0 PRs and local fixes from 03136853.
Preserve the verified subtitle behavior and model/runtime corrections.
Keep the numeric Beta tag compatible with released clients while exposing
the date as application build metadata.

Co-authored-by: DepengWang <2818245+DepengWang@users.noreply.github.com>
Co-authored-by: HKLHaoBin <we3q@qq.com>
Co-authored-by: Nahida <1139500183@qq.com>
Co-authored-by: TRIP <1933142963@qq.com>
Co-authored-by: chyuwei <weichangyuwcy@gmail.com>
Co-authored-by: leonoxo <leonoxo@gmail.com>
Co-authored-by: tayir <arnoldincredible@126.com>
2026-09-24 17:28:24 +08:00

114 lines
5.9 KiB
C++

// Exercise the real plugin entry points with two in-process native input
// contexts. No display, user keyboard, clipboard contents or DBus service is
// required; only the input-context boundary is replaced by a recording client.
#include "openless.cpp"
#include <cassert>
#include <filesystem>
#include <unistd.h>
class RecordingInputContext final : public fcitx::InputContext {
public:
explicit RecordingInputContext(fcitx::InputContextManager &manager)
: InputContext(manager, "openless-contract") { created(); }
~RecordingInputContext() override { destroy(); }
const char *frontend() const override { return "contract"; }
std::vector<std::string> committed;
protected:
void commitStringImpl(const std::string &text) override { committed.push_back(text); }
void deleteSurroundingTextImpl(int, unsigned int) override {}
void forwardKeyImpl(const fcitx::ForwardKeyEvent &) override {}
void updatePreeditImpl() override {}
};
namespace fcitx {
struct OpenLessInputTargetContract {
static void select(OpenLess &plugin, InputContext &context) { plugin.selectionIc_ = &context; }
static void type(OpenLess &plugin, InputContext &context) { plugin.savedIc_ = &context; }
};
}
int main() {
const auto config = std::filesystem::temp_directory_path() /
("openless-fcitx-contract-" + std::to_string(getpid()));
std::filesystem::create_directory(config);
setenv("XDG_CONFIG_HOME", config.c_str(), 1);
{
char name[] = "openless-contract";
char disabled[] = "--disable=all";
char *arguments[] = {name, disabled, nullptr};
fcitx::Instance instance(2, arguments);
instance.initialize();
fcitx::OpenLess plugin(&instance);
RecordingInputContext first(instance.inputContextManager());
first.focusIn();
fcitx::OpenLessInputTargetContract::select(plugin, first);
first.surroundingText().setText("foo foo", 3, 0);
assert(plugin.captureSelectionTarget("selection") == "foo");
const auto metadata=plugin.contextSnapshot("selection",false);
assert(metadata.find("openless-contract")!=std::string::npos);
assert(metadata.find("\"text\"")==std::string::npos);
assert(plugin.contextSnapshot("selection",true).find("foo foo")!=std::string::npos);
first.setCapabilityFlags(fcitx::CapabilityFlag::Password);
assert(plugin.captureSelectionTarget("password").empty());
assert(plugin.contextSnapshot("selection",true).find("\"text\"")==std::string::npos);
first.setCapabilityFlags({});
// Identical text at a different position is a different selection.
// Comparing only the selected string would corrupt the wrong range.
first.surroundingText().setCursor(7, 4);
assert(!plugin.applySelectionTarget("selection", "foo", "replacement"));
assert(first.committed.empty());
first.surroundingText().setCursor(3, 3);
assert(!plugin.applySelectionTarget("selection", "foo", "replacement"));
// Changing unselected context or invalidating the native snapshot must
// also invalidate Apply, even while PRIMARY still contains "foo".
first.surroundingText().setText("foo bar", 3, 0);
assert(!plugin.applySelectionTarget("selection", "foo", "replacement"));
first.surroundingText().invalidate();
assert(!plugin.applySelectionTarget("selection", "foo", "replacement"));
first.surroundingText().setText("foo foo", 3, 0);
// The QA -> preview handoff moves the captured values, never the live
// SurroundingText object. The old ticket must become unusable.
assert(plugin.rekeySelectionTarget("selection", "preview"));
assert(!plugin.applySelectionTarget("selection", "foo", "replacement"));
assert(plugin.applySelectionTarget("preview", "foo", "replacement"));
assert(first.committed == std::vector<std::string>{"replacement"});
// A client reports its new surrounding text after commit. Undo still
// uses the captured offsets after the ticket has been transferred.
first.surroundingText().setText("replacement foo", 11, 11);
assert(plugin.revertSelectionTarget("preview"));
assert(first.committed.back() == "foo");
assert(!plugin.revertSelectionTarget("preview"));
RecordingInputContext second(instance.inputContextManager());
fcitx::OpenLessInputTargetContract::type(plugin, first);
assert(plugin.captureDictationTarget("dictation"));
first.focusOut();
second.focusIn();
fcitx::OpenLessInputTargetContract::type(plugin, second);
assert(!plugin.commitDictationTarget("dictation", "focus changed"));
second.focusOut();
first.focusIn();
assert(plugin.commitDictationTarget("dictation", "original target"));
assert(first.committed.back() == "original target");
assert(second.committed.empty());
assert(plugin.cancelDictationTarget("dictation"));
assert(!plugin.commitDictationTarget("dictation", "late write"));
{
RecordingInputContext destroyed(instance.inputContextManager());
first.focusOut();
destroyed.focusIn();
destroyed.surroundingText().setText("original", 8, 0);
fcitx::OpenLessInputTargetContract::type(plugin, destroyed);
fcitx::OpenLessInputTargetContract::select(plugin, destroyed);
assert(plugin.captureDictationTarget("destroyed-dictation"));
assert(plugin.captureSelectionTarget("destroyed-selection") == "original");
}
// Destruction emits the actual InputContextDestroyed event. Both
// ticket maps must drop the raw handle before either late write runs.
assert(!plugin.commitDictationTarget("destroyed-dictation", "late write"));
assert(!plugin.applySelectionTarget("destroyed-selection", "original", "late write"));
}
std::filesystem::remove_all(config);
}