사용자 지정 확장

사용자 지정 확장에서는 사용자 지정 Wayland 확장 기능을 구현하는 방법을 보여 줍니다.

Wayland용 새 확장을 쉽게 작성할 수 있습니다. 이러한 확장은 XML 기반 형식을 사용하여 정의되며 wayland-scanner 도구는 이를 C++의 글루 코드로 변환합니다. Qt는 이를 확장하여 qtwaylandscanner 으로 추가 글루 코드를 생성하며, 이는 Qt 및 C++로 작성됩니다.

사용자 지정 확장 예제는 이러한 도구를 사용하여 Wayland 프로토콜을 확장하고 Wayland 클라이언트와 서버 간에 사용자 지정 요청 및 이벤트를 전송하는 방법을 보여줍니다.

이 예제는 네 가지 항목으로 구성되어 있습니다:

  • 프로토콜 자체의 정의.
  • 확장 기능을 지원하는 컴포저.
  • 확장을 지원하는 C++ 기반 클라이언트.
  • 확장을 지원하는 QML 기반 클라이언트.

프로토콜 정의

XML 파일 custom.xml 은 프로토콜을 정의합니다. 여기에는 "qt_example_extension"이라는 인터페이스가 포함되어 있습니다. 이 이름은 서버에서 브로드캐스트되고 클라이언트가 요청을 보내고 이벤트를 수신하기 위해 첨부할 이름입니다. 이 이름은 고유해야 하므로 공식 인터페이스와 구분할 수 있는 접두사를 사용하는 것이 좋습니다.

인터페이스는 일반적으로 요청과 이벤트의 두 가지 유형의 원격 프로시저 호출로 구성됩니다. "요청"은 클라이언트가 서버 측에서 호출하는 호출이고 "이벤트"는 서버가 클라이언트 측에서 호출하는 호출입니다.

예제 확장에는 서버가 클라이언트 창에 특정 변환을 적용하도록 지시하는 일련의 요청이 포함되어 있습니다. 예를 들어 클라이언트가 "바운스" 요청을 보내면 서버는 화면에 창이 바운스되도록 하여 이에 응답해야 합니다.

마찬가지로 서버가 클라이언트에 지침을 제공하는 데 사용할 수 있는 일련의 이벤트가 있습니다. 예를 들어, "set_font_size" 이벤트는 클라이언트가 기본 글꼴 크기를 특정 크기로 설정하도록 지시하는 이벤트입니다.

이 프로토콜은 요청과 이벤트의 존재 여부와 인수를 정의합니다. qtwaylandscanner 이 프로토콜에서 실행되면 프로시저 호출과 인수를 마샬링하고 연결을 통해 이를 전송하는 데 필요한 코드가 생성됩니다. 다른 쪽에서는 실제 응답을 제공하기 위해 구현할 수 있는 가상 함수에 대한 호출이 됩니다.

qtwaylandscanner 이 빌드의 일부로 자동으로 실행되도록 하기 위해, 서버 측과 클라이언트 측 글루 코드를 각각 생성하기 위해 CMake 함수 qt_generate_wayland_protocol_server_sources()qt_generate_wayland_protocol_client_sources() 를 사용합니다. ( qmake 를 사용하는 경우 WAYLANDSERVERSOURCESWAYLANDCLIENTSOURCES 변수를 사용하면 동일한 결과를 얻을 수 있습니다.)

컴포저 구현

Compositor 애플리케이션 자체는 QML과 Qt Quick 를 사용하여 구현되지만, 확장 기능은 C++ 로 구현됩니다.

첫 번째 단계는 qtwaylandscanner 에서 생성된 글루 코드의 하위 클래스를 생성하여 해당 기능에 액세스할 수 있도록 하는 것입니다. QML에서 액세스할 수 있도록 QML_ELEMENT 매크로를 클래스에 추가합니다.

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

생성된 클래스에서 상속하는 것 외에도 호기심 반복 템플릿 패턴을 사용하여 확장을 처리할 때 추가적인 편의를 제공하는 QWaylandCompositorExtensionTemplate 클래스도 상속합니다.

QWaylandCompositorExtensionTemplate 클래스는 QObject 기반 클래스이므로 상속 목록에서 첫 번째에 있어야 한다는 점에 유의하세요.

하위 클래스는 생성된 기본 클래스에서 가상 함수를 재구현하여 클라이언트의 요청을 처리할 수 있습니다.

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

이러한 재구현에서는 요청을 신호 방출로 간단히 변환하여 컴포저의 실제 QML 코드에서 처리할 수 있도록 합니다.

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); }

또한 서브클래스는 각 이벤트에 대한 슬롯을 정의하여 QML에서 호출하거나 신호에 연결할 수 있도록 합니다. 슬롯은 이벤트를 클라이언트로 전송하는 생성된 함수를 호출하기만 하면 됩니다.

void CustomExtension::setFontSize(QWaylandSurface*surface, 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); } } }

클래스 정의에 QML_ELEMENT 매크로를 추가하고 해당 빌드 단계를 빌드 시스템 파일에 추가했으므로 QML에서 인스턴스화할 수 있습니다.

컴포저가 확장자로 등록할 수 있도록 WaylandCompositor 객체의 직접 자식으로 만들었습니다.

    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)
        }
    }

이 객체에는 클라이언트로부터 받을 수 있는 요청에 대한 시그널 핸들러가 있으며 그에 따라 반응합니다. 또한 슬롯을 호출하여 이벤트를 전송할 수 있습니다.

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

C++ 클라이언트 구현

두 클라이언트 모두 인터페이스의 C++ 구현을 공유합니다. 컴포저에서와 마찬가지로 생성된 코드의 서브클래스를 템플릿 클래스로부터 상속받습니다. 이 경우 QWaylandClientExtensionTemplate을 상속합니다.

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

접근 방식은 컴포저의 접근 방식과 매우 유사하지만 반전된 점이 있습니다: 요청은 생성된 함수를 호출하는 슬롯으로 구현되고, 이벤트 가상 함수는 신호를 방출하기 위해 다시 구현됩니다.

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

클라이언트 코드 자체는 매우 간단하며 동작을 트리거하는 방법만 보여주기 위한 것입니다. 사용자 지정 페인트 이벤트에서는 직사각형과 레이블 세트를 그립니다. 이 중 하나라도 클릭하면 서버에 요청을 보냅니다.

    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();
    }

set_font_size 이벤트가 수신될 때 글꼴 크기를 업데이트하기 위해 확장 클래스의 신호가 슬롯에 연결됩니다.

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

슬롯은 글꼴 크기를 업데이트하고 창을 다시 칠합니다.

QML 클라이언트 구현

QML 클라이언트는 C++ 클라이언트와 유사합니다. C++ 클라이언트와 동일한 사용자 지정 확장 구현에 의존하며, 이를 활성화하기 위해 QML에서 이를 인스턴스화합니다.

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

UI는 클릭 가능한 사각형으로 구성되며, 사각형이 클릭될 때 TapHandler 을 사용하여 해당 요청을 전송합니다.

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

간단하게 설명하기 위해 예제에서는 bouncespin 요청과 set_font_size 이벤트만 보여드리도록 제한했습니다. 추가 기능에 대한 지원 추가는 독자의 몫으로 남겨두었습니다.

예제 프로젝트 @ 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.