From ebce64c591d36845374aa2837927c8279ebf8fac Mon Sep 17 00:00:00 2001 From: Wang Zichong Date: Tue, 28 Apr 2026 16:44:17 +0800 Subject: [PATCH] feat: support move xembed window on treeland MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过 treeland 提供的 API 移动 xembed 窗口 Log: --- .gitignore | 1 + panels/dock/DockCompositor.qml | 12 +++++ panels/dock/dockhelper.h | 3 ++ panels/dock/dockpanel.cpp | 8 +++ panels/dock/dockpanel.h | 3 ++ panels/dock/pluginmanagerextension.cpp | 73 ++++++++++++++++++++++++++ panels/dock/pluginmanagerextension_p.h | 24 +++++++++ panels/dock/waylanddockhelper.cpp | 37 +++++++++++++ panels/dock/waylanddockhelper.h | 10 ++++ 9 files changed, 171 insertions(+) diff --git a/.gitignore b/.gitignore index 7d71e5793..439796714 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ panels/dock/tray/plugins/power/dbusinterface/generation_dbus_interface/ obj-x86_64-linux-gnu/ *.autosave */utils/dbus/xml2cpp/*_interface.* +.qmlls.ini diff --git a/panels/dock/DockCompositor.qml b/panels/dock/DockCompositor.qml index 84c4aec54..c7609e144 100644 --- a/panels/dock/DockCompositor.qml +++ b/panels/dock/DockCompositor.qml @@ -113,6 +113,18 @@ Item { console.log("quick panel closed") dockCompositor.popupClosed() } + + onMoveXEmbedWindowRequested: (wid, pluginId, itemKey, dx, dy) => { + console.log("move xembed window requested:", wid, pluginId, itemKey, dx, dy) + // Delegate to Panel which has access to WaylandDockHelper + if (Panel && typeof Panel.moveXEmbedWindow === 'function') { + var success = Panel.moveXEmbedWindow(wid, dx, dy) + pluginManager.notifyXEmbedWindowMoveResult(wid, success) + } else { + console.warn("Panel.moveXEmbedWindow not available") + pluginManager.notifyXEmbedWindowMoveResult(wid, false) + } + } } PluginScaleManager{ diff --git a/panels/dock/dockhelper.h b/panels/dock/dockhelper.h index 778021ad3..0dfd18a60 100644 --- a/panels/dock/dockhelper.h +++ b/panels/dock/dockhelper.h @@ -21,6 +21,9 @@ class DockHelper : public QObject void enterScreen(QScreen *screen); void leaveScreen(); + + // Move XEmbed window relative to dock surface (no-op on X11) + virtual bool moveXEmbedWindow(uint32_t wid, double dx, double dy) { return false; } Q_SIGNALS: void isWindowOverlapChanged(bool overlap); diff --git a/panels/dock/dockpanel.cpp b/panels/dock/dockpanel.cpp index 0aaa99a2d..39e28f6f3 100644 --- a/panels/dock/dockpanel.cpp +++ b/panels/dock/dockpanel.cpp @@ -485,6 +485,14 @@ void DockPanel::setIsResizing(bool resizing) m_isResizing = resizing; emit isResizingChanged(m_isResizing); } + +bool DockPanel::moveXEmbedWindow(uint32_t wid, double dx, double dy) +{ + if (m_helper) { + return m_helper->moveXEmbedWindow(wid, dx, dy); + } + return false; +} } #include "dockpanel.moc" diff --git a/panels/dock/dockpanel.h b/panels/dock/dockpanel.h index 3bd31485c..6101fd2a5 100644 --- a/panels/dock/dockpanel.h +++ b/panels/dock/dockpanel.h @@ -83,6 +83,9 @@ class DockPanel : public DS_NAMESPACE::DPanel, public QDBusContext Q_INVOKABLE void openDockSettings() const; Q_INVOKABLE void notifyDockPositionChanged(int offsetX, int offsetY); + + // Move XEmbed window relative to dock surface (Wayland only) + Q_INVOKABLE bool moveXEmbedWindow(uint32_t wid, double dx, double dy); bool showInPrimary() const; void setShowInPrimary(bool newShowInPrimary); diff --git a/panels/dock/pluginmanagerextension.cpp b/panels/dock/pluginmanagerextension.cpp index d5e84f10d..9fb999e1d 100644 --- a/panels/dock/pluginmanagerextension.cpp +++ b/panels/dock/pluginmanagerextension.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -254,9 +255,15 @@ bool PluginSurface::isItemActive() const void PluginSurface::updatePluginGeometry(const QRect &geometry) { + m_itemPosition = geometry.topLeft(); send_geometry(geometry.x(), geometry.y(), geometry.width(), geometry.height()); } +QPoint PluginSurface::itemPosition() const +{ + return m_itemPosition; +} + void PluginSurface::plugin_mouse_event(QtWaylandServer::plugin::Resource *resource, int32_t type) { Q_UNUSED(resource) @@ -673,6 +680,62 @@ void PluginManager::plugin_manager_v1_create_popup_at(Resource *resource, const Q_EMIT pluginPopupCreated(plugin); } +void PluginManager::plugin_manager_v1_move_xembed_window(Resource *resource, uint32_t xembed_winid, const QString &plugin_id, const QString &item_key, uint32_t callback) +{ + qInfo() << "server pluginManager receive move_xembed_window:" << xembed_winid << plugin_id << item_key; + + // Look up the plugin surface to get its position relative to the dock window + PluginSurface *pluginSurface = findPluginSurface(plugin_id, item_key); + if (!pluginSurface) { + qWarning() << "move_xembed_window: plugin surface not found for" << plugin_id << item_key; + // Send error response immediately if surface not found + wl_resource *callbackResource = wl_resource_create(resource->client(), &wl_callback_interface, + 1, callback); + if (callbackResource) { + wl_callback_send_done(callbackResource, 1); // 1 = error + wl_resource_destroy(callbackResource); + } else { + qWarning() << "move_xembed_window: failed to create callback resource"; + } + return; + } + + double dx = pluginSurface->itemPosition().x(); + double dy = pluginSurface->itemPosition().y(); + + // Store pending callback for later response (supports concurrent requests) + m_pendingXEmbedCallbacks[xembed_winid] = {callback, resource->handle}; + + // Emit signal with position info for QML to handle + Q_EMIT moveXEmbedWindowRequested(xembed_winid, plugin_id, item_key, dx, dy); +} + +void PluginManager::notifyXEmbedWindowMoveResult(uint32_t wid, bool success) +{ + if (!m_pendingXEmbedCallbacks.contains(wid)) { + qWarning() << "notifyXEmbedWindowMoveResult: no pending callback for wid" << wid; + return; + } + + PendingXEmbedCallback pending = m_pendingXEmbedCallbacks.take(wid); + + // Get client from stored resource (safe during request handling) + struct ::wl_client *client = wl_resource_get_client(pending.resource); + if (!client) { + qWarning() << "notifyXEmbedWindowMoveResult: client no longer exists for wid" << wid; + return; + } + + wl_resource *callbackResource = wl_resource_create(client, &wl_callback_interface, + 1, pending.callback); + if (callbackResource) { + wl_callback_send_done(callbackResource, success ? 0 : 1); + wl_resource_destroy(callbackResource); + } else { + qWarning() << "notifyXEmbedWindowMoveResult: failed to create callback resource for wid" << wid; + } +} + QJsonObject PluginManager::getRootObj(const QString &jsonStr) { QJsonParseError jsonParseError; const QJsonDocument &resultDoc = QJsonDocument::fromJson(jsonStr.toLocal8Bit(), &jsonParseError); @@ -757,6 +820,16 @@ void PluginManager::onActiveColorChanged() }); } +PluginSurface* PluginManager::findPluginSurface(const QString &pluginId, const QString &itemKey) const +{ + for (PluginSurface *plugin : m_pluginSurfaces) { + if (plugin->pluginId() == pluginId && plugin->itemKey() == itemKey) { + return plugin; + } + } + return nullptr; +} + void PluginManager::onThemeChanged() { foreachPluginSurface([this](Resource *source) { diff --git a/panels/dock/pluginmanagerextension_p.h b/panels/dock/pluginmanagerextension_p.h index 4df253400..1b7b7ea6e 100644 --- a/panels/dock/pluginmanagerextension_p.h +++ b/panels/dock/pluginmanagerextension_p.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -65,6 +66,11 @@ class PluginManager : public QWaylandCompositorExtensionTemplate, Q_INVOKABLE void updateDockOverflowState(int state); Q_INVOKABLE void setPopupMinHeight(int height); + + // Called from QML when moveXEmbedWindowRequested is handled + // wid: the XEmbed window ID from the original request + // result: true = success, false = error + Q_INVOKABLE void notifyXEmbedWindowMoveResult(uint32_t wid, bool result); uint32_t dockPosition() const; void setDockPosition(uint32_t dockPosition); @@ -92,6 +98,9 @@ class PluginManager : public QWaylandCompositorExtensionTemplate, void messageRequest(PluginSurface *, const QString &msg); void dockSizeChanged(); void requestShutdown(const QString &type); + // Signal emitted when XEmbed window move is requested + // Parameters: wid (window ID), pluginId, itemKey, dx (relative x offset), dy (relative y offset) + void moveXEmbedWindowRequested(uint32_t wid, const QString &pluginId, const QString &itemKey, double dx, double dy); private Q_SLOTS: void onFontChanged(); @@ -103,6 +112,7 @@ private Q_SLOTS: virtual void plugin_manager_v1_request_message(Resource *resource, const QString &plugin_id, const QString &item_key, const QString &msg) override; virtual void plugin_manager_v1_create_popup_at(Resource *resource, const QString &plugin_id, const QString &item_key, int32_t type, int32_t x, int32_t y, struct ::wl_resource *surface, uint32_t id) override; virtual void plugin_manager_v1_create_plugin(Resource *resource, const QString &plugin_id, const QString &item_key, const QString &display_name, int32_t plugin_flags, int32_t type, int32_t size_policy, struct ::wl_resource *surface, uint32_t id) override; + virtual void plugin_manager_v1_move_xembed_window(Resource *resource, uint32_t xembed_winid, const QString &plugin_id, const QString &item_key, uint32_t callback) override; private: static QJsonObject getRootObj(const QString &jsonStr); @@ -113,14 +123,24 @@ private Q_SLOTS: QString popupMinHeightMsg() const; using PluginSurfaceCallback = std::function; void foreachPluginSurface(PluginSurfaceCallback callback); + PluginSurface* findPluginSurface(const QString &pluginId, const QString &itemKey) const; private: + struct PendingXEmbedCallback { + uint32_t callback; + struct ::wl_resource *resource; // Store resource instead of client for lifecycle safety + }; + QList m_pluginSurfaces; uint32_t m_dockPosition = 0; uint32_t m_dockColorTheme = 0; QSize m_dockSize; int m_popupMinHeight = 0; + + // Map of pending XEmbed callbacks: wid -> callback info + // Supports multiple concurrent requests from different clients + QMap m_pendingXEmbedCallbacks; }; class PluginSurface : public QWaylandShellSurfaceTemplate, public QtWaylandServer::plugin @@ -173,6 +193,9 @@ class PluginSurface : public QWaylandShellSurfaceTemplate, public Q_INVOKABLE void updatePluginGeometry(const QRect &geometry); Q_INVOKABLE void setGlobalPos(const QPoint &pos); + // Position relative to the dock window, set from QML via updatePluginGeometry + QPoint itemPosition() const; + int margins() const; void setMargins(int newMargins); @@ -211,6 +234,7 @@ class PluginSurface : public QWaylandShellSurfaceTemplate, public int m_margins = 0; int m_height; int m_width; + QPoint m_itemPosition; // Position relative to dock window, for XEmbed window positioning }; class PluginPopup : public QWaylandShellSurfaceTemplate, public QtWaylandServer::plugin_popup diff --git a/panels/dock/waylanddockhelper.cpp b/panels/dock/waylanddockhelper.cpp index 76bc0f768..6bdcdd337 100644 --- a/panels/dock/waylanddockhelper.cpp +++ b/panels/dock/waylanddockhelper.cpp @@ -28,6 +28,14 @@ WaylandDockHelper::WaylandDockHelper(DockPanel *panel) if (auto applet = bridge.applet()) { connect(applet, SIGNAL(windowFullscreenChanged(bool)), this, SLOT(setCurrentActiveWindowFullscreened(bool))); } + + // Store dock window's wl_surface for XEmbed window positioning + if (m_panel->window()) { + auto waylandWindow = dynamic_cast(m_panel->window()->handle()); + if (waylandWindow) { + m_dockWlSurface = waylandWindow->waylandSurface()->object(); + } + } connect(m_panel, &DockPanel::rootObjectChanged, this, [this]() { m_wallpaperColorManager->watchScreen(dockScreenName()); @@ -144,6 +152,27 @@ void WaylandDockHelper::setDockColorTheme(const ColorTheme &theme) m_panel->setColorTheme(theme); } +bool WaylandDockHelper::moveXEmbedWindow(uint32_t wid, double dx, double dy) +{ + // Update dock wl_surface if needed + if (!m_dockWlSurface && m_panel->window()) { + auto waylandWindow = dynamic_cast(m_panel->window()->handle()); + if (waylandWindow && waylandWindow->waylandSurface()) { + m_dockWlSurface = waylandWindow->waylandSurface()->object(); + } + } + + if (!m_ddeShellManager || !m_ddeShellManager->isActive() || !m_dockWlSurface) { + qWarning() << "WaylandDockHelper::moveXEmbedWindow: not ready, manager active:" + << (m_ddeShellManager && m_ddeShellManager->isActive()) + << "surface:" << (m_dockWlSurface != nullptr); + return false; + } + + m_ddeShellManager->setXWindowPositionRelative(wid, m_dockWlSurface, dx, dy); + return true; +} + WallpaperColorManager::WallpaperColorManager(WaylandDockHelper *helper) : QWaylandClientExtensionTemplate(treeland_wallpaper_color_manager_v1_interface.version) , m_helper(helper) @@ -169,6 +198,14 @@ TreeLandDDEShellManager::TreeLandDDEShellManager() { } +struct ::wl_callback *TreeLandDDEShellManager::setXWindowPositionRelative(uint32_t wid, struct ::wl_surface *anchor, double dx, double dy) +{ + if (!isActive()) { + return nullptr; + } + return QtWayland::treeland_dde_shell_manager_v1::set_xwindow_position_relative(wid, anchor, wl_fixed_from_double(dx), wl_fixed_from_double(dy)); +} + TreeLandWindowOverlapChecker::TreeLandWindowOverlapChecker(WaylandDockHelper *helper, struct ::treeland_window_overlap_checker *checker) : QWaylandClientExtensionTemplate(treeland_dde_shell_manager_v1_interface.version) , m_helper(helper) diff --git a/panels/dock/waylanddockhelper.h b/panels/dock/waylanddockhelper.h index 163e85fac..df7b9b553 100644 --- a/panels/dock/waylanddockhelper.h +++ b/panels/dock/waylanddockhelper.h @@ -30,6 +30,11 @@ class WaylandDockHelper : public DockHelper void setDockColorTheme(const ColorTheme &theme); QString dockScreenName(); + + // Move XEmbed window relative to dock surface + // dx, dy: offset relative to dock surface top-left + // Returns true if the request was sent successfully + bool moveXEmbedWindow(uint32_t wid, double dx, double dy) override; protected: bool currentActiveWindowFullscreened() override; @@ -51,6 +56,7 @@ protected Q_SLOTS: QScopedPointer m_wallpaperColorManager; QScopedPointer m_overlapChecker; QScopedPointer m_ddeShellManager; + struct ::wl_surface *m_dockWlSurface = nullptr; // Dock's wl_surface for XEmbed positioning }; class WallpaperColorManager : public QWaylandClientExtensionTemplate, public QtWayland::treeland_wallpaper_color_manager_v1 @@ -75,6 +81,10 @@ class TreeLandDDEShellManager : public QWaylandClientExtensionTemplate, public QtWayland::treeland_window_overlap_checker