En esta página

Compositor de fantasía

Fancy Compositor es un ejemplo que demuestra cómo escribir un compositor Wayland en QML puro.

Introducción

Fancy Compositor es un pequeño ejemplo de compositor Wayland de escritorio que demuestra la potencia y facilidad de las APIs Qt Wayland Compositor QML.

El ejemplo del Compositor Fancy es similar al ejemplo Minimal QML, en el sentido de que es un compositor Wayland completo, implementado sólo usando código QML.

Inicializando el Compositor

Al igual que el ejemplo Minimal QML, Fancy Compositor soporta las principales extensiones de shell soportadas por Qt.

// Shell surface extension. Needed to provide a window concept for Wayland clients.
// I.e. requests and events for maximization, minimization, resizing, closing etc.
XdgShell {
    onToplevelCreated: (toplevel, xdgSurface) => screen.handleShellSurface(xdgSurface)
}

// Minimalistic shell extension. Mainly used for embedded applications.
IviApplication {
    onIviSurfaceCreated: (iviSurface) => screen.handleShellSurface(iviSurface)
}

// Deprecated shell extension, still used by some clients
WlShell {
    onWlShellSurfaceCreated: (shellSurface) => screen.handleShellSurface(shellSurface)
}

Éstas se instancian como hijas de WaylandCompositor, que las añade automáticamente a la lista de interfaces soportadas que se transmite a los clientes desde el servidor.

Cuando un cliente conectado crea una superficie y la vincula a una de las extensiones de shell, se emite la señal correspondiente. A continuación, se llama a un método dentro de nuestra clase personalizada WaylandOutput, que anexa el ShellSurface a un ListModel.

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

Este modelo se utiliza como fuente para un Repeater que crea ShellSurfaceItems dentro del compositor WaylandOutput. Esto añade una vista de la superficie en la escena Qt Quick. Como es un ShellSurfaceItem, también tiene ciertas opciones de interacción para el usuario del compositor, dependiendo de la extensión de shell que esté en uso.

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

Teclado

Además de las funciones básicas del sistema de ventanas, el Compositor Fancy también soporta un teclado opcional en pantalla que se ejecuta en proceso. Esto utiliza el módulo Qt Virtual Keyboard y se activará si el módulo está disponible.

import QtQuick
import QtQuick.VirtualKeyboard

InputPanel {
    visible: active
    y: active ? parent.height - height : parent.height
    anchors.left: parent.left
    anchors.right: parent.right
}

El código es sencillo. Instanciamos un InputPanel en la parte inferior de la salida, y nos aseguramos de que es visible si y sólo si está actualmente activo.

Loader {
    anchors.fill: parent
    source: "Keyboard.qml"
}

A continuación, el teclado se añade al WaylandOutput mediante un elemento Loader. El Loader se utiliza aquí para evitar tener una dependencia dura del módulo Qt Virtual Keyboard módulo. Si la carga falla, el compositor seguirá funcionando normalmente, pero sin soporte para un teclado en pantalla.

Por último, necesitamos una forma para que el compositor comunique la entrada de texto a sus clientes. Esto se hace a través de una extensión text-input. El ejemplo del Compositor Fancy soporta tanto el protocolo text_input_unstable_v2 como el protocolo qt_text_input_method_unstable_v1 de Qt.

La extensión qt_text_input_method_unstable_v1 se añade al compositor instanciando QtTextInputMethodManager como hijo de WaylandCompositor, y TextInputManager añade text_input_unstable_v2.

Las aplicaciones Qt más recientes elegirán qt_text_input_method_unstable_v1 cuando esté disponible, mientras que otros clientes pueden utilizar text_input_unstable_v2.

Transiciones

Además de la funcionalidad básica, el ejemplo de Fancy Compositor también muestra transiciones animadas entre estados.

La primera de ellas es la transición de activación. Esto sólo es compatible con XdgShell, ya que es la única extensión de shell que tiene un estado activated.

Connections {
    target: shellSurface.toplevel !== undefined ? shellSurface.toplevel : null

    // some signals are not available on wl_shell, so let's ignore them
    ignoreUnknownSignals: true

    function onActivatedChanged() { // xdg_shell only
        if (shellSurface.toplevel.activated) {
            receivedFocusAnimation.start();
        }
    }
}

SequentialAnimation {
    id: receivedFocusAnimation

    ParallelAnimation {
        NumberAnimation { target: scaleTransform; property: "yScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad }
        NumberAnimation { target: scaleTransform; property: "xScale"; to: 1.02; duration: 100; easing.type: Easing.OutQuad }
    }
    ParallelAnimation {
        NumberAnimation { target: scaleTransform; property: "yScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad }
        NumberAnimation { target: scaleTransform; property: "xScale"; to: 1; duration: 100; easing.type: Easing.InOutQuad }
    }
}

Cuando una ventana de cliente se activa bajo el protocolo XdgShell, activamos una animación que hace que la ventana "salte" durante 200 ms.

El Compositor Fancy también soporta una animación de destrucción. Esta se activa siempre que la ventana se cierra y la superficie se destruye, ya sea porque el cliente cerró su ventana con gracia, o incluso si se bloquea.

onSurfaceDestroyed: {
    bufferLocked = true;
    destroyAnimation.start();
}

SequentialAnimation {
    id: destroyAnimation

    ParallelAnimation {
        NumberAnimation { target: scaleTransform; property: "yScale"; to: 2/height; duration: 150 }
        NumberAnimation { target: scaleTransform; property: "xScale"; to: 0.4; duration: 150 }
        NumberAnimation { target: chrome; property: "opacity"; to: chrome.isChild ? 0 : 1; duration: 150 }
    }
    NumberAnimation { target: scaleTransform; property: "xScale"; to: 0; duration: 150 }
    ScriptAction { script: destroyAnimationFinished() }
}

Para asegurar que el contenido existe durante la duración de la animación, empezamos bloqueando el buffer. Esto significa que el último fotograma renderizado por el cliente permanecerá en memoria hasta que terminemos con él.

De nuevo, activamos una animación en la escala del elemento. La animación en cuestión imita el apagado de una pantalla CRT, dando una pista visual al usuario de que la ventana se está cerrando, y no se desvaneció en el aire.

Cualquier tipo de efecto animado puede ser utilizado para cambios de estado como estos, con toda la gama de Qt Quick a su disposición.

Proyecto de ejemplo @ code.qt.io

© 2026 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.