From 5c1097cbc19131a7770b2a3f01f6f24e626991d2 Mon Sep 17 00:00:00 2001 From: joshua <54235339+sujoshua@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:54:33 +0800 Subject: [PATCH] IME: Improve handling of text-input and ime-relay (#5147) * input: Handling multiple surfaces for the text-input-v1 protocol implementation and imporve InputMethodRelay logic fixes #2708 * clang-format * minor style nits --------- Co-authored-by: Vaxry --- src/helpers/WLClasses.hpp | 9 +- src/managers/input/InputMethodRelay.cpp | 207 ++++++++++++++---------- src/managers/input/InputMethodRelay.hpp | 21 ++- src/protocols/TextInputV1.cpp | 8 +- 4 files changed, 145 insertions(+), 100 deletions(-) diff --git a/src/helpers/WLClasses.hpp b/src/helpers/WLClasses.hpp index d48fdff7..57f216b8 100644 --- a/src/helpers/WLClasses.hpp +++ b/src/helpers/WLClasses.hpp @@ -291,17 +291,14 @@ struct SSwipeGesture { struct STextInputV1; struct STextInput { - wlr_text_input_v3* pWlrInput = nullptr; - STextInputV1* pV1Input = nullptr; - - wlr_surface* pPendingSurface = nullptr; + wlr_text_input_v3* pWlrInput = nullptr; + STextInputV1* pV1Input = nullptr; + wlr_surface* focusedSurface = nullptr; DYNLISTENER(textInputEnable); DYNLISTENER(textInputDisable); DYNLISTENER(textInputCommit); DYNLISTENER(textInputDestroy); - - DYNLISTENER(pendingSurfaceDestroy); }; struct SIMEKbGrab { diff --git a/src/managers/input/InputMethodRelay.cpp b/src/managers/input/InputMethodRelay.cpp index fb668cc2..44ba3e42 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -87,14 +87,8 @@ void CInputMethodRelay::onNewIME(wlr_input_method_v2* pIME) { Debug::log(LOG, "IME Destroy"); - if (PTI) { - setPendingSurface(PTI, focusedSurface(PTI)); - - if (PTI->pWlrInput) - wlr_text_input_v3_send_leave(PTI->pWlrInput); - else - zwp_text_input_v1_send_leave(PTI->pV1Input->resourceImpl); - } + if (PTI) + onTextInputEnter(PTI->focusedSurface); }, this, "IMERelay"); @@ -144,16 +138,8 @@ void CInputMethodRelay::onNewIME(wlr_input_method_v2* pIME) { }, this, "IMERelay"); - const auto PTI = getFocusableTextInput(); - - if (PTI) { - if (PTI->pWlrInput) - wlr_text_input_v3_send_enter(PTI->pWlrInput, PTI->pPendingSurface); - else - zwp_text_input_v1_send_enter(PTI->pV1Input->resourceImpl, PTI->pPendingSurface->resource); - - setPendingSurface(PTI, nullptr); - } + if (const auto PTI = getFocusedTextInput(); PTI) + onTextInputEnter(PTI->focusedSurface); } wlr_surface* CInputMethodRelay::focusedSurface(STextInput* pTI) { @@ -322,22 +308,8 @@ SIMEKbGrab* CInputMethodRelay::getIMEKeyboardGrab(SKeyboard* pKeyboard) { } STextInput* CInputMethodRelay::getFocusedTextInput() { - - for (auto& ti : m_lTextInputs) { - if (focusedSurface(&ti)) { - return &ti; - } - } - - return nullptr; -} - -STextInput* CInputMethodRelay::getFocusableTextInput() { - for (auto& ti : m_lTextInputs) { - if (ti.pPendingSurface) { - return &ti; - } - } + if (m_pFocusedSurface) + return getTextInput(m_pFocusedSurface); return nullptr; } @@ -347,6 +319,13 @@ void CInputMethodRelay::onNewTextInput(wlr_text_input_v3* pInput) { } void CInputMethodRelay::createNewTextInput(wlr_text_input_v3* pInput, STextInputV1* pTIV1) { + + if (pInput) { + if (!setTextInputVersion(wl_resource_get_client(pInput->resource), 3)) + return; + } else if (!setTextInputVersion(pTIV1->client, 1)) + return; + const auto PTEXTINPUT = &m_lTextInputs.emplace_back(); PTEXTINPUT->pWlrInput = pInput; @@ -357,7 +336,7 @@ void CInputMethodRelay::createNewTextInput(wlr_text_input_v3* pInput, STextInput PTEXTINPUT->hyprListener_textInputEnable.initCallback( pInput ? &pInput->events.enable : &pTIV1->sEnable, - [](void* owner, void* data) { + [&](void* owner, void* data) { const auto PINPUT = (STextInput*)owner; if (!g_pInputManager->m_sIMERelay.m_pWLRIME) { @@ -365,6 +344,15 @@ void CInputMethodRelay::createNewTextInput(wlr_text_input_v3* pInput, STextInput return; } + // v1 only, map surface to PTI + if (PINPUT->pV1Input) { + wlr_surface* pSurface = wlr_surface_from_resource((wl_resource*)data); + PINPUT->focusedSurface = pSurface; + setSurfaceToPTI(pSurface, PINPUT); + if (m_pFocusedSurface == pSurface) + onTextInputEnter(pSurface); + } + Debug::log(LOG, "Enable TextInput"); wlr_input_method_v2_send_activate(g_pInputManager->m_sIMERelay.m_pWLRIME); @@ -405,6 +393,7 @@ void CInputMethodRelay::createNewTextInput(wlr_text_input_v3* pInput, STextInput wlr_input_method_v2_send_deactivate(g_pInputManager->m_sIMERelay.m_pWLRIME); + g_pInputManager->m_sIMERelay.removeSurfaceToPTI(PINPUT); g_pInputManager->m_sIMERelay.commitIMEState(PINPUT); }, PTEXTINPUT, "textInput"); @@ -425,13 +414,13 @@ void CInputMethodRelay::createNewTextInput(wlr_text_input_v3* pInput, STextInput g_pInputManager->m_sIMERelay.commitIMEState(PINPUT); } - g_pInputManager->m_sIMERelay.setPendingSurface(PINPUT, nullptr); - PINPUT->hyprListener_textInputCommit.removeCallback(); PINPUT->hyprListener_textInputDestroy.removeCallback(); PINPUT->hyprListener_textInputDisable.removeCallback(); PINPUT->hyprListener_textInputEnable.removeCallback(); + g_pInputManager->m_sIMERelay.removeTextInputVersion(PINPUT->pWlrInput ? wl_resource_get_client(PINPUT->pWlrInput->resource) : PINPUT->pV1Input->client); + g_pInputManager->m_sIMERelay.removeSurfaceToPTI(PINPUT); g_pInputManager->m_sIMERelay.removeTextInput(PINPUT); }, PTEXTINPUT, "textInput"); @@ -478,64 +467,110 @@ void CInputMethodRelay::onKeyboardFocus(wlr_surface* pSurface) { if (!m_pWLRIME) return; - auto client = [](STextInput* pTI) -> wl_client* { return pTI->pWlrInput ? wl_resource_get_client(pTI->pWlrInput->resource) : pTI->pV1Input->client; }; + if (pSurface == m_pFocusedSurface) + return; - for (auto& ti : m_lTextInputs) { - if (ti.pPendingSurface) { + // say goodbye to the last focused surface + if (STextInput* lastTI = getTextInput(m_pFocusedSurface); lastTI) { + wlr_input_method_v2_send_deactivate(m_pWLRIME); + commitIMEState(lastTI); + onTextInputLeave(m_pFocusedSurface); + } - if (pSurface != ti.pPendingSurface) - setPendingSurface(&ti, nullptr); + // do some work for the new focused surface + m_pFocusedSurface = pSurface; - } else if (focusedSurface(&ti)) { - - if (pSurface != focusedSurface(&ti)) { - wlr_input_method_v2_send_deactivate(m_pWLRIME); - commitIMEState(&ti); - - if (ti.pWlrInput) - wlr_text_input_v3_send_leave(ti.pWlrInput); - else { - zwp_text_input_v1_send_leave(ti.pV1Input->resourceImpl); - ti.pV1Input->focusedSurface = nullptr; - ti.pV1Input->active = false; - } - } else { - continue; - } - } - - if (pSurface && client(&ti) == wl_resource_get_client(pSurface->resource)) { - - if (m_pWLRIME) { - if (ti.pWlrInput) - wlr_text_input_v3_send_enter(ti.pWlrInput, pSurface); - else { - zwp_text_input_v1_send_enter(ti.pV1Input->resourceImpl, pSurface->resource); - ti.pV1Input->focusedSurface = pSurface; - ti.pV1Input->active = true; - } - } else { - setPendingSurface(&ti, pSurface); + /* + * v3 only. v1 is handled by hyprListener_textInputEnable. + * POSSIBLE BUG here: if one client has multiple STextInput and multiple surfaces, for any pSurface we can only record the last found ti. + * since original code has the same problem, it may not be a big deal. + */ + if (getTextInputVersion(wl_resource_get_client(pSurface->resource)) == 3) { + if (!getTextInput(pSurface)) { + auto client = [](STextInput* pTI) -> wl_client* { return pTI->pWlrInput ? wl_resource_get_client(pTI->pWlrInput->resource) : pTI->pV1Input->client; }; + for (auto& ti : m_lTextInputs) { + if (client(&ti) == wl_resource_get_client(pSurface->resource) && ti.pWlrInput) + setSurfaceToPTI(pSurface, &ti); } } } + + onTextInputEnter(m_pFocusedSurface); } -void CInputMethodRelay::setPendingSurface(STextInput* pInput, wlr_surface* pSurface) { - pInput->pPendingSurface = pSurface; +void CInputMethodRelay::onTextInputLeave(wlr_surface* pSurface) { + if (!pSurface) + return; + STextInput* ti = getTextInput(pSurface); + if (!ti) + return; + + if (ti->pWlrInput) + wlr_text_input_v3_send_leave(ti->pWlrInput); + else { + zwp_text_input_v1_send_leave(ti->pV1Input->resourceImpl); + ti->pV1Input->focusedSurface = nullptr; + ti->pV1Input->active = false; + } +} + +void CInputMethodRelay::onTextInputEnter(wlr_surface* pSurface) { + if (!pSurface) + return; + + STextInput* ti = getTextInput(pSurface); + if (!ti) + return; + + if (ti->pWlrInput) + wlr_text_input_v3_send_enter(ti->pWlrInput, pSurface); + else { + zwp_text_input_v1_send_enter(ti->pV1Input->resourceImpl, pSurface->resource); + ti->pV1Input->focusedSurface = pSurface; + ti->pV1Input->active = true; + } +} + +void CInputMethodRelay::setSurfaceToPTI(wlr_surface* pSurface, STextInput* pInput) { if (pSurface) { - pInput->hyprListener_pendingSurfaceDestroy.initCallback( - &pSurface->events.destroy, - [](void* owner, void* data) { - const auto PINPUT = (STextInput*)owner; - - PINPUT->pPendingSurface = nullptr; - - PINPUT->hyprListener_pendingSurfaceDestroy.removeCallback(); - }, - pInput, "TextInput"); - } else { - pInput->hyprListener_pendingSurfaceDestroy.removeCallback(); + m_mSurfaceToTextInput[pSurface] = pInput; + pInput->focusedSurface = pSurface; } } + +void CInputMethodRelay::removeSurfaceToPTI(STextInput* pInput) { + if (pInput->focusedSurface) { + m_mSurfaceToTextInput.erase(pInput->focusedSurface); + pInput->focusedSurface = nullptr; + } +} + +STextInput* CInputMethodRelay::getTextInput(wlr_surface* pSurface) { + auto result = m_mSurfaceToTextInput.find(pSurface); + if (result != m_mSurfaceToTextInput.end()) + return result->second; + + return nullptr; +} + +int CInputMethodRelay::setTextInputVersion(wl_client* pClient, int version) { + if (int v = getTextInputVersion(pClient); v != 0 && v != version) { + Debug::log(WARN, "Client attempt to register text-input-v{}, but it has already registered text-input-v{}, ignored", version, v); + return 0; + } + m_mClientTextInputVersion.insert({pClient, version}); + return 1; +} + +int CInputMethodRelay::getTextInputVersion(wl_client* pClient) { + auto result = m_mClientTextInputVersion.find(pClient); + if (result != m_mClientTextInputVersion.end()) + return result->second; + + return 0; +} + +void CInputMethodRelay::removeTextInputVersion(wl_client* pClient) { + m_mClientTextInputVersion.erase(pClient); +} diff --git a/src/managers/input/InputMethodRelay.hpp b/src/managers/input/InputMethodRelay.hpp index 9f9e95f6..39465cab 100644 --- a/src/managers/input/InputMethodRelay.hpp +++ b/src/managers/input/InputMethodRelay.hpp @@ -22,9 +22,6 @@ class CInputMethodRelay { void onKeyboardFocus(wlr_surface*); STextInput* getFocusedTextInput(); - STextInput* getFocusableTextInput(); - - void setPendingSurface(STextInput*, wlr_surface*); SIMEKbGrab* getIMEKeyboardGrab(SKeyboard*); @@ -45,8 +42,22 @@ class CInputMethodRelay { DYNLISTENER(IMEGrab); DYNLISTENER(IMENewPopup); - void createNewTextInput(wlr_text_input_v3*, STextInputV1* tiv1 = nullptr); - wlr_surface* focusedSurface(STextInput* pInput); + void createNewTextInput(wlr_text_input_v3*, STextInputV1* tiv1 = nullptr); + + wlr_surface* focusedSurface(STextInput* pInput); + wlr_surface* m_pFocusedSurface; + void onTextInputLeave(wlr_surface* pSurface); + void onTextInputEnter(wlr_surface* pSurface); + + std::unordered_map m_mSurfaceToTextInput; + void setSurfaceToPTI(wlr_surface* pSurface, STextInput* pInput); + STextInput* getTextInput(wlr_surface* pSurface); + void removeSurfaceToPTI(STextInput* pInput); + + std::unordered_map m_mClientTextInputVersion; + int setTextInputVersion(wl_client* pClient, int version); + int getTextInputVersion(wl_client* pClient); + void removeTextInputVersion(wl_client* pClient); friend class CHyprRenderer; friend class CInputManager; diff --git a/src/protocols/TextInputV1.cpp b/src/protocols/TextInputV1.cpp index d69a79ca..8d1f6f97 100644 --- a/src/protocols/TextInputV1.cpp +++ b/src/protocols/TextInputV1.cpp @@ -113,8 +113,6 @@ void CTextInputV1ProtocolManager::removeTI(STextInputV1* pTI) { // if ((*TI)->resourceImpl) // wl_resource_destroy((*TI)->resourceImpl); - g_pInputManager->m_sIMERelay.removeTextInput((*TI)->pTextInput); - std::erase_if(m_pClients, [&](const auto& other) { return other.get() == pTI; }); } @@ -165,7 +163,11 @@ void CTextInputV1ProtocolManager::createTI(wl_client* client, wl_resource* resou void CTextInputV1ProtocolManager::handleActivate(wl_client* client, wl_resource* resource, wl_resource* seat, wl_resource* surface) { const auto PTI = tiFromResource(resource); - PTI->pTextInput->hyprListener_textInputEnable.emit(nullptr); + if (!surface) { + Debug::log(WARN, "Text-input-v1 PTI{:x}: No surface to activate text input on!", (uintptr_t)PTI); + return; + } + PTI->pTextInput->hyprListener_textInputEnable.emit(surface); } void CTextInputV1ProtocolManager::handleDeactivate(wl_client* client, wl_resource* resource, wl_resource* seat) {