diff --git a/.gitignore b/.gitignore index f6b4d143..53f4d4d4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ compile_commands.json # qtcreator *.autosave +# vscode +.vscode diff --git a/.reuse/dep5 b/.reuse/dep5 index f90d00d9..195ea5dc 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -84,5 +84,6 @@ License: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only # Misc Files: tests/manual/cursor/res/HandCursor.png + examples/tinywl/res/xx.jpg Copyright: None License: CC0-1.0 diff --git a/debian/control b/debian/control index 129f29cb..b0507657 100644 --- a/debian/control +++ b/debian/control @@ -2,24 +2,24 @@ Source: waylib Section: libdevel Priority: optional Maintainer: JiDe Zhang -Build-Depends: debhelper (>= 9), - cmake, - pkg-config, - qt6-base-private-dev (>= 6.4.0), - qt6-base-dev-tools (>= 6.4.0), - qt6-declarative-private-dev (>= 6.4.0), - qml6-module-qtquick-templates, - qwlroots, - libwlroots-dev (>= 0.17.0), - libpixman-1-dev, - libxcb-ewmh-dev, - wayland-protocols, - wlr-protocols +Build-Depends: cmake, + debhelper (>= 9), + libpixman-1-dev, + libwlroots-dev (>= 0.17.0), + libxcb-ewmh-dev, + pkg-config, + qml6-module-qtquick-templates, + qt6-base-dev-tools (>= 6.4.0), + qt6-base-private-dev (>= 6.4.0), + qt6-declarative-private-dev (>= 6.4.0), + qwlroots, + wayland-protocols, + wlr-protocols, Standards-Version: 3.9.8 Package: libwaylib Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: ${misc:Depends}, ${shlibs:Depends}, Multi-Arch: same Description: A wrapper for wlroots based on Qt . @@ -27,15 +27,14 @@ Description: A wrapper for wlroots based on Qt Package: libwaylib-dev Architecture: any -Depends: ${shlibs:Depends}, - ${misc:Depends}, - libwaylib (=${binary:Version}), - qt6-base-private-dev (>= 6.6.0), - qt6-base-dev-tools (>= 6.6.0), - qt6-declarative-private-dev (>= 6.6.0), - libwlroots-dev (>= 0.17.0), - wlr-protocols +Depends: libwaylib (=${binary:Version}), + libwlroots-dev (>= 0.17.0), + qt6-base-dev-tools (>= 6.6.0), + qt6-base-private-dev (>= 6.6.0), + qt6-declarative-private-dev (>= 6.6.0), + wlr-protocols, + ${misc:Depends}, + ${shlibs:Depends}, Description: A devel package for libwaylib . This package contains the header files and static libraries of waylib. - diff --git a/debian/libwaylib-dev.install b/debian/libwaylib-dev.install index fc74ff62..9f3993e9 100644 --- a/debian/libwaylib-dev.install +++ b/debian/libwaylib-dev.install @@ -1,4 +1,4 @@ -usr/lib/*/lib*.so usr/include -usr/lib/*/pkgconfig usr/lib/*/cmake +usr/lib/*/lib*.so +usr/lib/*/pkgconfig diff --git a/examples/blur/Main.qml b/examples/blur/Main.qml index b22aa959..158f3f8f 100644 --- a/examples/blur/Main.qml +++ b/examples/blur/Main.qml @@ -45,14 +45,14 @@ Item { cursorDelegate: Cursor { id: cursorItem - required property QtObject outputCurosr + required property QtObject outputCursor readonly property point position: parent.mapFromGlobal(cursor.position.x, cursor.position.y) - cursor: outputCurosr.cursor - output: outputCurosr.output.output + cursor: outputCursor.cursor + output: outputCursor.output.output x: position.x - hotSpot.x y: position.y - hotSpot.y - visible: valid && outputCurosr.visible + visible: valid && outputCursor.visible OutputLayer.enabled: true OutputLayer.keepLayer: true OutputLayer.flags: OutputLayer.Cursor diff --git a/examples/outputcopy/PrimaryOutputDelegate.qml b/examples/outputcopy/PrimaryOutputDelegate.qml index a844e718..faf2ca26 100644 --- a/examples/outputcopy/PrimaryOutputDelegate.qml +++ b/examples/outputcopy/PrimaryOutputDelegate.qml @@ -17,14 +17,14 @@ OutputItem { cursorDelegate: Cursor { id: cursorItem - required property QtObject outputCurosr + required property QtObject outputCursor readonly property point position: parent.mapFromGlobal(cursor.position.x, cursor.position.y) - cursor: outputCurosr.cursor - output: outputCurosr.output.output + cursor: outputCursor.cursor + output: outputCursor.output.output x: position.x - hotSpot.x y: position.y - hotSpot.y - visible: valid && outputCurosr.visible + visible: valid && outputCursor.visible OutputLayer.enabled: true OutputLayer.keepLayer: true OutputLayer.flags: OutputLayer.Cursor diff --git a/examples/outputcopy/main.cpp b/examples/outputcopy/main.cpp index a9495afd..414a967c 100644 --- a/examples/outputcopy/main.cpp +++ b/examples/outputcopy/main.cpp @@ -210,7 +210,6 @@ int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine waylandEngine; - waylandEngine.loadFromModule("OutputCopy", "Main"); Helper *helper = waylandEngine.singletonInstance("OutputCopy", "Helper"); Q_ASSERT(helper); @@ -222,7 +221,7 @@ int main(int argc, char *argv[]) { qw_output *newOutput = nullptr; if (auto x11 = qw_x11_backend::from(backend)) { - newOutput = qw_output::from(x11->output_create()); + newOutput = qw_output::from(x11->output_create()); } else if (auto wayland = qw_wayland_backend::from(backend)) { newOutput = qw_output::from(wayland->output_create()); } diff --git a/examples/outputviewport/Main.qml b/examples/outputviewport/Main.qml index 85d3914e..266a1646 100644 --- a/examples/outputviewport/Main.qml +++ b/examples/outputviewport/Main.qml @@ -45,14 +45,14 @@ Item { cursorDelegate: Cursor { id: cursorItem - required property QtObject outputCurosr + required property QtObject outputCursor readonly property point position: parent.mapFromGlobal(cursor.position.x, cursor.position.y) - cursor: outputCurosr.cursor - output: outputCurosr.output.output + cursor: outputCursor.cursor + output: outputCursor.output.output x: position.x - hotSpot.x y: position.y - hotSpot.y - visible: valid && outputCurosr.visible + visible: valid && outputCursor.visible OutputLayer.enabled: true OutputLayer.keepLayer: true OutputLayer.flags: OutputLayer.Cursor diff --git a/examples/surface-delegate/Main.qml b/examples/surface-delegate/Main.qml index 4f976480..05f60b4f 100644 --- a/examples/surface-delegate/Main.qml +++ b/examples/surface-delegate/Main.qml @@ -119,14 +119,14 @@ Item { cursorDelegate: Cursor { id: cursorItem - required property QtObject outputCurosr + required property QtObject outputCursor readonly property point position: parent.mapFromGlobal(cursor.position.x, cursor.position.y) - cursor: outputCurosr.cursor - output: outputCurosr.output.output + cursor: outputCursor.cursor + output: outputCursor.output.output x: position.x - hotSpot.x y: position.y - hotSpot.y - visible: valid && outputCurosr.visible + visible: valid && outputCursor.visible OutputLayer.enabled: true OutputLayer.keepLayer: true OutputLayer.flags: OutputLayer.Cursor diff --git a/examples/tinywl/Border.qml b/examples/tinywl/Border.qml new file mode 100644 index 00000000..44eb6aed --- /dev/null +++ b/examples/tinywl/Border.qml @@ -0,0 +1,36 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick + +Item { + id: root + + property real radius: 0 + + Rectangle { + id: outsideBorder + anchors { + fill: parent + margins: -border.width + } + + color: "transparent" + border { + color: "yellow" + width: 1 + } + radius: root.radius + border.width + } + + Rectangle { + id: insideBorder + anchors.fill: parent + color: "transparent" + border { + color: "green" + width: 1 + } + radius: root.radius + } +} diff --git a/examples/tinywl/CMakeLists.txt b/examples/tinywl/CMakeLists.txt index 7ee51347..307fb453 100644 --- a/examples/tinywl/CMakeLists.txt +++ b/examples/tinywl/CMakeLists.txt @@ -10,59 +10,72 @@ if(POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() -set(QML_IMPORT_PATH "${PROJECT_BINARY_DIR}/src/server;${QML_IMPORT_PATH}" CACHE STRING "For LSP" FORCE) +set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR}/src/server/ CACHE STRING "" FORCE) option(START_DEMO "Start demo when boot" ON) find_package(PkgConfig REQUIRED) pkg_search_module(PIXMAN REQUIRED IMPORTED_TARGET pixman-1) pkg_search_module(WAYLAND REQUIRED IMPORTED_TARGET wayland-server) -add_executable(tinywl-qtquick - main.cpp -) +set(TARGET tinywl-qtquick) -set_source_files_properties(QmlHelper.qml - PROPERTIES - QT_QML_SINGLETON_TYPE TRUE +add_executable(${TARGET} + main.cpp ) -qt_add_qml_module(tinywl-qtquick +qt_add_qml_module(${TARGET} URI Tinywl - VERSION "1.0" - SOURCES - helper.h - QML_FILES - Main.qml - StackWorkspace.qml - XdgSurface.qml - LayerSurface.qml - TiledWorkspace.qml - QmlHelper.qml - OutputDelegate.qml - StackToplevelHelper.qml - TiledToplevelHelper.qml - WindowDecoration.qml - CloseAnimation.qml - MiniDock.qml - InputPopupSurface.qml + VERSION "2.0" + + SOURCES helper.h helper.cpp + SOURCES surfacewrapper.h surfacewrapper.cpp + SOURCES workspace.h workspace.cpp + SOURCES output.h output.cpp + SOURCES qmlengine.h qmlengine.cpp + SOURCES surfacecontainer.h surfacecontainer.cpp + SOURCES layersurfacecontainer.h layersurfacecontainer.cpp + SOURCES rootsurfacecontainer.h rootsurfacecontainer.cpp + SOURCES surfaceproxy.h surfaceproxy.cpp + SOURCES wallpaperprovider.h wallpaperprovider.cpp + SOURCES wallpaperimage.h wallpaperimage.cpp + SOURCES workspacemodel.h workspacemodel.cpp + + QML_FILES PrimaryOutput.qml + QML_FILES CopyOutput.qml + QML_FILES TitleBar.qml + QML_FILES Decoration.qml + QML_FILES TaskBar.qml + QML_FILES RoundedClipEffect.qml + QML_FILES SurfaceContent.qml + QML_FILES Shadow.qml + QML_FILES Border.qml + QML_FILES GeometryAnimation.qml + QML_FILES OutputMenuBar.qml + QML_FILES WorkspaceSwitcher.qml + QML_FILES WorkspaceProxy.qml + QML_FILES WindowMenu.qml + + RESOURCES + "res/xx.jpg" ) -target_compile_definitions(tinywl-qtquick +target_compile_definitions(${TARGET} PRIVATE + SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" PROJECT_BINARY_DIR="${PROJECT_BINARY_DIR}" $<$:START_DEMO> - WLR_USE_UNSTABLE ) -target_link_libraries(tinywl-qtquick +target_link_libraries(${TARGET} PRIVATE Qt6::Quick Qt6::QuickControls2 + Qt6::QuickPrivate Waylib::WaylibServer PkgConfig::PIXMAN PkgConfig::WAYLAND ) if (INSTALL_TINYWL) - install(TARGETS tinywl-qtquick DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(TARGETS ${TARGET} DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() diff --git a/examples/tinywl/CloseAnimation.qml b/examples/tinywl/CloseAnimation.qml deleted file mode 100644 index d4d5a88f..00000000 --- a/examples/tinywl/CloseAnimation.qml +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import QtQuick.Particles - -Item { - id: root - - signal stopped - - visible: false - - function start(target) { - width = target.width - height = target.height - effect.sourceItem = target - effect.width = target.width - effect.height = target.height - visible = true - hideAnimation.start() - } - - function stop() { - visible = false - effect.sourceItem = null - stopped() - } - - Item { - id: content - - width: parent.width - height: parent.height - clip: true - - ShaderEffectSource { - id: effect - live: false - hideSource: true - } - } - - PropertyAnimation { - id: hideAnimation - duration: 500 - target: content - from: parent.height - to: 0 - property: "height" - - onStopped: { - root.stopped() - } - } - - // Copy from the Qt particles example "affectors" - ParticleSystem { - id: flameSystem - - anchors.fill: parent - - ImageParticle { - groups: ["flame"] - source: "qrc:///particleresources/glowdot.png" - color: "#88ff200f" - colorVariation: 0.1 - } - - Row { - width: parent.width - anchors { - bottom: parent.bottom - bottomMargin: parent.height - content.height - } - - Repeater { - model: parent.width / 50 - - Emitter { - system: flameSystem - group: "flame" - width: 50 - height: 50 - - emitRate: 120 - lifeSpan: 200 - size: 50 - endSize: 10 - sizeVariation: 10 - acceleration: PointDirection { y: -40 } - velocity: AngleDirection { angle: 270; magnitude: 20; angleVariation: 22; magnitudeVariation: 5 } - } - } - } - } -} diff --git a/examples/tinywl/CopyOutput.qml b/examples/tinywl/CopyOutput.qml new file mode 100644 index 00000000..97162116 --- /dev/null +++ b/examples/tinywl/CopyOutput.qml @@ -0,0 +1,57 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import Waylib.Server + +OutputItem { + id: outputItem + + required property PrimaryOutput targetOutputItem + property OutputViewport screenViewport: targetOutputItem.screenViewport + + devicePixelRatio: output?.scale ?? devicePixelRatio + + Rectangle { + id: content + anchors.fill: parent + color: "gray" + + TextureProxy { + id: proxy + sourceItem: screenViewport + anchors.centerIn: parent + rotation: targetOutputItem.keepAllOutputRotation ? 0 : screenViewport.rotation + width: screenViewport.implicitWidth + height: screenViewport.implicitHeight + smooth: true + transformOrigin: Item.Center + scale: { + const isize = targetOutputItem.keepAllOutputRotation + ? Qt.size(width, height) + : Qt.size(targetOutputItem.width, targetOutputItem.height); + const osize = Qt.size(outputItem.width, outputItem.height); + const size = WaylibHelper.scaleSize(isize, osize, Qt.KeepAspectRatio); + return size.width / isize.width; + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "I'm a duplicate of the primary screen" + font.pointSize: 18 + color: "red" + } + } + + OutputViewport { + id: viewport + + anchors.centerIn: parent + depends: [screenViewport] + devicePixelRatio: outputItem.devicePixelRatio + input: content + output: outputItem.output + ignoreViewport: true + } +} diff --git a/examples/tinywl/Decoration.qml b/examples/tinywl/Decoration.qml new file mode 100644 index 00000000..14d9ecdc --- /dev/null +++ b/examples/tinywl/Decoration.qml @@ -0,0 +1,80 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Effects +import Waylib.Server +import Tinywl + +Item { + id: root + + required property SurfaceWrapper surface + readonly property SurfaceItem surfaceItem: surface.surfaceItem + + visible: surface && surface.visibleDecoration + x: shadow.boundingRect.x + y: shadow.boundingRect.y + width: shadow.boundingRect.width + height: shadow.boundingRect.height + + MouseArea { + property int edges: 0 + + anchors { + fill: shadow + margins: -10 + } + + hoverEnabled: true + Cursor.shape: { + switch(edges) { + case Qt.TopEdge: + return Waylib.CursorShape.TopSide + case Qt.RightEdge: + return Waylib.CursorShape.RightSide + case Qt.BottomEdge: + return Waylib.CursorShape.BottomSide + case Qt.LeftEdge: + return Waylib.CursorShape.LeftSide + case Qt.TopEdge | Qt.LeftEdge: + return Waylib.CursorShape.TopLeftCorner + case Qt.TopEdge | Qt.RightEdge: + return Waylib.CursorShape.TopRightCorner + case Qt.BottomEdge | Qt.LeftEdge: + return Waylib.CursorShape.BottomLeftCorner + case Qt.BottomEdge | Qt.RightEdge: + return Waylib.CursorShape.BottomRightCorner + } + + return Qt.ArrowCursor; + } + + onPositionChanged: function (event) { + edges = WaylibHelper.getEdges(Qt.rect(0, 0, width, height), Qt.point(event.x, event.y), 10) + } + + onPressed: function (event) { + // Maybe missing onPositionChanged when use touchscreen + edges = WaylibHelper.getEdges(Qt.rect(0, 0, width, height), Qt.point(event.x, event.y), 10) + if (edges) + surface.requestResize(edges) + } + } + + Shadow { + id: shadow + width: surface.width + height: surface.height + radius: surface.radius + anchors.centerIn: parent + } + + Border { + visible: surface.visibleDecoration + parent: surfaceItem + z: SurfaceItem.ZOrder.ContentItem + 1 + anchors.fill: parent + radius: surface.radius + } +} diff --git a/examples/tinywl/GeometryAnimation.qml b/examples/tinywl/GeometryAnimation.qml new file mode 100644 index 00000000..323d60d5 --- /dev/null +++ b/examples/tinywl/GeometryAnimation.qml @@ -0,0 +1,114 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import Tinywl + +Item { + id: root + + required property SurfaceWrapper surface + required property rect fromGeometry + required property rect toGeometry + property int duration: 200 * Helper.animationSpeed + + signal ready + signal finished + + x: fromGeometry.x + y: fromGeometry.y + width: fromGeometry.width + height: fromGeometry.height + + function start() { + animation.start(); + } + + ShaderEffectSource { + id: backgroundEffect + + readonly property real xScale: root.width / surface.width + readonly property real yScale: root.height / surface.height + + live: true + sourceItem: surface + hideSource: true + sourceRect: surface.boundingRect + width: sourceRect.width * xScale + height: sourceRect.height * yScale + x: sourceRect.x * xScale + y: sourceRect.y * yScale + } + + ShaderEffectSource { + id: frontEffect + + readonly property real xScale: root.width / fromGeometry.width + readonly property real yScale: root.height / fromGeometry.height + + live: false + sourceItem: surface + width: sourceRect.width * xScale + height: sourceRect.height * yScale + x: sourceRect.x * xScale + y: sourceRect.y * yScale + + onScheduledUpdateCompleted: { + root.ready(); + } + + Component.onCompleted: { + sourceRect = surface.boundingRect + } + } + + ParallelAnimation { + id: animation + + XAnimator { + target: root + duration: root.duration + easing.type: Easing.OutCubic + from: root.fromGeometry.x + to: root.toGeometry.x + } + + YAnimator { + target: root + duration: root.duration + easing.type: Easing.OutCubic + from: root.fromGeometry.y + to: root.toGeometry.y + } + + PropertyAnimation { + target: root + property: "width" + duration: root.duration + easing.type: Easing.OutCubic + from: root.fromGeometry.width + to: root.toGeometry.width + } + + PropertyAnimation { + target: root + property: "height" + duration: root.duration + easing.type: Easing.OutCubic + from: root.fromGeometry.height + to: root.toGeometry.height + } + + OpacityAnimator { + target: frontEffect + duration: root.duration / 2 + easing.type: Easing.OutCubic + from: 1.0 + to: 0.0 + } + + onFinished: { + root.finished(); + } + } +} diff --git a/examples/tinywl/InputPopupSurface.qml b/examples/tinywl/InputPopupSurface.qml deleted file mode 100644 index 1e7ace30..00000000 --- a/examples/tinywl/InputPopupSurface.qml +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2023 Yixue Wang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import Waylib.Server - -InputPopupSurfaceItem { - - property var cursorPoint: Qt.point(referenceRect.x, referenceRect.y) - property real cursorWidth: referenceRect.width - property real cursorHeight: referenceRect.height - - x: { - var x = cursorPoint.x + cursorWidth - if (x + width > parent.width) { - x = Math.max(0, parent.width - width) - } - return x - } - - y: { - var y = cursorPoint.y + cursorHeight - if (y + height > parent.height) { - y = Math.min(cursorPoint.y - height, parent.height - height) - } - return y - } - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - surface.surface.enterOutput(output); - } - onLeaveOutput: function(output) { - surface.surface.leaveOutput(output); - } - } -} diff --git a/examples/tinywl/LayerSurface.qml b/examples/tinywl/LayerSurface.qml deleted file mode 100644 index 3f2fe3eb..00000000 --- a/examples/tinywl/LayerSurface.qml +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (C) 2023 rewine . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import Waylib.Server -import QtQuick.Particles -import Tinywl - -Item { - property alias waylandSurface: surfaceItem.shellSurface - property alias surfaceItem: surfaceItem - property bool anchorWidth: false - property bool anchorHeight: false - // From Helper - required property DynamicCreatorComponent creator - property OutputItem output - property CoordMapper outputCoordMapper - property bool mapped: waylandSurface.surface && waylandSurface.surface.mapped && waylandSurface.WaylandSocket.rootSocket.enabled - property bool pendingDestroy: false - - id: root - z: zValueFormLayer((waylandSurface as WaylandLayerSurface).layer) - - LayerSurfaceItem { - anchors.centerIn: parent - - id: surfaceItem - - onHeightChanged: { - if (!anchorHeight) - parent.height = height - } - - onWidthChanged: { - if (!anchorWidth) - parent.width = width - } - - onEffectiveVisibleChanged: { - if (effectiveVisible && surface.isActivated) - forceActiveFocus() - } - } - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - waylandSurface.surface.enterOutput(output) - Helper.onSurfaceEnterOutput(waylandSurface, surfaceItem, output) - Helper.registerExclusiveZone(waylandSurface) - } - onLeaveOutput: function(output) { - Helper.unregisterExclusiveZone(waylandSurface) - waylandSurface.surface.leaveOutput(output) - Helper.onSurfaceLeaveOutput(waylandSurface, surfaceItem, output) - } - } - - Loader { - id: closeAnimation - } - - Component { - id: closeAnimationComponent - - CloseAnimation { - onStopped: { - if (pendingDestroy) - creator.destroyObject(root) - } - } - } - - onMappedChanged: { - // When Socket is enabled and mapped becomes false, set visible - // after closeAnimation completeļ¼Œ Otherwise set visible directly. - if (mapped) { - Helper.registerExclusiveZone(waylandSurface) - refreshMargin() - visible = true - if (surfaceItem.effectiveVisible) - Helper.activatedSurface = waylandSurface - } else { // if not mapped - Helper.unregisterExclusiveZone(waylandSurface) - if (!waylandSurface.WaylandSocket.rootSocket.enabled) { - visible = false - } else { - // do animation for window close - closeAnimation.parent = root.parent - closeAnimation.anchors.fill = root - closeAnimation.sourceComponent = closeAnimationComponent - closeAnimation.item.start(root) - } - } - } - - function doDestroy() { - pendingDestroy = true - - if (!surfaceItem.visible || !closeAnimation.active) { - //Helper.unregisterExclusiveZone(waylandSurface) - creator.destroyObject(root) - return - } - - // unbind some properties - mapped = false - } - - function getPrimaryOutputItem() { - let output = waylandSurface.surface.primaryOutput - if (!output) - return null - return output.OutputItem.item - } - - function updateOutputCoordMapper() { - let output = getPrimaryOutputItem() - if (!output) - return - root.output = output - root.outputCoordMapper = surface.CoordMapper.helper.get(output) - } - - function zValueFormLayer(layer) { - switch (layer) { - case WaylandLayerSurface.LayerType.Background: - return -100 - case WaylandLayerSurface.LayerType.Bottom: - return -50 - case WaylandLayerSurface.LayerType.Top: - return 50 - case WaylandLayerSurface.LayerType.Overlay: - return 100 - } - // Should not be reachable - return -50; - } - - function refreshAnchors() { - var top = waylandSurface.ancher & WaylandLayerSurface.AnchorType.Top - var bottom = waylandSurface.ancher & WaylandLayerSurface.AnchorType.Bottom - var left = waylandSurface.ancher & WaylandLayerSurface.AnchorType.Left - var right = waylandSurface.ancher & WaylandLayerSurface.AnchorType.Right - - anchorWidth = left && right - anchorHeight = top && bottom - - anchors.top = top ? parent.top : undefined - anchors.bottom = bottom ? parent.bottom : undefined - anchors.verticalCenter = (top || bottom) ? undefined : parent.verticalCenter; - anchors.left = left ? parent.left : undefined - anchors.right = right ? parent.right : undefined - anchors.horizontalCenter = (left || right) ? undefined : parent.horizontalCenter; - // Setting anchors may change the container size which should keep same with surfaceItem - if (!anchorWidth) - width = surfaceItem.width - if (!anchorHeight) - height = surfaceItem.height - } - - function refreshExclusiveZone() { - Helper.unregisterExclusiveZone(waylandSurface) - Helper.registerExclusiveZone(waylandSurface) - } - - function refreshMargin() { - var accpectExclusive = waylandSurface.exclusiveZone >= 0 ? 1 : 0; - - var exclusiveMargin = Helper.getExclusiveMargins(waylandSurface) - var topMargin = waylandSurface.topMargin + accpectExclusive * exclusiveMargin.top; - var bottomMargin = waylandSurface.bottomMargin + accpectExclusive * exclusiveMargin.bottom; - var leftMargin = waylandSurface.leftMargin + accpectExclusive * exclusiveMargin.left; - var rightMargin = waylandSurface.rightMargin + accpectExclusive * exclusiveMargin.right; - - anchors.topMargin = topMargin; - anchors.bottomMargin = bottomMargin; - anchors.leftMargin = leftMargin; - anchors.rightMargin = rightMargin; - } - - function configureSurfaceSize() { - var surfaceWidth = waylandSurface.desiredSize.width - var surfaceHeight = waylandSurface.desiredSize.height - - if (surfaceWidth === 0) - surfaceWidth = width - if (surfaceHeight === 0) - surfaceHeight = height - - if (surfaceWidth && surfaceHeight) - waylandSurface.configureSize(Qt.size(surfaceWidth, surfaceHeight)) - } - - onHeightChanged: { - if (waylandSurface.desiredSize.height === 0 && height != 0) { - configureSurfaceSize() - } - } - - onWidthChanged: { - if (waylandSurface.desiredSize.width === 0 && width != 0) { - configureSurfaceSize() - } - } - - Component.onCompleted: { - refreshAnchors() - refreshMargin() - configureSurfaceSize() - } - - Connections { - target: waylandSurface - - function onLayerPropertiesChanged() { - Helper.unregisterExclusiveZone(waylandSurface) - Helper.registerExclusiveZone(waylandSurface) - refreshAnchors() - refreshMargin() - configureSurfaceSize() - } - - function onActivateChanged() { - if (waylandSurface.isActivated && surfaceItem.effectiveVisible) { - surfaceItem.forceActiveFocus() - } else { - surfaceItem.focus = false - } - } - } - Connections { - target: Helper - - function onTopExclusiveMarginChanged() { - refreshMargin() - } - - function onBottomExclusiveMarginChanged() { - refreshMargin() - } - - function onLeftExclusiveMarginChanged() { - refreshMargin() - } - - function onRightExclusiveMarginChanged() { - refreshMargin() - } - } -} diff --git a/examples/tinywl/Main.qml b/examples/tinywl/Main.qml deleted file mode 100644 index 835f79c8..00000000 --- a/examples/tinywl/Main.qml +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Waylib.Server -import Tinywl - -Item { - id :root - - Binding { - target: Helper.seat - property: "keyboardFocus" - value: Helper.getFocusSurfaceFrom(renderWindow.activeFocusItem) - } - - OutputRenderWindow { - id: renderWindow - - width: Helper.outputLayout.implicitWidth - height: Helper.outputLayout.implicitHeight - - onOutputViewportInitialized: function (viewport) { - // Trigger QWOutput::frame signal in order to ensure WOutputHelper::renderable - // property is true, OutputRenderWindow when will render this output in next frame. - Helper.enableOutput(viewport.output) - } - - EventJunkman { - anchors.fill: parent - } - - Item { - DynamicCreatorComponent { - creator: Helper.outputCreator - - OutputDelegate { - property real topMargin: topbar.height - } - } - } - - ColumnLayout { - anchors.fill: parent - - TabBar { - id: topbar - - Layout.fillWidth: true - - TabButton { - text: qsTr("Stack Layout") - onClicked: { - Helper.xdgDecorationManager.preferredMode = XdgDecorationManager.Client - } - } - TabButton { - text: qsTr("Tiled Layout") - onClicked: { - Helper.xdgDecorationManager.preferredMode = XdgDecorationManager.Server - } - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - StackWorkspace { - visible: topbar.currentIndex === 0 - anchors.fill: parent - } - - TiledWorkspace { - visible: topbar.currentIndex === 1 - anchors.fill: parent - } - } - } - } -} diff --git a/examples/tinywl/MiniDock.qml b/examples/tinywl/MiniDock.qml deleted file mode 100644 index 3487b3a9..00000000 --- a/examples/tinywl/MiniDock.qml +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2023 rewine . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick - -Item { - property alias model: dockListView.model - - ListView { - id: dockListView - height: Math.min(parent.height, contentHeight) - spacing: 8 - anchors { - verticalCenter: parent.verticalCenter - left: parent.left - right: parent.right - } - - model: ListModel { - id: dockModel - function removeSurface(surface) { - for (var i = 0; i < dockModel.count; i++) { - if (dockModel.get(i).source === surface) { - dockModel.remove(i); - break; - } - } - } - } - - delegate: ShaderEffectSource { - id: dockitem - width: 100; height: 100 - sourceItem: source - smooth: true - - MouseArea { - anchors.fill: parent; - onClicked: { - dockitem.sourceItem.cancelMinimize(); - } - } - } - } -} diff --git a/examples/tinywl/OutputDelegate.qml b/examples/tinywl/OutputDelegate.qml deleted file mode 100644 index 032b8cc4..00000000 --- a/examples/tinywl/OutputDelegate.qml +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import QtQuick.Controls -import Waylib.Server -import Tinywl - -OutputItem { - id: rootOutputItem - required property WaylandOutput waylandOutput - property OutputViewport onscreenViewport: outputViewport - property Cursor lastActiveCursorItem - - output: waylandOutput - devicePixelRatio: waylandOutput.scale - - cursorDelegate: Cursor { - id: cursorItem - - required property QtObject outputCurosr - readonly property point position: parent.mapFromGlobal(cursor.position.x, cursor.position.y) - - cursor: outputCurosr.cursor - output: outputCurosr.output.output - x: position.x - hotSpot.x - y: position.y - hotSpot.y - visible: valid && outputCurosr.visible - OutputLayer.enabled: true - OutputLayer.keepLayer: true - OutputLayer.outputs: [onscreenViewport] - OutputLayer.flags: OutputLayer.Cursor - OutputLayer.cursorHotSpot: hotSpot - - function updateActiveCursor() { - if (cursorItems.size === 1) { - lastActiveCursorItem = this; - return; - } - - const pos = onscreenViewport.mapToOutput(this, Qt.point(0, 0)); - if (pos.x >= 0 && pos.x < onscreenViewport.width - && pos.y >= 0 && pos.y < onscreenViewport.height) { - lastActiveCursorItem = this; - } - } - - onXChanged: updateActiveCursor() - onYChanged: updateActiveCursor() - - SurfaceItem { - id: dragIcon - parent: cursorItem.parent - z: cursorItem.z - 1 - flags: SurfaceItem.DontCacheLastBuffer - surface: cursorItem.cursor.requestedDragSurface - x: cursorItem.position.x - y: cursorItem.position.y - } - } - - OutputViewport { - id: outputViewport - - output: waylandOutput - devicePixelRatio: parent.devicePixelRatio - anchors.centerIn: parent - - RotationAnimation { - id: rotationAnimator - - target: outputViewport - duration: 200 - alwaysRunToEnd: true - } - - Timer { - id: setTransform - - property var scheduleTransform - onTriggered: onscreenViewport.rotateOutput(scheduleTransform) - interval: rotationAnimator.duration / 2 - } - - function rotationOutput(orientation) { - setTransform.scheduleTransform = orientation - setTransform.start() - - switch(orientation) { - case WaylandOutput.R90: - rotationAnimator.to = 90 - break - case WaylandOutput.R180: - rotationAnimator.to = 180 - break - case WaylandOutput.R270: - rotationAnimator.to = -90 - break - default: - rotationAnimator.to = 0 - break - } - - rotationAnimator.from = rotation - rotationAnimator.start() - } - } - - Image { - id: background - source: "file:///usr/share/wallpapers/deepin/desktop.jpg" - fillMode: Image.PreserveAspectCrop - asynchronous: true - anchors.fill: parent - } - - Component { - id: outputScaleEffect - - OutputViewport { - readonly property OutputItem outputItem: waylandOutput.OutputItem.item - - id: viewport - input: this - output: waylandOutput - devicePixelRatio: outputViewport.devicePixelRatio - anchors.fill: outputViewport - rotation: outputViewport.rotation - - TextureProxy { - sourceItem: outputViewport - anchors.fill: parent - } - - Item { - width: outputItem.width - height: outputItem.height - anchors.centerIn: parent - rotation: -outputViewport.rotation - - Item { - y: 10 - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width / 2 - height: parent.height / 3 - clip: true - - Item { - id: centerItem - width: 1 - height: 1 - anchors.centerIn: parent - rotation: outputViewport.rotation - - TextureProxy { - id: magnifyingLens - - sourceItem: outputViewport - smooth: false - scale: 10 - transformOrigin: Item.TopLeft - width: viewport.width - height: viewport.height - - function updatePosition() { - const pos = outputItem.lastActiveCursorItem.mapToItem(outputViewport, Qt.point(0, 0)) - x = - pos.x * scale - y = - pos.y * scale - } - - Connections { - target: outputItem.lastActiveCursorItem - - function onXChanged() { - magnifyingLens.updatePosition() - } - - function onYChanged() { - magnifyingLens.updatePosition() - } - } - - Component.onCompleted: updatePosition() - } - } - } - } - } - } - - Text { - anchors.bottom: parent.bottom - text: { - if (!lastActiveCursorItem) - return ""; - let layer = lastActiveCursorItem.OutputLayer; - return layer.inOutputsByHardware.includes(onscreenViewport) - ? "Hardware Cursor" - : "Software Cursor"; - } - color: "red" - font.pointSize: 20 - } - - Column { - anchors { - bottom: parent.bottom - right: parent.right - margins: 10 - } - - spacing: 10 - - Switch { - property OutputViewport outputViewportEffect - - text: "Magnifying Lens" - onCheckedChanged: { - if (checked) { - outputViewport.cacheBuffer = true - outputViewport.offscreen = true - outputViewportEffect = outputScaleEffect.createObject(outputViewport.parent) - onscreenViewport = outputViewportEffect - } else { - outputViewportEffect.invalidate() - outputViewportEffect.destroy() - outputViewport.offscreen = false - outputViewport.cacheBuffer = false - onscreenViewport = outputViewport - } - } - } - - Switch { - text: "Socket" - checked: true - onCheckedChanged: { - Helper.setSocketEnabled(checked) - } - } - - Switch { - text: "Hardware Cursor" - checkable: false - checked: !onscreenViewport.disableHardwareLayers - onClicked: { - onscreenViewport.disableHardwareLayers = !onscreenViewport.disableHardwareLayers - } - } - - Button { - text: "1X" - onClicked: { - onscreenViewport.setOutputScale(1) - } - } - - Button { - text: "1.5X" - onClicked: { - onscreenViewport.setOutputScale(1.5) - } - } - - Button { - text: "Normal" - onClicked: { - outputViewport.rotationOutput(WaylandOutput.Normal) - } - } - - Button { - text: "R90" - onClicked: { - outputViewport.rotationOutput(WaylandOutput.R90) - } - } - - Button { - text: "R270" - onClicked: { - outputViewport.rotationOutput(WaylandOutput.R270) - } - } - - Button { - text: "Quit" - onClicked: { - Qt.quit() - } - } - } - - Text { - anchors.centerIn: parent - text: "'Ctrl+Q' quit" - font.pointSize: 40 - color: "white" - - SequentialAnimation on rotation { - id: ani - running: true - PauseAnimation { duration: 1500 } - NumberAnimation { from: 0; to: 360; duration: 5000; easing.type: Easing.InOutCubic } - loops: Animation.Infinite - } - } - - function setTransform(transform) { - onscreenViewport.rotationOutput(transform) - } - - function setScale(scale) { - onscreenViewport.setOutputScale(scale) - } - - Component.onDestruction: onscreenViewport.invalidate() -} diff --git a/examples/tinywl/OutputMenuBar.qml b/examples/tinywl/OutputMenuBar.qml new file mode 100644 index 00000000..dbcab7d1 --- /dev/null +++ b/examples/tinywl/OutputMenuBar.qml @@ -0,0 +1,158 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import Waylib.Server +import Tinywl + +Item { + required property PrimaryOutput output + + width: output.width + height: menuBar.contentHeight + x: output.x + y: output.y + + ToolBar { + id: menuBar + + width: parent.width + + Row { + anchors.fill: parent + + ToolButton { + text: "Quit" + onClicked: Qt.quit() + } + + ToolButton { + text: "Scale" + onClicked: scaleMenu.popup() + + Menu { + id: scaleMenu + + MenuItem { + text: "100%" + onClicked: { + output.setScale(1) + } + } + + MenuItem { + text: "125%" + onClicked: { + output.setScale(1.25) + } + } + + MenuItem { + text: "150%" + onClicked: { + output.setScale(1.5) + } + } + + MenuItem { + text: "175%" + onClicked: { + output.setScale(1.75) + } + } + + MenuItem { + text: "200%" + onClicked: { + output.setScale(2) + } + } + } + } + + ToolButton { + text: "Rotation" + + onClicked: rotationMenu.popup() + + Menu { + id: rotationMenu + + MenuItem { + text: "Normal" + onClicked: { + output.setTransform(WaylandOutput.Normal) + } + } + + MenuItem { + text: "R90" + onClicked: { + output.setTransform(WaylandOutput.R90) + } + } + + MenuItem { + text: "R270" + onClicked: { + output.setTransform(WaylandOutput.R270) + } + } + } + } + + ToolButton { + text: "New Workspace" + onClicked: Helper.workspace.createContainer("Workspace"+Math.random()); + } + + ToolButton { + text: "Delete Workspace" + onClicked: Helper.workspace.removeContainer(Helper.workspace.currentIndex); + } + + ToolButton { + text: "Prev Workspace" + onClicked: Helper.workspace.switchToPrev(); + } + + ToolButton { + text: "Next Workspace" + onClicked: Helper.workspace.switchToNext(); + } + + Label { + text: Helper.workspace.currentIndex + color: "red" + } + + ToolButton { + text: "Output" + + onClicked: outputMenu.popup() + + Menu { + id: outputMenu + + MenuItem { + text: "Add Output" + onClicked: { + Helper.addOutput() + } + } + + MenuItem { + text: (Helper.outputMode === Helper.OutputMode.Copy) ? "Extension Mode" : "Copy Mode" + onClicked: { + if (Helper.outputMode === Helper.OutputMode.Copy) + Helper.outputMode = Helper.OutputMode.Extension + else + Helper.outputMode = Helper.OutputMode.Copy + } + } + } + } + } + } +} diff --git a/examples/tinywl/PrimaryOutput.qml b/examples/tinywl/PrimaryOutput.qml new file mode 100644 index 00000000..42de5536 --- /dev/null +++ b/examples/tinywl/PrimaryOutput.qml @@ -0,0 +1,116 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import Waylib.Server +import Tinywl + +OutputItem { + id: rootOutputItem + readonly property OutputViewport screenViewport: outputViewport + property alias wallpaperVisible: wallpaper.visible + property bool forceSoftwareCursor: false + + devicePixelRatio: output?.scale ?? devicePixelRatio + + cursorDelegate: Cursor { + id: cursorItem + + required property QtObject outputCursor + readonly property point position: parent.mapFromGlobal(cursor.position.x, cursor.position.y) + + cursor: outputCursor.cursor + output: outputCursor.output.output + x: position.x - hotSpot.x + y: position.y - hotSpot.y + visible: valid && outputCursor.visible + OutputLayer.enabled: !outputCursor.output.forceSoftwareCursor + OutputLayer.keepLayer: true + OutputLayer.outputs: [screenViewport] + OutputLayer.flags: OutputLayer.Cursor + OutputLayer.cursorHotSpot: hotSpot + } + + OutputViewport { + id: outputViewport + + output: rootOutputItem.output + devicePixelRatio: parent.devicePixelRatio + anchors.centerIn: parent + + RotationAnimation { + id: rotationAnimator + + target: outputViewport + duration: 200 + alwaysRunToEnd: true + } + + Timer { + id: setTransform + + property var scheduleTransform + onTriggered: screenViewport.rotateOutput(scheduleTransform) + interval: rotationAnimator.duration / 2 + } + + function rotationOutput(orientation) { + setTransform.scheduleTransform = orientation + setTransform.start() + + switch(orientation) { + case WaylandOutput.R90: + rotationAnimator.to = 90 + break + case WaylandOutput.R180: + rotationAnimator.to = 180 + break + case WaylandOutput.R270: + rotationAnimator.to = -90 + break + default: + rotationAnimator.to = 0 + break + } + + rotationAnimator.from = rotation + rotationAnimator.start() + } + } + + Wallpaper { + id: wallpaper + userId: Helper.currentUserId + output: rootOutputItem.output + workspace: Helper.workspace.current + anchors.fill: parent + } + + Text { + anchors.centerIn: parent + text: "'Ctrl+Q' quit" + font.pointSize: 40 + color: "white" + + SequentialAnimation on rotation { + id: ani + running: true + PauseAnimation { duration: 1500 } + NumberAnimation { from: 0; to: 360; duration: 5000; easing.type: Easing.InOutCubic } + loops: Animation.Infinite + } + } + + function setTransform(transform) { + screenViewport.rotationOutput(transform) + } + + function setScale(scale) { + screenViewport.setOutputScale(scale) + } + + function invalidate() { + screenViewport.invalidate() + } +} diff --git a/examples/tinywl/QmlHelper.qml b/examples/tinywl/QmlHelper.qml deleted file mode 100644 index 02b0a16b..00000000 --- a/examples/tinywl/QmlHelper.qml +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -pragma Singleton - -import QtQuick -import Waylib.Server - -Item { - function printStructureObject(obj) { - var json = "" - for (var prop in obj){ - if (!obj.hasOwnProperty(prop)) { - continue; - } - let value = obj[prop] - try { - json += ` ${prop}: ${value},\n` - } catch (err) { - json += ` ${prop}: unknown,\n` - } - } - - return '{\n' + json + '}' - } -} diff --git a/examples/tinywl/RoundedClipEffect.qml b/examples/tinywl/RoundedClipEffect.qml new file mode 100644 index 00000000..eab14154 --- /dev/null +++ b/examples/tinywl/RoundedClipEffect.qml @@ -0,0 +1,43 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Effects + +Item { + id: root + + property alias sourceItem: effectSource.sourceItem + property real radius: 10 + property rect targetRect: Qt.rect(0, 0, width, height) + + ShaderEffectSource { + id: effectSource + hideSource: true + visible: false + } + + MultiEffect { + anchors.fill: parent + source: effectSource + maskEnabled: true + maskSource: ShaderEffectSource { + hideSource: true + mipmap: true + smooth: true + samples: 8 + sourceItem: Item { + width: root.width + height: root.height + Rectangle { + radius: root.radius + antialiasing: true + x: targetRect.x + y: targetRect.y + width: targetRect.width + height: targetRect.height + } + } + } + } +} diff --git a/examples/tinywl/Shadow.qml b/examples/tinywl/Shadow.qml new file mode 100644 index 00000000..5240475c --- /dev/null +++ b/examples/tinywl/Shadow.qml @@ -0,0 +1,46 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Effects + +Item { + id: root + + property alias color: shadowSource.color + property alias shadowEnabled: shadow.shadowEnabled + property alias radius: shadowSource.radius + readonly property rect boundingRect: Qt.rect(-shadow.blurMax, -shadow.blurMax, + width + 2 * shadow.blurMax, + height + 2 * shadow.blurMax) + + Rectangle { + id: shadowSource + anchors.fill: parent + color: shadow.shadowColor + layer { + enabled: true + live: true + } + visible: false + } + + MultiEffect { + id: shadow + x: blurMax + y: blurMax + anchors.centerIn: parent + width: parent.width + height: parent.height + shadowColor: shadowSource.color + shadowBlur: 1.0 + blurMax: 64 + shadowEnabled: true + shadowVerticalOffset: 10 + autoPaddingEnabled: true + maskEnabled: true + maskSource: shadowSource + maskInverted: true + source: shadowSource + } +} diff --git a/examples/tinywl/StackToplevelHelper.qml b/examples/tinywl/StackToplevelHelper.qml deleted file mode 100644 index 23c8834f..00000000 --- a/examples/tinywl/StackToplevelHelper.qml +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import Waylib.Server -import QtQuick.Particles -import Tinywl - -Item { - id: root - - required property SurfaceItem surface - required property ToplevelSurface waylandSurface - required property ListModel dockModel - required property DynamicCreatorComponent creator - property WindowDecoration decoration - - property OutputItem output - property CoordMapper outputCoordMapper - property bool mapped: waylandSurface.surface - && waylandSurface.surface.mapped - && waylandSurface.WaylandSocket.rootSocket.enabled - property bool pendingDestroy: false - property bool isMaximize: waylandSurface && waylandSurface.isMaximized && outputCoordMapper - property bool isFullScreen: waylandSurface && waylandSurface.isFullScreen && outputCoordMapper - - // For Maximize - function getMaximizeX() { - return outputCoordMapper.x + Helper.getLeftExclusiveMargin(waylandSurface) - } - function getMaximizeY() { - return outputCoordMapper.y + output.topMargin + Helper.getTopExclusiveMargin(waylandSurface) - } - function getMaximizeWidth() { - return outputCoordMapper.width - Helper.getLeftExclusiveMargin(waylandSurface) - Helper.getRightExclusiveMargin(waylandSurface) - } - function getMaximizeHeight() { - return outputCoordMapper.height - output.topMargin - Helper.getTopExclusiveMargin(waylandSurface) - Helper.getBottomExclusiveMargin(waylandSurface) - } - - // For Fullscreen - function getFullscreenX() { - return outputCoordMapper.x - } - function getFullscreenY() { - return outputCoordMapper.y + output.topMargin - } - function getFullscreenWidth() { - return outputCoordMapper.width - } - function getFullscreenHeight() { - return outputCoordMapper.height - output.topMargin - } - - Binding { - target: surface - property: "transitions" - restoreMode: Binding.RestoreNone - value: Transition { - id: stateTransition - - NumberAnimation { - properties: "x,y,width,height" - duration: 100 - } - } - } - - Binding { - target: surface - property: "resizeMode" - value: { - if (!surface.effectiveVisible) - return SurfaceItem.ManualResize - if (stateTransition.running - || waylandSurface.isMaximized) - return SurfaceItem.SizeToSurface - return SurfaceItem.SizeFromSurface - } - restoreMode: Binding.RestoreNone - } - - Loader { - id: closeAnimation - } - - Component { - id: closeAnimationComponent - - CloseAnimation { - onStopped: { - if (pendingDestroy) - creator.destroyObject(surface) - } - } - } - - Connections { - target: surface - - function onEffectiveVisibleChanged() { - if (surface.effectiveVisible) { - console.assert(surface.resizeMode !== SurfaceItem.ManualResize, - "The surface's resizeMode Shouldn't is ManualResize") - // Apply the WSurfaceItem's size to wl_surface - surface.resize(SurfaceItem.SizeToSurface) - - if (waylandSurface && waylandSurface.isActivated) - surface.forceActiveFocus() - } else { - Helper.cancelMoveResize(surface) - } - } - } - - Connections { - target: decoration - - // TODO: Don't call connOfSurface - - function onRequestMove() { - connOfSurface.onRequestMove(null, 0) - } - - function onRequestResize(edges) { - connOfSurface.onRequestResize(null, edges, null) - } - - function onRequestMinimize() { - connOfSurface.onRequestMinimize() - } - - function onRequestToggleMaximize(max) { - if (max) { - connOfSurface.onRequestMaximize() - } else { - connOfSurface.onRequestCancelMaximize() - } - } - - function onRequestClose() { - waylandSurface.close() - } - } - - onMappedChanged: { - // When Socket is enabled and mapped becomes false, set visible - // after closeAnimation completeļ¼Œ Otherwise set visible directly. - if (mapped) { - if (waylandSurface.isMinimized) { - surface.visible = false; - dockModel.append({ source: surface }); - } else { - surface.visible = true; - - if (surface.effectiveVisible) - Helper.activatedSurface = waylandSurface - } - } else { // if not mapped - if (waylandSurface.isMinimized) { - // mapped becomes false but not pendingDestroy - dockModel.removeSurface(surface) - } - - if (!waylandSurface.WaylandSocket.rootSocket.enabled) { - surface.visible = false; - } else { - // do animation for window close - closeAnimation.parent = surface.parent - closeAnimation.anchors.fill = surface - closeAnimation.sourceComponent = closeAnimationComponent - closeAnimation.item.start(surface) - } - } - } - - function doDestroy() { - pendingDestroy = true - - if (!surface.visible || !closeAnimation.active) { - if (waylandSurface.isMinimized) { - // mapped becomes false and pendingDestroy - dockModel.removeSurface(surface) - } - - creator.destroyObject(surface) - return - } - - // unbind some properties - mapped = false - surface.states = null - surface.transitions = null - } - - function getPrimaryOutputItem() { - let output = waylandSurface.surface.primaryOutput - if (!output) - return null - return output.OutputItem.item - } - - function updateOutputCoordMapper() { - let output = getPrimaryOutputItem() - if (!output) - return - - root.output = output - root.outputCoordMapper = surface.CoordMapper.helper.get(output) - } - - function cancelMinimize () { - if (waylandSurface.isResizeing) - return - - if (!waylandSurface.isMinimized) - return - - Helper.activatedSurface = waylandSurface - - surface.visible = true; - - dockModel.removeSurface(surface) - waylandSurface.setMinimize(false) - } - - Connections { - id: connOfSurface - - target: waylandSurface - ignoreUnknownSignals: true - - function onActivateChanged() { - if (waylandSurface.isActivated) { - WaylibHelper.itemStackToTop(surface) - if (surface.effectiveVisible) - surface.forceActiveFocus() - } else { - surface.focus = false - } - } - - function onRequestMove(seat, serial) { - if (waylandSurface.isMaximized) - return - - if (!surface.effectiveVisible) - return - - Helper.startMove(waylandSurface, surface, seat, serial) - } - - function onRequestResize(seat, edges, serial) { - if (waylandSurface.isMaximized) - return - - if (!surface.effectiveVisible) - return - - Helper.startResize(waylandSurface, surface, seat, edges, serial) - } - - function rectMarginsRemoved(rect, left, top, right, bottom) { - rect.x += left - rect.y += top - rect.width -= (left + right) - rect.height -= (top + bottom) - return rect - } - - function onRequestMaximize() { - if (waylandSurface.isResizeing) - return - - if (waylandSurface.isMaximized) - return - - if (!surface.effectiveVisible) - return - - updateOutputCoordMapper() - waylandSurface.setMaximize(true) - } - - function onRequestCancelMaximize() { - if (waylandSurface.isResizeing) - return - - if (!waylandSurface.isMaximized) - return - - if (!surface.effectiveVisible) - return - - waylandSurface.setMaximize(false) - } - - function onRequestMinimize() { - if (waylandSurface.isResizeing) - return - - if (waylandSurface.isMinimized) - return - - if (!surface.effectiveVisible) - return - - surface.focus = false; - if (Helper.activeSurface === surface) - Helper.activeSurface = null; - - surface.visible = false; - dockModel.append({ source: surface }); - waylandSurface.setMinimize(true) - } - - function onRequestCancelMinimize() { - if (!surface.effectiveVisible) - return - - cancelMinimize(); - } - - function onRequestFullscreen() { - if (waylandSurface.isResizeing) - return - - if (waylandSurface.isFullScreen) - return - - if (!surface.effectiveVisible) - return - - updateOutputCoordMapper() - waylandSurface.setFullScreen(true) - } - - function onRequestCancelFullscreen() { - if (waylandSurface.isResizeing) - return - - if (!waylandSurface.isFullScreen) - return - - if (!surface.effectiveVisible) - return - - waylandSurface.setFullScreen(false) - } - } - - Component.onCompleted: { - if (waylandSurface.isMaximized) { - updateOutputCoordMapper() - } - } -} diff --git a/examples/tinywl/StackWorkspace.qml b/examples/tinywl/StackWorkspace.qml deleted file mode 100644 index 7eb7e593..00000000 --- a/examples/tinywl/StackWorkspace.qml +++ /dev/null @@ -1,431 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import QtQuick.Controls -import Waylib.Server -import Tinywl - -Item { - id: root - - function getSurfaceItemFromWaylandSurface(surface) { - let finder = function(props) { - if (!props.waylandSurface) - return false - // surface is WToplevelSurface or WSurfce - if (props.waylandSurface === surface || props.waylandSurface.surface === surface) - return true - } - - let toplevel = Helper.xdgShellCreator.getIf(toplevelComponent, finder) - if (toplevel) { - return { - shell: toplevel, - item: toplevel, - type: "toplevel" - } - } - - let popup = Helper.xdgShellCreator.getIf(popupComponent, finder) - if (popup) { - return { - shell: popup, - item: popup.xdgSurface, - type: "popup" - } - } - - let layer = Helper.layerShellCreator.getIf(layerComponent, finder) - if (layer) { - return { - shell: layer, - item: layer.surfaceItem, - type: "layer" - } - } - - let xwayland = Helper.xwaylandCreator.getIf(xwaylandComponent, finder) - if (xwayland) { - return { - shell: xwayland, - item: xwayland, - type: "xwayland" - } - } - - return null - } - - MiniDock { - id: dock - anchors { - top: parent.top - left: parent.left - bottom: parent.bottom - margins: 8 - } - width: 250 - } - - DynamicCreatorComponent { - id: toplevelComponent - creator: Helper.xdgShellCreator - chooserRole: "type" - chooserRoleValue: "toplevel" - autoDestroy: false - - onObjectRemoved: function (obj) { - obj.doDestroy() - } - - // TODO: Support server decoration - XdgSurface { - id: toplevelSurfaceItem - property var doDestroy: helper.doDestroy - property var cancelMinimize: helper.cancelMinimize - property int outputCounter: 0 - - topPadding: decoration.visible ? decoration.topMargin : 0 - bottomPadding: decoration.visible ? decoration.bottomMargin : 0 - leftPadding: decoration.visible ? decoration.leftMargin : 0 - rightPadding: decoration.visible ? decoration.rightMargin : 0 - - WindowDecoration { - id: decoration - anchors.fill: parent - z: SurfaceItem.ZOrder.ContentItem - 1 - surface: waylandSurface - - Connections { - target: Helper.xdgDecorationManager - - function onSurfaceModeChanged(surface, mode) { - if (waylandSurface === surface) - visible = (mode !== XdgDecorationManager.Client) - } - } - - Component.onCompleted: { - visible = Helper.xdgDecorationManager.modeBySurface(surface.surface) !== XdgDecorationManager.Client - } - } - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - waylandSurface.surface.enterOutput(output) - Helper.onSurfaceEnterOutput(waylandSurface, toplevelSurfaceItem, output) - outputCounter++ - - if (outputCounter == 1) { - let outputDelegate = output.OutputItem.item - toplevelSurfaceItem.x = outputDelegate.x - + Helper.getLeftExclusiveMargin(waylandSurface) - + 10 - toplevelSurfaceItem.y = outputDelegate.y - + Helper.getTopExclusiveMargin(waylandSurface) - + 10 - } - } - onLeaveOutput: function(output) { - waylandSurface.surface.leaveOutput(output) - Helper.onSurfaceLeaveOutput(waylandSurface, toplevelSurfaceItem, output) - outputCounter-- - } - } - - StackToplevelHelper { - id: helper - surface: toplevelSurfaceItem - waylandSurface: toplevelSurfaceItem.waylandSurface - dockModel: dock.model - creator: toplevelComponent - decoration: decoration - } - - states: [ - State { - name: "maximize" - when: helper.isMaximize - PropertyChanges { - restoreEntryValues: true - target: toplevelSurfaceItem - x: helper.getMaximizeX() - y: helper.getMaximizeY() - width: helper.getMaximizeWidth() - height: helper.getMaximizeHeight() - } - }, - State { - name: "fullscreen" - when: helper.isFullScreen - PropertyChanges { - restoreEntryValues: true - target: toplevelSurfaceItem - x: helper.getFullscreenX() - y: helper.getFullscreenY() - z: 100 + 1 // LayerType.Overlay + 1 - width: helper.getFullscreenWidth() - height: helper.getFullscreenHeight() - } - } - ] - } - } - - DynamicCreatorComponent { - id: popupComponent - creator: Helper.xdgShellCreator - chooserRole: "type" - chooserRoleValue: "popup" - - Popup { - id: popup - - required property WaylandXdgSurface waylandSurface - property string type - - property alias xdgSurface: popupSurfaceItem - property var parentItem: root.getSurfaceItemFromWaylandSurface(waylandSurface.parentSurface) - - parent: parentItem ? parentItem.item : root - visible: parentItem && parentItem.item.effectiveVisible - && waylandSurface.surface.mapped && waylandSurface.WaylandSocket.rootSocket.enabled - x: { - let retX = 0 // X coordinate relative to parent - let minX = 0 - let maxX = root.width - xdgSurface.width - if (!parentItem) { - retX = popupSurfaceItem.implicitPosition.x - if (retX > maxX) - retX = maxX - if (retX < minX) - retX = minX - } else { - retX = popupSurfaceItem.implicitPosition.x / parentItem.item.surfaceSizeRatio + parentItem.item.contentItem.x - let parentX = parent.mapToItem(root, 0, 0).x - if (retX + parentX > maxX) { - if (parentItem.type === "popup") - retX = retX - xdgSurface.width - parent.width - else - retX = maxX - parentX - } - if (retX + parentX < minX) - retX = minX - parentX - } - return retX - } - y: { - let retY = 0 // Y coordinate relative to parent - let minY = 0 - let maxY = root.height - xdgSurface.height - if (!parentItem) { - retY = popupSurfaceItem.implicitPosition.y - if (retY > maxY) - retY = maxY - if (retY < minY) - retY = minY - } else { - retY = popupSurfaceItem.implicitPosition.y / parentItem.item.surfaceSizeRatio + parentItem.item.contentItem.y - let parentY = parent.mapToItem(root, 0, 0).y - if (retY + parentY > maxY) - retY = maxY - parentY - if (retY + parentY < minY) - retY = minY - parentY - } - return retY - } - padding: 0 - background: null - closePolicy: Popup.NoAutoClose - - XdgSurface { - id: popupSurfaceItem - waylandSurface: popup.waylandSurface - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - waylandSurface.surface.enterOutput(output) - Helper.onSurfaceEnterOutput(waylandSurface, popupSurfaceItem, output) - } - onLeaveOutput: function(output) { - waylandSurface.surface.leaveOutput(output) - Helper.onSurfaceLeaveOutput(waylandSurface, popupSurfaceItem, output) - } - } - } - } - } - - DynamicCreatorComponent { - id: layerComponent - creator: Helper.layerShellCreator - autoDestroy: false - - onObjectRemoved: function (obj) { - obj.doDestroy() - } - - LayerSurface { - id: layerSurface - creator: layerComponent - } - } - - DynamicCreatorComponent { - id: xwaylandComponent - creator: Helper.xwaylandCreator - autoDestroy: false - - onObjectRemoved: function (obj) { - obj.doDestroy() - } - - XWaylandSurfaceItem { - id: xwaylandSurfaceItem - - required property XWaylandSurface waylandSurface - property var doDestroy: helper.doDestroy - property var cancelMinimize: helper.cancelMinimize - property var surfaceParent: root.getSurfaceItemFromWaylandSurface(waylandSurface.parentXWaylandSurface) - property int outputCounter: 0 - - shellSurface: waylandSurface - parentSurfaceItem: surfaceParent ? surfaceParent.item : null - z: waylandSurface.bypassManager ? 1 : 0 // TODO: make to enum type - positionMode: { - if (!xwaylandSurfaceItem.effectiveVisible) - return XWaylandSurfaceItem.ManualPosition - - return (Helper.movingItem === xwaylandSurfaceItem || resizeMode === SurfaceItem.SizeToSurface) - ? XWaylandSurfaceItem.PositionToSurface - : XWaylandSurfaceItem.PositionFromSurface - } - - topPadding: decoration.enable ? decoration.topMargin : 0 - bottomPadding: decoration.enable ? decoration.bottomMargin : 0 - leftPadding: decoration.enable ? decoration.leftMargin : 0 - rightPadding: decoration.enable ? decoration.rightMargin : 0 - - surfaceSizeRatio: { - const po = waylandSurface.surface.primaryOutput - if (!po) - return 1.0 - if (bufferScale >= po.scale) - return 1.0 - return po.scale / bufferScale - } - - onEffectiveVisibleChanged: { - if (xwaylandSurfaceItem.effectiveVisible) - xwaylandSurfaceItem.move(XWaylandSurfaceItem.PositionToSurface) - } - - // TODO: ensure the event to WindowDecoration before WSurfaceItem::eventItem on surface's edges - // maybe can use the SinglePointHandler? - WindowDecoration { - id: decoration - - property bool enable: !waylandSurface.bypassManager - && waylandSurface.decorationsType !== XWaylandSurface.DecorationsNoBorder - - anchors.fill: parent - z: SurfaceItem.ZOrder.ContentItem - 1 - visible: enable - surface: waylandSurface - } - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - if (xwaylandSurfaceItem.waylandSurface.surface) - xwaylandSurfaceItem.waylandSurface.surface.enterOutput(output); - Helper.onSurfaceEnterOutput(waylandSurface, xwaylandSurfaceItem, output) - - outputCounter++ - - if (outputCounter == 1) { - let outputDelegate = output.OutputItem.item - xwaylandSurfaceItem.x = outputDelegate.x - + Helper.getLeftExclusiveMargin(waylandSurface) - + 10 - xwaylandSurfaceItem.y = outputDelegate.y - + Helper.getTopExclusiveMargin(waylandSurface) - + 10 - } - } - onLeaveOutput: function(output) { - if (xwaylandSurfaceItem.waylandSurface.surface) - xwaylandSurfaceItem.waylandSurface.surface.leaveOutput(output); - Helper.onSurfaceLeaveOutput(waylandSurface, xwaylandSurfaceItem, output) - outputCounter-- - } - } - - StackToplevelHelper { - id: helper - surface: xwaylandSurfaceItem - waylandSurface: xwaylandSurfaceItem.waylandSurface - dockModel: dock.model - creator: xwaylandComponent - decoration: decoration - } - - states: [ - State { - name: "maximize" - when: helper.isMaximize - PropertyChanges { - restoreEntryValues: true - target: xwaylandSurfaceItem - x: helper.getMaximizeX() - y: helper.getMaximizeY() - width: helper.getMaximizeWidth() - height: helper.getMaximizeHeight() - positionMode: XWaylandSurfaceItem.PositionToSurface - } - }, - State { - name: "fullscreen" - when: helper.isFullScreen - PropertyChanges { - restoreEntryValues: true - target: xwaylandSurfaceItem - x: helper.getFullscreenX() - y: helper.getFullscreenY() - z: 100 + 1 // LayerType.Overlay + 1 - width: helper.getFullscreenWidth() - height: helper.getFullscreenHeight() - positionMode: XWaylandSurfaceItem.PositionToSurface - } - PropertyChanges { - restoreEntryValues: true - target: decoration - enable: false - } - } - ] - } - } - - DynamicCreatorComponent { - id: inputPopupComponent - creator: Helper.inputPopupCreator - - InputPopupSurface { - required property WaylandInputPopupSurface popupSurface - - parent: getSurfaceItemFromWaylandSurface(popupSurface.parentSurface) - id: inputPopupSurface - shellSurface: popupSurface - } - } -} diff --git a/examples/tinywl/SurfaceContent.qml b/examples/tinywl/SurfaceContent.qml new file mode 100644 index 00000000..2fdbe0fe --- /dev/null +++ b/examples/tinywl/SurfaceContent.qml @@ -0,0 +1,42 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import Waylib.Server +import Tinywl + +Item { + id: root + + required property SurfaceItem surface + readonly property SurfaceWrapper wrapper: surface?.parent ?? null + readonly property real cornerRadius: wrapper?.radius ?? cornerRadius + + anchors.fill: parent + SurfaceItemContent { + id: content + surface: root.surface?.surface ?? null + anchors.fill: parent + opacity: effectLoader.active ? 0 : 1 + live: root.surface && !(root.surface.flags & SurfaceItem.NonLive) + smooth: root.surface?.smooth ?? true + } + + Loader { + id: effectLoader + + anchors.fill: parent + active: { + if (!root.wrapper) + return false; + return cornerRadius > 0 && !root.wrapper.noCornerRadius; + } + + sourceComponent: RoundedClipEffect { + sourceItem: content + radius: cornerRadius + targetRect: Qt.rect(-surface?.leftPadding ?? 0, -surface?.topPadding ?? 0, + root.surface?.width ?? 0, root.surface?.height ?? 0) + } + } +} diff --git a/examples/tinywl/TaskBar.qml b/examples/tinywl/TaskBar.qml new file mode 100644 index 00000000..9ab47efc --- /dev/null +++ b/examples/tinywl/TaskBar.qml @@ -0,0 +1,61 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import Waylib.Server +import Tinywl + +ListView { + required property QtObject output + readonly property OutputItem outputItem: output.outputItem + + width: 250 + leftMargin + rightMargin + height: Math.min(outputItem.height, contentHeight) + topMargin + bottomMargin + x: outputItem.x + y: (outputItem.height - height) / 2 + spacing: 80 + leftMargin: 40 + rightMargin: 40 + topMargin: 40 + bottomMargin: 40 + model: output.minimizedSurfaces + delegate: Item { + required property SurfaceWrapper surface + + width: proxy.width + height: proxy.height + + SurfaceProxy { + id: proxy + live: true + surface: parent.surface + maxSize: Qt.size(250, 150) + } + + MouseArea { + anchors.fill: parent + onClicked: parent.surface.requestCancelMinimize() + } + } + + transform: Rotation { + angle: 30 + axis { + x: 0 + y: 1 + z: 0 + } + + origin { + x: width / 2 + y: height / 2 + } + } + + layer { + enabled: true + live: true + mipmap: true + smooth: true + } +} diff --git a/examples/tinywl/TiledToplevelHelper.qml b/examples/tinywl/TiledToplevelHelper.qml deleted file mode 100644 index b387b974..00000000 --- a/examples/tinywl/TiledToplevelHelper.qml +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import QtQuick.Controls -import Waylib.Server -import Tinywl - -Item { - required property SurfaceItem surface - required property ToplevelSurface waylandSurface - required property DynamicCreatorComponent creator - - property OutputItem output - property CoordMapper outputCoordMapper - - property bool mapped: waylandSurface.surface - && waylandSurface.surface.mapped - && waylandSurface.WaylandSocket.rootSocket.enabled - property bool pendingDestroy: false - - OpacityAnimator { - id: hideAnimation - duration: 300 - target: surface - from: 1 - to: 0 - - onStopped: { - surface.visible = false - if (pendingDestroy) - creator.destroyObject(surface) - } - } - - Connections { - target: surface - - function onEffectiveVisibleChanged() { - if (surface.effectiveVisible) { - // Apply the WSurfaceItem's size to wl_surface - surface.resize(SurfaceItem.SizeToSurface) - surface.resizeMode = SurfaceItem.SizeToSurface - - if (waylandSurface && waylandSurface.isActivated) - surface.forceActiveFocus() - } else { - surface.resizeMode = SurfaceItem.ManualResize - } - } - } - - onMappedChanged: { - if (pendingDestroy) - return - - if (mapped && surface.effectiveVisible) - Helper.activatedSurface = waylandSurface - - // When Socket is enabled and mapped becomes false, set visible - // after hideAnimation completeļ¼Œ Otherwise set visible directly. - if (mapped || !waylandSurface.WaylandSocket.rootSocket.enabled) { - surface.visible = mapped - return - } - - // do animation for window close - hideAnimation.start() - } - - function doDestroy() { - pendingDestroy = true - - if (!surface.visible || !hideAnimation.running) { - creator.destroyObject(surface) - return - } - - // unbind some properties - mapped = visible - } - - Connections { - target: waylandSurface - - function onActivateChanged() { - if (waylandSurface.isActivated) { - if (surface.effectiveVisible) - surface.forceActiveFocus() - } else { - surface.focus = false - } - } - } -} diff --git a/examples/tinywl/TiledWorkspace.qml b/examples/tinywl/TiledWorkspace.qml deleted file mode 100644 index 40b39cf0..00000000 --- a/examples/tinywl/TiledWorkspace.qml +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Waylib.Server - -Item { - id: root - - function getSurfaceItemFromWaylandSurface(surface) { - let finder = function(props) { - if (!props.waylandSurface) - return false - // surface is WToplevelSurface or WSurfce - if (props.waylandSurface === surface || props.waylandSurface.surface === surface) - return true - } - - let toplevel = Helper.xdgShellCreator.getIf(toplevelComponent, finder) - if (toplevel) { - return { - shell: toplevel, - item: toplevel, - type: "toplevel" - } - } - - let popup = Helper.xdgShellCreator.getIf(popupComponent, finder) - if (popup) { - return { - shell: popup, - item: popup.xdgSurface, - type: "popup" - } - } - - let layer = Helper.layerShellCreator.getIf(layerComponent, finder) - if (layer) { - return { - shell: layer, - item: layer.surfaceItem, - type: "layer" - } - } - - let xwayland = Helper.xwaylandCreator.getIf(xwaylandComponent, finder) - if (xwayland) { - return { - shell: xwayland, - item: xwayland, - type: "xwayland" - } - } - - return null - } - - GridLayout { - anchors.fill: parent - columns: Math.floor(root.width / 1920 * 4) - - DynamicCreatorComponent { - id: toplevelComponent - creator: Helper.xdgShellCreator - chooserRole: "type" - chooserRoleValue: "toplevel" - autoDestroy: false - - onObjectRemoved: function (obj) { - obj.doDestroy() - } - - XdgSurface { - id: toplevelSurfaceItem - - property var doDestroy: helper.doDestroy - - resizeMode: SurfaceItem.SizeToSurface - z: (waylandSurface && waylandSurface.isActivated) ? 1 : 0 - - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumWidth: Math.max(toplevelSurfaceItem.minimumSize.width, 100) - Layout.minimumHeight: Math.max(toplevelSurfaceItem.minimumSize.height, 50) - Layout.maximumWidth: toplevelSurfaceItem.maximumSize.width - Layout.maximumHeight: toplevelSurfaceItem.maximumSize.height - Layout.horizontalStretchFactor: 1 - Layout.verticalStretchFactor: 1 - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - waylandSurface.surface.enterOutput(output) - Helper.onSurfaceEnterOutput(waylandSurface, toplevelSurfaceItem, output) - } - onLeaveOutput: function(output) { - waylandSurface.surface.leaveOutput(output) - Helper.onSurfaceLeaveOutput(waylandSurface, toplevelSurfaceItem, output) - } - } - - TiledToplevelHelper { - id: helper - - surface: toplevelSurfaceItem - waylandSurface: toplevelSurfaceItem.waylandSurface - creator: toplevelComponent - } - } - } - - DynamicCreatorComponent { - id: popupComponent - creator: Helper.xdgShellCreator - chooserRole: "type" - chooserRoleValue: "popup" - - Popup { - id: popup - - required property WaylandXdgSurface waylandSurface - property string type - - property alias xdgSurface: popupSurfaceItem - property var parentItem: root.getSurfaceItemFromWaylandSurface(waylandSurface.parentSurface) - - parent: parentItem ? parentItem.item : root - visible: parentItem && parentItem.item.effectiveVisible - && waylandSurface.surface.mapped && waylandSurface.WaylandSocket.rootSocket.enabled - x: { - let retX = 0 // X coordinate relative to parent - let minX = 0 - let maxX = root.width - xdgSurface.width - if (!parentItem) { - retX = popupSurfaceItem.implicitPosition.x - if (retX > maxX) - retX = maxX - if (retX < minX) - retX = minX - } else { - retX = popupSurfaceItem.implicitPosition.x / parentItem.item.surfaceSizeRatio + parentItem.item.contentItem.x - let parentX = parent.mapToItem(root, 0, 0).x - if (retX + parentX > maxX) { - if (parentItem.type === "popup") - retX = retX - xdgSurface.width - parent.width - else - retX = maxX - parentX - } - if (retX + parentX < minX) - retX = minX - parentX - } - return retX - } - y: { - let retY = 0 // Y coordinate relative to parent - let minY = 0 - let maxY = root.height - xdgSurface.height - if (!parentItem) { - retY = popupSurfaceItem.implicitPosition.y - if (retY > maxY) - retY = maxY - if (retY < minY) - retY = minY - } else { - retY = popupSurfaceItem.implicitPosition.y / parentItem.item.surfaceSizeRatio + parentItem.item.contentItem.y - let parentY = parent.mapToItem(root, 0, 0).y - if (retY + parentY > maxY) - retY = maxY - parentY - if (retY + parentY < minY) - retY = minY - parentY - } - return retY - } - padding: 0 - background: null - closePolicy: Popup.NoAutoClose - - XdgSurface { - id: popupSurfaceItem - waylandSurface: popup.waylandSurface - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - waylandSurface.surface.enterOutput(output) - Helper.onSurfaceEnterOutput(waylandSurface, popupSurfaceItem, output) - } - onLeaveOutput: function(output) { - waylandSurface.surface.leaveOutput(output) - Helper.onSurfaceLeaveOutput(waylandSurface, popupSurfaceItem, output) - } - } - } - } - } - - DynamicCreatorComponent { - id: xwaylandComponent - creator: Helper.xwaylandCreator - autoDestroy: false - - onObjectRemoved: function (obj) { - obj.doDestroy() - } - - XWaylandSurfaceItem { - id: xwaylandSurfaceItem - - required property XWaylandSurface waylandSurface - property var doDestroy: helper.doDestroy - - shellSurface: waylandSurface - resizeMode: SurfaceItem.SizeToSurface - // TODO: Support popup/menu - positionMode: xwaylandSurfaceItem.effectiveVisible ? XWaylandSurfaceItem.PositionToSurface : XWaylandSurfaceItem.ManualPosition - z: (waylandSurface && waylandSurface.isActivated) ? 1 : 0 - - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumWidth: Math.max(xwaylandSurfaceItem.minimumSize.width, 100) - Layout.minimumHeight: Math.max(xwaylandSurfaceItem.minimumSize.height, 50) - Layout.maximumWidth: xwaylandSurfaceItem.maximumSize.width - Layout.maximumHeight: xwaylandSurfaceItem.maximumSize.height - Layout.horizontalStretchFactor: 1 - Layout.verticalStretchFactor: 1 - - OutputLayoutItem { - anchors.fill: parent - layout: Helper.outputLayout - - onEnterOutput: function(output) { - if (xwaylandSurfaceItem.waylandSurface.surface) - xwaylandSurfaceItem.waylandSurface.surface.enterOutput(output); - Helper.onSurfaceEnterOutput(waylandSurface, xwaylandSurfaceItem, output) - } - onLeaveOutput: function(output) { - if (xwaylandSurfaceItem.waylandSurface.surface) - xwaylandSurfaceItem.waylandSurface.surface.leaveOutput(output); - Helper.onSurfaceLeaveOutput(waylandSurface, xwaylandSurfaceItem, output) - } - } - - TiledToplevelHelper { - id: helper - - surface: xwaylandSurfaceItem - waylandSurface: surface.waylandSurface - creator: xwaylandComponent - } - } - } - } - - DynamicCreatorComponent { - id: layerComponent - creator: Helper.layerShellCreator - autoDestroy: false - - onObjectRemoved: function (obj) { - obj.doDestroy() - } - - LayerSurface { - id: layerSurface - creator: layerComponent - } - } - - DynamicCreatorComponent { - id: inputPopupComponent - creator: Helper.inputPopupCreator - - InputPopupSurface { - required property WaylandInputPopupSurface popupSurface - - parent: getSurfaceItemFromWaylandSurface(popupSurface.parentSurface) - id: inputPopupSurface - shellSurface: popupSurface - } - } -} diff --git a/examples/tinywl/TitleBar.qml b/examples/tinywl/TitleBar.qml new file mode 100644 index 00000000..2dc9cecf --- /dev/null +++ b/examples/tinywl/TitleBar.qml @@ -0,0 +1,93 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import Waylib.Server +import Tinywl + +Item { + id: root + + required property SurfaceWrapper surface + readonly property SurfaceItem surfaceItem: surface.surfaceItem + + height: 30 + width: surfaceItem.width + + HoverHandler { + // block hover events to resizing mouse area, avoid cursor change + cursorShape: Qt.ArrowCursor + } + + //Normal mouse click + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onTapped: (eventPoint, button) => { + if (button === Qt.RightButton) { + surface.requestShowWindowMenu(eventPoint.position) + } else { + Helper.activeSurface(surface) + } + } + onPressedChanged: { + if (pressed) + surface.requestMove() + } + + onDoubleTapped: (_, button) => { + if (button === Qt.LeftButton) { + surface.requestToggleMaximize() + } + } + } + + //Touch screen click + TapHandler { + acceptedButtons: Qt.NoButton + acceptedDevices: PointerDevice.TouchScreen + onDoubleTapped: surface.requestToggleMaximize() + onLongPressed: surface.requestShowWindowMenu(point.position) + } + + Rectangle { + id: titlebar + anchors.fill: parent + color: surface.shellSurface.isActivated ? "white" : "gray" + + Row { + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 8 + } + + Button { + width: titlebar.height + text: "-" + onClicked: surface.requestMinimize() + } + Button { + width: titlebar.height + text: "O" + onClicked: surface.requestToggleMaximize() + } + Button { + width: titlebar.height + text: "X" + onClicked: surface.requestClose() + } + } + } + + Loader { + anchors.fill: parent + active: surface.radius > 0 && !surface.noCornerRadius + sourceComponent: RoundedClipEffect { + anchors.fill: parent + sourceItem: titlebar + radius: surface.radius + targetRect: Qt.rect(-root.x, -root.y, surfaceItem.width, surfaceItem.height) + } + } +} diff --git a/examples/tinywl/WindowDecoration.qml b/examples/tinywl/WindowDecoration.qml deleted file mode 100644 index 06839cf5..00000000 --- a/examples/tinywl/WindowDecoration.qml +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import QtQuick.Controls -import Waylib.Server - -Item { - id: root - - signal requestMove - signal requestMinimize - signal requestToggleMaximize(var max) - signal requestClose - signal requestResize(var edges) - - readonly property real topMargin: titlebar.height - readonly property real bottomMargin: 0 - readonly property real leftMargin: 0 - readonly property real rightMargin: 0 - required property ToplevelSurface surface - - MouseArea { - property int edges: 0 - - anchors { - fill: parent - margins: -10 - } - - hoverEnabled: true - Cursor.shape: { - switch(edges) { - case Qt.TopEdge: - return Waylib.CursorShape.TopSide - case Qt.RightEdge: - return Waylib.CursorShape.RightSide - case Qt.BottomEdge: - return Waylib.CursorShape.BottomSide - case Qt.LeftEdge: - return Waylib.CursorShape.LeftSide - case Qt.TopEdge | Qt.LeftEdge: - return Waylib.CursorShape.TopLeftCorner - case Qt.TopEdge | Qt.RightEdge: - return Waylib.CursorShape.TopRightCorner - case Qt.BottomEdge | Qt.LeftEdge: - return Waylib.CursorShape.BottomLeftCorner - case Qt.BottomEdge | Qt.RightEdge: - return Waylib.CursorShape.BottomRightCorner - } - - return Qt.ArrowCursor; - } - - onPositionChanged: function (event) { - edges = WaylibHelper.getEdges(Qt.rect(0, 0, width, height), Qt.point(event.x, event.y), 10) - } - - onPressed: function (event) { - // Maybe missing onPositionChanged when use touchscreen - edges = WaylibHelper.getEdges(Qt.rect(0, 0, width, height), Qt.point(event.x, event.y), 10) - Helper.activatedSurface = surface - if (edges) - root.requestResize(edges) - } - } - - Rectangle { - id: titlebar - anchors.top: parent.top - width: parent.width - height: 30 - - MouseArea { - anchors.fill: parent - Cursor.shape: pressed ? Waylib.CursorShape.Grabbing : Qt.ArrowCursor - - onPressed: { - root.requestMove() - } - } - - Row { - anchors { - verticalCenter: parent.verticalCenter - right: parent.right - rightMargin: 8 - } - - Button { - text: "Min" - onClicked: root.requestMinimize() - } - Button { - text: "Max" - onClicked: { - const max = (text === "Max") - root.requestToggleMaximize(max) - - if (max) { - text = "Restore" - } else { - text = "Max" - } - } - } - Button { - text: "Close" - onClicked: root.requestClose() - } - } - } -} diff --git a/examples/tinywl/WindowMenu.qml b/examples/tinywl/WindowMenu.qml new file mode 100644 index 00000000..c9cb2412 --- /dev/null +++ b/examples/tinywl/WindowMenu.qml @@ -0,0 +1,75 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import Waylib.Server +import Tinywl + +Menu { + id: menu + + property SurfaceWrapper surface: null + + function showWindowMenu(surface, pos) { + menu.surface = surface + menu.parent = surface + menu.popup(pos) + } + + MenuItem { + text: qsTr("Minimize") + onTriggered: surface.requestMinimize() + } + + MenuItem { + text: surface.surfaceState === SurfaceWrapper.State.Maximized ? qsTr("Unmaximize") : qsTr("Maximize") + onTriggered: surface.requestToggleMaximize() + } + + MenuItem { + text: qsTr("Move") + onTriggered: surface.requestMove() + } + + MenuItem { + text: qsTr("Resize") + onTriggered: Helper.fakePressSurfaceBottomRightToReszie(surface) + } + + MenuItem { + text: surface.alwaysOnTop ? qsTr("Not always on Top") : qsTr("Always on Top") + onTriggered: surface.alwaysOnTop = !surface.alwaysOnTop; + } + + MenuItem { + text: surface.showOnAllWorkspace ? qsTr("Only on Current Workspace") : qsTr("Always on Visible Workspace") + onTriggered: { + if (surface.showOnAllWorkspace) { + // Move to current workspace + Helper.workspace.addSurface(surface, Helper.workspace.currentIndex) + } else { + // Move to workspace 0, which is always visible + Helper.workspace.addSurface(surface, 0) + } + } + } + + MenuItem { + text: qsTr("Move to Left Work Space") + enabled: surface.workspaceId !== 1 && !surface.showOnAllWorkspace + onTriggered: Helper.workspace.addSurface(surface, surface.workspaceId - 1) + } + + MenuItem { + text: qsTr("Move to Right Work Space") + enabled: surface.workspaceId !== Helper.workspace.count - 1 && !surface.showOnAllWorkspace + onTriggered: Helper.workspace.addSurface(surface, surface.workspaceId + 1) + } + + MenuItem { + text: qsTr("Close") + onTriggered: surface.shellSurface.close() + } +} + diff --git a/examples/tinywl/WorkspaceProxy.qml b/examples/tinywl/WorkspaceProxy.qml new file mode 100644 index 00000000..fb831d66 --- /dev/null +++ b/examples/tinywl/WorkspaceProxy.qml @@ -0,0 +1,54 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import Tinywl + +Item { + required property WorkspaceModel workspace + required property QtObject output + + width: output.outputItem.width + height: output.outputItem.height + clip: true + + Repeater { + model: workspace + delegate: Loader { + id: loader + + required property SurfaceWrapper surface + required property int orderIndex + + x: surface.x - output.outputItem.x + y: surface.y - output.outputItem.y + z: orderIndex + active: surface.ownsOutput === output + && surface.surfaceState !== SurfaceWrapper.State.Minimized + sourceComponent: SurfaceProxy { + surface: loader.surface + fullProxy: true + } + } + } + + Repeater { + model: Helper.workspace.showOnAllWorkspaceModel + delegate: Loader { + id: loader + + required property SurfaceWrapper surface + required property int orderIndex + + x: surface.x - output.outputItem.x + y: surface.y - output.outputItem.y + z: orderIndex + active: surface.ownsOutput === output + && surface.surfaceState !== SurfaceWrapper.State.Minimized + sourceComponent: SurfaceProxy { + surface: loader.surface + fullProxy: true + } + } + } +} diff --git a/examples/tinywl/WorkspaceSwitcher.qml b/examples/tinywl/WorkspaceSwitcher.qml new file mode 100644 index 00000000..7f3dae43 --- /dev/null +++ b/examples/tinywl/WorkspaceSwitcher.qml @@ -0,0 +1,120 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import Tinywl + +Item { + id: root + + required property WorkspaceModel from + required property WorkspaceModel to + readonly property Item workspace: parent + readonly property WorkspaceModel leftWorkspace: { + if (!from || !to) + return null; + return from.index > to.index ? to : from; + } + readonly property WorkspaceModel rightWorkspace: { + if (!from || !to) + return null; + return from.index > to.index ? from : to; + } + property int duration: 200 * Helper.animationSpeed + + anchors.fill: parent + z: 1 + + Repeater { + model: workspace.root.outputModel + delegate: Item { + id: rootItem + + required property QtObject output + readonly property PrimaryOutput outputItem: output.outputItem + + x: outputItem.x + y: outputItem.y + width: outputItem.width + height: outputItem.height + + Row { + id: wallpapers + spacing: workspaces.spacing + x: workspaces.x + parent: rootItem.outputItem.parent + z: rootItem.outputItem.z - 1 + + Wallpaper { + userId: Helper.currentUserId + output: rootItem.outputItem.output + workspace: root.leftWorkspace + width: rootItem.outputItem.width + height: rootItem.outputItem.height + } + + Wallpaper { + userId: Helper.currentUserId + output: rootItem.outputItem.output + workspace: root.rightWorkspace + width: rootItem.outputItem.width + height: rootItem.outputItem.height + } + } + + Row { + id: workspaces + + spacing: 30 + + WorkspaceProxy { + workspace: root.leftWorkspace + output: rootItem.output + } + + WorkspaceProxy { + workspace: root.rightWorkspace + output: rootItem.output + } + } + + ParallelAnimation { + id: animation + + XAnimator { + id: wallpapersAnimation + target: wallpapers + duration: root.duration + } + + XAnimator { + id: workspacesAnimation + target: workspaces + duration: root.duration + } + + onFinished: { + rootItem.outputItem.wallpaperVisible = true; + Helper.workspace.showOnAllWorkspaceModel.visible = true; + root.workspace.current = root.to; + } + } + + Component.onCompleted: { + if (root.from === root.leftWorkspace) { + wallpapersAnimation.from = 0; + wallpapersAnimation.to = -workspaces.width + outputItem.width; + } else { + wallpapersAnimation.from = -workspaces.width + outputItem.width; + wallpapersAnimation.to = 0; + } + + workspacesAnimation.from = wallpapersAnimation.from; + workspacesAnimation.to = wallpapersAnimation.to; + + rootItem.outputItem.wallpaperVisible = false; + animation.start(); + } + } + } +} diff --git a/examples/tinywl/XdgSurface.qml b/examples/tinywl/XdgSurface.qml deleted file mode 100644 index 6aadbfc9..00000000 --- a/examples/tinywl/XdgSurface.qml +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2023 JiDe Zhang . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -import QtQuick -import Waylib.Server - -XdgSurfaceItem { - id: surfaceItem - required property WaylandXdgSurface waylandSurface - property string type - - shellSurface: waylandSurface -} diff --git a/examples/tinywl/helper.cpp b/examples/tinywl/helper.cpp new file mode 100644 index 00000000..43770e2d --- /dev/null +++ b/examples/tinywl/helper.cpp @@ -0,0 +1,764 @@ +// Copyright (C) 2023 JiDe Zhang . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "helper.h" +#include "surfacewrapper.h" +#include "output.h" +#include "workspace.h" +#include "qmlengine.h" +#include "surfacecontainer.h" +#include "rootsurfacecontainer.h" +#include "layersurfacecontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WLR_FRACTIONAL_SCALE_V1_VERSION 1 + +Helper *Helper::m_instance = nullptr; +Helper::Helper(QObject *parent) + : WSeatEventFilter(parent) + , m_renderWindow(new WOutputRenderWindow(this)) + , m_server(new WServer(this)) + , m_surfaceContainer(new RootSurfaceContainer(m_renderWindow->contentItem())) + , m_backgroundContainer(new LayerSurfaceContainer(m_surfaceContainer)) + , m_bottomContainer(new LayerSurfaceContainer(m_surfaceContainer)) + , m_workspace(new Workspace(m_surfaceContainer)) + , m_topContainer(new LayerSurfaceContainer(m_surfaceContainer)) + , m_overlayContainer(new LayerSurfaceContainer(m_surfaceContainer)) + , m_popupContainer(new SurfaceContainer(m_surfaceContainer)) +{ + setCurrentUserId(getuid()); + + Q_ASSERT(!m_instance); + m_instance = this; + + m_renderWindow->setColor(Qt::black); + m_surfaceContainer->setFlag(QQuickItem::ItemIsFocusScope, true); +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + m_surfaceContainer->setFocusPolicy(Qt::StrongFocus); +#endif + m_backgroundContainer->setZ(RootSurfaceContainer::BackgroundZOrder); + m_bottomContainer->setZ(RootSurfaceContainer::BottomZOrder); + m_workspace->setZ(RootSurfaceContainer::NormalZOrder); + m_topContainer->setZ(RootSurfaceContainer::TopZOrder); + m_overlayContainer->setZ(RootSurfaceContainer::OverlayZOrder); + m_popupContainer->setZ(RootSurfaceContainer::PopupZOrder); +} + +Helper::~Helper() +{ + for (auto s : m_surfaceContainer->surfaces()) { + if (auto c = s->container()) + c->removeSurface(s); + } + + delete m_surfaceContainer; + Q_ASSERT(m_instance == this); + m_instance = nullptr; +} + +Helper *Helper::instance() +{ + return m_instance; +} + +QmlEngine *Helper::qmlEngine() const +{ + return qobject_cast(::qmlEngine(this)); +} + +WOutputRenderWindow *Helper::window() const +{ + return m_renderWindow; +} + +Workspace* Helper::workspace() const +{ + return m_workspace; +} + +void Helper::init() +{ + auto engine = qmlEngine(); + engine->setContextForObject(m_renderWindow, engine->rootContext()); + engine->setContextForObject(m_renderWindow->contentItem(), engine->rootContext()); + m_surfaceContainer->setQmlEngine(engine); + + m_seat = m_server->attach(); + m_seat->setEventFilter(this); + m_seat->setCursor(m_surfaceContainer->cursor()); + m_seat->setKeyboardFocusWindow(m_renderWindow); + + m_backend = m_server->attach(); + connect(m_backend, &WBackend::inputAdded, this, [this] (WInputDevice *device) { + m_seat->attachInputDevice(device); + }); + + connect(m_backend, &WBackend::inputRemoved, this, [this] (WInputDevice *device) { + m_seat->detachInputDevice(device); + }); + + auto wOutputManager = m_server->attach(); + connect(m_backend, &WBackend::outputAdded, this, [this, wOutputManager] (WOutput *output) { + allowNonDrmOutputAutoChangeMode(output); + Output *o; + if (m_mode == OutputMode::Extension || !m_surfaceContainer->primaryOutput()) { + o = Output::createPrimary(output, qmlEngine(), this); + o->outputItem()->stackBefore(m_surfaceContainer); + m_surfaceContainer->addOutput(o); + } else if (m_mode == OutputMode::Copy) { + o = Output::createCopy(output, m_surfaceContainer->primaryOutput(), qmlEngine(), this); + } + + m_outputList.append(o); + enableOutput(output); + wOutputManager->newOutput(output); + }); + + connect(m_backend, &WBackend::outputRemoved, this, [this, wOutputManager] (WOutput *output) { + auto index = indexOfOutput(output); + Q_ASSERT(index >= 0); + const auto o = m_outputList.takeAt(index); + wOutputManager->removeOutput(output); + m_surfaceContainer->removeOutput(o); + delete o; + }); + + auto *xdgShell = m_server->attach(); + m_foreignToplevel = m_server->attach(xdgShell); + auto *layerShell = m_server->attach(); + auto *xdgOutputManager = m_server->attach(m_surfaceContainer->outputLayout()); + m_windowMenu = engine->createWindowMenu(this); + + connect(xdgShell, &WXdgShell::surfaceAdded, this, [this] (WXdgSurface *surface) { + SurfaceWrapper *wrapper = nullptr; + + if (surface->isToplevel()) { + wrapper = new SurfaceWrapper(qmlEngine(), surface, SurfaceWrapper::Type::XdgToplevel); + m_foreignToplevel->addSurface(surface); + } else { + wrapper = new SurfaceWrapper(qmlEngine(), surface, SurfaceWrapper::Type::XdgPopup); + } + + wrapper->setNoDecoration(m_xdgDecorationManager->modeBySurface(surface->surface()) + != WXdgDecorationManager::Server); + + if (surface->isPopup()) { + auto parent = surface->parentSurface(); + auto parentWrapper = m_surfaceContainer->getSurface(parent); + parentWrapper->addSubSurface(wrapper); + m_popupContainer->addSurface(wrapper); + wrapper->setOwnsOutput(parentWrapper->ownsOutput()); + } else { + auto updateSurfaceWithParentContainer = [this, wrapper, surface] { + if (wrapper->parentSurface()) + wrapper->parentSurface()->removeSubSurface(wrapper); + if (wrapper->container()) + wrapper->container()->removeSurface(wrapper); + + if (auto parent = surface->parentSurface()) { + auto parentWrapper = m_surfaceContainer->getSurface(parent); + auto container = parentWrapper->container(); + Q_ASSERT(container); + parentWrapper->addSubSurface(wrapper); + container->addSurface(wrapper); + } else { + m_workspace->addSurface(wrapper); + } + }; + + surface->safeConnect(&WXdgSurface::parentXdgSurfaceChanged, this, updateSurfaceWithParentContainer); + updateSurfaceWithParentContainer(); + + connect(wrapper, &SurfaceWrapper::requestShowWindowMenu, m_windowMenu, [this, wrapper] (QPoint pos) { + QMetaObject::invokeMethod(m_windowMenu, "showWindowMenu", QVariant::fromValue(wrapper), QVariant::fromValue(pos)); + }); + } + + Q_ASSERT(wrapper->parentItem()); + }); + connect(xdgShell, &WXdgShell::surfaceRemoved, this, [this] (WXdgSurface *surface) { + if (surface->isToplevel()) { + m_foreignToplevel->removeSurface(surface); + } + + m_surfaceContainer->destroyForSurface(surface->surface()); + }); + + connect(layerShell, &WLayerShell::surfaceAdded, this, [this] (WLayerSurface *surface) { + auto wrapper = new SurfaceWrapper(qmlEngine(), surface, SurfaceWrapper::Type::Layer); + wrapper->setNoDecoration(true); + updateLayerSurfaceContainer(wrapper); + + connect(surface, &WLayerSurface::layerChanged, this, [this, wrapper] { + updateLayerSurfaceContainer(wrapper); + }); + Q_ASSERT(wrapper->parentItem()); + }); + + connect(layerShell, &WLayerShell::surfaceRemoved, this, [this] (WLayerSurface *surface) { + m_surfaceContainer->destroyForSurface(surface->surface()); + }); + + m_server->start(); + m_renderer = WRenderHelper::createRenderer(m_backend->handle()); + if (!m_renderer) { + qFatal("Failed to create renderer"); + } + + m_allocator = qw_allocator::autocreate(*m_backend->handle(), *m_renderer); + m_renderer->init_wl_display(*m_server->handle()); + + // free follow display + m_compositor = qw_compositor::create(*m_server->handle(), 6, *m_renderer); + qw_subcompositor::create(*m_server->handle()); + qw_screencopy_manager_v1::create(*m_server->handle()); + m_renderWindow->init(m_renderer, m_allocator); + + // for xwayland + auto *xwaylandOutputManager = m_server->attach(m_surfaceContainer->outputLayout()); + xwaylandOutputManager->setScaleOverride(1.0); + + auto xwayland_lazy = true; + m_xwayland = m_server->attach(m_compositor, xwayland_lazy); + m_xwayland->setSeat(m_seat); + + xdgOutputManager->setFilter([this] (WClient *client) { + return client != m_xwayland->waylandClient(); + }); + xwaylandOutputManager->setFilter([this] (WClient *client) { + return client == m_xwayland->waylandClient(); + }); + + connect(m_xwayland, &WXWayland::surfaceAdded, this, [this] (WXWaylandSurface *surface) { + surface->safeConnect(&qw_xwayland_surface::notify_associate, this, [this, surface] { + auto wrapper = new SurfaceWrapper(qmlEngine(), surface, SurfaceWrapper::Type::XWayland); + wrapper->setNoDecoration(false); + m_foreignToplevel->addSurface(surface); + m_workspace->addSurface(wrapper); + Q_ASSERT(wrapper->parentItem()); + connect(wrapper, &SurfaceWrapper::requestShowWindowMenu, m_windowMenu, [this, wrapper] (QPoint pos) { + QMetaObject::invokeMethod(m_windowMenu, "showWindowMenu", QVariant::fromValue(wrapper), QVariant::fromValue(pos)); + }); + }); + surface->safeConnect(&qw_xwayland_surface::notify_dissociate, this, [this, surface] { + m_foreignToplevel->removeSurface(surface); + m_surfaceContainer->destroyForSurface(surface->surface()); + }); + }); + + m_inputMethodHelper = new WInputMethodHelper(m_server, m_seat); + + connect(m_inputMethodHelper, &WInputMethodHelper::inputPopupSurfaceV2Added, this, [this](WInputPopupSurface *inputPopup) { + auto wrapper = new SurfaceWrapper(qmlEngine(), inputPopup, SurfaceWrapper::Type::InputPopup); + auto parent = inputPopup->parentSurface();; + auto parentWrapper = m_surfaceContainer->getSurface(parent); + parentWrapper->addSubSurface(wrapper); + m_popupContainer->addSurface(wrapper); + wrapper->setOwnsOutput(parentWrapper->ownsOutput()); + Q_ASSERT(wrapper->parentItem()); + }); + + connect(m_inputMethodHelper, &WInputMethodHelper::inputPopupSurfaceV2Removed, this, [this](WInputPopupSurface *inputPopup) { + m_surfaceContainer->destroyForSurface(inputPopup->surface()); + }); + + m_xdgDecorationManager = m_server->attach(); + connect(m_xdgDecorationManager, &WXdgDecorationManager::surfaceModeChanged, + this, [this] (WSurface *surface, WXdgDecorationManager::DecorationMode mode) { + auto s = m_surfaceContainer->getSurface(surface); + if (!s) + return; + s->setNoDecoration(mode != WXdgDecorationManager::Server); + }); + + bool freezeClientWhenDisable = false; + m_socket = new WSocket(freezeClientWhenDisable); + if (m_socket->autoCreate()) { + m_server->addSocket(m_socket); + } else { + delete m_socket; + qCritical("Failed to create socket"); + return; + } + + auto gammaControlManager = qw_gamma_control_manager_v1::create(*m_server->handle()); + connect(gammaControlManager, &qw_gamma_control_manager_v1::notify_set_gamma, this, [this] + (wlr_gamma_control_manager_v1_set_gamma_event *event) { + auto *qwOutput = qw_output::from(event->output); + auto *wOutput = WOutput::fromHandle(qwOutput); + size_t ramp_size = 0; + uint16_t *r = nullptr, *g = nullptr, *b = nullptr; + wlr_gamma_control_v1 *gamma_control = event->control; + if (gamma_control) { + ramp_size = gamma_control->ramp_size; + r = gamma_control->table; + g = gamma_control->table + gamma_control->ramp_size; + b = gamma_control->table + 2 * gamma_control->ramp_size; + } + if (!wOutput->setGammaLut(ramp_size, r, g, b)) { + qw_gamma_control_v1::from(gamma_control)->send_failed_and_destroy(); + } + }); + + connect(wOutputManager, &WOutputManagerV1::requestTestOrApply, this, [this, wOutputManager] + (qw_output_configuration_v1 *config, bool onlyTest) { + QList states = wOutputManager->stateListPending(); + bool ok = true; + for (auto state : std::as_const(states)) { + WOutput *output = state.output; + output->enable(state.enabled); + if (state.enabled) { + if (state.mode) + output->setMode(state.mode); + else + output->setCustomMode(state.customModeSize, state.customModeRefresh); + + output->enableAdaptiveSync(state.adaptiveSyncEnabled); + if (!onlyTest) { + WOutputViewport *viewport = getOutput(output)->screenViewport(); + if (viewport) { + viewport->rotateOutput(state.transform); + viewport->setOutputScale(state.scale); + viewport->setX(state.x); + viewport->setY(state.y); + } + } + } + + if (onlyTest) + ok &= output->test(); + else + ok &= output->commit(); + } + wOutputManager->sendResult(config, ok); + }); + + m_server->attach(); + qw_fractional_scale_manager_v1::create(*m_server->handle(), WLR_FRACTIONAL_SCALE_V1_VERSION); + qw_data_control_manager_v1::create(*m_server->handle()); + + m_backend->handle()->start(); + + qInfo() << "Listing on:" << m_socket->fullServerName(); + startDemoClient(); +} + +bool Helper::socketEnabled() const +{ + return m_socket->isEnabled(); +} + +void Helper::setSocketEnabled(bool newEnabled) +{ + if (m_socket) + m_socket->setEnabled(newEnabled); + else + qWarning() << "Can't set enabled for empty socket!"; +} + +void Helper::activeSurface(SurfaceWrapper *wrapper, Qt::FocusReason reason) +{ + if (!wrapper || wrapper->shellSurface()->hasCapability(WToplevelSurface::Capability::Activate)) + setActivatedSurface(wrapper); + if (!wrapper || wrapper->shellSurface()->hasCapability(WToplevelSurface::Capability::Focus)) + setKeyboardFocusSurface(wrapper, reason); +} + +RootSurfaceContainer *Helper::rootContainer() const +{ + return m_surfaceContainer; +} + +void Helper::activeSurface(SurfaceWrapper *wrapper) +{ + activeSurface(wrapper, Qt::OtherFocusReason); +} + +void Helper::fakePressSurfaceBottomRightToReszie(SurfaceWrapper *surface) +{ + auto position = surface->geometry().bottomRight(); + m_fakelastPressedPosition = position; + m_seat->setCursorPosition(position); + Q_EMIT surface->requestResize(Qt::BottomEdge | Qt::RightEdge); +} + +bool Helper::startDemoClient() +{ +#ifdef START_DEMO + QProcess waylandClientDemo; + + waylandClientDemo.setProgram(PROJECT_BINARY_DIR"/examples/animationclient/animationclient"); + waylandClientDemo.setArguments({"-platform", "wayland"}); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("WAYLAND_DISPLAY", m_socket->fullServerName()); + + waylandClientDemo.setProcessEnvironment(env); + return waylandClientDemo.startDetached(); +#else + return false; +#endif +} + +bool Helper::beforeDisposeEvent(WSeat *seat, QWindow *, QInputEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + auto kevent = static_cast(event); + if (QKeySequence(kevent->keyCombination()) == QKeySequence::Quit) { + qApp->quit(); + return true; + } else if (event->modifiers() == Qt::MetaModifier) { + if (kevent->key() == Qt::Key_Right) { + m_workspace->switchToNext(); + return true; + } else if (kevent->key() == Qt::Key_Left) { + m_workspace->switchToPrev(); + return true; + } + } + } + + if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress) { + seat->cursor()->setVisible(true); + } else if (event->type() == QEvent::TouchBegin) { + seat->cursor()->setVisible(false); + } + + if (auto surface = m_surfaceContainer->moveResizeSurface()) { + // for move resize + if (Q_LIKELY(event->type() == QEvent::MouseMove || event->type() == QEvent::TouchUpdate)) { + auto cursor = seat->cursor(); + Q_ASSERT(cursor); + QMouseEvent *ev = static_cast(event); + + auto ownsOutput = surface->ownsOutput(); + if (!ownsOutput) { + m_surfaceContainer->endMoveResize(); + return false; + } + + auto lastPosition = m_fakelastPressedPosition.value_or(cursor->lastPressedOrTouchDownPosition()); + auto increment_pos = ev->globalPosition() - lastPosition; + m_surfaceContainer->doMoveResize(increment_pos); + + return true; + } else if (event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::TouchEnd) { + m_surfaceContainer->endMoveResize(); + m_fakelastPressedPosition.reset(); + } + } + + return false; +} + +bool Helper::afterHandleEvent(WSeat *seat, WSurface *watched, QObject *surfaceItem, QObject *, QInputEvent *event) +{ + Q_UNUSED(seat) + + if (event->isSinglePointEvent() && static_cast(event)->isBeginEvent()) { + // surfaceItem is qml type: XdgSurfaceItem or LayerSurfaceItem + auto toplevelSurface = qobject_cast(surfaceItem)->shellSurface(); + if (!toplevelSurface) + return false; + Q_ASSERT(toplevelSurface->surface() == watched); + + auto surface = m_surfaceContainer->getSurface(watched); + activeSurface(surface, Qt::MouseFocusReason); + } + + return false; +} + +bool Helper::unacceptedEvent(WSeat *, QWindow *, QInputEvent *event) +{ + if (event->isSinglePointEvent()) { + if (static_cast(event)->isBeginEvent()) { + activeSurface(nullptr, Qt::OtherFocusReason); + } + } + + return false; +} + +SurfaceWrapper *Helper::keyboardFocusSurface() const +{ + return m_keyboardFocusSurface; +} + +void Helper::setKeyboardFocusSurface(SurfaceWrapper *newActivate, Qt::FocusReason reason) +{ + if (m_keyboardFocusSurface == newActivate) + return; + + if (newActivate && !newActivate->shellSurface()->hasCapability(WToplevelSurface::Capability::Focus)) + return; + + if (m_keyboardFocusSurface) { + if (newActivate) { + if (m_keyboardFocusSurface->shellSurface()->keyboardFocusPriority() + > newActivate->shellSurface()->keyboardFocusPriority()) + return; + } else { + if (m_keyboardFocusSurface->shellSurface()->keyboardFocusPriority() > 0) + return; + } + } + + if (newActivate) { + newActivate->setFocus(true, reason); + m_seat->setKeyboardFocusSurface(newActivate->surface()); + } else if (m_keyboardFocusSurface) { + m_keyboardFocusSurface->setFocus(false, reason); + m_seat->setKeyboardFocusSurface(nullptr); + } + + m_keyboardFocusSurface = newActivate; + + Q_EMIT keyboardFocusSurfaceChanged(); +} + +SurfaceWrapper *Helper::activatedSurface() const +{ + return m_activatedSurface; +} + +void Helper::setActivatedSurface(SurfaceWrapper *newActivateSurface) +{ + if (m_activatedSurface == newActivateSurface) + return; + + if (newActivateSurface) { + newActivateSurface->stackToLast(); + } + + if (m_activatedSurface) + m_activatedSurface->setActivate(false); + if (newActivateSurface) + newActivateSurface->setActivate(true); + m_activatedSurface = newActivateSurface; + Q_EMIT activatedSurfaceChanged(); +} + +void Helper::setCursorPosition(const QPointF &position) +{ + m_surfaceContainer->endMoveResize(); + m_seat->setCursorPosition(position); +} + +void Helper::allowNonDrmOutputAutoChangeMode(WOutput *output) +{ + output->safeConnect(&qw_output::notify_request_state, + this, [this] (wlr_output_event_request_state *newState) { + if (newState->state->committed & WLR_OUTPUT_STATE_MODE) { + auto output = qobject_cast(sender()); + + if (newState->state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM) { + output->set_custom_mode(newState->state->custom_mode.width, + newState->state->custom_mode.height, + newState->state->custom_mode.refresh); + } else { + output->set_mode(newState->state->mode); + } + + output->commit(); + } + }); +} + +void Helper::enableOutput(WOutput *output) +{ + // Enable on default + auto qwoutput = output->handle(); + // Don't care for WOutput::isEnabled, must do WOutput::commit here, + // In order to ensure trigger QWOutput::frame signal, WOutputRenderWindow + // needs this signal to render next frmae. Because QWOutput::frame signal + // maybe emit before WOutputRenderWindow::attach, if no commit here, + // WOutputRenderWindow will ignore this ouptut on render. + if (!qwoutput->property("_Enabled").toBool()) { + qwoutput->setProperty("_Enabled", true); + + if (!qwoutput->handle()->current_mode) { + auto mode = qwoutput->preferred_mode(); + if (mode) + output->setMode(mode); + } + output->enable(true); + bool ok = output->commit(); + Q_ASSERT(ok); + } +} + +int Helper::indexOfOutput(WOutput *output) const +{ + for (int i = 0; i < m_outputList.size(); i++) { + if (m_outputList.at(i)->output() == output) + return i; + } + return -1; +} + +Output *Helper::getOutput(WOutput *output) const +{ + for (auto o : std::as_const(m_outputList)) { + if (o->output() == output) + return o; + } + return nullptr; +} + +void Helper::addOutput() +{ + qobject_cast(m_backend->handle())->for_each_backend([] (wlr_backend *backend, void *) { + if (auto x11 = qw_x11_backend::from(backend)) { + qw_output::from(x11->output_create()); + } else if (auto wayland = qw_wayland_backend::from(backend)) { + qw_output::from(wayland->output_create()); + } + }, nullptr); +} + +void Helper::setOutputMode(OutputMode mode) +{ + if (m_outputList.length() < 2 || m_mode == mode) + return; + + m_mode = mode; + Q_EMIT outputModeChanged(); + for (int i = 0; i < m_outputList.size(); i++) { + if (m_outputList.at(i) == m_surfaceContainer->primaryOutput()) + continue; + + Output *o; + if (mode == OutputMode::Copy) { + o = Output::createCopy(m_outputList.at(i)->output(), m_surfaceContainer->primaryOutput(), qmlEngine(), this); + m_surfaceContainer->removeOutput(m_outputList.at(i)); + } else if(mode == OutputMode::Extension) { + o = Output::createPrimary(m_outputList.at(i)->output(), qmlEngine(), this); + o->outputItem()->stackBefore(m_surfaceContainer); + m_surfaceContainer->addOutput(o); + enableOutput(o->output()); + } + + m_outputList.at(i)->deleteLater(); + m_outputList.replace(i,o); + } +} + +void Helper::setOutputProxy(Output *output) +{ + +} + +void Helper::updateLayerSurfaceContainer(SurfaceWrapper *surface) +{ + auto layer = qobject_cast(surface->shellSurface()); + Q_ASSERT(layer); + + if (auto oldContainer = surface->container()) + oldContainer->removeSurface(surface); + + switch (layer->layer()) { + case WLayerSurface::LayerType::Background: + m_backgroundContainer->addSurface(surface); + break; + case WLayerSurface::LayerType::Bottom: + m_bottomContainer->addSurface(surface); + break; + case WLayerSurface::LayerType::Top: + m_topContainer->addSurface(surface); + break; + case WLayerSurface::LayerType::Overlay: + m_overlayContainer->addSurface(surface); + break; + default: + Q_UNREACHABLE_RETURN(); + } +} + +int Helper::currentUserId() const +{ + return m_currentUserId; +} + +void Helper::setCurrentUserId(int uid) +{ + if (m_currentUserId == uid) + return; + m_currentUserId = uid; + Q_EMIT currentUserIdChanged(); +} + +float Helper::animationSpeed() const +{ + return m_animationSpeed; +} + +void Helper::setAnimationSpeed(float newAnimationSpeed) +{ + if (qFuzzyCompare(m_animationSpeed, newAnimationSpeed)) + return; + m_animationSpeed = newAnimationSpeed; + emit animationSpeedChanged(); +} + +Helper::OutputMode Helper::outputMode() const +{ + return m_mode; +} diff --git a/examples/tinywl/helper.h b/examples/tinywl/helper.h index 09fda3b4..7788dabe 100644 --- a/examples/tinywl/helper.h +++ b/examples/tinywl/helper.h @@ -1,183 +1,185 @@ -// Copyright (C) 2023 JiDe Zhang . +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "qmlengine.h" +#include "workspace.h" + +#include #include +#include + +#include +#include -#include +Q_MOC_INCLUDE() +Q_MOC_INCLUDE("surfacewrapper.h") -Q_DECLARE_OPAQUE_POINTER(QWindow*) +QT_BEGIN_NAMESPACE +class QQuickItem; +QT_END_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE -class WQuickCursor; +class WServer; class WOutputRenderWindow; -class WXdgOutputManager; -class WXdgDecorationManager; +class WOutputLayout; +class WCursor; +class WBackend; +class WOutputItem; +class WOutputViewport; +class WOutputLayer; +class WOutput; +class WXWayland; class WInputMethodHelper; -class WCursorShapeManagerV1; -class WOutputManagerV1; +class WXdgDecorationManager; +class WSocket; +class WSurface; +class WToplevelSurface; +class WSurfaceItem; +class WForeignToplevel; WAYLIB_SERVER_END_NAMESPACE QW_BEGIN_NAMESPACE +class qw_renderer; +class qw_allocator; class qw_compositor; -class qw_gamma_control_manager_v1; -class qw_fractional_scale_manager_v1; QW_END_NAMESPACE -struct wlr_output_event_request_state; -QW_USE_NAMESPACE WAYLIB_SERVER_USE_NAMESPACE +QW_USE_NAMESPACE -struct OutputInfo; - -class Q_DECL_HIDDEN Helper : public WSeatEventFilter { +class Output; +class SurfaceWrapper; +class Workspace; +class RootSurfaceContainer; +class LayerSurfaceContainer; +class Helper : public WSeatEventFilter +{ + friend class RootSurfaceContainer; Q_OBJECT - Q_PROPERTY(WToplevelSurface* activatedSurface READ activatedSurface WRITE setActivateSurface NOTIFY activatedSurfaceChanged FINAL) - Q_PROPERTY(WSurfaceItem* resizingItem READ resizingItem NOTIFY resizingItemChanged FINAL) - Q_PROPERTY(WSurfaceItem* movingItem READ movingItem NOTIFY movingItemChanged) - Q_PROPERTY(WQuickOutputLayout* outputLayout READ outputLayout CONSTANT) - Q_PROPERTY(WSeat* seat READ seat CONSTANT) - Q_PROPERTY(WXdgDecorationManager* xdgDecorationManager READ xdgDecorationManager NOTIFY xdgDecorationManagerChanged) - Q_PROPERTY(QW_NAMESPACE::qw_compositor* compositor READ compositor NOTIFY compositorChanged FINAL) - Q_PROPERTY(WQmlCreator* outputCreator READ outputCreator CONSTANT) - Q_PROPERTY(WQmlCreator* xdgShellCreator READ xdgShellCreator CONSTANT) - Q_PROPERTY(WQmlCreator* xwaylandCreator READ xwaylandCreator CONSTANT) - Q_PROPERTY(WQmlCreator* layerShellCreator READ layerShellCreator CONSTANT) - Q_PROPERTY(WQmlCreator* inputPopupCreator READ inputPopupCreator CONSTANT) + Q_PROPERTY(bool socketEnabled READ socketEnabled WRITE setSocketEnabled NOTIFY socketEnabledChanged FINAL) + Q_PROPERTY(SurfaceWrapper* activatedSurface READ activatedSurface NOTIFY activatedSurfaceChanged FINAL) + Q_PROPERTY(RootSurfaceContainer* rootContainer READ rootContainer CONSTANT FINAL) + Q_PROPERTY(Workspace* workspace READ workspace CONSTANT FINAL) + Q_PROPERTY(int currentUserId READ currentUserId WRITE setCurrentUserId NOTIFY currentUserIdChanged FINAL) + Q_PROPERTY(float animationSpeed READ animationSpeed WRITE setAnimationSpeed NOTIFY animationSpeedChanged FINAL) + Q_PROPERTY(OutputMode outputMode READ outputMode WRITE setOutputMode NOTIFY outputModeChanged FINAL) QML_ELEMENT QML_SINGLETON public: explicit Helper(QObject *parent = nullptr); - ~Helper() override; - - void initProtocols(WOutputRenderWindow *window, QQmlEngine *qmlEngine); - WQuickOutputLayout *outputLayout() const; - WSeat *seat() const; - qw_compositor *compositor() const; - - WQmlCreator *outputCreator() const; - WQmlCreator *xdgShellCreator() const; - WQmlCreator *xwaylandCreator() const; - WQmlCreator *layerShellCreator() const; - WQmlCreator *inputPopupCreator() const; - - void stopMoveResize(); - - WToplevelSurface *activatedSurface() const; - WSurfaceItem *resizingItem() const; - WSurfaceItem *movingItem() const; - WXdgDecorationManager *xdgDecorationManager() const; - - Q_INVOKABLE bool registerExclusiveZone(WLayerSurface *layerSurface); - Q_INVOKABLE bool unregisterExclusiveZone(WLayerSurface *layerSurface); - Q_INVOKABLE QJSValue getExclusiveMargins(WLayerSurface *layerSurface); - Q_INVOKABLE quint32 getTopExclusiveMargin(WToplevelSurface *layerSurface); - Q_INVOKABLE quint32 getBottomExclusiveMargin(WToplevelSurface *layerSurface); - Q_INVOKABLE quint32 getLeftExclusiveMargin(WToplevelSurface *layerSurface); - Q_INVOKABLE quint32 getRightExclusiveMargin(WToplevelSurface *layerSurface); - - // Output - Q_INVOKABLE void onSurfaceEnterOutput(WToplevelSurface *surface, WSurfaceItem *surfaceItem, WOutput *output); - Q_INVOKABLE void onSurfaceLeaveOutput(WToplevelSurface *surface, WSurfaceItem *surfaceItem, WOutput *output); - std::pair getFirstOutputOfSurface(WToplevelSurface *surface); - - // Socket - Q_INVOKABLE void setSocketEnabled(bool newEnabled); + ~Helper(); + + enum class OutputMode { + Copy, + Extension + }; + Q_ENUM(OutputMode) + + static Helper *instance(); + + QmlEngine *qmlEngine() const; + WOutputRenderWindow *window() const; + Workspace* workspace() const; + Output* output() const; + void init(); + + bool socketEnabled() const; + void setSocketEnabled(bool newSocketEnabled); + + void activeSurface(SurfaceWrapper *wrapper, Qt::FocusReason reason); + + RootSurfaceContainer *rootContainer() const; + Output *getOutput(WOutput *output) const; + + int currentUserId() const; + void setCurrentUserId(int uid); + + float animationSpeed() const; + void setAnimationSpeed(float newAnimationSpeed); + + OutputMode outputMode() const; + void setOutputMode(OutputMode mode); + + Q_INVOKABLE void addOutput(); + public Q_SLOTS: - void startMove(WToplevelSurface *surface, WSurfaceItem *shell, WSeat *seat, int serial); - void startResize(WToplevelSurface *surface, WSurfaceItem *shell, WSeat *seat, Qt::Edges edge, int serial); - void cancelMoveResize(WSurfaceItem *shell); - bool startDemoClient(const QString &socket); - WSurface *getFocusSurfaceFrom(QObject *object); + void activeSurface(SurfaceWrapper *wrapper); + void fakePressSurfaceBottomRightToReszie(SurfaceWrapper *surface); + +signals: + void socketEnabledChanged(); + void keyboardFocusSurfaceChanged(); + void activatedSurfaceChanged(); + void primaryOutputChanged(); + void currentUserIdChanged(); + void animationSpeedChanged(); + void outputModeChanged(); + +private: void allowNonDrmOutputAutoChangeMode(WOutput *output); void enableOutput(WOutput *output); -Q_SIGNALS: - void activatedSurfaceChanged(); - void resizingItemChanged(); - void movingItemChanged(); - void topExclusiveMarginChanged(); - void bottomExclusiveMarginChanged(); - void leftExclusiveMarginChanged(); - void rightExclusiveMarginChanged(); - void compositorChanged(); - void xdgDecorationManagerChanged(); + int indexOfOutput(WOutput *output) const; + + void setOutputProxy(Output *output); + + void updateLayerSurfaceContainer(SurfaceWrapper *surface); + + SurfaceWrapper *keyboardFocusSurface() const; + void setKeyboardFocusSurface(SurfaceWrapper *newActivateSurface, Qt::FocusReason reason); + SurfaceWrapper *activatedSurface() const; + void setActivatedSurface(SurfaceWrapper *newActivateSurface); + + void setCursorPosition(const QPointF &position); + + bool startDemoClient(); -private: bool beforeDisposeEvent(WSeat *seat, QWindow *watched, QInputEvent *event) override; bool afterHandleEvent(WSeat *seat, WSurface *watched, QObject *surfaceItem, QObject *, QInputEvent *event) override; - bool unacceptedEvent(WSeat *seat, QWindow *watched, QInputEvent *event) override; + bool unacceptedEvent(WSeat *, QWindow *, QInputEvent *event) override; - void setActivateSurface(WToplevelSurface *newActivate); - void setResizingItem(WSurfaceItem *newResizingItem); - void setMovingItem(WSurfaceItem *newMovingItem); - void onOutputRequeseState(wlr_output_event_request_state *newState); - OutputInfo* getOutputInfo(WOutput *output); + static Helper *m_instance; - WQuickOutputLayout *m_outputLayout = nullptr; - WCursor *m_cursor = nullptr; + // qtquick helper + WOutputRenderWindow *m_renderWindow = nullptr; + QObject *m_windowMenu = nullptr; + // wayland helper WServer *m_server = nullptr; + WSocket *m_socket = nullptr; + WSeat *m_seat = nullptr; + WBackend *m_backend = nullptr; qw_renderer *m_renderer = nullptr; qw_allocator *m_allocator = nullptr; + + // protocols qw_compositor *m_compositor = nullptr; - WSeat *m_seat = nullptr; WXWayland *m_xwayland = nullptr; - WXdgDecorationManager *m_xdgDecorationManager = nullptr; WInputMethodHelper *m_inputMethodHelper = nullptr; - WSocket *m_socket = nullptr; - qw_fractional_scale_manager_v1 *m_fractionalScaleManagerV1 = nullptr; - WCursorShapeManagerV1 *m_cursorShapeManager = nullptr; - qw_gamma_control_manager_v1 *m_gammaControlManager = nullptr; - WOutputManagerV1 *m_wOutputManager = nullptr; - - WQmlCreator *m_outputCreator = nullptr; - WQmlCreator *m_xdgShellCreator = nullptr; - WQmlCreator *m_xwaylandCreator = nullptr; - WQmlCreator *m_layerShellCreator = nullptr; - WQmlCreator *m_inputPopupCreator = nullptr; - - QPointer m_activateSurface; - QList> m_outputExclusiveZoneInfo; - - // for move resize - struct { - QPointer surface; - QPointer surfaceItem; - WSeat *seat = nullptr; - QPointF surfacePosOfStartMoveResize; - QSizeF surfaceSizeOfStartMoveResize; - Qt::Edges resizeEdgets; - WSurfaceItem *resizingItem = nullptr; - WSurfaceItem *movingItem = nullptr; - } moveReiszeState; -}; - -struct OutputInfo { - QList surfaceList; - QList surfaceItemList; - - // for Exclusive Zone - quint32 m_topExclusiveMargin = 0; - quint32 m_bottomExclusiveMargin = 0; - quint32 m_leftExclusiveMargin = 0; - quint32 m_rightExclusiveMargin = 0; - QList> registeredSurfaceList; + WXdgDecorationManager *m_xdgDecorationManager = nullptr; + WForeignToplevel *m_foreignToplevel = nullptr; + + // privaet data + QList m_outputList; + + QPointer m_keyboardFocusSurface; + QPointer m_activatedSurface; + + RootSurfaceContainer *m_surfaceContainer = nullptr; + LayerSurfaceContainer *m_backgroundContainer = nullptr; + LayerSurfaceContainer *m_bottomContainer = nullptr; + Workspace *m_workspace = nullptr; + LayerSurfaceContainer *m_topContainer = nullptr; + LayerSurfaceContainer *m_overlayContainer = nullptr; + SurfaceContainer *m_popupContainer = nullptr; + int m_currentUserId = -1; + float m_animationSpeed = 1.0; + OutputMode m_mode = OutputMode::Extension; + std::optional m_fakelastPressedPosition; }; -Q_DECLARE_OPAQUE_POINTER(WAYLIB_SERVER_NAMESPACE::WOutputRenderWindow*) -Q_DECLARE_OPAQUE_POINTER(QW_NAMESPACE::qw_compositor*) +Q_DECLARE_OPAQUE_POINTER(RootSurfaceContainer*) diff --git a/examples/tinywl/layersurfacecontainer.cpp b/examples/tinywl/layersurfacecontainer.cpp new file mode 100644 index 00000000..5bebb256 --- /dev/null +++ b/examples/tinywl/layersurfacecontainer.cpp @@ -0,0 +1,132 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "layersurfacecontainer.h" +#include "surfacewrapper.h" +#include "output.h" +#include "helper.h" +#include "rootsurfacecontainer.h" + +#include +#include + +WAYLIB_SERVER_USE_NAMESPACE + +OutputLayerSurfaceContainer::OutputLayerSurfaceContainer(Output *output, LayerSurfaceContainer *parent) + : SurfaceContainer(parent) + , m_output(output) +{ + +} + +Output *OutputLayerSurfaceContainer::output() const +{ + return m_output; +} + +void OutputLayerSurfaceContainer::addSurface(SurfaceWrapper *surface) +{ + SurfaceContainer::addSurface(surface); + surface->setOwnsOutput(m_output); +} + +void OutputLayerSurfaceContainer::removeSurface(SurfaceWrapper *surface) +{ + SurfaceContainer::removeSurface(surface); + if (surface->ownsOutput() == m_output) + surface->setOwnsOutput(nullptr); +} + +LayerSurfaceContainer::LayerSurfaceContainer(SurfaceContainer *parent) + : SurfaceContainer(parent) +{ + +} + +void LayerSurfaceContainer::addOutput(Output *output) +{ + Q_ASSERT(!getSurfaceContainer(output)); + auto container = new OutputLayerSurfaceContainer(output, this); + m_surfaceContainers.append(container); + updateSurfacesContainer(); +} + +void LayerSurfaceContainer::removeOutput(Output *output) +{ + OutputLayerSurfaceContainer *container = getSurfaceContainer(output); + Q_ASSERT(container); + m_surfaceContainers.removeOne(container); + + for (SurfaceWrapper *surface : container->surfaces()) { + container->removeSurface(surface); + auto layerSurface = qobject_cast(surface->shellSurface()); + Q_ASSERT(layerSurface); + // Needs to be moved to the new primary output + if (!layerSurface->output()) + addSurfaceToContainer(surface); + } + + container->deleteLater(); +} + +OutputLayerSurfaceContainer *LayerSurfaceContainer::getSurfaceContainer(const Output *output) const +{ + for (OutputLayerSurfaceContainer *container : std::as_const(m_surfaceContainers)) { + if (container->output() == output) + return container; + } + return nullptr; +} + +OutputLayerSurfaceContainer *LayerSurfaceContainer::getSurfaceContainer(const WOutput *output) const +{ + for (OutputLayerSurfaceContainer *container : std::as_const(m_surfaceContainers)) { + if (container->output()->output() == output) + return container; + } + return nullptr; +} + +void LayerSurfaceContainer::addSurface(SurfaceWrapper *surface) +{ + Q_ASSERT(surface->type() == SurfaceWrapper::Type::Layer); + if (!SurfaceContainer::doAddSurface(surface, false)) + return; + addSurfaceToContainer(surface); +} + +void LayerSurfaceContainer::removeSurface(SurfaceWrapper *surface) +{ + if (!SurfaceContainer::doRemoveSurface(surface, false)) + return; + auto shell = qobject_cast(surface->shellSurface()); + auto output = shell->output(); + auto container = getSurfaceContainer(output); + Q_ASSERT(container); + Q_ASSERT(container->surfaces().contains(surface)); + container->removeSurface(surface); +} + +void LayerSurfaceContainer::addSurfaceToContainer(SurfaceWrapper *surface) +{ + Q_ASSERT(!surface->container()); + auto shell = qobject_cast(surface->shellSurface()); + auto output = shell->output() ? shell->output() : rootContainer()->primaryOutput()->output(); + if (!output) { + qCWarning(qLcLayerShell) << "No output, will close layer surface!"; + shell->closed(); + return; + } + auto container = getSurfaceContainer(output); + Q_ASSERT(container); + Q_ASSERT(!container->surfaces().contains(surface)); + container->addSurface(surface); +} + +void LayerSurfaceContainer::updateSurfacesContainer() +{ + for (SurfaceWrapper *surface : surfaces()) { + if (!surface->container()) + addSurfaceToContainer(surface); + } +} diff --git a/examples/tinywl/layersurfacecontainer.h b/examples/tinywl/layersurfacecontainer.h new file mode 100644 index 00000000..97796863 --- /dev/null +++ b/examples/tinywl/layersurfacecontainer.h @@ -0,0 +1,49 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "surfacecontainer.h" + +#include + +WAYLIB_SERVER_USE_NAMESPACE + +class LayerSurfaceContainer; +class OutputLayerSurfaceContainer : public SurfaceContainer +{ +public: + explicit OutputLayerSurfaceContainer(Output *output, LayerSurfaceContainer *parent); + + Output *output() const; + + void addSurface(SurfaceWrapper *surface) override; + void removeSurface(SurfaceWrapper *surface) override; + +private: + Output *m_output; +}; + +WAYLIB_SERVER_BEGIN_NAMESPACE +class WOutput; +WAYLIB_SERVER_END_NAMESPACE + +class LayerSurfaceContainer : public SurfaceContainer +{ +public: + explicit LayerSurfaceContainer(SurfaceContainer *parent); + + void addOutput(Output *output) override; + void removeOutput(Output *output) override; + OutputLayerSurfaceContainer *getSurfaceContainer(const Output *output) const; + OutputLayerSurfaceContainer *getSurfaceContainer(const WOutput *output) const; + + void addSurface(SurfaceWrapper *surface) override; + void removeSurface(SurfaceWrapper *surface) override; + +private: + void addSurfaceToContainer(SurfaceWrapper *surface); + void updateSurfacesContainer(); + + QList m_surfaceContainers; +}; diff --git a/examples/tinywl/main.cpp b/examples/tinywl/main.cpp index 1225a670..64a0dff4 100644 --- a/examples/tinywl/main.cpp +++ b/examples/tinywl/main.cpp @@ -1,800 +1,15 @@ -// Copyright (C) 2023 JiDe Zhang . +// Copyright (C) 2024 JiDe Zhang . // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "helper.h" -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define WLR_FRACTIONAL_SCALE_V1_VERSION 1 - -inline QPointF getItemGlobalPosition(QQuickItem *item) -{ - auto parent = item->parentItem(); - return parent ? parent->mapToGlobal(item->position()) : item->position(); -} - -Helper::Helper(QObject *parent) - : WSeatEventFilter(parent) - , m_server(new WServer(this)) - , m_outputLayout(new WQuickOutputLayout(this)) - , m_cursor(new WCursor(this)) - , m_seat(new WSeat()) - , m_outputCreator(new WQmlCreator(this)) - , m_xdgShellCreator(new WQmlCreator(this)) - , m_xwaylandCreator(new WQmlCreator(this)) - , m_layerShellCreator(new WQmlCreator(this)) - , m_inputPopupCreator(new WQmlCreator(this)) -{ - m_seat->setEventFilter(this); - m_seat->setCursor(m_cursor); - m_cursor->setLayout(m_outputLayout); -} - -Helper::~Helper() -{ - for (auto info : std::as_const(m_outputExclusiveZoneInfo)) { - if (info.second) { - delete info.second; - } - } -} - -void Helper::initProtocols(WOutputRenderWindow *window, QQmlEngine *qmlEngine) -{ - auto backend = m_server->attach(); - m_server->start(); - m_renderer = WRenderHelper::createRenderer(backend->handle()); - - if (!m_renderer) { - qFatal("Failed to create renderer"); - } - - connect(backend, &WBackend::outputAdded, this, [backend, this, window, qmlEngine] (WOutput *output) { - allowNonDrmOutputAutoChangeMode(output); - - auto initProperties = qmlEngine->newObject(); - initProperties.setProperty("waylandOutput", qmlEngine->toScriptValue(output)); - initProperties.setProperty("layout", qmlEngine->toScriptValue(outputLayout())); - initProperties.setProperty("x", qmlEngine->toScriptValue(outputLayout()->implicitWidth())); - - m_outputCreator->add(output, initProperties); - }); - - connect(backend, &WBackend::outputRemoved, this, [this] (WOutput *output) { - m_outputCreator->removeByOwner(output); - }); - - connect(backend, &WBackend::inputAdded, this, [this] (WInputDevice *device) { - m_seat->attachInputDevice(device); - }); - - connect(backend, &WBackend::inputRemoved, this, [this] (WInputDevice *device) { - m_seat->detachInputDevice(device); - }); - - m_allocator = qw_allocator::autocreate(*backend->handle(), *m_renderer); - m_renderer->init_wl_display(*m_server->handle()); - - // free follow display - m_compositor = qw_compositor::create(*m_server->handle(), 6, *m_renderer); - qw_subcompositor::create(*m_server->handle()); - qw_screencopy_manager_v1::create(*m_server->handle()); - - auto *xdgShell = m_server->attach(); - auto *foreignToplevel = m_server->attach(xdgShell); - auto *layerShell = m_server->attach(); - m_server->attach(m_seat); - - auto *xdgOutputManager = m_server->attach(m_outputLayout); - auto *xwaylandOutputManager = m_server->attach(m_outputLayout); - - xwaylandOutputManager->setScaleOverride(1.0); - - connect(xdgShell, &WXdgShell::surfaceAdded, this, [this, qmlEngine, foreignToplevel](WXdgSurface *surface) { - auto initProperties = qmlEngine->newObject(); - initProperties.setProperty("type", surface->isPopup() ? "popup" : "toplevel"); - initProperties.setProperty("waylandSurface", qmlEngine->toScriptValue(surface)); - m_xdgShellCreator->add(surface, initProperties); - - if (!surface->isPopup()) { - foreignToplevel->addSurface(surface); - } - }); - connect(xdgShell, &WXdgShell::surfaceRemoved, m_xdgShellCreator, &WQmlCreator::removeByOwner); - connect(xdgShell, - &WXdgShell::surfaceRemoved, - foreignToplevel, - [foreignToplevel](WXdgSurface *surface) { - if (!surface->isPopup()) { - foreignToplevel->removeSurface(surface); - } - }); - - auto xwayland_lazy = true; - m_xwayland = m_server->attach(m_compositor, xwayland_lazy); - m_xwayland->setSeat(m_seat); - - xdgOutputManager->setFilter([this] (WClient *client) { - return client != m_xwayland->waylandClient(); - }); - xwaylandOutputManager->setFilter([this] (WClient *client) { - return client == m_xwayland->waylandClient(); - }); - - connect(m_xwayland, &WXWayland::surfaceAdded, this, [this, qmlEngine, foreignToplevel] (WXWaylandSurface *surface) { - surface->safeConnect(&qw_xwayland_surface::notify_associate, this, [this, surface, qmlEngine, foreignToplevel] { - auto initProperties = qmlEngine->newObject(); - initProperties.setProperty("waylandSurface", qmlEngine->toScriptValue(surface)); - - m_xwaylandCreator->add(surface, initProperties); - foreignToplevel->addSurface(surface); - }); - surface->safeConnect(&qw_xwayland_surface::notify_dissociate, this, [this, surface, foreignToplevel] { - m_xwaylandCreator->removeByOwner(surface); - foreignToplevel->removeSurface(surface); - }); - }); - - connect(layerShell, &WLayerShell::surfaceAdded, this, [this, qmlEngine](WLayerSurface *surface) { - auto initProperties = qmlEngine->newObject(); - initProperties.setProperty("waylandSurface", qmlEngine->toScriptValue(surface)); - m_layerShellCreator->add(surface, initProperties); - }); - - connect(layerShell, &WLayerShell::surfaceRemoved, m_layerShellCreator, &WQmlCreator::removeByOwner); - - m_inputMethodHelper = new WInputMethodHelper(m_server, m_seat); - - connect(m_inputMethodHelper, &WInputMethodHelper::inputPopupSurfaceV2Added, this, [this, qmlEngine](WInputPopupSurface *inputPopup) { - auto initProperties = qmlEngine->newObject(); - initProperties.setProperty("popupSurface", qmlEngine->toScriptValue(inputPopup)); - m_inputPopupCreator->add(inputPopup, initProperties); - }); - - connect(m_inputMethodHelper, &WInputMethodHelper::inputPopupSurfaceV2Removed, m_inputPopupCreator, &WQmlCreator::removeByOwner); - - Q_EMIT compositorChanged(); - - window->init(m_renderer, m_allocator); - m_xdgDecorationManager = m_server->attach(); - - bool freezeClientWhenDisable = false; - m_socket = new WSocket(freezeClientWhenDisable); - if (m_socket->autoCreate()) { - m_server->addSocket(m_socket); - } else { - delete m_socket; - qCritical("Failed to create socket"); - } - - m_gammaControlManager = qw_gamma_control_manager_v1::create(*m_server->handle()); - connect(m_gammaControlManager, &qw_gamma_control_manager_v1::notify_set_gamma, this, [this] - (wlr_gamma_control_manager_v1_set_gamma_event *event) { - auto *qwOutput = qw_output::from(event->output); - auto *wOutput = WOutput::fromHandle(qwOutput); - size_t ramp_size = 0; - uint16_t *r = nullptr, *g = nullptr, *b = nullptr; - wlr_gamma_control_v1 *gamma_control = event->control; - if (gamma_control) { - ramp_size = gamma_control->ramp_size; - r = gamma_control->table; - g = gamma_control->table + gamma_control->ramp_size; - b = gamma_control->table + 2 * gamma_control->ramp_size; - if (!wOutput->setGammaLut(ramp_size, r, g, b)) { - qw_gamma_control_v1::from(gamma_control)->send_failed_and_destroy(); - } - } - }); - m_wOutputManager = m_server->attach(); - connect(m_wOutputManager, &WOutputManagerV1::requestTestOrApply, this, [this] - (qw_output_configuration_v1 *config, bool onlyTest) { - QList states = m_wOutputManager->stateListPending(); - bool ok = true; - for (auto state : std::as_const(states)) { - WOutput *output = state.output; - output->enable(state.enabled); - if (state.enabled) { - if (state.mode) - output->setMode(state.mode); - else - output->setCustomMode(state.customModeSize, state.customModeRefresh); - - output->enableAdaptiveSync(state.adaptiveSyncEnabled); - if (!onlyTest) { - WOutputItem *item = WOutputItem::getOutputItem(output); - if (item) { - WOutputViewport *viewport = item->property("onscreenViewport").value(); - if (viewport) { - viewport->rotateOutput(state.transform); - viewport->setOutputScale(state.scale); - viewport->setX(state.x); - viewport->setY(state.y); - } - } - } - } - - if (onlyTest) - ok &= output->test(); - else - ok &= output->commit(); - } - m_wOutputManager->sendResult(config, ok); - }); - - m_cursorShapeManager = m_server->attach(); - m_fractionalScaleManagerV1 = qw_fractional_scale_manager_v1::create(*m_server->handle(), WLR_FRACTIONAL_SCALE_V1_VERSION); - qw_data_control_manager_v1::create(*m_server->handle()); - - backend->handle()->start(); - - qInfo() << "Listing on:" << m_socket->fullServerName(); - startDemoClient(m_socket->fullServerName()); -} - -WQuickOutputLayout *Helper::outputLayout() const -{ - return m_outputLayout; -} - -WSeat *Helper::seat() const -{ - return m_seat; -} - -qw_compositor *Helper::compositor() const -{ - return m_compositor; -} - -WQmlCreator *Helper::outputCreator() const -{ - return m_outputCreator; -} - -WQmlCreator *Helper::xdgShellCreator() const -{ - return m_xdgShellCreator; -} - -WQmlCreator *Helper::xwaylandCreator() const -{ - return m_xwaylandCreator; -} - -WQmlCreator *Helper::layerShellCreator() const -{ - return m_layerShellCreator; -} - -WQmlCreator *Helper::inputPopupCreator() const -{ - return m_inputPopupCreator; -} - -WSurfaceItem *Helper::resizingItem() const -{ - return moveReiszeState.resizingItem; -} - -void Helper::setResizingItem(WSurfaceItem *newResizingItem) -{ - if (moveReiszeState.resizingItem == newResizingItem) - return; - moveReiszeState.resizingItem = newResizingItem; - emit resizingItemChanged(); -} - -WSurfaceItem *Helper::movingItem() const -{ - return moveReiszeState.movingItem; -} - -bool Helper::registerExclusiveZone(WLayerSurface *layerSurface) -{ - auto [ output, infoPtr ] = getFirstOutputOfSurface(layerSurface); - if (!output) - return 0; - - auto exclusiveZone = layerSurface->exclusiveZone(); - auto exclusiveEdge = layerSurface->getExclusiveZoneEdge(); - - if (exclusiveZone <= 0 || exclusiveEdge == WLayerSurface::AnchorType::None) - return false; - - QListIterator> listIter(infoPtr->registeredSurfaceList); - while (listIter.hasNext()) { - if (std::get(listIter.next()) == layerSurface) - return false; - } - - infoPtr->registeredSurfaceList.append(std::make_tuple(layerSurface, exclusiveZone, exclusiveEdge)); - switch(exclusiveEdge) { - using enum WLayerSurface::AnchorType; - case Top: - infoPtr->m_topExclusiveMargin += exclusiveZone; - Q_EMIT topExclusiveMarginChanged(); - break; - case Bottom: - infoPtr->m_bottomExclusiveMargin += exclusiveZone; - Q_EMIT bottomExclusiveMarginChanged(); - break; - case Left: - infoPtr->m_leftExclusiveMargin += exclusiveZone; - Q_EMIT leftExclusiveMarginChanged(); - break; - case Right: - infoPtr->m_rightExclusiveMargin += exclusiveZone; - Q_EMIT rightExclusiveMarginChanged(); - break; - default: - Q_UNREACHABLE(); - } - return true; -} - -bool Helper::unregisterExclusiveZone(WLayerSurface *layerSurface) -{ - auto [ output, infoPtr ] = getFirstOutputOfSurface(layerSurface); - if (!output) - return 0; - - QMutableListIterator> listIter(infoPtr->registeredSurfaceList); - while (listIter.hasNext()) { - auto [ registeredSurface, exclusiveZone, exclusiveEdge ] = listIter.next(); - if (registeredSurface == layerSurface) { - listIter.remove(); - - switch(exclusiveEdge) { - using enum WLayerSurface::AnchorType; - case Top: - infoPtr->m_topExclusiveMargin -= exclusiveZone; - Q_EMIT topExclusiveMarginChanged(); - break; - case Bottom: - infoPtr->m_bottomExclusiveMargin -= exclusiveZone; - Q_EMIT bottomExclusiveMarginChanged(); - break; - case Left: - infoPtr->m_leftExclusiveMargin -= exclusiveZone; - Q_EMIT leftExclusiveMarginChanged(); - break; - case Right: - infoPtr->m_rightExclusiveMargin -= exclusiveZone; - Q_EMIT rightExclusiveMarginChanged(); - break; - default: - Q_UNREACHABLE(); - } - return true; - } - } - - return false; -} - -QJSValue Helper::getExclusiveMargins(WLayerSurface *layerSurface) -{ - auto [ output, infoPtr ] = getFirstOutputOfSurface(layerSurface); - QMargins margins{0, 0, 0, 0}; - - if (output) { - QMutableListIterator> listIter(infoPtr->registeredSurfaceList); - while (listIter.hasNext()) { - auto [ registeredSurface, exclusiveZone, exclusiveEdge ] = listIter.next(); - if (registeredSurface == layerSurface) - break; - switch(exclusiveEdge) { - using enum WLayerSurface::AnchorType; - case Top: - margins.setTop(margins.top() + exclusiveZone); - break; - case Bottom: - margins.setBottom(margins.bottom() + exclusiveZone); - break; - case Left: - margins.setLeft(margins.left() + exclusiveZone); - break; - case Right: - margins.setRight(margins.right() + exclusiveZone); - break; - default: - Q_UNREACHABLE(); - } - } - } - - QJSValue jsMargins = qmlEngine(this)->newObject(); // Can't use QMargins in QML - jsMargins.setProperty("top" , margins.top()); - jsMargins.setProperty("bottom", margins.bottom()); - jsMargins.setProperty("left", margins.left()); - jsMargins.setProperty("right", margins.right()); - return jsMargins; -} -quint32 Helper::getTopExclusiveMargin(WToplevelSurface *layerSurface) -{ - auto [ _, infoPtr ] = getFirstOutputOfSurface(layerSurface); - if (!infoPtr) - return 0; - return infoPtr->m_topExclusiveMargin; -} - -quint32 Helper::getBottomExclusiveMargin(WToplevelSurface *layerSurface) -{ - auto [ _, infoPtr ] = getFirstOutputOfSurface(layerSurface); - if (!infoPtr) - return 0; - return infoPtr->m_bottomExclusiveMargin; -} - -quint32 Helper::getLeftExclusiveMargin(WToplevelSurface *layerSurface) -{ - auto [ _, infoPtr ] = getFirstOutputOfSurface(layerSurface); - if (!infoPtr) - return 0; - return infoPtr->m_leftExclusiveMargin; -} - -quint32 Helper::getRightExclusiveMargin(WToplevelSurface *layerSurface) -{ - auto [ _, infoPtr ] = getFirstOutputOfSurface(layerSurface); - if (!infoPtr) - return 0; - return infoPtr->m_rightExclusiveMargin; -} - -void Helper::onSurfaceEnterOutput(WToplevelSurface *surface, WSurfaceItem *surfaceItem, WOutput *output) -{ - auto *info = getOutputInfo(output); - info->surfaceList.append(surface); - info->surfaceItemList.append(surfaceItem); -} - -void Helper::onSurfaceLeaveOutput(WToplevelSurface *surface, WSurfaceItem *surfaceItem, WOutput *output) -{ - auto *info = getOutputInfo(output); - info->surfaceList.removeOne(surface); - info->surfaceItemList.removeOne(surfaceItem); - // should delete OutputInfo if no surface? -} - -std::pair Helper::getFirstOutputOfSurface(WToplevelSurface *surface) -{ - for (auto zoneInfo: m_outputExclusiveZoneInfo) { - if (std::get(zoneInfo)->surfaceList.contains(surface)) - return zoneInfo; - } - return std::make_pair(nullptr, nullptr); -} - -void Helper::setSocketEnabled(bool newEnabled) -{ - if (m_socket) - m_socket->setEnabled(newEnabled); - else - qWarning() << "Can't set enabled for empty socket!"; -} - -WXdgDecorationManager *Helper::xdgDecorationManager() const -{ - return m_xdgDecorationManager; -} - -void Helper::setMovingItem(WSurfaceItem *newMovingItem) -{ - if (moveReiszeState.movingItem == newMovingItem) - return; - moveReiszeState.movingItem = newMovingItem; - emit movingItemChanged(); -} - -void Helper::stopMoveResize() -{ - if (moveReiszeState.surface) - moveReiszeState.surface->setResizeing(false); - - setResizingItem(nullptr); - setMovingItem(nullptr); - - moveReiszeState.surfaceItem = nullptr; - moveReiszeState.surface = nullptr; - moveReiszeState.seat = nullptr; - moveReiszeState.resizeEdgets = {0}; -} - -void Helper::startMove(WToplevelSurface *surface, WSurfaceItem *shell, WSeat *seat, int serial) -{ - stopMoveResize(); - - Q_UNUSED(serial) - - moveReiszeState.surfaceItem = shell; - moveReiszeState.surface = surface; - moveReiszeState.seat = seat; - moveReiszeState.resizeEdgets = {0}; - moveReiszeState.surfacePosOfStartMoveResize = getItemGlobalPosition(moveReiszeState.surfaceItem); - - setMovingItem(shell); -} - -void Helper::startResize(WToplevelSurface *surface, WSurfaceItem *shell, WSeat *seat, Qt::Edges edge, int serial) -{ - stopMoveResize(); - - Q_UNUSED(serial) - Q_ASSERT(edge != 0); - - moveReiszeState.surfaceItem = shell; - moveReiszeState.surface = surface; - moveReiszeState.seat = seat; - moveReiszeState.surfacePosOfStartMoveResize = getItemGlobalPosition(moveReiszeState.surfaceItem); - moveReiszeState.surfaceSizeOfStartMoveResize = moveReiszeState.surfaceItem->size(); - moveReiszeState.resizeEdgets = edge; - - surface->setResizeing(true); - setResizingItem(shell); -} - -void Helper::cancelMoveResize(WSurfaceItem *shell) -{ - if (moveReiszeState.surfaceItem != shell) - return; - stopMoveResize(); -} - -bool Helper::startDemoClient(const QString &socket) -{ -#ifdef START_DEMO - QProcess waylandClientDemo; - - waylandClientDemo.setProgram(PROJECT_BINARY_DIR"/examples/animationclient/animationclient"); - waylandClientDemo.setArguments({"-platform", "wayland"}); - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("WAYLAND_DISPLAY", socket); - - waylandClientDemo.setProcessEnvironment(env); - return waylandClientDemo.startDetached(); -#else - return false; -#endif -} - -WSurface *Helper::getFocusSurfaceFrom(QObject *object) -{ - auto item = WSurfaceItem::fromFocusObject(object); - return item ? item->surface() : nullptr; -} - -void Helper::allowNonDrmOutputAutoChangeMode(WOutput *output) -{ - output->safeConnect(&qw_output::notify_request_state, this, &Helper::onOutputRequeseState); -} - -void Helper::enableOutput(WOutput *output) -{ - // Enable on default - auto qwoutput = output->handle(); - // Don't care for WOutput::isEnabled, must do WOutput::commit here, - // In order to ensure trigger QWOutput::frame signal, WOutputRenderWindow - // needs this signal to render next frmae. Because QWOutput::frame signal - // maybe emit before WOutputRenderWindow::attach, if no commit here, - // WOutputRenderWindow will ignore this ouptut on render. - if (!qwoutput->property("_Enabled").toBool()) { - qwoutput->setProperty("_Enabled", true); - - if (!qwoutput->handle()->current_mode) { - auto mode = qwoutput->preferred_mode(); - if (mode) - output->setMode(mode); - } - output->enable(true); - bool ok = output->commit(); - Q_ASSERT(ok); - } -} - -bool Helper::beforeDisposeEvent(WSeat *seat, QWindow *watched, QInputEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - auto kevent = static_cast(event); - if (QKeySequence(kevent->keyCombination()) == QKeySequence::Quit) { - qApp->quit(); - return true; - } - } - - if (watched) { - if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TouchBegin) { - seat->setKeyboardFocusWindow(watched); - } else if (event->type() == QEvent::MouseMove && !seat->keyboardFocusWindow()) { - // TouchMove keep focus on first window - seat->setKeyboardFocusWindow(watched); - } - } - - if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress) { - seat->cursor()->setVisible(true); - } else if (event->type() == QEvent::TouchBegin) { - seat->cursor()->setVisible(false); - } - - if (moveReiszeState.surfaceItem && (seat == moveReiszeState.seat || moveReiszeState.seat == nullptr)) { - // for move resize - if (Q_LIKELY(event->type() == QEvent::MouseMove || event->type() == QEvent::TouchUpdate)) { - auto cursor = seat->cursor(); - Q_ASSERT(cursor); - QMouseEvent *ev = static_cast(event); - - if (moveReiszeState.resizeEdgets == 0) { - auto increment_pos = ev->globalPosition() - cursor->lastPressedOrTouchDownPosition(); - auto new_pos = moveReiszeState.surfacePosOfStartMoveResize + moveReiszeState.surfaceItem->parentItem()->mapFromGlobal(increment_pos); - moveReiszeState.surfaceItem->setPosition(new_pos); - } else { - auto increment_pos = moveReiszeState.surfaceItem->parentItem()->mapFromGlobal(ev->globalPosition() - cursor->lastPressedOrTouchDownPosition()); - QRectF geo(moveReiszeState.surfacePosOfStartMoveResize, moveReiszeState.surfaceSizeOfStartMoveResize); - - if (moveReiszeState.resizeEdgets & Qt::LeftEdge) - geo.setLeft(geo.left() + increment_pos.x()); - if (moveReiszeState.resizeEdgets & Qt::TopEdge) - geo.setTop(geo.top() + increment_pos.y()); - - if (moveReiszeState.resizeEdgets & Qt::RightEdge) - geo.setRight(geo.right() + increment_pos.x()); - if (moveReiszeState.resizeEdgets & Qt::BottomEdge) - geo.setBottom(geo.bottom() + increment_pos.y()); - - if (moveReiszeState.surfaceItem->resizeSurface(geo.size().toSize())) - moveReiszeState.surfaceItem->setPosition(geo.topLeft()); - } - - return true; - } else if (event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::TouchEnd) { - stopMoveResize(); - } - } - - return false; -} - -bool Helper::afterHandleEvent(WSeat *seat, WSurface *watched, QObject *surfaceItem, QObject *, QInputEvent *event) -{ - Q_UNUSED(seat) - - if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TouchBegin) { - // surfaceItem is qml type: XdgSurfaceItem or LayerSurfaceItem - auto toplevelSurface = qobject_cast(surfaceItem)->shellSurface(); - if (!toplevelSurface) - return false; - Q_ASSERT(toplevelSurface->surface() == watched); - if (auto *xdgSurface = qobject_cast(toplevelSurface)) { - // TODO: popupSurface should not inherit WToplevelSurface - if (xdgSurface->isPopup()) { - return false; - } - } - setActivateSurface(toplevelSurface); - } - - return false; -} - -bool Helper::unacceptedEvent(WSeat *, QWindow *, QInputEvent *event) -{ - if (event->isSinglePointEvent()) { - if (static_cast(event)->isBeginEvent()) - setActivateSurface(nullptr); - } - - return false; -} - -WToplevelSurface *Helper::activatedSurface() const -{ - return m_activateSurface; -} - -void Helper::setActivateSurface(WToplevelSurface *newActivate) -{ - if (m_activateSurface == newActivate) - return; - - if (newActivate && newActivate->doesNotAcceptFocus()) - return; - - if (m_activateSurface) { - if (newActivate) { - if (m_activateSurface->keyboardFocusPriority() > newActivate->keyboardFocusPriority()) - return; - } else { - if (m_activateSurface->keyboardFocusPriority() > 0) - return; - } - - m_activateSurface->setActivate(false); - } - m_activateSurface = newActivate; - if (newActivate) - newActivate->setActivate(true); - Q_EMIT activatedSurfaceChanged(); -} - -void Helper::onOutputRequeseState(wlr_output_event_request_state *newState) -{ - if (newState->state->committed & WLR_OUTPUT_STATE_MODE) { - auto output = qobject_cast(sender()); - - if (newState->state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM) { - const QSize size(newState->state->custom_mode.width, newState->state->custom_mode.height); - output->set_custom_mode(size.width(), size.height(), newState->state->custom_mode.refresh); - } else { - output->set_mode(newState->state->mode); - } - - output->commit(); - } -} - -OutputInfo* Helper::getOutputInfo(WOutput *output) -{ - for (const auto &[woutput, infoPtr]: m_outputExclusiveZoneInfo) - if (woutput == output) - return infoPtr; - auto infoPtr = new OutputInfo; - m_outputExclusiveZoneInfo.append(std::make_pair(output, infoPtr)); - return infoPtr; -} +WAYLIB_SERVER_USE_NAMESPACE int main(int argc, char *argv[]) { WRenderHelper::setupRendererBackend(); @@ -802,7 +17,7 @@ int main(int argc, char *argv[]) { qw_log::init(); WServer::initializeQPA(); -// QQuickStyle::setStyle("Material"); + // QQuickStyle::setStyle("Material"); QPointer helper; int quitCode = 0; @@ -812,17 +27,15 @@ int main(int argc, char *argv[]) { QGuiApplication::setQuitOnLastWindowClosed(false); QGuiApplication app(argc, argv); - QQmlApplicationEngine waylandEngine; - - waylandEngine.loadFromModule("Tinywl", "Main"); + QmlEngine qmlEngine; - auto window = waylandEngine.rootObjects().first()->findChild(); - Q_ASSERT(window); - - helper = waylandEngine.singletonInstance("Tinywl", "Helper"); - Q_ASSERT(helper); + QObject::connect(&qmlEngine, &QQmlEngine::quit, &app, &QGuiApplication::quit); + QObject::connect(&qmlEngine, &QQmlEngine::exit, &app, [] (int code) { + qApp->exit(code); + }); - helper->initProtocols(window, &waylandEngine); + Helper *helper = qmlEngine.singletonInstance("Tinywl", "Helper"); + helper->init(); quitCode = app.exec(); } diff --git a/examples/tinywl/output.cpp b/examples/tinywl/output.cpp new file mode 100644 index 00000000..a4dcdb3f --- /dev/null +++ b/examples/tinywl/output.cpp @@ -0,0 +1,462 @@ +// Copyright (C) 2024 JiDe Zhang . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "output.h" +#include "surfacewrapper.h" +#include "helper.h" +#include "rootsurfacecontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(qLcLayerShell, "tinywl.shell.layer", QtWarningMsg) + +Output *Output::createPrimary(WOutput *output, QQmlEngine *engine, QObject *parent) +{ + QQmlComponent delegate(engine, "Tinywl", "PrimaryOutput"); + QObject *obj = delegate.beginCreate(engine->rootContext()); + delegate.setInitialProperties(obj, { + {"forceSoftwareCursor", output->handle()->is_x11()} + }); + delegate.completeCreate(); + WOutputItem *outputItem = qobject_cast(obj); + Q_ASSERT(outputItem); + QQmlEngine::setObjectOwnership(outputItem, QQmlEngine::CppOwnership); + outputItem->setOutput(output); + + auto o = new Output(outputItem, parent); + o->m_type = Type::Primary; + obj->setParent(o); + + o->minimizedSurfaces->setFilter([] (SurfaceWrapper *s) { + return s->isMinimized(); + }); + + o->connect(outputItem, &WOutputItem::geometryChanged, o, &Output::layoutAllSurfaces); + + auto contentItem = Helper::instance()->window()->contentItem(); + outputItem->setParentItem(contentItem); + + o->m_taskBar = Helper::instance()->qmlEngine()->createTaskBar(o, contentItem); + o->m_taskBar->setZ(RootSurfaceContainer::TaskBarZOrder); + + o->m_menuBar = Helper::instance()->qmlEngine()->createMenuBar(outputItem, contentItem); + o->m_menuBar->setZ(RootSurfaceContainer::MenuBarZOrder); + o->setExclusiveZone(Qt::TopEdge, o->m_menuBar, o->m_menuBar->height()); + + return o; +} + +Output *Output::createCopy(WOutput *output, Output *proxy, QQmlEngine *engine, QObject *parent) +{ + QQmlComponent delegate(engine, "Tinywl", "CopyOutput"); + QObject *obj = delegate.createWithInitialProperties({ + {"targetOutputItem", QVariant::fromValue(proxy->outputItem())}, + }, engine->rootContext()); + + WOutputItem *outputItem = qobject_cast(obj); + Q_ASSERT(outputItem); + QQmlEngine::setObjectOwnership(outputItem, QQmlEngine::CppOwnership); + outputItem->setOutput(output); + + auto o = new Output(outputItem, parent); + o->m_type = Type::Proxy; + o->m_proxy = proxy; + obj->setParent(o); + + auto contentItem = Helper::instance()->window()->contentItem(); + outputItem->setParentItem(contentItem); + o->updatePrimaryOutputHardwareLayers(); + connect(o->m_outputViewport, &WOutputViewport::hardwareLayersChanged, + o, &Output::updatePrimaryOutputHardwareLayers); + + return o; +} + +Output::Output(WOutputItem *output, QObject *parent) + : SurfaceListModel(parent) + , m_item(output) + , minimizedSurfaces(new SurfaceFilterModel(this)) +{ + m_outputViewport = output->property("screenViewport").value(); +} + +Output::~Output() +{ + if (m_taskBar) { + delete m_taskBar; + m_taskBar = nullptr; + } + + if (m_menuBar) { + delete m_menuBar; + m_menuBar = nullptr; + } + + if (m_item) { + delete m_item; + m_item = nullptr; + } +} + +bool Output::isPrimary() const +{ + return m_type == Type::Primary; +} + +void Output::addSurface(SurfaceWrapper *surface) +{ + Q_ASSERT(!hasSurface(surface)); + SurfaceListModel::addSurface(surface); + + if (surface->type() == SurfaceWrapper::Type::Layer) { + auto layer = qobject_cast(surface->shellSurface()); + layer->safeConnect(&WLayerSurface::layerPropertiesChanged, this, &Output::layoutLayerSurfaces); + + layoutLayerSurfaces(); + } else { + auto layoutSurface = [surface, this] { + layoutNonLayerSurface(surface, {}); + }; + + connect(surface, &SurfaceWrapper::widthChanged, this, layoutSurface); + connect(surface, &SurfaceWrapper::heightChanged, this, layoutSurface); + layoutSurface(); + } +} + +void Output::removeSurface(SurfaceWrapper *surface) +{ + Q_ASSERT(hasSurface(surface)); + SurfaceListModel::removeSurface(surface); + surface->disconnect(this); + + if (surface->type() == SurfaceWrapper::Type::Layer) { + if (auto ss = surface->shellSurface()) { + ss->safeDisconnect(this); + removeExclusiveZone(ss); + } + layoutLayerSurfaces(); + } +} + +WOutput *Output::output() const +{ + auto o = m_item->output(); + Q_ASSERT(o); + return o; +} + +WOutputItem *Output::outputItem() const +{ + return m_item; +} + +void Output::setExclusiveZone(Qt::Edge edge, QObject *object, int value) +{ + Q_ASSERT(value > 0); + removeExclusiveZone(object); + switch (edge) { + case Qt::TopEdge: + m_topExclusiveZones.append(std::make_pair(object, value)); + m_exclusiveZone.setTop(m_exclusiveZone.top() + value); + break; + case Qt::BottomEdge: + m_bottomExclusiveZones.append(std::make_pair(object, value)); + m_exclusiveZone.setBottom(m_exclusiveZone.bottom() + value); + break; + case Qt::LeftEdge: + m_leftExclusiveZones.append(std::make_pair(object, value)); + m_exclusiveZone.setLeft(m_exclusiveZone.left() + value); + break; + case Qt::RightEdge: + m_rightExclusiveZones.append(std::make_pair(object, value)); + m_exclusiveZone.setRight(m_exclusiveZone.right() + value); + break; + default: + Q_UNREACHABLE_RETURN(); + } +} + +bool Output::removeExclusiveZone(QObject *object) +{ + auto finder = [object](const auto &pair) { return pair.first == object; }; + auto tmp = std::find_if(m_topExclusiveZones.begin(), m_topExclusiveZones.end(), finder); + if (tmp != m_topExclusiveZones.end()) { + m_topExclusiveZones.erase(tmp); + m_exclusiveZone.setTop(m_exclusiveZone.top() - tmp->second); + Q_ASSERT(m_exclusiveZone.top() >= 0); + return true; + } + + tmp = std::find_if(m_bottomExclusiveZones.begin(), m_bottomExclusiveZones.end(), finder); + if (tmp != m_bottomExclusiveZones.end()) { + m_bottomExclusiveZones.erase(tmp); + m_exclusiveZone.setBottom(m_exclusiveZone.bottom() - tmp->second); + Q_ASSERT(m_exclusiveZone.bottom() >= 0); + return true; + } + + tmp = std::find_if(m_leftExclusiveZones.begin(), m_leftExclusiveZones.end(), finder); + if (tmp != m_leftExclusiveZones.end()) { + m_leftExclusiveZones.erase(tmp); + m_exclusiveZone.setLeft(m_exclusiveZone.left() - tmp->second); + Q_ASSERT(m_exclusiveZone.left() >= 0); + return true; + } + + tmp = std::find_if(m_rightExclusiveZones.begin(), m_rightExclusiveZones.end(), finder); + if (tmp != m_rightExclusiveZones.end()) { + m_rightExclusiveZones.erase(tmp); + m_exclusiveZone.setRight(m_exclusiveZone.right() - tmp->second); + Q_ASSERT(m_exclusiveZone.right() >= 0); + return true; + } + + return false; +} + +void Output::layoutLayerSurface(SurfaceWrapper *surface) +{ + WLayerSurface* layer = qobject_cast(surface->shellSurface()); + Q_ASSERT(layer); + + auto validGeo = layer->exclusiveZone() == -1 ? this->rect() : validRect(); + validGeo = validGeo.marginsRemoved(QMargins(layer->leftMargin(), + layer->topMargin(), + layer->rightMargin(), + layer->bottomMargin())); + auto anchor = layer->ancher(); + QRectF surfaceGeo(QPointF(0, 0), layer->desiredSize()); + + if (anchor.testFlags(WLayerSurface::AnchorType::Left | WLayerSurface::AnchorType::Right)) { + surfaceGeo.moveLeft(validGeo.left()); + surfaceGeo.setWidth(validGeo.width()); + } else if (anchor & WLayerSurface::AnchorType::Left) { + surfaceGeo.moveLeft(validGeo.left()); + } else if (anchor & WLayerSurface::AnchorType::Right) { + surfaceGeo.moveRight(validGeo.right()); + } else { + surfaceGeo.moveLeft(validGeo.left() + (validGeo.width() - surfaceGeo.width()) / 2); + } + + if (anchor.testFlags(WLayerSurface::AnchorType::Top | WLayerSurface::AnchorType::Bottom)) { + surfaceGeo.moveTop(validGeo.top()); + surfaceGeo.setHeight(validGeo.height()); + } else if (anchor & WLayerSurface::AnchorType::Top) { + surfaceGeo.moveTop(validGeo.top()); + } else if (anchor & WLayerSurface::AnchorType::Bottom) { + surfaceGeo.moveBottom(validGeo.bottom()); + } else { + surfaceGeo.moveTop(validGeo.top() + (validGeo.height() - surfaceGeo.height()) / 2); + } + + if (layer->exclusiveZone() > 0) { + // TODO:: support `set_exclusive_edge` in layer-shell v5, need wlroots 0.19 + switch (layer->getExclusiveZoneEdge()) { + using enum WLayerSurface::AnchorType; + case Top: + setExclusiveZone(Qt::TopEdge, layer, layer->exclusiveZone()); + break; + case Bottom: + setExclusiveZone(Qt::BottomEdge, layer, layer->exclusiveZone()); + break; + case Left: + setExclusiveZone(Qt::LeftEdge, layer, layer->exclusiveZone()); + break; + case Right: + setExclusiveZone(Qt::RightEdge, layer, layer->exclusiveZone()); + break; + default: + qCWarning(qLcLayerShell) << layer->appId() << " has set exclusive zone, but exclusive edge is invalid!"; + break; + } + } + + surface->setSize(surfaceGeo.size()); + surface->setPosition(surfaceGeo.topLeft()); +} + +void Output::layoutLayerSurfaces() +{ + auto oldExclusiveZone = m_exclusiveZone; + + for (auto *s : surfaces()) { + if (s->type() != SurfaceWrapper::Type::Layer) + continue; + removeExclusiveZone(s->shellSurface()); + } + + for (auto *s : surfaces()) { + if (s->type() != SurfaceWrapper::Type::Layer) + continue; + layoutLayerSurface(s); + } + + if (oldExclusiveZone != m_exclusiveZone) { + layoutNonLayerSurfaces(); + emit exclusiveZoneChanged(); + } +} + +void Output::layoutNonLayerSurface(SurfaceWrapper *surface, const QSizeF &sizeDiff) +{ + Q_ASSERT(surface->type() != SurfaceWrapper::Type::Layer); + surface->setFullscreenGeometry(geometry()); + const auto validGeo = this->validGeometry(); + surface->setMaximizedGeometry(validGeo); + + QRectF normalGeo = surface->normalGeometry(); + do { + if (surface->positionAutomatic()) { + if (normalGeo.size().isEmpty()) + return; + + SurfaceWrapper* parentSurfaceWrapper = surface->parentSurface(); + if (parentSurfaceWrapper) { + auto xdgSurface = qobject_cast(surface->shellSurface()); + auto inputPopupSurface = qobject_cast(surface->shellSurface()); + if ((xdgSurface && xdgSurface->isPopup()) || inputPopupSurface) { + QPointF dPos = xdgSurface ? xdgSurface->getPopupPosition() : inputPopupSurface->cursorRect().topLeft(); + QPointF topLeft; + // TODO: remove parentSurfaceWrapper->surfaceItem()->x() + topLeft.setX(parentSurfaceWrapper->x() + parentSurfaceWrapper->surfaceItem()->x() + dPos.x()); + topLeft.setY(parentSurfaceWrapper->y() + parentSurfaceWrapper->surfaceItem()->y() + dPos.y()); + auto output = surface->ownsOutput()->outputItem(); + + normalGeo.setWidth(std::min(output->width(), surface->width())); + normalGeo.setHeight(std::min(output->height(), surface->height())); + surface->setSize(normalGeo.size()); + + if (topLeft.x() + normalGeo.width() > output->x() + output->width()) + topLeft.setX(output->x() + output->width() - normalGeo.width()); + if (topLeft.y() + normalGeo.height() > output->y() + output->height()) + topLeft.setY(output->y() + output->height() - normalGeo.height()); + normalGeo.moveTopLeft(topLeft); + surface->moveNormalGeometryInOutput(normalGeo.topLeft()); + } else { + QPointF dPos { + (parentSurfaceWrapper->width() - surface->width()) / 2, + (parentSurfaceWrapper->height() - surface->height()) / 2 + }; + QPointF topLeft; + topLeft.setX(parentSurfaceWrapper->x() + dPos.x()); + topLeft.setY(parentSurfaceWrapper->y() + dPos.y()); + normalGeo.moveTopLeft(topLeft); + surface->moveNormalGeometryInOutput(normalGeo.topLeft()); + } + } else { + normalGeo.moveCenter(validGeo.center()); + normalGeo.moveTop(qMax(normalGeo.top(), validGeo.top())); + normalGeo.moveLeft(qMax(normalGeo.left(), validGeo.left())); + surface->moveNormalGeometryInOutput(normalGeo.topLeft()); + } + } else if (!sizeDiff.isNull() && sizeDiff.isValid()) { + const QSizeF outputSize = m_item->size(); + const auto xScale = outputSize.width() / (outputSize.width() - sizeDiff.width()); + const auto yScale = outputSize.height() / (outputSize.height() - sizeDiff.height()); + normalGeo.moveLeft(normalGeo.x() * xScale); + normalGeo.moveTop(normalGeo.y() * yScale); + surface->moveNormalGeometryInOutput(normalGeo.topLeft()); + } else { + break; + } + } while (false); +} + +void Output::layoutNonLayerSurfaces() +{ + const auto currentSize = validRect().size(); + const auto sizeDiff = m_lastSizeOnLayoutNonLayerSurfaces.isValid() + ? currentSize - m_lastSizeOnLayoutNonLayerSurfaces + : QSizeF(0, 0); + m_lastSizeOnLayoutNonLayerSurfaces = currentSize; + + for (SurfaceWrapper *surface : surfaces()) { + if (surface->type() == SurfaceWrapper::Type::Layer) + continue; + layoutNonLayerSurface(surface, sizeDiff); + } +} + +void Output::layoutAllSurfaces() +{ + layoutLayerSurfaces(); + layoutNonLayerSurfaces(); +} + +void Output::updatePositionFromLayout() +{ + WOutputLayout * layout = output()->layout(); + Q_ASSERT(layout); + + auto *layoutOutput = layout->get(output()->nativeHandle()); + QPointF pos(layoutOutput->x, layoutOutput->y); + m_item->setPosition(pos); +} + +std::pair Output::getOutputItemProperty() +{ + WOutputViewport *viewportCopy = outputItem()->findChild({}, Qt::FindDirectChildrenOnly); + Q_ASSERT(viewportCopy); + auto textureProxy = outputItem()->findChild(); + Q_ASSERT(textureProxy); + + return std::make_pair(viewportCopy, textureProxy); +} + +void Output::updatePrimaryOutputHardwareLayers() +{ + WOutputViewport *viewportPrimary = screenViewport(); + std::pair copyOutput = getOutputItemProperty(); + const auto layers = viewportPrimary->hardwareLayers(); + for (auto layer : layers) { + if (m_hardwareLayersOfPrimaryOutput.removeOne(layer)) + continue; + Helper::instance()->window()->attach(layer, copyOutput.first, viewportPrimary, copyOutput.second); + } + + for (auto oldLayer : std::as_const(m_hardwareLayersOfPrimaryOutput)) { + Helper::instance()->window()->detach(oldLayer, copyOutput.first); + } + + m_hardwareLayersOfPrimaryOutput = layers; +} + +QMargins Output::exclusiveZone() const +{ + return m_exclusiveZone; +} + +QRectF Output::rect() const +{ + return QRectF(QPointF(0, 0), m_item->size()); +} + +QRectF Output::geometry() const +{ + return QRectF(m_item->position(), m_item->size()); +} + +QRectF Output::validRect() const +{ + return rect().marginsRemoved(m_exclusiveZone); +} + +WOutputViewport *Output::screenViewport() const +{ + return m_outputViewport; +} + +QRectF Output::validGeometry() const +{ + return geometry().marginsRemoved(m_exclusiveZone); +} diff --git a/examples/tinywl/output.h b/examples/tinywl/output.h new file mode 100644 index 00000000..7aa76cea --- /dev/null +++ b/examples/tinywl/output.h @@ -0,0 +1,105 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#pragma once + +#include "surfacecontainer.h" + +#include +#include +#include +#include +#include + +Q_MOC_INCLUDE() + +Q_DECLARE_LOGGING_CATEGORY(qLcLayerShell) + +WAYLIB_SERVER_BEGIN_NAMESPACE +class WOutput; +class WOutputItem; +class WOutputViewport; +class WOutputLayout; +class WOutputLayer; +class WQuickTextureProxy; +class WSeat; +WAYLIB_SERVER_END_NAMESPACE + +WAYLIB_SERVER_USE_NAMESPACE + +class SurfaceWrapper; +class Output : public SurfaceListModel +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(QMargins exclusiveZone READ exclusiveZone NOTIFY exclusiveZoneChanged FINAL) + Q_PROPERTY(QRectF validRect READ validRect NOTIFY exclusiveZoneChanged FINAL) + Q_PROPERTY(WOutputItem* outputItem MEMBER m_item CONSTANT) + Q_PROPERTY(SurfaceListModel* minimizedSurfaces MEMBER minimizedSurfaces CONSTANT) + Q_PROPERTY(WOutputViewport* screenViewport MEMBER m_outputViewport CONSTANT) + +public: + enum class Type { + Primary, + Proxy + }; + + static Output *createPrimary(WOutput *output, QQmlEngine *engine, QObject *parent = nullptr); + static Output *createCopy(WOutput *output, Output *proxy, QQmlEngine *engine, QObject *parent = nullptr); + + explicit Output(WOutputItem *output, QObject *parent = nullptr); + ~Output(); + + bool isPrimary() const; + + void addSurface(SurfaceWrapper *surface) override; + void removeSurface(SurfaceWrapper *surface) override; + + WOutput *output() const; + WOutputItem *outputItem() const; + + QMargins exclusiveZone() const; + QRectF rect() const; + QRectF geometry() const; + QRectF validRect() const; + QRectF validGeometry() const; + WOutputViewport *screenViewport() const; + void updatePositionFromLayout(); + +signals: + void exclusiveZoneChanged(); + void moveResizeFinised(); + +public Q_SLOTS: + void updatePrimaryOutputHardwareLayers(); + +private: + friend class SurfaceWrapper; + + void setExclusiveZone(Qt::Edge edge, QObject *object, int value); + bool removeExclusiveZone(QObject *object); + void layoutLayerSurface(SurfaceWrapper *surface); + void layoutLayerSurfaces(); + void layoutNonLayerSurface(SurfaceWrapper *surface, const QSizeF &sizeDiff); + void layoutNonLayerSurfaces(); + void layoutAllSurfaces(); + std::pair getOutputItemProperty(); + + Type m_type; + WOutputItem *m_item; + Output *m_proxy = nullptr; + SurfaceFilterModel *minimizedSurfaces; + QPointer m_taskBar; + QPointer m_menuBar; + WOutputViewport *m_outputViewport; + + QMargins m_exclusiveZone; + QList> m_topExclusiveZones; + QList> m_bottomExclusiveZones; + QList> m_leftExclusiveZones; + QList> m_rightExclusiveZones; + + QSizeF m_lastSizeOnLayoutNonLayerSurfaces; + QList m_hardwareLayersOfPrimaryOutput; +}; + +Q_DECLARE_OPAQUE_POINTER(WAYLIB_SERVER_NAMESPACE::WOutputItem*) diff --git a/examples/tinywl/qmlengine.cpp b/examples/tinywl/qmlengine.cpp new file mode 100644 index 00000000..8a95ba51 --- /dev/null +++ b/examples/tinywl/qmlengine.cpp @@ -0,0 +1,181 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "output.h" +#include "qmlengine.h" +#include "surfacewrapper.h" +#include "wallpaperprovider.h" +#include "workspace.h" + +#include + +#include + +QmlEngine::QmlEngine(QObject *parent) + : QQmlApplicationEngine(parent) + , titleBarComponent(this, "Tinywl", "TitleBar") + , decorationComponent(this, "Tinywl", "Decoration") + , windowMenuComponent(this, "Tinywl", "WindowMenu") + , taskBarComponent(this, "Tinywl", "TaskBar") + , surfaceContent(this, "Tinywl", "SurfaceContent") + , shadowComponent(this, "Tinywl", "Shadow") + , geometryAnimationComponent(this, "Tinywl", "GeometryAnimation") + , menuBarComponent(this, "Tinywl", "OutputMenuBar") + , workspaceSwitcher(this, "Tinywl", "WorkspaceSwitcher") +{ +} + +QQuickItem *QmlEngine::createTitleBar(SurfaceWrapper *surface, QQuickItem *parent) +{ + auto context = qmlContext(parent); + auto obj = titleBarComponent.beginCreate(context); + titleBarComponent.setInitialProperties(obj, { + {"surface", QVariant::fromValue(surface)} + }); + auto item = qobject_cast(obj); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + titleBarComponent.completeCreate(); + + return item; +} + +QQuickItem *QmlEngine::createDecoration(SurfaceWrapper *surface, QQuickItem *parent) +{ + auto context = qmlContext(parent); + auto obj = decorationComponent.beginCreate(context); + decorationComponent.setInitialProperties(obj, { + {"surface", QVariant::fromValue(surface)} + }); + auto item = qobject_cast(obj); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + decorationComponent.completeCreate(); + + return item; +} + +QObject *QmlEngine::createWindowMenu(QObject *parent) +{ + auto context = qmlContext(parent); + auto obj = windowMenuComponent.beginCreate(context); + + obj->setParent(parent); + windowMenuComponent.completeCreate(); + + return obj; +} + +QQuickItem *QmlEngine::createBorder(SurfaceWrapper *surface, QQuickItem *parent) +{ + auto context = qmlContext(parent); + auto obj = borderComponent.beginCreate(context); + borderComponent.setInitialProperties(obj, { + {"surface", QVariant::fromValue(surface)} + }); + auto item = qobject_cast(obj); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + borderComponent.completeCreate(); + + return item; +} + +QQuickItem *QmlEngine::createTaskBar(Output *output, QQuickItem *parent) +{ + auto context = qmlContext(parent); + auto obj = taskBarComponent.beginCreate(context); + taskBarComponent.setInitialProperties(obj, { + {"output", QVariant::fromValue(output)} + }); + auto item = qobject_cast(obj); + qDebug() << taskBarComponent.errorString(); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + taskBarComponent.completeCreate(); + + return item; +} + +QQuickItem *QmlEngine::createShadow(QQuickItem *parent) +{ + auto context = qmlContext(parent); + auto obj = shadowComponent.beginCreate(context); + auto item = qobject_cast(obj); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + shadowComponent.completeCreate(); + + return item; +} + +QQuickItem *QmlEngine::createGeometryAnimation(SurfaceWrapper *surface, + const QRectF &startGeo, + const QRectF &endGeo, + QQuickItem *parent) +{ + auto context = qmlContext(parent); + auto obj = geometryAnimationComponent.beginCreate(context); + geometryAnimationComponent.setInitialProperties(obj, { + {"surface", QVariant::fromValue(surface)}, + {"fromGeometry", QVariant::fromValue(startGeo)}, + {"toGeometry", QVariant::fromValue(endGeo)}, + }); + auto item = qobject_cast(obj); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + geometryAnimationComponent.completeCreate(); + + return item; +} + +QQuickItem *QmlEngine::createMenuBar(WOutputItem *output, QQuickItem *parent) +{ + auto context = qmlContext(parent); + auto obj = menuBarComponent.beginCreate(context); + menuBarComponent.setInitialProperties(obj, { + {"output", QVariant::fromValue(output)} + }); + auto item = qobject_cast(obj); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + menuBarComponent.completeCreate(); + + return item; +} + +QQuickItem *QmlEngine::createWorkspaceSwitcher(Workspace *parent, WorkspaceModel *from, WorkspaceModel *to) +{ + auto context = qmlContext(parent); + auto obj = workspaceSwitcher.beginCreate(context); + workspaceSwitcher.setInitialProperties(obj, { + {"parent", QVariant::fromValue(parent)}, + {"from", QVariant::fromValue(from)}, + {"to", QVariant::fromValue(to)}, + }); + auto item = qobject_cast(obj); + Q_ASSERT(item); + item->setParent(parent); + item->setParentItem(parent); + workspaceSwitcher.completeCreate(); + + return item; +} + +WallpaperImageProvider *QmlEngine::wallpaperImageProvider() +{ + if (!wallpaperProvider) { + wallpaperProvider = new WallpaperImageProvider; + Q_ASSERT(!this->imageProvider("wallpaper")); + addImageProvider("wallpaper", wallpaperProvider); + } + + return wallpaperProvider; +} diff --git a/examples/tinywl/qmlengine.h b/examples/tinywl/qmlengine.h new file mode 100644 index 00000000..97eab695 --- /dev/null +++ b/examples/tinywl/qmlengine.h @@ -0,0 +1,57 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QQuickItem; +QT_END_NAMESPACE + +WAYLIB_SERVER_BEGIN_NAMESPACE +class WOutputItem; +WAYLIB_SERVER_END_NAMESPACE + +WAYLIB_SERVER_USE_NAMESPACE + +class WallpaperImageProvider; +class SurfaceWrapper; +class Output; +class Workspace; +class WorkspaceModel; +class QmlEngine : public QQmlApplicationEngine +{ + Q_OBJECT +public: + explicit QmlEngine(QObject *parent = nullptr); + + QQuickItem *createTitleBar(SurfaceWrapper *surface, QQuickItem *parent); + QQuickItem *createDecoration(SurfaceWrapper *surface, QQuickItem *parent); + QObject *createWindowMenu(QObject *parent); + QQuickItem *createBorder(SurfaceWrapper *surface, QQuickItem *parent); + QQuickItem *createTaskBar(Output *output, QQuickItem *parent); + QQuickItem *createShadow(QQuickItem *parent); + QQuickItem *createGeometryAnimation(SurfaceWrapper *surface, const QRectF &startGeo, + const QRectF &endGeo, QQuickItem *parent); + QQuickItem *createMenuBar(WOutputItem *output, QQuickItem *parent); + QQuickItem *createWorkspaceSwitcher(Workspace *parent, WorkspaceModel *from, WorkspaceModel *to); + QQmlComponent *surfaceContentComponent() { return &surfaceContent; } + WallpaperImageProvider *wallpaperImageProvider(); + +private: + QQmlComponent titleBarComponent; + QQmlComponent decorationComponent; + QQmlComponent windowMenuComponent; + QQmlComponent borderComponent; + QQmlComponent taskBarComponent; + QQmlComponent surfaceContent; + QQmlComponent shadowComponent; + QQmlComponent geometryAnimationComponent; + QQmlComponent menuBarComponent; + QQmlComponent workspaceSwitcher; + WallpaperImageProvider *wallpaperProvider = nullptr; +}; diff --git a/examples/tinywl/res/xx.jpg b/examples/tinywl/res/xx.jpg new file mode 100644 index 00000000..ae98229c Binary files /dev/null and b/examples/tinywl/res/xx.jpg differ diff --git a/examples/tinywl/rootsurfacecontainer.cpp b/examples/tinywl/rootsurfacecontainer.cpp new file mode 100644 index 00000000..b32cea62 --- /dev/null +++ b/examples/tinywl/rootsurfacecontainer.cpp @@ -0,0 +1,472 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "rootsurfacecontainer.h" +#include "helper.h" +#include "surfacewrapper.h" +#include "output.h" + +#include +#include +#include +#include +#include + +#include + +WAYLIB_SERVER_USE_NAMESPACE + +OutputListModel::OutputListModel(QObject *parent) + : ObjectListModel("output", parent) +{ + +} + +RootSurfaceContainer::RootSurfaceContainer(QQuickItem *parent) + : SurfaceContainer(parent) + , m_outputLayout(new WOutputLayout(this)) + , m_outputModel(new OutputListModel(this)) + , m_cursor(new WCursor(this)) +{ + m_cursor->setLayout(m_outputLayout); + m_cursor->setEventWindow(window()); + + connect(m_outputLayout, &WOutputLayout::implicitWidthChanged, this, [this] { + const auto width = m_outputLayout->implicitWidth(); + window()->setWidth(width); + setWidth(width); + }); + + connect(m_outputLayout, &WOutputLayout::implicitHeightChanged, this, [this] { + const auto height = m_outputLayout->implicitHeight(); + window()->setHeight(height); + setHeight(height); + }); + + connect(m_outputLayout, &WOutputLayout::notify_change, this, [this] { + for (auto output : std::as_const(outputs())) { + output->updatePositionFromLayout(); + } + + ensureCursorVisible(); + + // for (auto s : m_surfaceContainer->surfaces()) { + // ensureSurfaceNormalPositionValid(s); + // updateSurfaceOutputs(s); + // } + }); + + m_dargSurfaceItem = new WSurfaceItem(window()->contentItem()); + m_dargSurfaceItem->setZ(static_cast>(WOutputLayout::Layer::Cursor) - 1); + m_dargSurfaceItem->setFlags(WSurfaceItem::DontCacheLastBuffer); + + m_cursor->safeConnect(&WCursor::positionChanged, this, [this] { + m_dargSurfaceItem->setPosition(m_cursor->position()); + }); + + m_cursor->safeConnect(&WCursor::requestedDragSurfaceChanged, this, [this] { + m_dargSurfaceItem->setSurface(m_cursor->requestedDragSurface()); + }); +} + +SurfaceWrapper *RootSurfaceContainer::getSurface(WSurface *surface) const +{ + const auto surfaces = this->surfaces(); + for (const auto &wrapper: surfaces) { + if (wrapper->surface() == surface) + return wrapper; + } + return nullptr; +} + +SurfaceWrapper *RootSurfaceContainer::getSurface(WToplevelSurface *surface) const +{ + const auto surfaces = this->surfaces(); + for (const auto &wrapper: surfaces) { + if (wrapper->shellSurface() == surface) + return wrapper; + } + return nullptr; +} + +void RootSurfaceContainer::destroyForSurface(WSurface *surface) +{ + auto wrapper = getSurface(surface); + if (wrapper == moveResizeState.surface) + endMoveResize(); + + delete wrapper; +} + +void RootSurfaceContainer::addOutput(Output *output) +{ + m_outputModel->addObject(output); + m_outputLayout->autoAdd(output->output()); + if (!m_primaryOutput) + setPrimaryOutput(output); + + SurfaceContainer::addOutput(output); +} + +void RootSurfaceContainer::removeOutput(Output *output) +{ + m_outputModel->removeObject(output); + SurfaceContainer::removeOutput(output); + + if (moveResizeState.surface && moveResizeState.surface->ownsOutput() == output) { + endMoveResize(); + } + + if (m_primaryOutput == output) { + const auto outputs = m_outputLayout->outputs(); + if (!outputs.isEmpty()) { + auto newPrimaryOutput = Helper::instance()->getOutput(outputs.first()); + setPrimaryOutput(newPrimaryOutput); + } + } + + // ensure cursor within output + const auto outputPos = output->outputItem()->position(); + if (output->geometry().contains(m_cursor->position()) && m_primaryOutput) { + const auto posInOutput = m_cursor->position() - outputPos; + const auto newCursorPos = m_primaryOutput->outputItem()->position() + posInOutput; + + if (m_primaryOutput->geometry().contains(newCursorPos)) + Helper::instance()->setCursorPosition(newCursorPos); + else + Helper::instance()->setCursorPosition(m_primaryOutput->geometry().center()); + } + + m_outputLayout->remove(output->output()); +} + +void RootSurfaceContainer::beginMoveResize(SurfaceWrapper *surface, Qt::Edges edges) +{ + if (surface->surfaceState() != SurfaceWrapper::State::Normal + || surface->isAnimationRunning()) + return; + + Q_ASSERT(!moveResizeState.surface); + moveResizeState.surface = surface; + moveResizeState.startGeometry = surface->geometry(); + moveResizeState.resizeEdges = edges; + surface->setPositionAutomatic(false); +} + +void RootSurfaceContainer::doMoveResize(const QPointF &incrementPos) +{ + Q_ASSERT(moveResizeState.surface); + + if (moveResizeState.resizeEdges) { + QRectF geo = moveResizeState.startGeometry; + + if (moveResizeState.resizeEdges & Qt::LeftEdge) + geo.setLeft(geo.left() + incrementPos.x()); + if (moveResizeState.resizeEdges & Qt::TopEdge) + geo.setTop(geo.top() + incrementPos.y()); + + if (moveResizeState.resizeEdges & Qt::RightEdge) + geo.setRight(geo.right() + incrementPos.x()); + if (moveResizeState.resizeEdges & Qt::BottomEdge) + geo.setBottom(geo.bottom() + incrementPos.y()); + + moveResizeState.surface->resize(geo.size()); + } else { + auto new_pos = moveResizeState.startGeometry.topLeft() + incrementPos; + moveResizeState.surface->setPosition(new_pos); + } +} + +void RootSurfaceContainer::cancelMoveResize(SurfaceWrapper *surface) +{ + if (moveResizeState.surface != surface) + return; + endMoveResize(); +} + +void RootSurfaceContainer::endMoveResize() +{ + if (!moveResizeState.surface) + return; + + auto o = moveResizeState.surface->ownsOutput(); + moveResizeState.surface->shellSurface()->setResizeing(false); + + if (!o || !moveResizeState.surface->surface()->outputs().contains(o->output())) { + o = cursorOutput(); + Q_ASSERT(o); + moveResizeState.surface->setOwnsOutput(o); + } + + ensureSurfaceNormalPositionValid(moveResizeState.surface); + + moveResizeState.surface = nullptr; + Q_EMIT moveResizeFinised(); +} + +SurfaceWrapper *RootSurfaceContainer::moveResizeSurface() const +{ + return moveResizeState.surface; +} + +void RootSurfaceContainer::startMove(SurfaceWrapper *surface) +{ + endMoveResize(); + beginMoveResize(surface, Qt::Edges{0}); + + Helper::instance()->activeSurface(surface); +} + +void RootSurfaceContainer::startResize(SurfaceWrapper *surface, Qt::Edges edges) +{ + endMoveResize(); + Q_ASSERT(edges != 0); + + beginMoveResize(surface, edges); + surface->shellSurface()->setResizeing(true); + Helper::instance()->activeSurface(surface); +} + +void RootSurfaceContainer::addSurface(SurfaceWrapper *) +{ + Q_UNREACHABLE_RETURN(); +} + +void RootSurfaceContainer::removeSurface(SurfaceWrapper *) +{ + Q_UNREACHABLE_RETURN(); +} + +void RootSurfaceContainer::addBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface) +{ + SurfaceContainer::addBySubContainer(sub, surface); + + connect(surface, &SurfaceWrapper::requestMove, this, [this] { + auto surface = qobject_cast(sender()); + Q_ASSERT(surface); + startMove(surface); + }); + connect(surface, &SurfaceWrapper::requestResize, this, [this] (Qt::Edges edges) { + auto surface = qobject_cast(sender()); + Q_ASSERT(surface); + startResize(surface, edges); + }); + connect(surface, &SurfaceWrapper::surfaceStateChanged, this, [surface, this] { + if (surface->surfaceState() == SurfaceWrapper::State::Minimized + || surface->surfaceState() == SurfaceWrapper::State::Tiling) + return; + Helper::instance()->activeSurface(surface); + }); + + if (!surface->ownsOutput()) { + auto parentSurface = surface->parentSurface(); + auto output = parentSurface ? parentSurface->ownsOutput() : primaryOutput(); + + if (auto xdgSurface = qobject_cast(surface->shellSurface())) { + if (xdgSurface->isPopup() && parentSurface->type() != SurfaceWrapper::Type::Layer) { + auto pos = parentSurface->position() + parentSurface->surfaceItem()->position() + xdgSurface->getPopupPosition(); + if (auto op = m_outputLayout->output_at(pos.x(), pos.y())) + output = Helper::instance()->getOutput(WOutput::fromHandle(qw_output::from(op))); + } + } + surface->setOwnsOutput(output); + } + + connect(surface, &SurfaceWrapper::geometryChanged, this, [this, surface] { + updateSurfaceOutputs(surface); + }); + + updateSurfaceOutputs(surface); + Helper::instance()->activeSurface(surface, Qt::OtherFocusReason); +} + +void RootSurfaceContainer::removeBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface) +{ + if (moveResizeState.surface == surface) + endMoveResize(); + + SurfaceContainer::removeBySubContainer(sub, surface); +} + +bool RootSurfaceContainer::filterSurfaceGeometryChanged(SurfaceWrapper *surface, QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (surface != moveResizeState.surface) + return false; + if (moveResizeState.setSurfacePositionForAnchorEdgets) { + Q_ASSERT(newGeometry.size() == oldGeometry.size()); + return true; + } + + if (moveResizeState.resizeEdges != 0) { + QRectF geometry = newGeometry; + if (moveResizeState.resizeEdges & Qt::RightEdge) + geometry.moveLeft(oldGeometry.left()); + if (moveResizeState.resizeEdges & Qt::BottomEdge) + geometry.moveTop(oldGeometry.top()); + if (moveResizeState.resizeEdges & Qt::LeftEdge) + geometry.moveRight(oldGeometry.right()); + if (moveResizeState.resizeEdges & Qt::TopEdge) + geometry.moveBottom(oldGeometry.bottom()); + + if (geometry.topLeft() != newGeometry.topLeft()) { + newGeometry = geometry; + moveResizeState.setSurfacePositionForAnchorEdgets = true; + surface->setPosition(geometry.topLeft()); + moveResizeState.setSurfacePositionForAnchorEdgets = false; + } + } + + return false; +} + +bool RootSurfaceContainer::filterSurfaceStateChange(SurfaceWrapper *surface, SurfaceWrapper::State newState, SurfaceWrapper::State oldState) +{ + Q_UNUSED(oldState); + Q_UNUSED(newState); + return surface == moveResizeState.surface; +} + +WOutputLayout *RootSurfaceContainer::outputLayout() const +{ + return m_outputLayout; +} + +WCursor *RootSurfaceContainer::cursor() const +{ + return m_cursor; +} + +Output *RootSurfaceContainer::cursorOutput() const +{ + Q_ASSERT(m_cursor->layout() == m_outputLayout); + const auto &pos = m_cursor->position(); + auto o = m_outputLayout->output_at(pos.x(), pos.y()); + if (!o) + return nullptr; + + return Helper::instance()->getOutput(WOutput::fromHandle(qw_output::from(o))); +} + +Output *RootSurfaceContainer::primaryOutput() const +{ + return m_primaryOutput; +} + +void RootSurfaceContainer::setPrimaryOutput(Output *newPrimaryOutput) +{ + if (m_primaryOutput == newPrimaryOutput) + return; + m_primaryOutput = newPrimaryOutput; + emit primaryOutputChanged(); +} + +const QList &RootSurfaceContainer::outputs() const +{ + return m_outputModel->objects(); +} + +void RootSurfaceContainer::ensureCursorVisible() +{ + const auto cursorPos = m_cursor->position(); + if (m_outputLayout->output_at(cursorPos.x(), cursorPos.y())) + return; + + if (m_primaryOutput) { + Helper::instance()->setCursorPosition(m_primaryOutput->geometry().center()); + } +} + +void RootSurfaceContainer::updateSurfaceOutputs(SurfaceWrapper *surface) +{ + const QRectF geometry = surface->geometry(); + auto outputs = m_outputLayout->getIntersectedOutputs(geometry.toRect()); + surface->setOutputs(outputs); +} + +static qreal pointToRectMinDistance(const QPointF &pos, const QRectF &rect) { + if (rect.contains(pos)) + return 0; + return std::min({std::abs(rect.x() - pos.x()), std::abs(rect.y() - pos.y()), + std::abs(rect.right() - pos.x()), std::abs(rect.bottom() - pos.y())}); +} + +static QRectF adjustRectToMakePointVisible(const QRectF& inputRect, const QPointF& absolutePoint, const QList& visibleAreas) +{ + Q_ASSERT(inputRect.contains(absolutePoint)); + QRectF adjustedRect = inputRect; + + QRectF targetRect; + qreal distanceToTargetRect = std::numeric_limits::max(); + for (const QRectF& area : visibleAreas) { + Q_ASSERT(!area.isEmpty()); + if (area.contains(absolutePoint)) + return adjustedRect; + const auto distance = pointToRectMinDistance(absolutePoint, area); + if (distance < distanceToTargetRect) { + distanceToTargetRect = distance; + targetRect = area; + } + } + Q_ASSERT(!targetRect.isEmpty()); + + if (absolutePoint.x() < targetRect.x()) + adjustedRect.moveLeft(adjustedRect.x() + targetRect.x() - absolutePoint.x()); + else if (absolutePoint.x() > targetRect.right()) + adjustedRect.moveRight(adjustedRect.right() + targetRect.right() - absolutePoint.x()); + + if (absolutePoint.y() < targetRect.y()) + adjustedRect.moveTop(adjustedRect.y() + targetRect.y() - absolutePoint.y()); + else if (absolutePoint.y() > targetRect.bottom()) + adjustedRect.moveBottom(adjustedRect.bottom() + targetRect.bottom() - absolutePoint.y()); + + return adjustedRect; +} + +void RootSurfaceContainer::ensureSurfaceNormalPositionValid(SurfaceWrapper *surface) +{ + if (surface->type() == SurfaceWrapper::Type::Layer) + return; + + auto normalGeo = surface->normalGeometry(); + if (normalGeo.size().isEmpty()) + return; + + auto output = surface->ownsOutput(); + if (!output) + return; + + QList outputRects; + outputRects.reserve(outputs().size()); + for (auto o : outputs()) + outputRects << o->validGeometry(); + + // Ensure window is not outside the screen + const QPointF mustVisiblePosOfSurface(qMin(normalGeo.right(), normalGeo.x() + 20), + qMin(normalGeo.bottom(), normalGeo.y() + 20)); + normalGeo = adjustRectToMakePointVisible(normalGeo, mustVisiblePosOfSurface, outputRects); + + // Ensure titlebar is not outside the screen + const auto titlebarGeometry = surface->titlebarGeometry().translated(normalGeo.topLeft()); + if (titlebarGeometry.isValid()) { + bool titlebarGeometryAdjusted = false; + for (auto r : std::as_const(outputRects)) { + if ((r & titlebarGeometry).isEmpty()) + continue; + if (titlebarGeometry.top() < r.top()) { + normalGeo.moveTop(normalGeo.top() + r.top() - titlebarGeometry.top()); + titlebarGeometryAdjusted = true; + } + } + + if (!titlebarGeometryAdjusted) { + normalGeo = adjustRectToMakePointVisible(normalGeo, titlebarGeometry.topLeft(), outputRects); + } + } + + surface->moveNormalGeometryInOutput(normalGeo.topLeft()); +} + +OutputListModel *RootSurfaceContainer::outputModel() const +{ + return m_outputModel; +} diff --git a/examples/tinywl/rootsurfacecontainer.h b/examples/tinywl/rootsurfacecontainer.h new file mode 100644 index 00000000..be9b9ac1 --- /dev/null +++ b/examples/tinywl/rootsurfacecontainer.h @@ -0,0 +1,111 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#pragma once + +#include "surfacecontainer.h" + +#include + +WAYLIB_SERVER_BEGIN_NAMESPACE +class WSurface; +class WSurfaceItem; +class WToplevelSurface; +class WOutputLayout; +class WCursor; +WAYLIB_SERVER_END_NAMESPACE + +WAYLIB_SERVER_USE_NAMESPACE + +class OutputListModel : public ObjectListModel +{ + Q_OBJECT + QML_ANONYMOUS + +public: + explicit OutputListModel(QObject *parent = nullptr); +}; + +class RootSurfaceContainer : public SurfaceContainer +{ + Q_OBJECT + Q_PROPERTY(WAYLIB_SERVER_NAMESPACE::WOutputLayout *outputLayout READ outputLayout CONSTANT FINAL) + Q_PROPERTY(WAYLIB_SERVER_NAMESPACE::WCursor *cursor READ cursor CONSTANT FINAL) + Q_PROPERTY(Output *primaryOutput READ primaryOutput WRITE setPrimaryOutput NOTIFY primaryOutputChanged FINAL) + Q_PROPERTY(OutputListModel* outputModel READ outputModel CONSTANT FINAL) + +public: + explicit RootSurfaceContainer(QQuickItem *parent); + + enum ContainerZOrder { + BackgroundZOrder = -2, + BottomZOrder = -1, + NormalZOrder = 0, + TopZOrder = 1, + OverlayZOrder = 2, + TaskBarZOrder = 3, + MenuBarZOrder = 3, + PopupZOrder = 4, + }; + + SurfaceWrapper *getSurface(WSurface *surface) const; + SurfaceWrapper *getSurface(WToplevelSurface *surface) const; + void destroyForSurface(WSurface *surface); + + WOutputLayout *outputLayout() const; + WCursor *cursor() const; + + Output *cursorOutput() const; + Output *primaryOutput() const; + void setPrimaryOutput(Output *newPrimaryOutput); + const QList &outputs() const; + + void addOutput(Output *output) override; + void removeOutput(Output *output) override; + + void beginMoveResize(SurfaceWrapper *surface, Qt::Edges edges); + void doMoveResize(const QPointF &incrementPos); + void endMoveResize(); + SurfaceWrapper *moveResizeSurface() const; + + OutputListModel *outputModel() const; + +public slots: + void startMove(SurfaceWrapper *surface); + void startResize(SurfaceWrapper *surface, Qt::Edges edges); + void cancelMoveResize(SurfaceWrapper *surface); + +signals: + void primaryOutputChanged(); + void moveResizeFinised(); + +private: + void addSurface(SurfaceWrapper *surface) override; + void removeSurface(SurfaceWrapper *surface) override; + + void addBySubContainer(SurfaceContainer *, SurfaceWrapper *surface) override; + void removeBySubContainer(SurfaceContainer *, SurfaceWrapper *surface) override; + + bool filterSurfaceGeometryChanged(SurfaceWrapper *surface, QRectF &newGeometry, const QRectF &oldGeometry) override; + bool filterSurfaceStateChange(SurfaceWrapper *surface, SurfaceWrapper::State newState, SurfaceWrapper::State oldState) override; + + void ensureCursorVisible(); + void updateSurfaceOutputs(SurfaceWrapper *surface); + void ensureSurfaceNormalPositionValid(SurfaceWrapper *surface); + + WOutputLayout *m_outputLayout = nullptr; + OutputListModel *m_outputModel = nullptr; + QPointer m_primaryOutput; + WCursor *m_cursor = nullptr; + WSurfaceItem *m_dargSurfaceItem = nullptr; + + // for move resize + struct { + SurfaceWrapper *surface = nullptr; + QRectF startGeometry; + Qt::Edges resizeEdges; + bool setSurfacePositionForAnchorEdgets = false; + } moveResizeState; +}; + +Q_DECLARE_OPAQUE_POINTER(WAYLIB_SERVER_NAMESPACE::WOutputLayout*) +Q_DECLARE_OPAQUE_POINTER(WAYLIB_SERVER_NAMESPACE::WCursor*) diff --git a/examples/tinywl/surfacecontainer.cpp b/examples/tinywl/surfacecontainer.cpp new file mode 100644 index 00000000..866c89c2 --- /dev/null +++ b/examples/tinywl/surfacecontainer.cpp @@ -0,0 +1,272 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "surfacecontainer.h" +#include "output.h" +#include "rootsurfacecontainer.h" + +SurfaceListModel::SurfaceListModel(QObject *parent) + : ObjectListModel("surface", parent) +{ + +} + +QHash SurfaceListModel::roleNames() const +{ + auto roleMap = ObjectListModel::roleNames(); + roleMap.insert(Qt::InitialSortOrderRole, "orderIndex"); + + return roleMap; +} + +QMap SurfaceListModel::itemData(const QModelIndex &index) const +{ + auto datas = ObjectListModel::itemData(index); + if (datas.isEmpty()) + return datas; + + auto surface = surfaces().at(index.row()); + auto container = surface->container(); + const auto orderIndex = container->childItems().indexOf(surface); + Q_ASSERT(orderIndex >= 0); + datas.insert(Qt::InitialSortOrderRole, orderIndex); + + return datas; +} + +QVariant SurfaceListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_objects.count()) + return {}; + + if (role == Qt::InitialSortOrderRole) { + auto surface = surfaces().at(index.row()); + auto container = surface->container(); + const auto orderIndex = container->childItems().indexOf(surface); + Q_ASSERT(orderIndex >= 0); + return orderIndex; + } + + return ObjectListModel::data(index, role); +} + +SurfaceFilterModel::SurfaceFilterModel(SurfaceListModel *parent) + : SurfaceListModel(parent) +{ + connect(parent, &SurfaceListModel::surfaceAdded, + this, &SurfaceFilterModel::initForSourceSurface); + connect(parent, &SurfaceListModel::surfaceRemoved, + this, [this] (SurfaceWrapper *surface) { + removeSurface(surface); + m_properties.erase(surface); + }); +} + +void SurfaceFilterModel::setFilter(std::function filter) +{ + m_filter = filter; + m_properties.clear(); + + for (auto surface : parent()->surfaces()) { + initForSourceSurface(surface); + } +} + +void SurfaceFilterModel::initForSourceSurface(SurfaceWrapper *surface) +{ + if (m_filter) { + makeBindingForSourceSurface(surface); + } + updateSurfaceVisibility(surface); +} + +void SurfaceFilterModel::makeBindingForSourceSurface(SurfaceWrapper *surface) +{ + Property &p = m_properties[surface]; + + if (!p.init) { + p.init = true; + p.notifier = p.prop.addNotifier([this, surface] { + updateSurfaceVisibility(surface); + }); + } + + p.setBinding([this, surface] { + return m_filter(surface); + }); +} + +void SurfaceFilterModel::updateSurfaceVisibility(SurfaceWrapper *surface) +{ + if (!m_filter) { + if (hasSurface(surface)) + return; + addSurface(surface); + return; + } + + Q_ASSERT(m_properties.contains(surface)); + Q_ASSERT(parent()->hasSurface(surface)); + const Property &p = m_properties.at(surface); + Q_ASSERT(p.init); + if (p.prop.value()) { + addSurface(surface); + } else { + removeSurface(surface); + } +} + +SurfaceContainer::SurfaceContainer(QQuickItem *parent) + : QQuickItem(parent) + , m_model(new SurfaceListModel(this)) +{ + +} + +SurfaceContainer::SurfaceContainer(SurfaceContainer *parent) + : SurfaceContainer(static_cast(parent)) +{ + +} + +SurfaceContainer::~SurfaceContainer() +{ + if (!m_model->surfaces().isEmpty()) { + qWarning() << "SurfaceContainer destroyed with surfaces still attached:" << m_model->surfaces(); + } +} + +RootSurfaceContainer *SurfaceContainer::rootContainer() const +{ + SurfaceContainer *root = const_cast(this); + while (auto p = root->parentContainer()) { + root = p; + } + + auto r = qobject_cast(root); + Q_ASSERT(r); + return r; +} + +SurfaceContainer *SurfaceContainer::parentContainer() const +{ + return qobject_cast(parent()); +} + +QList SurfaceContainer::subContainers() const +{ + return findChildren(Qt::FindDirectChildrenOnly); +} + +void SurfaceContainer::setQmlEngine(QQmlEngine *engine) +{ + engine->setContextForObject(this, engine->rootContext()); + + const auto subContainers = this->subContainers(); + for (auto sub : subContainers) { + sub->setQmlEngine(engine); + } +} + +void SurfaceContainer::addSurface(SurfaceWrapper *surface) +{ + doAddSurface(surface, true); +} + +void SurfaceContainer::removeSurface(SurfaceWrapper *surface) +{ + doRemoveSurface(surface, true); +} + +void SurfaceContainer::addOutput(Output *output) +{ + Q_ASSERT(output->isPrimary()); + const auto subContainers = this->subContainers(); + for (auto sub : subContainers) { + sub->addOutput(output); + } +} + +void SurfaceContainer::removeOutput(Output *output) +{ + const auto subContainers = this->subContainers(); + for (auto sub : subContainers) { + sub->removeOutput(output); + } +} + +void SurfaceContainer::geometryChange(const QRectF &newGeo, const QRectF &oldGeo) +{ + const auto subContainers = this->subContainers(); + for (SurfaceContainer *c : subContainers) { + c->setPosition(newGeo.topLeft()); + c->setSize(newGeo.size()); + } + + QQuickItem::geometryChange(newGeo, oldGeo); +} + +bool SurfaceContainer::doAddSurface(SurfaceWrapper *surface, bool setContainer) +{ + if (m_model->hasSurface(surface)) + return false; + + if (setContainer) { + Q_ASSERT(!surface->container()); + surface->setContainer(this); + surface->setParent(this); + } + + m_model->addSurface(surface); + emit surfaceAdded(surface); + + if (auto p = parentContainer()) + p->addBySubContainer(this, surface); + + return true; +} + +bool SurfaceContainer::doRemoveSurface(SurfaceWrapper *surface, bool setContainer) +{ + if (!m_model->hasSurface(surface)) + return false; + + if (setContainer) { + Q_ASSERT(surface->container() == this); + surface->setContainer(nullptr); + } + + m_model->removeSurface(surface); + emit surfaceRemoved(surface); + + if (auto p = parentContainer()) + p->removeBySubContainer(this, surface); + + return true; +} + +void SurfaceContainer::addBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface) +{ + Q_UNUSED(sub); + doAddSurface(surface, false); +} + +void SurfaceContainer::removeBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface) +{ + Q_UNUSED(sub); + doRemoveSurface(surface, false); +} + +bool SurfaceContainer::filterSurfaceGeometryChanged(SurfaceWrapper *surface, QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (auto p = parentContainer()) + return p->filterSurfaceGeometryChanged(surface, newGeometry, oldGeometry); + return false; +} + +bool SurfaceContainer::filterSurfaceStateChange(SurfaceWrapper *surface, SurfaceWrapper::State newState, SurfaceWrapper::State oldState) +{ + if (auto p = parentContainer()) + return p->filterSurfaceStateChange(surface, newState, oldState); + return false; +} diff --git a/examples/tinywl/surfacecontainer.h b/examples/tinywl/surfacecontainer.h new file mode 100644 index 00000000..8d2915e8 --- /dev/null +++ b/examples/tinywl/surfacecontainer.h @@ -0,0 +1,226 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#pragma once + +#include "surfacewrapper.h" + +#include +#include +#include +#include + +#include + +Q_MOC_INCLUDE("rootsurfacecontainer.h") + +template +class ObjectListModel : public QAbstractListModel +{ +public: + explicit ObjectListModel(QByteArrayView objectName, QObject *parent = nullptr) + : QAbstractListModel(parent) + , m_name(objectName.toByteArray()) + { + + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override { + if (parent.isValid()) + return 0; + + return m_objects.count(); + } + + QVariant data(const QModelIndex &index, int role) const override { + if (!index.isValid() || index.row() >= m_objects.count()) + return {}; + + if (role == Qt::DisplayRole) + return QVariant::fromValue(m_objects.at(index.row())); + + return {}; + } + QMap itemData(const QModelIndex &index) const override { + if (!index.isValid() || index.row() >= m_objects.count()) + return {}; + + QMap data; + data.insert(Qt::DisplayRole, QVariant::fromValue(m_objects.at(index.row()))); + return data; + } + Qt::ItemFlags flags(const QModelIndex &index) const override { + Q_UNUSED(index); + return Qt::ItemIsSelectable|Qt::ItemIsEnabled; + } + QHash roleNames() const override { + return {{Qt::DisplayRole, m_name}}; + } + + bool addObject(T *object) { + if (m_objects.contains(object)) + return false; + + beginInsertRows(QModelIndex(), m_objects.count(), m_objects.count()); + m_objects.append(object); + endInsertRows(); + return true; + } + bool removeObject(T *object) { + int index = m_objects.indexOf(object); + if (index < 0) + return false; + beginRemoveRows({}, index, index); + m_objects.removeAt(index); + endRemoveRows(); + return true; + } + bool hasObject(T *object) const { + return m_objects.contains(object); + } + const QList &objects() const { + return m_objects; + } + +protected: + QByteArray m_name; + QList m_objects; +}; + +class SurfaceListModel : public ObjectListModel +{ + Q_OBJECT + QML_ANONYMOUS + +public: + explicit SurfaceListModel(QObject *parent = nullptr); + + virtual void addSurface(SurfaceWrapper *surface) { + if (addObject(surface)) + emit surfaceAdded(surface); + } + virtual void removeSurface(SurfaceWrapper *surface) { + if (removeObject(surface)) + emit surfaceRemoved(surface); + } + + QHash roleNames() const override; + QMap itemData(const QModelIndex &index) const override; + QVariant data(const QModelIndex &index, int role) const override; + + inline const QList &surfaces() const { + return objects(); + } + + inline bool hasSurface(SurfaceWrapper *surface) const { + return hasObject(surface); + } + +signals: + void surfaceAdded(SurfaceWrapper *surface); + void surfaceRemoved(SurfaceWrapper *surface); + +private: + using ObjectListModel::addObject; + using ObjectListModel::removeObject; + using ObjectListModel::objects; +}; + +class SurfaceFilterModel : public SurfaceListModel +{ + Q_OBJECT + QML_ANONYMOUS + +public: + explicit SurfaceFilterModel(SurfaceListModel *parent); + + inline SurfaceListModel *parent() const { + auto op = QObject::parent(); + auto p = qobject_cast(op); + Q_ASSERT(p); + return p; + } + void setFilter(std::function filter); + +private: + using SurfaceListModel::addSurface; + using SurfaceListModel::removeSurface; + + void initForSourceSurface(SurfaceWrapper *surface); + void makeBindingForSourceSurface(SurfaceWrapper *surface); + void updateSurfaceVisibility(SurfaceWrapper *surface); + + std::function m_filter; + + struct Property { + Property() + : prop(false) + { + + } + + template + inline void setBinding(F f) { + prop.setBinding(f); + } + + bool init = false; + QProperty prop; + QPropertyNotifier notifier; + }; + + std::map m_properties; +}; + +class Output; +class RootSurfaceContainer; +class SurfaceContainer : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(SurfaceListModel* model READ model CONSTANT FINAL) + Q_PROPERTY(RootSurfaceContainer* root READ rootContainer CONSTANT FINAL) + QML_ANONYMOUS + +public: + explicit SurfaceContainer(QQuickItem *parent = nullptr); + explicit SurfaceContainer(SurfaceContainer *parent); + ~SurfaceContainer() override; + + RootSurfaceContainer *rootContainer() const; + SurfaceContainer *parentContainer() const; + QList subContainers() const; + void setQmlEngine(QQmlEngine *engine); + + virtual void addSurface(SurfaceWrapper *surface); + virtual void removeSurface(SurfaceWrapper *surface); + + virtual void addOutput(Output *output); + virtual void removeOutput(Output *output); + + const QList &surfaces() const { + return m_model->surfaces(); + } + + SurfaceListModel *model() const { + return m_model; + } + +signals: + void surfaceAdded(SurfaceWrapper *surface); + void surfaceRemoved(SurfaceWrapper *surface); + +protected: + friend class SurfaceWrapper; + + void geometryChange(const QRectF &newGeo, const QRectF &oldGeo) override; + + bool doAddSurface(SurfaceWrapper *surface, bool setContainer); + bool doRemoveSurface(SurfaceWrapper *surface, bool setContainer); + + virtual void addBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface); + virtual void removeBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface); + + virtual bool filterSurfaceGeometryChanged(SurfaceWrapper *surface, QRectF &newGeometry, const QRectF &oldGeometry); + virtual bool filterSurfaceStateChange(SurfaceWrapper *surface, SurfaceWrapper::State newState, SurfaceWrapper::State oldState); + + SurfaceListModel *m_model = nullptr; +}; diff --git a/examples/tinywl/surfaceproxy.cpp b/examples/tinywl/surfaceproxy.cpp new file mode 100644 index 00000000..9ef2c1f7 --- /dev/null +++ b/examples/tinywl/surfaceproxy.cpp @@ -0,0 +1,237 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "surfaceproxy.h" +#include "surfacewrapper.h" +#include "qmlengine.h" + +SurfaceProxy::SurfaceProxy(QQuickItem *parent) + : QQuickItem(parent) +{ + +} + +SurfaceWrapper *SurfaceProxy::surface() const +{ + return m_sourceSurface; +} + +void SurfaceProxy::setSurface(SurfaceWrapper *newSurface) +{ + if (m_sourceSurface == newSurface) + return; + m_sourceSurface = newSurface; + + if (m_proxySurface) { + m_proxySurface->deleteLater(); + m_proxySurface = nullptr; + } + + if (m_sourceSurface) { + m_proxySurface = new SurfaceWrapper(m_sourceSurface->m_engine, + m_sourceSurface->m_shellSurface, + m_sourceSurface->type(), this); + m_proxySurface->setTransformOrigin(QQuickItem::TransformOrigin::TopLeft); + if (!m_fullProxy) { + Q_ASSERT(!m_shadow); + m_shadow = m_sourceSurface->m_engine->createShadow(this); + m_shadow->setProperty("radius", radius()); + m_shadow->stackBefore(m_proxySurface); + } + + auto item = m_proxySurface->surfaceItem(); + if (m_live) { + item->setFlags(WSurfaceItem::RejectEvent); + } else { + item->setFlags(WSurfaceItem::RejectEvent | WSurfaceItem::NonLive); + } + item->setDelegate(m_sourceSurface->surfaceItem()->delegate()); + + connect(m_sourceSurface, &SurfaceWrapper::destroyed, this, [this] { + Q_ASSERT(m_proxySurface); + setSurface(nullptr); + }); + connect(m_sourceSurface->surfaceItem(), &WSurfaceItem::delegateChanged, + this, [this] { + Q_ASSERT(m_proxySurface); + auto sender = m_sourceSurface->surfaceItem(); + m_proxySurface->surfaceItem()->setDelegate(sender->delegate()); + }); + connect(m_sourceSurface, &SurfaceWrapper::noTitleBarChanged, + this, &SurfaceProxy::updateProxySurfaceTitleBarAndDecoration); + connect(m_sourceSurface, &SurfaceWrapper::radiusChanged, this, &SurfaceProxy::onSourceRadiusChanged); + connect(m_sourceSurface, &SurfaceWrapper::noDecorationChanged, + this, &SurfaceProxy::updateProxySurfaceTitleBarAndDecoration); + connect(m_sourceSurface, &SurfaceWrapper::noCornerRadiusChanged, + this, &SurfaceProxy::updateProxySurfaceTitleBarAndDecoration); + + connect(m_proxySurface, &SurfaceWrapper::widthChanged, this, &SurfaceProxy::updateImplicitSize); + connect(m_proxySurface, &SurfaceWrapper::heightChanged, this, &SurfaceProxy::updateImplicitSize); + + updateImplicitSize(); + updateProxySurfaceScale(); + updateProxySurfaceTitleBarAndDecoration(); + } else { + m_shadow->deleteLater(); + m_shadow = nullptr; + } + + emit surfaceChanged(); +} + +void SurfaceProxy::geometryChange(const QRectF &newGeo, const QRectF &oldGeo) +{ + QQuickItem::geometryChange(newGeo, oldGeo); + + if (m_proxySurface) { + updateProxySurfaceScale(); + if (m_shadow) + m_shadow->setSize(newGeo.size()); + } +} + +void SurfaceProxy::updateProxySurfaceScale() +{ + if (size().isEmpty()) + return; + + QSizeF surfaceSize = m_proxySurface->size(); + surfaceSize.scale(size(), Qt::KeepAspectRatio); + + if (surfaceSize.width() < m_proxySurface->width()) { + m_proxySurface->setScale(surfaceSize.width() / m_proxySurface->width()); + m_proxySurface->setRadius(radius() / m_proxySurface->scale()); + } else { + m_proxySurface->setScale(1.0); + m_proxySurface->setRadius(radius()); + } +} + +void SurfaceProxy::updateProxySurfaceTitleBarAndDecoration() +{ + if (m_fullProxy) { + m_proxySurface->setNoTitleBar(m_sourceSurface->noTitleBar()); + m_proxySurface->setNoDecoration(m_sourceSurface->noDecoration()); + m_proxySurface->setNoCornerRadius(m_sourceSurface->noCornerRadius()); + } else { + m_proxySurface->setNoTitleBar(m_sourceSurface->noTitleBar()); + m_proxySurface->setNoDecoration(true); + m_proxySurface->setNoCornerRadius(false); + } +} + +void SurfaceProxy::updateImplicitSize() +{ + if (!m_proxySurface) { + return; + } + + const auto size = m_proxySurface->size(); + if (size.isEmpty()) { + return; + } + + const auto scaledSize = size.scaled(m_maxSize, Qt::KeepAspectRatio); + const auto scale = scaledSize.width() / size.width(); + setImplicitSize(size.width() * scale, size.height() * scale); +} + +void SurfaceProxy::onSourceRadiusChanged() +{ + m_proxySurface->setRadius(radius() / m_proxySurface->scale()); + if (m_shadow) + m_shadow->setProperty("radius", radius()); + if (m_radius < 0) + emit radiusChanged(); +} + +qreal SurfaceProxy::radius() const +{ + if (m_radius >= 0) + return m_radius; + return m_sourceSurface ? m_sourceSurface->radius() : 0; +} + +void SurfaceProxy::setRadius(qreal newRadius) +{ + if (qFuzzyCompare(m_radius, newRadius)) + return; + m_radius = newRadius; + if (m_proxySurface) { + m_proxySurface->setRadius(radius() / m_proxySurface->scale()); + if (m_shadow) + m_shadow->setProperty("radius", radius()); + } + + emit radiusChanged(); +} + +void SurfaceProxy::resetRadius() +{ + setRadius(-1); +} + +bool SurfaceProxy::live() const +{ + return m_live; +} + +void SurfaceProxy::setLive(bool newLive) +{ + if (m_live == newLive) + return; + m_live = newLive; + if (m_proxySurface) { + auto item = m_proxySurface->surfaceItem(); + if (m_live) { + item->setFlags(item->flags() & ~WSurfaceItem::NonLive); + } else { + item->setFlags(item->flags() | WSurfaceItem::NonLive); + } + } + + emit liveChanged(); +} + +QSizeF SurfaceProxy::maxSize() const +{ + return m_maxSize; +} + +void SurfaceProxy::setMaxSize(const QSizeF &newMaxSize) +{ + if (m_maxSize == newMaxSize) + return; + m_maxSize = newMaxSize; + updateImplicitSize(); + + emit maxSizeChanged(); +} + +bool SurfaceProxy::fullProxy() const +{ + return m_fullProxy; +} + +void SurfaceProxy::setFullProxy(bool newFullProxy) +{ + if (m_fullProxy == newFullProxy) + return; + m_fullProxy = newFullProxy; + + if (m_proxySurface) { + if (m_fullProxy) { + if (m_shadow) { + m_shadow->deleteLater(); + m_shadow = nullptr; + } + } else if (!m_shadow) { + m_shadow = m_sourceSurface->m_engine->createShadow(this); + m_shadow->setProperty("radius", radius()); + m_shadow->stackBefore(m_proxySurface); + } + updateProxySurfaceTitleBarAndDecoration(); + } + + emit fullProxyChanged(); +} diff --git a/examples/tinywl/surfaceproxy.h b/examples/tinywl/surfaceproxy.h new file mode 100644 index 00000000..776de72a --- /dev/null +++ b/examples/tinywl/surfaceproxy.h @@ -0,0 +1,59 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +class SurfaceWrapper; +class SurfaceProxy : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(SurfaceWrapper* surface READ surface WRITE setSurface NOTIFY surfaceChanged FINAL) + Q_PROPERTY(qreal radius READ radius WRITE setRadius RESET resetRadius NOTIFY radiusChanged FINAL) + Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged FINAL) + Q_PROPERTY(QSizeF maxSize READ maxSize WRITE setMaxSize NOTIFY maxSizeChanged FINAL) + Q_PROPERTY(bool fullProxy READ fullProxy WRITE setFullProxy NOTIFY fullProxyChanged FINAL) + QML_ELEMENT + +public: + explicit SurfaceProxy(QQuickItem *parent = nullptr); + + SurfaceWrapper *surface() const; + void setSurface(SurfaceWrapper *newSurface); + + qreal radius() const; + void setRadius(qreal newRadius); + void resetRadius(); + + bool live() const; + void setLive(bool newLive); + + QSizeF maxSize() const; + void setMaxSize(const QSizeF &newMaxSize); + + bool fullProxy() const; + void setFullProxy(bool newFullProxy); + +signals: + void surfaceChanged(); + void radiusChanged(); + void liveChanged(); + void maxSizeChanged(); + void fullProxyChanged(); + +private: + void geometryChange(const QRectF &newGeo, const QRectF &oldGeo) override; + void updateProxySurfaceScale(); + void updateProxySurfaceTitleBarAndDecoration(); + void updateImplicitSize(); + void onSourceRadiusChanged(); + + SurfaceWrapper *m_sourceSurface = nullptr; + SurfaceWrapper *m_proxySurface = nullptr; + QQuickItem *m_shadow = nullptr; + qreal m_radius = -1; + bool m_live = true; + bool m_fullProxy = false; + QSizeF m_maxSize; +}; diff --git a/examples/tinywl/surfacewrapper.cpp b/examples/tinywl/surfacewrapper.cpp new file mode 100644 index 00000000..b981673a --- /dev/null +++ b/examples/tinywl/surfacewrapper.cpp @@ -0,0 +1,967 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "surfacewrapper.h" +#include "qmlengine.h" +#include "output.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +SurfaceWrapper::SurfaceWrapper(QmlEngine *qmlEngine, WToplevelSurface *shellSurface, Type type, QQuickItem *parent) + : QQuickItem(parent) + , m_engine(qmlEngine) + , m_shellSurface(shellSurface) + , m_type(type) + , m_positionAutomatic(true) + , m_visibleDecoration(true) + , m_clipInOutput(false) + , m_noDecoration(true) + , m_titleBarState(TitleBarState::Default) + , m_noCornerRadius(false) + , m_alwaysOnTop(false) +{ + QQmlEngine::setContextForObject(this, qmlEngine->rootContext()); + + if (type == Type::XWayland) { + m_surfaceItem = new WXWaylandSurfaceItem(this); + } else if (type == Type::Layer) { + m_surfaceItem = new WLayerSurfaceItem(this); + } else if (type == Type::InputPopup) { + m_surfaceItem = new WInputPopupSurfaceItem(this); + } else { + m_surfaceItem = new WXdgSurfaceItem(this); + } + + QQmlEngine::setContextForObject(m_surfaceItem, qmlEngine->rootContext()); + m_surfaceItem->setDelegate(qmlEngine->surfaceContentComponent()); + m_surfaceItem->setResizeMode(WSurfaceItem::ManualResize); + m_surfaceItem->setShellSurface(shellSurface); + + shellSurface->safeConnect(&WToplevelSurface::requestMinimize, this, &SurfaceWrapper::requestMinimize); + shellSurface->safeConnect(&WToplevelSurface::requestCancelMinimize, this, &SurfaceWrapper::requestCancelMinimize); + shellSurface->safeConnect(&WToplevelSurface::requestMaximize, this, &SurfaceWrapper::requestMaximize); + shellSurface->safeConnect(&WToplevelSurface::requestCancelMaximize, this, &SurfaceWrapper::requestCancelMaximize); + shellSurface->safeConnect(&WToplevelSurface::requestMove, this, [this](WSeat *, quint32) { + Q_EMIT requestMove(); + }); + shellSurface->safeConnect(&WToplevelSurface::requestResize, this, [this](WSeat *, Qt::Edges edge, quint32) { + Q_EMIT requestResize(edge); + }); + shellSurface->safeConnect(&WToplevelSurface::requestFullscreen, this, &SurfaceWrapper::requestFullscreen); + shellSurface->safeConnect(&WToplevelSurface::requestCancelFullscreen, this, &SurfaceWrapper::requestCancelFullscreen); + if (type == Type::XdgToplevel) { + shellSurface->safeConnect(&WToplevelSurface::requestShowWindowMenu, this, [this](WSeat *, QPoint pos, quint32) { + Q_EMIT requestShowWindowMenu(pos); + }); + } + + connect(m_surfaceItem, &WSurfaceItem::boundingRectChanged, this, &SurfaceWrapper::updateBoundingRect); + connect(m_surfaceItem, &WSurfaceItem::implicitWidthChanged, this, [this] { + setImplicitWidth(m_surfaceItem->implicitWidth()); + }); + connect(m_surfaceItem, &WSurfaceItem::heightChanged, this, [this] { + setImplicitHeight(m_surfaceItem->implicitHeight()); + }); + setImplicitSize(m_surfaceItem->implicitWidth(), m_surfaceItem->implicitHeight()); + + if (!shellSurface->hasCapability(WToplevelSurface::Capability::Focus)) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + m_surfaceItem->setFocusPolicy(Qt::NoFocus); +#endif + } +} + +SurfaceWrapper::~SurfaceWrapper() +{ + if (m_titleBar) + delete m_titleBar; + if (m_decoration) + delete m_decoration; + if (m_geometryAnimation) + delete m_geometryAnimation; + + if (m_ownsOutput) { + m_ownsOutput->removeSurface(this); + m_ownsOutput = nullptr; + } + + if (m_container) { + m_container->removeSurface(this); + m_container = nullptr; + } + + for (auto subS : std::as_const(m_subSurfaces)) { + subS->m_parentSurface = nullptr; + } + + if (m_parentSurface) + m_parentSurface->removeSubSurface(this); +} + +void SurfaceWrapper::setParent(QQuickItem *item) +{ + QObject::setParent(item); + setParentItem(item); +} + +void SurfaceWrapper::setActivate(bool activate) +{ + m_shellSurface->setActivate(activate); + auto parent = parentSurface(); + while (parent) { + parent->setActivate(activate); + parent = parent->parentSurface(); + } +} + +void SurfaceWrapper::setFocus(bool focus, Qt::FocusReason reason) +{ + if (focus) + m_surfaceItem->forceActiveFocus(reason); + else + m_surfaceItem->setFocus(false, reason); +} + +WSurface *SurfaceWrapper::surface() const +{ + return m_shellSurface->surface(); +} + +WToplevelSurface *SurfaceWrapper::shellSurface() const +{ + return m_shellSurface; +} + +WSurfaceItem *SurfaceWrapper::surfaceItem() const +{ + return m_surfaceItem; +} + +bool SurfaceWrapper::resize(const QSizeF &size) +{ + return m_surfaceItem->resizeSurface(size); +} + +QRectF SurfaceWrapper::titlebarGeometry() const +{ + return m_titleBar ? QRectF({0, 0}, m_titleBar->size()) : QRectF(); +} + +QRectF SurfaceWrapper::boundingRect() const +{ + return m_boundedRect; +} + +QRectF SurfaceWrapper::normalGeometry() const +{ + return m_normalGeometry; +} + +void SurfaceWrapper::moveNormalGeometryInOutput(const QPointF &position) +{ + setNormalGeometry(QRectF(position, m_normalGeometry.size())); + if (isNormal()) { + setPosition(position); + } else if (m_pendingState == State::Normal && m_geometryAnimation) { + m_geometryAnimation->setProperty("targetGeometry", m_normalGeometry); + } +} + +void SurfaceWrapper::setNormalGeometry(const QRectF &newNormalGeometry) +{ + if (m_normalGeometry == newNormalGeometry) + return; + m_normalGeometry = newNormalGeometry; + emit normalGeometryChanged(); +} + +QRectF SurfaceWrapper::maximizedGeometry() const +{ + return m_maximizedGeometry; +} + +void SurfaceWrapper::setMaximizedGeometry(const QRectF &newMaximizedGeometry) +{ + if (m_maximizedGeometry == newMaximizedGeometry) + return; + m_maximizedGeometry = newMaximizedGeometry; + if (m_surfaceState == State::Maximized) { + setPosition(newMaximizedGeometry.topLeft()); + resize(newMaximizedGeometry.size()); + } else if (m_pendingState == State::Maximized && m_geometryAnimation) { + m_geometryAnimation->setProperty("targetGeometry", newMaximizedGeometry); + } + + emit maximizedGeometryChanged(); +} + +QRectF SurfaceWrapper::fullscreenGeometry() const +{ + return m_fullscreenGeometry; +} + +void SurfaceWrapper::setFullscreenGeometry(const QRectF &newFullscreenGeometry) +{ + if (m_fullscreenGeometry == newFullscreenGeometry) + return; + m_fullscreenGeometry = newFullscreenGeometry; + if (m_surfaceState == State::Fullscreen) { + setPosition(newFullscreenGeometry.topLeft()); + resize(newFullscreenGeometry.size()); + } else if (m_pendingState == State::Fullscreen && m_geometryAnimation) { + m_geometryAnimation->setProperty("targetGeometry", newFullscreenGeometry); + } + + emit fullscreenGeometryChanged(); + + updateClipRect(); +} + +QRectF SurfaceWrapper::tilingGeometry() const +{ + return m_tilingGeometry; +} + +void SurfaceWrapper::setTilingGeometry(const QRectF &newTilingGeometry) +{ + if (m_tilingGeometry == newTilingGeometry) + return; + m_tilingGeometry = newTilingGeometry; + if (m_surfaceState == State::Tiling) { + setPosition(newTilingGeometry.topLeft()); + resize(newTilingGeometry.size()); + } + + emit tilingGeometryChanged(); +} + +bool SurfaceWrapper::positionAutomatic() const +{ + return m_positionAutomatic; +} + +void SurfaceWrapper::setPositionAutomatic(bool newPositionAutomatic) +{ + if (m_positionAutomatic == newPositionAutomatic) + return; + m_positionAutomatic = newPositionAutomatic; + emit positionAutomaticChanged(); +} + +void SurfaceWrapper::resetWidth() +{ + m_surfaceItem->resetWidth(); + QQuickItem::resetWidth(); +} + +void SurfaceWrapper::resetHeight() +{ + m_surfaceItem->resetHeight(); + QQuickItem::resetHeight(); +} + +SurfaceWrapper::Type SurfaceWrapper::type() const +{ + return m_type; +} + +SurfaceWrapper *SurfaceWrapper::parentSurface() const +{ + return m_parentSurface; +} + +Output *SurfaceWrapper::ownsOutput() const +{ + return m_ownsOutput; +} + +void SurfaceWrapper::setOwnsOutput(Output *newOwnsOutput) +{ + if (m_ownsOutput == newOwnsOutput) + return; + + if (m_ownsOutput) { + m_ownsOutput->removeSurface(this); + } + + m_ownsOutput = newOwnsOutput; + + if (m_ownsOutput) { + m_ownsOutput->addSurface(this); + } + + emit ownsOutputChanged(); +} + +void SurfaceWrapper::setOutputs(const QList &outputs) +{ + auto oldOutputs = surface()->outputs(); + for (auto output : oldOutputs) { + if (outputs.contains(output)) { + continue; + } + surface()->leaveOutput(output); + } + + for (auto output : outputs) { + if (oldOutputs.contains(output)) + continue; + surface()->enterOutput(output); + } +} + +QRectF SurfaceWrapper::geometry() const +{ + return QRectF(position(), size()); +} + +SurfaceWrapper::State SurfaceWrapper::previousSurfaceState() const +{ + return m_previousSurfaceState; +} + +SurfaceWrapper::State SurfaceWrapper::surfaceState() const +{ + return m_surfaceState; +} + +void SurfaceWrapper::setSurfaceState(State newSurfaceState) +{ + if (m_geometryAnimation) + return; + + if (m_surfaceState == newSurfaceState) + return; + + if (container()->filterSurfaceStateChange(this, newSurfaceState, m_surfaceState)) + return; + + QRectF targetGeometry; + + if (newSurfaceState == State::Maximized) { + targetGeometry = m_maximizedGeometry; + } else if (newSurfaceState == State::Fullscreen) { + targetGeometry = m_fullscreenGeometry; + } else if (newSurfaceState == State::Normal) { + targetGeometry = m_normalGeometry; + } else if (newSurfaceState == State::Tiling) { + targetGeometry = m_tilingGeometry; + } + + if (targetGeometry.isValid()) { + startStateChangeAnimation(newSurfaceState, targetGeometry); + } else { + if (m_geometryAnimation) { + m_geometryAnimation->deleteLater(); + } + + doSetSurfaceState(newSurfaceState); + } +} + +QBindable SurfaceWrapper::bindableSurfaceState() +{ + return &m_surfaceState; +} + +bool SurfaceWrapper::isNormal() const +{ + return m_surfaceState == State::Normal; +} + +bool SurfaceWrapper::isMaximized() const +{ + return m_surfaceState == State::Maximized; +} + +bool SurfaceWrapper::isMinimized() const +{ + return m_surfaceState == State::Minimized; +} + +bool SurfaceWrapper::isTiling() const +{ + return m_surfaceState == State::Tiling; +} + +bool SurfaceWrapper::isAnimationRunning() const +{ + return m_geometryAnimation; +} + +void SurfaceWrapper::setNoDecoration(bool newNoDecoration) +{ + setNoCornerRadius(newNoDecoration); + if (m_noDecoration == newNoDecoration) + return; + + m_noDecoration = newNoDecoration; + if (m_titleBarState == TitleBarState::Default) + updateTitleBar(); + + if (m_noDecoration) { + Q_ASSERT(m_decoration); + m_decoration->deleteLater(); + m_decoration = nullptr; + } else { + Q_ASSERT(!m_decoration); + m_decoration = m_engine->createDecoration(this, this); + m_decoration->stackBefore(m_surfaceItem); + connect(m_decoration, &QQuickItem::xChanged, this, &SurfaceWrapper::updateBoundingRect); + connect(m_decoration, &QQuickItem::yChanged, this, &SurfaceWrapper::updateBoundingRect); + connect(m_decoration, &QQuickItem::widthChanged, this, &SurfaceWrapper::updateBoundingRect); + connect(m_decoration, &QQuickItem::heightChanged, this, &SurfaceWrapper::updateBoundingRect); + } + + updateBoundingRect(); + emit noDecorationChanged(); +} + +void SurfaceWrapper::updateTitleBar() +{ + if (noTitleBar() == !m_titleBar) + return; + + if (m_titleBar) { + m_titleBar->deleteLater(); + m_titleBar = nullptr; + m_surfaceItem->setTopPadding(0); + } else { + m_titleBar = m_engine->createTitleBar(this, m_surfaceItem); + m_titleBar->setZ(static_cast(WSurfaceItem::ZOrder::ContentItem)); + m_surfaceItem->setTopPadding(m_titleBar->height()); + connect(m_titleBar, &QQuickItem::heightChanged, this, [this] { + m_surfaceItem->setTopPadding(m_titleBar->height()); + }); + } + + emit noTitleBarChanged(); +} + +void SurfaceWrapper::setBoundedRect(const QRectF &newBoundedRect) +{ + if (m_boundedRect == newBoundedRect) + return; + m_boundedRect = newBoundedRect; + emit boundingRectChanged(); +} + +void SurfaceWrapper::updateBoundingRect() +{ + QRectF rect(QRectF(QPointF(0, 0), size())); + rect |= m_surfaceItem->boundingRect(); + + if (!m_decoration || !m_visibleDecoration) { + setBoundedRect(rect); + return; + } + + const QRectF dr(m_decoration->position(), m_decoration->size()); + setBoundedRect(dr | rect); +} + +void SurfaceWrapper::updateVisible() +{ + setVisible(!isMinimized() && surface()->mapped()); +} + +void SurfaceWrapper::updateSubSurfaceStacking() +{ + SurfaceWrapper *lastSurface = this; + for (auto surface : std::as_const(m_subSurfaces)) { + surface->stackAfter(lastSurface); + lastSurface = surface->stackLastSurface(); + } +} + +void SurfaceWrapper::updateClipRect() +{ + if (!clip() || !window()) + return; + auto rw = qobject_cast(window()); + Q_ASSERT(rw); + rw->markItemClipRectDirty(this); +} + +void SurfaceWrapper::geometryChange(const QRectF &newGeo, const QRectF &oldGeometry) +{ + QRectF newGeometry = newGeo; + if (m_container && m_container->filterSurfaceGeometryChanged(this, newGeometry, oldGeometry)) + return; + + if (isNormal() && !m_geometryAnimation) { + setNormalGeometry(newGeometry); + } + + if (widthValid() && heightValid()) { + resize(newGeometry.size()); + } + + Q_EMIT geometryChanged(); + QQuickItem::geometryChange(newGeometry, oldGeometry); + if (newGeometry.size() != oldGeometry.size()) + updateBoundingRect(); + updateClipRect(); +} + +void SurfaceWrapper::doSetSurfaceState(State newSurfaceState) +{ + setVisibleDecoration(newSurfaceState == State::Normal); + setNoCornerRadius(newSurfaceState != State::Normal); + + m_previousSurfaceState.setValueBypassingBindings(m_surfaceState); + m_surfaceState.setValueBypassingBindings(newSurfaceState); + + switch (m_previousSurfaceState.value()) { + case State::Maximized: + m_shellSurface->setMaximize(false); + break; + case State::Minimized: + m_shellSurface->setMinimize(false); + break; + case State::Fullscreen: + m_shellSurface->setFullScreen(false); + break; + case State::Normal: [[fallthrough]]; + case State::Tiling: [[fallthrough]]; + default: + break; + } + m_previousSurfaceState.notify(); + + switch (m_surfaceState.value()) { + case State::Maximized: + m_shellSurface->setMaximize(true); + break; + case State::Minimized: + m_shellSurface->setMinimize(true); + break; + case State::Fullscreen: + m_shellSurface->setFullScreen(true); + break; + case State::Normal: [[fallthrough]]; + case State::Tiling: [[fallthrough]]; + default: + break; + } + m_surfaceState.notify(); + updateTitleBar(); + updateVisible(); +} + +void SurfaceWrapper::onAnimationReady() +{ + Q_ASSERT(m_pendingState != m_surfaceState); + Q_ASSERT(m_pendingGeometry.isValid()); + + if (!resize(m_pendingGeometry.size())) { + // abort change state if resize failed + m_geometryAnimation->deleteLater(); + return; + } + + setPosition(m_pendingGeometry.topLeft()); + doSetSurfaceState(m_pendingState); +} + +void SurfaceWrapper::onAnimationFinished() +{ + Q_ASSERT(m_geometryAnimation); + m_geometryAnimation->deleteLater(); +} + +bool SurfaceWrapper::startStateChangeAnimation(State targetState, const QRectF &targetGeometry) +{ + if (m_geometryAnimation) // animation running + return false; + + m_geometryAnimation = m_engine->createGeometryAnimation(this, geometry(), targetGeometry, container()); + m_pendingState = targetState; + m_pendingGeometry = targetGeometry; + bool ok = connect(m_geometryAnimation, SIGNAL(ready()), this, SLOT(onAnimationReady())); + Q_ASSERT(ok); + ok = connect(m_geometryAnimation, SIGNAL(finished()), this, SLOT(onAnimationFinished())); + Q_ASSERT(ok); + + ok = QMetaObject::invokeMethod(m_geometryAnimation, "start"); + Q_ASSERT(ok); + return ok; +} + +qreal SurfaceWrapper::radius() const +{ + return m_radius; +} + +void SurfaceWrapper::setRadius(qreal newRadius) +{ + if (qFuzzyCompare(m_radius, newRadius)) + return; + m_radius = newRadius; + emit radiusChanged(); +} + +void SurfaceWrapper::requestMinimize() +{ + setSurfaceState(State::Minimized); +} + +void SurfaceWrapper::requestCancelMinimize() +{ + if (m_surfaceState != State::Minimized) + return; + + setSurfaceState(m_previousSurfaceState); +} + +void SurfaceWrapper::requestMaximize() +{ + if (m_surfaceState == State::Minimized || m_surfaceState == State::Fullscreen) + return; + + setSurfaceState(State::Maximized); +} + +void SurfaceWrapper::requestCancelMaximize() +{ + if (m_surfaceState != State::Maximized) + return; + + setSurfaceState(State::Normal); +} + +void SurfaceWrapper::requestToggleMaximize() +{ + if (m_surfaceState == State::Maximized) + requestCancelMaximize(); + else + requestMaximize(); +} + +void SurfaceWrapper::requestFullscreen() +{ + if (m_surfaceState == State::Minimized) + return; + + setSurfaceState(State::Fullscreen); +} + +void SurfaceWrapper::requestCancelFullscreen() +{ + if (m_surfaceState != State::Fullscreen) + return; + + setSurfaceState(m_previousSurfaceState); +} + +void SurfaceWrapper::requestClose() +{ + m_shellSurface->close(); + updateVisible(); +} + +SurfaceWrapper *SurfaceWrapper::stackFirstSurface() const +{ + return m_subSurfaces.isEmpty() ? const_cast(this) : m_subSurfaces.first()->stackFirstSurface(); +} + +SurfaceWrapper *SurfaceWrapper::stackLastSurface() const +{ + return m_subSurfaces.isEmpty() ? const_cast(this) : m_subSurfaces.last()->stackLastSurface(); +} + +bool SurfaceWrapper::hasChild(SurfaceWrapper *child) const +{ + for (auto s : std::as_const(m_subSurfaces)) { + if (s == child || s->hasChild(child)) + return true; + } + + return false; +} + +bool SurfaceWrapper::stackBefore(QQuickItem *item) +{ + if (!parentItem() || item->parentItem() != parentItem()) + return false; + if (this == item) + return false; + + do { + auto s = qobject_cast(item); + if (s) { + if (s->hasChild(this)) + return false; + if (hasChild(s)) { + QQuickItem::stackBefore(item); + break; + } + item = s->stackFirstSurface(); + + if (m_parentSurface && m_parentSurface == s->m_parentSurface) { + QQuickItem::stackBefore(item); + + int myIndex = m_parentSurface->m_subSurfaces.lastIndexOf(this); + int siblingIndex = m_parentSurface->m_subSurfaces.lastIndexOf(s); + Q_ASSERT(myIndex != -1 && siblingIndex != -1); + if (myIndex != siblingIndex - 1) + m_parentSurface->m_subSurfaces.move(myIndex, + myIndex < siblingIndex ? siblingIndex - 1 : siblingIndex); + break; + } + } + + if (m_parentSurface) { + if (!m_parentSurface->stackBefore(item)) + return false; + } else { + QQuickItem::stackBefore(item); + } + } while (false); + + updateSubSurfaceStacking(); + return true; +} + +bool SurfaceWrapper::stackAfter(QQuickItem *item) +{ + if (!parentItem() || item->parentItem() != parentItem()) + return false; + if (this == item) + return false; + + do { + auto s = qobject_cast(item); + if (s) { + if (hasChild(s)) + return false; + if (s->hasChild(this)) { + QQuickItem::stackAfter(item); + break; + } + item = s->stackLastSurface(); + + if (m_parentSurface && m_parentSurface == s->m_parentSurface) { + QQuickItem::stackAfter(item); + + int myIndex = m_parentSurface->m_subSurfaces.lastIndexOf(this); + int siblingIndex = m_parentSurface->m_subSurfaces.lastIndexOf(s); + Q_ASSERT(myIndex != -1 && siblingIndex != -1); + if (myIndex != siblingIndex + 1) + m_parentSurface->m_subSurfaces.move(myIndex, + myIndex > siblingIndex ? siblingIndex + 1 : siblingIndex); + + break; + } + } + + if (m_parentSurface) { + if (!m_parentSurface->stackAfter(item)) + return false; + } else { + QQuickItem::stackAfter(item); + } + } while (false); + + updateSubSurfaceStacking(); + return true; +} + +void SurfaceWrapper::stackToLast() +{ + if (!parentItem()) + return; + + if (m_parentSurface) { + m_parentSurface->stackToLast(); + stackAfter(m_parentSurface->stackLastSurface()); + } else { + auto last = parentItem()->childItems().last(); + stackAfter(last); + } +} + +void SurfaceWrapper::addSubSurface(SurfaceWrapper *surface) +{ + Q_ASSERT(!surface->m_parentSurface); + surface->m_parentSurface = this; + surface->updateExplicitAlwaysOnTop(); + m_subSurfaces.append(surface); +} + +void SurfaceWrapper::removeSubSurface(SurfaceWrapper *surface) +{ + Q_ASSERT(surface->m_parentSurface == this); + surface->m_parentSurface = nullptr; + surface->updateExplicitAlwaysOnTop(); + m_subSurfaces.removeOne(surface); +} + +const QList &SurfaceWrapper::subSurfaces() const +{ + return m_subSurfaces; +} + +SurfaceContainer *SurfaceWrapper::container() const +{ + return m_container; +} + +void SurfaceWrapper::setContainer(SurfaceContainer *newContainer) +{ + if (m_container == newContainer) + return; + m_container = newContainer; + emit containerChanged(); +} + +QQuickItem *SurfaceWrapper::titleBar() const +{ + return m_titleBar; +} + +QQuickItem *SurfaceWrapper::decoration() const +{ + return m_decoration; +} + +bool SurfaceWrapper::noDecoration() const +{ + return m_noDecoration; +} + +bool SurfaceWrapper::visibleDecoration() const +{ + return m_visibleDecoration; +} + +void SurfaceWrapper::setVisibleDecoration(bool newVisibleDecoration) +{ + if (m_visibleDecoration == newVisibleDecoration) + return; + m_visibleDecoration = newVisibleDecoration; + updateBoundingRect(); + emit visibleDecorationChanged(); +} + +bool SurfaceWrapper::clipInOutput() const +{ + return m_clipInOutput; +} + +void SurfaceWrapper::setClipInOutput(bool newClipInOutput) +{ + if (m_clipInOutput == newClipInOutput) + return; + m_clipInOutput = newClipInOutput; + updateClipRect(); + emit clipInOutputChanged(); +} + +QRectF SurfaceWrapper::clipRect() const +{ + if (m_clipInOutput) { + return m_fullscreenGeometry & geometry(); + } + + return QQuickItem::clipRect(); +} + +bool SurfaceWrapper::noTitleBar() const +{ + if (m_surfaceState == State::Fullscreen) + return true; + if (m_titleBarState == TitleBarState::Visible) + return false; + + return m_titleBarState == TitleBarState::Hidden || m_noDecoration; +} + +void SurfaceWrapper::setNoTitleBar(bool newNoTitleBar) +{ + if (newNoTitleBar) { + m_titleBarState = TitleBarState::Hidden; + } else { + m_titleBarState = TitleBarState::Visible; + } + updateTitleBar(); +} + +void SurfaceWrapper::resetNoTitleBar() +{ + m_titleBarState = TitleBarState::Default; + updateTitleBar(); +} + +bool SurfaceWrapper::noCornerRadius() const +{ + return m_noCornerRadius; +} + +void SurfaceWrapper::setNoCornerRadius(bool newNoCornerRadius) +{ + if (m_noCornerRadius == newNoCornerRadius) + return; + m_noCornerRadius = newNoCornerRadius; + emit noCornerRadiusChanged(); +} + +int SurfaceWrapper::workspaceId() const +{ + return m_workspaceId; +} + +void SurfaceWrapper::setWorkspaceId(int newWorkspaceId) +{ + if (m_workspaceId == newWorkspaceId) + return; + + bool onAllWorkspaceHasChanged = m_workspaceId == 0 || newWorkspaceId == 0; + m_workspaceId = newWorkspaceId; + + if (onAllWorkspaceHasChanged) + Q_EMIT showOnAllWorkspaceChanged(); + Q_EMIT workspaceIdChanged(); +} + +bool SurfaceWrapper::alwaysOnTop() const +{ + return m_alwaysOnTop; +} + +void SurfaceWrapper::setAlwaysOnTop(bool alwaysOnTop) +{ + if (m_alwaysOnTop == alwaysOnTop) + return; + m_alwaysOnTop = alwaysOnTop; + updateExplicitAlwaysOnTop(); + + Q_EMIT alwaysOnTopChanged(); +} + +bool SurfaceWrapper::showOnAllWorkspace() const +{ + return m_workspaceId == 0; +} + +void SurfaceWrapper::updateExplicitAlwaysOnTop() +{ + int newExplicitAlwaysOnTop = m_alwaysOnTop; + if (m_parentSurface) + newExplicitAlwaysOnTop += m_parentSurface->m_explicitAlwaysOnTop; + + if (m_explicitAlwaysOnTop == newExplicitAlwaysOnTop) + return; + + m_explicitAlwaysOnTop = newExplicitAlwaysOnTop; + setZ(m_explicitAlwaysOnTop ? 1 : 0); + for (const auto& sub : std::as_const(m_subSurfaces)) + sub->updateExplicitAlwaysOnTop(); +} diff --git a/examples/tinywl/surfacewrapper.h b/examples/tinywl/surfacewrapper.h new file mode 100644 index 00000000..7fc919d0 --- /dev/null +++ b/examples/tinywl/surfacewrapper.h @@ -0,0 +1,265 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#pragma once + +#include +#include + +#include + +Q_MOC_INCLUDE() + +WAYLIB_SERVER_USE_NAMESPACE + +class QmlEngine; +class Output; +class SurfaceContainer; +class SurfaceWrapper : public QQuickItem +{ + friend class Helper; + friend class SurfaceContainer; + friend class SurfaceProxy; + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("SurfaceWrapper objects are created by c++") + Q_PROPERTY(Type type READ type CONSTANT) + // make to readonly + Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged FINAL) + Q_PROPERTY(qreal implicitHeight READ implicitHeight NOTIFY implicitHeightChanged FINAL) + Q_PROPERTY(WAYLIB_SERVER_NAMESPACE::WSurface* surface READ surface CONSTANT) + Q_PROPERTY(WAYLIB_SERVER_NAMESPACE::WToplevelSurface* shellSurface READ shellSurface CONSTANT) + Q_PROPERTY(WAYLIB_SERVER_NAMESPACE::WSurfaceItem* surfaceItem READ surfaceItem CONSTANT) + Q_PROPERTY(QRectF boundingRect READ boundingRect NOTIFY boundingRectChanged) + Q_PROPERTY(QRectF geometry READ geometry NOTIFY geometryChanged FINAL) + Q_PROPERTY(QRectF normalGeometry READ normalGeometry NOTIFY normalGeometryChanged FINAL) + Q_PROPERTY(QRectF maximizedGeometry READ maximizedGeometry NOTIFY maximizedGeometryChanged FINAL) + Q_PROPERTY(QRectF fullscreenGeometry READ fullscreenGeometry NOTIFY fullscreenGeometryChanged FINAL) + Q_PROPERTY(QRectF tilingGeometry READ tilingGeometry NOTIFY tilingGeometryChanged FINAL) + Q_PROPERTY(Output* ownsOutput READ ownsOutput NOTIFY ownsOutputChanged FINAL) + Q_PROPERTY(bool positionAutomatic READ positionAutomatic WRITE setPositionAutomatic NOTIFY positionAutomaticChanged FINAL) + Q_PROPERTY(State previousSurfaceState READ previousSurfaceState NOTIFY previousSurfaceStateChanged FINAL) + Q_PROPERTY(State surfaceState READ surfaceState NOTIFY surfaceStateChanged BINDABLE bindableSurfaceState FINAL) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) + Q_PROPERTY(SurfaceContainer* container READ container NOTIFY containerChanged FINAL) + Q_PROPERTY(QQuickItem* titleBar READ titleBar NOTIFY noTitleBarChanged FINAL) + Q_PROPERTY(QQuickItem* decoration READ decoration NOTIFY noDecorationChanged FINAL) + Q_PROPERTY(bool visibleDecoration READ visibleDecoration NOTIFY visibleDecorationChanged FINAL) + Q_PROPERTY(bool clipInOutput READ clipInOutput WRITE setClipInOutput NOTIFY clipInOutputChanged FINAL) + Q_PROPERTY(bool noTitleBar READ noTitleBar RESET resetNoTitleBar NOTIFY noTitleBarChanged FINAL) + Q_PROPERTY(bool noCornerRadius READ noCornerRadius NOTIFY noCornerRadiusChanged FINAL) + Q_PROPERTY(int workspaceId READ workspaceId NOTIFY workspaceIdChanged FINAL) + Q_PROPERTY(bool alwaysOnTop READ alwaysOnTop WRITE setAlwaysOnTop NOTIFY alwaysOnTopChanged FINAL) + Q_PROPERTY(bool showOnAllWorkspace READ showOnAllWorkspace NOTIFY showOnAllWorkspaceChanged FINAL) + +public: + enum class Type { + XdgToplevel, + XdgPopup, + XWayland, + Layer, + InputPopup, + }; + Q_ENUM(Type) + + enum class State { + Normal, + Maximized, + Minimized, + Fullscreen, + Tiling, + }; + Q_ENUM(State) + + explicit SurfaceWrapper(QmlEngine *qmlEngine, + WToplevelSurface *shellSurface, Type type, + QQuickItem *parent = nullptr); + ~SurfaceWrapper(); + + void setFocus(bool focus, Qt::FocusReason reason); + + WSurface *surface() const; + WToplevelSurface *shellSurface() const; + WSurfaceItem *surfaceItem() const; + bool resize(const QSizeF &size); + + QRectF titlebarGeometry() const; + QRectF boundingRect() const override; + + Type type() const; + SurfaceWrapper *parentSurface() const; + + Output *ownsOutput() const; + void setOwnsOutput(Output *newOwnsOutput); + void setOutputs(const QList &outputs); + + QRectF geometry() const; + QRectF normalGeometry() const; + void moveNormalGeometryInOutput(const QPointF &position); + + QRectF maximizedGeometry() const; + void setMaximizedGeometry(const QRectF &newMaximizedGeometry); + + QRectF fullscreenGeometry() const; + void setFullscreenGeometry(const QRectF &newFullscreenGeometry); + + QRectF tilingGeometry() const; + void setTilingGeometry(const QRectF &newTilingGeometry); + + bool positionAutomatic() const; + void setPositionAutomatic(bool newPositionAutomatic); + + void resetWidth(); + void resetHeight(); + + State previousSurfaceState() const; + State surfaceState() const; + void setSurfaceState(State newSurfaceState); + QBindable bindableSurfaceState(); + bool isNormal() const; + bool isMaximized() const; + bool isMinimized() const; + bool isTiling() const; + bool isAnimationRunning() const; + + qreal radius() const; + void setRadius(qreal newRadius); + + SurfaceContainer *container() const; + + void addSubSurface(SurfaceWrapper *surface); + void removeSubSurface(SurfaceWrapper *surface); + const QList &subSurfaces() const; + SurfaceWrapper *stackFirstSurface() const; + SurfaceWrapper *stackLastSurface() const; + bool hasChild(SurfaceWrapper *child) const; + + QQuickItem *titleBar() const; + QQuickItem *decoration() const; + + bool noDecoration() const; + bool visibleDecoration() const; + + bool clipInOutput() const; + void setClipInOutput(bool newClipInOutput); + QRectF clipRect() const override; + + bool noTitleBar() const; + void setNoTitleBar(bool newNoTitleBar); + void resetNoTitleBar(); + + bool noCornerRadius() const; + void setNoCornerRadius(bool newNoCornerRadius); + + int workspaceId() const; + void setWorkspaceId(int newWorkspaceId); + + bool alwaysOnTop() const; + void setAlwaysOnTop(bool alwaysOnTop); + + bool showOnAllWorkspace() const; + void setShowOnAllWorkspace(bool showOnAllWorkspace); + +public Q_SLOTS: + // for titlebar + void requestMinimize(); + void requestCancelMinimize(); + void requestMaximize(); + void requestCancelMaximize(); + void requestToggleMaximize(); + void requestFullscreen(); + void requestCancelFullscreen(); + void requestClose(); + + bool stackBefore(QQuickItem *item); + bool stackAfter(QQuickItem *item); + void stackToLast(); + +Q_SIGNALS: + void boundingRectChanged(); + void ownsOutputChanged(); + void normalGeometryChanged(); + void maximizedGeometryChanged(); + void fullscreenGeometryChanged(); + void tilingGeometryChanged(); + void positionAutomaticChanged(); + void previousSurfaceStateChanged(); + void surfaceStateChanged(); + void radiusChanged(); + void requestMove(); // for titlebar + void requestResize(Qt::Edges edges); + void requestShowWindowMenu(QPoint pos); + void geometryChanged(); + void containerChanged(); + void visibleDecorationChanged(); + void clipInOutputChanged(); + void noDecorationChanged(); + void noTitleBarChanged(); + void noCornerRadiusChanged(); + void workspaceIdChanged(); + void alwaysOnTopChanged(); + void showOnAllWorkspaceChanged(); + +private: + using QQuickItem::setParentItem; + using QQuickItem::stackBefore; + using QQuickItem::stackAfter; + void setParent(QQuickItem *item); + void setActivate(bool activate); + void setNormalGeometry(const QRectF &newNormalGeometry); + void setNoDecoration(bool newNoDecoration); + void updateTitleBar(); + void setBoundedRect(const QRectF &newBoundedRect); + void setContainer(SurfaceContainer *newContainer); + void setVisibleDecoration(bool newVisibleDecoration); + void updateBoundingRect(); + void updateVisible(); + void updateSubSurfaceStacking(); + void updateClipRect(); + void geometryChange(const QRectF &newGeo, const QRectF &oldGeometry) override; + + void doSetSurfaceState(State newSurfaceState); + Q_SLOT void onAnimationReady(); + Q_SLOT void onAnimationFinished(); + bool startStateChangeAnimation(SurfaceWrapper::State targetState, const QRectF &targetGeometry); + void updateExplicitAlwaysOnTop(); + + QmlEngine *m_engine; + QPointer m_container; + QList m_subSurfaces; + SurfaceWrapper *m_parentSurface = nullptr; + + WToplevelSurface *m_shellSurface = nullptr; + WSurfaceItem *m_surfaceItem = nullptr; + QPointer m_titleBar; + QPointer m_decoration; + QPointer m_geometryAnimation; + QRectF m_boundedRect; + QRectF m_normalGeometry; + QRectF m_maximizedGeometry; + QRectF m_fullscreenGeometry; + QRectF m_tilingGeometry; + Type m_type; + QPointer m_ownsOutput; + QPointF m_positionInOwnsOutput; + SurfaceWrapper::State m_pendingState; + QRectF m_pendingGeometry; + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(SurfaceWrapper, SurfaceWrapper::State, m_previousSurfaceState, State::Normal, &SurfaceWrapper::previousSurfaceStateChanged) + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(SurfaceWrapper, SurfaceWrapper::State, m_surfaceState, State::Normal, &SurfaceWrapper::surfaceStateChanged) + qreal m_radius = 18.0; + int m_workspaceId = -1; + int m_explicitAlwaysOnTop = 0; + + struct TitleBarState { + constexpr static uint Default = 0; + constexpr static uint Visible = 1; + constexpr static uint Hidden = 2; + }; + + uint m_positionAutomatic:1; + uint m_visibleDecoration:1; + uint m_clipInOutput:1; + uint m_noDecoration:1; + uint m_titleBarState:2; + uint m_noCornerRadius:1; + uint m_alwaysOnTop:1; +}; diff --git a/examples/tinywl/wallpaperimage.cpp b/examples/tinywl/wallpaperimage.cpp new file mode 100644 index 00000000..5026acf5 --- /dev/null +++ b/examples/tinywl/wallpaperimage.cpp @@ -0,0 +1,105 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wallpaperprovider.h" +#include "wallpaperimage.h" +#include "helper.h" +#include "workspacemodel.h" + +#include + +#include + +WAYLIB_SERVER_USE_NAMESPACE + +WallpaperImage::WallpaperImage(QQuickItem *parent) + : QQuickImage(parent) +{ + auto provider = Helper::instance()->qmlEngine()->wallpaperImageProvider(); + connect(provider, &WallpaperImageProvider::wallpaperTextureUpdate, this, &WallpaperImage::updateWallpaperTexture); + + setFillMode(Tile); + setCache(false); + setAsynchronous(true); +} + +WallpaperImage::~WallpaperImage() +{ + +} + +int WallpaperImage::userId() +{ + return m_userId; +} + +void WallpaperImage::setUserId(const int id) +{ + if (m_userId != id) { + m_userId = id; + Q_EMIT userIdChanged(); + updateSource(); + } +} + +WorkspaceModel *WallpaperImage::workspace() +{ + return m_workspace; +} + +void WallpaperImage::setWorkspace(WorkspaceModel *workspace) +{ + if (m_workspace != workspace) { + m_workspace = workspace; + Q_EMIT workspaceChanged(); + updateSource(); + } +} + +WOutput* WallpaperImage::output() +{ + return m_output; +} + +void WallpaperImage::setOutput(WOutput* output) +{ + if (m_output != output) { + if (m_output) + QObject::disconnect(m_output, nullptr, this, nullptr); + + m_output = output; + Q_EMIT outputChanged(); + + if (output) { + setSourceSize(output->transformedSize()); + connect(output, &WOutput::transformedSizeChanged, this, [this] { + setSourceSize(m_output->transformedSize()); + }); + } + updateSource(); + } +} + +void WallpaperImage::updateSource() +{ + if (m_userId == -1 || + !m_output || + !m_workspace) { + return; + } + + QStringList paras; + paras << QString::number(m_userId) << m_output->name() << m_workspace->name(); + QString source = "image://wallpaper/" + paras.join("/"); + setSource(source); +} + +void WallpaperImage::updateWallpaperTexture(const QString& id, int size) +{ + QString item_id = source().toString().remove("image://wallpaper/"); + int item_size = sourceSize().width() * sourceSize().height(); + if (item_size < size && item_id == id) { + load(); + update(); + } +} diff --git a/examples/tinywl/wallpaperimage.h b/examples/tinywl/wallpaperimage.h new file mode 100644 index 00000000..a933ca30 --- /dev/null +++ b/examples/tinywl/wallpaperimage.h @@ -0,0 +1,56 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "wglobal.h" + +#include +#include + +Q_MOC_INCLUDE("workspace.h") + +WAYLIB_SERVER_BEGIN_NAMESPACE +class WOutput; +WAYLIB_SERVER_END_NAMESPACE + +WAYLIB_SERVER_USE_NAMESPACE + +class WorkspaceModel; +class WallpaperImage : public QQuickImage +{ + Q_OBJECT + Q_PROPERTY(int userId READ userId WRITE setUserId NOTIFY userIdChanged FINAL) + Q_PROPERTY(WorkspaceModel* workspace READ workspace WRITE setWorkspace NOTIFY workspaceChanged FINAL) + Q_PROPERTY(WAYLIB_SERVER_NAMESPACE::WOutput* output READ output WRITE setOutput NOTIFY outputChanged FINAL) + + QML_NAMED_ELEMENT(Wallpaper) + QML_ADDED_IN_VERSION(1, 0) + +public: + WallpaperImage(QQuickItem *parent = nullptr); + ~WallpaperImage(); + + int userId(); + void setUserId(const int id); + + WorkspaceModel *workspace(); + void setWorkspace(WorkspaceModel *workspace); + + WOutput* output(); + void setOutput(WOutput* output); + +Q_SIGNALS: + void userIdChanged(); + void outputChanged(); + void workspaceChanged(); + +protected: + void updateSource(); + void updateWallpaperTexture(const QString& id, int size); + +private: + int m_userId = -1; + QPointer m_workspace; + QPointer m_output; +}; diff --git a/examples/tinywl/wallpaperprovider.cpp b/examples/tinywl/wallpaperprovider.cpp new file mode 100644 index 00000000..451e9aaf --- /dev/null +++ b/examples/tinywl/wallpaperprovider.cpp @@ -0,0 +1,145 @@ +// Copyright (C) 2024 WenHao Peng . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wallpaperprovider.h" + +#include +#include +#include +#include +#include +#include + +WallpaperTextureFactory::WallpaperTextureFactory(WallpaperImageProvider* provider, const QImage &image) + : m_wallpaperProvider(provider) +{ + if (image.format() == QImage::Format_ARGB32_Premultiplied + || image.format() == QImage::Format_RGB32) { + im = image; + } else { + im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + size = im.size(); +} + +WallpaperTextureFactory::WallpaperTextureFactory(WallpaperImageProvider *provider, const QString &id) + : m_wallpaperProvider(provider) + , m_wallpaperId(id) +{ + m_textureExist = true; +} + +QSGTexture *WallpaperTextureFactory::createTexture(QQuickWindow *window) const +{ + if (m_textureExist) + m_texture = m_wallpaperProvider->getExistTexture(m_wallpaperId); + + if (!m_texture) { + m_texture = window->createTextureFromImage(im, QQuickWindow::TextureCanUseAtlas); + const_cast(this)->im = QImage(); + } + + return m_texture; +} + +WallpaperImageProvider::WallpaperImageProvider() + : QQuickImageProvider(QQuickImageProvider::Texture) +{ +} + +WallpaperImageProvider::~WallpaperImageProvider() +{ + textureCache.clear(); +} + +QImage WallpaperImageProvider::loadFile(const QString &path, const QSize &requestedSize) +{ + QImageReader imgio(path); + QSize realSize = imgio.size(); + + if (requestedSize.isValid() && + requestedSize.width() < realSize.width() && + requestedSize.height() < realSize.height()) + imgio.setScaledSize(requestedSize); + + QImage image; + imgio.read(&image); + return image; +} + +QSGTexture *WallpaperImageProvider::getExistTexture(const QString& id) const +{ + if (textureCache.contains(id)) { + return dynamic_cast(textureCache[id].data())->texture(); + } + + return nullptr; +} + +QString WallpaperImageProvider::parseFilePath(const QString &id) +{ + QStringList components = id.split("/"); + QString home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + QString local_path = QString("%1/.cache/treeland/wallpaper/%2/%3/%4/") + .arg(home) + .arg(components[0]) + .arg(components[1]) + .arg(components[2]); + + QDir dir(local_path); + QFileInfo fi; + QString img_path; + if (dir.exists()) { + dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); + QFileInfoList filelist = dir.entryInfoList(); + + fi = filelist.first(); + img_path = fi.absoluteFilePath(); + } + + if (!(fi.exists() && fi.isFile())) { + img_path = ":/qt/qml/Tinywl/res/xx.jpg"; + } + + return img_path; +} + +QQuickTextureFactory *WallpaperImageProvider::requestTexture(const QString &id, QSize *size, const QSize &requestedSize) +{ + QQuickTextureFactory *factory = nullptr; + QSize readSize; + + QString img_path = parseFilePath(id); + if (textureCache.contains(img_path)) { + auto cache_factory = textureCache[img_path]; + + if (!cache_factory.isNull()) { + readSize = cache_factory->textureSize(); + if (requestedSize.width() < readSize.width() && requestedSize.height() < readSize.height()) { + *size = readSize; + return new WallpaperTextureFactory(this, img_path); + } + } + } + + QFileInfo fi(QDir::root(), img_path); + QString path = fi.canonicalFilePath(); + if (!path.isEmpty()) { + QImage img = loadFile(path, requestedSize); + readSize = img.size(); + if (!img.isNull()) { + factory = new WallpaperTextureFactory(this, img); + } + } + + if (size) { + *size = readSize; + } + + if (factory) { + textureCache.insert(img_path, factory); + Q_EMIT wallpaperTextureUpdate(img_path, readSize.width() * readSize.height()); + } + + return factory; +} diff --git a/examples/tinywl/wallpaperprovider.h b/examples/tinywl/wallpaperprovider.h new file mode 100644 index 00000000..03991cdc --- /dev/null +++ b/examples/tinywl/wallpaperprovider.h @@ -0,0 +1,49 @@ +// Copyright (C) 2024 WenHao Peng . +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once +#include + +class WallpaperImageProvider; +class WallpaperTextureFactory : public QQuickTextureFactory +{ + Q_OBJECT +public: + WallpaperTextureFactory(WallpaperImageProvider *provider, const QImage &i); + WallpaperTextureFactory(WallpaperImageProvider *provider, const QString &id); + + QSGTexture *createTexture(QQuickWindow *window) const override; + QSGTexture *texture() const { return m_texture; } + QSize textureSize() const override { return size; } + int textureByteCount() const override { return size.width() * size.height() * 4; } + QImage image() const override { return im; } + +private: + QImage im; + QSize size; + QString m_wallpaperId; + bool m_textureExist = false; + mutable QSGTexture *m_texture = nullptr; + WallpaperImageProvider *m_wallpaperProvider = nullptr; +}; + +class WallpaperImageProvider : public QQuickImageProvider +{ + Q_OBJECT +public: + WallpaperImageProvider(); + ~WallpaperImageProvider(); + + QQuickTextureFactory *requestTexture(const QString &id, QSize *size, const QSize &requestedSize) override; + QSGTexture *getExistTexture(const QString& id) const; + +private: + QImage loadFile(const QString &path, const QSize &requestedSize); + QString parseFilePath(const QString &id); + +Q_SIGNALS: + void wallpaperTextureUpdate(const QString& id, int size); + +private: + QHash> textureCache; +}; diff --git a/examples/tinywl/workspace.cpp b/examples/tinywl/workspace.cpp new file mode 100644 index 00000000..a1b026b8 --- /dev/null +++ b/examples/tinywl/workspace.cpp @@ -0,0 +1,222 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "workspace.h" +#include "surfacewrapper.h" +#include "output.h" +#include "helper.h" +#include "rootsurfacecontainer.h" + +Workspace::Workspace(SurfaceContainer *parent) + : SurfaceContainer(parent) +{ + // TODO: save and restore from local storage + static int workspaceGlobalIndex = 0; + + // TODO: save and restore workpsace's name from local storage + createContainer(QStringLiteral("show-on-all-workspace"), true); + createContainer(QStringLiteral("workspace-%1").arg(++workspaceGlobalIndex), true); + createContainer(QStringLiteral("workspace-%1").arg(++workspaceGlobalIndex)); +} + +void Workspace::addSurface(SurfaceWrapper *surface, int workspaceIndex) +{ + doAddSurface(surface, true); + + if (workspaceIndex < 0) + workspaceIndex = m_currentIndex; + + auto container = m_models.at(workspaceIndex); + + if (container->hasSurface(surface)) + return; + + for (auto c : std::as_const(m_models)) { + if (c == container) + continue; + if (c->surfaces().contains(surface)) { + c->removeSurface(surface); + break; + } + } + + container->addSurface(surface); + if (!surface->ownsOutput()) + surface->setOwnsOutput(rootContainer()->primaryOutput()); +} + +void Workspace::removeSurface(SurfaceWrapper *surface) +{ + if (!doRemoveSurface(surface, false)) + return; + + for (auto container : std::as_const(m_models)) { + if (container->surfaces().contains(surface)) { + container->removeSurface(surface); + break; + } + } +} + +int Workspace::containerIndexOfSurface(SurfaceWrapper *surface) const +{ + for (int i = 0; i < m_models.size(); ++i) { + if (m_models.at(i)->hasSurface(surface)) + return i; + } + + return -1; +} + +int Workspace::createContainer(const QString &name, bool visible) +{ + m_models.append(new WorkspaceModel(this, m_models.size())); + auto newContainer = m_models.last(); + newContainer->setName(name); + newContainer->setVisible(visible); + return newContainer->index(); +} + +void Workspace::removeContainer(int index) +{ + if (m_models.size() == 2) // id 0 used for show on all workspace + return; + if (index <= 0 || index >= m_models.size()) + return; + + auto container = m_models.at(index); + m_models.removeAt(index); + + // reset index + for (int i = index; i < m_models.size(); ++i) { + m_models.at(i)->setIndex(i); + } + + auto oldCurrent = this->current(); + m_currentIndex = qMin(m_currentIndex, m_models.size() - 1); + auto current = this->current(); + + const auto tmp = container->surfaces(); + for (auto s : tmp) { + container->removeSurface(s); + if (current) + current->addSurface(s); + } + + container->deleteLater(); + + if (oldCurrent != current) + emit currentChanged(); +} + +WorkspaceModel *Workspace::container(int index) const +{ + if (index < 0 || index >= m_models.size()) + return nullptr; + return m_models.at(index); +} + +int Workspace::count() const +{ + return m_models.size(); +} + +int Workspace::currentIndex() const +{ + return m_currentIndex; +} + +WorkspaceModel *Workspace::showOnAllWorkspaceModel() const +{ + return m_models.at(0); +} + +void Workspace::setCurrentIndex(int newCurrentIndex) +{ + if (newCurrentIndex <= 0 || newCurrentIndex >= m_models.size()) + return; + + if (m_currentIndex == newCurrentIndex) + return; + m_currentIndex = newCurrentIndex; + + if (m_switcher) { + m_switcher->deleteLater(); + } + + for (int i = 1; i < m_models.size(); ++i) { + m_models.at(i)->setVisible(i == m_currentIndex); + } + + emit currentChanged(); +} + +void Workspace::switchToNext() +{ + if (m_currentIndex + 1 < m_models.size()) + switchTo(m_currentIndex + 1); +} + +void Workspace::switchToPrev() +{ + if (m_currentIndex - 1 > 0) + switchTo(m_currentIndex - 1); +} + +void Workspace::switchTo(int index) +{ + if (m_switcher) + return; + + Q_ASSERT(index != m_currentIndex); + Q_ASSERT(index > 0 && index < m_models.size()); + auto from = current(); + auto to = m_models.at(index); + auto engine = Helper::instance()->qmlEngine(); + from->setVisible(false); + to->setVisible(false); + showOnAllWorkspaceModel()->setVisible(false); + m_switcher = engine->createWorkspaceSwitcher(this, from, to); +} + +WorkspaceModel *Workspace::current() const +{ + if (m_currentIndex <= 0 || m_currentIndex >= m_models.size()) + return nullptr; + + return m_models.at(m_currentIndex); +} + +void Workspace::setCurrent(WorkspaceModel *container) +{ + int index = m_models.indexOf(container); + if (index <= 0) + return; + setCurrentIndex(index); +} + +void Workspace::updateSurfaceOwnsOutput(SurfaceWrapper *surface) +{ + auto outputs = surface->surface()->outputs(); + if (surface->ownsOutput() && outputs.contains(surface->ownsOutput()->output())) + return; + + Output *output = nullptr; + if (!outputs.isEmpty()) + output = Helper::instance()->getOutput(outputs.first()); + if (!output) + output = rootContainer()->cursorOutput(); + if (!output) + output = rootContainer()->primaryOutput(); + if (output) + surface->setOwnsOutput(output); +} + +void Workspace::updateSurfacesOwnsOutput() +{ + const auto surfaces = this->surfaces(); + for (auto surface : surfaces) { + updateSurfaceOwnsOutput(surface); + } +} + diff --git a/examples/tinywl/workspace.h b/examples/tinywl/workspace.h new file mode 100644 index 00000000..4df53dff --- /dev/null +++ b/examples/tinywl/workspace.h @@ -0,0 +1,55 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#pragma once + +#include "surfacecontainer.h" +#include "workspacemodel.h" + +class SurfaceWrapper; +class Workspace; + +class Workspace : public SurfaceContainer +{ + Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentChanged FINAL) + Q_PROPERTY(WorkspaceModel* current READ current WRITE setCurrent NOTIFY currentChanged FINAL) + Q_PROPERTY(WorkspaceModel* showOnAllWorkspaceModel READ showOnAllWorkspaceModel CONSTANT) + Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) + QML_ANONYMOUS + +public: + explicit Workspace(SurfaceContainer *parent); + + Q_INVOKABLE void addSurface(SurfaceWrapper *surface, int workspaceIndex = -1); + void removeSurface(SurfaceWrapper *surface) override; + int containerIndexOfSurface(SurfaceWrapper *surface) const; + + Q_INVOKABLE int createContainer(const QString &name, bool visible = false); + Q_INVOKABLE void removeContainer(int index); + WorkspaceModel *container(int index) const; + + int count() const; + int currentIndex() const; + WorkspaceModel *showOnAllWorkspaceModel() const; + void setCurrentIndex(int newCurrentIndex); + Q_INVOKABLE void switchToNext(); + Q_INVOKABLE void switchToPrev(); + void switchTo(int index); + + WorkspaceModel *current() const; + void setCurrent(WorkspaceModel *container); + +signals: + void currentChanged(); + void countChanged(); + +private: + void updateSurfaceOwnsOutput(SurfaceWrapper *surface); + void updateSurfacesOwnsOutput(); + + // Workspace id starts from 1, the WorkspaceModel with id 0 is used to + // store the surface that is always in the visible workspace. + int m_currentIndex = 1; + QList m_models; + QPointer m_switcher; +}; diff --git a/examples/tinywl/workspacemodel.cpp b/examples/tinywl/workspacemodel.cpp new file mode 100644 index 00000000..2fecadfe --- /dev/null +++ b/examples/tinywl/workspacemodel.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "workspacemodel.h" +#include "surfacewrapper.h" +#include "helper.h" + +WorkspaceModel::WorkspaceModel(QObject *parent, int index) + : SurfaceListModel(parent) + , m_index(index) +{ + +} + +QString WorkspaceModel::name() const +{ + return m_name; +} + +void WorkspaceModel::setName(const QString &newName) +{ + if (m_name == newName) + return; + m_name = newName; + Q_EMIT nameChanged(); +} + +int WorkspaceModel::index() const +{ + return m_index; +} + +void WorkspaceModel::setIndex(int newIndex) +{ + if (m_index == newIndex) + return; + m_index = newIndex; + Q_EMIT indexChanged(); +} + +bool WorkspaceModel::visible() const +{ + return m_visible; +} + +void WorkspaceModel::setVisible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + for (auto surface : surfaces()) + surface->setVisible(visible); + Q_EMIT visibleChanged(); +} + +void WorkspaceModel::addSurface(SurfaceWrapper *surface) +{ + SurfaceListModel::addSurface(surface); + surface->setVisible(m_visible); + surface->setWorkspaceId(m_index); +} + +void WorkspaceModel::removeSurface(SurfaceWrapper *surface) +{ + SurfaceListModel::removeSurface(surface); + surface->setWorkspaceId(-1); +} diff --git a/examples/tinywl/workspacemodel.h b/examples/tinywl/workspacemodel.h new file mode 100644 index 00000000..2be04939 --- /dev/null +++ b/examples/tinywl/workspacemodel.h @@ -0,0 +1,43 @@ +// Copyright (C) 2024 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#pragma once + +#include "surfacecontainer.h" + +class SurfaceWrapper; +class Workspace; +class WorkspaceModel : public SurfaceListModel +{ + friend class Workspace; + Q_OBJECT + Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL) + Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged FINAL) + + QML_ELEMENT + +public: + explicit WorkspaceModel(QObject *parent, int index); + + QString name() const; + void setName(const QString &newName); + + int index() const; + void setIndex(int newIndex); + + bool visible() const; + void setVisible(bool visible); + + void addSurface(SurfaceWrapper *surface) override; + void removeSurface(SurfaceWrapper *surface) override; + +Q_SIGNALS: + void nameChanged(); + void indexChanged(); + void visibleChanged(); + +private: + QString m_name; + int m_index = -1; + bool m_visible = false; +}; diff --git a/nix/default.nix b/nix/default.nix index b9f2fb8d..737a6cf8 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -16,7 +16,7 @@ , libinput , nixos-artwork -# only for test +# only for test , makeTest ? null , pkgs ? null , waylib ? null @@ -40,12 +40,6 @@ stdenv.mkDerivation (finalAttrs: { ]; }; - postPatch = '' - substituteInPlace examples/tinywl/OutputDelegate.qml \ - --replace "/usr/share/wallpapers/deepin/desktop.jpg" \ - "${nixos-artwork.wallpapers.simple-blue}/share/backgrounds/nixos/nix-wallpaper-simple-blue.png" - ''; - depsBuildBuild = [ pkg-config ]; nativeBuildInputs = [ @@ -58,6 +52,7 @@ stdenv.mkDerivation (finalAttrs: { buildInputs = [ qtbase qtquick3d + qwlroots wayland wayland-protocols wlr-protocols @@ -66,10 +61,6 @@ stdenv.mkDerivation (finalAttrs: { libinput ]; - propagatedBuildInputs = [ - qwlroots - ]; - cmakeBuildType = if debug then "Debug" else "Release"; cmakeFlags = [ diff --git a/src/server/kernel/private/woutputlayout_p.h b/src/server/kernel/private/woutputlayout_p.h index 49352782..f5c653f2 100644 --- a/src/server/kernel/private/woutputlayout_p.h +++ b/src/server/kernel/private/woutputlayout_p.h @@ -10,6 +10,9 @@ class Q_DECL_HIDDEN WOutputLayoutPrivate : public WObjectPrivate { public: WOutputLayoutPrivate(WOutputLayout *qq); + ~WOutputLayoutPrivate(); + + void doAdd(WOutput *output); W_DECLARE_PUBLIC(WOutputLayout) diff --git a/src/server/kernel/private/wsurface_p.h b/src/server/kernel/private/wsurface_p.h index b7eb3029..9cc19e1a 100644 --- a/src/server/kernel/private/wsurface_p.h +++ b/src/server/kernel/private/wsurface_p.h @@ -37,7 +37,6 @@ class Q_DECL_HIDDEN WSurfacePrivate : public WWrapObjectPrivate { void connect(); void instantRelease() override; // release qwobject etc. void updateOutputs(); - void setPrimaryOutput(WOutput *output); void setBuffer(QW_NAMESPACE::qw_buffer *newBuffer); void updateBuffer(); void updateBufferOffset(); @@ -59,7 +58,6 @@ class Q_DECL_HIDDEN WSurfacePrivate : public WWrapObjectPrivate { std::unique_ptr buffer; QVector outputs; - WOutput *primaryOutput = nullptr; QMetaObject::Connection frameDoneConnection; QPoint bufferOffset; }; diff --git a/src/server/kernel/wcursor.cpp b/src/server/kernel/wcursor.cpp index f24fb501..efbc0ed2 100644 --- a/src/server/kernel/wcursor.cpp +++ b/src/server/kernel/wcursor.cpp @@ -511,7 +511,7 @@ void WCursor::setSeat(WSeat *seat) connect(d->seat, &WSeat::requestCursorSurface, this, &WCursor::requestedCursorSurfaceChanged); connect(d->seat, &WSeat::requestDrag, this, &WCursor::requestedDragSurfaceChanged); - if (d->eventWindow) { + if (d->eventWindow && !d->deviceList.isEmpty()) { d->sendEnterEvent(); } } diff --git a/src/server/kernel/woutputlayout.cpp b/src/server/kernel/woutputlayout.cpp index ccd1ebbb..23fa1965 100644 --- a/src/server/kernel/woutputlayout.cpp +++ b/src/server/kernel/woutputlayout.cpp @@ -18,6 +18,30 @@ WOutputLayoutPrivate::WOutputLayoutPrivate(WOutputLayout *qq) } +WOutputLayoutPrivate::~WOutputLayoutPrivate() +{ + for (auto o : std::as_const(outputs)) { + o->setLayout(nullptr); + } +} + +void WOutputLayoutPrivate::doAdd(WOutput *output) +{ + Q_ASSERT(!outputs.contains(output)); + outputs.append(output); + + W_Q(WOutputLayout); + Q_ASSERT(output->layout() == q); + + output->safeConnect(&WOutput::effectiveSizeChanged, q, [this] { + updateImplicitSize(); + }); + updateImplicitSize(); + + Q_EMIT q->outputAdded(output); + Q_EMIT q->outputsChanged(); +} + void WOutputLayoutPrivate::updateImplicitSize() { W_Q(WOutputLayout); @@ -58,19 +82,17 @@ const QList &WOutputLayout::outputs() const void WOutputLayout::add(WOutput *output, const QPoint &pos) { W_D(WOutputLayout); - Q_ASSERT(!d->outputs.contains(output)); - d->outputs.append(output); - - qw_output_layout::add(output->nativeHandle(), pos.x(), pos.y()); output->setLayout(this); + qw_output_layout::add(output->nativeHandle(), pos.x(), pos.y()); + d->doAdd(output); +} - output->safeConnect(&WOutput::effectiveSizeChanged, this, [d](){ - d->updateImplicitSize(); - }); - d->updateImplicitSize(); - - Q_EMIT outputAdded(output); - Q_EMIT outputsChanged(); +void WOutputLayout::autoAdd(WOutput *output) +{ + W_D(WOutputLayout); + output->setLayout(this); + qw_output_layout::add_auto(output->nativeHandle()); + d->doAdd(output); } void WOutputLayout::move(WOutput *output, const QPoint &pos) diff --git a/src/server/kernel/woutputlayout.h b/src/server/kernel/woutputlayout.h index 6f2eb40a..abe99c3a 100644 --- a/src/server/kernel/woutputlayout.h +++ b/src/server/kernel/woutputlayout.h @@ -33,6 +33,7 @@ class WAYLIB_SERVER_EXPORT WOutputLayout : public QW_NAMESPACE::qw_output_layout const QList &outputs() const; void add(WOutput *output, const QPoint &pos); + void autoAdd(WOutput *output); void move(WOutput *output, const QPoint &pos); void remove(WOutput *output); diff --git a/src/server/kernel/wsurface.cpp b/src/server/kernel/wsurface.cpp index bcbaf3a0..e73f83d9 100644 --- a/src/server/kernel/wsurface.cpp +++ b/src/server/kernel/wsurface.cpp @@ -114,14 +114,6 @@ void WSurfacePrivate::updateOutputs() updatePreferredBufferScale(); } -void WSurfacePrivate::setPrimaryOutput(WOutput *output) -{ - W_Q(WSurface); - - primaryOutput = output; - Q_EMIT q->primaryOutputChanged(); -} - void WSurfacePrivate::setBuffer(qw_buffer *newBuffer) { if (buffer) { @@ -333,11 +325,6 @@ void WSurface::enterOutput(WOutput *output) d->updateOutputs(); - if (!d->primaryOutput) { - d->primaryOutput = output; - Q_EMIT primaryOutputChanged(); - } - // for subsurface auto surface = d->nativeHandle(); wlr_subsurface *subsurface; @@ -362,11 +349,6 @@ void WSurface::leaveOutput(WOutput *output) output->safeDisconnect(this); d->updateOutputs(); - if (d->primaryOutput == output) { - d->primaryOutput = d->outputs.isEmpty() ? nullptr : d->outputs.last(); - Q_EMIT primaryOutputChanged(); - } - // for subsurface auto surface = d->nativeHandle(); wlr_subsurface *subsurface; @@ -381,18 +363,12 @@ void WSurface::leaveOutput(WOutput *output) Q_EMIT outputLeft(output); } -QVector WSurface::outputs() const +const QVector &WSurface::outputs() const { W_DC(WSurface); return d->outputs; } -WOutput *WSurface::primaryOutput() const -{ - W_DC(WSurface); - return d->primaryOutput; -} - bool WSurface::isSubsurface() const { W_DC(WSurface); diff --git a/src/server/kernel/wsurface.h b/src/server/kernel/wsurface.h index d0164bb5..05d531de 100644 --- a/src/server/kernel/wsurface.h +++ b/src/server/kernel/wsurface.h @@ -31,7 +31,6 @@ class WAYLIB_SERVER_EXPORT WSurface : public WWrapObject Q_PROPERTY(bool isSubsurface READ isSubsurface NOTIFY isSubsurfaceChanged) Q_PROPERTY(bool hasSubsurface READ hasSubsurface NOTIFY hasSubsurfaceChanged) Q_PROPERTY(QList subsurfaces READ subsurfaces NOTIFY newSubsurface) - Q_PROPERTY(WOutput* primaryOutput READ primaryOutput NOTIFY primaryOutputChanged) Q_PROPERTY(uint32_t preferredBufferScale READ preferredBufferScale WRITE setPreferredBufferScale RESET resetPreferredBufferScale NOTIFY preferredBufferScaleChanged FINAL) QML_NAMED_ELEMENT(WaylandSurface) QML_UNCREATABLE("Only create in C++") @@ -54,7 +53,6 @@ class WAYLIB_SERVER_EXPORT WSurface : public WWrapObject QW_NAMESPACE::qw_buffer *buffer() const; void notifyFrameDone(); - WOutput *primaryOutput() const; bool isSubsurface() const; bool hasSubsurface() const; @@ -67,14 +65,13 @@ class WAYLIB_SERVER_EXPORT WSurface : public WWrapObject public Q_SLOTS: void enterOutput(WOutput *output); void leaveOutput(WOutput *output); - QVector outputs() const; + const QVector &outputs() const; bool inputRegionContains(const QPointF &localPos) const; void map(); void unmap(); Q_SIGNALS: - void primaryOutputChanged(); void mappedChanged(); void bufferChanged(); void bufferOffsetChanged(); diff --git a/src/server/kernel/wtoplevelsurface.h b/src/server/kernel/wtoplevelsurface.h index bc634f5a..8153d78e 100644 --- a/src/server/kernel/wtoplevelsurface.h +++ b/src/server/kernel/wtoplevelsurface.h @@ -26,9 +26,17 @@ class WAYLIB_SERVER_EXPORT WToplevelSurface : public WWrapObject QML_UNCREATABLE("Only create in C++") public: - virtual bool doesNotAcceptFocus() const { + enum class Capability { + Focus, + Activate, + Maximized, + FullScreen, + Resize, + }; + + virtual bool hasCapability([[maybe_unused]] Capability cap) const { return false; - } + }; virtual WSurface *surface() const { return nullptr; @@ -112,7 +120,7 @@ public Q_SLOTS: void requestMaximize(); void requestCancelMaximize(); void requestMinimize(); - void requestCancelMinimize(); + void requestCancelMinimize(); // Only for XWaylandSurface void requestFullscreen(); void requestCancelFullscreen(); void requestShowWindowMenu(WSeat *seat, QPoint pos, quint32 serial); diff --git a/src/server/protocols/wforeigntoplevelv1.cpp b/src/server/protocols/wforeigntoplevelv1.cpp index ff208b6d..bdc63d27 100644 --- a/src/server/protocols/wforeigntoplevelv1.cpp +++ b/src/server/protocols/wforeigntoplevelv1.cpp @@ -6,6 +6,8 @@ #include "private/wglobal_p.h" #include "wforeigntoplevelv1.h" #include "wtoplevelsurface.h" +#include "wxdgsurface.h" +#include "wxwaylandsurface.h" #include #include @@ -60,23 +62,37 @@ class Q_DECL_HIDDEN WForeignToplevelPrivate : public WObjectPrivate { handle->set_activated(surface->isActivated()); })); - auto updateSurfaceParent = [this, surface, handle] { - if (surface->parentSurface()) { - auto find = std::find_if(surfaces.begin(), surfaces.end(), [surface](const auto &pair) { - return pair.first->surface() == surface->parentSurface(); - }); - if (find == surfaces.end()) { - qCCritical(qLcWlrForeignToplevel) << "Toplevel surface " << surface - << "has set parent surface, but foreign_toplevel_handle for parent surface not found!"; + if (auto *xdgSurface = qobject_cast(surface)) { + auto updateSurfaceParent = [this, handle, xdgSurface] { + WToplevelSurface* p = xdgSurface->parentXdgSurface(); + if (!p) { + handle->set_parent(nullptr); return; } - - handle->set_parent(*find->second.get()); - } - else - handle->set_parent(nullptr); - }; - connection.push_back(surface->safeConnect(&WToplevelSurface::parentSurfaceChanged, surface, updateSurfaceParent)); + if (!surfaces.contains(p)) { + qCCritical(qLcWlrForeignToplevel) << "Xdg toplevel surface " << xdgSurface + << "has set parent surface, but foreign_toplevel_handle for parent surface not found!"; + } + handle->set_parent(*surfaces[p]); + }; + connection.push_back(xdgSurface->safeConnect(&WXdgSurface::parentXdgSurfaceChanged, surface, updateSurfaceParent)); + updateSurfaceParent(); + } else if (auto *xwaylandSurface = qobject_cast(surface)) { + auto updateSurfaceParent = [this, handle, xwaylandSurface] { + WToplevelSurface* p = xwaylandSurface->parentXWaylandSurface(); + if (!p) { + handle->set_parent(nullptr); + return; + } + if (!surfaces.contains(p)) { + qCCritical(qLcWlrForeignToplevel) << "X11 surface " << xwaylandSurface + << "has set parent surface, but foreign_toplevel_handle for parent surface not found!"; + } + handle->set_parent(*surfaces[p]); + }; + connection.push_back(xwaylandSurface->safeConnect(&WXWaylandSurface::parentXWaylandSurface, surface, updateSurfaceParent)); + updateSurfaceParent(); + } connection.push_back(surface->surface()->safeConnect(&WSurface::outputEntered, surface, [this, handle](WOutput *output) { handle->output_enter(output->nativeHandle()); @@ -128,7 +144,6 @@ class Q_DECL_HIDDEN WForeignToplevelPrivate : public WObjectPrivate { handle->set_maximized(surface->isMaximized()); handle->set_fullscreen(surface->isFullScreen()); handle->set_activated(surface->isActivated()); - updateSurfaceParent(); connections.insert({surface, connection}); } diff --git a/src/server/protocols/winputpopupsurface.cpp b/src/server/protocols/winputpopupsurface.cpp index ab676344..be6b70ef 100644 --- a/src/server/protocols/winputpopupsurface.cpp +++ b/src/server/protocols/winputpopupsurface.cpp @@ -38,9 +38,21 @@ WInputPopupSurface::WInputPopupSurface(qw_input_popup_surface_v2 *surface, WSurf : WToplevelSurface(*new WInputPopupSurfacePrivate(surface, parentSurface, this), parent) { } -bool WInputPopupSurface::doesNotAcceptFocus() const +bool WInputPopupSurface::hasCapability(Capability cap) const { - return true; + W_DC(WInputPopupSurface); + switch (cap) { + using enum Capability; + case Focus: + case Activate: + case Maximized: + case FullScreen: + case Resize: + return false; + default: + break; + } + Q_UNREACHABLE(); } WSurface *WInputPopupSurface::surface() const diff --git a/src/server/protocols/winputpopupsurface.h b/src/server/protocols/winputpopupsurface.h index 50929d1b..1aa39d8e 100644 --- a/src/server/protocols/winputpopupsurface.h +++ b/src/server/protocols/winputpopupsurface.h @@ -25,7 +25,7 @@ class WAYLIB_SERVER_EXPORT WInputPopupSurface : public WToplevelSurface WSurface *surface() const override; QW_NAMESPACE::qw_input_popup_surface_v2 *handle() const; QRect getContentGeometry() const override; - bool doesNotAcceptFocus() const override; + bool hasCapability(Capability cap) const override; bool isActivated() const override; WSurface *parentSurface() const override; diff --git a/src/server/protocols/wlayersurface.cpp b/src/server/protocols/wlayersurface.cpp index b0426d40..191d4dab 100644 --- a/src/server/protocols/wlayersurface.cpp +++ b/src/server/protocols/wlayersurface.cpp @@ -50,7 +50,6 @@ class Q_DECL_HIDDEN WLayerSurfacePrivate : public WToplevelSurfacePrivate { W_DECLARE_PUBLIC(WLayerSurface) WSurface *surface = nullptr; - uint activated:1; QSize desiredSize; WLayerSurface::LayerType layer = WLayerSurface::LayerType::Bottom; WLayerSurface::AnchorTypes ancher = WLayerSurface::AnchorType::None; @@ -63,7 +62,6 @@ class Q_DECL_HIDDEN WLayerSurfacePrivate : public WToplevelSurfacePrivate { WLayerSurfacePrivate::WLayerSurfacePrivate(WLayerSurface *qq, qw_layer_surface_v1 *hh) : WToplevelSurfacePrivate(qq) - , activated(false) { initHandle(hh); } @@ -252,24 +250,25 @@ WLayerSurface::~WLayerSurface() } -bool WLayerSurface::isPopup() const -{ - return false; -} - -bool WLayerSurface::doesNotAcceptFocus() const +bool WLayerSurface::hasCapability(Capability cap) const { W_DC(WLayerSurface); - if (d->keyboardInteractivity == WLayerSurface::KeyboardInteractivity::None) + switch (cap) { + using enum Capability; + case Focus: + return d->keyboardInteractivity != WLayerSurface::KeyboardInteractivity::None; + case Activate: + case Maximized: + case FullScreen: + return false; + case Resize: return true; - return false; + default: + break; + } + Q_UNREACHABLE(); } -bool WLayerSurface::isActivated() const -{ - W_D(const WLayerSurface); - return d->activated; -} WSurface *WLayerSurface::surface() const { @@ -427,15 +426,6 @@ void WLayerSurface::closed() wlr_layer_surface_v1_destroy(nativeHandle()); } -void WLayerSurface::setActivate(bool on) -{ - W_D(WLayerSurface); - if (d->activated != on) { - d->activated = on; - Q_EMIT activateChanged(); - } -} - bool WLayerSurface::checkNewSize(const QSize &size) { W_D(WLayerSurface); diff --git a/src/server/protocols/wlayersurface.h b/src/server/protocols/wlayersurface.h index 1a7b91e6..b4ce2851 100644 --- a/src/server/protocols/wlayersurface.h +++ b/src/server/protocols/wlayersurface.h @@ -70,9 +70,7 @@ class WAYLIB_SERVER_EXPORT WLayerSurface : public WToplevelSurface }; Q_ENUM(KeyboardInteractivity) - bool isPopup() const; - bool doesNotAcceptFocus() const override; - bool isActivated() const override; + bool hasCapability(Capability cap) const override; WSurface *surface() const override; QW_NAMESPACE::qw_layer_surface_v1 *handle() const; wlr_layer_surface_v1 *nativeHandle() const; @@ -117,7 +115,6 @@ class WAYLIB_SERVER_EXPORT WLayerSurface : public WToplevelSurface public Q_SLOTS: bool checkNewSize(const QSize &size) override; - void setActivate(bool on) override; }; Q_DECLARE_OPERATORS_FOR_FLAGS(WLayerSurface::AnchorTypes) diff --git a/src/server/protocols/woutputmanagerv1.cpp b/src/server/protocols/woutputmanagerv1.cpp index 4ed41768..ba43af63 100644 --- a/src/server/protocols/woutputmanagerv1.cpp +++ b/src/server/protocols/woutputmanagerv1.cpp @@ -3,7 +3,6 @@ #include "woutputmanagerv1.h" #include "woutputitem.h" -#include "woutputitem_p.h" #include "private/wglobal_p.h" #include @@ -122,16 +121,14 @@ void WOutputManagerV1::newOutput(WOutput *output) W_D(WOutputManagerV1); const auto *wlr_output = output->nativeHandle(); - auto *attached = output->findChild(QString(), Qt::FindDirectChildrenOnly); - if (!attached) - attached = WOutputItem::qmlAttachedProperties(output); + auto outputItem = WOutputItem::getOutputItem(output); WOutputState state { .output = output, .enabled = wlr_output->enabled, .mode = wlr_output->current_mode, - .x = attached ? static_cast(attached->item()->x()) : 0, - .y = attached ? static_cast(attached->item()->y()) : 0, + .x = outputItem ? static_cast(outputItem->x()) : 0, + .y = outputItem ? static_cast(outputItem->y()) : 0, .customModeSize = { wlr_output->width, wlr_output->height }, .customModeRefresh = wlr_output->refresh, .transform = static_cast(wlr_output->transform), diff --git a/src/server/protocols/wxdgsurface.cpp b/src/server/protocols/wxdgsurface.cpp index 5a6677ce..9c4d0880 100644 --- a/src/server/protocols/wxdgsurface.cpp +++ b/src/server/protocols/wxdgsurface.cpp @@ -164,10 +164,9 @@ void WXdgSurfacePrivate::connect() } }); QObject::connect(toplevel, &qw_xdg_toplevel::notify_request_minimize, q, [q, toplevel] () { + // Wayland clients can't request unset minimization on this surface if ((*toplevel)->requested.minimized) { Q_EMIT q->requestMinimize(); - } else { - Q_EMIT q->requestCancelMinimize(); } }); QObject::connect(toplevel, &qw_xdg_toplevel::notify_request_fullscreen, q, [q, toplevel] () { @@ -212,10 +211,22 @@ bool WXdgSurface::isToplevel() const return d->isToplevel(); } -bool WXdgSurface::doesNotAcceptFocus() const +bool WXdgSurface::hasCapability(Capability cap) const { W_DC(WXdgSurface); - return d->nativeHandle()->role == WLR_XDG_SURFACE_ROLE_NONE; + switch (cap) { + using enum Capability; + case Resize: + return d->nativeHandle()->role != WLR_XDG_SURFACE_ROLE_NONE; + case Focus: + case Activate: + case Maximized: + case FullScreen: + return isToplevel(); + default: + break; + } + Q_UNREACHABLE(); } WSurface *WXdgSurface::surface() const @@ -370,8 +381,7 @@ WSurface *WXdgSurface::parentSurface() const return WSurface::fromHandle(parent->base->surface); } else if (isPopup()) { auto parent = d->nativeHandle()->popup->parent; - if (!parent) - return nullptr; + Q_ASSERT(parent); return WSurface::fromHandle(parent); } return nullptr; diff --git a/src/server/protocols/wxdgsurface.h b/src/server/protocols/wxdgsurface.h index 4a61d35c..4143322b 100644 --- a/src/server/protocols/wxdgsurface.h +++ b/src/server/protocols/wxdgsurface.h @@ -32,7 +32,7 @@ class WAYLIB_SERVER_EXPORT WXdgSurface : public WToplevelSurface bool isPopup() const; bool isToplevel() const; - bool doesNotAcceptFocus() const override; + bool hasCapability(Capability cap) const override; WSurface *surface() const override; QW_NAMESPACE::qw_xdg_surface *handle() const; diff --git a/src/server/protocols/wxwaylandsurface.cpp b/src/server/protocols/wxwaylandsurface.cpp index 9501e02c..54baf621 100644 --- a/src/server/protocols/wxwaylandsurface.cpp +++ b/src/server/protocols/wxwaylandsurface.cpp @@ -333,12 +333,24 @@ bool WXWaylandSurface::isActivated() const return d->activated; } -bool WXWaylandSurface::doesNotAcceptFocus() const +bool WXWaylandSurface::hasCapability(Capability cap) const { W_DC(WXWaylandSurface); - - return !wlr_xwayland_or_surface_wants_focus(d->nativeHandle()) - || wlr_xwayland_icccm_input_model(d->nativeHandle()) == WLR_ICCCM_INPUT_MODEL_NONE; + switch (cap) { + using enum Capability; + case Focus: + return !wlr_xwayland_or_surface_wants_focus(d->nativeHandle()) + || wlr_xwayland_icccm_input_model(d->nativeHandle()) == WLR_ICCCM_INPUT_MODEL_NONE; + case Activate: + case Maximized: + case FullScreen: + case Resize: + // TODO: should check WindowType + return !isBypassManager(); + default: + break; + } + Q_UNREACHABLE(); } QSize WXWaylandSurface::minSize() const diff --git a/src/server/protocols/wxwaylandsurface.h b/src/server/protocols/wxwaylandsurface.h index c5179a08..d1308720 100644 --- a/src/server/protocols/wxwaylandsurface.h +++ b/src/server/protocols/wxwaylandsurface.h @@ -94,7 +94,7 @@ class WAYLIB_SERVER_EXPORT WXWaylandSurface : public WToplevelSurface bool isFullScreen() const override; bool isActivated() const override; - bool doesNotAcceptFocus() const override; + bool hasCapability(Capability cap) const override; QSize minSize() const override; QSize maxSize() const override; diff --git a/src/server/qtquick/private/woutputitem_p.h b/src/server/qtquick/private/woutputitem_p.h index 33845c9e..edf75f7f 100644 --- a/src/server/qtquick/private/woutputitem_p.h +++ b/src/server/qtquick/private/woutputitem_p.h @@ -4,43 +4,12 @@ #pragma once #include "woutputitem.h" - -#include -#include - -#include -#include -#include - -Q_MOC_INCLUDE() +#include "wcursor.h" QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE -class WAYLIB_SERVER_EXPORT WOutputItemAttached : public QObject -{ - friend class WOutputItem; - Q_OBJECT - Q_PROPERTY(WAYLIB_SERVER_NAMESPACE::WOutputItem* item READ item NOTIFY itemChanged FINAL) - QML_ANONYMOUS - -public: - explicit WOutputItemAttached(QObject *parent = nullptr); - - WOutputItem *item() const; - -Q_SIGNALS: - void itemChanged(); - -private: - void setItem(WOutputItem *positioner); - -private: - WOutputItem *m_positioner = nullptr; -}; - -class WCursor; -class Q_DECL_HIDDEN WOutputCursor : public QObject +class WAYLIB_SERVER_EXPORT WOutputCursor : public QObject { friend class WOutputItem; friend class WOutputItemPrivate; diff --git a/src/server/qtquick/private/wsurfaceitem_p.h b/src/server/qtquick/private/wsurfaceitem_p.h index fffd9be4..45dec6db 100644 --- a/src/server/qtquick/private/wsurfaceitem_p.h +++ b/src/server/qtquick/private/wsurfaceitem_p.h @@ -48,8 +48,10 @@ class Q_DECL_HIDDEN WSurfaceItemPrivate : public QQuickItemPrivate paddings.top() + paddings.bottom()); } - qreal getImplicitWidth() const override; - qreal getImplicitHeight() const override; + qreal calculateImplicitWidth() const; + qreal calculateImplicitHeight() const; + QRectF calculateBoundingRect() const; + void updateBoundingRect(); inline WSurfaceItemContent *getItemContent() const { if (delegate || !contentContainer) @@ -75,6 +77,7 @@ class Q_DECL_HIDDEN WSurfaceItemPrivate : public QQuickItemPrivate bool live = true; uint32_t beforeRequestResizeSurfaceStateSeq = 0; + QRectF boundingRect; }; WAYLIB_SERVER_END_NAMESPACE diff --git a/src/server/qtquick/woutputitem.cpp b/src/server/qtquick/woutputitem.cpp index 9faf3835..799b82ec 100644 --- a/src/server/qtquick/woutputitem.cpp +++ b/src/server/qtquick/woutputitem.cpp @@ -11,27 +11,11 @@ #include #include +#include + QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE -WOutputItemAttached::WOutputItemAttached(QObject *parent) - : QObject(parent) -{ - -} - -WOutputItem *WOutputItemAttached::item() const -{ - return m_positioner; -} - -void WOutputItemAttached::setItem(WOutputItem *positioner) -{ - if (m_positioner == positioner) - return; - m_positioner = positioner; - Q_EMIT itemChanged(); -} #define DATA_OF_WOUPTUT "_WOutputItem" class Q_DECL_HIDDEN WOutputItemPrivate : public WObjectPrivate @@ -140,7 +124,7 @@ void WOutputItemPrivate::updateCursors() Q_ASSERT(q->window()); auto obj = cursorDelegate->createWithInitialProperties({ - {"outputCurosr", QVariant::fromValue(oc)}, + {"outputCursor", QVariant::fromValue(oc)}, {"parent", QVariant::fromValue(q->window()->contentItem())}, }, qmlContext(q)); oc->item = qobject_cast(obj); @@ -242,17 +226,6 @@ WOutputItem::~WOutputItem() } -WOutputItemAttached *WOutputItem::qmlAttachedProperties(QObject *target) -{ - auto output = qobject_cast(target); - if (!output) - return nullptr; - auto attached = new WOutputItemAttached(output); - attached->setItem(getOutputItem(output)); - - return attached; -} - WOutputItem *WOutputItem::getOutputItem(WOutput *output) { return qvariant_cast(output->property(DATA_OF_WOUPTUT)) ; @@ -264,11 +237,6 @@ WOutput *WOutputItem::output() const return d->output.get(); } -inline static WOutputItemAttached *getAttached(WOutput *output) -{ - return output->findChild(QString(), Qt::FindDirectChildrenOnly); -} - void WOutputItem::setOutput(WOutput *newOutput) { W_D(WOutputItem); @@ -277,11 +245,7 @@ void WOutputItem::setOutput(WOutput *newOutput) d->output = newOutput; if (newOutput) { - if (auto attached = getAttached(newOutput)) { - attached->setItem(this); - } else { - newOutput->setProperty(DATA_OF_WOUPTUT, QVariant::fromValue(this)); - } + newOutput->setProperty(DATA_OF_WOUPTUT, QVariant::fromValue(this)); } if (isComponentComplete()) { @@ -396,6 +360,12 @@ void WOutputItem::itemChange(ItemChange change, const ItemChangeData &data) QQuickItem::itemChange(change, data); } +void WOutputItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_EMIT geometryChanged(); + QQuickItem::geometryChange(newGeometry, oldGeometry); +} + qreal WOutputItem::getImplicitWidth() const { W_DC(WOutputItem); diff --git a/src/server/qtquick/woutputitem.h b/src/server/qtquick/woutputitem.h index 48dcfcd3..b8b5ab3a 100644 --- a/src/server/qtquick/woutputitem.h +++ b/src/server/qtquick/woutputitem.h @@ -8,32 +8,29 @@ #include Q_MOC_INCLUDE() -Q_MOC_INCLUDE() WAYLIB_SERVER_BEGIN_NAMESPACE class WQuickSeat; class WOutput; +class WOutputCursor; class WQuickOutputLayout; -class WOutputItemAttached; class WOutputItemPrivate; class WAYLIB_SERVER_EXPORT WOutputItem : public WQuickObserver, public WObject { Q_OBJECT W_DECLARE_PRIVATE(WOutputItem) - Q_PROPERTY(WOutput* output READ output WRITE setOutput NOTIFY outputChanged REQUIRED) + Q_PROPERTY(WOutput* output READ output WRITE setOutput NOTIFY outputChanged) Q_PROPERTY(WQuickOutputLayout* layout READ layout WRITE setLayout NOTIFY layoutChanged) Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged) Q_PROPERTY(QQmlComponent* cursorDelegate READ cursorDelegate WRITE setCursorDelegate NOTIFY cursorDelegateChanged) Q_PROPERTY(QList cursorItems READ cursorItems NOTIFY cursorItemsChanged) QML_NAMED_ELEMENT(OutputItem) - QML_ATTACHED(WOutputItemAttached) public: explicit WOutputItem(QQuickItem *parent = nullptr); ~WOutputItem(); - static WOutputItemAttached *qmlAttachedProperties(QObject *target); static WOutputItem *getOutputItem(WOutput *output); WOutput *output() const; @@ -57,12 +54,14 @@ class WAYLIB_SERVER_EXPORT WOutputItem : public WQuickObserver, public WObject void seatChanged(); void cursorDelegateChanged(); void cursorItemsChanged(); + void geometryChanged(); private: void classBegin() override; void componentComplete() override; void releaseResources() override; void itemChange(ItemChange change, const ItemChangeData &data) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; qreal getImplicitWidth() const override; qreal getImplicitHeight() const override; diff --git a/src/server/qtquick/woutputrenderwindow.cpp b/src/server/qtquick/woutputrenderwindow.cpp index d09d47d2..e70f4fad 100644 --- a/src/server/qtquick/woutputrenderwindow.cpp +++ b/src/server/qtquick/woutputrenderwindow.cpp @@ -12,6 +12,7 @@ #include "woutputlayer.h" #include "wbufferrenderer_p.h" #include "wquicktextureproxy.h" +#include "weventjunkman.h" #include "platformplugin/qwlrootsintegration.h" #include "platformplugin/qwlrootscreen.h" @@ -34,6 +35,7 @@ #include #include #include +#include #include #define protected public @@ -65,6 +67,7 @@ extern "C" { } #include +#include WAYLIB_SERVER_BEGIN_NAMESPACE @@ -1245,6 +1248,11 @@ void WOutputRenderWindowPrivate::init() q->update(); }); + // for WSeat::filterUnacceptedEvent + auto eventJunkman = new WEventJunkman(contentItem); + QQuickItemPrivate::get(eventJunkman)->anchors()->setFill(contentItem); + eventJunkman->setZ(std::numeric_limits::lowest()); + Q_EMIT q->initialized(); } @@ -1867,6 +1875,30 @@ void WOutputRenderWindow::setHeight(qreal arg) contentItem()->setHeight(arg); } +void WOutputRenderWindow::markItemClipRectDirty(QQuickItem *item) +{ + class MarkItemClipRectDirtyJob : public QRunnable + { + public: + MarkItemClipRectDirtyJob(QQuickItem *item) + : item(item) { } + void run() override { + if (!item) + return; + auto d = QQuickItemPrivate::get(item); + if (auto clip = d->clipNode()) { + clip->setClipRect(item->clipRect()); + clip->update(); + } + } + QPointer item; + }; + + // Delay clean the qt rhi textures. + scheduleRenderJob(new MarkItemClipRectDirtyJob(item), + QQuickWindow::AfterSynchronizingStage); +} + void WOutputRenderWindow::classBegin() { Q_D(WOutputRenderWindow); diff --git a/src/server/qtquick/woutputrenderwindow.h b/src/server/qtquick/woutputrenderwindow.h index 3669ff4e..04a07ec0 100644 --- a/src/server/qtquick/woutputrenderwindow.h +++ b/src/server/qtquick/woutputrenderwindow.h @@ -68,6 +68,7 @@ public Q_SLOTS: void update(WOutputViewport *output); void setWidth(qreal arg); void setHeight(qreal arg); + void markItemClipRectDirty(QQuickItem *item); Q_SIGNALS: void widthChanged(); diff --git a/src/server/qtquick/wsurfaceitem.cpp b/src/server/qtquick/wsurfaceitem.cpp index 02c36fa0..b9af98cb 100644 --- a/src/server/qtquick/wsurfaceitem.cpp +++ b/src/server/qtquick/wsurfaceitem.cpp @@ -155,7 +155,6 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate updateFrameDoneConnection(); updateSurfaceState(); - q->rendered = true; } @@ -476,6 +475,12 @@ WSurfaceItem::~WSurfaceItem() } +QRectF WSurfaceItem::boundingRect() const +{ + W_DC(WSurfaceItem); + return d->boundingRect; +} + WSurfaceItem *WSurfaceItem::fromFocusObject(QObject *focusObject) { if (auto item = qobject_cast(focusObject)) @@ -588,8 +593,10 @@ void WSurfaceItem::setFlags(const Flags &newFlags) d->surfaceFlags = newFlags; d->updateEventItem(false); - if (auto content = d->getItemContent()) + if (auto content = d->getItemContent()) { content->setCacheLastBuffer(!newFlags.testFlag(DontCacheLastBuffer)); + content->setLive(!newFlags.testFlag(NonLive)); + } for (auto sub : std::as_const(d->subsurfaces)) sub->setFlags(newFlags); @@ -610,7 +617,7 @@ void WSurfaceItem::setRightPadding(qreal newRightPadding) return; d->paddings.setRight(newRightPadding); d->onPaddingsChanged(); - d->implicitWidthChanged(); + setImplicitWidth(d->calculateImplicitWidth()); Q_EMIT rightPaddingChanged(); } @@ -657,8 +664,10 @@ void WSurfaceItem::setDelegate(QQmlComponent *newDelegate) if (d->componentComplete) d->initForDelegate(); - for (auto sub : std::as_const(d->subsurfaces)) - sub->setDelegate(newDelegate); + if (flags() & DelegateForSubsurface) { + for (auto sub : std::as_const(d->subsurfaces)) + sub->setDelegate(newDelegate); + } Q_EMIT delegateChanged(); } @@ -676,7 +685,7 @@ void WSurfaceItem::setLeftPadding(qreal newLeftPadding) return; d->paddings.setLeft(newLeftPadding); d->onPaddingsChanged(); - d->implicitWidthChanged(); + setImplicitWidth(d->calculateImplicitWidth()); Q_EMIT leftPaddingChanged(); } @@ -693,7 +702,7 @@ void WSurfaceItem::setBottomPadding(qreal newBottomPadding) return; d->paddings.setBottom(newBottomPadding); d->onPaddingsChanged(); - d->implicitHeightChanged(); + setImplicitHeight(d->calculateImplicitHeight()); Q_EMIT bottomPaddingChanged(); } @@ -710,7 +719,7 @@ void WSurfaceItem::setTopPadding(qreal newTopPadding) return; d->paddings.setTop(newTopPadding); d->onPaddingsChanged(); - d->implicitHeightChanged(); + setImplicitHeight(d->calculateImplicitHeight()); Q_EMIT topPaddingChanged(); } @@ -743,9 +752,12 @@ void WSurfaceItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGe ((newSize - oldSize) * d->surfaceSizeRatio).toSize()); } } else if (!d->surface && d->resizeMode != ManualResize) { - d->contentContainer->setSize(d->contentContainer->size() + - (newGeometry.size() - oldGeometry.size()) * d->surfaceSizeRatio); + if (d->contentContainer) + d->contentContainer->setSize(d->contentContainer->size() + + (newGeometry.size() - oldGeometry.size()) * d->surfaceSizeRatio); } + + d->updateBoundingRect(); } void WSurfaceItem::itemChange(ItemChange change, const ItemChangeData &data) @@ -762,6 +774,7 @@ void WSurfaceItem::itemChange(ItemChange change, const ItemChangeData &data) if (d->resizeMode != ManualResize) d->doResize(d->resizeMode); d->contentContainer->setSize(d->surfaceState->contentSize); + d->updateBoundingRect(); } } @@ -771,6 +784,7 @@ void WSurfaceItem::itemChange(ItemChange change, const ItemChangeData &data) // Use static_cast to avoid convert failed. auto item = static_cast(data.item); if (item && d->subsurfaces.removeOne(item)) { + d->updateBoundingRect(); Q_EMIT subsurfaceRemoved(item); } } @@ -780,9 +794,9 @@ void WSurfaceItem::focusInEvent(QFocusEvent *event) { QQuickItem::focusInEvent(event); - Q_D(WSurfaceItem); - if (d->eventItem) - d->eventItem->forceActiveFocus(event->reason()); + // Q_D(WSurfaceItem); + // if (d->eventItem) + // d->eventItem->forceActiveFocus(event->reason()); } void WSurfaceItem::releaseResources() @@ -872,7 +886,8 @@ bool WSurfaceItem::resizeSurface(const QSizeF &newSize) Q_D(const WSurfaceItem); if (!d->shellSurface || !d->contentContainer) return false; - const QRectF tmp(0, 0, newSize.width(), newSize.height()); + QRectF tmp(0, 0, newSize.width(), newSize.height()); + tmp -= d->paddings; // See surfaceSizeRatio, the content item maybe has been scaled. const QSize mappedSize = d->contentContainer->mapRectFromItem(this, tmp).size().toSize(); if (!d->shellSurface->checkNewSize(mappedSize)) @@ -932,14 +947,11 @@ void WSurfaceItem::updateSurfaceState() d->surfaceState->bufferScale = d->surface->bufferScale(); } - auto oldSize = d->surfaceState->contentGeometry.size(); d->surfaceState->contentGeometry = getContentGeometry(); d->surfaceState->contentSize = getContentSize(); - if (!qFuzzyCompare(oldSize.width(), d->surfaceState->contentGeometry.width())) - implicitWidthChanged(); - if (!qFuzzyCompare(oldSize.height(), d->surfaceState->contentGeometry.height())) - implicitHeightChanged(); + setImplicitSize(d->calculateImplicitWidth(), + d->calculateImplicitHeight()); if (bufferScaleChanged) Q_EMIT this->bufferScaleChanged(); @@ -996,6 +1008,7 @@ void WSurfaceItemPrivate::initForDelegate() contentItem->setSurface(surface); contentItem->setCacheLastBuffer(!surfaceFlags.testFlag(WSurfaceItem::DontCacheLastBuffer)); contentItem->setSmooth(q->smooth()); + contentItem->setLive(!q->flags().testFlag(WSurfaceItem::NonLive)); QObject::connect(q, &WSurfaceItem::smoothChanged, contentItem, &WSurfaceItemContent::setSmooth); newContentContainer.reset(contentItem); } else if (delegateIsDirty) { @@ -1033,6 +1046,7 @@ void WSurfaceItemPrivate::initForDelegate() } contentContainer = newContentContainer.release(); updateEventItem(false); + updateBoundingRect(); if (eventItem) updateEventItemGeometry(); @@ -1089,6 +1103,8 @@ void WSurfaceItemPrivate::updateSubsurfaceItem() const QPointF pos = contentContainer->position() + QPointF(subsurface->current.x, subsurface->current.y) / surfaceSizeRatio; item->setPosition(pos); } + + updateBoundingRect(); } void WSurfaceItemPrivate::onPaddingsChanged() @@ -1111,6 +1127,7 @@ void WSurfaceItemPrivate::updateContentPosition() Q_ASSERT(surfaceState); contentContainer->setPosition(-surfaceState->contentGeometry.topLeft() / surfaceSizeRatio + QPointF(paddings.left(), paddings.top())); + updateBoundingRect(); } WSurfaceItem *WSurfaceItemPrivate::ensureSubsurfaceItem(WSurface *subsurfaceSurface) @@ -1140,6 +1157,9 @@ WSurfaceItem *WSurfaceItemPrivate::ensureSubsurfaceItem(WSurface *subsurfaceSurf surfaceItem->setSurface(subsurfaceSurface); surfaceItem->setSmooth(q->smooth()); QObject::connect(q, &WSurfaceItem::smoothChanged, surfaceItem, &WSurfaceItem::setSmooth); + QObject::connect(surfaceItem, &WSurfaceItem::boundingRectChanged, q, [this] { + updateBoundingRect(); + }); // remove list element in WSurfaceItem::itemChange subsurfaces.append(surfaceItem); Q_EMIT q->subsurfaceAdded(surfaceItem); @@ -1156,12 +1176,14 @@ void WSurfaceItemPrivate::resizeSurfaceToItemSize(const QSize &itemSize, const Q if (!surface) { contentContainer->setSize(contentContainer->size() + sizeDiff); + updateBoundingRect(); return; } if (q->resizeSurface(itemSize)) { contentContainer->setSize(contentContainer->size() + sizeDiff); beforeRequestResizeSurfaceStateSeq = surface->handle()->handle()->pending.seq; + updateBoundingRect(); } } @@ -1182,6 +1204,7 @@ void WSurfaceItemPrivate::updateEventItem(bool forceDestroy) } else { eventItem = new EventItem(q_func()); eventItem->setZ(qreal(WSurfaceItem::ZOrder::EventItem)); + eventItem->setFocus(true); updateEventItemGeometry(); } @@ -1217,7 +1240,7 @@ void WSurfaceItemPrivate::doResize(WSurfaceItem::ResizeMode mode) } } -qreal WSurfaceItemPrivate::getImplicitWidth() const +qreal WSurfaceItemPrivate::calculateImplicitWidth() const { const auto ps = paddingsSize(); if (!surfaceState) @@ -1226,7 +1249,7 @@ qreal WSurfaceItemPrivate::getImplicitWidth() const return surfaceState->contentGeometry.width() + ps.width(); } -qreal WSurfaceItemPrivate::getImplicitHeight() const +qreal WSurfaceItemPrivate::calculateImplicitHeight() const { const auto ps = paddingsSize(); if (!surfaceState) @@ -1235,6 +1258,30 @@ qreal WSurfaceItemPrivate::getImplicitHeight() const return surfaceState->contentGeometry.height() + ps.height(); } +QRectF WSurfaceItemPrivate::calculateBoundingRect() const +{ + W_QC(WSurfaceItem); + QRectF rect = QRectF(0, 0, q->width(), q->height()); + + if (contentContainer) + rect |= q->mapFromItem(contentContainer, contentContainer->boundingRect()); + + for (auto sub : std::as_const(subsurfaces)) + rect |= sub->boundingRect().translated(sub->position()); + + return rect; +} + +void WSurfaceItemPrivate::updateBoundingRect() +{ + auto newBoundingRect = calculateBoundingRect(); + if (newBoundingRect == boundingRect) + return; + boundingRect = newBoundingRect; + + W_Q(WSurfaceItem); + Q_EMIT q->boundingRectChanged(); +} WToplevelSurface *WSurfaceItem::shellSurface() const { diff --git a/src/server/qtquick/wsurfaceitem.h b/src/server/qtquick/wsurfaceitem.h index 484b1b33..97504ec4 100644 --- a/src/server/qtquick/wsurfaceitem.h +++ b/src/server/qtquick/wsurfaceitem.h @@ -105,6 +105,7 @@ class WAYLIB_SERVER_EXPORT WSurfaceItem : public QQuickItem Q_PROPERTY(qreal surfaceSizeRatio READ surfaceSizeRatio WRITE setSurfaceSizeRatio NOTIFY surfaceSizeRatioChanged) Q_PROPERTY(qreal bufferScale READ bufferScale NOTIFY bufferScaleChanged) Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + Q_PROPERTY(QRectF boundingRect READ boundingRect NOTIFY boundingRectChanged) QML_NAMED_ELEMENT(SurfaceItem) public: @@ -117,7 +118,9 @@ class WAYLIB_SERVER_EXPORT WSurfaceItem : public QQuickItem enum Flag { DontCacheLastBuffer = 0x1, - RejectEvent = 0x2 + RejectEvent = 0x2, + NonLive = 0x4, + DelegateForSubsurface = 0x8, }; Q_ENUM(Flag) Q_DECLARE_FLAGS(Flags, Flag) @@ -133,6 +136,8 @@ class WAYLIB_SERVER_EXPORT WSurfaceItem : public QQuickItem explicit WSurfaceItem(QQuickItem *parent = nullptr); ~WSurfaceItem(); + QRectF boundingRect() const override; + static WSurfaceItem *fromFocusObject(QObject *focusObject); WSurface *surface() const; @@ -178,8 +183,8 @@ class WAYLIB_SERVER_EXPORT WSurfaceItem : public QQuickItem Q_SIGNALS: void surfaceChanged(); - void subsurfaceAdded(WSurfaceItem *item); - void subsurfaceRemoved(WSurfaceItem *item); + void subsurfaceAdded(WAYLIB_SERVER_NAMESPACE::WSurfaceItem *item); + void subsurfaceRemoved(WAYLIB_SERVER_NAMESPACE::WSurfaceItem *item); void resizeModeChanged(); void effectiveVisibleChanged(); void eventItemChanged(); @@ -193,6 +198,7 @@ class WAYLIB_SERVER_EXPORT WSurfaceItem : public QQuickItem void contentItemChanged(); void delegateChanged(); void shellSurfaceChanged(); + void boundingRectChanged(); protected: explicit WSurfaceItem(WSurfaceItemPrivate &dd, QQuickItem *parent = nullptr); diff --git a/src/server/qtquick/wxdgsurfaceitem.cpp b/src/server/qtquick/wxdgsurfaceitem.cpp index a174e186..8c466932 100644 --- a/src/server/qtquick/wxdgsurfaceitem.cpp +++ b/src/server/qtquick/wxdgsurfaceitem.cpp @@ -70,8 +70,7 @@ void WXdgSurfaceItem::onSurfaceCommit() Q_D(WXdgSurfaceItem); WSurfaceItem::onSurfaceCommit(); - if (auto popup = xdgSurface()->handle()->handle()->popup) { - Q_UNUSED(popup); + if (xdgSurface()->isPopup()) { d->setImplicitPosition(xdgSurface()->getPopupPosition()); } else if (auto toplevel = xdgSurface()->handle()->handle()->toplevel) { const QSize minSize(getValidSize(toplevel->current.min_width, 0), diff --git a/src/server/qtquick/wxwaylandsurfaceitem.cpp b/src/server/qtquick/wxwaylandsurfaceitem.cpp index 21db45ef..5b0cbf44 100644 --- a/src/server/qtquick/wxwaylandsurfaceitem.cpp +++ b/src/server/qtquick/wxwaylandsurfaceitem.cpp @@ -139,6 +139,11 @@ WXWaylandSurfaceItem::~WXWaylandSurfaceItem() } +WXWaylandSurface *WXWaylandSurfaceItem::xwaylandSurface() const +{ + return qobject_cast(shellSurface()); +} + bool WXWaylandSurfaceItem::setShellSurface(WToplevelSurface *surface) { Q_D(WXWaylandSurfaceItem); diff --git a/src/server/qtquick/wxwaylandsurfaceitem.h b/src/server/qtquick/wxwaylandsurfaceitem.h index b5837e69..08b89f72 100644 --- a/src/server/qtquick/wxwaylandsurfaceitem.h +++ b/src/server/qtquick/wxwaylandsurfaceitem.h @@ -36,7 +36,7 @@ class WAYLIB_SERVER_EXPORT WXWaylandSurfaceItem : public WSurfaceItem explicit WXWaylandSurfaceItem(QQuickItem *parent = nullptr); ~WXWaylandSurfaceItem(); - inline WXWaylandSurface* xwaylandSurface() const { return qobject_cast(shellSurface()); } + WXWaylandSurface* xwaylandSurface() const; bool setShellSurface(WToplevelSurface *surface) override; WXWaylandSurfaceItem *parentSurfaceItem() const; diff --git a/src/server/utils/wtools.cpp b/src/server/utils/wtools.cpp index d823a7ee..3c2d6fa6 100644 --- a/src/server/utils/wtools.cpp +++ b/src/server/utils/wtools.cpp @@ -11,6 +11,7 @@ extern "C" { #include #include +#include #include #include diff --git a/src/server/utils/wtools.h b/src/server/utils/wtools.h index d648fa00..5e746823 100644 --- a/src/server/utils/wtools.h +++ b/src/server/utils/wtools.h @@ -11,6 +11,10 @@ struct pixman_region32; +QT_BEGIN_NAMESPACE +class QQuickItem; +QT_END_NAMESPACE + WAYLIB_SERVER_BEGIN_NAMESPACE class WAYLIB_SERVER_EXPORT WTools