사용자 정의 확장 기능
'사용자 정의 확장 기능'에서는 사용자 정의 Wayland 확장 기능을 구현하는 방법을 설명합니다.
Wayland용 새로운 확장 기능을 작성하는 것은 쉽습니다. 확장 기능은 XML 기반 형식으로 정의되며, ` wayland-scanner ` 도구가 이를 C 언어의 글루 코드로 변환합니다. Qt는 ` qtwaylandscanner`을 통해 이를 확장하여, Qt 및 C++로 작성된 추가 글루 코드를 생성합니다.

'사용자 정의 확장(Custom Extension)' 예제는 이러한 도구를 사용하여 Wayland 프로토콜을 확장하고, Wayland 클라이언트와 서버 간에 사용자 정의 요청 및 이벤트를 전송하는 방법을 보여줍니다.
이 예제는 다음 네 가지 항목으로 구성됩니다:
- 프로토콜 자체의 정의.
- 확장을 지원하는 컴포지터.
- 확장을 지원하는 C++ 기반 클라이언트.
- 이 확장을 지원하는 QML 기반 클라이언트.
프로토콜 정의
XML 파일 custom.xml 이 프로토콜을 정의합니다. 이 파일의 기본 이름은 루트 요소(protocol)의 name 속성과 일치해야 합니다. 이 파일에는 "qt_example_extension"이라는 인터페이스가 포함되어 있습니다. 이 이름은 서버에서 브로드캐스트되며, 클라이언트가 요청을 보내고 이벤트를 수신하기 위해 연결할 이름입니다. 이 이름은 고유해야 하므로, 공식 인터페이스와 구별될 수 있는 접두사를 사용하는 것이 좋습니다.
인터페이스는 일반적으로 “요청(requests )”과 “이벤트(events)”라는 두 가지 유형의 원격 프로시저 호출로 구성됩니다. “요청”은 클라이언트가 서버 측에서 수행하는 호출이며, “이벤트”는 서버가 클라이언트 측에서 수행하는 호출입니다.
예제 확장 프로그램에는 서버가 클라이언트 창에 특정 변환을 적용하도록 지시하는 일련의 요청이 포함되어 있습니다. 예를 들어, 클라이언트가 “bounce” 요청을 보내면 서버는 창이 화면에서 튀어오르도록 만들어 이에 응답해야 합니다.
마찬가지로, 이 확장 기능에는 서버가 클라이언트에 지시를 내리는 데 사용할 수 있는 일련의 이벤트가 포함되어 있습니다. 예를 들어, “set_font_size” 이벤트는 클라이언트가 기본 글꼴 크기를 특정 크기로 설정하도록 지시하는 명령입니다.
이 프로토콜은 요청과 이벤트의 존재는 물론, 이들이 받는 인자도 정의합니다. qtwaylandscanner 를 이 프로토콜에 적용하면, 프로시저 호출과 그 인자를 마샬링하고 이를 연결을 통해 전송하는 데 필요한 코드를 생성합니다. 반대쪽에서는 이것이 가상 함수 호출로 변환되며, 이 함수를 구현하여 실제 응답을 제공할 수 있습니다.
빌드 과정의 일부로 qtwaylandscanner 가 자동으로 실행되도록 하기 위해, CMake 함수인 qt_generate_wayland_protocol_server_sources() 와 qt_generate_wayland_protocol_client_sources() 를 사용하여 각각 서버 측 및 클라이언트 측 글루 코드를 생성합니다. ( qmake 를 사용할 경우, WAYLANDSERVERSOURCES 및 WAYLANDCLIENTSOURCES 변수를 통해 동일한 결과를 얻을 수 있습니다.)
컴포지터 구현
컴포지터 애플리케이션 자체는 QML과 Qt Quick 를 사용하여 구현되지만, 확장 기능은 C++로 구현됩니다.
첫 번째 단계는 qtwaylandscanner 에서 생성된 글루 코드의 서브클래스를 생성하여 해당 기능에 접근할 수 있도록 하는 것입니다. QML에서 이 클래스에 접근할 수 있도록 하기 위해 클래스에 QML_ELEMENT 매크로를 추가합니다.
class CustomExtension : public QWaylandCompositorExtensionTemplate<CustomExtension>
, public QtWaylandServer::qt_example_extension
{
Q_OBJECT
QML_ELEMENT생성된 클래스를 상속하는 것 외에도, Curiously Recurring Template Pattern을 사용하여 확장 기능을 다룰 때 추가적인 편의성을 제공하는 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) => {
const item = comp.itemForSurface(surface)
item.isCustom = true
}
onBounce: (surface, ms) => {
const item = comp.itemForSurface(surface)
item.doBounce(ms)
}
onSpin: (surface, ms) => {
const item = comp.itemForSurface(surface)
item.doSpin(ms)
}
onCustomObjectCreated: (obj) => {
const item = customObjectComponent.createObject(output.surfaceArea, { "obj": obj } )
}
}
function setDecorations(shown) {
for (const elem of itemList) {
if (elem.isCustom)
custom.showDecorations(elem.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)
}
}간결함을 위해 이 예제는 bounce 및 spin 요청과 set_font_size 이벤트만 시연하도록 제한되었습니다. 추가 기능에 대한 지원은 독자의 연습 과제로 남겨둡니다.
© 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.