사용자 지정 셸
사용자 지정 셸은 사용자 지정 셸 확장을 구현하는 방법을 보여줍니다.
Wayland의셸 확장은 창 상태, 위치 및 크기를 관리하는 프로토콜입니다. 대부분의 컴포저는 하나 이상의 기본 제공 확장을 지원하지만, 경우에 따라 애플리케이션에 필요한 정확한 기능을 포함하는 사용자 지정 확장을 작성하는 것이 유용할 수 있습니다.
이를 위해서는 Wayland 연결의 서버 측과 클라이언트 측 모두에서 셸 확장을 구현해야 하므로 주로 플랫폼을 구축하고 컴포저와 클라이언트 애플리케이션을 모두 제어할 때 유용합니다.
사용자 지정 셸 예제는 간단한 셸 확장의 구현을 보여줍니다. 이 예제는 세 부분으로 나뉩니다:
- 커스텀 셸 인터페이스에 대한 프로토콜 설명.
- 클라이언트 애플리케이션에서 인터페이스에 연결하기 위한 플러그인.
- 인터페이스의 서버 측 구현이 포함된 컴포저 예제.
프로토콜 설명은 wayland-scanner
에서 읽는 표준 XML 형식을 따릅니다. 여기서는 자세히 다루지는 않지만 다음과 같은 기능을 다룹니다:
wl_surface
에 대한 셸 표면을 생성하기 위한 인터페이스. 이를 통해 프로토콜은 기존wl_surface
API 위에 기능을 추가할 수 있습니다.- 셸 서페이스에 창 제목을 설정하는 요청.
- 셸 표면을 최소화/비최소화하라는 요청.
- 셸 서페이스의 현재 최소화 상태를 클라이언트에 알리는 이벤트.
qtwaylandscanner
이 빌드의 일부로 자동으로 실행되도록 하기 위해, 서버 측과 클라이언트 측 글루 코드를 각각 생성하기 위해 CMake 함수 qt_generate_wayland_protocol_server_sources() 및 qt_generate_wayland_protocol_client_sources() 를 사용합니다. ( qmake
를 사용하는 경우 WAYLANDSERVERSOURCES
및 WAYLANDCLIENTSOURCES
변수를 사용하면 동일한 결과를 얻을 수 있습니다.)
클라이언트 플러그인
Qt 클라이언트에서 셸 통합을 검색하려면 QWaylandShellIntegrationPlugin을 다시 구현해야 합니다.
class QWaylandExampleShellIntegrationPlugin : public QWaylandShellIntegrationPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QWaylandShellIntegrationFactoryInterface_iid FILE "example-shell.json") public: QWaylandShellIntegration *create(const QString &key, const QStringList ¶mList) override; }; QWaylandShellIntegration *QWaylandExampleShellIntegrationPlugin::create(const QString &key, const QStringList ¶mList) { Q_UNUSED(key); Q_UNUSED(paramList); return new ExampleShellIntegration(); }
이것은 셸 통합에 "example-shell" 키를 첨부하고 클라이언트가 인터페이스에 연결할 때 ExampleShellIntegration
클래스를 인스턴스화할 수 있는 방법을 제공합니다.
셸 확장을 만들기 위한 API는 qwaylandclientshellapi_p.h
헤더에서 사용할 수 있습니다.
#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>
이 헤더에는 공용 Qt API와 달리 바이너리 호환성이 보장되지 않으므로 비공개 API를 포함해야 합니다. 이 API는 여전히 안정적인 것으로 간주되며 소스 호환성을 유지하며 이 점에서는 Qt의 다른 플러그인 API와 유사합니다.
ExampleShellIntegration
은 위에서 설명한 대로 셸 서페이스를 만들기 위한 클라이언트 측 진입점입니다. 이것은 호기심 반복 템플릿 패턴을 사용하여 QWaylandShellIntegrationTemplate 클래스를 확장합니다.
class Q_WAYLANDCLIENT_EXPORT ExampleShellIntegration : public QWaylandShellIntegrationTemplate<ExampleShellIntegration> , public QtWayland::qt_example_shell { public: ExampleShellIntegration(); QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; };
또한 프로토콜의 XML 설명을 기반으로 qtwaylandscanner
에서 생성된 QtWayland::qt_example_shell
클래스에서 상속됩니다.
생성자는 지원하는 프로토콜의 버전을 지정합니다:
ExampleShellIntegration::ExampleShellIntegration() : QWaylandShellIntegrationTemplate(/* Supported protocol version */ 1) { }
example_shell 프로토콜은 현재 버전 1이므로 1
을 부모 클래스에 전달합니다. 이는 프로토콜 협상에서 사용되며 컴포저가 최신 버전의 프로토콜을 사용하는 경우 이전 클라이언트가 계속 작동할 수 있도록 합니다.
ExampleShellIntegration
이 초기화되면 애플리케이션은 서버에 연결되고 컴포저가 지원하는 전역 인터페이스의 브로드캐스트를 수신하게 됩니다. 성공하면 인터페이스에 대한 요청을 발행할 수 있습니다. 이 경우 지원 요청은 하나만 있습니다: 셸 표면 생성입니다. 이 컴포짓은 내장 함수 wlSurfaceForWindow()
를 사용하여 QWaylandWindow를 wl_surface
로 변환한 다음 요청을 발행합니다. 그런 다음 qt_example_shell_surface
인터페이스의 요청과 이벤트를 처리할 ExampleShellSurface
객체로 반환된 서페이스를 확장합니다.
QWaylandShellSurface *ExampleShellIntegration::createShellSurface(QWaylandWindow *window) { if (!isActive()) return nullptr; auto *surface = surface_create(wlSurfaceForWindow(window)); return new ExampleShellSurface(surface, window); }
ExampleShellSurface
은 두 개의 클래스를 확장합니다.
class ExampleShellSurface : public QWaylandShellSurface , public QtWayland::qt_example_shell_surface
첫 번째는 프로토콜의 XML 설명을 기반으로 생성되는 QtWayland::qt_example_shell_surface
클래스입니다. 이 클래스는 이벤트에 대한 가상 함수와 프로토콜의 요청에 대한 일반 멤버 함수를 제공합니다.
QtWayland::qt_example_shell_surface
클래스에는 이벤트가 하나만 있습니다.
void example_shell_surface_minimize(uint32_t minimized) override;
ExampleShellSurface
은 이를 재구현하여 내부 창 상태를 업데이트합니다. 창 상태가 변경되면 보류 중인 상태를 나중에까지 저장하고 QWaylandShellSurface 에서 applyConfigureWhenPossible()
를 호출합니다. 상태, 크기 및 위치 변경은 이와 같이 구성해야 합니다. 이렇게 하면 변경 사항이 표면 렌더링을 방해하지 않고 여러 관련 변경 사항을 하나로 쉽게 적용할 수 있습니다.
서페이스를 재구성해도 안전하면 가상 applyConfigure()
함수가 호출됩니다.
void ExampleShellSurface::applyConfigure() { if (m_stateChanged) QWindowSystemInterface::handleWindowStateChanged(platformWindow()->window(), m_pendingStates); m_stateChanged = false; }
여기서 실제로 새(최소화 또는 최소화 해제) 상태를 창에 커밋합니다.
두 번째 슈퍼 클래스는 QWaylandShellSurface 입니다. 이것은 Wayland의 QPA 플러그인 및 QWaylandWindow가 셸과 통신하는 데 사용하는 인터페이스입니다. ExampleShellSurface
는 이 인터페이스의 몇 가지 가상 함수도 재구현합니다.
bool wantsDecorations() const override; void setTitle(const QString &) override; void requestWindowStates(Qt::WindowStates states) override; void applyConfigure() override;
예를 들어, Qt 애플리케이션이 창 제목을 설정하면 이는 가상 setTitle()
함수에 대한 호출로 변환됩니다.
void ExampleShellSurface::setTitle(const QString &windowTitle) { set_window_title(windowTitle); }
ExampleShellSurface
에서 이것은 다시 사용자 지정 셸 표면 인터페이스에 대한 요청으로 변환됩니다.
컴포저
예제의 마지막 부분은 컴포저 자체입니다. 이것은 다른 컴포저 예제와 동일한 일반적인 구조를 가지고 있습니다. 컴포저의 빌딩 블록에 대한 자세한 내용은 최소 QML 예제를 참조하세요. Qt Wayland Compositor.
사용자 지정 셸 컴포저에서 주목할 만한 차이점 중 하나는 셸 확장의 인스턴스화입니다. Minimal QML 예제에서는 셸 확장자 IviApplication, XdgShell 및 WlShell 를 인스턴스화하지만, Custom Shell 예제에서는 ExampleShell
확장자의 인스턴스만 생성합니다.
ExampleShell { id: shell onShellSurfaceCreated: (shellSurface) => { shellSurfaces.append({shellSurface: shellSurface}); } }
셸 확장을 글로벌 인터페이스로 등록하기 위해 WaylandCompositor 의 직접 자식으로 셸 확장 인스턴스를 생성합니다. 이는 클라이언트가 연결할 때 브로드캐스트되며, 클라이언트는 이전 섹션에서 설명한 대로 인터페이스에 연결할 수 있습니다.
ExampleShell
은 생성된 QtWaylandServer::qt_example_shell
인터페이스의 서브클래스로, 프로토콜 XML에 정의된 API를 포함합니다. 또한 QWaylandCompositorExtensionTemplate 의 서브클래스로, QWaylandCompositor 에서 객체를 확장으로 인식하도록 합니다.
class ExampleShell : public QWaylandCompositorExtensionTemplate<ExampleShell> , QtWaylandServer::qt_example_shell
이러한 이중 상속은 확장을 구축할 때 Qt Wayland Compositor 의 일반적인 패턴입니다. QWaylandCompositorExtensionTemplate 클래스는 QWaylandCompositorExtension 과 qtwaylandscanner
에서 생성된 qt_example_shell
클래스 사이의 연결을 생성합니다.
마찬가지로 ExampleShellSurface
클래스는 생성된 QtWaylandServer::qt_example_shell_surface
클래스와 QWaylandShellSurfaceTemplate 클래스를 확장하여 ShellSurface 클래스의 하위 클래스가 되고 Qt Wayland Compositor 와 생성된 프로토콜 코드 사이의 연결을 설정합니다.
Qt Quick 에서 이 유형을 사용할 수 있도록 하기 위해 편의를 위해 Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS 전처리기 매크로를 사용합니다. 이 매크로는 무엇보다도 Qt Quick 그래프에 확장자가 추가되었을 때 자동으로 초기화하는 작업을 처리합니다.
void ExampleShell::initialize() { QWaylandCompositorExtensionTemplate::initialize(); QWaylandCompositor *컴포저 = static_cast<QWaylandCompositor *>(extensionContainer()); if (!compositor) { qWarning() << "Failed to find QWaylandCompositor when initializing ExampleShell"; return; } init(compositor->display(), 1); }
initialize()
함수의 기본 구현은 컴포저에 확장자를 등록합니다. 이 외에도 프로토콜 확장자 자체를 초기화합니다. QtWaylandServer::qt_example_shell_surface
클래스에서 생성된 init()
함수를 호출하여 이를 수행합니다.
또한 surface_create
요청에 대해 생성된 가상 함수도 다시 구현합니다.
void ExampleShell::example_shell_surface_create(Resource *resource, wl_resource *surfaceResource, uint32_t id) { QWaylandSurface *surface = QWaylandSurface::fromResource(surfaceResource); if (!surface->setRole(ExampleShellSurface::role(), resource->handle, QT_EXAMPLE_SHELL_ERROR_ROLE)) return; QWaylandResource shellSurfaceResource(wl_resource_create(resource->client(), &::qt_example_shell_surface_interface, wl_resource_get_version(resource->handle), id)); auto *shellSurface = new ExampleShellSurface(this, surface, shellSurfaceResource); emit shellSurfaceCreated(shellSurface); }
이 가상 함수는 클라이언트가 연결에서 요청을 발행할 때마다 호출됩니다.
셸 확장은 단일 QWaylandSurfaceRole 만 지원하지만 셸 표면을 만들 때 QWaylandSurface 에 할당하는 것이 여전히 중요합니다. 그 주된 이유는 동일한 서페이스에 충돌하는 역할을 할당하는 것은 프로토콜 오류로 간주되며, 이 오류가 발생할 경우 컴포저의 책임이 있기 때문입니다. 서피스를 채택할 때 역할을 설정하면 나중에 서피스를 다른 역할로 재사용할 때 프로토콜 오류가 발생하지 않도록 할 수 있습니다.
내장 함수를 사용하여 Wayland와 Qt 유형 간에 변환하고 ExampleShellSurface
객체를 생성합니다. 모든 준비가 완료되면 shellSurfaceCreated()
신호를 전송하고, 이 신호는 QML 코드에서 가로채서 셸 서피스 목록에 추가됩니다.
ExampleShell { id: shell onShellSurfaceCreated: (shellSurface) => { shellSurfaces.append({shellSurface: shellSurface}); } }
ExampleShellSurface
에서는 프로토콜 확장의 셸 표면 부분을 동일하게 활성화합니다.
예제 실행하기
클라이언트가 새 셸 확장에 성공적으로 연결하려면 처리해야 할 몇 가지 구성 세부 정보가 있습니다.
우선 클라이언트가 셸 확장의 플러그인을 찾을 수 있어야 합니다. 이를 위한 간단한 방법 중 하나는 QT_PLUGIN_PATH
이 플러그인 설치 디렉터리를 가리키도록 설정하는 것입니다. Qt는 카테고리별로 플러그인을 검색하므로, 플러그인 경로는 카테고리 wayland-shell-integration
의 디렉터리가 포함된 상위 디렉터리를 가리켜야 합니다. 따라서 설치된 파일이 /path/to/build/plugins/wayland-shell-integration/libexampleshellplugin.so
인 경우 QT_PLUGIN_PATH
을 다음과 같이 설정해야 합니다:
export QT_PLUGIN_PATH=/path/to/build/plugins
플러그인 디렉터리를 구성하는 다른 방법은 플러그인 설명서를 참조하십시오.
마지막 단계는 클라이언트가 실제로 올바른 셸 확장에 연결되는지 확인하는 것입니다. Qt 클라이언트는 자동으로 내장 셸 확장에 연결하려고 시도하지만, QT_WAYLAND_SHELL_INTEGRATION
환경 변수를 로드할 확장 이름으로 설정하면 이를 재정의할 수 있습니다.
export QT_WAYLAND_SHELL_INTEGRATION=example-shell
이것이 전부입니다. 사용자 지정 셸 예제는 몇 가지 기능만 있는 제한된 셸 확장이지만 특수한 확장을 구축하기 위한 시작점으로 사용할 수 있습니다.
© 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.