From b63e0137fdbcf892b1aa6aef59d08ade7295bb1b Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Wed, 4 Mar 2026 17:29:57 +0800 Subject: [PATCH 1/2] fix(dock): support wayland text input on plugin popup windows This commit enables Chinese input methods (like fcitx) to correctly interact with Wayland dock plugin windows. The fix introduces an explicit text input proxy routing: 1. Since QWaylandSeat::setKeyboardFocus() does not update text input protocols automatically, we manually assign the focused surface to all text input global extensions (QWaylandTextInput/V3 & QWaylandQtTextInputMethod) via private APIs. 2. In QWaylandQtTextInputMethod, the d->resource was missing during continuous window focus change, breaking client messages like update_cursor_rectangle. We intercept focus management using a custom WlQtTextInputMethodHelper. 3. Connecting surfaceEnabled signal to automatically invoke forceActiveFocus() on the QWaylandQuickItem rendering the surface frame. This is crucial because Qt requires active focus to correctly route QInputMethodEvent downwards to the client. Log: support wayland text input on plugin popup windows Pms: BUG-323547 --- panels/dock/pluginmanagerextension.cpp | 168 ++++++++++++++++++++++++- panels/dock/pluginmanagerextension_p.h | 3 + 2 files changed, 170 insertions(+), 1 deletion(-) diff --git a/panels/dock/pluginmanagerextension.cpp b/panels/dock/pluginmanagerextension.cpp index 3f77ac101..80c1fe9f1 100644 --- a/panels/dock/pluginmanagerextension.cpp +++ b/panels/dock/pluginmanagerextension.cpp @@ -15,6 +15,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -29,8 +39,55 @@ #include #endif +#include +#include + DGUI_USE_NAMESPACE +struct WlQtTextInputMethodHelper : public QtWaylandServer::qt_text_input_method_v1 { + static void setFocusCustom(QWaylandQtTextInputMethodPrivate *d, QWaylandSurface *surface) { + auto *base = static_cast(d); + if (d->focusedSurface == surface) return; + + QtWaylandServer::qt_text_input_method_v1::Resource *newResource = nullptr; + if (surface && surface->client()) { + auto resources = base->resourceMap().values(surface->client()->client()); + if (!resources.isEmpty()) { + newResource = resources.first(); + } + } + + if (d->focusedSurface) { + if (d->resource) { + base->send_leave(d->resource->handle, d->focusedSurface->resource()); + } + d->focusDestroyListener.reset(); + } + + d->focusedSurface = surface; + d->resource = newResource; + + if (d->resource != nullptr && d->focusedSurface != nullptr) { + d->surroundingText.clear(); + d->cursorPosition = 0; + d->anchorPosition = 0; + d->absolutePosition = 0; + d->cursorRectangle = QRect(); + d->preferredLanguage.clear(); + d->hints = Qt::InputMethodHints(); + auto *helper = static_cast(base); + helper->send_enter(d->resource->handle, d->focusedSurface->resource()); + + helper->send_input_direction_changed(d->resource->handle, int(qApp->inputMethod()->inputDirection())); + helper->send_locale_changed(d->resource->handle, qApp->inputMethod()->locale().bcp47Name()); + + d->focusDestroyListener.listenForDestruction(surface->resource()); + if (d->inputPanelVisible && d->enabledSurfaces.values().contains(surface)) + qApp->inputMethod()->show(); + } + } +}; + PluginScaleManager::PluginScaleManager(QWaylandCompositor *compositor) : QWaylandCompositorExtensionTemplate(compositor) , m_compositor(compositor) @@ -445,8 +502,20 @@ void PluginManager::initialize() init(compositor->display(), 1); + // We ONLY register QtTextInputMethodManager. + // If we register v2 or v3, they will intercept `inputMethodEvent` inside `QWaylandInputMethodControl::inputMethodEvent` + // because Qt's server-side logic checks v2 -> v3 -> QtTextInputMethod. + // But the Qt 6 client-side creates QWaylandInputMethodContext which only listens to QtTextInputMethod + // if it is available. This causes mismatched preferences: server sends via v2/v3, client listens to QtTextInputMethod. + // So ONLY register QtTextInputMethodManager. + auto qtTextInputMethodManager = new QWaylandQtTextInputMethodManager(compositor); + qtTextInputMethodManager->initialize(); + // 设置鼠标焦点监听器 setupMouseFocusListener(); + + // 设置 text input 焦点代理 + setupTextInputProxy(compositor); } void PluginManager::updateDockOverflowState(int state) @@ -720,8 +789,105 @@ void PluginManager::setupMouseFocusListener() return; if (auto surface = newFocus->surface()) { - qDebug()<<"setKeyboardFocus"; seat->setKeyboardFocus(surface); } }); } + +void PluginManager::setupTextInputProxy(QWaylandCompositor *compositor) +{ + // === IME 事件转发的完整链路说明 === + // + // 【步骤1】设置 text input 协议对象的 focus(keyboardFocusChanged 监听器) + // 当插件 surface 的键盘焦点发生变化时,需要同步更新所有 text input 协议对象的焦点。 + // QWaylandSeat::setKeyboardFocus() 只处理 wl_keyboard 协议的 enter/leave, + // 不会自动通知 text input 协议对象(QWaylandTextInput/V3/QtTextInputMethod)。 + // 因此需要在 keyboardFocusChanged 信号中手动调用各协议对象的 setFocus()。 + // 这样,当插件进程的输入框触发 text_input::enable 时,compositor 端的 text input + // 对象已有正确的 focus surface,surfaceEnabled 信号会正确触发,进而: + // → QWaylandInputMethodControl::setEnabled(true) + // → QWaylandQuickItem::updateInputMethod() 设上 ItemAcceptsInputMethod flag + // + // 【步骤2】让 QWaylandQuickItem 获取 Qt active focus(surfaceEnabled 监听器) + // Qt 的 QInputMethodEvent 只会发给当前有 active focus 的 QQuickItem。 + // 即便 ShellSurfaceItem/QWaylandQuickItem 已设上 ItemAcceptsInputMethod flag, + // 如果它没有 Qt active focus,外层 compositor 发来的 commit_string 生成的 + // QInputMethodEvent 就会丢失,中文字无法提交到插件进程的输入框。 + // 因此,当 surfaceEnabled 触发时(插件进程输入框 enable IME),需要通过 + // surface->views() 找到对应的 QWaylandQuickItem,调用 forceActiveFocus(), + // 确保 QInputMethodEvent 能正确路由到 + // QWaylandQuickItem::inputMethodEvent() + // → QWaylandInputMethodControl::inputMethodEvent() + // → QWaylandTextInput::sendInputMethodEvent() + // → send_commit_string() 到插件进程 + // + // 注意:QWaylandTextInput 等 per-seat 对象是在客户端绑定全局时懒创建的, + // 初始化时 seat->extensions() 为空。因此步骤2的连接放在 + // keyboardFocusChanged lambda 内部(那时扩展已经存在)。 + + QWaylandSeat *seat = compositor->defaultSeat(); + if (!seat) + return; + + // 【步骤1 & 2 实现】在 keyboardFocusChanged 中同时完成两件事: + // a) 同步 text input 协议对象的焦点(步骤1) + // b) 用 Qt::UniqueConnection 连接 surfaceEnabled,让 QWaylandQuickItem 获取 active focus(步骤2) + QObject::connect(seat, &QWaylandSeat::keyboardFocusChanged, this, [this, seat] + (QWaylandSurface *newFocus, QWaylandSurface *oldFocus) { + Q_UNUSED(oldFocus); + + // 由于 Deepin Qt6 构建中 QWaylandTextInput/V3 的公开类符号没有导出, + // 无法使用 findIn() 和公开的 setFocus()。 + // 改用 Private API:遍历 seat 的 extension 列表, + // 通过 QObject 元对象系统找到 text input 对象, + // 再通过 d_func() 调用 Private 类的 setFocus()。 + const auto extensions = seat->extensions(); + for (auto *ext : extensions) { + const QString className = ext->metaObject()->className(); + if (className == QStringLiteral("QWaylandTextInput")) { + // 步骤1:设置 text input 焦点 + auto *d = static_cast(QObjectPrivate::get(ext)); + d->setFocus(newFocus); + // 步骤2:连接 surfaceEnabled,当插件输入框 enable IME 时, + // 让对应的 QWaylandQuickItem 获取 Qt active focus。 + // Qt::UniqueConnection 防止重复连接。 + QObject::connect(ext, SIGNAL(surfaceEnabled(QWaylandSurface*)), + this, SLOT(onTextInputSurfaceEnabled(QWaylandSurface*)), + Qt::UniqueConnection); + } else if (className == QStringLiteral("QWaylandTextInputV3")) { + auto *d = static_cast(QObjectPrivate::get(ext)); + d->setFocus(newFocus); + // 同样连接 v3 的 surfaceEnabled(fcitx5/ibus 等可能使用 v3) + QObject::connect(ext, SIGNAL(surfaceEnabled(QWaylandSurface*)), + this, SLOT(onTextInputSurfaceEnabled(QWaylandSurface*)), + Qt::UniqueConnection); + } else if (className == QStringLiteral("QWaylandQtTextInputMethod")) { + auto *qtMethodPriv = static_cast(QObjectPrivate::get(ext)); + qtMethodPriv->inputPanelVisible = true; // ensure IME panel can be shown + WlQtTextInputMethodHelper::setFocusCustom(qtMethodPriv, newFocus); + // Qt6 WPA 默认优先使用此私有协议(qt_text_input_method_manager_v1), + // 插件进程通常通过此协议发 enable,必须连接其 surfaceEnabled。 + QObject::connect(ext, SIGNAL(surfaceEnabled(QWaylandSurface*)), + this, SLOT(onTextInputSurfaceEnabled(QWaylandSurface*)), + Qt::UniqueConnection); + } + } + }); +} + +void PluginManager::onTextInputSurfaceEnabled(QWaylandSurface *surface) +{ + if (!surface) + return; + + // 通过 surface 的 views 找到对应的 QWaylandQuickItem, + // 调用 forceActiveFocus() 使其成为 Qt 的 active focus item, + // 这样外层 compositor 的 QInputMethodEvent 才能被路由到它。 + const auto views = surface->views(); + for (auto *view : views) { + if (auto *quickItem = qobject_cast(view->renderObject())) { + quickItem->forceActiveFocus(Qt::OtherFocusReason); + break; + } + } +} diff --git a/panels/dock/pluginmanagerextension_p.h b/panels/dock/pluginmanagerextension_p.h index ff5dd316d..bd28370d9 100644 --- a/panels/dock/pluginmanagerextension_p.h +++ b/panels/dock/pluginmanagerextension_p.h @@ -80,6 +80,8 @@ class PluginManager : public QWaylandCompositorExtensionTemplate, //处理鼠标焦点给到相应插件 void setupMouseFocusListener(); + //处理 IME 输入代理:将插件进程的 text input 请求透传给外层 compositor + void setupTextInputProxy(QWaylandCompositor *compositor); Q_SIGNALS: void pluginPopupCreated(PluginPopup*); @@ -94,6 +96,7 @@ private Q_SLOTS: void onFontChanged(); void onActiveColorChanged(); void onThemeChanged(); + void onTextInputSurfaceEnabled(QWaylandSurface *surface); protected: virtual void plugin_manager_v1_request_message(Resource *resource, const QString &plugin_id, const QString &item_key, const QString &msg) override; From ff8f1fc3ec8fe44c2ab9360ec5b151320de58904 Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Thu, 5 Mar 2026 15:18:19 +0800 Subject: [PATCH 2/2] fix(dock): fix pasting text in Wayland plugin windows To fix pasting text in Wayland plugin windows, this commit ensures that nested Wayland clients can correctly receive host clipboard contents. Wayland compositor (dde-shell) does not automatically synchronize the host system's clipboard to its nested clients. This caused plugin popup windows to be unable to paste text that was copied from outside applications. This fix enables retained selection in QWaylandCompositor and explicitly listens to QClipboard::changed from the host environment to forward the QMimeData to the compositor via overrideSelection(). Log: fix pasting text in Wayland plugin windows Pms: BUG-329435 --- panels/dock/pluginmanagerextension.cpp | 37 +++++++++++++++++++------- panels/dock/pluginmanagerextension_p.h | 2 +- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/panels/dock/pluginmanagerextension.cpp b/panels/dock/pluginmanagerextension.cpp index 80c1fe9f1..945be4c5e 100644 --- a/panels/dock/pluginmanagerextension.cpp +++ b/panels/dock/pluginmanagerextension.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -26,6 +26,11 @@ #include #include +#include +#include +#include +#include + #include #include @@ -39,9 +44,6 @@ #include #endif -#include -#include - DGUI_USE_NAMESPACE struct WlQtTextInputMethodHelper : public QtWaylandServer::qt_text_input_method_v1 { @@ -75,11 +77,9 @@ struct WlQtTextInputMethodHelper : public QtWaylandServer::qt_text_input_method_ d->cursorRectangle = QRect(); d->preferredLanguage.clear(); d->hints = Qt::InputMethodHints(); - auto *helper = static_cast(base); - helper->send_enter(d->resource->handle, d->focusedSurface->resource()); - - helper->send_input_direction_changed(d->resource->handle, int(qApp->inputMethod()->inputDirection())); - helper->send_locale_changed(d->resource->handle, qApp->inputMethod()->locale().bcp47Name()); + base->send_enter(d->resource->handle, d->focusedSurface->resource()); + base->send_input_direction_changed(d->resource->handle, int(qApp->inputMethod()->inputDirection())); + base->send_locale_changed(d->resource->handle, qApp->inputMethod()->locale().bcp47Name()); d->focusDestroyListener.listenForDestruction(surface->resource()); if (d->inputPanelVisible && d->enabledSurfaces.values().contains(surface)) @@ -516,6 +516,21 @@ void PluginManager::initialize() // 设置 text input 焦点代理 setupTextInputProxy(compositor); + + // 启用剪贴板保留并在宿主系统剪贴板更新时同步给插件 Wayland 客户端,实现粘贴功能 + compositor->setRetainedSelectionEnabled(true); + if (auto *clipboard = QGuiApplication::clipboard()) { + QObject::connect(clipboard, &QClipboard::changed, this, [compositor](QClipboard::Mode mode) { + if (mode == QClipboard::Clipboard) { + if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData(mode)) { + compositor->overrideSelection(mimeData); + } + } + }); + if (const QMimeData *mimeData = clipboard->mimeData(QClipboard::Clipboard)) { + compositor->overrideSelection(mimeData); + } + } } void PluginManager::updateDockOverflowState(int state) @@ -836,6 +851,10 @@ void PluginManager::setupTextInputProxy(QWaylandCompositor *compositor) (QWaylandSurface *newFocus, QWaylandSurface *oldFocus) { Q_UNUSED(oldFocus); + if (newFocus) { + newFocus->updateSelection(); + } + // 由于 Deepin Qt6 构建中 QWaylandTextInput/V3 的公开类符号没有导出, // 无法使用 findIn() 和公开的 setFocus()。 // 改用 Private API:遍历 seat 的 extension 列表, diff --git a/panels/dock/pluginmanagerextension_p.h b/panels/dock/pluginmanagerextension_p.h index bd28370d9..709e2b375 100644 --- a/panels/dock/pluginmanagerextension_p.h +++ b/panels/dock/pluginmanagerextension_p.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later