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

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

창 관리 API

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

추천 사용 사례

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

  • 멀티 윈도우 그래픽 편집기인 Gimp를 사용해 정확한 위치가 지정된 창에 다양한 편집 도구를 배치할 수 있습니다.
  • 가상 트레이딩 데스크는 전체 화면 모드로 볼 수 있는 여러 창에서 마켓 트렌드를 보여줄 수 있습니다.
  • 슬라이드쇼 앱은 내부 기본 화면에 발표자 노트를 표시하고 외부 프로젝터에 프레젠테이션을 표시할 수 있습니다.

Window Management API 사용 방법

문제

시간 테스트를 거친 창 제어 방식인 Window.open()는 안타깝게도 추가 화면을 인식하지 못합니다. 이 API의 일부 측면(예: windowFeatures DOMString 매개변수)은 약간 오래된 것처럼 보이지만 지난 몇 년 동안 충분히 도움을 주었습니다. 창의 위치를 지정하려면 좌표를 lefttop (또는 각각 screenXscreenY)로 전달하고 원하는 크기widthheight (또는 각각 innerWidthinnerHeight)로 전달하면 됩니다. 예를 들어 400×300 창을 왼쪽에서 50픽셀, 상단으로부터 50픽셀 떨어진 위치에 열려면 다음 코드를 사용하면 됩니다.

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는 사이드카를 통해 노트북에 연결되므로 필요할 때마다 iPad를 보조 화면으로 빠르게 전환할 수 있습니다.

의자 2개에 학교 벤치가 놓여 있습니다. 학교 벤치 위에는 노트북을 지지하는 신발 상자가 있고 그 주변에는 iPad 2대가 놓여 있습니다.
멀티스크린 설정

더 큰 화면을 활용하려면 위의 코드 샘플의 팝업을 두 번째 화면으로 표시하면 됩니다. 방법은 다음과 같습니다.

popup.moveTo(2500, 50);

두 번째 화면의 크기를 알 수 있는 방법이 없기 때문에 대략적인 추측입니다. window.screen의 정보는 내장 화면만 포함하고 iPad 화면은 다루지 않습니다. 내장 화면의 보고된 width1680픽셀이었으므로 2500 픽셀로 이동하면 창이 iPad로 이동할 수도 있습니다. 저는 MacBook의 오른쪽에 있다는 것을 알고 있기 때문입니다. 일반적인 경우에는 어떻게 해야 하나요? 그 결과, 추측하는 것보다 더 좋은 방법이 있습니다. 이것이 Window Management API입니다.

기능 감지

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

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

window-management 권한

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

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 객체가 있는 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'는 복수형입니다. 즉, 새 화면이나 기존 화면이 (사이드카의 경우 물리적으로 또는 가상으로) 연결되거나 연결 해제될 때마다 이벤트가 실행됩니다.

새 화면 세부정보를 비동기식으로 조회해야 하며 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() 메서드를 통해 요소가 전체 화면 모드로 표시되도록 요청할 수 있었습니다. 이 메서드는 FullscreenOptions를 전달할 수 있는 options 매개변수를 사용합니다. 지금까지 유일한 속성은 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 유혈 목욕을 목격한 패니키

아래에 삽입된 데모를 사용해 보거나 결함이 있는 소스 코드를 확인하세요.

보안 및 권한

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

사용자 제어

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

엔터프라이즈 제어

Chrome Enterprise 사용자는 원자적 정책 그룹 설정의 관련 섹션에 설명된 대로 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팀이 기능의 우선순위를 정하는 데 도움이 되며 다른 브라우저 공급업체에 이러한 기능을 지원하는 것이 얼마나 중요한지 보여줍니다.

유용한 링크

감사의 말

Window Management API 사양은 빅터 코스탄, 조슈아 벨, 마이크 와서만이 수정했습니다. 이 API는 마이크 와서만아드리엔 워커가 구현했습니다. 이 문서는 조 메들리, 프랑수아 보퍼트, 카이스 바스크가 검토했습니다. 사진을 제공해 주신 로라 토렌트 푸이그님께 감사드립니다.