이 글은 AI가 단독으로 생성했으며 사람이 직접 작성하지 않았습니다. 내용을 신중히 판단하시거나, AI에 정확성 검증을 맡겨 보세요.
AI 번역简体中文한국어
주요 통찰
요구 사항
miniLED 외장 모니터를 하나 쓰고 있어서 영상 파일을 받을 때는 보통 DV나 HDR 버전을 먼저 고릅니다. MacBook 내장 디스플레이는 평소에도 EDR을 활용해 하이라이트를 더 밝게 표시할 수 있지만, 제가 쓰는 서드파티 모니터에서 HDR 영상을 제대로 보려면 먼저 ‘디스플레이’ 설정에서 HDR을 켜야 합니다.
엄밀히 말해 EDR이 애플 디스플레이에만 있는 기능은 아니며, 호환되는 외장 디스플레이도 지원할 수 있습니다. 여기서는 제 실제 사용 방식만 이야기하겠습니다. 영상을 보기 전에 HDR을 켜고, 다 본 뒤에는 SDR로 다시 전환합니다.
직접 전환하는 방법 자체는 어렵지 않습니다. ‘시스템 설정’을 열고 ‘디스플레이’로 들어가 외장 디스플레이를 선택한 다음 ‘고명암 대비’를 켜거나 끄면 됩니다. 문제는 영상을 볼 때마다 이 과정을 거쳐야 하고, 다 보고 나면 다시 반복해야 한다는 점입니다. 보다가 다른 일을 하려면 또 오가며 전환해야 하죠. 몇 번 반복하다 보면 꽤 번거롭습니다.
예전에 몇 번 추천한 적 있는 Space Launcher는 단축키로 스크립트를 실행할 수 있습니다. 그러니 가장 간단한 방법은 명확합니다. .command.command 파일을 만들어 한 번 눌러 HDR을 켜고, 다시 눌러 끄는 것입니다.
또 한 가지 걸리는 점은 일하는 장소가 일정하지 않고, 연결하는 모니터도 늘 같지 않다는 것입니다. 집에서는 4K 160 Hz 모니터를 쓰다가 다른 곳에서는 4K 144 Hz 모니터를 쓸 수도 있습니다. 포트와 케이블, 디스플레이 모드에 따라 이런 고주사율과 HDR을 동시에 켤 수 없는 경우도 있습니다.
그래서 스크립트에 몇 가지 원칙을 정했습니다.
스크립트를 한 번 실행하면 외장 디스플레이의 HDR 상태를 전환한다.
디스플레이 모델이나 이름, 임시 displayID를 코드에 고정하지 않는다. 일하는 장소에 따라 디스플레이가 달라지기 때문이다.
현재는 물리 외장 디스플레이 한 대만 처리한다. 나중에 실제로 두 대를 쓰게 되면 선택 기능을 추가한다.
현재 4K 160/144 Hz 모드에서 HDR을 사용할 수 있다면 주사율은 건드리지 않는다.
현재 모드에서 HDR을 켤 수 없는 경우에만 해상도와 스케일링을 그대로 유지한 채 주사율을 약 120 Hz로 낮춘다.
120 Hz에서도 HDR을 지원하지 않으면 원래 모드로 복구하고 오류를 알린다. 100 Hz나 60 Hz까지 더 낮추지는 않는다.
서드파티 디스플레이 도구를 설치하지 않고, root도 필요 없으며 손쉬운 사용 권한이나 UI 자동화에도 의존하지 않는다.
특히 마지막 조건 때문에 ‘시스템 설정’을 UI 자동화로 조작하는 방법은 사실상 제외했습니다.
셸 명령 하나로 해결할 수 없는 이유
Apple 공식 안내에서는 HDR 스위치가 ‘시스템 설정 → 디스플레이’에 있다고 설명하며, 외장 HDR은 Mac과 HDR10 디스플레이뿐 아니라 포트, 케이블, 변환기, 디스플레이 펌웨어에도 영향을 받는다고 안내합니다. Apple의 HDR 안내
공개된 CoreGraphics API로도 여러 정보를 읽을 수 있습니다. 어떤 디스플레이가 연결되어 있는지, 어느 것이 내장 디스플레이인지, 현재 해상도와 주사율이 얼마인지 등을 알 수 있죠. 예를 들어 CGGetOnlineDisplayList는 연결된 디스플레이를 나열하고, CGDisplayIsBuiltin는 내장 디스플레이인지 판별하며, CGDisplayMode는 현재 디스플레이 모드를 읽습니다.
하지만 Apple의 공개 API를 살펴봐도 ‘고명암 대비’ 스위치를 직접 바꿀 수 있는 인터페이스는 찾지 못했습니다. 즉, 디스플레이 정보는 공개적으로 읽을 수 있지만 HDR 스위치를 조작하는 공개 API는 따로 없습니다.
저도 osascript로 ‘시스템 설정’을 제어하는 방법을 생각해 봤습니다. 서드파티 도구는 설치하지 않아도 되지만 손쉬운 사용 및 자동화 권한이 필요하고, 실행하면 창이 열리면서 포커스를 빼앗습니다. 더 큰 문제는 시스템 설정의 화면 구성과 문구에 의존한다는 점입니다. macOS가 업데이트되면 작동하지 않을 수도 있습니다. 제가 원한 건 단축키를 누르면 조용히 전환되는 방식이라 이 방법은 쓰지 않았습니다.
결국 사용한 것은 macOS에 기본으로 포함되어 있지만 공개 문서는 없는 MonitorPanel.framework입니다. 현재 디스플레이 모드를 읽고 그 모드에서 HDR을 사용할 수 있는지 확인하며, HDR 상태도 바꿀 수 있습니다. 구현할 때는 주로 ToggleHDR.swift를 참고했습니다. 같은 프레임워크를 사용하고 있어 적어도 접근 방식이 가능하다는 점은 확인할 수 있었습니다.
물론 비공개 API의 가장 큰 문제는 Apple이 앞으로도 계속 작동한다고 보장하지 않는다는 것입니다. macOS가 업데이트되면 클래스명이나 메서드명, 호출 방식은 물론 같은 메서드의 실제 동작까지 달라질 수 있습니다. 그래서 스크립트는 실행할 때마다 먼저 점검하고, 인터페이스가 예상과 다르면 디스플레이 설정을 건드리지 않고 바로 중단합니다.
스크립트의 동작 방식
전체 흐름은 간단히 말해 이렇습니다. 현재 모드에서 HDR을 켤 수 있으면 바로 켜고, 안 되면 약 120 Hz를 시도합니다. 120 Hz에서도 안 되면 원래 상태로 복구합니다. 아래는 인수 없이 실행했을 때의 흐름이며, --on와 --off는 목표 상태를 각각 켜기와 끄기로 고정하는 옵션일 뿐입니다.
1. 올바른 디스플레이 찾기
스크립트는 ‘지난번에 조작한 디스플레이가 무엇인지’를 기억하지 않고, 실행할 때마다 새로 찾습니다.
먼저 CoreGraphics로 현재 연결된 디스플레이를 찾습니다.
현재 사용 중인 디스플레이만 남깁니다.
내장 디스플레이, Sidecar, AirPlay는 제외합니다.
미러링 모드라면 작업하지 않습니다.
마지막으로 후보가 정확히 한 대인지 확인합니다.
최종적으로 외장 디스플레이가 한 대만 남아야 합니다. 외장 디스플레이가 없거나 두 대가 연결되어 있으면 스크립트는 찾은 디스플레이를 나열한 뒤 종료합니다. 이번에는 전환하지 않는 편이 다른 디스플레이에 HDR을 켜는 것보다 낫습니다.
대상을 고를 때는 의도적으로 UUID를 사용하지 않습니다. 대상은 ‘현재 유일하게 연결된 외장 디스플레이’로 정합니다. 선택한 뒤에야 UUID를 사용해 전환 과정에서 같은 디스플레이를 다시 찾고, 해당 디스플레이의 복구 기록과 연결합니다. 따라서 다른 장소로 옮겼을 때 이전 디스플레이의 기록을 새 디스플레이에 잘못 적용하지 않습니다.
2. 현재 모드에서 HDR을 켤 수 있으면 주사율은 그대로
스크립트는 먼저 현재 디스플레이 모드인 currentMode를 읽고 hasHDRModes를 확인합니다. 이 값은 간단히 말해 MonitorPanel가 ‘현재 모드에서 HDR을 사용할 수 있다’고 보고했는지를 나타냅니다.
현재 모드에서 HDR을 사용할 수 있으면 바로 켭니다. 설정 후에는 인터페이스의 반환값만 믿지 않고 디스플레이 상태를 다시 읽습니다. MonitorPanel로 해상도와 스케일링이 그대로인지 확인하고, CoreGraphics로 실제 주사율이 바뀌지 않았는지도 확인합니다. 연속 두 번 읽은 결과가 모두 일치해야 전환이 성공한 것으로 봅니다.
즉, 4K 160/144 Hz에서 원래 HDR을 지원한다면 안전을 위해 120 Hz로 낮추지 않습니다. 반대로 HDR을 켜는 과정에서 시스템이 디스플레이 모드를 바꿔 버리면 스크립트가 이를 감지하므로 성공으로 처리하지 않습니다.
3. HDR을 켤 수 없으면 120 Hz 시도
다음 두 경우에만 120 Hz 모드를 찾습니다. 현재 모드가 HDR을 지원하지 않는 것이 확실하고 주사율이 약 120 Hz를 초과하는 경우, 또는 VRR/ProMotion 같은 가변 주사율 모드를 사용 중인 경우입니다.
찾은 모드는 아래 조건을 모두 충족해야 합니다.
고정 주사율이 119~121 Hz일 것
현재 모드와 논리 해상도가 같을 것
물리 픽셀 해상도가 같을 것
HiDPI, 스케일링 비율, 인터레이스 속성을 유지할 것
시스템에서 사용자가 선택할 수 있는 데스크톱 모드로 표시할 것
이 조건은 모두 충족해야 합니다. ‘3840 × 2160’만 비교해서는 충분하지 않습니다. 같은 물리 해상도에도 스케일링 단계가 여러 개 있을 수 있기 때문입니다. 주사율이 잠시 낮아지는 건 괜찮지만 데스크톱 스케일링이 갑자기 달라지는 건 원치 않습니다.
후보가 여러 개면 120 Hz와의 차이가 가장 작은 모드를 선택하고, 차이가 같으면 주사율이 더 높은 쪽을 우선합니다. 적절한 120 Hz 모드를 찾지 못하면 바로 실패 처리하며 100 Hz나 60 Hz까지 계속 시도하지 않습니다. HDR을 위해 주사율만 조금 낮추려는 것이지, 디스플레이 사용 경험을 통째로 바꾸고 싶은 건 아니니까요.
4. HDR을 끌 때 원래 주사율로 복구
HDR을 켤 때 실제로 160/144 Hz에서 약 120 Hz로 낮췄다면 스크립트는 전환 전후의 디스플레이 모드를 모두 기록합니다. 다시 연결할 때 바뀔 수 있는 모드 번호만 저장하는 게 아니라 해상도, 주사율, HiDPI, 스케일링, 픽셀 형식과 디스플레이의 UUID, vendor, model, serial 등의 정보도 함께 기록합니다.
HDR을 끌 때는 먼저 같은 디스플레이인지, 현재 모드가 이전에 전환한 약 120 Hz 모드인지 확인한 다음에야 원래 주사율로 복구합니다. 디스플레이를 다시 연결하면 원래 모드 번호가 바뀌었을 수 있습니다. 이때는 해상도와 주사율, 스케일링 등의 정보를 이용해 모드를 다시 찾습니다. 정확히 일치하는 모드가 하나일 때만 복구하며, 찾지 못하거나 여러 개가 일치하면 복구를 포기합니다.
HDR이 켜진 상태에서 주사율이나 스케일링을 직접 바꿨다면 스크립트는 이전 설정을 복구하지 않습니다. HDR만 끄고, 나중에 직접 선택한 모드는 그대로 둡니다.
첫 실행 때 컴파일이 필요한 이유
셸에서는 Objective-C 비공개 프레임워크를 직접 호출할 수 없습니다. 모든 내용을 파일 하나에 담기 위해 toggle-external-hdr.command 안에 Objective-C helper 소스 코드를 넣었습니다.
처음 실행할 때 바깥쪽 zsh 스크립트가 시스템에 포함된 xcrun clang를 호출해 helper를 컴파일하고 결과를 다음 위치에 캐시합니다.
TEXT
~/Library/Caches/toggle-external-hdr/
캐시는 macOS 빌드 버전과 CPU 아키텍처, 내장 소스 코드의 해시를 기준으로 구분됩니다. 시스템이나 helper 소스가 바뀌면 다시 컴파일하고, 그대로면 이후에는 캐시를 사용하므로 매번 컴파일을 기다릴 필요가 없습니다.
스크립트는 캐시 디렉터리와 바이너리가 현재 사용자 소유인지, 권한이 올바른지, 의심스러운 심볼릭 링크나 하드 링크는 아닌지도 확인합니다. 검사를 통과하지 못하면 중단하며, 컴파일에 실패해도 이전 버전을 대신 실행하지 않습니다.
서드파티 디스플레이 소프트웨어를 설치할 필요는 없지만, Mac에 Apple Xcode 또는 Command Line Tools가 설치되어 있어야 합니다.
스크립트가 작업 도중 디스플레이 모드를 전환하면 화면이 몇 초간 검게 변할 수 있고, 디스플레이의 내부 식별 번호가 바뀔 수도 있습니다. 더 곤란한 경우는 주사율을 120 Hz로 낮춘 뒤 HDR을 켜기 전에 스크립트가 중단되는 상황입니다.
그래서 스크립트는 진행 상태를 기록하며, 상태는 다음 네 가지입니다.
단계
의미
`pending`
원래 모드를 저장했고 주사율 전환을 준비하는 중
`enabling`
대체 모드로 전환했고 HDR을 켤 준비가 된 상태
`active`
HDR이 켜졌고 복구 정보가 유효한 상태
`disabling`
HDR을 끄는 중이며 다음 단계에서 원래 모드로 복구할 상태
다음에 실행하면 스크립트는 먼저 이전 실행이 중간에 끝났는지 확인합니다. 현재 모드가 스크립트가 바꾼 모드라고 확실히 판단되면 복구를 이어갑니다. 확신할 수 없으면 중단하고, ‘비슷해 보이는’ 모드로 현재 설정을 덮어쓰지 않습니다. 상태가 active인데 그 뒤에 직접 모드를 바꿨다면, 스크립트는 오래된 복구 기록을 지우고 HDR을 계속 끕니다. 다음번에 만료된 주사율 설정을 다시 적용하지 않도록 하기 위해서입니다.
실행 잠금도 추가했습니다. 다른 프로세스가 전환 중이면 새로 실행한 스크립트는 기다리지 않고 바로 종료합니다. 단축키를 빠르게 두 번 누르거나 스크립트를 연달아 두 번 실행하거나, 터미널 두 개에서 동시에 실행해도 두 프로세스가 같은 디스플레이를 함께 바꾸는 일이 없습니다.
사용 방법
스크립트에는 이미 실행 권한이 설정되어 있습니다. 스크립트가 있는 디렉터리로 이동한 다음 현재 상태를 먼저 확인하는 것을 권장합니다.
BASH
./toggle-external-hdr.command --status
평소에는 인수 없이 실행하면 됩니다. HDR을 켜거나 끄도록 명시할 수도 있습니다.
CodeBlock Loading...
확장자가 .command이므로 Finder에서 더블 클릭해도 실행할 수 있습니다. 더 편리하게 쓰려면 Space Launcher에서 단축키를 지정하면 됩니다. 스크립트를 다운로드하거나 복사한 뒤 실행 권한이 사라졌다면 다시 부여할 수 있습니다.
BASH
chmod +x ./toggle-external-hdr.command
--status는 스크립트가 찾은 디스플레이와 현재 해상도 및 주사율, HDR 지원 여부와 현재 켜져 있는지, 다음에 어떤 작업을 할지 보여 줍니다. HDR을 전환하거나 디스플레이 모드를 바꾸지는 않지만, 첫 실행 때 helper를 컴파일하면서 캐시를 기록할 수는 있습니다.
이전 실행이 중간에 중단됐다면 다음에 실제로 실행할 때 남은 상태를 먼저 처리합니다. 따라서 --status에 표시되는 ‘다음 단계’는 참고 정보일 뿐, 실제 실행 흐름과 다를 수 있습니다.
실제로는 대략 다음과 같이 동작합니다.
4K 144 Hz에서 HDR 지원: 두 번 연속 실행해 켜고 끄며, 주사율은 계속 144 Hz로 유지합니다.
144 Hz에서는 미지원, 약 120 Hz에서는 지원: 켤 때 약 120 Hz로 낮추고, 끌 때 원래 모드로 복구합니다.
약 120 Hz 모드가 없거나 120 Hz에서도 HDR 미지원: 오류를 알리고 원래 모드로 복구합니다.
다른 모델의 디스플레이로 교체: 현재 유일하게 연결된 외장 디스플레이를 선택하며, 이전 디스플레이의 복구 상태는 재사용하지 않습니다.
HDR을 켠 뒤 주사율을 직접 변경: HDR을 다시 끌 때 직접 변경한 설정을 덮어쓰지 않습니다.
외장 디스플레이가 없거나 여러 대이거나 미러링 모드: 문제만 알리고 어떤 디스플레이도 조작하지 않습니다.
정리
이 스크립트로 할 수 있는 일은 HDR과 디스플레이 모드를 전환하는 것뿐입니다. 포트나 케이블의 대역폭을 늘릴 수는 없습니다. 4K, 120/144/160 Hz, HDR, 색 심도를 동시에 사용할 수 있는지는 Mac과 케이블 또는 어댑터, 디스플레이가 보고하는 기능, OSD 설정, 펌웨어에 달려 있습니다. Apple도 일부 스케일링 해상도에서는 선택할 수 있는 주사율이나 HDR 모드가 달라질 수 있다고 안내합니다. Apple의 외장 디스플레이 안내
현재 스크립트는 일반 외장 디스플레이 한 대만 처리합니다. DisplayLink나 가상 디스플레이 같은 특수한 연결 방식은 아직 지원하지 않습니다. 이런 경우에는 먼저 --status를 실행해 디스플레이를 올바르게 찾았는지 확인한 뒤 전환 여부를 결정하세요.
또한 MonitorPanel.framework는 어디까지나 비공개 인터페이스입니다. macOS 주요 버전이 업데이트된 뒤에는 먼저 --status를 실행하고, 화면을 관찰할 수 있고 언제든 수동으로 설정을 되돌릴 수 있는 상황에서 한 번 시험해 보는 편이 좋습니다.--status가 정상 작동한다는 것은 읽기 인터페이스가 여전히 동작한다는 뜻일 뿐, 쓰기 동작도 업데이트 전과 완전히 같다는 보장은 아닙니다.
처음에는 HDR 스위치에 해당하는 인터페이스만 찾으면 거의 다 된 줄 알았습니다. 막상 구현해 보니 스위치 자체가 오히려 가장 쉬운 부분이었습니다. 올바른 디스플레이를 찾는 방법, 언제 120 Hz로 낮출지, 스케일링을 어떻게 유지할지, 중간에 실패했을 때 설정을 어떻게 복구할지가 훨씬 까다로웠습니다.
이런 부분만 잘 처리해 두면 일상적인 사용은 간단하고 안심하고 맡길 수 있습니다. 영상을 보기 전에 단축키를 한 번 누르고, 다 본 뒤 다시 누르면 됩니다. 전환할 수 있으면 전환하고, 확실하지 않으면 아무것도 바꾸지 않습니다.
Loading...
Loading...
Loading...
Loading...
Loading...
이 스크립트는 macOS 외장 디스플레이의 HDR을 원클릭으로 전환하는 도구로, HDR 영상을 볼 때 시스템 설정에서 수동으로 켜고 꺼야 하는 번거로움을 해결한다. 타사 도구, root 권한, UI 자동화에 의존하지 않고 CoreGraphics로 디스플레이 정보를 읽어 비공개 프레임워크인 MonitorPanel.framework를 호출해 HDR 상태를 변경한다. 먼저 현재 모드에서 HDR 켜기를 바로 시도하고, 지원되지 않으면 주사율이 약 120Hz를 초과하는 경우 해상도와 스케일링을 바꾸지 않은 채 약 120Hz로 낮춘다. 그래도 실패하면 원래 모드로 복원하고 오류를 보고한다. 스크립트는 실행할 때마다 연결된 유일한 외장 디스플레이를 동적으로 식별하며, 상태 파일과 실행 잠금으로 중단·동시 실행을 처리하고 원래 모드를 기록해 HDR을 끌 때 복원한다. Xcode 또는 Command Line Tools가 필요하며 첫 실행 시 내장된 Objective-C 헬퍼를 컴파일하고 결과를 캐시한다. 비공개 인터페이스는 시스템 업그레이드 후 더 이상 동작하지 않을 수 있으므로 실행 전에 검사를 수행한다.
요구 사항
miniLED 외장 모니터를 하나 쓰고 있어서 영상 파일을 받을 때는 보통 DV나 HDR 버전을 먼저 고릅니다. MacBook 내장 디스플레이는 평소에도 EDR을 활용해 하이라이트를 더 밝게 표시할 수 있지만, 제가 쓰는 서드파티 모니터에서 HDR 영상을 제대로 보려면 먼저 ‘디스플레이’ 설정에서 HDR을 켜야 합니다.
엄밀히 말해 EDR이 애플 디스플레이에만 있는 기능은 아니며, 호환되는 외장 디스플레이도 지원할 수 있습니다. 여기서는 제 실제 사용 방식만 이야기하겠습니다. 영상을 보기 전에 HDR을 켜고, 다 본 뒤에는 SDR로 다시 전환합니다.
직접 전환하는 방법 자체는 어렵지 않습니다. ‘시스템 설정’을 열고 ‘디스플레이’로 들어가 외장 디스플레이를 선택한 다음 ‘고명암 대비’를 켜거나 끄면 됩니다. 문제는 영상을 볼 때마다 이 과정을 거쳐야 하고, 다 보고 나면 다시 반복해야 한다는 점입니다. 보다가 다른 일을 하려면 또 오가며 전환해야 하죠. 몇 번 반복하다 보면 꽤 번거롭습니다.
예전에 몇 번 추천한 적 있는 Space Launcher는 단축키로 스크립트를 실행할 수 있습니다. 그러니 가장 간단한 방법은 명확합니다. .command.command 파일을 만들어 한 번 눌러 HDR을 켜고, 다시 눌러 끄는 것입니다.
또 한 가지 걸리는 점은 일하는 장소가 일정하지 않고, 연결하는 모니터도 늘 같지 않다는 것입니다. 집에서는 4K 160 Hz 모니터를 쓰다가 다른 곳에서는 4K 144 Hz 모니터를 쓸 수도 있습니다. 포트와 케이블, 디스플레이 모드에 따라 이런 고주사율과 HDR을 동시에 켤 수 없는 경우도 있습니다.
그래서 스크립트에 몇 가지 원칙을 정했습니다.
특히 마지막 조건 때문에 ‘시스템 설정’을 UI 자동화로 조작하는 방법은 사실상 제외했습니다.
셸 명령 하나로 해결할 수 없는 이유
Apple 공식 안내에서는 HDR 스위치가 ‘시스템 설정 → 디스플레이’에 있다고 설명하며, 외장 HDR은 Mac과 HDR10 디스플레이뿐 아니라 포트, 케이블, 변환기, 디스플레이 펌웨어에도 영향을 받는다고 안내합니다. Apple의 HDR 안내
공개된 CoreGraphics API로도 여러 정보를 읽을 수 있습니다. 어떤 디스플레이가 연결되어 있는지, 어느 것이 내장 디스플레이인지, 현재 해상도와 주사율이 얼마인지 등을 알 수 있죠. 예를 들어 CGGetOnlineDisplayList는 연결된 디스플레이를 나열하고, CGDisplayIsBuiltin는 내장 디스플레이인지 판별하며, CGDisplayMode는 현재 디스플레이 모드를 읽습니다.
하지만 Apple의 공개 API를 살펴봐도 ‘고명암 대비’ 스위치를 직접 바꿀 수 있는 인터페이스는 찾지 못했습니다. 즉, 디스플레이 정보는 공개적으로 읽을 수 있지만 HDR 스위치를 조작하는 공개 API는 따로 없습니다.
저도 osascript로 ‘시스템 설정’을 제어하는 방법을 생각해 봤습니다. 서드파티 도구는 설치하지 않아도 되지만 손쉬운 사용 및 자동화 권한이 필요하고, 실행하면 창이 열리면서 포커스를 빼앗습니다. 더 큰 문제는 시스템 설정의 화면 구성과 문구에 의존한다는 점입니다. macOS가 업데이트되면 작동하지 않을 수도 있습니다. 제가 원한 건 단축키를 누르면 조용히 전환되는 방식이라 이 방법은 쓰지 않았습니다.
결국 사용한 것은 macOS에 기본으로 포함되어 있지만 공개 문서는 없는 MonitorPanel.framework입니다. 현재 디스플레이 모드를 읽고 그 모드에서 HDR을 사용할 수 있는지 확인하며, HDR 상태도 바꿀 수 있습니다. 구현할 때는 주로 ToggleHDR.swift를 참고했습니다. 같은 프레임워크를 사용하고 있어 적어도 접근 방식이 가능하다는 점은 확인할 수 있었습니다.
물론 비공개 API의 가장 큰 문제는 Apple이 앞으로도 계속 작동한다고 보장하지 않는다는 것입니다. macOS가 업데이트되면 클래스명이나 메서드명, 호출 방식은 물론 같은 메서드의 실제 동작까지 달라질 수 있습니다. 그래서 스크립트는 실행할 때마다 먼저 점검하고, 인터페이스가 예상과 다르면 디스플레이 설정을 건드리지 않고 바로 중단합니다.
스크립트의 동작 방식
전체 흐름은 간단히 말해 이렇습니다. 현재 모드에서 HDR을 켤 수 있으면 바로 켜고, 안 되면 약 120 Hz를 시도합니다. 120 Hz에서도 안 되면 원래 상태로 복구합니다. 아래는 인수 없이 실행했을 때의 흐름이며, --on와 --off는 목표 상태를 각각 켜기와 끄기로 고정하는 옵션일 뿐입니다.
1. 올바른 디스플레이 찾기
스크립트는 ‘지난번에 조작한 디스플레이가 무엇인지’를 기억하지 않고, 실행할 때마다 새로 찾습니다.
최종적으로 외장 디스플레이가 한 대만 남아야 합니다. 외장 디스플레이가 없거나 두 대가 연결되어 있으면 스크립트는 찾은 디스플레이를 나열한 뒤 종료합니다. 이번에는 전환하지 않는 편이 다른 디스플레이에 HDR을 켜는 것보다 낫습니다.
대상을 고를 때는 의도적으로 UUID를 사용하지 않습니다. 대상은 ‘현재 유일하게 연결된 외장 디스플레이’로 정합니다. 선택한 뒤에야 UUID를 사용해 전환 과정에서 같은 디스플레이를 다시 찾고, 해당 디스플레이의 복구 기록과 연결합니다. 따라서 다른 장소로 옮겼을 때 이전 디스플레이의 기록을 새 디스플레이에 잘못 적용하지 않습니다.
2. 현재 모드에서 HDR을 켤 수 있으면 주사율은 그대로
스크립트는 먼저 현재 디스플레이 모드인 currentMode를 읽고 hasHDRModes를 확인합니다. 이 값은 간단히 말해 MonitorPanel가 ‘현재 모드에서 HDR을 사용할 수 있다’고 보고했는지를 나타냅니다.
현재 모드에서 HDR을 사용할 수 있으면 바로 켭니다. 설정 후에는 인터페이스의 반환값만 믿지 않고 디스플레이 상태를 다시 읽습니다. MonitorPanel로 해상도와 스케일링이 그대로인지 확인하고, CoreGraphics로 실제 주사율이 바뀌지 않았는지도 확인합니다. 연속 두 번 읽은 결과가 모두 일치해야 전환이 성공한 것으로 봅니다.
즉, 4K 160/144 Hz에서 원래 HDR을 지원한다면 안전을 위해 120 Hz로 낮추지 않습니다. 반대로 HDR을 켜는 과정에서 시스템이 디스플레이 모드를 바꿔 버리면 스크립트가 이를 감지하므로 성공으로 처리하지 않습니다.
3. HDR을 켤 수 없으면 120 Hz 시도
다음 두 경우에만 120 Hz 모드를 찾습니다. 현재 모드가 HDR을 지원하지 않는 것이 확실하고 주사율이 약 120 Hz를 초과하는 경우, 또는 VRR/ProMotion 같은 가변 주사율 모드를 사용 중인 경우입니다.
찾은 모드는 아래 조건을 모두 충족해야 합니다.
이 조건은 모두 충족해야 합니다. ‘3840 × 2160’만 비교해서는 충분하지 않습니다. 같은 물리 해상도에도 스케일링 단계가 여러 개 있을 수 있기 때문입니다. 주사율이 잠시 낮아지는 건 괜찮지만 데스크톱 스케일링이 갑자기 달라지는 건 원치 않습니다.
후보가 여러 개면 120 Hz와의 차이가 가장 작은 모드를 선택하고, 차이가 같으면 주사율이 더 높은 쪽을 우선합니다. 적절한 120 Hz 모드를 찾지 못하면 바로 실패 처리하며 100 Hz나 60 Hz까지 계속 시도하지 않습니다. HDR을 위해 주사율만 조금 낮추려는 것이지, 디스플레이 사용 경험을 통째로 바꾸고 싶은 건 아니니까요.
4. HDR을 끌 때 원래 주사율로 복구
HDR을 켤 때 실제로 160/144 Hz에서 약 120 Hz로 낮췄다면 스크립트는 전환 전후의 디스플레이 모드를 모두 기록합니다. 다시 연결할 때 바뀔 수 있는 모드 번호만 저장하는 게 아니라 해상도, 주사율, HiDPI, 스케일링, 픽셀 형식과 디스플레이의 UUID, vendor, model, serial 등의 정보도 함께 기록합니다.
HDR을 끌 때는 먼저 같은 디스플레이인지, 현재 모드가 이전에 전환한 약 120 Hz 모드인지 확인한 다음에야 원래 주사율로 복구합니다. 디스플레이를 다시 연결하면 원래 모드 번호가 바뀌었을 수 있습니다. 이때는 해상도와 주사율, 스케일링 등의 정보를 이용해 모드를 다시 찾습니다. 정확히 일치하는 모드가 하나일 때만 복구하며, 찾지 못하거나 여러 개가 일치하면 복구를 포기합니다.
HDR이 켜진 상태에서 주사율이나 스케일링을 직접 바꿨다면 스크립트는 이전 설정을 복구하지 않습니다. HDR만 끄고, 나중에 직접 선택한 모드는 그대로 둡니다.
첫 실행 때 컴파일이 필요한 이유
셸에서는 Objective-C 비공개 프레임워크를 직접 호출할 수 없습니다. 모든 내용을 파일 하나에 담기 위해 toggle-external-hdr.command 안에 Objective-C helper 소스 코드를 넣었습니다.
처음 실행할 때 바깥쪽 zsh 스크립트가 시스템에 포함된 xcrun clang를 호출해 helper를 컴파일하고 결과를 다음 위치에 캐시합니다.
캐시는 macOS 빌드 버전과 CPU 아키텍처, 내장 소스 코드의 해시를 기준으로 구분됩니다. 시스템이나 helper 소스가 바뀌면 다시 컴파일하고, 그대로면 이후에는 캐시를 사용하므로 매번 컴파일을 기다릴 필요가 없습니다.
스크립트는 캐시 디렉터리와 바이너리가 현재 사용자 소유인지, 권한이 올바른지, 의심스러운 심볼릭 링크나 하드 링크는 아닌지도 확인합니다. 검사를 통과하지 못하면 중단하며, 컴파일에 실패해도 이전 버전을 대신 실행하지 않습니다.
서드파티 디스플레이 소프트웨어를 설치할 필요는 없지만, Mac에 Apple Xcode 또는 Command Line Tools가 설치되어 있어야 합니다.
스크립트가 작업 도중 디스플레이 모드를 전환하면 화면이 몇 초간 검게 변할 수 있고, 디스플레이의 내부 식별 번호가 바뀔 수도 있습니다. 더 곤란한 경우는 주사율을 120 Hz로 낮춘 뒤 HDR을 켜기 전에 스크립트가 중단되는 상황입니다.
그래서 스크립트는 진행 상태를 기록하며, 상태는 다음 네 가지입니다.
단계
의미
`pending`
원래 모드를 저장했고 주사율 전환을 준비하는 중
`enabling`
대체 모드로 전환했고 HDR을 켤 준비가 된 상태
`active`
HDR이 켜졌고 복구 정보가 유효한 상태
`disabling`
HDR을 끄는 중이며 다음 단계에서 원래 모드로 복구할 상태
다음에 실행하면 스크립트는 먼저 이전 실행이 중간에 끝났는지 확인합니다. 현재 모드가 스크립트가 바꾼 모드라고 확실히 판단되면 복구를 이어갑니다. 확신할 수 없으면 중단하고, ‘비슷해 보이는’ 모드로 현재 설정을 덮어쓰지 않습니다. 상태가 active인데 그 뒤에 직접 모드를 바꿨다면, 스크립트는 오래된 복구 기록을 지우고 HDR을 계속 끕니다. 다음번에 만료된 주사율 설정을 다시 적용하지 않도록 하기 위해서입니다.
실행 잠금도 추가했습니다. 다른 프로세스가 전환 중이면 새로 실행한 스크립트는 기다리지 않고 바로 종료합니다. 단축키를 빠르게 두 번 누르거나 스크립트를 연달아 두 번 실행하거나, 터미널 두 개에서 동시에 실행해도 두 프로세스가 같은 디스플레이를 함께 바꾸는 일이 없습니다.
사용 방법
스크립트에는 이미 실행 권한이 설정되어 있습니다. 스크립트가 있는 디렉터리로 이동한 다음 현재 상태를 먼저 확인하는 것을 권장합니다.
평소에는 인수 없이 실행하면 됩니다. HDR을 켜거나 끄도록 명시할 수도 있습니다.
확장자가 .command이므로 Finder에서 더블 클릭해도 실행할 수 있습니다. 더 편리하게 쓰려면 Space Launcher에서 단축키를 지정하면 됩니다. 스크립트를 다운로드하거나 복사한 뒤 실행 권한이 사라졌다면 다시 부여할 수 있습니다.
--status는 스크립트가 찾은 디스플레이와 현재 해상도 및 주사율, HDR 지원 여부와 현재 켜져 있는지, 다음에 어떤 작업을 할지 보여 줍니다. HDR을 전환하거나 디스플레이 모드를 바꾸지는 않지만, 첫 실행 때 helper를 컴파일하면서 캐시를 기록할 수는 있습니다.
이전 실행이 중간에 중단됐다면 다음에 실제로 실행할 때 남은 상태를 먼저 처리합니다. 따라서 --status에 표시되는 ‘다음 단계’는 참고 정보일 뿐, 실제 실행 흐름과 다를 수 있습니다.
실제로는 대략 다음과 같이 동작합니다.
정리
이 스크립트로 할 수 있는 일은 HDR과 디스플레이 모드를 전환하는 것뿐입니다. 포트나 케이블의 대역폭을 늘릴 수는 없습니다. 4K, 120/144/160 Hz, HDR, 색 심도를 동시에 사용할 수 있는지는 Mac과 케이블 또는 어댑터, 디스플레이가 보고하는 기능, OSD 설정, 펌웨어에 달려 있습니다. Apple도 일부 스케일링 해상도에서는 선택할 수 있는 주사율이나 HDR 모드가 달라질 수 있다고 안내합니다. Apple의 외장 디스플레이 안내
현재 스크립트는 일반 외장 디스플레이 한 대만 처리합니다. DisplayLink나 가상 디스플레이 같은 특수한 연결 방식은 아직 지원하지 않습니다. 이런 경우에는 먼저 --status를 실행해 디스플레이를 올바르게 찾았는지 확인한 뒤 전환 여부를 결정하세요.
또한 MonitorPanel.framework는 어디까지나 비공개 인터페이스입니다. macOS 주요 버전이 업데이트된 뒤에는 먼저 --status를 실행하고, 화면을 관찰할 수 있고 언제든 수동으로 설정을 되돌릴 수 있는 상황에서 한 번 시험해 보는 편이 좋습니다.--status가 정상 작동한다는 것은 읽기 인터페이스가 여전히 동작한다는 뜻일 뿐, 쓰기 동작도 업데이트 전과 완전히 같다는 보장은 아닙니다.
처음에는 HDR 스위치에 해당하는 인터페이스만 찾으면 거의 다 된 줄 알았습니다. 막상 구현해 보니 스위치 자체가 오히려 가장 쉬운 부분이었습니다. 올바른 디스플레이를 찾는 방법, 언제 120 Hz로 낮출지, 스케일링을 어떻게 유지할지, 중간에 실패했을 때 설정을 어떻게 복구할지가 훨씬 까다로웠습니다.
이런 부분만 잘 처리해 두면 일상적인 사용은 간단하고 안심하고 맡길 수 있습니다. 영상을 보기 전에 단축키를 한 번 누르고, 다 본 뒤 다시 누르면 됩니다. 전환할 수 있으면 전환하고, 확실하지 않으면 아무것도 바꾸지 않습니다.