Como gerenciar várias telas com a API Window Management

Receba informações sobre telas conectadas e janelas de posição em relação a elas.

API Window Management

Com a API Window Management, é possível enumerar as telas conectadas à máquina e posicionar janelas em telas específicas.

Casos de uso sugeridos

Exemplos de sites que podem usar essa API incluem:

  • Os editores de gráficos de várias janelas à la Gimp podem colocar várias ferramentas de edição em janelas posicionadas com precisão.
  • As mesas de operações virtuais podem mostrar tendências de mercado em várias janelas, e qualquer uma delas pode ser visualizada no modo de tela cheia.
  • Os apps de apresentação de slides podem mostrar as anotações do apresentador na tela principal interna e a apresentação em um projetor externo.

Como usar a API Window Management

O problema

A abordagem testada para controlar janelas, Window.open(), infelizmente não reconhece outras telas. Embora alguns aspectos dessa API pareçam um pouco arcaicos, como o parâmetro DOMString windowFeatures, ela foi muito útil ao longo dos anos. Para especificar a posição de uma janela, transmita as coordenadas como left e top (ou screenX e screenY, respectivamente) e transmita o tamanho desejado como width e height (ou innerWidth e innerHeight, respectivamente). Por exemplo, para abrir uma janela de 400×300 a 50 pixels da esquerda e a 50 pixels do topo, use este código:

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

Você pode conferir informações sobre a tela atual na propriedade window.screen, que retorna um objeto Screen. Esta é a saída no meu 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
*/

Como a maioria das pessoas que trabalha em tecnologia, tive que me adaptar à nova realidade do trabalho e configurar meu escritório pessoal. O meu está como na foto abaixo (se você tiver interesse, leia os detalhes completos sobre minha configuração). O iPad ao lado do meu MacBook está conectado ao laptop usando o Sidecar. Portanto, sempre que eu precisar, posso transformar rapidamente o iPad em uma segunda tela.

Banco da escola em duas cadeiras. Em cima do banco da escola, há caixas de sapatos apoiando um laptop e dois iPads ao redor.
Uma configuração para várias telas.

Se eu quiser aproveitar a tela maior, posso colocar o pop-up do exemplo de código acima na segunda tela. Faço assim:

popup.moveTo(2500, 50);

Este é um palpite aproximado, já que não há como saber as dimensões da segunda tela. As informações de window.screen cobrem apenas a tela integrada, mas não a do iPad. O width informado da tela integrada era de 1680 pixels. Portanto, mover para 2500 pixels pode funcionar para deslocar a janela para o iPad, já que sei que ele está localizado à direita do meu MacBook. Como posso fazer isso no caso geral? Acontece que há uma maneira melhor do que adivinhar. Dessa forma, fica a API Window Management.

Detecção de recursos

Para verificar se há suporte para a API Window Management, use:

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

A permissão window-management

Antes de usar a API Window Management, preciso pedir permissão ao usuário. A permissão window-management pode ser consultada com a API Permissions da seguinte forma:

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

Enquanto os navegadores com a permissão antiga e o nome novo estão em uso, use o código defensivo quando solicitar a permissão, como no exemplo abaixo.

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

O navegador pode escolher mostrar a solicitação de permissão dinamicamente na primeira tentativa de usar qualquer um dos métodos da nova API. Continue lendo para saber mais.

A propriedade window.screen.isExtended

Para descobrir se mais de uma tela está conectada ao meu dispositivo, acesso a propriedade window.screen.isExtended. Ela retorna true ou false. Na minha configuração, o resultado é true.

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

Método getScreenDetails()

Agora que sei que a configuração atual é multitelas, posso acessar mais informações sobre a segunda tela usando Window.getScreenDetails(). Chamar essa função vai mostrar uma solicitação de permissão que me pergunta se o site pode abrir e colocar janelas na minha tela. A função retorna uma promessa que é resolvida com um objeto ScreenDetailed. No MacBook Pro 13 com um iPad conectado, isso inclui um campo screens com dois objetos 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
  }]
}
*/

As informações sobre as telas conectadas estão disponíveis na matriz screens. Observe como o valor de left para o iPad começa em 1680, que é exatamente o width da tela integrada. Isso me permite determinar exatamente como as telas são organizadas de maneira lógica (uma lado a outra, uma sobre a outra etc.). Agora, também há dados para cada tela para mostrar se é isInternal e se é isPrimary. A tela integrada não é necessariamente a tela principal.

O campo currentScreen é um objeto ativo correspondente ao window.screen atual. O objeto é atualizado em posições de janelas em várias telas ou mudanças no dispositivo.

O evento screenschange

A única coisa que falta agora é uma forma de detectar quando minha configuração de tela muda. Um novo evento, screenschange, faz exatamente isso: ele é acionado sempre que a constelação da tela é modificada. Observe que "telas" está no plural no nome do evento. Isso significa que o evento é disparado sempre que uma nova tela ou uma tela atual é conectada ou desconectada (física ou virtualmente, no caso do arquivo secundário).

É necessário procurar os novos detalhes da tela de forma assíncrona. O evento screenschange em si não fornece esses dados. Para procurar os detalhes da tela, use o objeto ativo de uma interface Screens armazenada em cache.

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

O evento currentscreenchange

Se eu só tiver interesse em mudanças na tela atual (ou seja, no valor do objeto ativo currentScreen), posso detectar o evento currentscreenchange.

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

O evento change

Por fim, se eu só tiver interesse em mudanças em uma tela concreta, posso detectar o evento change dessa tela.

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

Novas opções de tela cheia

Até agora, era possível solicitar que os elementos fossem exibidos no modo de tela cheia usando o método requestFullScreen() (link em inglês) apropriado. O método usa um parâmetro options em que você pode transmitir FullscreenOptions. Até agora, a única propriedade dele foi navigationUI. A API Window Management adiciona uma nova propriedade screen, que permite determinar em qual tela iniciar a visualização em tela cheia. Por exemplo, se você quiser colocar a tela principal em tela cheia:

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

Plástico poligonal

Não é possível fazer o polyfill da API Window Management, mas é possível mudar a forma dela para codificar exclusivamente com a nova 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;
}

Os outros aspectos da API, ou seja, os vários eventos de mudança de tela e a propriedade screen do FullscreenOptions, simplesmente nunca seriam acionados ou ignorados silenciosamente, respectivamente, por navegadores sem suporte.

Demonstração

Se você for como eu, fique de olho no desenvolvimento das diversas criptomoedas. (Na verdade, eu não gosto muito desse planeta, mas, pelo motivo deste artigo, presuma que eu eu.) Para acompanhar as criptomoedas que tenho, desenvolvi um app da Web que permite observar os mercados em todas as situações da vida, como no conforto da cama, onde tenho uma configuração de tela única de boa qualidade.

Tela de TV enorme ao fim da cama com as pernas do autor parcialmente visíveis. Na tela, uma mesa de operações de criptomoedas falsa.
Para relaxar e acompanhar o mercado.

Em relação às criptomoedas, os mercados podem ficar bagunçados a qualquer momento. Se isso acontecer, posso ir rapidamente para a minha mesa, onde tenho uma configuração multitelas. Posso clicar em qualquer janela de moeda e ver rapidamente todos os detalhes em uma visualização de tela cheia na tela oposta. Veja abaixo uma foto recente minha tirada durante o último banho de sangue no ano YCY (em inglês). Isso me pegou totalmente desprevenido e me deixou com minhas mãos no rosto.

O autor com as mãos no rosto em pânico e olhando para a mesa de operações de criptomoedas falsa.
Panicky, testemunhando o banho de sangue YCY.

Você pode testar a demonstração incorporada abaixo ou conferir o código-fonte dela em falhas.

Segurança e permissões

A equipe do Chrome projetou e implementou a API Window Management usando os princípios básicos definidos em Como controlar o acesso a recursos avançados da plataforma Web, incluindo controle do usuário, transparência e ergonomia. A API Window Management expõe novas informações sobre as telas conectadas a um dispositivo, aumentando a superfície de impressão digital dos usuários, especialmente aqueles com várias telas conectadas consistentemente aos dispositivos. Como mitigação dessa questão de privacidade, as propriedades da tela exposta são limitadas ao mínimo necessário para casos de uso de posicionamento comuns. A permissão do usuário é necessária para que sites recebam informações sobre várias telas e coloquem janelas em outras. Embora o Chromium retorne rótulos de tela detalhados, os navegadores podem retornar rótulos menos descritivos (ou até mesmo vazios).

Controle do usuário

O usuário tem controle total sobre a exposição da própria configuração. Eles podem aceitar ou recusar a solicitação de permissão e revogar uma permissão concedida anteriormente pelo recurso de informações do site no navegador.

Controle empresarial

Os usuários do Chrome Enterprise podem controlar vários aspectos da API Window Management, conforme descrito na seção relevante das configurações de Grupos de políticas atômicas.

Transparência

O fato de a permissão para usar a API Window Management ter sido concedida fica exposto nas informações do site do navegador e também pode ser consultado usando a API Permissions.

Persistência da permissão

O navegador mantém as concessões de permissão. A permissão pode ser revogada nas informações do site do navegador.

Feedback

A equipe do Chrome quer saber mais sobre suas experiências com a API Window Management.

Fale sobre o design da API

Há algo na API que não funciona como você esperava? Ou há métodos ou propriedades ausentes que você precisa para implementar sua ideia? Tem alguma dúvida ou comentário sobre o modelo de segurança?

  • Registre um problema de especificação no repositório do GitHub correspondente ou adicione suas ideias a um problema existente.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente da especificação?

  • Registre um bug em new.crbug.com. Inclua o máximo de detalhes possível, instruções simples para reprodução e insira Blink>Screen>MultiScreen na caixa Componentes. O Glitch funciona muito bem para compartilhar repetições rápidas e fáceis.

Mostrar suporte à API

Você planeja usar a API Window Management? Seu suporte público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegador a importância do suporte a eles.

Links úteis

Agradecimentos

A especificação da API Window Management foi editada por Victor Costan, Joshua Bell e Mike Wasserman. Ela foi implementada por Mike Wasserman e Adrienne Walker (links em inglês). Este artigo foi revisado por Joe Medley, François Beaufort e Kayce Basques. Agradecemos a Laura Torrent Puig pelas fotos.