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

Obtenez des informations sur les écrans connectés et les fenêtres de positionnement par rapport à ces écrans.

API de gestion des fenêtres

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.

Cas d'utilisation suggérés

Voici quelques exemples de sites susceptibles d'utiliser cette API:

  • Les éditeurs graphiques multifenêtres à la Gimp peuvent placer divers outils de retouche dans des fenêtres bien positionnées.
  • Les services de négociation virtuels peuvent afficher les tendances du marché dans plusieurs fenêtres, lesquelles peuvent être visualisées en mode plein écran.
  • Les applications de diaporama peuvent afficher les commentaires du présentateur sur l'écran principal interne et la présentation sur un projecteur externe.

Utiliser l'API de gestion des fenêtres

Problème

L'approche éprouvée pour contrôler les fenêtres, Window.open(), ne tient malheureusement pas compte des écrans supplémentaires. Bien que certains aspects de cette API semblent un peu archaïques, tels que son paramètre DOMString windowFeatures, elle nous a tout de même bien servi au fil des années. Pour spécifier la position d'une fenêtre, vous pouvez transmettre les coordonnées en tant que left et top (ou screenX et screenY respectivement), et transmettre la taille souhaitée en tant que width et height (ou innerWidth et innerHeight respectivement). Par exemple, pour ouvrir une fenêtre de 400 × 300 à 50 pixels à partir de la gauche et à 50 pixels du haut, utilisez 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 consultant 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 travaillant dans le secteur de la technologie, j'ai dû m'adapter à la nouvelle réalité du travail et mettre en place mon bureau personnel à domicile. Le mien ressemble sur la photo ci-dessous (si cela vous intéresse, vous pouvez lire les détails complets de ma configuration). L'iPad situé à côté de mon MacBook est connecté à l'ordinateur portable via Sidecar. Ainsi, chaque fois que j'en ai besoin, je peux rapidement transformer l'iPad en un deuxième écran.

Banc d'école sur deux chaises. Sur le banc de l'école se trouvent des boîtes à chaussures sur lesquelles sont posés un ordinateur portable et deux iPad.
Une configuration multi-écran

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

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 de window.screen ne concernent que l'écran intégré, mais pas l'écran de l'iPad. La taille (width) signalée de l'écran intégré était de 1680 pixels. Passer à 2500 pixels pourrait entraîner le déplacement de la fenêtre vers l'iPad, car je sais qu'il se trouve à droite de mon MacBook. Comment faire ? Il s'avère qu'il existe une meilleure façon de deviner. C'est de cette façon l'API de gestion des fenêtres.

Détection de fonctionnalités

Pour vérifier si l'API de gestion des fenêtres est compatible, utilisez la commande suivante:

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

L'autorisation window-management

Avant de pouvoir utiliser l'API Window Management, je dois demander l'autorisation à l'utilisateur. 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.
}

Bien que les navigateurs avec l'ancien et le nouveau nom d'autorisation soient utilisés, assurez-vous d'utiliser un code de défense 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 de manière dynamique l'invite d'autorisation lors de la première tentative d'utilisation de l'une des méthodes de la nouvelle API. Lisez la suite pour en savoir plus.

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. Pour ma configuration, il renvoie true.

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

La 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 affichera une invite d'autorisation qui me demandera 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 organisés de manière logique (les uns à côté des autres, les uns au-dessus des autres, etc.). Des données sont désormais disponibles pour chaque écran pour indiquer s'il s'agit d'un écran isInternal et s'il s'agit d'un écran 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 changement d'appareil ou de positionnement des fenêtres multi-écrans.

L'événement screenschange

Il ne manque plus qu'un moyen de détecter si la configuration de mon écran change. C'est exactement ce que fait un nouvel événement, screenschange, qui se déclenche chaque fois que la constellation d'écran est modifiée. Notez que le mot "screens" (écran) est au pluriel dans le nom de l'événement. 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 du side-car).

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

L'é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);
});

L'événement change

Enfin, si je ne m'intéresse qu'aux modifications apportées à un écran concret, 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 de plein écran

Jusqu'à présent, vous pouviez demander que les éléments s'affichent en mode plein écran via la méthode requestFullScreen(), qui porte bien son nom. La méthode utilise un paramètre options dans lequel vous pouvez transmettre FullscreenOptions. Jusqu'à présent, sa seule propriété était navigationUI. L'API de gestion des fenêtres ajoute une nouvelle propriété screen qui vous permet de déterminer sur quel écran lancer l'affichage plein écran. Par exemple, si vous souhaitez afficher l'écran principal 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 d'émuler l'API Window Management, mais vous pouvez modifier sa forme afin de 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, c'est-à-dire les différents événements de changement d'écran et la propriété screen de FullscreenOptions, ne se déclenchent tout simplement jamais et ne sont jamais ignorés sans notification par les navigateurs non compatibles.

Démonstration

Si vous êtes comme moi, vous surveillez de près le développement des différentes cryptomonnaies. (En réalité, ce n'est pas parce que j'aime cette planète que je l'aime beaucoup, mais, pour les besoins de cet article, je suppose que c'est ce que j'ai fait.) Pour suivre les cryptomonnaies que je possède, j'ai développé une application Web qui me permet de surveiller les marchés dans toutes les situations de la vie, par exemple dans le confort de mon lit, où j'ai une bonne configuration avec écran unique.

Immense écran de télévision au bout d'un lit, avec les jambes de l'auteur partiellement visibles. À l'écran montre une fausse plate-forme d'échange de cryptomonnaies.
Détente et observation des marchés

Concernant les cryptomonnaies, les marchés peuvent être dynamiques à tout moment. Si cela se produit, je peux passer rapidement à mon bureau où j'ai une configuration multi-écran. Je clique sur la fenêtre d'une devise pour afficher 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 bain de sang YCY. Cela m'a pris complètement au dépourvu et m'a laissé les mains sur le visage.

L'auteur, les mains sur le visage paniqué, fixe le bureau de trading de cryptomonnaies.
Panicky, témoin du bain de sang YCY.

Vous pouvez jouer avec la démonstration intégrée ci-dessous ou voir son code source sur Glitch.

Sécurité et autorisations

L'équipe Chrome a conçu et mis en œuvre l'API Window Management en suivant les principes fondamentaux définis dans la section Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web, telles que 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 de fingerprinting des utilisateurs, en particulier ceux dont plusieurs écrans sont constamment connectés à leurs appareils. Afin d'atténuer ce problème de confidentialité, les propriétés de l'écran exposé sont limitées au minimum nécessaire pour les cas d'utilisation d'emplacement courants. L'autorisation de l'utilisateur est requise pour que les sites puissent obtenir des informations multi-écrans 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 totalement 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 les sites du navigateur.

Contrôle d'entreprise

Les utilisateurs de Chrome Enterprise peuvent contrôler plusieurs aspects de l'API de gestion des fenêtres, comme décrit dans la section correspondante des paramètres Groupes de règles atomiques.

Transparence

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

Persistance des autorisations

Le navigateur conserve les autorisations accordées. L'autorisation peut être révoquée à partir des informations du site du navigateur.

Commentaires

L'équipe Chrome souhaite connaître votre avis sur l'API de gestion des fenêtres.

Décrivez-nous la conception de l'API.

Y a-t-il quelque chose dans l'API qui ne fonctionne pas comme prévu ? Ou manque-t-il des méthodes ou des propriétés dont vous avez besoin pour mettre en œuvre 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 des spécifications ?

  • Signalez un bug sur new.crbug.com. Veillez à inclure autant de détails que possible et des instructions simples pour reproduire le bug, puis saisissez Blink>Screen>MultiScreen dans la zone Composants. Glitch est idéal pour partager des reproductions simples et rapides.

Afficher la compatibilité avec l'API

Comptez-vous utiliser l'API de gestion des fenêtres ? Votre assistance publique 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

La spécification de l'API Window Management a été modifiée 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é lu par Joe Medley, François Beaufort et Kayce Basques. Merci à Laura Torrent Puig pour les photos.