앱들이 스크린샷으로부터 창을 보호하는 방법: Windows, macOS, Linux 상세 분석
넷플릭스나 스포티파이를 스크린샷으로 찍으려고 하면 비디오 대신 검은 사각형이 나타나죠. 이건 버그가 아니라 시스템 API를 통한 의도적인 보호 조치입니다. 개발자들은 Windows의 SetWindowDisplayAffinity 같은 플래그나 macOS의 sharingType을 사용해 해당 창을 화면 캡처 스트림에서 제외합니다. 하지만 macOS 15 Sequoia에서 애플이 호환성을 깨뜨렸고, Linux에서는 디스플레이 서버에 따라 전부 달라집니다. 각 플랫폼에서 보호가 어떻게 작동하는지, 어디서 실패하는지, 우회 방법은 무엇인지 자세히 살펴보죠.
Windows의 화면 캡처 메커니즘: 세 가지 주요 API
Zoom 같은 앱이 화면 공유 액세스를 요청하면 운영 체제의 프레임 스트림을 구독합니다. Windows에는 이 스트림을 얻는 세 가지 주요 방법이 있으며, 각기 특이점이 있습니다:
- GDI BitBlt from desktop DC—가장 오래된 방법으로 Windows 2000부터 작동합니다.
GetDC(NULL)에서BitBlt호출을 사용해 DWM(Desktop Window Manager) 표면의 픽셀을 임의의 HDC로 복사합니다. 느리고 하드웨어 가속 없지만 범용적입니다. 레거시 앱과 모니터링 도구에서 사용됩니다. - Desktop Duplication API (DXGI)—Windows 8부터 도입.
IDXGIOutputDuplication::AcquireNextFrame을 통해 완전히 합성된 데스크톱 프레임을 GPU 텍스처로 반환합니다. 빠르고 하드웨어 가속되지만 전체 모니터만 캡처합니다. 클래식 버전의 Zoom, TeamViewer, AnyDesk, Teams에서 사용됩니다. - Windows.Graphics.Capture (WGC)—현대 표준으로 Windows 10 1803부터 도입. 전체 모니터나 특정 HWND 창을 캡처할 수 있습니다. 현재 OBS, Microsoft Teams, Chromium, Zoom, 내장 Snipping Tool에서 사용됩니다.
이 세 방법 모두 최종적으로 DWM에서 데이터를 읽습니다. 여기서 최종 화면 이미지가 합성되는 유일한 장소죠. 이곳에서 보호 메커니즘이 작동합니다.
SetWindowDisplayAffinity: 모드와 동작 방식
DWM은 각 창이 SetWindowDisplayAffinity 함수를 통해 display affinity 속성을 설정할 수 있게 합니다. 이 속성은 창이 캡처 스트림에 나타날지 결정합니다. 세 가지 값이 있습니다:
WDA_NONE(0x00)—모든 캡처에서 창이 보입니다 (기본값).WDA_MONITOR(0x01)—모니터에는 표시되지만 캡처에서는 검은 사각형으로 대체됩니다. Windows Vista부터 작동. Netflix가 콘텐츠 보호에 사용합니다.WDA_EXCLUDEFROMCAPTURE(0x11)—캡처 스트림에서 완전히 제외되며, 존재하지 않는 것처럼 됩니다. Windows 10 May 2020 Update (빌드 19041)부터 도입. 하위 창 콘텐츠가 그대로 보이는 더 깔끔한 해결책입니다.
C++ 예제:
#include <windows.h>
#include <cstdio>
int main() {
HWND hwnd = GetConsoleWindow();
if (!SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE)) {
printf("SetWindowDisplayAffinity failed: %lu\n", GetLastError());
return 1;
}
printf("Okno teper nevidimo for zakhvata. Poprobuy sdelat skrinshot.\n");
Sleep(60000);
return 0;
}
C#에서는 P/Invoke을 통해, Electron에서는 setContentProtection(true)로, Tauri에서는 with_content_protection(true)로 비슷하게 작동합니다.
내부적으로 DWM은 합성 파이프라인을 분리합니다: WDA_EXCLUDEFROMCAPTURE가 설정된 창은 물리적 모니터 파이프라인에만 들어가고 캡처 클라이언트에는 들어가지 않습니다. 픽셀 읽기 수준에서 우회할 수 없습니다.
Windows의 호환성과 제한 사항
Windows 11 23H2에서 테스트한 결과 WDA_EXCLUDEFROMCAPTURE는 모든 주요 캡처 방법에 대해 올바르게 작동합니다:
- GDI BitBlt—창이 없습니다.
- Desktop Duplication (DXGI)—창이 없습니다 (Windows 11 24H2에서
AcquireNextFrame이 숨겨진 창 업데이트에 깨어나는 특이점이 있지만 콘텐츠는 전달되지 않습니다). - Windows.Graphics.Capture (WGC)—창이 없습니다.
유일한 예외는 PrintWindow입니다. 이 방법은 DWM을 우회하고 지정된 DC로 직접 렌더링을 요청하며 플래그를 무시합니다. 하지만 대부분의 화면 공유 앱(Zoom, Teams, Discord, OBS)은 PrintWindow를 사용하지 않습니다.
주요 제한: WDA_EXCLUDEFROMCAPTURE는 Windows 10 20H1(May 2020 Update)부터 지원됩니다. 이전 버전( LTSC 포함)에서는 ERROR_INVALID_PARAMETER를 반환합니다. 이런 경우 WDA_MONITOR로 대체하면 캡처에서 검은 사각형이 보입니다.
또한 SetWindowCompositionAttribute를 통한 비공식 속성 WCA_EXCLUDED_FROM_DDA가 있어서 Desktop Duplication API에서만 창을 제외합니다. 실제로는 WDA_EXCLUDEFROMCAPTURE가 더 포괄적이므로 불필요합니다.
macOS 14까지: sharingType의 안정적 메커니즘
macOS 14까지는 NSWindow 속성 sharingType을 사용했습니다:
window.sharingType = .none
사용 가능한 값:
.readWrite—다른 프로세스에서 읽고 수정 가능 (드물게 사용)..readOnly—캡처 가능 (기본값)..none—캡처에서 제외.
이 플래그는 Core Graphics를 통해 합성을 처리하는 WindowServer에서 작동했습니다. macOS 14 Sonoma 포함까지 sharingType = .none은 레거시 API(CGWindowListCreateImage)와 새로운 ScreenCaptureKit(macOS 12.3 도입) 모두에서 캡처를 효과적으로 차단했습니다.
Electron과 Tauri는 setContentProtection(true) 메서드에서 이 메커니즘을 내부적으로 사용했습니다.
macOS 15 Sequoia: ScreenCaptureKit의 퇴행
macOS 15 Sequoia에서 애플은 WindowServer 아키텍처를 변경했습니다: 이제 모든 보이는 창을 단일 프레임버퍼로 합성하고 ScreenCaptureKit이 이를 직접 캡처합니다. 결과적으로 sharingType = .none은 더 이상 SCStream에 영향을 주지 않습니다.
애플 공식 담당자가 Developer Forums에서 확인했습니다: “현재 화면 캡처를 방지하는 공용 API는 없습니다”.
주요 뉘앙스: 레거시 API(CGWindowListCreateImage)는 여전히 sharingType = .none을 존중합니다. 이로 인해 앱 간 동작이 분리됩니다:
- Zoom(현재), Teams, QuickTime, 시스템 스크린샷(Cmd+Shift+3/4/5), 최신 OBS 버전—ScreenCaptureKit 사용 → 창이 보입니다.
- Google Chrome(
getDisplayMedia), Chrome의 Google Meet, 구 OBS 버전—CoreGraphics 사용 → 창이 숨겨집니다.
이건 화면 캡처 보호가 필요한 제품에 대한 근본적인 퇴행입니다. 비공용 API 없이 해결책이 없고, 사용하면 App Store 리뷰 거부와 OS 업데이트 시 고장 보장이 됩니다. 대안으로는 macOS 14 이하만 지원 제한하거나, 행동적 방법: 화면 공유 시작 시 창 자동 최소화, 시스템 신호로 SCStream 활동 감지 등입니다.
Linux: X11 vs Wayland—완전히 다른 세계
Linux에서는 디스플레이 서버에 따라 상황이 극명하게 다릅니다.
X11—아키텍처상 창 보호를 캡처로부터 지원하지 않습니다. 디스플레이 액세스 권한이 있는 클라이언트는 XGetImage나 XCompositeNameWindowPixmap으로 임의 창의 픽셀을 가져올 수 있습니다. 합성 확장(Picom, KWin, Mutter)이 부분 보호를 추가할 수 있지만 WM 간 해결책은 없습니다.
Wayland—격리 원칙에 기반: 클라이언트는 타인 표면을 읽을 수 없습니다. 화면 캡처는 xdg-desktop-portal + PipeWire를 통해서만 가능하며 명시적 사용자 동의가 필요합니다. 창 보호는 프로토콜 수준에서 처리—접근 권한이 없으면 캡처에 나타나지 않습니다. 이로 인해 Wayland는 X11보다 훨씬 안전하지만 앱이 프로토콜을 올바르게 구현해야 합니다.
주요 요약
SetWindowDisplayAffinity의WDA_EXCLUDEFROMCAPTURE—Windows에서 창을 캡처에서 완전히 제외하는 사실상 표준. Windows 10 May 2020 Update부터 작동.- macOS 15 Sequoia에서
sharingType = .none은 ScreenCaptureKit에 더 이상 작동하지 않음—애플 아키텍처 변경으로 공용 우회 방법 없음. - Linux에서 X11은 프로토콜 설계상 창 보호 불가능; Wayland는 프로토콜 수준에서 처리.
- Windows의
PrintWindow는 보호를 우회하지만 인기 화면 공유 앱에서 사용되지 않음. - Electron과 Tauri는 이러한 API를 내부적으로 활용—플랫폼과 OS 버전에 따라 동작이 달라짐.
— Editorial Team
아직 댓글이 없습니다.