Window Management API로 여러 디스플레이 관리

연결된 디스플레이에 관한 정보를 가져오고 해당 디스플레이를 기준으로 창을 배치합니다.

창 관리 API

Window Management API를 사용하면 머신에 연결된 디스플레이를 열거하고 특정 화면에 창을 배치할 수 있습니다.

추천 사용 사례

이 API를 사용할 수 있는 사이트의 예는 다음과 같습니다.

  • Gimp를 사용하여 멀티 윈도우 그래픽 편집기를 사용하면 다양한 편집 도구를 정확하게 배치된 창에 배치할 수 있습니다.
  • 가상 거래소는 여러 창에 시장 동향을 표시할 수 있으며, 이러한 창은 모두 전체 화면 모드로 볼 수 있습니다.
  • 슬라이드쇼 앱은 내부 기본 화면에 발표자 노트를 표시하고 외부 프로젝터에 프레젠테이션을 표시할 수 있습니다.

Window Management API 사용 방법

문제

창을 제어하는 오랜 경험을 바탕으로 한 접근 방식인 Window.open()은 안타깝게도 추가 화면을 인식하지 못합니다. 이 API의 일부 측면(예: windowFeatures DOMString 매개변수)은 약간 오래된 것처럼 보이지만, 그럼에도 불구하고 오랜 세월 동안 유용하게 사용되었습니다. 창의 위치를 지정하려면 좌표를 lefttop (또는 각각 screenXscreenY)로 전달하고 원하는 크기widthheight (또는 각각 innerWidthinnerHeight)로 전달하면 됩니다. 예를 들어 왼쪽에서 50픽셀, 위쪽에서 50픽셀 떨어진 곳에 400×300 창을 열려면 다음 코드를 사용할 수 있습니다.

const popup = window.open(
  'https://example.com/',
  'My Popup',
  'left=50,top=50,width=400,height=300',
);

Screen 객체를 반환하는 window.screen 속성을 확인하여 현재 화면에 관한 정보를 가져올 수 있습니다. MacBook Pro 13인치의 출력은 다음과 같습니다.

window.screen;
/* Output from my MacBook Pro 13″:
  availHeight: 969
  availLeft: 0
  availTop: 25
  availWidth: 1680
  colorDepth: 30
  height: 1050
  isExtended: true
  onchange: null
  orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
  pixelDepth: 30
  width: 1680
*/

기술 분야에서 일하는 대부분의 사람들과 마찬가지로 저도 새로운 업무 환경에 적응하고 개인 홈 오피스를 설정해야 했습니다. 제 설정은 아래 사진과 같습니다. 관심이 있다면 설정에 관한 전체 세부정보를 읽어 보세요. MacBook 옆에 있는 iPad는 Sidecar를 통해 노트북에 연결되어 있으므로 필요할 때마다 iPad를 보조 화면으로 빠르게 전환할 수 있습니다.

의자 두 개가 놓인 학교 벤치 학교 벤치 위에 노트북을 지지하는 신발 상자와 그 주위에 iPad 2개가 있습니다.
다중 화면 설정

더 큰 화면을 활용하려면 위의 코드 샘플에 있는 팝업을 두 번째 화면에 배치하면 됩니다. 방법은 다음과 같습니다.

popup.moveTo(2500, 50);

두 번째 화면의 크기를 알 수 있는 방법이 없으므로 대략적인 추측입니다. window.screen의 정보는 내장 화면에만 적용되고 iPad 화면에는 적용되지 않습니다. 내장 화면의 보고된 width1680픽셀이므로 2500픽셀로 이동하면 창이 MacBook 오른쪽에 있는 것으로 알고 있으므로 iPad로 창을 이동하는 데 도움이 될 수 있습니다. 일반적으로 이를 실행하려면 어떻게 해야 하나요? 추측하는 것보다 더 나은 방법이 있습니다. 이렇게 하면 Window Management API가 됩니다.

특성 감지

Window Management API가 지원되는지 확인하려면 다음을 사용하세요.

if ('getScreenDetails' in window) {
  // The Window Management API is supported.
}

window-management 권한

Window Management API를 사용하려면 먼저 사용자에게 권한을 요청해야 합니다. window-management 권한은 다음과 같이 Permissions API로 쿼리할 수 있습니다.

let granted = false;
try {
  const { state } = await navigator.permissions.query({ name: 'window-management' });
  granted = state === 'granted';
} catch {
  // Nothing.
}

이전 권한 이름과 새 권한 이름이 있는 브라우저가 사용되는 동안에는 아래 예와 같이 권한을 요청할 때 방어 코드를 사용해야 합니다.

async function getWindowManagementPermissionState() {
  let state;
  // The new permission name.
  try {
    ({ state } = await navigator.permissions.query({
      name: "window-management",
    }));
  } catch (err) {
    return `${err.name}: ${err.message}`;
  }
  return state;
}

document.querySelector("button").addEventListener("click", async () => {
  const state = await getWindowManagementPermissionState();
  document.querySelector("pre").textContent = state;
});

브라우저는 새 API의 메서드를 처음 사용하려고 할 때 권한 메시지를 동적으로 표시하도록 선택할 있습니다. 자세한 내용을 알아보려면 계속 읽어보세요.

window.screen.isExtended 속성

기기에 두 개 이상의 화면이 연결되어 있는지 확인하려면 window.screen.isExtended 속성에 액세스합니다. true 또는 false를 반환합니다. 제 설정의 경우 true을 반환합니다.

window.screen.isExtended;
// Returns `true` or `false`.

getScreenDetails() 메서드

이제 현재 설정이 멀티스크린이라는 것을 알았으므로 Window.getScreenDetails()를 사용하여 두 번째 화면에 관한 자세한 정보를 얻을 수 있습니다. 이 함수를 호출하면 사이트에서 창을 열어 화면에 배치할 수 있는지 묻는 권한 메시지가 표시됩니다. 이 함수는 ScreenDetailed 객체로 확인되는 프로미스를 반환합니다. iPad가 연결된 MacBook Pro 13에서는 ScreenDetailed 객체 2개가 있는 screens 필드가 포함됩니다.

await window.getScreenDetails();
/* Output from my MacBook Pro 13″ with the iPad attached:
{
  currentScreen: ScreenDetailed {left: 0, top: 0, isPrimary: true, isInternal: true, devicePixelRatio: 2, …}
  oncurrentscreenchange: null
  onscreenschange: null
  screens: [{
    // The MacBook Pro
    availHeight: 969
    availLeft: 0
    availTop: 25
    availWidth: 1680
    colorDepth: 30
    devicePixelRatio: 2
    height: 1050
    isExtended: true
    isInternal: true
    isPrimary: true
    label: "Built-in Retina Display"
    left: 0
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 30
    top: 0
    width: 1680
  },
  {
    // The iPad
    availHeight: 999
    availLeft: 1680
    availTop: 25
    availWidth: 1366
    colorDepth: 24
    devicePixelRatio: 2
    height: 1024
    isExtended: true
    isInternal: false
    isPrimary: false
    label: "Sidecar Display (AirPlay)"
    left: 1680
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 24
    top: 0
    width: 1366
  }]
}
*/

연결된 화면에 관한 정보는 screens 배열에서 확인할 수 있습니다. iPad의 left 값이 1680에서 시작하는 것을 볼 수 있습니다. 이는 내장 디스플레이의 width와 정확히 일치합니다. 이렇게 하면 화면이 논리적으로 어떻게 정렬되는지 (서로 나란히, 서로 위에 등) 정확하게 결정할 수 있습니다. 이제 각 화면에는 isInternal인지, isPrimary인지를 보여주는 데이터도 있습니다. 내장 화면이 반드시 기본 화면이 아닐 수 있습니다.

currentScreen 필드는 현재 window.screen에 상응하는 라이브 객체입니다. 객체는 교차 화면 창 배치 또는 기기 변경 시 업데이트됩니다.

screenschange 이벤트

이제 화면 설정이 변경될 때 이를 감지하는 방법만 있으면 됩니다. 새 이벤트인 screenschange가 바로 이 역할을 합니다. 화면 별자리가 수정될 때마다 실행됩니다. (이벤트 이름에서 'screens'는 복수형입니다.) 즉, 새 화면 또는 기존 화면이 (Sidecar의 경우 실제 또는 가상으로) 연결되거나 연결 해제될 때마다 이벤트가 발생합니다.

새 화면 세부정보를 비동기식으로 조회해야 합니다. screenschange 이벤트 자체는 이 데이터를 제공하지 않습니다. 화면 세부정보를 조회하려면 캐시된 Screens 인터페이스에서 실시간 객체를 사용하세요.

const screenDetails = await window.getScreenDetails();
let cachedScreensLength = screenDetails.screens.length;
screenDetails.addEventListener('screenschange', (event) => {
  if (screenDetails.screens.length !== cachedScreensLength) {
    console.log(
      `The screen count changed from ${cachedScreensLength} to ${screenDetails.screens.length}`,
    );
    cachedScreensLength = screenDetails.screens.length;
  }
});

currentscreenchange 이벤트

현재 화면의 변경사항 (즉, 실시간 객체 currentScreen의 값)에만 관심이 있는 경우 currentscreenchange 이벤트를 리슨할 수 있습니다.

const screenDetails = await window.getScreenDetails();
screenDetails.addEventListener('currentscreenchange', async (event) => {
  const details = screenDetails.currentScreen;
  console.log('The current screen has changed.', event, details);
});

change 이벤트

마지막으로, 특정 화면의 변경사항에만 관심이 있는 경우 해당 화면의 change 이벤트를 리슨할 수 있습니다.

const firstScreen = (await window.getScreenDetails())[0];
firstScreen.addEventListener('change', async (event) => {
  console.log('The first screen has changed.', event, firstScreen);
});

새로운 전체 화면 옵션

지금까지는 적절한 이름의 requestFullScreen() 메서드를 통해 요소가 전체 화면 모드로 표시되도록 요청할 수 있었습니다. 이 메서드는 options 매개변수를 사용하며 여기서 FullscreenOptions를 전달할 수 있습니다. 지금까지 유일한 속성은 navigationUI였습니다. Window Management API에는 전체 화면 뷰를 시작할 화면을 결정할 수 있는 새로운 screen 속성이 추가되었습니다. 예를 들어 기본 화면을 전체 화면으로 설정하려면 다음을 실행합니다.

try {
  const primaryScreen = (await getScreenDetails()).screens.filter((screen) => screen.isPrimary)[0];
  await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
  console.error(err.name, err.message);
}

폴리필

Window Management API를 폴리필할 수는 없지만, 새 API에 대해서만 코딩할 수 있도록 도형을 시밍할 수 있습니다.

if (!('getScreenDetails' in window)) {
  // Returning a one-element array with the current screen,
  // noting that there might be more.
  window.getScreenDetails = async () => [window.screen];
  // Set to `false`, noting that this might be a lie.
  window.screen.isExtended = false;
}

API의 다른 측면, 즉 다양한 화면 변경 이벤트와 FullscreenOptionsscreen 속성은 지원되지 않는 브라우저에서 각각 실행되지 않거나 자동으로 무시됩니다.

데모

저와 마찬가지로 다양한 암호화폐의 발전을 주시하고 계실 겁니다. (사실 저는 이 행성을 좋아해서 그렇게 하지 않지만 이 글의 관점에서는 제가 그랬다고만 가정합니다.) 제가 보유한 암호화폐를 추적하기 위해 편안한 싱글 화면 설정이 있는 침대 등 모든 생활 환경에서 시장을 볼 수 있는 웹 앱을 개발했습니다.

침대 끝에 놓인 대형 TV 화면에 작가의 다리가 부분적으로 보입니다. 화면에 가짜 암호화폐 거래소가 있습니다.
시장을 보면서 휴식을 취합니다.

암호화폐 관련 시장은 언제든지 혼란스러워질 수 있습니다. 이런 일이 발생하면 멀티스크린이 설정된 책상으로 빠르게 이동할 수 있습니다. 통화 창을 클릭하면 반대쪽 화면의 전체 화면 뷰에서 전체 세부정보를 빠르게 확인할 수 있습니다. 다음은 지난 YCY 학살 당시 찍은 제 최근 사진입니다. 완전히 예상치 못해 손으로 얼굴을 가리고 있었습니다.

가짜 암호화폐 거래소를 바라보며 당황한 얼굴을 감싸 쥔 저자의 모습
패니키, YCY 유혈 목격.

아래에 삽입된 데모를 사용해 보거나 glitch에서 소스 코드를 확인할 수 있습니다.

보안 및 권한

Chrome팀은 사용자 제어, 투명성, 인체공학을 비롯하여 강력한 웹 플랫폼 기능에 대한 액세스 제어에 정의된 핵심 원칙을 사용하여 Window Management API를 설계하고 구현했습니다. Window Management API는 기기에 연결된 화면에 관한 새로운 정보를 노출하여 사용자의 지문 식별 노출 영역을 늘립니다. 특히 기기에 여러 화면이 일관되게 연결된 사용자의 경우 더욱 그렇습니다. 이러한 개인 정보 보호 문제를 완화하기 위해 노출되는 화면 속성은 일반적인 게재위치 사용 사례에 필요한 최소한으로 제한됩니다. 사이트에서 멀티스크린 정보를 가져오고 다른 화면에 창을 배치하려면 사용자 권한이 필요합니다. Chromium은 상세한 화면 라벨을 반환하지만, 브라우저는 덜 구체적이거나 빈 라벨을 자유롭게 반환할 수 있습니다.

사용자 제어

사용자는 설정의 노출을 완전히 제어할 수 있습니다. 권한 메시지를 수락하거나 거부할 수 있으며 브라우저의 사이트 정보 기능을 통해 이전에 부여된 권한을 취소할 수 있습니다.

엔터프라이즈 제어

Chrome Enterprise 사용자는 Atomic Policy Groups 설정의 관련 섹션에 설명된 대로 Window Management API의 여러 측면을 제어할 수 있습니다.

투명성

Window Management API 사용 권한이 부여되었는지 여부가 브라우저의 사이트 정보에 노출되며 Permissions API를 통해서도 쿼리할 수 있습니다.

권한 유지

브라우저는 권한 부여를 유지합니다. 이 권한은 브라우저의 사이트 정보를 통해 취소할 수 있습니다.

의견

Chrome팀에서 Window Management API 사용 경험에 관한 의견을 듣고자 합니다.

API 설계에 대해 알려주세요.

API에서 예상대로 작동하지 않는 부분이 있나요? 아니면 아이디어를 구현하는 데 필요한 메서드나 속성이 누락되었나요? 보안 모델에 관해 질문이나 의견이 있으신가요?

  • 해당 GitHub 저장소에서 사양 문제를 제출하거나 기존 문제에 의견을 추가합니다.

구현 문제 신고

Chrome 구현에서 버그를 발견했나요? 아니면 구현이 사양과 다른가요?

  • new.crbug.com에서 버그를 신고합니다. 최대한 많은 세부정보와 재현을 위한 간단한 안내를 포함하고 구성요소 상자에 Blink>Screen>MultiScreen를 입력합니다. Glitch는 빠르고 쉬운 재현을 공유할 때 유용합니다.

API 지원 표시

Window Management API를 사용할 계획인가요? 공개적으로 지원하면 Chrome팀에서 기능의 우선순위를 지정하는 데 도움이 되며 다른 브라우저 공급업체에 기능을 지원하는 것이 얼마나 중요한지 보여줍니다.

  • WICG Discourse 대화목록에서 사용 계획을 공유하세요.
  • #WindowManagement 해시태그를 사용하여 @ChromiumDev에 트윗을 보내고 사용 중인 위치와 사용 방법을 알려주세요.
  • 다른 브라우저 공급업체에 API를 구현해 달라고 요청합니다.

유용한 링크

감사의 말씀

Window Management API 사양은 Victor Costan, Joshua Bell, Mike Wasserman이 수정했습니다. 이 API는 마이크 워즈먼애드리엔 워커가 구현했습니다. 이 도움말은 조 미들리, 프랑소와 보포르, 케이스 바스케스가 검토했습니다. 로라 토렌트 푸이그님, 사진을 제공해 주셔서 감사합니다.