Wayland 애플리케이션 개발이 왜 끔찍한 경험이 될 수 있는지, 창 생성부터 입력 처리, 프로토콜 확장, 클립보드와 화면 공유까지 개발자 관점에서 비판적으로 살펴본 글.
Felix' Ramblings<< 아무도 거의 요청하지 않은 음악 파일 배포하기>> Linux에서 내 태블릿 버튼 고치기
2025.05.12
나는 싫다: Wayland 애플리케이션 프로그래밍
짧은 소개부터 하자면:
linux용 그래픽 애플리케이션을 프로그래밍하고 싶다면, 주요 선택지는 X11 또는 Wayland를 사용하는 것입니다. Wikipedia에 따르면, X11의 첫 릴리스는 1984년이었습니다. X11은 클라이언트-서버 모델을 따릅니다. 아마 당시의 전체 계산 환경이 지금과 매우 달랐기 때문이라고 생각합니다. 무거운 작업을 전부 처리하는 중앙 서버가 있고, 사용자는 슬림 클라이언트 / 덤 터미널 / 기타 무엇이든을 통해 거기에 접속만 한다면, 데스크톱 시스템이 클라이언트-서버 모델을 따르는 것은 말이 됩니다. 반면 Wayland (protocol)의 첫 릴리스는 2008년이었습니다. 이것도 꽤 오래전이지만, 2000년대의 계산 환경은 아마 오늘날 우리가 가진 것과 훨씬 더 가까웠을 것입니다. 즉, PC에는 보통 데스크톱 / 그래픽 인터페이스가 함께 제공되고, 기계가 대부분의 연산을 하기 위해 외부 서버에 의존할 필요가 없습니다.
그래서 수년에 걸쳐 X11에서 Wayland로 전환하려는 움직임이 있었습니다. 그리고 적어도 표면적으로는, 이것은 제게 말이 됩니다. 개발자들은 아마 데스크톱의 다양한 요구사항에 대해 많은 것을 배웠을 테니, 이 새로운 데스크톱 환경을 위해 (대체로) 깔끔하게 단절하는 것은 유망해 보입니다. Wayland가 X11보다 본질적으로 더 안전하다는 주장도 읽어본 적이 있습니다. Wayland는 “구식”이 아니고, 성능과 현대적인 사용 사례를 염두에 두고 데스크톱을 설계할 수 있습니다.
저는 지금 sway를 실행하는 데스크톱 머신에서 이 글을 타이핑하고 있는데, sway는 Wayland 컴포지터입니다. 데스크톱 녹화 / 공유가 동작하지 않는 것 같은 흔한 장애물은 분명 있었습니다. 하지만 시간이 지나면서 이런 문제들은 해결되었습니다. 적어도 제 머신에서는요. 몇 년 전, 저는 X11과 Wayland를 둘 다 써봤습니다(아마 Arch Linux에서였던 것 같습니다). 그리고 솔직히, sway 설치는 i3/X11 설치보다 훨씬 쉬웠습니다. 이런 설치의 용이함, Wayland가 supposedly “Linux 데스크톱의 미래”라는 점, 그리고 XWayland를 통해 X11 애플리케이션을 지원한다는 점 때문에, 거친 부분이 있어도 저는 sway를 계속 사용하게 되었습니다.
그것이 Wayland를 사용하는 저의 이야기였습니다. 이제 개발하는 부분이 나옵니다. 그리고 그건 씨발 악몽이었습니다.
다른 개발자들이 사용할 라이브러리라면, 저는 다음 원칙을 아주 좋아합니다:
쉬운 일은 쉽게. 어려운 일도 가능하게. 그래픽 창을 열고 GPU로 간단한 렌더링을 하고 싶다면, raylib는 환상적인 라이브러리입니다. 예제 애플리케이션은 다음과 같습니다:
#include <raylib.h>
int
main(void)
{
InitWindow(1280, 720, "Test Window");
SetTargetFPS(144);
while ( ! WindowShouldClose())
{
BeginDrawing();
ClearBackground(RAYWHITE);
EndDrawing();
}
return 0;
}
믿기 어려울 정도로 단순합니다. Raylib는 “쉬운 일은 쉽게”라는 부분을 정말 잘 해냅니다. 이상적으로는, “업그레이드 경로”가 있어서 더 복잡한 코드를 사용하면 더 복잡한 엣지 케이스도 단계적으로 처리할 수 있어야 합니다 [0].
그래픽 Windows 애플리케이션을 개발할 때는 Windows.h를 사용해야 하고, 창 핸들을 생성하고 가져오기 위해 다소 난해한 함수 호출을 몇 번 해야 하며, 그런 다음 “메인 루프”에서 여러 창 이벤트(마우스 이동, 키보드 입력, 창 다시 그리기 요청, ...)를 처리하고 그에 맞게 대응합니다. raylib와 비교하면 꽤 더 복잡하므로, 저는 개선의 여지가 아주 많다고 봅니다.
이것이 제가 Wayland에 큰 희망을 걸었던 이유입니다. 와, 제가 틀렸습니다. Wayland는 단순한 사용 사례를 전혀 신경 쓰지 않습니다. 어떤 예제 애플리케이션이든 동작하게 만드는 과정이 너무나도 터무니없어서, Wayland에서 프로그래밍하는 매 순간마다 저는 Win32 프로그래밍을 하던 시절을 그리워하게 됩니다.
오해하지 마세요. 예를 들어 DPI를 인식하는 멀티 모니터 애플리케이션이 여러 입력 장치, 혼합된 주사율, 장치 핫플러깅까지 “그냥 동작하길” 기대하는 건 아닙니다. 다만 제가 꽤 단순할 거라고 기대하는 모든 것, 혹은 어떤 종류의 헬퍼 함수라도 있을 거라고 기대하는 모든 것이, 매 단계마다 믿을 수 없을 정도로 고통스럽다는 것입니다.
여기에는 코드를 올리지 않겠습니다. OpenGL 창을 열고 Wayland의 이 모든 광기를 이벤트 목록으로 변환하는 제 헬퍼 함수들이 1300줄이 넘기 때문입니다.
Wayland에서 창을 여는 과정은 대략 다음과 같습니다:
wl_display_roundtrip()&wl_display_dispatch()를 사용해 Wayland가 이벤트를 수집하고 콜백을 호출하도록 트리거합니다그리고 제가 모든 것이라고 할 때, 정말 모든 것을 뜻합니다.
wl_output을 저장한 뒤, 거기에 모든 콜백을 등록하세요.저는 이 모든 책임이 Wayland가 객체 지향 프로토콜이라는 점에 있다고 봅니다. 제어 흐름이 끔찍합니다. wl_display_roundtrip()&wl_display_dispatch() 호출 뒤에 어떤 코드가 어떤 순서로 실행될지 예측해 보면서 즐겨보세요. 저는 아직도 둘의 차이가 무엇인지, 어떤 순서로 호출해야 하는지 모릅니다. OpenGL 초기화 코드는 꽤 취약한데, 이런 콜백 함수들 중 일부는 초기화 중 여러 번 호출되기 때문입니다.
초기화 중에 뭐라도 망치면, 창은 안 뜨고 프로그램은 끝없이 실행되기만 할 가능성이 큽니다. 그리고 이 모든 콜백 개소리를 다 해냈다고 해도, 그것 중 어느 것도 사용하기 쉽지 않습니다.
키보드 입력: 이벤트 정보를 xkb로 초기화하고 변환해야 합니다. 그렇지 않으면 그 키 입력이 어떤 문자를 나타내야 하는지 알 수 없습니다.
키 눌림과 키 떼기 이벤트만 옵니다. 키 반복을 지원하고 싶다면, 맞습니다, 어떤 콜백에서 키 반복 정보를 가져와야 합니다. 그다음 그 정보를 사용해 timer FD를 수동으로 설정하고, 어떤 버튼이 반복되어야 하는지 직접 추적해야 합니다.
현재 주사율이 무엇인지 알고 싶나요?
* 모든 전역 wl_output 객체를 추적하고 각각에 대해 모든 콜백을 등록합니다
* 콜백에서 받은 주사율 정보를 wl_output 객체와 함께 저장합니다
* 표시 중인 surface에 대해 콜백을 등록합니다.
* surface에 진입하면 사용 중인 wl_output을 포함한 콜백을 받습니다. 그 wl_output을 저장해둔 전역 객체와 매칭하고 해당 주사율을 찾습니다.
기본적으로 sway에서는 프로그램이 타일링되어 있으면 창 장식이 보이지만, 창을 floating으로 만들면 장식이 없습니다. 이 동작을 바꾸고 싶다면:
* registry에서 zxdg_toplevel_decoration global을 가져옵니다
* 그것을 사용해 어떤 종류의 장식을 원하는지 명시적으로 요청합니다.
창이 팝업이어야 하나요? 그러면 최소 크기와 최대 크기를 같게 설정하세요.
가장 가치 있는 자료는 Wayland.app입니다. 여기에는 다음에 대한 개요가 있습니다
이걸 보면 이 전체가 얼마나 파편화되어 있는지 알 수 있습니다. 완전한 난장판입니다. 아, 그리고 저는 아직도 이런 확장들을 어떻게 사용하는지는 언급조차 하지 않았습니다. 이건 중요합니다. 왜냐하면 핵심 프로토콜만 사용해서는 창을 열 수 없기 때문입니다. 예전에는 할 수 있었지만, 표시를 담당하던 “shell”인 wl_shell이 deprecated 처리되었습니다. 농담이 아닙니다. 대신 XdgShell을 써야 합니다 [1]. 그런데 그 확장을 찾아보면 아마 XML 파일만 찾게 될 가능성이 큽니다. 인터페이스 코드는 wayland-scanner를 사용해 그 XML로부터 다음처럼 생성되기 때문입니다:
wayland-scanner private-code < xdg-shell.xml > xdg_shell.c
wayland-scanner client-header < xdg-shell.xml > xdg_shell.h
공식 Wayland 문서는 wayland-scanner를 언급하지 않고, XML로부터 생성된다는 말만 합니다. 그리고 저는 아직도 XML 파일을 어디서 구해야 하는지 확실하지 않습니다. Void Linux의 경우 wayland-protocols 패키지가 있어서 XML 파일들을 /usr/share/wayland-protocols에 넣어줍니다. 이건 순전한 광기입니다. 빈 창 하나 띄우기 위해 넘어야 할 장애물이 너무 많고, 설령 그걸 다 넘었다 해도 애플리케이션의 제어 흐름은 씨발 쓰레기입니다. 왜 “메인 이벤트 루프”를 유지하고, 관심 없는 이벤트는 쉽게 무시할 수 있는 방식만 제공하지 않았는지 전혀 모르겠습니다.
조금 더 감을 드리자면, 어떤 엿같은 이유에서인지 OpenGL 창을 여는 것이 CPU로 픽셀을 직접 그리는 것보다 더 쉽습니다. 네, 정말입니다. 소프트웨어 렌더링 애플리케이션에서는 대략 다음을 해야 합니다:
모든 global object를 가져옵니다
WlSurface를 생성합니다
WlSurface에 대한 모든 콜백을 등록합니다
XdgSurface를 생성합니다
XdgSurface에 대한 모든 콜백을 등록합니다
XdgToplevel을 생성합니다
XdgToplevel에 대한 모든 콜백을 등록합니다
shm_open을 사용해 제가 ShmFD라고 부를 shared memory object를 생성합니다
ftruncate로 크기를 설정하고 mmap으로 매핑합니다
전역 WlShm과 ShmFD를 사용해 wl_shm_pool을 생성합니다
wl_shm_pool을 사용해 WlBuffer를 생성합니다
WlSurface에 대해 wl_surface_commit을 호출합니다
wl_display_dispatch()와 wl_display_roundtrip()을 호출합니다
* XdgSurface.Configure 콜백에서는 WlBuffer를 WlSurface에 붙입니다. 그리고 표시할 내용이 있음을 알리기 위해 WlSurface를 damage 처리하고 commit합니다.
* XdgToplevel.Configure 콜백에서는 해상도가 바뀌었는지 추적해야 합니다. 바뀌었다면, 아마 새 크기에 맞는 또 다른 WlBuffer를 만들고 거기에 그려야 할 것입니다
언제 다시 그려야 하는지 알기 위해서는:
* WlSurface용 wl_callback을 생성합니다
* 그것에 대한 콜백을 만듭니다. 그 콜백이 호출되면 다음을 해야 합니다:
wl_callback을 파괴합니다wl_callback을 생성합니다wl_callback에 콜백들을 추가합니다여기서는 몇 가지 세부 사항을 생략하고 있지만, 이 개소리가 얼마나 미친 건지는 보실 수 있을 겁니다. 뭐라도 망치면, 애플리케이션은 반응성이 떨어지거나, 아예 렌더링하지 않거나, 너무 자주 렌더링하거나, 메모리를 누수하거나, 그리고 기타 등등의 문제가 생깁니다.
아래는 제가 특별한 순서 없이 우연히 마주친 것들의 목록입니다.
GPU로 렌더링한다면, 아마 EGL을 사용해 eglSwapBuffers를 쓰고 있을 것입니다. VSync를 활성화하면 eglSwapBuffers는 프레임이 표시될 때까지 자동으로 블록되는데, 이건 꽤 편리합니다(수동 sleep / 주사율 조회가 필요 없음). 하지만 모니터를 뽑았다가 다시 꽂으면, eglSwapBuffers는 무기한 블록될 수 있습니다.
제가 아는 한, Wayland에는 primary monitor라는 개념이 없습니다. 제 개인 애플리케이션에서는 가장 오른쪽 모니터를 기준으로 무언가를 하드코딩하고 싶었습니다. Wayland는 geometry 콜백을 제공하는데, 이 콜백은 전역 컴포지터 공간 내의 x와 y 위치(그리고 물리적 크기, subpixel 레이아웃, transform 등 기타 정보)를 반환해야 합니다. 그런데 몇몇 Wayland 환경들은 모니터 위치에 대해 항상 (0, 0)을 반환하는 것 같습니다.
Wayland 프로토콜 개발이 너무 느려서, 누군가(Valve 쪽 사람?)가 훨씬 더 빠르게 반복할 수 있도록 하기 위한 유일한 목적으로 Frog Protocols를 시작했습니다.
제가 아는 한, 현재의 “desktop state”, 즉 열린 창 목록, 그 위치 등을 가져오는 표준화된 방법은 없습니다. Wayland가 “안전”해야 한다는 건 알고 있지만, 단순히 이 기능을 제공하지 않는 것은 파편화를 초래하고, 어떤 종류의 권한 시스템이 있는 API를 제공하는 것보다 오히려 더 안전하지 않을 가능성이 큽니다. Sway에서는 JSON으로 통신하는 sway-ipc-socket을 사용해 “desktop state”를 가져올 수 있습니다(죽여줘). 하지만 작업공간과 무관한 창(예: 상태 표시줄)에 사용되는 Wlr Layer Shell 확장을 사용하는 애플리케이션 / surface를 질의하는 방법은 없습니다 [2].
Wayland 클립보드를 동작시키는 것은 악몽입니다. 저는 그저 텍스트를 클립보드에 복사하고 싶었을 뿐인데, 하다가 포기했습니다. 텍스트의 경우 제가 찾은 가장 쉬운 꼼수는 wl-clipboard를 실행하는 다른 프로세스를 시작하고, 관련 인자를 넘겨주는 것이었습니다.
핫플러깅이 반드시 동작하는 것도 아닙니다. 제 드로잉 태블릿 패드의 키들은 프로그램을 재시작하기 전까지는 어떤 애플리케이션에서도 인식되지 않습니다. 제 실험으로 판단하건대, Wayland는 장치가 연결되었음을 알리는 관련 이벤트를 생성하지 않는 것 같고, 반면 분리했을 때는 장치를 무효화하는 이벤트는 보내는 것처럼 보입니다 [3].
어떤 형태로든 화면 공유를 하려면 Xdg-Desktop-Portal이 필요합니다. Wayland가 화면 공유 / 녹화를 “범위 밖”이라고 생각한 덕분에, xdg-desktop-portal은 여러 컴포지터에 의해 구현되었고, 그 결과 파편화가 생겼습니다. Void Linux에서는 다음 구현들이 제공됩니다:
Wayland 창의 일부를 투명하게 만드는 데는 성공했지만, 그것을 “click-through”로 만들거나 / 이벤트를 뒤에 있는 창으로 전달하게 만드는 데는 성공하지 못했습니다.
마우스 커서를 설정하는 것도 엄청나게 귀찮습니다. 이제는 커서를 숨기거나 “일반” 커서를 표시하는 방법은 알지만, 다른 커서 종류는 어떻게 처리해야 할지 모르겠습니다. “CursorType” 문자열을 넘겨야 하는데, 유효한 문자열 전체 목록을 진짜로 찾을 수가 없었습니다 [4].
사용자로서, Wayland를 사용하는 것은 좋습니다.
X11과 비교했을 때, Wayland 컴포지터의 내부 구조도 훌륭한 업그레이드일 수 있습니다. 그건 잘 모르겠습니다.
개발자로서는, Wayland 코드를 다룰 때 자살하고 싶어집니다. 이 “비동기 객체 지향 프로토콜”의 API는 씨발 재앙입니다. Win32나 XLib을 사용하는 X11과 비교하면 엄청난 퇴보입니다. Wayland로는 단순한 애플리케이션을 작성할 수 없습니다. 각종 확장들, XML에서 API 코드를 생성한다는 사실, 그리고 Wayland와 상호작용할 때의 전체 제어 흐름까지, 모든 것이 철저하게 끔찍합니다. 이게 supposedly 앞으로의 모든 Linux 애플리케이션이 기반해야 할 토대라는 것입니다.
[0]: raylib가 그걸 얼마나 잘 지원하는지는 저도 모르겠습니다.
[1]: XDG Shell은 “일반적인 데스크톱 애플리케이션”에 사용됩니다. Wlr Layer Shell 같은 것도 있는데, 저는 상태 표시줄 같은 “작업공간 독립적인” 요소를 표시하는 데 사용합니다.
[2]: 이것을 질의하는 프로토콜 확장이 없습니다(sway에서). 소켓은 shell들에 대한 어떤 정보도 반환하지 않습니다. 제가 찾을 수 있었던 유일한 언급은, 이걸 할 방법이 없다고 말하는 어떤 reddit post였습니다.
[3]: 덧붙이자면: 몇 달 동안은 무엇이든 핫플러그하는 것만으로도 OBS가 GTK 라이브러리 코드 깊숙한 어딘가에서 그냥 바로 크래시 나기도 했습니다.
[4]: 다만 다른 protocol extension이 하나 더 생겼으니, 어쩌면 이게 상황을 좀 명확하게 해줄지도 모르겠습니다.
<< 아무도 거의 요청하지 않은 음악 파일 배포하기>> Linux에서 내 태블릿 버튼 고치기
Felix' Ramblings