Zarządzanie kilkoma wyświetlaczami za pomocą interfejsu Window Management API

uzyskiwać informacje o podłączonych wyświetlaczach i ustawiać okna względem nich;

Opublikowano: 14 września 2020 r.

Window Management API

Interfejs Window Management API umożliwia wyliczanie wyświetlaczy podłączonych do komputera i umieszczanie okien na określonych ekranach.

Sugerowane przypadki użycia

Przykłady witryn, które mogą korzystać z tego interfejsu API:

  • Edytory graficzne z wieloma oknami, takie jak Gimp, mogą umieszczać różne narzędzia do edycji w dokładnie określonych oknach.
  • Wirtualne platformy handlowe mogą wyświetlać trendy rynkowe w wielu oknach, z których każde można wyświetlić w trybie pełnoekranowym.
  • Aplikacje do prezentacji mogą wyświetlać notatki prelegenta na wewnętrznym ekranie głównym, a prezentację na zewnętrznym projektorze.

Jak korzystać z interfejsu Window Management API

Sprawdzony sposób sterowania oknami,Window.open() niestety nie uwzględnia dodatkowych ekranów. Chociaż niektóre aspekty tego interfejsu API wydają się nieco przestarzałe, np. parametr windowFeatures DOMString, to jednak przez lata dobrze nam służył. Aby określić położenie okna, możesz przekazać współrzędne jako lefttop (lub odpowiednio screenXscreenY), a rozmiar jako widthheight (lub odpowiednio innerWidthinnerHeight). Jeśli na przykład chcesz otworzyć okno o wymiarach 400 × 300 pikseli w odległości 50 pikseli od lewej krawędzi i 50 pikseli od górnej krawędzi, możesz użyć tego kodu:

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

Informacje o bieżącym ekranie możesz uzyskać, sprawdzając właściwość window.screen, która zwraca obiekt Screen. Oto dane wyjściowe na moim MacBooku 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
*/

Jak większość osób pracujących w branży technologicznej musiałem dostosować się do realiów pracy w 2020 roku i skonfigurować osobiste biuro domowe. Mój wygląda tak jak na zdjęciu (jeśli chcesz, możesz przeczytać szczegółowe informacje o mojej konfiguracji). iPad obok MacBooka jest połączony z laptopem za pomocą funkcji Sidecar, więc w razie potrzeby mogę szybko przekształcić iPada w drugi ekran.

Ławka szkolna na 2 krzesłach. Na ławce szkolnej stoją pudełka po butach, na których opiera się laptop. Wokół niego leżą 2 iPady.
Konfiguracja wielu ekranów.

Jeśli chcę wykorzystać większy ekran, mogę przenieść wyskakujące okienko z przykładowego kodu na drugi ekran. Robię to tak:

popup.moveTo(2500, 50);

To tylko przybliżone oszacowanie, ponieważ nie ma możliwości poznania wymiarów drugiego ekranu. Informacje z window.screen obejmują tylko wbudowany ekran, a nie ekran iPada. Zgłoszona width wartość wbudowanego ekranu wynosiła 1680 pikseli, więc przejście na 2500 pikseli może przesunąć okno na iPada, ponieważ wiem, że znajduje się on po prawej stronie mojego MacBooka. Jak mogę to zrobić w przypadku ogólnym? Okazuje się, że istnieje lepszy sposób niż zgadywanie. Jest to interfejs Window Management API.

Wykrywanie cech

Aby sprawdzić, czy interfejs Window Management API jest obsługiwany, użyj tego kodu:

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

Uprawnienie window-management

Zanim będę mógł używać interfejsu Window Management API, muszę poprosić użytkownika o zgodę na to. Uprawnienie window-management można sprawdzić za pomocą interfejsu Permissions API w ten sposób:

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

Dopóki przeglądarki z nową i starą nazwą uprawnień są w użyciu, podczas wysyłania prośby o uprawnienia używaj kodu defensywnego, jak w przykładzie.

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;
});

Przeglądarka może wyświetlać prośbę o przyznanie uprawnień dynamicznie przy pierwszej próbie użycia dowolnej metody nowego interfejsu API. Czytaj dalej, aby dowiedzieć się więcej.

Usługa window.screen.isExtended

Aby sprawdzić, czy do mojego urządzenia jest podłączony więcej niż 1 ekran, otwieram właściwość window.screen.isExtended. Zwraca wartość true lub false. W moim przypadku zwraca wartość true.

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

Metoda getScreenDetails()

Teraz, gdy wiem, że bieżąca konfiguracja obejmuje wiele ekranów, mogę uzyskać więcej informacji o drugim ekranie za pomocą Window.getScreenDetails(). Wywołanie tej funkcji spowoduje wyświetlenie prośby o uprawnienia, w której użytkownik zostanie zapytany, czy witryna może otwierać i umieszczać okna na jego ekranie. Funkcja zwraca obietnicę, która jest realizowana za pomocą obiektu ScreenDetailed. Na MacBooku Pro 13 z podłączonym iPadem obejmuje to pole screens z 2 obiektami ScreenDetailed:

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
  }]
}
*/

Informacje o połączonych ekranach są dostępne w tablicy screens. Zwróć uwagę, że wartość left na iPadzie zaczyna się od 1680, czyli dokładnie od width wbudowanego wyświetlacza. Dzięki temu mogę dokładnie określić, jak ekrany są rozmieszczone logicznie (obok siebie, jeden nad drugim itp.). Dostępne są też dane dotyczące każdego ekranu, które pokazują, czy jest on isInternal, czy isPrimary. Pamiętaj, że wbudowany ekran nie musi być ekranem głównym.

Pole currentScreen to aktywny obiekt odpowiadający bieżącemu window.screen. Obiekt jest aktualizowany w przypadku umieszczeń w oknach na różnych ekranach lub zmian urządzeń.

Zdarzenie screenschange

Brakuje tylko sposobu wykrywania zmian w konfiguracji ekranu. Nowe zdarzenie, screenschange, robi dokładnie to: uruchamia się za każdym razem, gdy zmienia się konfiguracja ekranu. (Zwróć uwagę, że w nazwie zdarzenia „screens” jest w liczbie mnogiej). Oznacza to, że zdarzenie jest wywoływane za każdym razem, gdy nowy ekran lub istniejący ekran jest podłączany lub odłączany (fizycznie lub wirtualnie w przypadku Sidecar).

Musisz asynchronicznie wyszukać szczegóły nowego ekranu, ponieważ samo zdarzenie screenschange nie zawiera tych danych. Aby sprawdzić szczegóły ekranu, użyj obiektu na żywo z interfejsu Screensw pamięci podręcznej.

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;
  }
});

Zdarzenie currentscreenchange

Jeśli interesują mnie tylko zmiany na bieżącym ekranie (czyli wartość obiektu na żywo currentScreen), mogę nasłuchiwać zdarzenia currentscreenchange.

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

Zdarzenie change

Jeśli interesują mnie tylko zmiany na konkretnym ekranie, mogę nasłuchiwać zdarzenia change tego ekranu.

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

Nowe opcje pełnego ekranu

Do tej pory można było poprosić o wyświetlenie elementów w trybie pełnoekranowym za pomocą metody o odpowiedniej nazwie requestFullScreen(). Metoda przyjmuje parametr options, w którym możesz przekazać wartość FullscreenOptions. Do tej pory jedyną właściwością tej usługi była navigationUI. Interfejs Window Management API dodaje nową właściwość screen, która pozwala określić, na którym ekranie ma się rozpocząć widok pełnoekranowy. Jeśli na przykład chcesz wyświetlić ekran główny na pełnym ekranie:

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);
}

Wypełnienie

Nie można zastosować polyfillu do interfejsu Window Management API, ale można zastosować shim do jego kształtu, aby można było pisać kod wyłącznie pod kątem nowego interfejsu API:

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

Pozostałe aspekty interfejsu API, czyli różne zdarzenia zmiany ekranu i właściwość screen obiektu FullscreenOptions, nigdy nie byłyby wywoływane lub byłyby ignorowane przez przeglądarki, które ich nie obsługują.

Prezentacja

Jeśli uważnie śledzisz rozwój różnych kryptowalut, możesz monitorować rynki z wygody łóżka dzięki konfiguracji z jednym ekranem w mojej aplikacji. (Wcale tak nie jest, bo kocham tę planetę, ale na potrzeby tego artykułu załóżmy, że tak jest).

Ogromny ekran telewizora na końcu łóżka, z którego wystają nogi autora. Na ekranie znajduje się fałszywe stanowisko do handlu kryptowalutami.
Relaks i obserwowanie rynków.

Ponieważ chodzi o kryptowaluty, rynki mogą w każdej chwili stać się niestabilne. W takiej sytuacji mogę szybko przejść do biurka, gdzie mam konfigurację z wieloma ekranami. Mogę kliknąć okno dowolnej waluty i szybko wyświetlić pełne szczegóły w widoku pełnoekranowym na przeciwnym ekranie. Oto moje ostatnie zdjęcie zrobione podczas ostatniej rzezi YCY. Zaskoczyło mnie to całkowicie i zostałem z rękami na twarzy.

Ja, w panice, obserwuję rzeź w YCY.

Wypróbuj wersję demonstracyjną lub zobacz jej kod źródłowy na GitHubie.

Zabezpieczenia i uprawnienia

Zespół Chrome zaprojektował i wdrożył interfejs Window Management API zgodnie z głównymi zasadami określonymi w dokumencie Controlling Access to Powerful Web Platform Features, w tym z zasadami dotyczącymi kontroli użytkownika, przejrzystości i ergonomii. Interfejs Window Management API udostępnia nowe informacje o ekranach podłączonych do urządzenia, co zwiększa obszar odcisków cyfrowych użytkowników, zwłaszcza tych, którzy mają wiele ekranów stale podłączonych do swoich urządzeń. Aby ograniczyć to zagrożenie dla prywatności, udostępniane właściwości ekranu są ograniczone do minimum potrzebnego w przypadku typowych miejsc docelowych.

Aby witryny mogły uzyskiwać informacje o wielu ekranach i umieszczać okna na innych ekranach, wymagane jest zezwolenie użytkownika. Chromium zwraca szczegółowe etykiety ekranu, ale inne przeglądarki mogą zwracać mniej opisowe etykiety (lub nawet puste).

Kontrola sprawowana przez użytkowników

Użytkownik ma pełną kontrolę nad widocznością swojej konfiguracji. Mogą zaakceptować lub odrzucić prośbę o zezwolenie oraz cofnąć wcześniej przyznane uprawnienia za pomocą funkcji informacji o witrynie w przeglądarce.

Kontrola na poziomie firmy

Użytkownicy Chrome Enterprise mogą kontrolować różne aspekty interfejsu Window Management API, jak opisano w odpowiedniej sekcji ustawień atomowych grup zasad.

Przejrzystość

Informacja o tym, czy przyznano uprawnienia do korzystania z interfejsu Window Management API, jest widoczna w informacjach o witrynie w przeglądarce i można ją też uzyskać za pomocą interfejsu Permissions API.

Trwałość uprawnień

Przeglądarka zachowuje przyznane uprawnienia. Uprawnienia można cofnąć w informacjach o witrynie w przeglądarce.

Prześlij opinię

Czy coś w API nie działa tak, jak oczekujesz? Czy brakuje metod lub właściwości, które są potrzebne do realizacji Twojego pomysłu? Masz pytania lub uwagi dotyczące modelu zabezpieczeń?

Wyrażanie poparcia dla interfejsu API

Czy planujesz używać interfejsu Window Management API? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich obsługiwanie.

Zasoby

Podziękowania

Specyfikację interfejsu Window Management API edytowali Victor Costan, Joshua BellMike Wasserman. Interfejs API został wdrożony przez Mike’a Wassermana i Adrienne Walker. Ten artykuł został sprawdzony przez Joego Medleya, François Beauforta i Kayce Basques. Dziękujemy Laurze Torrent Puig za zdjęcia.