Force show CandidatesView only when input method switched by user

This commit is contained in:
Rocka 2025-11-09 15:11:02 +08:00
parent f7d8e44380
commit 85196e6a73
No known key found for this signature in database
GPG Key ID: 28031158FFDD6853
7 changed files with 133 additions and 49 deletions

View File

@ -5,10 +5,12 @@
#include <fcitx/addonfactory.h>
#include <fcitx/addonmanager.h>
#include <fcitx/candidatelist.h>
#include <fcitx/focusgroup.h>
#include <fcitx/inputcontextmanager.h>
#include <fcitx/inputmethodengine.h>
#include <fcitx/focusgroup.h>
#include <fcitx/inputmethodmanager.h>
#include <fcitx/inputpanel.h>
#include <fcitx/statusarea.h>
#include <fcitx-utils/event.h>
#include "androidfrontend.h"
@ -286,8 +288,20 @@ AndroidFrontend::AndroidFrontend(Instance *instance)
EventType::InputContextInputMethodActivated,
EventWatcherPhase::Default,
[this](Event &event) {
FCITX_UNUSED(event);
imChangeCallback();
auto &e = static_cast<InputMethodActivatedEvent &>(event);
if (e.inputContext() != activeIC_) return;
imChangeCallback(makeInputMethodStatus(activeIC_));
}
));
eventHandlers_.emplace_back(instance_->watchEvent(
EventType::InputContextSwitchInputMethod,
EventWatcherPhase::Default,
[this](Event &event) {
auto &e = static_cast<InputContextSwitchInputMethodEvent &>(event);
if (e.inputContext() != activeIC_) return;
const auto &reason = static_cast<std::underlying_type<InputMethodSwitchedReason>::type>(e.reason());
const std::string &oldIM = e.oldInputMethod();
switchInputMethodCallback(reason, oldIM);
}
));
eventHandlers_.emplace_back(instance_->watchEvent(
@ -295,20 +309,19 @@ AndroidFrontend::AndroidFrontend(Instance *instance)
EventWatcherPhase::Default,
[this](Event &event) {
auto &e = static_cast<InputContextFlushUIEvent &>(event);
if (e.inputContext() != activeIC_) return;
switch (e.component()) {
case UserInterfaceComponent::InputPanel: {
if (activeIC_) {
activeIC_->updateInputPanel();
if (pagingMode_ == 0) {
activeIC_->updateCandidatesBulk();
} else {
activeIC_->updateCandidatesPaged();
}
activeIC_->updateInputPanel();
if (pagingMode_ == 0) {
activeIC_->updateCandidatesBulk();
} else {
activeIC_->updateCandidatesPaged();
}
break;
}
case UserInterfaceComponent::StatusArea: {
statusAreaUpdateCallback();
statusAreaUpdateCallback(makeStatusAreaActions(activeIC_), makeInputMethodStatus(activeIC_));
break;
}
}
@ -494,6 +507,28 @@ void AndroidFrontend::setPagedCandidateCallback(const PagedCandidateCallback &ca
pagedCandidateCallback = callback;
}
void AndroidFrontend::setSwitchInputMethodCallback(const SwitchInputMethodCallback &callback) {
switchInputMethodCallback = callback;
}
InputMethodStatus AndroidFrontend::makeInputMethodStatus(InputContext *ic) {
auto *entry = instance_->inputMethodEntry(ic);
auto *engine = instance_->inputMethodEngine(ic);
return {entry, engine, ic};
}
std::vector<ActionEntity> AndroidFrontend::makeStatusAreaActions(fcitx::InputContext *ic) {
auto actions = std::vector<ActionEntity>();
for (auto group: {fcitx::StatusGroup::BeforeInputMethod,
fcitx::StatusGroup::InputMethod,
fcitx::StatusGroup::AfterInputMethod}) {
for (auto act: ic->statusArea().actions(group)) {
actions.emplace_back(act, ic);
}
}
return actions;
}
class AndroidFrontendFactory : public AddonFactory {
public:
AddonInstance *create(AddonManager *manager) override {

View File

@ -23,30 +23,30 @@ public:
Instance *instance() { return instance_; }
void updateCandidateList(const std::vector<std::string> &candidates, const int size);
void commitString(const std::string &str, const int cursor);
void updateCandidateList(const std::vector<std::string> &candidates, int size);
void commitString(const std::string &str, int cursor);
void updateClientPreedit(const Text &clientPreedit);
void updateInputPanel(const Text &preedit, const Text &auxUp, const Text &auxDown);
void releaseInputContext(const int uid);
void releaseInputContext(int uid);
void updatePagedCandidate(const PagedCandidateEntity &paged);
void keyEvent(const Key &key, bool isRelease, const int timestamp);
void keyEvent(const Key &key, bool isRelease, int timestamp);
void forwardKey(const Key &key, bool isRelease);
bool selectCandidate(int idx);
bool isInputPanelEmpty();
void resetInputContext();
void repositionCursor(int idx);
void focusInputContext(bool focus);
void activateInputContext(const int uid, const std::string &pkgName);
void deactivateInputContext(const int uid);
void activateInputContext(int uid, const std::string &pkgName);
void deactivateInputContext(int uid);
[[nodiscard]] InputContext *activeInputContext() const;
void setCapabilityFlags(uint64_t flag);
std::vector<std::string> getCandidates(const int offset, const int limit);
std::vector<CandidateAction> getCandidateActions(const int idx);
void triggerCandidateAction(const int idx, const int actionIdx);
void deleteSurrounding(const int before, const int after);
std::vector<std::string> getCandidates(int offset, int limit);
std::vector<CandidateAction> getCandidateActions(int idx);
void triggerCandidateAction(int idx, int actionIdx);
void deleteSurrounding(int before, int after);
void showToast(const std::string &s);
void setCandidatePagingMode(const int mode);
void setCandidatePagingMode(int mode);
void offsetCandidatePage(int delta);
void setCandidateListCallback(const CandidateListCallback &callback);
void setCommitStringCallback(const CommitStringCallback &callback);
@ -58,6 +58,7 @@ public:
void setDeleteSurroundingCallback(const DeleteSurroundingCallback &callback);
void setToastCallback(const ToastCallback &callback);
void setPagedCandidateCallback(const PagedCandidateCallback &callback);
void setSwitchInputMethodCallback(const SwitchInputMethodCallback &callback);
private:
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, keyEvent);
@ -86,6 +87,7 @@ private:
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setDeleteSurroundingCallback);
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setToastCallback);
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPagedCandidateCallback);
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setSwitchInputMethodCallback);
Instance *instance_;
FocusGroup focusGroup_;
@ -99,11 +101,15 @@ private:
ClientPreeditCallback preeditCallback = [](const Text &) {};
InputPanelCallback inputPanelCallback = [](const fcitx::Text &, const fcitx::Text &, const Text &) {};
KeyEventCallback keyEventCallback = [](const int, const uint32_t, const uint32_t, const bool, const int) {};
InputMethodChangeCallback imChangeCallback = [] {};
StatusAreaUpdateCallback statusAreaUpdateCallback = [] {};
InputMethodChangeCallback imChangeCallback = [](const InputMethodStatus &) {};
StatusAreaUpdateCallback statusAreaUpdateCallback = [](const std::vector<ActionEntity> &, const InputMethodStatus &) {};
DeleteSurroundingCallback deleteSurroundingCallback = [](const int, const int) {};
ToastCallback toastCallback = [](const std::string &) {};
PagedCandidateCallback pagedCandidateCallback = [](const PagedCandidateEntity &) {};
SwitchInputMethodCallback switchInputMethodCallback = [](const int, const std::string &) {};
InputMethodStatus makeInputMethodStatus(InputContext* ic);
std::vector<ActionEntity> makeStatusAreaActions(InputContext* ic);
};
} // namespace fcitx

View File

@ -17,11 +17,12 @@ typedef std::function<void(const std::string &, const int)> CommitStringCallback
typedef std::function<void(const fcitx::Text &)> ClientPreeditCallback;
typedef std::function<void(const fcitx::Text &, const fcitx::Text &, const fcitx::Text &)> InputPanelCallback;
typedef std::function<void(const int, const uint32_t, const uint32_t, const bool, const int)> KeyEventCallback;
typedef std::function<void()> InputMethodChangeCallback;
typedef std::function<void()> StatusAreaUpdateCallback;
typedef std::function<void(const InputMethodStatus &)> InputMethodChangeCallback;
typedef std::function<void(const std::vector<ActionEntity> &, const InputMethodStatus &)> StatusAreaUpdateCallback;
typedef std::function<void(const int, const int)> DeleteSurroundingCallback;
typedef std::function<void(const std::string &)> ToastCallback;
typedef std::function<void(const PagedCandidateEntity &)> PagedCandidateCallback;
typedef std::function<void(const int, const std::string &)> SwitchInputMethodCallback;
FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, keyEvent,
void(const fcitx::Key &, bool isRelease, const int timestamp))
@ -101,4 +102,7 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setToastCallback,
FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setPagedCandidateCallback,
void(const PagedCandidateCallback &))
FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setSwitchInputMethodCallback,
void(const SwitchInputMethodCallback &))
#endif // FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H

View File

@ -154,7 +154,7 @@ public:
auto *ic = p_frontend->call<fcitx::IAndroidFrontend::activeInputContext>();
if (!ic) return nullptr;
auto *entry = p_instance->inputMethodEntry(ic);
auto *engine = static_cast<fcitx::InputMethodEngine *>(p_instance->addonManager().addon(entry->addon(), true));
auto *engine = p_instance->inputMethodEngine(ic);
return std::make_unique<InputMethodStatus>(entry, engine, ic);
}
@ -637,21 +637,16 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx(
env->SetObjectArrayElement(vararg, 4, env->NewObject(GlobalRef->Integer, GlobalRef->IntegerInit, timestamp));
env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 5, *vararg);
};
auto imChangeCallback = []() {
std::unique_ptr<InputMethodStatus> status = Fcitx::Instance().inputMethodStatus();
if (!status) return;
auto imChangeCallback = [](const InputMethodStatus &status) {
auto env = GlobalRef->AttachEnv();
auto vararg = JRef<jobjectArray>(env, env->NewObjectArray(1, GlobalRef->Object, nullptr));
auto obj = JRef(env, fcitxInputMethodStatusToJObject(env, *status));
auto vararg = JRef<jobjectArray>(env, env->NewObjectArray(2, GlobalRef->Object, nullptr));
auto obj = JRef(env, fcitxInputMethodStatusToJObject(env, status));
env->SetObjectArrayElement(vararg, 0, obj);
env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 6, *vararg);
};
auto statusAreaUpdateCallback = []() {
std::unique_ptr<InputMethodStatus> status = Fcitx::Instance().inputMethodStatus();
if (!status) return;
auto statusAreaUpdateCallback = [](const std::vector<ActionEntity> &actions, const InputMethodStatus &status) {
auto env = GlobalRef->AttachEnv();
auto vararg = JRef<jobjectArray>(env, env->NewObjectArray(static_cast<int>(2), GlobalRef->Object, nullptr));
const auto actions = Fcitx::Instance().statusAreaActions();
auto vararg = JRef<jobjectArray>(env, env->NewObjectArray(2, GlobalRef->Object, nullptr));
auto actionArray = JRef<jobjectArray>(env, env->NewObjectArray(static_cast<int>(actions.size()), GlobalRef->Action, nullptr));
int i = 0;
for (const auto &a: actions) {
@ -659,7 +654,7 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx(
env->SetObjectArrayElement(actionArray, i++, obj);
}
env->SetObjectArrayElement(vararg, 0, actionArray);
auto statusObj = JRef(env, fcitxInputMethodStatusToJObject(env, *status));
auto statusObj = JRef(env, fcitxInputMethodStatusToJObject(env, status));
env->SetObjectArrayElement(vararg, 1, statusObj);
env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 7, *vararg);
};
@ -696,6 +691,14 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx(
env->SetObjectArrayElement(vararg, 4, hasNext);
env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 9, *vararg);
};
auto switchInputMethodCallback = [](const int reason, const std::string &oldIM) {
auto env = GlobalRef->AttachEnv();
auto vararg = JRef<jobjectArray>(env, env->NewObjectArray(2, GlobalRef->Object, nullptr));
auto reasonInteger = JRef(env, env->NewObject(GlobalRef->Integer, GlobalRef->IntegerInit, reason));
env->SetObjectArrayElement(vararg, 0, reasonInteger);
env->SetObjectArrayElement(vararg, 1, JString(env, oldIM));
env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 10, *vararg);
};
auto toastCallback = [](const std::string &s) {
auto env = GlobalRef->AttachEnv();
env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->ShowToast, *JString(env, s));
@ -716,6 +719,7 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx(
androidfrontend->template call<fcitx::IAndroidFrontend::setStatusAreaUpdateCallback>(statusAreaUpdateCallback);
androidfrontend->template call<fcitx::IAndroidFrontend::setDeleteSurroundingCallback>(deleteSurroundingCallback);
androidfrontend->template call<fcitx::IAndroidFrontend::setPagedCandidateCallback>(pagedCandidateCallback);
androidfrontend->template call<fcitx::IAndroidFrontend::setSwitchInputMethodCallback>(switchInputMethodCallback);
androidfrontend->template call<fcitx::IAndroidFrontend::setToastCallback>(toastCallback);
});
FCITX_INFO() << "Finishing startup";
@ -996,7 +1000,7 @@ extern "C"
JNIEXPORT void JNICALL
Java_org_fcitx_fcitx5_android_core_Fcitx_focusInputContextOutIn(JNIEnv *env, jclass clazz) {
RETURN_IF_NOT_RUNNING
auto& instance = Fcitx::Instance();
auto &instance = Fcitx::Instance();
instance.focusInputContext(false);
instance.focusInputContext(true);
}

View File

@ -133,8 +133,8 @@ sealed class FcitxEvent<T>(open val data: T) {
override val eventType = EventType.PagedCandidate
enum class LayoutHint(value: Int) {
NotSet(0), Vertical(1), Horizontal(2);
enum class LayoutHint {
NotSet, Vertical, Horizontal;
companion object {
private val Types = entries.toTypedArray()
@ -180,6 +180,30 @@ sealed class FcitxEvent<T>(open val data: T) {
}
}
data class SwitchInputMethodEvent(override val data: Data) :
FcitxEvent<SwitchInputMethodEvent.Data>(data) {
override val eventType: EventType
get() = EventType.SwitchInputMethod
/**
* The reason why input method is switched to another.
*
* translated from
* [fcitx/event.h](https://github.com/fcitx/fcitx5/blob/5.1.16/src/lib/fcitx/event.h#L41)
*/
enum class Reason {
Trigger, Deactivate, AltTrigger, Activate, Enumerate, GroupChange, CapabilityChanged, Other;
companion object {
private val Types = entries.toTypedArray()
fun of(value: Int) = Types[value]
}
}
data class Data(val reason: Reason, val oldInputMethod: String)
}
data class UnknownEvent(override val data: Array<Any>) : FcitxEvent<Array<Any>>(data) {
override val eventType = EventType.Unknown
@ -211,6 +235,7 @@ sealed class FcitxEvent<T>(open val data: T) {
StatusArea,
DeleteSurrounding,
PagedCandidate,
SwitchInputMethod,
Unknown
}
@ -274,6 +299,12 @@ sealed class FcitxEvent<T>(open val data: T) {
)
)
}
EventType.SwitchInputMethod -> SwitchInputMethodEvent(
SwitchInputMethodEvent.Data(
SwitchInputMethodEvent.Reason.of(params[0] as Int),
params[1] as String
)
)
else -> UnknownEvent(params)
}
}

View File

@ -307,9 +307,16 @@ class FcitxInputMethodService : LifecycleInputMethodService() {
// [^1]: notify system that input method subtype has changed
switchInputMethod(InputMethodUtil.componentName, subtype)
}
if (inputDeviceMgr.evaluateOnInputMethodChange()) {
// show inputView for [CandidatesView] when it's likely changed by the user
forceShowSelf()
}
is FcitxEvent.SwitchInputMethodEvent -> {
val (reason) = event.data
if (reason != FcitxEvent.SwitchInputMethodEvent.Reason.CapabilityChanged &&
reason != FcitxEvent.SwitchInputMethodEvent.Reason.Other
) {
if (inputDeviceMgr.evaluateOnInputMethodChange()) {
// show inputView for [CandidatesView] when input method switched by user
forceShowSelf()
}
}
}
else -> {}

View File

@ -78,7 +78,6 @@ class InputDeviceManager(private val onChange: (Boolean) -> Unit) {
private var startedInputView = false
private var isNullInputType = true
private var hasKeyDown = false
private var candidatesViewMode by AppPrefs.getInstance().candidates.mode
@ -105,7 +104,6 @@ class InputDeviceManager(private val onChange: (Boolean) -> Unit) {
* @return should force show input views on hardware key down
*/
fun evaluateOnKeyDown(e: KeyEvent, service: FcitxInputMethodService): Boolean {
hasKeyDown = true
if (startedInputView) {
// filter out back/home/volume buttons and combination keys
if (e.unicodeChar != 0) {
@ -156,14 +154,13 @@ class InputDeviceManager(private val onChange: (Boolean) -> Unit) {
}
/**
* @return should force show inputView for [CandidatesView] when input method changes
* @return should force show inputView for [CandidatesView] when input method switched by user
*/
fun evaluateOnInputMethodChange(): Boolean {
return if (isVirtualKeyboard || startedInputView) false else hasKeyDown
return !isVirtualKeyboard && !startedInputView
}
fun onFinishInputView() {
startedInputView = false
hasKeyDown = false
}
}