QtShell Compositor shows how to use the QtShell shell extension.
QtShell Compositor is a desktop-style Wayland compositor example implementing a complete Qt Wayland Compositor which uses the specialized shell extension protocol called QtShell .
The compositor is implemented with Qt Quick and QML.
The example lists QtShell as the only extension to the WaylandCompositor object. This means that any client connecting to the server must also support this extension, thus they should be Qt applications running against the same version of Qt as the compositor.
QtShell { onQtShellSurfaceCreated: (qtShellSurface) => screen.handleShellSurface(qtShellSurface) }
When a client connects to the QtShell interface, it creates a QtShellSurface . The compositor is notified of this by the emission of the qtShellSurfaceCreated signal. The example then adds the shell surface to a ListModel for easy access later.
property ListModel shellSurfaces: ListModel {} function handleShellSurface(shellSurface) { shellSurfaces.append({shellSurface: shellSurface}); }
The ListModel is used as the model for a Repeater which creates the Qt Quick items required to display the client contents on screen.
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) } } }
It uses the local
Chrome
type, which handles window states and decorations.
The
Chrome
is the type that ensures the client contents are visible and also handles window state, position, size, and so on. It uses the built-in
QtShellChrome
as a basis, which automatically handles window state (maximized, minimized, fullscreen) and window activation (ensuring that only a single window is active at the time).
Its behavior can be customized to some extent, but it is also possible to write the
Chrome
functionality from scratch, building from a basic
Item
type instead.
QtShellChrome
is a convenience class which provides typical compositor behavior, and saves us the time of implementing this logic in the example.
However the
Chrome
is written, it should have a
ShellSurfaceItem
to hold the client contents.
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
The
ShellSurfaceItem
is the visual representation of the client's contents in the Qt Quick scene. Its size should usually match the size of the client's buffer, otherwise it may look stretched or squeezed.
QtShellChrome
will automatically be sized to the
QtShellSurface
's
windowGeometry
, which is size of the client's buffer plus the size of the frame margins. The frame margins are reserved areas on the sides of the
Chrome
which can be used to contain window decorations.
The ShellSurfaceItem is therefore anchored to the window decorations to fill the area reserved for the client buffer.
The window decoration is usually a frame around a client's contents which adds information (such as a window title) and the possibility of user interaction (such as resizing, closing, moving the window, and so on.)
采用 QtShell , window decorations are always drawn by the compositor and not by the client. In order for sizes and positions to be communicated correctly, QtShell also needs to know how much of the window is reserved for these decorations. This can be handled automatically by QtShellChrome , or manually, by setting frameMarginLeft , frameMarginRight , frameMarginTop and frameMarginBottom .
For typical cases where there are resize handles around the window and a title bar at the top, it is more convenient to rely on the default frame margins. The QtShell Compositor example does this.
First, we create Qt Quick items to represent the different parts of the window's decorations. On the left side, for example, there should be a resize handle that the user can grab and drag in order to resize the window.
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 }
We simply make this a five-pixel wide rectangle in the example, anchored to the top, bottom and left side of the
Chrome
.
Similarly, we add Qt Quick items that represent the right, top, bottom, top-left, top-right, bottom-left and bottom-right resize handles. We also add a title bar. When the decorations have been created and anchored correctly to the sides of the
Chrome
, we set corresponding properties in
QtShellChrome
.
leftResizeHandle: leftResizeHandle rightResizeHandle: rightResizeHandle topResizeHandle: topResizeHandle bottomResizeHandle: bottomResizeHandle bottomLeftResizeHandle: bottomLeftResizeHandle bottomRightResizeHandle: bottomRightResizeHandle topLeftResizeHandle: topLeftResizeHandle topRightResizeHandle: topRightResizeHandle titleBar: titleBar
When the decoration properties are set, the default resizing and repositioning behavior will be added automatically. The user will be able to interact with the resize handles in order to resize the window, and drag the title bar to reposition it. The frame margins of the QtShellSurface will also be set automatically to account for the size of the decorations (as long as none of the frame margins properties have been set explicitly.)
The visibility of the decorations will be handled automatically by the QtShellChrome based on the window flags of the QtShellSurface .
As part of the decorations, it is common to have tool buttons which manage the window state and life span. In the example, these are added to the title bar.
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() } }
The visibility of each button is conditional on the window flag for that button, and when each of them is clicked, we simply call the corresponding method in QtShellChrome . The exception is the "close" button, which calls the sendClose () method in QtShellSurface . This instructs the client to close itself, and ensures a graceful shutdown of the application.
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() } } } }
As an additional window management tool, the example has a "task bar". This is just a row of tool buttons at the bottom with the window titles. The buttons can be clicked to de-minimize applications and bring them to the front if they are obscured by other windows. Similarly to the
Chrome
,使用
Repeater
for creating the tool buttons and use the shell surface list as model for this. For simplicity, the example does not have any handling of overflow (when there are too many applications for the task bar), but in a proper compositor, this is also something that should be considered.
Finally, to avoid maximized applications expanding to fill the area covered by the task bar, we create a special item to manage the parts of the WaylandOutput real estate that is available to client windows.
Item { id: usableArea anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: taskbar.top }
It is simply anchored to the sides of the WaylandOutput , but its bottom anchor is at the top of the task bar.
在
Chrome
, we use this area to define the
maximizedRect
为窗口。
maximizedRect: Qt.rect(usableArea.x, usableArea.y, usableArea.width, usableArea.height)
By default, this property will match the full WaylandOutput . In our case, however, we do not want to include the task bar in the available area, so we override the default.