Gestion de plusieurs écrans avec l'API de gestion des fenêtres

Obtenir des informations sur les écrans connectés et positionner les fenêtres par rapport à ces écrans

API Window Management

L'API Window Management vous permet d'énumérer les écrans connectés à votre ordinateur et de placer des fenêtres sur des écrans spécifiques.

Suggestions de cas d'utilisation

Voici quelques exemples de sites qui peuvent utiliser cette API:

  • Les éditeurs graphiques multifenêtres à la Gimp peuvent placer divers outils de retouche dans des fenêtres positionnées avec précision.
  • Les plates-formes de trading virtuelles peuvent afficher les tendances du marché dans plusieurs fenêtres, chacune pouvant être affichée en plein écran.
  • Les applications de diaporama peuvent afficher les notes du présentateur sur l'écran principal interne et la présentation sur un projecteur externe.

Utiliser l'API Window Management

Problème

L'approche éprouvée pour contrôler les fenêtres, Window.open(), n'est malheureusement pas consciente des écrans supplémentaires. Bien que certains aspects de cette API semblent un peu archaïques, comme son paramètre DOMString windowFeatures, elle nous a néanmoins bien servi au fil des ans. Pour spécifier la position d'une fenêtre, vous pouvez transmettre les coordonnées sous la forme left et top (ou screenX et screenY respectivement) et transmettre la taille souhaitée sous la forme width et height (ou innerWidth et innerHeight respectivement). Par exemple, pour ouvrir une fenêtre de 400 x 300 à 50 pixels à gauche et à 50 pixels en haut, vous pouvez utiliser le code suivant:

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

Vous pouvez obtenir des informations sur l'écran actuel en examinant la propriété window.screen, qui renvoie un objet Screen. Voici la sortie sur mon 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
*/

Comme la plupart des personnes qui travaillent dans le secteur de la technologie, j'ai dû m'adapter à la nouvelle réalité du travail et aménager mon bureau personnel à domicile. La mienne ressemble à celle de la photo ci-dessous (si vous êtes intéressé, vous pouvez lire les détails complets de ma configuration). L'iPad à côté de mon MacBook est connecté à l'ordinateur portable via Sidecar. Je peux donc rapidement transformer l'iPad en deuxième écran si nécessaire.

Banc d'école sur deux chaises. Sur le banc d'école, des boîtes à chaussures soutiennent un ordinateur portable et deux iPad l'entourent.
Configuration multi-écran

Si je souhaite profiter de l'écran plus grand, je peux placer le pop-up de l'exemple de code ci-dessus sur le deuxième écran. Je procède comme suit:

popup.moveTo(2500, 50);

Il s'agit d'une estimation approximative, car il n'y a aucun moyen de connaître les dimensions du deuxième écran. Les informations provenant de window.screen ne couvrent que l'écran intégré, mais pas l'écran de l'iPad. La width de l'écran intégré était de 1680 pixels. Passer à 2500 pixels pourrait fonctionner pour déplacer la fenêtre vers l'iPad, car je sais qu'il se trouve à droite de mon MacBook. Comment procéder dans le cas général ? Il s'avère qu'il existe une meilleure façon de procéder que de deviner. Il s'agit de l'API Window Management.

Détection de fonctionnalités

Pour vérifier si l'API Window Management est compatible, utilisez:

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

Autorisation window-management

Avant de pouvoir utiliser l'API Window Management, je dois demander à l'utilisateur l'autorisation de le faire. L'autorisation window-management peut être interrogée avec l'API Permissions comme suit:

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

Tant que les navigateurs avec l'ancien et le nouveau nom d'autorisation sont utilisés, veillez à utiliser un code défensif lorsque vous demandez une autorisation, comme dans l'exemple ci-dessous.

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

Le navigateur peut choisir d'afficher l'invite d'autorisation de manière dynamique lors de la première tentative d'utilisation de l'une des méthodes de la nouvelle API. Pour en savoir plus, poursuivez votre lecture !

Propriété window.screen.isExtended

Pour savoir si plusieurs écrans sont connectés à mon appareil, j'accède à la propriété window.screen.isExtended. Elle renvoie true ou false. Dans ma configuration, il renvoie true.

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

Méthode getScreenDetails()

Maintenant que je sais que la configuration actuelle est multiécran, je peux obtenir plus d'informations sur le deuxième écran à l'aide de Window.getScreenDetails(). L'appel de cette fonction affiche une invite d'autorisation me demandant si le site peut ouvrir et placer des fenêtres sur mon écran. La fonction renvoie une promesse qui se résout avec un objet ScreenDetailed. Sur mon MacBook Pro 13 avec un iPad connecté, cela inclut un champ screens avec deux objets 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
  }]
}
*/

Les informations sur les écrans connectés sont disponibles dans le tableau screens. Notez que la valeur de left pour l'iPad commence à 1680, qui correspond exactement à la width de l'écran intégré. Cela me permet de déterminer exactement comment les écrans sont disposés de manière logique (l'un à côté de l'autre, l'un sur l'autre, etc.). Des données sont désormais disponibles pour chaque écran afin d'indiquer s'il s'agit d'un écran isInternal ou isPrimary. Notez que l'écran intégré n'est pas nécessairement l'écran principal.

Le champ currentScreen est un objet actif correspondant au window.screen actuel. L'objet est mis à jour en cas de placement de fenêtre multi-écran ou de modification de l'appareil.

Événement screenschange

Il ne manque plus qu'un moyen de détecter quand la configuration de mon écran change. C'est exactement ce qu'effectue un nouvel événement screenschange: il se déclenche chaque fois que le groupe d'écrans est modifié. Notez que le terme "écrans" est utilisé dans le nom de l'événement au pluriel. Cela signifie que l'événement se déclenche chaque fois qu'un nouvel écran ou un écran existant est branché ou débranché (physiquement ou virtuellement dans le cas de Sidecar).

Notez que vous devez rechercher les détails du nouvel écran de manière asynchrone, car l'événement screenschange lui-même ne fournit pas ces données. Pour rechercher les détails de l'écran, utilisez l'objet en direct à partir d'une interface Screens mise en 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;
  }
});

Événement currentscreenchange

Si je ne m'intéresse qu'aux modifications apportées à l'écran actuel (c'est-à-dire à la valeur de l'objet actif currentScreen), je peux écouter l'événement currentscreenchange.

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

Événement change

Enfin, si je ne suis intéressé que par les modifications apportées à un écran spécifique, je peux écouter l'événement change de cet écran.

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

Nouvelles options pour le plein écran

Jusqu'à présent, vous pouviez demander que les éléments soient affichés en mode plein écran via la méthode requestFullScreen(), bien nommée. La méthode utilise un paramètre options dans lequel vous pouvez transmettre FullscreenOptions. Jusqu'à présent, sa seule propriété a été navigationUI. L'API Window Management ajoute une nouvelle propriété screen qui vous permet de déterminer sur quel écran démarrer la vue plein écran. Par exemple, si vous souhaitez que l'écran principal s'affiche en plein écran:

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

Polyfill

Il n'est pas possible de polyfiller l'API Window Management, mais vous pouvez en remplacer la forme afin de pouvoir coder exclusivement avec la nouvelle 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;
}

Les autres aspects de l'API, à savoir les différents événements de changement d'écran et la propriété screen de FullscreenOptions, ne se déclencheraient jamais ou seraient ignorés de manière silencieuse, respectivement, par les navigateurs non compatibles.

Démo

Si vous êtes comme moi, vous suivez de près le développement des différentes cryptomonnaies. (En réalité, je ne le fais pas du tout, car j'aime cette planète, mais, pour les besoins de cet article, supposons que je le fasse.) Pour suivre les cryptomonnaies que je possède, j'ai développé une application Web qui me permet de suivre les marchés dans toutes les situations de la vie, par exemple, confortablement assis au lit, où j'ai un écran unique bien configuré.

Écran de télévision massif au bout d'un lit, avec les jambes de l'auteur partiellement visibles. À l'écran, on voit une fausse plate-forme d'échange de cryptomonnaies.
Se détendre et surveiller les marchés

Concernant les cryptomonnaies, les marchés peuvent être agités à tout moment. Si cela se produit, je peux rapidement me rendre à mon bureau, où j'ai une configuration multi-écran. Je peux cliquer sur la fenêtre de n'importe quelle devise et afficher rapidement tous les détails en plein écran sur l'écran opposé. Vous trouverez ci-dessous une photo récente de moi prise lors du dernier massacre YCY. Je n'ai pas vu ça venir et j'ai mis mes mains sur mon visage.

L'auteur, les mains sur son visage paniqué, fixe le faux bureau de trading de cryptomonnaies.
Paniqué, il assiste au bain de sang de YCY.

Vous pouvez jouer avec la démonstration intégrée ci-dessous ou consulter son code source en cas de glitch.

Sécurité et autorisations

L'équipe Chrome a conçu et implémenté l'API Window Management en suivant les principes de base définis dans Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web, y compris le contrôle utilisateur, la transparence et l'ergonomie. L'API Window Management expose de nouvelles informations sur les écrans connectés à un appareil, ce qui augmente la surface d'empreinte digitale des utilisateurs, en particulier ceux qui ont plusieurs écrans connectés de manière cohérente à leurs appareils. Pour atténuer ce problème de confidentialité, les propriétés d'écran exposées sont limitées au minimum nécessaire pour les cas d'utilisation courants des emplacements. L'autorisation de l'utilisateur est requise pour que les sites puissent obtenir des informations multi-écran et placer des fenêtres sur d'autres écrans. Alors que Chromium renvoie des libellés d'écran détaillés, les navigateurs sont libres de renvoyer des libellés moins descriptifs (voire des libellés vides).

Contrôle des utilisateurs

L'utilisateur contrôle entièrement l'exposition de sa configuration. Ils peuvent accepter ou refuser l'invite d'autorisation et révoquer une autorisation précédemment accordée via la fonctionnalité d'informations sur le site dans le navigateur.

Contrôle de l'entreprise

Les utilisateurs de Chrome Enterprise peuvent contrôler plusieurs aspects de l'API Window Management, comme indiqué dans la section correspondante des paramètres des groupes de règles atomiques.

Transparence

Le fait que l'autorisation d'utiliser l'API Window Management ait été accordée est exposé dans les informations sur le site du navigateur et peut également être interrogé via l'API Permissions.

Persistance des autorisations

Le navigateur conserve les autorisations accordées. L'autorisation peut être révoquée via les informations sur le site du navigateur.

Commentaires

L'équipe Chrome souhaite connaître votre expérience avec l'API Window Management.

Parlez-nous de la conception de l'API

L'API ne fonctionne-t-elle pas comme prévu ? Ou manque-t-il des méthodes ou des propriétés dont vous avez besoin pour implémenter votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ?

  • Signalez un problème de spécification dans le dépôt GitHub correspondant ou ajoutez vos commentaires à un problème existant.

Signaler un problème d'implémentation

Avez-vous détecté un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente de la spécification ?

  • Envoyez un bug sur new.crbug.com. Veillez à inclure autant de détails que possible, des instructions simples pour reproduire le problème et saisissez Blink>Screen>MultiScreen dans le champ Composants. Glitch est idéal pour partager des reproductions rapidement et facilement.

Apportez votre soutien à l'API

Prévoyez-vous d'utiliser l'API Window Management ? Votre soutien public aide l'équipe Chrome à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge.

Liens utiles

Remerciements

Les spécifications de l'API Window Management ont été rédigées par Victor Costan, Joshua Bell et Mike Wasserman. L'API a été mise en œuvre par Mike Wasserman et Adrienne Walker. Cet article a été relu par Joe Medley, François Beaufort et Kayce Basques. Merci à Laura Torrent Puig pour les photos.