Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new global option to allow change the time limit on modifier only key triggering #1204

Merged
merged 2 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/lib/fcitx/globalconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
*/

#include "globalconfig.h"
#include <cstdint>
#include "fcitx-config/configuration.h"
#include "fcitx-config/enum.h"
#include "fcitx-config/iniparser.h"
#include "fcitx-config/option.h"
#include "fcitx-utils/eventloopinterface.h"
#include "fcitx-utils/i18n.h"
#include "fcitx-utils/macros.h"
#include "config.h"
#include "inputcontextmanager.h"

Expand Down Expand Up @@ -133,7 +136,19 @@ FCITX_CONFIGURATION(
"TogglePreedit",
_("Toggle embedded preedit"),
{Key("Control+Alt+P")},
KeyListConstrain()};);
KeyListConstrain()};
Option<int, IntConstrain, DefaultMarshaller<int>, ToolTipAnnotation>
modifierOnlyKeyTimeout{
this,
"ModifierOnlyKeyTimeout",
_("Modifier Only Hotkey Timeout in Milliseconds"),
250,
IntConstrain{-1, 5000},
{},
ToolTipAnnotation{
_("When using modifier only hotkey, the action may "
"only be triggered if it is released within the timeout. -1 "
"means there is no timeout.")}};);

FCITX_CONFIGURATION(
BehaviorConfig, Option<bool> activeByDefault{this, "ActiveByDefault",
Expand Down Expand Up @@ -401,8 +416,23 @@ int GlobalConfig::autoSavePeriod() const {
return *d->behavior->autoSavePeriod;
}

int GlobalConfig::modifierOnlyKeyTimeout() const {
FCITX_D();
return *d->hotkey->modifierOnlyKeyTimeout;
}

bool GlobalConfig::checkModifierOnlyKeyTimeout(uint64_t lastPressedTime) const {
const auto timeout = modifierOnlyKeyTimeout();
if (timeout < 0) {
return true;
}
return now(CLOCK_MONOTONIC) <=
(lastPressedTime + static_cast<uint64_t>(timeout) * 1000ULL);
}

const Configuration &GlobalConfig::config() const {
FCITX_D();
return *d;
}

} // namespace fcitx
26 changes: 26 additions & 0 deletions src/lib/fcitx/globalconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
#ifndef _FCITX_GLOBALCONFIG_H_
#define _FCITX_GLOBALCONFIG_H_

#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <fcitx-config/configuration.h>
#include <fcitx-config/rawconfig.h>
#include <fcitx-utils/key.h>
#include <fcitx-utils/macros.h>
Expand Down Expand Up @@ -109,6 +112,29 @@ class FCITXCORE_EXPORT GlobalConfig {
*/
int autoSavePeriod() const;

/**
* Number of milliseconds that modifier only key can be triggered with key
* release.
*
* @return timeout
* @since 5.1.12
*/
int modifierOnlyKeyTimeout() const;

/**
* Helper function to check whether the modifier only key should be
* triggered.
*
* The user may need to record the time when the corresponding modifier only
* key is pressed. The input time should use CLOCK_MONOTONIC.
*
* If timeout < 0, always return true.
* Otherwise, check if it should be triggered based on current time.
*
* @return should trigger modifier only key
*/
bool checkModifierOnlyKeyTimeout(uint64_t lastPressedTime) const;

const std::vector<std::string> &enabledAddons() const;
const std::vector<std::string> &disabledAddons() const;

Expand Down
13 changes: 12 additions & 1 deletion src/lib/fcitx/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <unistd.h>
#include <cstdint>
#include <ctime>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
Expand All @@ -23,7 +24,9 @@
#include "fcitx-utils/capabilityflags.h"
#include "fcitx-utils/event.h"
#include "fcitx-utils/eventdispatcher.h"
#include "fcitx-utils/eventloopinterface.h"
#include "fcitx-utils/i18n.h"
#include "fcitx-utils/key.h"
#include "fcitx-utils/log.h"
#include "fcitx-utils/macros.h"
#include "fcitx-utils/misc.h"
Expand Down Expand Up @@ -580,6 +583,7 @@ void InputState::reset() {
pendingGroupIndex_ = 0;
keyReleased_ = -1;
lastKeyPressed_ = Key();
lastKeyPressedTime_ = 0;
totallyReleased_ = true;
}

Expand Down Expand Up @@ -787,7 +791,12 @@ Instance::Instance(int argc, char **argv) {
origKey.isReleaseOfModifier(lastKeyPressed) &&
keyHandler.check()) {
if (isModifier) {
keyHandler.trigger(inputState->totallyReleased_);
if (d->globalConfig_.checkModifierOnlyKeyTimeout(
inputState->lastKeyPressedTime_)) {
keyHandler.trigger(
inputState->totallyReleased_);
}
inputState->lastKeyPressedTime_ = 0;
if (origKey.hasModifier()) {
inputState->totallyReleased_ = false;
}
Expand Down Expand Up @@ -820,6 +829,8 @@ Instance::Instance(int argc, char **argv) {
inputState->keyReleased_ = idx;
inputState->lastKeyPressed_ = origKey;
if (isModifier) {
inputState->lastKeyPressedTime_ =
now(CLOCK_MONOTONIC);
// don't forward to input method, but make it pass
// through to client.
return keyEvent.filter();
Expand Down
1 change: 1 addition & 0 deletions src/lib/fcitx/instance_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct InputState : public InputContextProperty {
CheckInputMethodChanged *imChanged_ = nullptr;
int keyReleased_ = -1;
Key lastKeyPressed_;
uint64_t lastKeyPressedTime_ = 0;
bool totallyReleased_ = true;
bool firstTrigger_ = false;
size_t pendingGroupIndex_ = 0;
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ foreach(TESTCASE ${FCITX_CONFIG_TEST})
endforeach()

set(testinputcontext_LIBS Fcitx5::Module::TestFrontend Fcitx5::Module::TestIM)
set(testinstance_LIBS Fcitx5::Module::TestFrontend)

set(FCITX_CORE_TEST
testinputcontext
Expand Down
126 changes: 100 additions & 26 deletions test/testinstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,125 @@
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include <chrono>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include "fcitx-utils/eventdispatcher.h"
#include "fcitx-utils/key.h"
#include "fcitx-utils/log.h"
#include "fcitx-utils/macros.h"
#include "fcitx-utils/testing.h"
#include "fcitx/addonmanager.h"
#include "fcitx/event.h"
#include "fcitx/inputmethodgroup.h"
#include "fcitx/inputmethodmanager.h"
#include "fcitx/instance.h"
#include "testdir.h"
#include "testfrontend_public.h"

using namespace fcitx;

void testCheckUpdate(EventDispatcher *dispatcher, Instance *instance) {
dispatcher->schedule([instance]() {
FCITX_ASSERT(!instance->checkUpdate());
void testCheckUpdate(Instance &instance) {
instance.eventDispatcher().schedule([&instance]() {
FCITX_ASSERT(!instance.checkUpdate());
auto hasUpdateTrue =
instance->watchEvent(EventType::CheckUpdate,
EventWatcherPhase::Default, [](Event &event) {
auto &checkUpdate =
static_cast<CheckUpdateEvent &>(event);
checkUpdate.setHasUpdate();
});
FCITX_ASSERT(instance->checkUpdate());
instance.watchEvent(EventType::CheckUpdate,
EventWatcherPhase::Default, [](Event &event) {
auto &checkUpdate =
static_cast<CheckUpdateEvent &>(event);
checkUpdate.setHasUpdate();
});
FCITX_ASSERT(instance.checkUpdate());
hasUpdateTrue.reset();
FCITX_ASSERT(!instance->checkUpdate());
FCITX_ASSERT(!instance.checkUpdate());
});
}

void testReloadGlobalConfig(EventDispatcher *dispatcher, Instance *instance) {
dispatcher->schedule([instance]() {
void testReloadGlobalConfig(Instance &instance) {
instance.eventDispatcher().schedule([&instance]() {
bool globalConfigReloadedEventFired = false;
auto reloadConfigEventWatcher =
instance->watchEvent(EventType::GlobalConfigReloaded,
EventWatcherPhase::Default, [&](Event &) {
globalConfigReloadedEventFired = true;
FCITX_INFO() << "Global config reloaded";
});
instance->reloadConfig();
instance.watchEvent(EventType::GlobalConfigReloaded,
EventWatcherPhase::Default, [&](Event &) {
globalConfigReloadedEventFired = true;
FCITX_INFO() << "Global config reloaded";
});
instance.reloadConfig();
FCITX_ASSERT(globalConfigReloadedEventFired);
instance->exit();
});
}

void testModifierOnlyHotkey(Instance &instance) {
instance.eventDispatcher().schedule([&instance]() {
auto defaultGroup = instance.inputMethodManager().currentGroup();
defaultGroup.inputMethodList().clear();
defaultGroup.inputMethodList().push_back(
InputMethodGroupItem("keyboard-us"));
defaultGroup.inputMethodList().push_back(
InputMethodGroupItem("testim"));
instance.inputMethodManager().setGroup(std::move(defaultGroup));

auto *testfrontend = instance.addonManager().addon("testfrontend");
auto uuid =
testfrontend->call<ITestFrontend::createInputContext>("testapp");
auto *ic = instance.inputContextManager().findByUUID(uuid);
FCITX_ASSERT(ic);

FCITX_ASSERT(instance.inputMethod(ic) == "keyboard-us");
// Alt trigger doesn't work since we are at first im.
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift_L"), false));
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift+Shift_L"), true));
FCITX_ASSERT(instance.inputMethod(ic) == "keyboard-us");

FCITX_ASSERT(instance.inputMethod(ic) == "keyboard-us");
FCITX_ASSERT(testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Control+space"), false));
FCITX_ASSERT(instance.inputMethod(ic) == "testim");

FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift_L"), false));
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift+Shift_L"), true));
FCITX_ASSERT(instance.inputMethod(ic) == "keyboard-us");

FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift_L"), false));
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift+Shift_L"), true));
FCITX_ASSERT(instance.inputMethod(ic) == "testim");

// Sleep 1 sec between press and release, should not trigger based on
// default 250ms.
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift_L"), false));
std::this_thread::sleep_until(std::chrono::steady_clock::now() +
std::chrono::seconds(1));
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift+Shift_L"), true));
FCITX_ASSERT(instance.inputMethod(ic) == "testim");

// Some other key pressed between shift, should not trigger.
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift_L"), false));
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift+A"), false));
FCITX_ASSERT(!testfrontend->call<ITestFrontend::sendKeyEvent>(
uuid, Key("Shift+Shift_L"), true));
FCITX_ASSERT(instance.inputMethod(ic) == "testim");
});
}

int main() {
setupTestingEnvironment(FCITX5_BINARY_DIR, {"testing/testim"}, {});
setupTestingEnvironment(FCITX5_BINARY_DIR,
{"testing/testim", "testing/testfrontend"}, {});

char arg0[] = "testinstance";
char arg1[] = "--disable=all";
char arg2[] = "--enable=testim";
char arg2[] = "--enable=testim,testfrontend";
char arg3[] = "--option=name1=opt1a:opt1b,name2=opt2a:opt2b";
char *argv[] = {arg0, arg1, arg2, arg3};
Instance instance(FCITX_ARRAY_SIZE(argv), argv);
Expand All @@ -59,10 +133,10 @@ int main() {
std::vector<std::string>{"opt2a", "opt2b"});
FCITX_ASSERT(instance.addonManager().addonOptions("name3") ==
std::vector<std::string>{});
EventDispatcher dispatcher;
dispatcher.attach(&instance.eventLoop());
testCheckUpdate(&dispatcher, &instance);
testReloadGlobalConfig(&dispatcher, &instance);
testCheckUpdate(instance);
testReloadGlobalConfig(instance);
testModifierOnlyHotkey(instance);
instance.eventDispatcher().schedule([&instance]() { instance.exit(); });
instance.exec();
return 0;
}
Loading