From 8bcccf9f0f0f69986e315d4288dfdbf119dd19db Mon Sep 17 00:00:00 2001 From: Vaxry Date: Wed, 1 May 2024 16:41:17 +0100 Subject: [PATCH] ime-v2: move to new impl --- CMakeLists.txt | 1 + protocols/input-method-unstable-v2.xml | 494 ++++++++++++++++++++++++ protocols/meson.build | 1 + src/Compositor.cpp | 4 - src/Compositor.hpp | 1 - src/events/Events.hpp | 1 - src/events/Misc.cpp | 6 - src/helpers/MiscFunctions.cpp | 35 ++ src/helpers/MiscFunctions.hpp | 1 + src/helpers/WLClasses.hpp | 8 - src/includes.hpp | 1 - src/managers/KeybindManager.cpp | 3 - src/managers/ProtocolManager.cpp | 22 +- src/managers/input/InputManager.cpp | 27 +- src/managers/input/InputMethodPopup.cpp | 46 +-- src/managers/input/InputMethodPopup.hpp | 39 +- src/managers/input/InputMethodRelay.cpp | 128 ++---- src/managers/input/InputMethodRelay.hpp | 45 +-- src/managers/input/TextInput.cpp | 62 +-- src/managers/input/TextInput.hpp | 5 +- src/protocols/InputMethodV2.cpp | 383 ++++++++++++++++++ src/protocols/InputMethodV2.hpp | 160 ++++++++ 22 files changed, 1208 insertions(+), 265 deletions(-) create mode 100644 protocols/input-method-unstable-v2.xml create mode 100644 src/protocols/InputMethodV2.cpp create mode 100644 src/protocols/InputMethodV2.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fbb517e..abdcd967 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,6 +261,7 @@ protocol("unstable/text-input/text-input-unstable-v1.xml" "text-input-unstable-v protocolNew("protocols/wlr-gamma-control-unstable-v1.xml" "wlr-gamma-control-unstable-v1" true) protocolNew("protocols/wlr-foreign-toplevel-management-unstable-v1.xml" "wlr-foreign-toplevel-management-unstable-v1" true) protocolNew("protocols/wlr-output-power-management-unstable-v1.xml" "wlr-output-power-management-unstable-v1" true) +protocolNew("protocols/input-method-unstable-v2.xml" "input-method-unstable-v2" true) protocolNew("staging/tearing-control/tearing-control-v1.xml" "tearing-control-v1" false) protocolNew("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false) protocolNew("unstable/xdg-output/xdg-output-unstable-v1.xml" "xdg-output-unstable-v1" false) diff --git a/protocols/input-method-unstable-v2.xml b/protocols/input-method-unstable-v2.xml new file mode 100644 index 00000000..51bccf28 --- /dev/null +++ b/protocols/input-method-unstable-v2.xml @@ -0,0 +1,494 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows applications to act as input methods for compositors. + + An input method context is used to manage the state of the input method. + + Text strings are UTF-8 encoded, their indices and lengths are in bytes. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + An input method object allows for clients to compose text. + + The objects connects the client to a text input in an application, and + lets the client to serve as an input method for a seat. + + The zwp_input_method_v2 object can occupy two distinct states: active and + inactive. In the active state, the object is associated to and + communicates with a text input. In the inactive state, there is no + associated text input, and the only communication is with the compositor. + Initially, the input method is in the inactive state. + + Requests issued in the inactive state must be accepted by the compositor. + Because of the serial mechanism, and the state reset on activate event, + they will not have any effect on the state of the next text input. + + There must be no more than one input method object per seat. + + + + + + + + + Notification that a text input focused on this seat requested the input + method to be activated. + + This event serves the purpose of providing the compositor with an + active input method. + + This event resets all state associated with previous enable, disable, + surrounding_text, text_change_cause, and content_type events, as well + as the state associated with set_preedit_string, commit_string, and + delete_surrounding_text requests. In addition, it marks the + zwp_input_method_v2 object as active, and makes any existing + zwp_input_popup_surface_v2 objects visible. + + The surrounding_text, and content_type events must follow before the + next done event if the text input supports the respective + functionality. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Notification that no focused text input currently needs an active + input method on this seat. + + This event marks the zwp_input_method_v2 object as inactive. The + compositor must make all existing zwp_input_popup_surface_v2 objects + invisible until the next activate event. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Updates the surrounding plain text around the cursor, excluding the + preedit text. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. + + The argument text is a buffer containing the preedit string, and must + include the cursor position, and the complete selection. It should + contain additional characters before and after these. There is a + maximum length of wayland messages, so text can not be longer than 4000 + bytes. + + cursor is the byte offset of the cursor within the text buffer. + + anchor is the byte offset of the selection anchor within the text + buffer. If there is no selected text, anchor must be the same as + cursor. + + If this event does not arrive before the first done event, the input + method may assume that the text input does not support this + functionality and ignore following surrounding_text events. + + Values set with this event are double-buffered. They will get applied + and set to initial values on the next zwp_input_method_v2.done + event. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Tells the input method why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor position, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this event is double-buffered. It will get applied + and set to its initial value on the next zwp_input_method_v2.done + event. + + The initial value of cause is input_method. + + + + + + + Indicates the content type and hint for the current + zwp_input_method_v2 instance. + + Values set with this event are double-buffered. They will get applied + on the next zwp_input_method_v2.done event. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Atomically applies state changes recently sent to the client. + + The done event establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (content purpose, content hint, surrounding text, and + change cause) is conceptually double-buffered within an input method + context. + + Events modify the pending state, as opposed to the current state in use + by the input method. A done event atomically applies all pending state, + replacing the current state. After done, the new pending state is as + documented for each related request. + + Events must be applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + + + + + Send the commit string text for insertion to the application. + + Inserts a string at current cursor position (see commit event + sequence). The string to commit could be either just a single character + after a key press or the result of some composing. + + The argument text is a buffer containing the string to insert. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.commit request. + + The initial value of text is an empty string. + + + + + + + Send the pre-edit string text to the application text input. + + Place a new composing text (pre-edit) at the current cursor position. + Any previously set composing text must be removed. Any previously + existing selected text must be removed. The cursor is moved to a new + position within the preedit string. + + The argument text is a buffer containing the preedit string. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + The arguments cursor_begin and cursor_end are counted in bytes relative + to the beginning of the submitted string buffer. Cursor should be + hidden by the text input when both are equal to -1. + + cursor_begin indicates the beginning of the cursor. cursor_end + indicates the end of the cursor. It may be equal or different than + cursor_begin. + + Values set with this event are double-buffered. They must be applied on + the next zwp_input_method_v2.commit event. + + The initial value of text is an empty string. The initial value of + cursor_begin, and cursor_end are both 0. + + + + + + + + + Remove the surrounding text. + + before_length and after_length are the number of bytes before and after + the current cursor index (excluding the preedit text) to delete. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. In effect before_length is counted from the + beginning of preedit text, and after_length from its end (see commit + event sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_input_method_v2.commit request. + + The initial values of both before_length and after_length are 0. + + + + + + + + Apply state changes from commit_string, set_preedit_string and + delete_surrounding_text requests. + + The state relating to these events is double-buffered, and each one + modifies the pending state. This request replaces the current state + with the pending state. + + The connected text input is expected to proceed by evaluating the + changes in the following order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_input_method_v2 + object known to the client. The value of the serial argument must be + equal to the number of done events already issued by that object. When + the compositor receives a commit request with a serial different than + the number of past done events, it must proceed as normal, except it + should not change the current state of the zwp_input_method_v2 object. + + + + + + + Creates a new zwp_input_popup_surface_v2 object wrapping a given + surface. + + The surface gets assigned the "input_popup" role. If the surface + already has an assigned role, the compositor must issue a protocol + error. + + + + + + + + Allow an input method to receive hardware keyboard input and process + key events to generate text events (with pre-edit) over the wire. This + allows input methods which compose multiple key events for inputting + text like it is done for CJK languages. + + The compositor should send all keyboard events on the seat to the grab + holder via the returned wl_keyboard object. Nevertheless, the + compositor may decide not to forward any particular event. The + compositor must not further process any event after it has been + forwarded to the grab holder. + + Releasing the resulting wl_keyboard object releases the grab. + + + + + + + The input method ceased to be available. + + The compositor must issue this event as the only event on the object if + there was another input_method object associated with the same seat at + the time of its creation. + + The compositor must issue this request when the object is no longer + usable, e.g. due to seat removal. + + The input method context becomes inert and should be destroyed after + deactivation is handled. Any further requests and events except for the + destroy request must be ignored. + + + + + + Destroys the zwp_text_input_v2 object and any associated child + objects, i.e. zwp_input_popup_surface_v2 and + zwp_input_method_keyboard_grab_v2. + + + + + + + This interface marks a surface as a popup for interacting with an input + method. + + The compositor should place it near the active text input area. It must + be visible if and only if the input method is in the active state. + + The client must not destroy the underlying wl_surface while the + zwp_input_popup_surface_v2 object exists. + + + + + Notify about the position of the area of the text input expressed as a + rectangle in surface local coordinates. + + This is a hint to the input method telling it the relative position of + the text being entered. + + + + + + + + + + + + + + The zwp_input_method_keyboard_grab_v2 interface represents an exclusive + grab of the wl_keyboard interface associated with the seat. + + + + + This event provides a file descriptor to the client which can be + memory-mapped to provide a keyboard mapping description. + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. + + + + + + + + + + Notifies clients that the modifier and/or group state has changed, and + it should update its local state. + + + + + + + + + + + + + + + Informs the client about the keyboard's repeat rate and delay. + + This event is sent as soon as the zwp_input_method_keyboard_grab_v2 + object has been created, and is guaranteed to be received by the + client before any key press event. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + This event can be sent later on as well with a new value if necessary, + so clients should continue listening for the event past the creation + of zwp_input_method_keyboard_grab_v2. + + + + + + + + + The input method manager allows the client to become the input method on + a chosen seat. + + No more than one input method must be associated with any seat at any + given time. + + + + + Request a new input zwp_input_method_v2 object associated with a given + seat. + + + + + + + + Destroys the zwp_input_method_manager_v2 object. + + The zwp_input_method_v2 objects originating from it remain valid. + + + + diff --git a/protocols/meson.build b/protocols/meson.build index a0728e39..4017ff27 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -38,6 +38,7 @@ new_protocols = [ ['wlr-gamma-control-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], ['wlr-output-power-management-unstable-v1.xml'], + ['input-method-unstable-v2.xml'], [wl_protocol_dir, 'staging/tearing-control/tearing-control-v1.xml'], [wl_protocol_dir, 'staging/fractional-scale/fractional-scale-v1.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], diff --git a/src/Compositor.cpp b/src/Compositor.cpp index a4647afe..9ed0ea38 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -262,8 +262,6 @@ void CCompositor::initServer() { wlr_xdg_foreign_v1_create(m_sWLDisplay, m_sWLRForeignRegistry); wlr_xdg_foreign_v2_create(m_sWLDisplay, m_sWLRForeignRegistry); - m_sWLRIMEMgr = wlr_input_method_manager_v2_create(m_sWLDisplay); - m_sWLRHeadlessBackend = wlr_headless_backend_create(m_sWLEventLoop); if (!m_sWLRHeadlessBackend) { @@ -312,7 +310,6 @@ void CCompositor::initAllSignals() { addWLSignal(&m_sWLRVirtPtrMgr->events.new_virtual_pointer, &Events::listen_newVirtPtr, m_sWLRVirtPtrMgr, "VirtPtrMgr"); addWLSignal(&m_sWLRVKeyboardMgr->events.new_virtual_keyboard, &Events::listen_newVirtualKeyboard, m_sWLRVKeyboardMgr, "VKeyboardMgr"); addWLSignal(&m_sWLRRenderer->events.destroy, &Events::listen_RendererDestroy, m_sWLRRenderer, "WLRRenderer"); - addWLSignal(&m_sWLRIMEMgr->events.input_method, &Events::listen_newIME, m_sWLRIMEMgr, "IMEMgr"); if (m_sWRLDRMLeaseMgr) addWLSignal(&m_sWRLDRMLeaseMgr->events.request, &Events::listen_leaseRequest, &m_sWRLDRMLeaseMgr, "DRM"); @@ -355,7 +352,6 @@ void CCompositor::removeAllSignals() { removeWLSignal(&Events::listen_newVirtPtr); removeWLSignal(&Events::listen_newVirtualKeyboard); removeWLSignal(&Events::listen_RendererDestroy); - removeWLSignal(&Events::listen_newIME); if (m_sWRLDRMLeaseMgr) removeWLSignal(&Events::listen_leaseRequest); diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 3201b28f..b867742b 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -64,7 +64,6 @@ class CCompositor { wlr_virtual_pointer_manager_v1* m_sWLRVirtPtrMgr; wlr_tablet_manager_v2* m_sWLRTabletManager; wlr_xdg_foreign_registry* m_sWLRForeignRegistry; - wlr_input_method_manager_v2* m_sWLRIMEMgr; wlr_linux_dmabuf_v1* m_sWLRLinuxDMABuf; wlr_backend* m_sWLRHeadlessBackend; // ------------------------------------------------- // diff --git a/src/events/Events.hpp b/src/events/Events.hpp index f147c6bf..2dabac11 100644 --- a/src/events/Events.hpp +++ b/src/events/Events.hpp @@ -107,7 +107,6 @@ namespace Events { LISTENER(pinchEnd); // IME - LISTENER(newIME); LISTENER(newVirtualKeyboard); // Touch diff --git a/src/events/Misc.cpp b/src/events/Misc.cpp index e5ead5ff..13dc85a3 100644 --- a/src/events/Misc.cpp +++ b/src/events/Misc.cpp @@ -187,9 +187,3 @@ void Events::listener_sessionActive(wl_listener* listener, void* data) { g_pConfigManager->m_bWantsMonitorReload = true; } - -void Events::listener_newIME(wl_listener* listener, void* data) { - Debug::log(LOG, "New IME added!"); - - g_pInputManager->m_sIMERelay.onNewIME((wlr_input_method_v2*)data); -} diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index b8061bef..22aa48f4 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -2,9 +2,12 @@ #include "../defines.hpp" #include #include "../Compositor.hpp" +#include "../managers/TokenManager.hpp" #include #include #include +#include +#include #include #include #ifdef HAS_EXECINFO @@ -834,3 +837,35 @@ bool envEnabled(const std::string& env) { return false; return std::string(ENV) == "1"; } + +std::pair openExclusiveShm() { + std::string name = g_pTokenManager->getRandomUUID(); + + for (size_t i = 0; i < 69; ++i) { + int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) + return {fd, name}; + } + + return {-1, ""}; +} + +int allocateSHMFile(size_t len) { + auto [fd, name] = openExclusiveShm(); + if (fd < 0) + return -1; + + shm_unlink(name.c_str()); + + int ret; + do { + ret = ftruncate(fd, len); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +} diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index d8a86b95..80103eac 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -38,6 +38,7 @@ void throwError(const std::string& err); uint32_t drmFormatToGL(uint32_t drm); uint32_t glFormatToType(uint32_t gl); bool envEnabled(const std::string& env); +int allocateSHMFile(size_t len); template [[deprecated("use std::format instead")]] std::string getFormat(std::format_string fmt, Args&&... args) { diff --git a/src/helpers/WLClasses.hpp b/src/helpers/WLClasses.hpp index 024ac38d..2f14059b 100644 --- a/src/helpers/WLClasses.hpp +++ b/src/helpers/WLClasses.hpp @@ -218,14 +218,6 @@ struct SSwipeGesture { CMonitor* pMonitor = nullptr; }; -struct SIMEKbGrab { - wlr_input_method_keyboard_grab_v2* pWlrKbGrab = nullptr; - - wlr_keyboard* pKeyboard = nullptr; - - DYNLISTENER(grabDestroy); -}; - struct STouchDevice { wlr_input_device* pWlrDevice = nullptr; diff --git a/src/includes.hpp b/src/includes.hpp index ed40331f..15d5c1aa 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -83,7 +83,6 @@ extern "C" { #include #include #include -#include #include #include #include diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index ef74bf5c..1150ac2e 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -324,9 +324,6 @@ bool CKeybindManager::onKeyEvent(wlr_keyboard_key_event* e, SKeyboard* pKeyboard return true; } - if (pKeyboard->isVirtual && g_pInputManager->shouldIgnoreVirtualKeyboard(pKeyboard)) - return true; - if (!m_pXKBTranslationState) { Debug::log(ERR, "BUG THIS: m_pXKBTranslationState NULL!"); updateXKBTranslationState(); diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 580622b0..09ca5206 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -19,26 +19,7 @@ #include "../protocols/XDGActivation.hpp" #include "../protocols/IdleNotify.hpp" #include "../protocols/SessionLock.hpp" - -#include "tearing-control-v1.hpp" -#include "fractional-scale-v1.hpp" -#include "xdg-output-unstable-v1.hpp" -#include "cursor-shape-v1.hpp" -#include "idle-inhibit-unstable-v1.hpp" -#include "relative-pointer-unstable-v1.hpp" -#include "xdg-decoration-unstable-v1.hpp" -#include "alpha-modifier-v1.hpp" -#include "wlr-gamma-control-unstable-v1.hpp" -#include "ext-foreign-toplevel-list-v1.hpp" -#include "pointer-gestures-unstable-v1.hpp" -#include "wlr-foreign-toplevel-management-unstable-v1.hpp" -#include "keyboard-shortcuts-inhibit-unstable-v1.hpp" -#include "text-input-unstable-v3.hpp" -#include "pointer-constraints-unstable-v1.hpp" -#include "wlr-output-power-management-unstable-v1.hpp" -#include "xdg-activation-v1.hpp" -#include "ext-idle-notify-v1.hpp" -#include "ext-session-lock-v1.hpp" +#include "../protocols/InputMethodV2.hpp" CProtocolManager::CProtocolManager() { @@ -61,6 +42,7 @@ CProtocolManager::CProtocolManager() { PROTO::activation = std::make_unique(&xdg_activation_v1_interface, 1, "XDGActivation"); PROTO::idle = std::make_unique(&ext_idle_notifier_v1_interface, 1, "IdleNotify"); PROTO::sessionLock = std::make_unique(&ext_session_lock_manager_v1_interface, 1, "SessionLock"); + PROTO::ime = std::make_unique(&zwp_input_method_manager_v2_interface, 1, "IMEv2"); // Old protocol implementations. // TODO: rewrite them to use hyprwayland-scanner. diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index ca1597b1..7d14719f 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -10,6 +10,7 @@ #include "../../protocols/PointerConstraints.hpp" #include "../../protocols/IdleNotify.hpp" #include "../../protocols/SessionLock.hpp" +#include "../../protocols/InputMethodV2.hpp" CInputManager::CInputManager() { m_sListeners.setCursorShape = PROTO::cursorShape->events.setShape.registerListener([this](std::any data) { @@ -1207,6 +1208,8 @@ void CInputManager::onKeyboardKey(wlr_keyboard_key_event* e, SKeyboard* pKeyboar if (!pKeyboard->enabled) return; + const bool DISALLOWACTION = pKeyboard->isVirtual && shouldIgnoreVirtualKeyboard(pKeyboard); + const auto EMAP = std::unordered_map{{"keyboard", pKeyboard}, {"event", e}}; EMIT_HOOK_EVENT_CANCELLABLE("keyPress", EMAP); @@ -1216,17 +1219,16 @@ void CInputManager::onKeyboardKey(wlr_keyboard_key_event* e, SKeyboard* pKeyboar g_pKeybindManager->dpms("on"); } - bool passEvent = g_pKeybindManager->onKeyEvent(e, pKeyboard); + bool passEvent = DISALLOWACTION || g_pKeybindManager->onKeyEvent(e, pKeyboard); PROTO::idle->onActivity(); if (passEvent) { + const auto IME = m_sIMERelay.m_pIME.lock(); - const auto PIMEGRAB = m_sIMERelay.getIMEKeyboardGrab(pKeyboard); - - if (PIMEGRAB && PIMEGRAB->pWlrKbGrab && PIMEGRAB->pWlrKbGrab->input_method) { - wlr_input_method_keyboard_grab_v2_set_keyboard(PIMEGRAB->pWlrKbGrab, wlr_keyboard_from_input_device(pKeyboard->keyboard)); - wlr_input_method_keyboard_grab_v2_send_key(PIMEGRAB->pWlrKbGrab, e->time_msec, e->keycode, e->state); + if (IME && IME->hasGrab() && !DISALLOWACTION) { + IME->setKeyboard(wlr_keyboard_from_input_device(pKeyboard->keyboard)); + IME->sendKey(e->time_msec, e->keycode, e->state); } else { wlr_seat_set_keyboard(g_pCompositor->m_sSeat.seat, wlr_keyboard_from_input_device(pKeyboard->keyboard)); wlr_seat_keyboard_notify_key(g_pCompositor->m_sSeat.seat, e->time_msec, e->keycode, e->state); @@ -1240,16 +1242,18 @@ void CInputManager::onKeyboardMod(void* data, SKeyboard* pKeyboard) { if (!pKeyboard->enabled) return; - const auto PIMEGRAB = m_sIMERelay.getIMEKeyboardGrab(pKeyboard); + const bool DISALLOWACTION = pKeyboard->isVirtual && shouldIgnoreVirtualKeyboard(pKeyboard); const auto ALLMODS = accumulateModsFromAllKBs(); auto MODS = wlr_keyboard_from_input_device(pKeyboard->keyboard)->modifiers; MODS.depressed = ALLMODS; - if (PIMEGRAB && PIMEGRAB->pWlrKbGrab && PIMEGRAB->pWlrKbGrab->input_method) { - wlr_input_method_keyboard_grab_v2_set_keyboard(PIMEGRAB->pWlrKbGrab, wlr_keyboard_from_input_device(pKeyboard->keyboard)); - wlr_input_method_keyboard_grab_v2_send_modifiers(PIMEGRAB->pWlrKbGrab, &MODS); + const auto IME = m_sIMERelay.m_pIME.lock(); + + if (IME && IME->hasGrab() && !DISALLOWACTION) { + IME->setKeyboard(wlr_keyboard_from_input_device(pKeyboard->keyboard)); + IME->sendMods(MODS.depressed, MODS.latched, MODS.locked, MODS.group); } else { wlr_seat_set_keyboard(g_pCompositor->m_sSeat.seat, wlr_keyboard_from_input_device(pKeyboard->keyboard)); wlr_seat_keyboard_notify_modifiers(g_pCompositor->m_sSeat.seat, &MODS); @@ -1273,8 +1277,7 @@ void CInputManager::onKeyboardMod(void* data, SKeyboard* pKeyboard) { bool CInputManager::shouldIgnoreVirtualKeyboard(SKeyboard* pKeyboard) { return !pKeyboard || - (m_sIMERelay.m_pKeyboardGrab && - wl_resource_get_client(m_sIMERelay.m_pKeyboardGrab->pWlrKbGrab->resource) == wl_resource_get_client(wlr_input_device_get_virtual_keyboard(pKeyboard->keyboard)->resource)); + (!m_sIMERelay.m_pIME.expired() && m_sIMERelay.m_pIME.lock()->grabClient() == wl_resource_get_client(wlr_input_device_get_virtual_keyboard(pKeyboard->keyboard)->resource)); } void CInputManager::refocus() { diff --git a/src/managers/input/InputMethodPopup.cpp b/src/managers/input/InputMethodPopup.cpp index 3a56d30c..f2d49ae8 100644 --- a/src/managers/input/InputMethodPopup.cpp +++ b/src/managers/input/InputMethodPopup.cpp @@ -2,37 +2,14 @@ #include "InputManager.hpp" #include "../../Compositor.hpp" #include "../../protocols/FractionalScale.hpp" +#include "../../protocols/InputMethodV2.hpp" -CInputPopup::CInputPopup(wlr_input_popup_surface_v2* surf) : pWlr(surf) { - surface.assign(surf->surface); - initCallbacks(); -} - -static void onCommit(void* owner, void* data) { - const auto PPOPUP = (CInputPopup*)owner; - PPOPUP->onCommit(); -} - -static void onMap(void* owner, void* data) { - const auto PPOPUP = (CInputPopup*)owner; - PPOPUP->onMap(); -} - -static void onUnmap(void* owner, void* data) { - const auto PPOPUP = (CInputPopup*)owner; - PPOPUP->onUnmap(); -} - -static void onDestroy(void* owner, void* data) { - const auto PPOPUP = (CInputPopup*)owner; - PPOPUP->onDestroy(); -} - -void CInputPopup::initCallbacks() { - hyprListener_commitPopup.initCallback(&pWlr->surface->events.commit, &::onCommit, this, "IME Popup"); - hyprListener_mapPopup.initCallback(&pWlr->surface->events.map, &::onMap, this, "IME Popup"); - hyprListener_unmapPopup.initCallback(&pWlr->surface->events.unmap, &::onUnmap, this, "IME Popup"); - hyprListener_destroyPopup.initCallback(&pWlr->events.destroy, &::onDestroy, this, "IME Popup"); +CInputPopup::CInputPopup(SP popup_) : popup(popup_) { + listeners.commit = popup_->events.commit.registerListener([this](std::any d) { onCommit(); }); + listeners.map = popup_->events.map.registerListener([this](std::any d) { onMap(); }); + listeners.unmap = popup_->events.unmap.registerListener([this](std::any d) { onUnmap(); }); + listeners.destroy = popup_->events.destroy.registerListener([this](std::any d) { onDestroy(); }); + surface.assign(popup_->surface()); } CWLSurface* CInputPopup::queryOwner() { @@ -45,11 +22,6 @@ CWLSurface* CInputPopup::queryOwner() { } void CInputPopup::onDestroy() { - hyprListener_commitPopup.removeCallback(); - hyprListener_destroyPopup.removeCallback(); - hyprListener_mapPopup.removeCallback(); - hyprListener_unmapPopup.removeCallback(); - g_pInputManager->m_sIMERelay.removePopup(this); } @@ -101,7 +73,7 @@ void CInputPopup::damageSurface() { } void CInputPopup::updateBox() { - if (!surface.wlr()->mapped) + if (!popup.lock()->mapped) return; const auto OWNER = queryOwner(); @@ -142,7 +114,7 @@ void CInputPopup::updateBox() { popupOffset.x -= popupOverflow; CBox cursorBoxLocal({-popupOffset.x, -popupOffset.y}, cursorBoxParent.size()); - wlr_input_popup_surface_v2_send_text_input_rectangle(pWlr, cursorBoxLocal.pWlr()); + popup.lock()->sendInputRectangle(cursorBoxLocal); CBox popupBoxParent(cursorBoxParent.pos() + popupOffset, currentPopupSize); if (popupBoxParent != lastBoxLocal) { diff --git a/src/managers/input/InputMethodPopup.hpp b/src/managers/input/InputMethodPopup.hpp index aed4dbc2..61b45d74 100644 --- a/src/managers/input/InputMethodPopup.hpp +++ b/src/managers/input/InputMethodPopup.hpp @@ -4,17 +4,13 @@ #include "../../desktop/WLSurface.hpp" #include "../../macros.hpp" #include "../../helpers/Box.hpp" +#include "../../helpers/signal/Listener.hpp" -struct wlr_input_popup_surface_v2; +class CInputMethodPopupV2; class CInputPopup { public: - CInputPopup(wlr_input_popup_surface_v2* surf); - - void onDestroy(); - void onMap(); - void onUnmap(); - void onCommit(); + CInputPopup(SP popup); void damageEntire(); void damageSurface(); @@ -24,18 +20,25 @@ class CInputPopup { CBox globalBox(); wlr_surface* getWlrSurface(); + void onCommit(); + private: - void initCallbacks(); - CWLSurface* queryOwner(); - void updateBox(); + CWLSurface* queryOwner(); + void updateBox(); - wlr_input_popup_surface_v2* pWlr = nullptr; - CWLSurface surface; - CBox lastBoxLocal; - uint64_t lastMonitor = -1; + void onDestroy(); + void onMap(); + void onUnmap(); - DYNLISTENER(mapPopup); - DYNLISTENER(unmapPopup); - DYNLISTENER(destroyPopup); - DYNLISTENER(commitPopup); + WP popup; + CWLSurface surface; + CBox lastBoxLocal; + uint64_t lastMonitor = -1; + + struct { + CHyprSignalListener map; + CHyprSignalListener unmap; + CHyprSignalListener destroy; + CHyprSignalListener commit; + } listeners; }; diff --git a/src/managers/input/InputMethodRelay.cpp b/src/managers/input/InputMethodRelay.cpp index 4f1cd278..778f3086 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -2,98 +2,53 @@ #include "InputManager.hpp" #include "../../Compositor.hpp" #include "../../protocols/TextInputV3.hpp" +#include "../../protocols/InputMethodV2.hpp" CInputMethodRelay::CInputMethodRelay() { static auto P = g_pHookSystem->hookDynamic("keyboardFocus", [&](void* self, SCallbackInfo& info, std::any param) { onKeyboardFocus(std::any_cast(param)); }); listeners.newTIV3 = PROTO::textInputV3->events.newTextInput.registerListener([this](std::any ti) { onNewTextInput(ti); }); + listeners.newIME = PROTO::ime->events.newIME.registerListener([this](std::any ime) { onNewIME(std::any_cast>(ime)); }); } -void CInputMethodRelay::onNewIME(wlr_input_method_v2* pIME) { - if (m_pWLRIME) { +void CInputMethodRelay::onNewIME(SP pIME) { + if (!m_pIME.expired()) { Debug::log(ERR, "Cannot register 2 IMEs at once!"); - wlr_input_method_v2_send_unavailable(pIME); + pIME->unavailable(); return; } - m_pWLRIME = pIME; + m_pIME = pIME; - hyprListener_IMECommit.initCallback( - &m_pWLRIME->events.commit, - [&](void* owner, void* data) { - const auto PTI = getFocusedTextInput(); - const auto PIMR = (CInputMethodRelay*)owner; + listeners.commitIME = pIME->events.onCommit.registerListener([this](std::any d) { + const auto PTI = getFocusedTextInput(); - if (!PTI) { - Debug::log(LOG, "No focused TextInput on IME Commit"); - return; - } + if (!PTI) { + Debug::log(LOG, "No focused TextInput on IME Commit"); + return; + } - PTI->updateIMEState(PIMR->m_pWLRIME); - }, - this, "IMERelay"); + PTI->updateIMEState(m_pIME.lock()); + }); - hyprListener_IMEDestroy.initCallback( - &m_pWLRIME->events.destroy, - [&](void* owner, void* data) { - m_pWLRIME = nullptr; + listeners.destroyIME = pIME->events.destroy.registerListener([this](std::any d) { + const auto PTI = getFocusedTextInput(); - hyprListener_IMEDestroy.removeCallback(); - hyprListener_IMECommit.removeCallback(); - hyprListener_IMEGrab.removeCallback(); - hyprListener_IMENewPopup.removeCallback(); + Debug::log(LOG, "IME Destroy"); - m_pKeyboardGrab.reset(nullptr); + if (PTI) + PTI->leave(); - const auto PTI = getFocusedTextInput(); + m_pIME.reset(); + }); - Debug::log(LOG, "IME Destroy"); + listeners.newPopup = pIME->events.newPopup.registerListener([this](std::any d) { + m_vIMEPopups.emplace_back(std::make_unique(std::any_cast>(d))); - if (PTI) - PTI->leave(); - }, - this, "IMERelay"); - - hyprListener_IMEGrab.initCallback( - &m_pWLRIME->events.grab_keyboard, - [&](void* owner, void* data) { - Debug::log(LOG, "IME TextInput Keyboard Grab new"); - - m_pKeyboardGrab.reset(nullptr); - - m_pKeyboardGrab = std::make_unique(); - - m_pKeyboardGrab->pKeyboard = wlr_seat_get_keyboard(g_pCompositor->m_sSeat.seat); - - const auto PKBGRAB = (wlr_input_method_keyboard_grab_v2*)data; - - m_pKeyboardGrab->pWlrKbGrab = PKBGRAB; - - wlr_input_method_keyboard_grab_v2_set_keyboard(m_pKeyboardGrab->pWlrKbGrab, m_pKeyboardGrab->pKeyboard); - - m_pKeyboardGrab->hyprListener_grabDestroy.initCallback( - &PKBGRAB->events.destroy, - [&](void* owner, void* data) { - m_pKeyboardGrab->hyprListener_grabDestroy.removeCallback(); - - Debug::log(LOG, "IME TextInput Keyboard Grab destroy"); - - m_pKeyboardGrab.reset(nullptr); - }, - m_pKeyboardGrab.get(), "IME Keyboard Grab"); - }, - this, "IMERelay"); - - hyprListener_IMENewPopup.initCallback( - &m_pWLRIME->events.new_popup_surface, - [&](void* owner, void* data) { - m_vIMEPopups.emplace_back(std::make_unique((wlr_input_popup_surface_v2*)data)); - - Debug::log(LOG, "New input popup"); - }, - this, "IMERelay"); + Debug::log(LOG, "New input popup"); + }); if (!g_pCompositor->m_pLastFocus) return; @@ -117,22 +72,6 @@ void CInputMethodRelay::removePopup(CInputPopup* pPopup) { std::erase_if(m_vIMEPopups, [pPopup](const auto& other) { return other.get() == pPopup; }); } -SIMEKbGrab* CInputMethodRelay::getIMEKeyboardGrab(SKeyboard* pKeyboard) { - - if (!m_pWLRIME) - return nullptr; - - if (!m_pKeyboardGrab.get()) - return nullptr; - - const auto VIRTKB = wlr_input_device_get_virtual_keyboard(pKeyboard->keyboard); - - if (VIRTKB && (wl_resource_get_client(VIRTKB->resource) == wl_resource_get_client(m_pKeyboardGrab->pWlrKbGrab->resource))) - return nullptr; - - return m_pKeyboardGrab.get(); -} - CTextInput* CInputMethodRelay::getFocusedTextInput() { if (!g_pCompositor->m_pLastFocus) return nullptr; @@ -164,33 +103,30 @@ void CInputMethodRelay::updateAllPopups() { } void CInputMethodRelay::activateIME(CTextInput* pInput) { - if (!m_pWLRIME) + if (m_pIME.expired()) return; - wlr_input_method_v2_send_activate(g_pInputManager->m_sIMERelay.m_pWLRIME); + m_pIME.lock()->activate(); commitIMEState(pInput); } void CInputMethodRelay::deactivateIME(CTextInput* pInput) { - if (!m_pWLRIME) + if (m_pIME.expired()) return; - if (!m_pWLRIME->active) - return; - - wlr_input_method_v2_send_deactivate(g_pInputManager->m_sIMERelay.m_pWLRIME); + m_pIME.lock()->deactivate(); commitIMEState(pInput); } void CInputMethodRelay::commitIMEState(CTextInput* pInput) { - if (!m_pWLRIME) + if (m_pIME.expired()) return; - pInput->commitStateToIME(m_pWLRIME); + pInput->commitStateToIME(m_pIME.lock()); } void CInputMethodRelay::onKeyboardFocus(wlr_surface* pSurface) { - if (!m_pWLRIME) + if (m_pIME.expired()) return; if (pSurface == m_pLastKbFocus) diff --git a/src/managers/input/InputMethodRelay.hpp b/src/managers/input/InputMethodRelay.hpp index 1b2cb07a..2896e8e8 100644 --- a/src/managers/input/InputMethodRelay.hpp +++ b/src/managers/input/InputMethodRelay.hpp @@ -11,39 +11,36 @@ class CInputManager; class CHyprRenderer; struct STextInputV1; +class CInputMethodV2; class CInputMethodRelay { public: CInputMethodRelay(); - void onNewIME(wlr_input_method_v2*); - void onNewTextInput(std::any tiv3); - void onNewTextInput(STextInputV1* pTIV1); + void onNewIME(SP); + void onNewTextInput(std::any tiv3); + void onNewTextInput(STextInputV1* pTIV1); - wlr_input_method_v2* m_pWLRIME = nullptr; + void activateIME(CTextInput* pInput); + void deactivateIME(CTextInput* pInput); + void commitIMEState(CTextInput* pInput); + void removeTextInput(CTextInput* pInput); - void activateIME(CTextInput* pInput); - void deactivateIME(CTextInput* pInput); - void commitIMEState(CTextInput* pInput); - void removeTextInput(CTextInput* pInput); + void onKeyboardFocus(wlr_surface*); - void onKeyboardFocus(wlr_surface*); + CTextInput* getFocusedTextInput(); - CTextInput* getFocusedTextInput(); + void setIMEPopupFocus(CInputPopup*, wlr_surface*); + void removePopup(CInputPopup*); - SIMEKbGrab* getIMEKeyboardGrab(SKeyboard*); + CInputPopup* popupFromCoords(const Vector2D& point); + CInputPopup* popupFromSurface(const wlr_surface* surface); - void setIMEPopupFocus(CInputPopup*, wlr_surface*); - void removePopup(CInputPopup*); + void updateAllPopups(); - CInputPopup* popupFromCoords(const Vector2D& point); - CInputPopup* popupFromSurface(const wlr_surface* surface); - - void updateAllPopups(); + WP m_pIME; private: - std::unique_ptr m_pKeyboardGrab; - std::vector> m_vTextInputs; std::vector> m_vIMEPopups; @@ -51,14 +48,12 @@ class CInputMethodRelay { struct { CHyprSignalListener newTIV3; + CHyprSignalListener newIME; + CHyprSignalListener commitIME; + CHyprSignalListener destroyIME; + CHyprSignalListener newPopup; } listeners; - DYNLISTENER(textInputNew); - DYNLISTENER(IMECommit); - DYNLISTENER(IMEDestroy); - DYNLISTENER(IMEGrab); - DYNLISTENER(IMENewPopup); - friend class CHyprRenderer; friend class CInputManager; friend class CTextInputV1ProtocolManager; diff --git a/src/managers/input/TextInput.cpp b/src/managers/input/TextInput.cpp index 12f13971..77718174 100644 --- a/src/managers/input/TextInput.cpp +++ b/src/managers/input/TextInput.cpp @@ -4,6 +4,7 @@ #include "../../protocols/TextInputV1.hpp" #include "../../Compositor.hpp" #include "../../protocols/TextInputV3.hpp" +#include "../../protocols/InputMethodV2.hpp" CTextInput::CTextInput(STextInputV1* ti) : pV1Input(ti) { ti->pTextInput = this; @@ -67,7 +68,7 @@ void CTextInput::initCallbacks() { void CTextInput::onEnabled(wlr_surface* surfV1) { Debug::log(LOG, "TI ENABLE"); - if (!g_pInputManager->m_sIMERelay.m_pWLRIME) { + if (g_pInputManager->m_sIMERelay.m_pIME.expired()) { // Debug::log(WARN, "Enabling TextInput on no IME!"); return; } @@ -85,7 +86,7 @@ void CTextInput::onEnabled(wlr_surface* surfV1) { } void CTextInput::onDisabled() { - if (!g_pInputManager->m_sIMERelay.m_pWLRIME) { + if (g_pInputManager->m_sIMERelay.m_pIME.expired()) { // Debug::log(WARN, "Disabling TextInput on no IME!"); return; } @@ -103,7 +104,7 @@ void CTextInput::onDisabled() { } void CTextInput::onCommit() { - if (!g_pInputManager->m_sIMERelay.m_pWLRIME) { + if (g_pInputManager->m_sIMERelay.m_pIME.expired()) { // Debug::log(WARN, "Committing TextInput on no IME!"); return; } @@ -218,67 +219,66 @@ wl_client* CTextInput::client() { return isV3() ? pV3Input.lock()->client() : pV1Input->client; } -void CTextInput::commitStateToIME(wlr_input_method_v2* ime) { +void CTextInput::commitStateToIME(SP ime) { if (isV3()) { const auto INPUT = pV3Input.lock(); if (INPUT->current.surrounding.updated) - wlr_input_method_v2_send_surrounding_text(ime, INPUT->current.surrounding.text.c_str(), INPUT->current.surrounding.cursor, INPUT->current.surrounding.anchor); + ime->surroundingText(INPUT->current.surrounding.text, INPUT->current.surrounding.cursor, INPUT->current.surrounding.anchor); - wlr_input_method_v2_send_text_change_cause(ime, INPUT->current.cause); + ime->textChangeCause(INPUT->current.cause); if (INPUT->current.contentType.updated) - wlr_input_method_v2_send_content_type(ime, INPUT->current.contentType.hint, INPUT->current.contentType.purpose); + ime->textContentType(INPUT->current.contentType.hint, INPUT->current.contentType.purpose); } else { if (pV1Input->pendingSurrounding.isPending) - wlr_input_method_v2_send_surrounding_text(ime, pV1Input->pendingSurrounding.text.c_str(), pV1Input->pendingSurrounding.cursor, pV1Input->pendingSurrounding.anchor); + ime->surroundingText(pV1Input->pendingSurrounding.text, pV1Input->pendingSurrounding.cursor, pV1Input->pendingSurrounding.anchor); - wlr_input_method_v2_send_text_change_cause(ime, 0); + ime->textChangeCause(ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD); if (pV1Input->pendingContentType.isPending) - wlr_input_method_v2_send_content_type(ime, pV1Input->pendingContentType.hint, pV1Input->pendingContentType.purpose); + ime->textContentType((zwpTextInputV3ContentHint)pV1Input->pendingContentType.hint, (zwpTextInputV3ContentPurpose)pV1Input->pendingContentType.purpose); } g_pInputManager->m_sIMERelay.updateAllPopups(); - wlr_input_method_v2_send_done(ime); + ime->done(); } -void CTextInput::updateIMEState(wlr_input_method_v2* ime) { +void CTextInput::updateIMEState(SP ime) { if (isV3()) { const auto INPUT = pV3Input.lock(); - if (ime->current.preedit.text) - INPUT->preeditString(ime->current.preedit.text, ime->current.preedit.cursor_begin, ime->current.preedit.cursor_end); + if (ime->current.preeditString.committed) + INPUT->preeditString(ime->current.preeditString.string, ime->current.preeditString.begin, ime->current.preeditString.end); - if (ime->current.commit_text) - INPUT->commitString(ime->current.commit_text); + if (ime->current.committedString.committed) + INPUT->commitString(ime->current.committedString.string); - if (ime->current.delete_.before_length || ime->current.delete_.after_length) - INPUT->deleteSurroundingText(ime->current.delete_.before_length, ime->current.delete_.after_length); + if (ime->current.deleteSurrounding.committed) + INPUT->deleteSurroundingText(ime->current.deleteSurrounding.before, ime->current.deleteSurrounding.after); INPUT->sendDone(); } else { - if (ime->current.preedit.text) { - zwp_text_input_v1_send_preedit_cursor(pV1Input->resourceImpl, ime->current.preedit.cursor_begin); - zwp_text_input_v1_send_preedit_styling(pV1Input->resourceImpl, 0, std::string(ime->current.preedit.text).length(), ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT); - zwp_text_input_v1_send_preedit_string(pV1Input->resourceImpl, pV1Input->serial, ime->current.preedit.text, ""); + if (ime->current.preeditString.committed) { + zwp_text_input_v1_send_preedit_cursor(pV1Input->resourceImpl, ime->current.preeditString.begin); + zwp_text_input_v1_send_preedit_styling(pV1Input->resourceImpl, 0, std::string(ime->current.preeditString.string).length(), ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT); + zwp_text_input_v1_send_preedit_string(pV1Input->resourceImpl, pV1Input->serial, ime->current.preeditString.string.c_str(), ""); } else { - zwp_text_input_v1_send_preedit_cursor(pV1Input->resourceImpl, ime->current.preedit.cursor_begin); + zwp_text_input_v1_send_preedit_cursor(pV1Input->resourceImpl, ime->current.preeditString.begin); zwp_text_input_v1_send_preedit_styling(pV1Input->resourceImpl, 0, 0, ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT); zwp_text_input_v1_send_preedit_string(pV1Input->resourceImpl, pV1Input->serial, "", ""); } - if (ime->current.commit_text) { - zwp_text_input_v1_send_commit_string(pV1Input->resourceImpl, pV1Input->serial, ime->current.commit_text); - } + if (ime->current.committedString.committed) + zwp_text_input_v1_send_commit_string(pV1Input->resourceImpl, pV1Input->serial, ime->current.committedString.string.c_str()); - if (ime->current.delete_.before_length || ime->current.delete_.after_length) { - zwp_text_input_v1_send_delete_surrounding_text(pV1Input->resourceImpl, std::string(ime->current.preedit.text).length() - ime->current.delete_.before_length, - ime->current.delete_.after_length + ime->current.delete_.before_length); + if (ime->current.deleteSurrounding.committed) { + zwp_text_input_v1_send_delete_surrounding_text(pV1Input->resourceImpl, std::string(ime->current.preeditString.string).length() - ime->current.deleteSurrounding.before, + ime->current.deleteSurrounding.after + ime->current.deleteSurrounding.before); - if (ime->current.preedit.text) - zwp_text_input_v1_send_commit_string(pV1Input->resourceImpl, pV1Input->serial, ime->current.preedit.text); + if (ime->current.preeditString.committed) + zwp_text_input_v1_send_commit_string(pV1Input->resourceImpl, pV1Input->serial, ime->current.preeditString.string.c_str()); } } } diff --git a/src/managers/input/TextInput.hpp b/src/managers/input/TextInput.hpp index 6d7e9c59..481d60c1 100644 --- a/src/managers/input/TextInput.hpp +++ b/src/managers/input/TextInput.hpp @@ -11,6 +11,7 @@ struct wl_client; struct STextInputV1; class CTextInputV3; +class CInputMethodV2; class CTextInput { public: @@ -23,8 +24,8 @@ class CTextInput { void leave(); void tiV1Destroyed(); wl_client* client(); - void commitStateToIME(wlr_input_method_v2* ime); - void updateIMEState(wlr_input_method_v2* ime); + void commitStateToIME(SP ime); + void updateIMEState(SP ime); void onEnabled(wlr_surface* surfV1 = nullptr); void onDisabled(); diff --git a/src/protocols/InputMethodV2.cpp b/src/protocols/InputMethodV2.cpp new file mode 100644 index 00000000..91dac671 --- /dev/null +++ b/src/protocols/InputMethodV2.cpp @@ -0,0 +1,383 @@ +#include "InputMethodV2.hpp" +#include "../Compositor.hpp" +#include + +#define LOGM PROTO::ime->protoLog + +CInputMethodKeyboardGrabV2::CInputMethodKeyboardGrabV2(SP resource_, SP owner_) : resource(resource_), owner(owner_) { + if (!resource->resource()) + return; + + resource->setRelease([this](CZwpInputMethodKeyboardGrabV2* r) { PROTO::ime->destroyResource(this); }); + resource->setOnDestroy([this](CZwpInputMethodKeyboardGrabV2* r) { PROTO::ime->destroyResource(this); }); + + const auto PKEYBOARD = wlr_seat_get_keyboard(g_pCompositor->m_sSeat.seat); + + if (!PKEYBOARD) { + LOGM(ERR, "IME called but no active keyboard???"); + return; + } + + sendKeyboardData(PKEYBOARD); +} + +CInputMethodKeyboardGrabV2::~CInputMethodKeyboardGrabV2() { + if (!owner.expired()) + std::erase_if(owner.lock()->grabs, [](const auto& g) { return g.expired(); }); +} + +void CInputMethodKeyboardGrabV2::sendKeyboardData(wlr_keyboard* keyboard) { + + if (keyboard == pLastKeyboard) + return; + + pLastKeyboard = keyboard; + + int keymapFD = allocateSHMFile(keyboard->keymap_size); + if (keymapFD < 0) { + LOGM(ERR, "Failed to create a keymap file for keyboard grab"); + return; + } + + void* data = mmap(nullptr, keyboard->keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED, keymapFD, 0); + if (data == MAP_FAILED) { + LOGM(ERR, "Failed to mmap a keymap file for keyboard grab"); + close(keymapFD); + return; + } + + memcpy(data, keyboard->keymap_string, keyboard->keymap_size); + munmap(data, keyboard->keymap_size); + + resource->sendKeymap(WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymapFD, keyboard->keymap_size); + + close(keymapFD); + + sendMods(0, 0, 0, 0); + + resource->sendRepeatInfo(keyboard->repeat_info.rate, keyboard->repeat_info.delay); +} + +void CInputMethodKeyboardGrabV2::sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state) { + const auto SERIAL = wlr_seat_client_next_serial(wlr_seat_client_for_wl_client(g_pCompositor->m_sSeat.seat, wl_resource_get_client(resource->resource()))); + + resource->sendKey(SERIAL, time, key, (uint32_t)state); +} + +void CInputMethodKeyboardGrabV2::sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { + const auto SERIAL = wlr_seat_client_next_serial(wlr_seat_client_for_wl_client(g_pCompositor->m_sSeat.seat, wl_resource_get_client(resource->resource()))); + + resource->sendModifiers(SERIAL, depressed, latched, locked, group); +} + +bool CInputMethodKeyboardGrabV2::good() { + return resource->resource(); +} + +SP CInputMethodKeyboardGrabV2::getOwner() { + return owner.lock(); +} + +wl_client* CInputMethodKeyboardGrabV2::client() { + return wl_resource_get_client(resource->resource()); +} + +CInputMethodPopupV2::CInputMethodPopupV2(SP resource_, SP owner_, wlr_surface* wlrSurface) : resource(resource_), owner(owner_) { + if (!resource->resource()) + return; + + resource->setDestroy([this](CZwpInputPopupSurfaceV2* r) { PROTO::ime->destroyResource(this); }); + resource->setOnDestroy([this](CZwpInputPopupSurfaceV2* r) { PROTO::ime->destroyResource(this); }); + + pSurface = wlrSurface; + + hyprListener_destroySurface.initCallback( + &wlrSurface->events.destroy, + [this](void* owner, void* data) { + if (mapped) + events.unmap.emit(); + + hyprListener_commitSurface.removeCallback(); + hyprListener_destroySurface.removeCallback(); + + if (g_pCompositor->m_pLastFocus == pSurface) + g_pCompositor->m_pLastFocus = nullptr; + + pSurface = nullptr; + }, + this, "IMEPopup"); + + hyprListener_commitSurface.initCallback( + &wlrSurface->events.commit, + [this](void* owner, void* data) { + if (pSurface->pending.buffer_width > 0 && pSurface->pending.buffer_height > 0 && !mapped) { + mapped = true; + wlr_surface_map(pSurface); + events.map.emit(); + return; + } + + if (pSurface->pending.buffer_width <= 0 && pSurface->pending.buffer_height <= 0 && mapped) { + mapped = false; + wlr_surface_unmap(pSurface); + events.unmap.emit(); + return; + } + + events.commit.emit(); + }, + this, "IMEPopup"); +} + +CInputMethodPopupV2::~CInputMethodPopupV2() { + if (!owner.expired()) + std::erase_if(owner.lock()->popups, [](const auto& p) { return p.expired(); }); + + events.destroy.emit(); +} + +bool CInputMethodPopupV2::good() { + return resource->resource(); +} + +void CInputMethodPopupV2::sendInputRectangle(const CBox& box) { + resource->sendTextInputRectangle(box.x, box.y, box.w, box.h); +} + +wlr_surface* CInputMethodPopupV2::surface() { + return pSurface; +} + +void CInputMethodV2::SState::reset() { + committedString.committed = false; + deleteSurrounding.committed = false; + preeditString.committed = false; +} + +CInputMethodV2::CInputMethodV2(SP resource_) : resource(resource_) { + if (!resource->resource()) + return; + + resource->setDestroy([this](CZwpInputMethodV2* r) { + events.destroy.emit(); + PROTO::ime->destroyResource(this); + }); + resource->setOnDestroy([this](CZwpInputMethodV2* r) { + events.destroy.emit(); + PROTO::ime->destroyResource(this); + }); + + resource->setCommitString([this](CZwpInputMethodV2* r, const char* str) { + pending.committedString.string = str; + pending.committedString.committed = true; + }); + + resource->setDeleteSurroundingText([this](CZwpInputMethodV2* r, uint32_t before, uint32_t after) { + pending.deleteSurrounding.before = before; + pending.deleteSurrounding.after = after; + pending.deleteSurrounding.committed = true; + }); + + resource->setSetPreeditString([this](CZwpInputMethodV2* r, const char* str, int32_t begin, int32_t end) { + pending.preeditString.string = str; + pending.preeditString.begin = begin; + pending.preeditString.end = end; + pending.preeditString.committed = true; + }); + + resource->setCommit([this](CZwpInputMethodV2* r, uint32_t serial) { + current = pending; + pending.reset(); + events.onCommit.emit(); + }); + + resource->setGetInputPopupSurface([this](CZwpInputMethodV2* r, uint32_t id, wl_resource* surface) { + const auto CLIENT = wl_resource_get_client(r->resource()); + const auto RESOURCE = PROTO::ime->m_vPopups.emplace_back(std::make_shared( + std::make_shared(CLIENT, wl_resource_get_version(r->resource()), id), self.lock(), wlr_surface_from_resource(surface))); + + if (!RESOURCE->good()) { + wl_resource_post_no_memory(r->resource()); + PROTO::ime->m_vPopups.pop_back(); + return; + } + + LOGM(LOG, "New IME Popup with resource id {}", id); + + popups.emplace_back(RESOURCE); + + events.newPopup.emit(RESOURCE); + }); + + resource->setGrabKeyboard([this](CZwpInputMethodV2* r, uint32_t id) { + const auto CLIENT = wl_resource_get_client(r->resource()); + const auto RESOURCE = PROTO::ime->m_vGrabs.emplace_back( + std::make_shared(std::make_shared(CLIENT, wl_resource_get_version(r->resource()), id), self.lock())); + + if (!RESOURCE->good()) { + wl_resource_post_no_memory(r->resource()); + PROTO::ime->m_vGrabs.pop_back(); + return; + } + + LOGM(LOG, "New IME Grab with resource id {}", id); + + grabs.emplace_back(RESOURCE); + }); +} + +CInputMethodV2::~CInputMethodV2() { + events.destroy.emit(); +} + +bool CInputMethodV2::good() { + return resource->resource(); +} + +void CInputMethodV2::activate() { + if (active) + return; + resource->sendActivate(); + active = true; +} + +void CInputMethodV2::deactivate() { + if (!active) + return; + resource->sendDeactivate(); + active = false; +} + +void CInputMethodV2::surroundingText(const std::string& text, uint32_t cursor, uint32_t anchor) { + resource->sendSurroundingText(text.c_str(), cursor, anchor); +} + +void CInputMethodV2::textChangeCause(zwpTextInputV3ChangeCause changeCause) { + resource->sendTextChangeCause((uint32_t)changeCause); +} + +void CInputMethodV2::textContentType(zwpTextInputV3ContentHint hint, zwpTextInputV3ContentPurpose purpose) { + resource->sendContentType((uint32_t)hint, (uint32_t)purpose); +} + +void CInputMethodV2::done() { + resource->sendDone(); +} + +void CInputMethodV2::unavailable() { + resource->sendUnavailable(); +} + +bool CInputMethodV2::hasGrab() { + return !grabs.empty(); +} + +wl_client* CInputMethodV2::grabClient() { + if (grabs.empty()) + return nullptr; + + for (auto& gw : grabs) { + auto g = gw.lock(); + + if (!g) + continue; + + return g->client(); + } + + return nullptr; +} + +void CInputMethodV2::sendInputRectangle(const CBox& box) { + inputRectangle = box; + for (auto& wp : popups) { + auto p = wp.lock(); + + if (!p) + continue; + + p->sendInputRectangle(inputRectangle); + } +} + +void CInputMethodV2::sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state) { + for (auto& gw : grabs) { + auto g = gw.lock(); + + if (!g) + continue; + + g->sendKey(time, key, state); + } +} + +void CInputMethodV2::sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { + for (auto& gw : grabs) { + auto g = gw.lock(); + + if (!g) + continue; + + g->sendMods(depressed, latched, locked, group); + } +} + +void CInputMethodV2::setKeyboard(wlr_keyboard* keyboard) { + for (auto& gw : grabs) { + auto g = gw.lock(); + + if (!g) + continue; + + g->sendKeyboardData(keyboard); + } +} + +wl_client* CInputMethodV2::client() { + return wl_resource_get_client(resource->resource()); +} + +CInputMethodV2Protocol::CInputMethodV2Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CInputMethodV2Protocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(std::make_unique(client, ver, id)).get(); + RESOURCE->setOnDestroy([this](CZwpInputMethodManagerV2* p) { this->onManagerResourceDestroy(p->resource()); }); + + RESOURCE->setDestroy([this](CZwpInputMethodManagerV2* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); }); + RESOURCE->setGetInputMethod([this](CZwpInputMethodManagerV2* pMgr, wl_resource* seat, uint32_t id) { this->onGetIME(pMgr, seat, id); }); +} + +void CInputMethodV2Protocol::onManagerResourceDestroy(wl_resource* res) { + std::erase_if(m_vManagers, [&](const auto& other) { return other->resource() == res; }); +} + +void CInputMethodV2Protocol::destroyResource(CInputMethodPopupV2* popup) { + std::erase_if(m_vPopups, [&](const auto& other) { return other.get() == popup; }); +} + +void CInputMethodV2Protocol::destroyResource(CInputMethodKeyboardGrabV2* grab) { + std::erase_if(m_vGrabs, [&](const auto& other) { return other.get() == grab; }); +} + +void CInputMethodV2Protocol::destroyResource(CInputMethodV2* ime) { + std::erase_if(m_vIMEs, [&](const auto& other) { return other.get() == ime; }); +} + +void CInputMethodV2Protocol::onGetIME(CZwpInputMethodManagerV2* mgr, wl_resource* seat, uint32_t id) { + const auto CLIENT = wl_resource_get_client(mgr->resource()); + const auto RESOURCE = m_vIMEs.emplace_back(std::make_shared(std::make_shared(CLIENT, wl_resource_get_version(mgr->resource()), id))); + + if (!RESOURCE->good()) { + wl_resource_post_no_memory(mgr->resource()); + m_vIMEs.pop_back(); + return; + } + + RESOURCE->self = RESOURCE; + + LOGM(LOG, "New IME with resource id {}", id); + + events.newIME.emit(RESOURCE); +} \ No newline at end of file diff --git a/src/protocols/InputMethodV2.hpp b/src/protocols/InputMethodV2.hpp new file mode 100644 index 00000000..1f5d2598 --- /dev/null +++ b/src/protocols/InputMethodV2.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "input-method-unstable-v2.hpp" +#include "text-input-unstable-v3.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../desktop/WLSurface.hpp" + +class CInputMethodKeyboardGrabV2; +class CInputMethodPopupV2; + +class CInputMethodV2 { + public: + CInputMethodV2(SP resource_); + ~CInputMethodV2(); + + struct { + CSignal onCommit; + CSignal destroy; + CSignal newPopup; + } events; + + struct SState { + void reset(); + + struct { + std::string string; + bool committed = false; + } committedString; + + struct { + std::string string; + int32_t begin = 0, end = 0; + bool committed = false; + } preeditString; + + struct { + uint32_t before = 0, after = 0; + bool committed = false; + } deleteSurrounding; + }; + + SState pending, current; + + bool good(); + void activate(); + void deactivate(); + void surroundingText(const std::string& text, uint32_t cursor, uint32_t anchor); + void textChangeCause(zwpTextInputV3ChangeCause changeCause); + void textContentType(zwpTextInputV3ContentHint hint, zwpTextInputV3ContentPurpose purpose); + void done(); + void unavailable(); + + void sendInputRectangle(const CBox& box); + bool hasGrab(); + void sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state); + void sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group); + void setKeyboard(wlr_keyboard* keyboard); + + wl_client* client(); + wl_client* grabClient(); + + private: + SP resource; + std::vector> grabs; + std::vector> popups; + + WP self; + + bool active = false; + + CBox inputRectangle; + + friend class CInputMethodPopupV2; + friend class CInputMethodKeyboardGrabV2; + friend class CInputMethodV2Protocol; +}; + +class CInputMethodKeyboardGrabV2 { + public: + CInputMethodKeyboardGrabV2(SP resource_, SP owner_); + ~CInputMethodKeyboardGrabV2(); + + bool good(); + SP getOwner(); + wl_client* client(); + + void sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state); + void sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group); + void sendKeyboardData(wlr_keyboard* keyboard); + + private: + SP resource; + WP owner; + + wlr_keyboard* pLastKeyboard = nullptr; // READ-ONLY +}; + +class CInputMethodPopupV2 { + public: + CInputMethodPopupV2(SP resource_, SP owner_, wlr_surface* surface); + ~CInputMethodPopupV2(); + + bool good(); + void sendInputRectangle(const CBox& box); + wlr_surface* surface(); + + struct { + CSignal map; + CSignal unmap; + CSignal commit; + CSignal destroy; + } events; + + bool mapped = false; + + private: + SP resource; + WP owner; + wlr_surface* pSurface = nullptr; + + DYNLISTENER(commitSurface); + DYNLISTENER(destroySurface); +}; + +class CInputMethodV2Protocol : public IWaylandProtocol { + public: + CInputMethodV2Protocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + struct { + CSignal newIME; // SP + } events; + + private: + void onManagerResourceDestroy(wl_resource* res); + void destroyResource(CInputMethodPopupV2* popup); + void destroyResource(CInputMethodKeyboardGrabV2* grab); + void destroyResource(CInputMethodV2* ime); + + void onGetIME(CZwpInputMethodManagerV2* mgr, wl_resource* seat, uint32_t id); + + // + std::vector> m_vManagers; + std::vector> m_vIMEs; + std::vector> m_vGrabs; + std::vector> m_vPopups; + + friend class CInputMethodPopupV2; + friend class CInputMethodKeyboardGrabV2; + friend class CInputMethodV2; +}; + +namespace PROTO { + inline UP ime; +}; \ No newline at end of file