diff --git a/panels/dock/CMakeLists.txt b/panels/dock/CMakeLists.txt index 7c084ec29..06f117083 100644 --- a/panels/dock/CMakeLists.txt +++ b/panels/dock/CMakeLists.txt @@ -26,6 +26,8 @@ file( dockhelper.cpp waylanddockhelper.h waylanddockhelper.cpp + layershellextension.h + layershellextension.cpp loadtrayplugins.h loadtrayplugins.cpp ) @@ -64,6 +66,7 @@ qt_generate_wayland_protocol_client_sources(dockpanel FILES ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-wallpaper-color-v1.xml ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-dde-shell-v1.xml + ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-layer-shell-extension-unstable-v1.xml ) target_link_libraries(dockpanel PRIVATE diff --git a/panels/dock/dockpanel.cpp b/panels/dock/dockpanel.cpp index b9488979f..561dd708f 100644 --- a/panels/dock/dockpanel.cpp +++ b/panels/dock/dockpanel.cpp @@ -6,6 +6,7 @@ #include "constants.h" #include "dockadaptor.h" #include "docksettings.h" +#include "layershellextension.h" #include "panel.h" #include "pluginfactory.h" #include "waylanddockhelper.h" @@ -26,6 +27,10 @@ #include +#include +#include +#include + #ifdef HAVE_DDE_API_EVENTLOGGER #include #endif @@ -167,6 +172,7 @@ bool DockPanel::init() m_helper = new WaylandDockHelper(this); connect(static_cast(m_helper), &WaylandDockHelper::xembedWindowMoveResult, this, &DockPanel::xembedWindowMoveResult); + m_dockShellExtensionManager.reset(new TreeLandLayerShellExtensionManager()); // Fallback to DGuiApplicationHelper for theme color when wayland wallpaper color is not available. // TODO: remove this when initWallpaperColorManager is re-enabled QObject::connect(Dtk::Gui::DGuiApplicationHelper::instance(), &Dtk::Gui::DGuiApplicationHelper::themeTypeChanged, @@ -520,6 +526,94 @@ bool DockPanel::moveXEmbedWindow(uint32_t wid, double dx, double dy, QQuickWindo } return false; } + +bool DockPanel::beginResize() +{ + uint edges = 0; + int minW = 0, maxW = 0, minH = 0, maxH = 0; + switch (position()) { + case Position::Left: + edges = TreeLandLayerShellExtensionObject::resize_edge_right; + minW = MIN_DOCK_SIZE; + maxW = MAX_DOCK_SIZE; + break; + case Position::Right: + edges = TreeLandLayerShellExtensionObject::resize_edge_left; + minW = MIN_DOCK_SIZE; + maxW = MAX_DOCK_SIZE; + break; + case Position::Top: + edges = TreeLandLayerShellExtensionObject::resize_edge_bottom; + minH = MIN_DOCK_SIZE; + maxH = MAX_DOCK_SIZE; + break; + case Position::Bottom: + edges = TreeLandLayerShellExtensionObject::resize_edge_top; + minH = MIN_DOCK_SIZE; + maxH = MAX_DOCK_SIZE; + break; + default: + Q_UNREACHABLE(); + break; + } + + if (!m_dockShellExtensionManager) { + qCWarning(dockLog) << "beginResize: layer shell extension manager is null"; + return false; + } + + if (!m_dockShellExtensionManager->isActive()) { + qCWarning(dockLog) << "beginResize: layer shell extension manager inactive"; + return false; + } + + auto *w = window(); + if (!w) { + qCWarning(dockLog) << "beginResize: window is null"; + return false; + } + + auto *waylandWindow = dynamic_cast(w->handle()); + if (!waylandWindow) { + qCWarning(dockLog) << "beginResize: failed to get QWaylandWindow"; + return false; + } + + auto *surface = waylandWindow->wlSurface(); + + if (!surface) { + qCWarning(dockLog) << "beginResize: wl_surface is null"; + return false; + } + + auto *inputDevice = waylandWindow->display()->lastInputDevice(); + + if (!inputDevice) { + qCWarning(dockLog) << "beginResize: no last input device"; + return false; + } + + auto *dockSurface = m_dockShellExtensionManager->getLayerShellExtensionObject(surface); + + if (!dockSurface) { + qCWarning(dockLog) << "beginResize: failed to get layer shell extension object"; + return false; + } + if (m_dockShellExtensionObject) + return false; + m_dockShellExtensionObject = new TreeLandLayerShellExtensionObject(dockSurface, surface); + m_dockShellExtensionObject->setParent(this); + connect(m_dockShellExtensionObject, &TreeLandLayerShellExtensionObject::resizingChanged, this, [this](bool resizing) { + setIsResizing(resizing); + if (!resizing) { + m_dockShellExtensionObject->deleteLater(); + } + }); + + m_dockShellExtensionObject->beginResize(inputDevice->wl_seat(), inputDevice->serial(), edges, minW, minH, maxW, maxH); + inputDevice->handleEndDrag(); + return true; +} } #include "dockpanel.moc" diff --git a/panels/dock/dockpanel.h b/panels/dock/dockpanel.h index 1b4be8429..cfa5362fb 100644 --- a/panels/dock/dockpanel.h +++ b/panels/dock/dockpanel.h @@ -14,6 +14,8 @@ namespace dock { class DockHelper; class LoadTrayPlugins; +class TreeLandLayerShellExtensionManager; +class TreeLandLayerShellExtensionObject; class DockPanel : public DS_NAMESPACE::DPanel, public QDBusContext { @@ -89,6 +91,8 @@ class DockPanel : public DS_NAMESPACE::DPanel, public QDBusContext // anchorWindow: the window containing the plugin item (dock panel or popup window) Q_INVOKABLE bool moveXEmbedWindow(uint32_t wid, double dx, double dy, QQuickWindow *anchorWindow = nullptr); + Q_INVOKABLE bool beginResize(); + bool showInPrimary() const; void setShowInPrimary(bool newShowInPrimary); @@ -156,6 +160,8 @@ private Q_SLOTS: bool m_contextDragging; bool m_isResizing; QRect m_frontendWindowRect; + QScopedPointer m_dockShellExtensionManager; + QPointer m_dockShellExtensionObject; }; } diff --git a/panels/dock/layershellextension.cpp b/panels/dock/layershellextension.cpp new file mode 100644 index 000000000..4e475e0b3 --- /dev/null +++ b/panels/dock/layershellextension.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "layershellextension.h" + +namespace dock +{ + +TreeLandLayerShellExtensionManager::TreeLandLayerShellExtensionManager() + : QWaylandClientExtensionTemplate(treeland_layer_shell_extension_manager_v1_interface.version) +{ +} + +struct ::treeland_layer_shell_extension_object_v1 *TreeLandLayerShellExtensionManager::getLayerShellExtensionObject(struct ::wl_surface *surface) +{ + return get_layer_shell_extension_object(surface); +} + +TreeLandLayerShellExtensionObject::TreeLandLayerShellExtensionObject(struct ::treeland_layer_shell_extension_object_v1 *object, + struct ::wl_surface *nativeSurface) + : QWaylandClientExtensionTemplate(treeland_layer_shell_extension_object_v1_interface.version) + , m_nativeSurface(nativeSurface) +{ + init(object); +} + +TreeLandLayerShellExtensionObject::~TreeLandLayerShellExtensionObject() +{ + if (object()) { + destroy(); + } +} + +struct ::wl_surface *TreeLandLayerShellExtensionObject::nativeSurface() const +{ + return m_nativeSurface; +} + +void TreeLandLayerShellExtensionObject::beginResize(struct ::wl_seat *seat, + uint32_t serial, + uint32_t edges, + int32_t minWidth, + int32_t minHeight, + int32_t maxWidth, + int32_t maxHeight) +{ + begin_resize(seat, serial, edges, minWidth, minHeight, maxWidth, maxHeight); +} + +void TreeLandLayerShellExtensionObject::treeland_layer_shell_extension_object_v1_resizing(uint32_t resizing) +{ + Q_EMIT resizingChanged(resizing != 0); +} + +} diff --git a/panels/dock/layershellextension.h b/panels/dock/layershellextension.h new file mode 100644 index 000000000..2d7850d29 --- /dev/null +++ b/panels/dock/layershellextension.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "qwayland-treeland-layer-shell-extension-unstable-v1.h" + +#include + +#include + +namespace dock +{ + +// Client wrapper for the treeland_layer_shell_extension_manager_v1 protocol global. +// Lets the dock opt in to compositor-driven interactive resize: the compositor +// owns the drag state machine and drives the surface size through the standard +// layer-surface configure events (see treeland-layer-shell-extension-unstable-v1.xml). +class TreeLandLayerShellExtensionManager : public QWaylandClientExtensionTemplate, + public QtWayland::treeland_layer_shell_extension_manager_v1 +{ + Q_OBJECT + +public: + explicit TreeLandLayerShellExtensionManager(); + + // Creates an interactive object for an existing wl_surface and returns the + // raw protocol object; the caller wraps it in a TreeLandLayerShellExtensionObject. + struct ::treeland_layer_shell_extension_object_v1 *getLayerShellExtensionObject(struct ::wl_surface *surface); +}; + +// Client wrapper for the treeland_layer_shell_extension_object_v1 protocol object bound +// to the layer surface. This is the single entry point for every interactive +// operation on a treeland layer-shell surface (resize today, move later). +class TreeLandLayerShellExtensionObject : public QWaylandClientExtensionTemplate, + public QtWayland::treeland_layer_shell_extension_object_v1 +{ + Q_OBJECT + +public: + explicit TreeLandLayerShellExtensionObject(struct ::treeland_layer_shell_extension_object_v1 *object, struct ::wl_surface *nativeSurface); + ~TreeLandLayerShellExtensionObject(); + + void beginResize(struct ::wl_seat *seat, uint32_t serial, uint32_t edges, int32_t minWidth, int32_t minHeight, int32_t maxWidth, int32_t maxHeight); + + // Returns the wl_surface this object was bound to. Does not validate + // lifetime; callers must compare it against the window's current surface + // and re-bind (a new object) when it changed, before issuing any request. + struct ::wl_surface *nativeSurface() const; + +Q_SIGNALS: + void resizingChanged(bool resizing); + +protected: + void treeland_layer_shell_extension_object_v1_resizing(uint32_t resizing) override; + +private: + struct ::wl_surface *m_nativeSurface = nullptr; +}; + +} diff --git a/panels/dock/package/main.qml b/panels/dock/package/main.qml index a9a01f077..e1104215e 100644 --- a/panels/dock/package/main.qml +++ b/panels/dock/package/main.qml @@ -42,6 +42,8 @@ Window { property bool isDragging: false + readonly property bool compositorDrivenResize: Qt.platform.pluginName !== "xcb" + property real dockItemIconSize: dockItemMaxSize * 9 / 14 // NOTE: -1 means not set its size, follow the platform size @@ -650,6 +652,10 @@ Window { oldDockSize = dockSize recentDeltas = [] Panel.requestClosePopup() + + if (dock.compositorDrivenResize) { + Panel.beginResize() + } DS.grabMouse(Panel.rootObject, true) } @@ -658,6 +664,9 @@ Window { onPositionChanged: function(mouse) { if (Panel.locked || !dock.isDragging) return + + if (dock.compositorDrivenResize) return + var newPos = mapToGlobal(mouse.x, mouse.y) var xChange = newPos.x - oldMousePos.x var yChange = newPos.y - oldMousePos.y @@ -694,6 +703,13 @@ Window { onReleased: function(mouse) { if (Panel.locked) return + + if (dock.compositorDrivenResize && Applet.isResizing) return + + finishResize() + } + + function finishResize() { dock.isDragging = false Applet.dockSize = dockSize itemIconSizeBase = dockItemMaxSize @@ -730,9 +746,32 @@ Window { anchors.top = parent.top dragArea.width = 5 } + } + Connections { + target: Panel + function onIsResizingChanged(resizing) { + if (!resizing && dock.isDragging) { + dragArea.finishResize() + } + } } + Connections { + target: dock + enabled: dock.compositorDrivenResize && Applet.isResizing + function onWidthChanged() { + if (dock.useColumnLayout) { + dock.dockSize = dock.width + } + } + + function onHeightChanged() { + if (!dock.useColumnLayout) { + dock.dockSize = dock.height + } + } + } function changeDragAreaAnchor() { switch(dock.positionForAnimation) { case Dock.Top: {