터미널은 어떻게 작업 표시줄에 진행률을 그릴까
빌드를 걸어 두고 다른 창에서 일하다 보면 끝났는지 보려고 터미널을 자꾸 다시 열게 됩니다. 어떤 터미널에서는 작업 표시줄 아이콘에 초록색 막대가 차오르면서 진행 상황을 알려 줍니다. 그런데 제가 쓰는 Windows Terminal에서는 Gradle 빌드를 돌려도 이 막대가 나오지 않았습니다. 같은 불편을 적은 issue도 이미 올라와 있었습니다.
이 글에서는 그 막대가 어떻게 그려지는지, Windows Terminal에서는 왜 안 나왔는지, 그리고 어떻게 고쳤는지를 차례로 적습니다.
터미널에 보내는 글자 중에는 화면에 찍히지 않는 것이 있습니다
프로그램이 터미널에 글자를 보내면 터미널은 그것을 화면에 그립니다. 그런데 보낸 글자가 전부 화면에 찍히는 것은 아닙니다. 정해진 모양으로 시작하는 글자 묶음은 터미널이 “글자"가 아니라 “명령"으로 받아들입니다. 글자색을 바꾸거나 커서를 옮기는 일이 모두 이렇게 이루어집니다.
이런 명령 가운데 터미널 창 자체에 무언가를 시키는 종류가 있습니다. 이것을 OSC(Operating System Command)라고 부릅니다. 창 제목을 바꾸는 명령이 대표적이고 작업 표시줄 진행률도 이 종류에 속합니다.
직접 해 볼 수 있습니다. Windows Terminal을 열고 아래 한 줄을 실행하면 작업 표시줄 아이콘에 절반쯤 찬 막대가 생깁니다.
printf '\033]9;4;1;50\a'\033]가 “지금부터 OSC"라는 시작 표시이고 \a가 끝 표시입니다. 그 사이의 9;4는 진행률 명령입니다. 1은 “진행 중”, 50은 50퍼센트입니다. 막대를 지울 때는 상태 자리에 0을 넣습니다.
printf '\033]9;4;0\a'화면에는 아무 글자도 찍히지 않습니다. 터미널이 이 묶음을 가로채서 운영 체제에 “이 창의 진행률을 이만큼으로 표시해 달라"고 대신 전해 주기 때문입니다.
Gradle은 이미 이 신호를 보낼 줄 알았습니다
Gradle에는 빌드가 진행되는 동안 이 신호를 보내는 기능이 이미 들어 있었습니다. 다만 신호를 이해하지 못하는 터미널에 보내면 이상한 글자가 화면에 찍힐 수 있어서 아무 터미널에나 보내지는 않습니다.
그래서 Gradle은 신호를 보내기 전에 “지금 어떤 터미널에서 실행 중인가"를 먼저 확인합니다. 확인 대상에는 ConEmu, Ghostty, kitty, iTerm2가 있었습니다. Windows Terminal도 이 신호를 받아 작업 표시줄에 그려 줄 수 있지만 목록에는 없었습니다.
Gradle이 Windows Terminal을 알아보지 못해서 막대가 나오지 않았습니다.
Windows Terminal을 알아보는 방법
프로그램은 대개 환경 변수를 보고 자기가 어떤 터미널 안에서 실행 중인지 알아냅니다. 많은 터미널이 자기 이름과 버전을 환경 변수에 적어 둡니다. 하지만 Windows Terminal은 그렇게 하지 않습니다.
대신 Windows Terminal은 창 안에서 셸을 띄울 때마다 WT_SESSION이라는 환경 변수를 만들어 둡니다. 아래 명령으로 확인할 수 있습니다.
echo $WT_SESSION값이 나오면 Windows Terminal 안이고, 비어 있으면 다른 터미널입니다.
이 변수는 WSL 안에서도 보입니다. Windows Terminal이 이 변수를 WSL 쪽으로도 넘겨 주도록 설정해 두어서 그렇습니다. 덕분에 WSL 셸에서 Gradle을 돌려도 같은 방법으로 알아볼 수 있습니다.
버전을 알 수 없다는 점이 마음에 걸렸습니다. 진행률 표시는 Windows Terminal 1.6부터 지원되는데 그보다 오래된 버전인지 확인할 길이 없기 때문입니다. 그래도 1.6이 나온 지 여러 해가 지났습니다. Gradle 안의 다른 코드도 이미 같은 변수로 Windows Terminal을 알아보고 있어서 변수가 있으면 지원하는 것으로 보기로 했습니다.
고친 뒤에 확인한 것
고친 내용 자체는 작습니다. 터미널을 확인하는 곳은 ConsoleMetaData.java의 evaluateTaskBarProgressSupport()입니다. 여기에 “WT_SESSION이 있는가"를 하나 더했습니다.
static boolean evaluateTaskBarProgressSupport() {
// 원래 있던 확인: ConEmu
if (System.getenv("ConEmuPID") != null) {
return true;
}
+ /*
+ * 더한 부분.
+ * Windows Terminal은 버전을 알려 주지 않아서,
+ * WT_SESSION 값이 있기만 하면 지원하는 것으로 본다.
+ */
+ if (System.getenv("WT_SESSION") != null) {
+ return true;
+ }
// 이 아래로 Ghostty, kitty, iTerm2 확인
String term = System.getenv("TERM");
...
}시간이 더 든 쪽은 제대로 동작하는지 확인하는 일이었습니다.
화면에 보이지 않는 신호는 눈으로 확인할 수 없습니다. 그래서 먼저 Gradle이 내보내는 글자를 가로채서 그대로 저장해 봤습니다.
| 상황 | 신호를 보내는가 |
|---|---|
WT_SESSION이 있고 기본 출력 방식 | 보냄. 빌드가 끝나면 지우는 신호도 보냄 |
WT_SESSION이 없음 | 보내지 않음 |
--console=plain | 보내지 않음 |
| 출력을 다른 프로그램으로 넘길 때 | 보내지 않음 |
그다음 실제 Windows Terminal에서 WSL 탭, PowerShell 탭, cmd 탭을 열어 각각 빌드를 돌려 봤습니다. 세 곳 모두 작업 표시줄에 막대가 차올랐다가 빌드가 끝나면 사라졌습니다. 빌드 도중에 멈춰도 막대가 남지 않는 것까지 확인했습니다.
아직 남은 경우
한 가지 경우는 여전히 표시되지 않습니다. 실행 창에서 cmd를 바로 띄우면 Windows가 그 창을 Windows Terminal에 넘겨줍니다. 이렇게 열린 창에는 WT_SESSION이 없습니다. Gradle 쪽에서는 구분할 방법이 없어서 이전과 똑같이 동작합니다.
마치며
이 변경은 gradle/gradle#39260으로 반영됐고 Gradle 9.9.0에 들어갈 예정입니다.
고친 코드는 몇 줄이지만 그 몇 줄을 쓰려고 터미널이 글자와 명령을 어떻게 구분하는지부터 알아야 했습니다. 매일 쓰는 도구에서 불편한 점을 만나면 그냥 넘기지 않고 한 번 들여다볼 만하다는 것을 배웠습니다.