Créer votre propre cadran

Créez vos propres cadrans de jauge en HTML, CSS et JavaScript. Chaque cadran fonctionne avec toutes les sources, de la batterie aux chiffres que vous lui envoyez vous-même.

Démarrage rapide

  1. Créez un cadran : Dans les Réglages, cliquez sur Créer un cadran… (Option + Commande + N). Multimeter crée un dossier de fichiers de départ qui utilisent déjà les données de la version 2.
  2. Modifiez les fichiers : Multimeter affiche le dossier dans le Finder. Modifiez les fichiers dans n’importe quel éditeur de code.
  3. Voyez les changements en direct : Multimeter recharge votre cadran à chaque enregistrement. Essayez-le avec différentes sources dans Ajouter une jauge… ou Personnaliser cette jauge….
  4. Partagez-la : Compressez le dossier dans le Finder, renommez le .zip en .multimeter et proposez-le à la galerie. N’importe qui peut l’ajouter avec Importer… (Commande + O) ou en double-cliquant sur le fichier.

Comment fonctionne une jauge

Une jauge dans la barre des menus, c’est un cadran plus une source plus les réglages de la personne. Le cadran, c’est votre dossier : une petite page web que Multimeter charge dans une vue transparente de 22 points de haut, aussi large que le width de votre manifest. La source, c’est ce que mesure la jauge, choisie par la personne quand elle ajoute la jauge.

Dès que quelque chose change, Multimeter met à jour window.meterData et envoie un événement meterUpdate. Votre cadran n’a pas besoin de savoir d’où viennent les chiffres : dessinez level (de 0 à 1) et affichez formatted, et il fonctionnera aussi bien pour le CPU, le réseau, un minuteur ou la batterie.

Structure des fichiers

Chaque cadran est un dossier contenant :

my-meter/
├── manifest.json    # name, size, which sources it's made for
├── index.html       # the page Multimeter loads
├── styles.css
└── meter.js         # listens for meterUpdate and draws

manifest.json

Le manifest décrit votre cadran et indique à Multimeter sa largeur et ce pour quoi il est conçu :

{
  "name": "My Meter",
  "description": "What it shows, in one line.",
  "version": "1.0.0",
  "author": "Your Name",
  "apiVersion": 2,
  "sources": ["any"],
  "defaultSource": "cpu",
  "width": 60,
  "minWidth": 40,
  "maxWidth": 100,
  "supportsLightMode": true,
  "supportsDarkMode": true
}
CléTypeSignification
namestringAffiché dans la liste des cadrans et dans la galerie.
descriptionstringUne ligne sur ce qu’affiche le cadran.
versionstringVotre numéro de version, comme 1.0.0.
authorstringVotre nom, affiché dans la galerie.
apiVersion2Mettez 2 pour les données décrites sur cette page.
sourcesstring[]["any"] pour un cadran capable de dessiner n’importe quelle source, ou une liste d’ids de source comme ["battery"] ou ["battery", "countdown", "timer"]. Multimeter ne propose votre cadran que pour ces sources.
defaultSourcestringLa source choisie quand quelqu’un ajoute votre cadran pour la première fois, comme "cpu".
widthnumberLargeur en points. La hauteur est toujours de 22.
minWidth / maxWidthnumberLa largeur minimale et maximale que la personne peut choisir dans Personnaliser.
supportsLightMode / supportsDarkModebooleanSi le cadran est conçu pour les barres des menus claires et sombres.

Un manifest sans apiVersion ni sources est traité comme un cadran pensé pour la batterie qui fonctionne quand même avec n’importe quelle source, donc les jauges créées pour les versions précédentes fonctionnent toujours.

Données de la jauge

Multimeter place les données actuelles dans window.meterData et les renvoie à chaque changement. Voici tout ce qu’elles contiennent (version 2 de l’API) :

window.meterData = {
  apiVersion: 2,
  source: "cpu",            // what this meter measures
  sourceName: "CPU",        // that source's name, translated
  label: "CPU",             // the meter's label ("" if the person cleared it)
  value: 23.4,              // raw value in `unit` (null if unavailable)
  min: 0, max: 100,         // the range `level` is measured against
  level: 0.234,             // value placed in the range, 0 to 1
  formatted: "23%",         // ready to show, unit included, in the person's language
  unit: "%",                // "%", "W", "B/s", "rpm", "s", or your own
  state: "normal",          // "normal", "warning" or "critical"
  tint: null,               // "#30D158" if the person picked a color
  history: [0.21, 0.25],    // up to 60 recent levels, oldest first
  warningLevel: 0.7,        // alert levels on the same 0 to 1 scale (or null)
  criticalLevel: 0.9,
  alertDirection: "above",  // "above" or "below" (or null)
  available: true,          // false when the source can't be read right now
  colorScheme: "dark",      // the menu bar: "light" or "dark"
  reduceMotion: false,      // System Settings > Accessibility > Reduce motion
  strings: { timeLeft: "left" },
  battery: {                // always the Mac's battery, whatever the source
    level: 0.82, percentage: 82, isCharging: false, isPluggedIn: false,
    hasBattery: true, timeRemaining: 312, timeToFullCharge: null,
    formattedTime: "5:12", cycleCount: 212, health: 0.94, temperature: 30.5
  },
  // Version 1 fields, so battery meters keep working with any source:
  percentage: 23, isCharging: false, isPluggedIn: false,
  timeRemaining: null, timeToFullCharge: null, formattedTime: null,
  cycleCount: 212, health: 0.94, temperature: 30.5, hasBattery: true
}
ChampSignification
sourceL’id de la source (voir le tableau ci-dessous). sourceName est son nom traduit.
labelLe libellé de la jauge, comme CPU ou DOWN. La personne peut le renommer ou l’effacer, il peut donc s’agir d’une chaîne vide.
valueLa mesure brute en unit, ou null. Avec min et max, vous avez le tableau complet.
levelLa valeur ramenée à la plage de 0 à 1, bornée. La plupart des cadrans n’ont besoin que de ça.
formattedLa mesure sous forme de texte, unité comprise, formatée selon la langue de la personne : 23%, 12.4 W, 1.2 MB/s, 4:12.
unit%, W, B/s, rpm, s (minuteurs et comptes à rebours), une chaîne vide, ou l’unité choisie par la personne.
statenormal, warning ou critical, selon les seuils d’alerte définis par la personne. Choisissez vos couleurs en fonction.
tintUne couleur comme #30D158 choisie par la personne, ou null pour utiliser les couleurs propres à votre cadran.
historyJusqu’à 60 niveaux récents (de 0 à 1), du plus ancien au plus récent, pour les graphiques.
warningLevelwarningLevel et criticalLevel placent les seuils d’alerte sur la même échelle de 0 à 1 que level, et alertDirection indique si les alertes se déclenchent au-dessus (above) ou en dessous (below). Les trois valent null quand la jauge n’a pas d’alertes.
availablefalse quand la source est illisible pour le moment : pas de batterie, accessoire éteint, adresse web en échec. Affichez un tiret.
colorSchemeL’apparence de la barre des menus, light ou dark. Elle suit le fond d’écran, pas seulement le réglage du système.
reduceMotiontrue quand Réduire les animations est activé. Passez les animations.
batteryLa batterie du Mac, toujours présente quoi que mesure la jauge : charge, en charge, branché, temps restant, temps avant la charge complète, cycles, santé et température.

Écouter les mises à jour

Dessinez une première fois avec ce qui se trouve déjà dans window.meterData, puis à chaque meterUpdate. Définissez window.meterReady = true quand vous êtes prêt à recevoir des données.

function render(data) {
  // draw the meter from data.level, data.formatted, data.state ...
}

window.addEventListener('meterUpdate', (e) => render(e.detail));
render(window.meterData || { level: 0.5, formatted: '50%', state: 'normal' });
window.meterReady = true;

Sources

Voici les sources qu’une jauge peut avoir. Votre cadran voit l’id dans source et reçoit formatted déjà rédigé, vous avez donc rarement besoin de les traiter différemment.

SourcesourceunitExemple de formatted
Batteriebattery"%"82%
Accessoire Bluetoothbluetooth"%"64%
Consommationpower"W"12.4 W
CPUcpu"%"23%
Mémoirememory"%"71%
Espace disquedisk"%"58%
Pression thermiquethermal""Normale, Modérée, Élevée, Critique
Vitesse du ventilateur*fan"rpm"2,400 rpm
Réseaunetwork"B/s"1.2 MB/s, 340 KB/s
Minuteurtimer"s"24:51
Compte à rebourscountdown"s"3d 4h, 4:12, 12:05
Avancée de la journéeprogress"%"63%
Valeur envoyéepush""42
Adresse weburl""1,284
Fichierfile""12
Script*script""412

* La vitesse du ventilateur et les scripts nécessitent la version en téléchargement direct de Multimeter. La version du Mac App Store ne peut ni lire les capteurs des ventilateurs ni exécuter vos scripts ; utilisez-y plutôt une valeur envoyée ou un fichier.

Les pourcentages vont toujours de 0 à 100. La consommation, le réseau et la vitesse du ventilateur s’ajustent à leur pic récent, sauf si la personne définit une plage. La pression thermique a quatre paliers, donc level vaut 0, 0.33, 0.67 ou 1. Pour les minuteurs et les comptes à rebours, level est la part de temps restante et value le nombre de secondes restantes.

Alertes et couleurs

Chaque jauge peut avoir un seuil d’alerte et un seuil critique, définis par la personne (par défaut, la batterie sous 20% et 10%, le CPU au-dessus de 70% et 90%, le disque au-dessus de 85% et 95%). Multimeter calcule state pour vous. Un cadran devrait rester calme en normal et se faire plus voyant en passant à warning puis critical, et utiliser tint quand la personne a choisi une couleur :

document.body.classList.remove('warning', 'critical');
if (data.state !== 'normal') document.body.classList.add(data.state);

// The person's color wins over the face's own
if (data.tint) document.body.style.setProperty('--accent', data.tint);
else document.body.style.removeProperty('--accent');
body.warning  { --accent: #ff9f0a; }
body.critical { --accent: #ff453a; }

Vous voulez dessiner les zones d’alerte sur une échelle, comme un compteur avec une zone rouge au bout ? Utilisez warningLevel, criticalLevel et alertDirection.

Historique

history contient jusqu’à 60 niveaux récents, du plus ancien au plus récent, déjà sur l’échelle de 0 à 1. Ajoutez le level actuel à la fin et vous avez un graphique. L’exemple Graphique d’historique ci-dessous en dessine un.

Détails de la batterie

La batterie reste à part. data.battery est toujours la batterie du Mac, quoi que mesure la jauge, donc n’importe quel cadran peut afficher un éclair de charge. Un cadran conçu uniquement pour la batterie devrait indiquer "sources": ["battery"] dans son manifest. timeRemaining et timeToFullCharge sont en minutes et valent null tant que macOS est encore en train d’estimer.

// A battery-only face: "sources": ["battery"] in manifest.json
const b = data.battery;
bolt.hidden = !b.isCharging;
time.textContent = b.isCharging
  ? (b.timeToFullCharge ? `${b.timeToFullCharge} min to full` : '')
  : (b.formattedTime ?? '');

Les cadrans écrits pour la version 1 lisent percentage, isCharging et formattedTime au premier niveau. Ils sont toujours envoyés : pour la source batterie, ce sont les valeurs de la batterie elle-même, et pour les autres sources isCharging vaut false et formattedTime est le temps restant d’un minuteur ou d’un compte à rebours (h:mm), sinon null.

Vos propres données

Quatre sources affichent des chiffres que vous fournissez : une valeur envoyée, une adresse web, un fichier ou un script. Votre cadran les traite comme n’importe quelle autre source.

Envoyer une valeur avec un lien

Ouvrez un lien multimeter://set de n’importe où : Terminal, un script shell, Raccourcis (Ouvrir les URL), Raycast, une tâche cron, un script de build. Ajoutez une jauge avec la source Valeur envoyée et la même clé, et elle se met à jour dès que le lien s’ouvre. open -g laisse Multimeter en arrière-plan.

# Set a value (the meter shows the "deploys" key)
open -g "multimeter://set?key=deploys&value=42"

# Range, label, unit and your own text ride along
open -g "multimeter://set?key=coffee&value=3&min=0&max=5&label=Coffee&unit=cups"
open -g "multimeter://set?key=build&value=0.8&max=1&formatted=80%25%20done"
ParamètreSignification
keyLa jauge concernée. Les clés ne tiennent pas compte de la casse.
valueLe chiffre. Obligatoire.
min / maxLa plage, pour que level soit juste. De 0 à 100 par défaut.
labelLe texte du libellé de la jauge.
unitUne unité ajoutée après le chiffre, comme cups. Utilisez % pour un pourcentage.
formattedVotre propre texte pour formatted, utilisé à la place du chiffre.

Les valeurs envoyées fonctionnent aussi dans la version du Mac App Store. Multimeter retient la dernière valeur de chaque clé.

Adresses web, fichiers et scripts

Les trois lisent la même sortie : un simple chiffre, ou du JSON avec une value et n’importe lequel des extras. Tout sauf value est facultatif.

42
42.5
42%

{"value": 42, "min": 0, "max": 100, "label": "Deploys", "unit": "", "formatted": "42 left"}

Vous pointez vers un JSON plus gros, comme une réponse d’API ? Donnez à la jauge un chemin de clé et elle va chercher le chiffre :

{"data": {"count": 17}}                → key path  data.count
{"items": [{"value": 9.5}]}             → key path  items.0.value
  • Adresse web : interrogée à l’intervalle de votre choix, au plus toutes les 30 secondes (toutes les 5 minutes si vous ne changez rien).
  • Fichier : relu dès que le fichier change. Pratique pour tout ce qui sait écrire un fichier.
  • Script : une commande shell exécutée avec zsh à l’intervalle de votre choix (toutes les 30 secondes si vous ne changez rien). Multimeter affiche le chiffre qu’elle imprime. Les dossiers de Homebrew sont dans le PATH, et une commande qui dure plus de 10 secondes est arrêtée.
# Open pull requests waiting on you (direct download version)
gh pr list --search "review-requested:@me" --json number --jq length

# Free memory pressure as a percentage
memory_pressure | awk '/percentage/ {print 100 - $5}'

La version du Mac App Store peut utiliser les adresses web et les fichiers, mais pas les scripts. Dans ce cas, faites en sorte que votre script envoie un lien ou écrive un fichier.

Barres des menus claires et sombres

La barre des menus est claire ou sombre selon le fond d’écran. Multimeter ajoute light-mode ou dark-mode à body, renseigne colorScheme dans les données, et prefers-color-scheme suit aussi la barre des menus. Utilisez ce que vous préférez :

/* Dark menu bar (the default) */
body { color: white; }

/* Light menu bar */
body.light-mode { color: black; }

/* Same thing with the media query, which follows the menu bar too */
@media (prefers-color-scheme: light) {
  body { color: black; }
}

Animations réduites

Quand Réduire les animations est activé, reduceMotion vaut true et prefers-reduced-motion correspond. Gardez la jauge lisible et supprimez les rebonds, les pulsations et les balayages :

body.reduce-motion * { transition: none !important; animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

Exemples complets

Deux cadrans complets, qui tournent en direct sur cette page avec les mêmes données que celles envoyées par Multimeter. Copiez les fichiers dans un dossier et importez-le.

Le cadran de départ de Créer un cadran

Exactement ce qu’écrit Créer un cadran… : un libellé, une barre et la valeur formatée, avec l’API dans ses commentaires. Il se colore selon state, respecte tint et suit l’apparence de la barre des menus.

starter/manifest.json

{
  "name": "My Face",
  "description": "A label, a bar and the value. Works with any source.",
  "version": "1.0.0",
  "author": "Your Name",
  "apiVersion": 2,
  "sources": ["any"],
  "defaultSource": "cpu",
  "width": 70,
  "minWidth": 50,
  "maxWidth": 120,
  "supportsLightMode": true,
  "supportsDarkMode": true
}

starter/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div id="meter">
        <span id="label"></span>
        <span id="track"><span id="fill"></span></span>
        <span id="value">--</span>
    </div>
    <script src="meter.js"></script>
</body>
</html>

starter/styles.css

* { margin: 0; padding: 0; box-sizing: border-box; }

/* The meter is 22 points tall and as wide as manifest.json says. */
body {
    font: 600 11px/1 -apple-system, BlinkMacSystemFont, sans-serif;
    font-variant-numeric: tabular-nums;
    color: white;                 /* dark menu bar */
    background: transparent;
    height: 22px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    --accent: #30d158;            /* replaced by the meter's color, if one is set */
}

/* Light menu bar: the system setting, or the preview's light mode. */
#track { background: rgba(255, 255, 255, 0.25); }
@media (prefers-color-scheme: light) { body { color: black; } #track { background: rgba(0, 0, 0, 0.18); } }
body.light-mode { color: black; }
body.light-mode #track { background: rgba(0, 0, 0, 0.18); }
body.dark-mode { color: white; }
body.dark-mode #track { background: rgba(255, 255, 255, 0.25); }

#meter { display: flex; align-items: center; gap: 4px; }
#label { font-size: 8px; letter-spacing: 0.04em; opacity: 0.7; }
#track { width: 18px; height: 6px; border-radius: 3px; position: relative; overflow: hidden; }
#fill { position: absolute; inset: 0 auto 0 0; width: 0; background: var(--accent); border-radius: 3px; transition: width 0.4s ease; }

/* state is "normal", "warning" or "critical" (from the meter's alert levels). */
body.warning { --accent: #ffd60a; }
body.critical { --accent: #ff453a; }

@media (prefers-reduced-motion: reduce) { #fill { transition: none; } }

starter/meter.js

// Multimeter meter API, version 2. The app sets window.meterData and fires
// a 'meterUpdate' event whenever anything changes. The fields you'll use most:
//
//   level        0.0 to 1.0, the value within the meter's range
//   formatted    ready-to-show text with its unit: "23%", "1.2 MB/s", "4:12"
//   label        short label ("CPU"), may be "" if hidden
//   value, unit, min, max   the raw number and its range
//   state        "normal", "warning" or "critical"
//   tint         the color picked in Customize ("#30D158"), or null
//   history      up to 60 recent levels (0 to 1), oldest first, for graphs
//   source       what it measures: "battery", "cpu", "memory", "network", "timer"...
//   available    false when there's nothing to read (no battery, URL failing)
//   battery      the Mac's battery, always: level, isCharging, isPluggedIn,
//                hasBattery, timeRemaining, timeToFullCharge, health...
//   isCharging   true only when this meter shows the battery and it's charging
//   colorScheme  "light" or "dark"; reduceMotion  true if animations should stop
//
// Full reference: https://multimeterapp.com/creating-meters

class Meter {
    constructor() {
        this.label = document.getElementById('label');
        this.fill = document.getElementById('fill');
        this.value = document.getElementById('value');
        window.addEventListener('meterUpdate', (e) => this.render(e.detail));
        if (window.meterData) this.render(window.meterData);
        window.meterReady = true;
    }

    render(data) {
        const level = Math.max(0, Math.min(1, data.level || 0));
        this.label.textContent = data.label || '';
        this.label.style.display = data.label ? '' : 'none';
        this.value.textContent = data.available === false ? '\u2013' : (data.formatted || Math.round(level * 100) + '%');
        this.fill.style.width = (level * 100) + '%';

        document.body.classList.toggle('warning', data.state === 'warning');
        document.body.classList.toggle('critical', data.state === 'critical');
        if (data.tint) document.body.style.setProperty('--accent', data.tint);
        else document.body.style.removeProperty('--accent');
    }
}

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => new Meter());
} else {
    new Meter();
}

Graphique d’historique

Un graphique en courbe de la dernière minute à partir de history, dessiné sur un canvas à la vraie résolution de l’écran.

history-graph/manifest.json

{
  "name": "History Graph",
  "description": "The last minute as a little line graph.",
  "version": "1.0.0",
  "author": "Your Name",
  "apiVersion": 2,
  "sources": ["any"],
  "defaultSource": "network",
  "width": 44,
  "minWidth": 30,
  "maxWidth": 80,
  "supportsLightMode": true,
  "supportsDarkMode": true
}

history-graph/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    html, body { margin: 0; height: 22px; background: transparent; overflow: hidden; }
    canvas { display: block; width: 100%; height: 22px; }
  </style>
</head>
<body>
  <canvas id="graph"></canvas>
  <script src="meter.js"></script>
</body>
</html>

history-graph/meter.js

const canvas = document.getElementById('graph');
const ctx = canvas.getContext('2d');

function render(data) {
  // Draw at the screen's real resolution so the line stays sharp
  const scale = window.devicePixelRatio || 1;
  const w = canvas.clientWidth, h = 22;
  canvas.width = w * scale;
  canvas.height = h * scale;
  ctx.setTransform(scale, 0, 0, scale, 0, 0);
  ctx.clearRect(0, 0, w, h);

  const points = (data.history || []).concat(data.level ?? 0);
  if (points.length < 2) return;

  const dark = data.colorScheme !== 'light';
  const color = data.tint
    || (data.state === 'critical' ? '#ff453a' : data.state === 'warning' ? '#ff9f0a' : dark ? '#fff' : '#1d1d1f');
  const x = (i) => (i / (points.length - 1)) * (w - 2) + 1;
  const y = (v) => h - 3 - v * (h - 6);

  // Soft fill under the line, then the line
  ctx.beginPath();
  points.forEach((v, i) => (i ? ctx.lineTo(x(i), y(v)) : ctx.moveTo(x(i), y(v))));
  ctx.lineTo(x(points.length - 1), h);
  ctx.lineTo(x(0), h);
  ctx.closePath();
  ctx.globalAlpha = 0.22;
  ctx.fillStyle = color;
  ctx.fill();

  ctx.globalAlpha = 1;
  ctx.beginPath();
  points.forEach((v, i) => (i ? ctx.lineTo(x(i), y(v)) : ctx.moveTo(x(i), y(v))));
  ctx.strokeStyle = color;
  ctx.lineWidth = 1.5;
  ctx.lineJoin = 'round';
  ctx.stroke();
}

window.addEventListener('meterUpdate', (e) => render(e.detail));
render(window.meterData || { history: [0.2, 0.4, 0.3, 0.6, 0.5], level: 0.7 });
window.meterReady = true;

Conseils de test

  • Gardez tout dans une hauteur de 22 points, et vérifiez votre cadran à sa minWidth et à sa maxWidth.
  • Utilisez la police système (-apple-system) et font-variant-numeric: tabular-nums pour que les chiffres ne sautillent pas.
  • Essayez votre cadran avec plusieurs sources dans Ajouter une jauge… et Personnaliser cette jauge… : un pourcentage, un débit réseau, un minuteur, une source indisponible.
  • Vérifiez les barres des menus claires et sombres, une teinte, et les états d’avertissement et critique.
  • Les erreurs de votre JavaScript apparaissent dans Console.app. Vous pouvez aussi ouvrir votre index.html dans Safari et lui envoyer des données depuis la console :
  • Gardez des fichiers légers et évitez les requêtes réseau, pour que le cadran s’affiche instantanément.
// Paste in Safari's console with your index.html open to try a value
window.meterData = { level: 0.92, formatted: '92%', label: 'CPU', state: 'critical',
  available: true, colorScheme: 'dark', history: [] };
window.dispatchEvent(new CustomEvent('meterUpdate', { detail: window.meterData }));

Prêt à créer ?

$19.99 en achat unique. Sans abonnement. Remboursement sous 30 jours.

Acheter pour $19.99