Ghostty 개발 일지 004
안녕하세요! Ghostty 👻의 네 번째 공식 개발 일지에 오신 것을 환영합니다!
이전 개발 일지를 놓치셨거나 Ghostty가 무엇인지 더 자세히 알고 싶으시다면 이 웹사이트의 Ghostty 페이지를 참고해 주세요.
커뮤니티 소식
지난주에 공개 Discord 서버를 공유했고, Ghostty와 Zig에 대한 발표도 진행했습니다. 두 소식 중 하나라도 놓치셨다면 꼭 확인해 보세요. 새로운 베타 초대는 Discord 커뮤니티를 통해 배포하고 있습니다. 그리고 Discord 커뮤니티는 어느덧 600명이 넘었습니다. 정말 놀랍네요!
지난 Ghostty 개발 일지 이후 클로즈드 베타 규모는 약 30명에서 약 100명으로 늘어났습니다. 프로젝트 초기에는 베타를 새로 모집할 때마다 몇 명에 불과했지만, Ghostty가 점점 안정화되면서 이제는 한 번에 15~20명씩 무리 없이 늘릴 수 있게 되었습니다. 아주 긍정적인 신호라고 생각합니다!
멋진 GUI
터미널 내부 구조와 오래된 기술의 깊은 세계로 들어가기 전에, GUI 개선 사항을 이야기하고 눈이 즐거운 것들을 살펴보겠습니다. 깊이 있는 기술적 내용을 찾고 계신다면 뒤쪽 섹션에서 본격적으로 다룹니다.
macOS와 Linux 모두에서 네이티브 GUI가 크게 발전했습니다. Linux에서는 Ghostty에 헤더바 메뉴, 와이드 탭(설정 가능), 정보 창 등이 추가되었습니다. Gnome Terminal이나 다른 GTK 기반 터미널과 나란히 놓아도 Ghostty가 전혀 어색하지 않습니다:

Linux에서는 이제 Sway, Hyprland 등 다양한 컴포지터를 통해 Wayland를 사용하는 베타 테스터가 여러 명 생겼습니다. 여러 문제를 수정했고, 이제 Ghostty는 Wayland에서 아주 잘 동작합니다.
macOS에서는 시스템 설정을 따르는 대신 Ghostty를 항상 다크 모드나 라이트 모드로 고정할 수 있는 window-theme 설정을 추가했습니다. 저는 개인적으로 macOS는 라이트 모드로 사용하지만 터미널은 항상 다크 모드로 쓰고 싶어서 다크로 고정해 두었습니다. 또한 포커스를 잃은 스플릿은 흐리게 표시되도록 했습니다(설정 가능). 모두 정말 아름답습니다:

Ghostty가 내세운 목표 중 하나는 현재 사용 중인 플랫폼을 위해 특별히 만들어진 것처럼 느껴지는 네이티브 경험을 제공하는 것입니다. 저희는 GUI 개선을 매우 중요하게 생각하며, Ghostty가 실행되는 플랫폼의 관용적인 디자인을 따르도록 하고 있습니다. 위의 개선 사항들은 이러한 이상을 향한 저희의 진전을 보여줍니다.
Vim 버그를 찾았습니다! 그리고 다른 말썽꾸러기 프로그램들
Ghostty는 대부분의 기간 동안 자신을 TERM=xterm-ghostty라고 밝혀 왔습니다. xterm- 접두사를 붙인 이유는 많은 말썽꾸러기 프로그램들이 TERM 값을 문자열 매칭으로 검사해 터미널이 특정 기능을 지원하는지 판단하기 때문입니다. 이는 잘못된 방법이며 아주 나쁜 습관입니다. 절대 이렇게 하지 마세요! 올바른 방법은 terminfo 데이터베이스를 조회하는 것입니다.
Terminfo 자체만으로도(주로 너무나 골치 아프기 때문에) 극도로 흥미로운 주제입니다. 언젠가 terminfo에 대한 글을 따로 써 보고 싶습니다. 이번 개발 일지에서는 terminfo에 대해 자세히 다루지 않겠습니다. 이 오래된 괴물의 모습을 살짝 엿보고 싶으시다면 터미널에서 infocmp를 실행해 보세요.
지난 한 달 동안 저희는 xterm- 접두사를 떼고 단순히 TERM=ghostty가 되려고 노력해 왔습니다. 그 과정에서 자체 terminfo 데이터베이스의 버그뿐 아니라 업스트림의 문제도 많이 발견했습니다. 발견하는 대로 모두 수정하고 있습니다.
가장 최근에는 Vim 버그 때문에 막혀 있습니다. Vim 9.0은 Kitty Keyboard Protocol을 지원하지만, 이를 지원하는 터미널 목록을 하드코딩하고 있어 terminfo 데이터베이스를 제대로 존중하지 않습니다. 이는 명백한 버그이며, 놀랍게도 Ghostty 커뮤니티 멤버 한 분이 이를 수정하는 풀 리퀘스트를 올려 주셨습니다. 이 문제는 Neovim에는 영향을 주지 않습니다.
Vim은 조금(?) 중요하기 때문에1, 해당 버그가 수정되고 하위 배포판에 널리 반영될 때까지는 아쉽게도 다시 TERM=xterm-ghostty로 되돌릴 수밖에 없었습니다. 그럼에도 지금까지 이룬 진전이 자랑스럽고, 곧 xterm이라는 족쇄에서 벗어날 수 있으리라 낙관합니다.
다른 업스트림 버그도 발견했는데, Ghostty 커뮤니티가 이슈를 제보하는 데 그치지 않고 직접 수정까지 제공해 주셔서 자랑스럽습니다. Tim이 인기 있는 Go 라이브러리 tcell에 특히 심각한 문제를 수정하는 PR을 올려 주셨습니다. 이 라이브러리는 lazygit, lazydocker 등 인기 있는 TUI 애플리케이션들이 사용하기 때문에 매우 중요합니다. 이 수정이 없으면 해당 프로그램들을 TERM=xterm-256color로 명시적으로 실행하지 않는 한 화면에 온통 깨진 문자를 쏟아냅니다. 곧 반영되기를 바랍니다!
이러한 버그나 수정 사항 중 어느 것도 Ghostty에만 국한된 것은 아니며, 모두 낯선 터미널 에뮬레이터를 마주했을 때 프로그램이 더 견고하게 동작하도록 만드는 데 기여할 것입니다.
이 작업은 주로 Tim Culverhouse 님이 주도해 주셨으며, 그가 해 주신 모든 일에 깊이 감사드립니다.
XTGETTCAP (#563)
terminfo의 큰 문제 중 하나는 실행 중인 프로그램이 직접 확인해야 하는 로컬 파일에 의존한다는 점입니다. 즉, 원격 호스트에 SSH로 접속했을 때 그 호스트에 내 터미널의 terminfo가 없으면 프로그램이 터미널의 기능을 제대로 파악하지 못해 오작동하거나 최적으로 동작하지 않을 수 있습니다.
XTGETTCAP은 로컬 파일을 읽는 대신 VT 이스케이프 시퀀스를 통해 터미널의 terminfo 데이터베이스를 조회할 수 있게 하는 터미널 기능입니다. 여기에는 몇 가지 이점이 있습니다. 프로그램이 바이너리 형식을 파싱하거나 파일 경로를 찾을 방법을 알 필요가 없고, 원격에서 실행 중이더라도 terminfo를 조회할 수 있습니다.
XTGETTCAP은 이렇게 생겼습니다:
# Query for stylized underline support (key "Smulx")
ESC P + q 536D756C78 ESC \
# Response from the terminal (Smulx=\E[4:%p1%dm)
ESC P 1 + r 536D756C78=5C45343A25703125646D ESC \완전히 직관적이죠? 사실 그렇게 어렵지는 않습니다. 프로그램은 ESC P + q <key> ESC \ 형태의 텍스트를 보내는데, 여기서 <key>는 16진수로 인코딩된 문자열입니다. 위 예시에서는 “Smulx”를 16진수로 인코딩했습니다. 그리고 응답에는 해당 terminfo 항목의 키와 값이 역시 16진수로 인코딩되어 담겨 있습니다.
저희 구현에서 특히 멋진 점은 Zig의 comptime 기능을 활용해 가능한 모든 요청과 응답을 컴파일 타임에 생성했다는 것입니다. 실제 terminfo 파일을 생성하는 것과 동일한 소스 파일에서 생성되므로, terminfo와 XTGETTCAP이 항상 완벽하게 동기화됩니다.
XTGETTCAP 성능이 사실 중요한 것은 아니지만, 저희 XTGETTCAP 구현은 컴파일 타임에 최적화된 룩업 테이블 덕분에 터무니없이 빠릅니다. 할당이나 문자열 포매팅 등을 할 필요가 없습니다. 단순히 조회해서 안정적인 문자 포인터를 찾은 뒤, 그것을 pty에 직접 쓰는 방식입니다. 정말 멋지죠.
이를 생성하는 Zig 코드 조각을 한번 살펴보세요. Zig comptime이 정말 마음에 듭니다.
가변 폰트 (#345)
대부분의 폰트는 “Bold”, “Heavy”, “Medium”, “Italic” 같은 고정된 스타일 세트를 제공합니다. 반면 가변 폰트는 하나의 폰트 페이스에서 두께, 기울기 등 다양한 축을 일정 범위 내의 연속적인 값으로 조절할 수 있게 해 줍니다. Ghostty는 이제 가변 폰트와 배리에이션 축 설정을 지원합니다.
먼저 가변 폰트가 어떻게 보이는지 살펴보겠습니다. Inconsolata에는 가변 폰트 버전이 있으며, 아래 영상에서 문자 너비와 두께를 세밀하게 조절하는 모습을 보실 수 있습니다.
다음은 Inconsolata를 사용했을 때 Ghostty의 기본 모습입니다. 폰트 배리에이션 축을 설정하지 않은 상태입니다:

그리고 다음은 두께를 살짝 수정한 설정입니다. 차이는 미묘하지만 분명히 보입니다. 먼저 설정 내용이고, 그 다음이 스크린샷입니다.
font-family = Inconsolata
font-variation-bold = wght=500

위에서 왜 weight가 아니라 wght일까요? 배리에이션의 키는 폰트에 의해 정해지며, 폰트 배리에이션은 반드시 네 글자여야 합니다. 이는 표준화되어 있지 않기 때문에 weight를 wght로 안전하게 변환할 수 없습니다. Ghostty 설정에서는 문자 그대로의 배리에이션 키를 사용하며, 사용자는 FontDrop 같은 폰트 검사 프로그램을 이용해 유효한 키를 확인할 수 있습니다.
가변 폰트 지원은 터미널 에뮬레이터 중에서는 비교적 드물기 때문에, 이를(게다가 크로스 플랫폼으로!) 지원하게 되어 자랑스럽습니다. 이런 기능 덕분에 터미널을 원하는 모습 그대로 세밀하게 조정하고 딱 맞는 느낌을 만들 수 있습니다.
마치며
가장 최근 베타 테스터 분들께 다시 한번 감사드립니다. 매번 특정 분야에 깊은 지식을 가진 분들이나 터미널을 새롭고 독특한 방식으로 사용해 문제를 찾아내는 정말 멋진 분들이 함께해 주십니다.
최신 소식을 받아보고 싶으시다면 Twitter나 Mastodon에서 저를 팔로우해 주세요(링크는 푸터에 있습니다). 이 블로그는 RSS 피드도 제공합니다.
Boo. 👻
각주
Bram Moolenaar 님의 명복을 빕니다. 우리에게 주신 모든 것에 감사드립니다. ↩
글을 무작위로 읽기