QtShell 编辑器

QtShell Compositor 展示了如何使用 shell 扩展。QtShell

QtShell Compositor 是一个桌面风格的 Wayland Compositor 示例,它实现了一个完整的 ,使用了名为 的专门Qt Wayland Compositor QtShellshell 扩展协议

该合成器使用Qt Quick 和 QML 实现。

建立连接

该示例将QtShell 列为WaylandCompositor 对象的唯一扩展。这意味着任何连接到服务器的客户端也必须支持该扩展,因此它们应该是与合成器运行相同版本 Qt 的 Qt 应用程序。

QtShell {
    onQtShellSurfaceCreated: (qtShellSurface) => screen.handleShellSurface(qtShellSurface)
}

当客户端连接到QtShell 接口时,就会创建一个QtShellSurface 。合成器会通过发射qtShellSurfaceCreated 信号来通知客户端。然后,该示例将外壳表面添加到ListModel ,以便日后轻松访问。

property ListModel shellSurfaces: ListModel {}
function handleShellSurface(shellSurface) {
    shellSurfaces.append({shellSurface: shellSurface});
}

ListModel 被用作Repeater 的模型,而 则创建在屏幕上显示客户端内容所需的Qt Quick 项目。

Repeater {
    id: chromeRepeater
    model: output.shellSurfaces
    // Chrome displays a shell surface on the screen (See Chrome.qml)
    Chrome {
        shellSurface: modelData
        onClientDestroyed:
        {
            output.shellSurfaces.remove(index)
        }
    }
}

它使用本地Chrome 类型来处理窗口状态和装饰。

Chrome

Chrome 是确保客户端内容可见并处理窗口状态、位置、大小等的类型。它以内置的QtShellChrome 为基础,自动处理窗口状态(最大化、最小化、全屏)和窗口激活(确保当时只有一个窗口处于活动状态)。

它的行为可以在一定程度上进行自定义,但也可以从头开始编写Chrome 功能,而不是从基本的Item 类型开始。QtShellChrome 是一个提供典型合成器行为的便利类,它为我们节省了在示例中实现这一逻辑的时间。

无论如何编写Chrome ,它都应该有一个ShellSurfaceItem 来保存客户端内容。

ShellSurfaceItem {
    id: shellSurfaceItemId
    anchors.top: titleBar.bottom
    anchors.bottom: bottomResizeHandle.top
    anchors.left: leftResizeHandle.right
    anchors.right: rightResizeHandle.left

    moveItem: chrome

    staysOnBottom: shellSurface.windowFlags & Qt.WindowStaysOnBottomHint
    staysOnTop: !staysOnBottom && shellSurface.windowFlags & Qt.WindowStaysOnTopHint
}
shellSurfaceItem: shellSurfaceItemId

ShellSurfaceItemQt Quick 场景中客户端内容的可视化表示。它的大小通常应与客户端缓冲区的大小相匹配,否则看起来可能会被拉长或挤压。QtShellChrome 的大小会自动与QtShellSurfacewindowGeometry 大小相匹配,即客户端缓冲区的大小加上框架边距的大小。边框边距是Chrome 两侧的保留区域,可用于包含窗口装饰。

因此,ShellSurfaceItem 被锚定到窗口装饰上,以填充为客户端缓冲区预留的区域。

窗口装饰

窗口装饰通常是客户端内容周围的一个框架,它可以添加信息(如窗口标题)和用户交互的可能性(如调整窗口大小、关闭、移动窗口等)。

通过QtShell ,窗口装饰总是由合成器而非客户端绘制。为了正确传达尺寸和位置,QtShell 还需要知道有多少窗口是为这些装饰预留的。这可以通过QtShellChrome 自动处理,也可以通过设置frameMarginLeftframeMarginRightframeMarginTopframeMarginBottom 手动处理。

对于窗口周围有调整大小手柄、顶部有标题栏的典型情况,使用默认的框架边距更为方便。QtShell Compositor 示例就是这样做的。

首先,我们创建Qt Quick 项来表示窗口装饰的不同部分。例如,在左侧应该有一个调整大小的手柄,用户可以抓住并拖动它来调整窗口的大小。

Rectangle {
    id: leftResizeHandle
    color: "gray"
    width: visible ? 5 : 0
    anchors.topMargin: 5
    anchors.bottomMargin: 5
    anchors.left: parent.left
    anchors.top: parent.top
    anchors.bottom: parent.bottom
}

在示例中,我们只需将其设置为一个 5 像素宽的矩形,锚定在Chrome 的顶部、底部和左侧。

同样,我们添加了Qt Quick 项,分别代表右侧、顶部、底部、左上角、右上角、左下角和右下角的大小调整手柄。我们还添加了标题栏。当装饰创建完成并正确锚定到Chrome 的两侧后,我们在QtShellChrome 中设置相应的属性。

leftResizeHandle: leftResizeHandle
rightResizeHandle: rightResizeHandle
topResizeHandle: topResizeHandle
bottomResizeHandle: bottomResizeHandle
bottomLeftResizeHandle: bottomLeftResizeHandle
bottomRightResizeHandle: bottomRightResizeHandle
topLeftResizeHandle: topLeftResizeHandle
topRightResizeHandle: topRightResizeHandle
titleBar: titleBar

设置装饰属性后,将自动添加默认的调整大小和重新定位行为。用户可以通过与调整大小的手柄交互来调整窗口的大小,也可以拖动标题栏来调整窗口的位置。QtShellSurface 的边框边距也将自动设置,以考虑装饰物的大小(只要没有显式设置边框边距属性)。

装饰物的可见性将由QtShellChrome 根据QtShellSurface 的窗口标志自动处理。

窗口管理

作为装饰的一部分,通常会有管理窗口状态和寿命的工具按钮。在示例中,这些按钮被添加到标题栏中。

RowLayout {
    id: rowLayout
    anchors.right: parent.right
    anchors.rightMargin: 5

    ToolButton {
        text: "-"
        Layout.margins: 5
        visible: (chrome.windowFlags & Qt.WindowMinimizeButtonHint) != 0
        onClicked: {
            chrome.toggleMinimized()
        }
    }

    ToolButton {
        text: "+"
        Layout.margins: 5
        visible: (chrome.windowFlags & Qt.WindowMaximizeButtonHint) != 0
        onClicked: {
            chrome.toggleMaximized()
        }
    }

    ToolButton {
        id: xButton
        text: "X"
        Layout.margins: 5
        visible: (chrome.windowFlags & Qt.WindowCloseButtonHint) != 0
        onClicked: shellSurface.sendClose()
    }
}

每个按钮的可见性取决于该按钮的窗口标志,当点击每个按钮时,我们只需调用QtShellChrome 中的相应方法。关闭 "按钮是个例外,它会调用QtShellSurface 中的sendClose() 方法。这将指示客户端自行关闭,并确保应用程序的优雅关闭。

Row {
    id: taskbar
    height: 40
    anchors.left: parent.left
    anchors.right: parent.right
    anchors.bottom: parent.bottom

    Repeater {
        anchors.fill: parent
        model: output.shellSurfaces

        ToolButton {
            anchors.verticalCenter: parent.verticalCenter
            text: modelData.windowTitle
            onClicked: {
                var item = chromeRepeater.itemAt(index)
                if ((item.windowState & Qt.WindowMinimized) != 0)
                    item.toggleMinimized()
                chromeRepeater.itemAt(index).activate()
            }
        }
    }
}

作为额外的窗口管理工具,示例中还有一个 "任务栏"。这只是底部的一排工具按钮,带有窗口标题。如果应用程序被其他窗口遮挡,可以点击这些按钮来去最小化应用程序,并将其放到最前面。与Chrome 类似,我们使用Repeater 来创建工具按钮,并使用外壳表面列表作为模型。为简单起见,本示例未对溢出(当任务栏上的应用程序过多时)进行任何处理,但在适当的合成器中,这也是应该考虑的问题。

最后,为了避免最大化后的应用程序扩展到填满任务栏所覆盖的区域,我们创建了一个特殊的项目来管理WaylandOutput 上可供客户端窗口使用的部分区域。

Item {
    id: usableArea
    anchors.top: parent.top
    anchors.left: parent.left
    anchors.right: parent.right
    anchors.bottom: taskbar.top
}

它被简单地固定在WaylandOutput 的两侧,但其底部锚点位于任务栏的顶部。

Chrome 中,我们使用该区域定义窗口的maximizedRect

maximizedRect: Qt.rect(usableArea.x,
                       usableArea.y,
                       usableArea.width,
                       usableArea.height)

默认情况下,该属性将与完整的WaylandOutput 匹配。但在本例中,我们不想将任务栏包含在可用区域中,因此我们覆盖了默认值。

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.