QtShell 组合器

QtShell 组合器展示了如何使用 QtShell 壳扩展。

QtShell 组合器是一个桌面风格的 Wayland 组合器示例,它实现了一个完整的 Qt Wayland 组合器,该组合器使用了名为 QtShell 的专用 shell 扩展协议

该组合器是用 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

ShellSurfaceItem 是在 Qt Quick 场景中客户端内容的可视化表示。它的大小通常应该与客户端缓存的尺寸相匹配,否则可能会看起来拉伸或挤压。QtShellChrome 会自动调整大小以匹配 QtShellSurface 的 windowGeometry(客户端缓冲区的大小加上边框的尺寸)。边框的边距是 Chrome 两侧的保留区域,可以用作包含窗口装饰。

因此,ShellSurfaceItem 锚定到窗口装饰,以填充客户端缓存保留的区域。

窗口装饰

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

使用 QtShell,窗口装饰总是由合成器绘制,而不是由客户端绘制。为了正确传达尺寸和位置,QtShell 还需要知道多少窗口被用于这些装饰。这可以通过 QtShellChrome 自动处理,或者通过设置 frameMarginLeft、frameMarginRight、frameMarginTop 和 frameMarginBottom 手动处理。

在典型的窗口周围有调整大小控件和顶部标题栏的案例中,依赖默认的边框边距更方便。QtShell 合成器示例就是这样做的。

首先,我们创建 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
}

在示例中,我们简单地将其做成一个五像素宽的矩形,锚定到 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来创建工具按钮,并使用shell表面列表作为模型。为了简单起见,示例没有对溢出(当任务栏中的应用程序过多时)进行处理,但在一个合适的合成器中,这也应该是需要考虑的。

最后,为了避免最大化的应用程序扩展到填充任务栏覆盖的区域,我们创建一个特殊的项目来管理客户端窗口可用的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

© 2024 The Qt Company Ltd. 本文档的贡献是各自所有者的版权。本文档是根据自由软件基金会发布的GNU自由文档许可证版本1.3的条款许可的。Qt及其相关标志是芬兰乃至全世界的商标,归The Qt Company Ltd.所有。所有其他商标均归其所有者所有。