En esta página

Ampliación personalizada

Custom Extension muestra cómo implementar una extensión Wayland personalizada.

Es fácil escribir nuevas extensiones para Wayland. Se definen utilizando un formato basado en XML y la herramienta wayland-scanner convierte esto en código glue en C. Qt amplía esto con el qtwaylandscanner, que genera código glue adicional en Qt y C++.

El ejemplo de extensión personalizada muestra cómo usar estas herramientas para extender el protocolo Wayland y enviar peticiones y eventos personalizados entre un cliente Wayland y un servidor.

El ejemplo consta de cuatro elementos:

  • La definición del propio protocolo.
  • Un compositor que soporta la extensión.
  • Un cliente basado en C++ que soporta la extensión.
  • Un cliente basado en QML que soporta la extensión.

La definición del protocolo

El archivo XML custom.xml define el protocolo. Contiene una interfaz llamada "qt_example_extension". Este es el nombre que se emitirá desde el servidor y al que se asociará el cliente para enviar peticiones y recibir eventos. Este nombre debe ser único, por lo que es bueno utilizar un prefijo que lo diferencie de las interfaces oficiales.

Una interfaz suele constar de dos tipos de llamadas a procedimientos remotos: peticiones y eventos. Las "peticiones" son llamadas que el cliente realiza en el lado del servidor, y los "eventos" son llamadas que el servidor realiza en el lado del cliente.

La extensión de ejemplo contiene un conjunto de peticiones que ordenan al servidor aplicar determinadas transformaciones a la ventana del cliente. Por ejemplo, si el cliente envía una petición de "rebote", el servidor debe responder haciendo que la ventana rebote en la pantalla.

Del mismo modo, dispone de un conjunto de eventos que el servidor puede utilizar para dar instrucciones al cliente. Por ejemplo, el evento "set_font_size" es una instrucción para que el cliente establezca su tamaño de fuente predeterminado a un tamaño específico.

El protocolo define la existencia de peticiones y eventos, así como los argumentos que toman. Cuando se ejecuta qtwaylandscanner, se genera el código necesario para marshallar la llamada al procedimiento y sus argumentos y transmitirlo a través de la conexión. En el otro extremo, esto se convierte en una llamada a una función virtual que puede implementarse para proporcionar la respuesta real.

Para que qtwaylandscanner se ejecute automáticamente como parte de la compilación, utilizamos las funciones de CMake qt_generate_wayland_protocol_server_sources () y qt_generate_wayland_protocol_client_sources() para generar el código de cola del lado del servidor y del lado del cliente, respectivamente. (Cuando se utiliza qmake, las variables WAYLANDSERVERSOURCES y WAYLANDCLIENTSOURCES consiguen lo mismo).

Implementación del compositor

La propia aplicación Compositor se implementa utilizando QML y Qt Quick, pero la extensión se implementa en C++.

El primer paso es crear una subclase del código glue generado por qtwaylandscanner para que podamos acceder a su funcionalidad. Añadimos la macro QML_ELEMENT a la clase para hacerla accesible desde QML.

class CustomExtension  : public QWaylandCompositorExtensionTemplate<CustomExtension>
        , public QtWaylandServer::qt_example_extension
{
    Q_OBJECT
    QML_ELEMENT

Además de heredar de la clase generada, también heredamos de la clase QWaylandCompositorExtensionTemplate que proporciona cierta comodidad adicional a la hora de tratar con extensiones, utilizando el Patrón de Plantilla Curiosamente Recurrente.

Nótese que QWaylandCompositorExtensionTemplate debe ser la primera en la lista de herencia, ya que es una clase basada en QObject.

La subclase tiene reimplementaciones de funciones virtuales en la clase base generada, donde podemos manejar peticiones emitidas por un cliente.

protected:
    void example_extension_bounce(Resource *resource, wl_resource *surface, uint32_t duration) override;

En estas reimplementaciones, simplemente traducimos la petición a una emisión de señal, de modo que podamos manejarla en el código QML real del compositor.

void CustomExtension::example_extension_bounce(QtWaylandServer::qt_example_extension::Resource *resource, wl_resource *wl_surface, uint32_t duration) { Q_UNUSED(resource); auto surface = QWaylandSurface::fromResource(wl_surface);    qDebug() << "server received bounce" << surface << duration;
   emit bounce(surface, duration); }

Además, la subclase define ranuras para cada uno de los eventos, de modo que éstos pueden ser llamados desde QML o ser conectados a señales. Las ranuras simplemente llaman a las funciones generadas que envían los eventos al cliente.

void CustomExtension::setFontSize(QWaylandSurface*superficie, uint pixelSize) { if (surface) { Resource *target = resourceMap().value(surface->waylandClient()); if (target) {            qDebug() << "Server-side extension sending setFontSize:" << pixelSize;
            send_set_font_size(target->handle,  surface->resource(), pixelSize); } } }

Desde que añadimos la macro QML_ELEMENT a la definición de la clase (y añadimos los pasos de construcción correspondientes a los archivos del sistema de construcción), puede ser instanciada en QML.

La hacemos hija directa del objeto WaylandCompositor para que el compositor la registre como extensión.

    CustomExtension {
        id: custom

        onSurfaceAdded: (surface) => {
            var item = itemForSurface(surface)
            item.isCustom = true
        }

        onBounce: (surface, ms) => {
            var item = itemForSurface(surface)
            item.doBounce(ms)
        }

        onSpin: (surface, ms) => {
            var item = itemForSurface(surface)
            item.doSpin(ms)
        }

        onCustomObjectCreated: (obj) => {
            var item = customObjectComponent.createObject(defaultOutput.surfaceArea,
                                                          { "color": obj.color,
                                                            "text": obj.text,
                                                            "obj": obj } )
        }
    }

    function setDecorations(shown) {
        var n = itemList.length
        for (var i = 0; i < n; i++) {
            if (itemList[i].isCustom)
                custom.showDecorations(itemList[i].surface.client, shown)
        }
    }

El objeto tiene manejadores de señales para las peticiones que pueda recibir del cliente y reacciona a ellas en consecuencia. Además, podemos llamar a sus ranuras para enviar eventos.

            onFontSizeChanged: {
                custom.setFontSize(surface, fontSize)
            }

Implementación del cliente en C

Ambos clientes comparten la implementación C++ de la interfaz. Como en el compositor, hacemos una subclase del código generado que también hereda de una clase plantilla. En este caso, heredamos de QWaylandClientExtensionTemplate.

class CustomExtension : public QWaylandClientExtensionTemplate<CustomExtension>
        , public QtWayland::qt_example_extension

El enfoque es muy similar al del compositor, excepto que invertido: Las peticiones se implementan como slots que llaman a las funciones generadas, y los eventos funciones virtuales que reimplementamos para emitir señales.

void CustomExtension::sendBounce(QWindow *window, uint ms)
{
    QtWayland::qt_example_extension::bounce(getWlSurface(window), ms);
}

El código del cliente en sí es muy simple y sólo pretende mostrar cómo desencadenar el comportamiento. En un evento de pintura personalizado, dibuja un conjunto de rectángulos y etiquetas. Cuando se hace clic en cualquiera de ellos, emite peticiones al servidor.

    void mousePressEvent(QMouseEvent *ev) override
    {
        if (rect1.contains(ev->position()))
            doSpin();
        else if (rect2.contains(ev->position()))
            doBounce();
        else if (rect3.contains(ev->position()))
            newWindow();
        else if (rect4.contains(ev->position()))
            newObject();
    }

Para actualizar el tamaño de la fuente cuando se recibe el evento set_font_size, la señal en nuestra clase de extensión está conectada a una ranura.

        connect(m_extension, &CustomExtension::fontSize, this, &TestWindow::handleSetFontSize);

La ranura actualizará el tamaño de la fuente y repintará la ventana.

Implementación del cliente QML

El cliente QML es similar al cliente C++. Se basa en la misma implementación de la extensión personalizada que el cliente C++, y la instanciará en QML para habilitarla.

    CustomExtension {
        id: customExtension
        onActiveChanged: {
            registerWindow(topLevelWindow)
        }
        onFontSize: (window, pixelSize) => {
            topLevelWindow.fontSize = pixelSize
        }
    }

La interfaz de usuario consiste en algunos rectángulos clicables, y utiliza TapHandler para enviar las peticiones correspondientes cuando se hace clic en un rectángulo.

            TapHandler {
                onTapped: {
                    if (customExtension.active)
                        customExtension.sendBounce(topLevelWindow, 1000)
                }
            }

Para simplificar, el ejemplo se ha limitado a demostrar únicamente las peticiones bounce y spin, así como el evento set_font_size. Añadir soporte para las características adicionales se deja como ejercicio para el lector.

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.