MacBook Proの充電器の情報をメニューバーに表示するElectronアプリをつくった


MacBook Proで使われてるType-Cの充電は条件により充電速度が変わってきます。

例えば、私の場合RAVPowerのType-AとType-Cの両方が使え 最大60W まで供給できる充電器を普段使いしています。


しかしType-AとType-CにそれぞれiPhoneとMacBook Proへ同時に充電しようとすると、45W に供給される電力が低下してしまいます。

この状態で頑張って仕事していると、徐々バッテリーが減っていってしまいます。
減らないにしても充電速度がかなり遅くなる。



充電器、ケーブル、PD対応など、Type-C関連は仕様が複雑過ぎるので、繋いでみないと正直わかりません。
充電できたとしても、この供給電力の情報はかなり奥まったところにあるので確認しづらい。
ならメニューバーに表示するアプリをつくってしまえ!

つくった

Charger Information for Mac というアプリをElectronでつくりました。

https://github.com/narikei/Charger-Information-for-Mac

アプリを導入すると画面上部のメニューバーに充電器の供給電力が表示されます。


さらにつくった

このアプリをちゃんとSwiftで作り直しました。
Electronから乗り換えたことでマシンリソース消費を削減し、
こっちはMac App Storeで配信もしてみました。(1月中は無料で公開しています!)
https://apps.apple.com/jp/app/wattageviewer/id1548099536?mt=12

充電情報の取得

Macの場合この詳しい充電器の供給電力の情報はシステムレポートから確認しないといけないのですが、
ioreg コマンドで取得することができます。

$ ioreg -rn AppleSmartBattery | grep '"AdapterDetails"'
      "AdapterDetails" = {"Watts"=30,"Current"=1500,"PMUConfiguration"=0,"Voltage"=20000}

Watts: 電力(W)
Current: 電流(mA)
Voltage: 電圧(mV)
とそれぞれ取得できます。
電流と電圧は1/1000で計算すると単位からmがとれてわかりやすい。

Node.jsからMacのコマンドを叩く

child_process でOSのコマンドを実行することができます。
execSync は同期。

const { execSync } = require('child_process');
const stdout = execSync('ioreg -rn AppleSmartBattery | grep \\\"AdapterDetails\\\"');

メニューバーへアイコンの表示

Tray というモジュールを使いメニューバーにアイコンを追加できます。

const { app, Tray, Menu, MenuItem } = require('electron');
const ICON_PATH = 'icon.png';
app.on('ready', () => {
  const appIcon = new Tray(ICON_PATH);
  const menu = new Menu();

  menu.append(new MenuItem({
    label: 'item',
    click: () => {
      alert('click item');
    },
  }));
  menu.append(new MenuItem({ type: 'separator' }));
  menu.append(new MenuItem({ role: 'quit' }));

  appIcon.setContextMenu(menu);
});



こんな感じの記述で充電状態と非充電状態でアイコンを変えられます。

  if (!isCharging()) {
    appIcon.setImage(ICON_MISSED_PATH);
    return;
  }

  appIcon.setImage(ICON_CHARGING_PATH);

Dockからアイコンを非表示にする

Electronアプリを実行すると、Dockにアイコンが表示されますが、
こういった常駐型のアプリではDockにあっても邪魔なだけなので非表示にします。
app.dock.hide() で非表示になる。

const { app } = require('electron');
app.on('ready', () => {
  app.dock.hide();
});

充電情報を通知する

Notification モジュールを使いMacの通知機能に投げることができます。
充電情報が変化したときに、通知できるようにしました。

const { app, Notification } = require('electron');
app.on('ready', () => {
  const notification = new Notification({
    title: '⚡Charging',
    body: 'Power: 30W\nVoltage: 20V / Current: 1.5A',
    silent: true,
  });
  notification.show();
});

\nで改行できますが、どうも最初の1つ以降は無視されるっぽい。。。

ダークモード対応する

ダークモードなる存在を完全に忘れていた。(https://github.com/narikei/Charger-Information-for-Mac/issues/2

const { app, nativeTheme } = require('electron');
app.on('ready', () => {
  if (nativeTheme.shouldUseDarkColors) {
    charging_path = ICON__WHITE_PATH;
    return;
  }

  appIcon.setImage(ICON__BLACK_PATH);
});

アプリケーション化する

Electronのビルドにはいくつかやり方があるのですが、
個人的には electron-builder でパッケージングするのが簡単

追加

devDependencies にしないとビルド時に怒られます。

$ yarn add --dev electron-builder

アイコンの作成

icons.iconset というディレクトリーを作り、その中に各サイズのアイコン画像を入れ、
electron-builder に渡してあげるだけで良い。

(今回Dockに表示されないし、導入時にしか見る機会はないが一応、、、)

ビルド情報を記述

ビルド情報は package.json に記述していきます。

package.json
{
  "name": "charger-information-for-mac",
  "main": "main.js",
  "scripts": {
    "build": "electron-builder"
  },
  "devDependencies": {
    "electron": "^7.1.2",
    "electron-builder": "^21.2.0"
  },
  "build": {
    "appId": "com.ozonicsky.charger-information-for-mac",
    "productName": "Charger Information",
    "icon": "icons.iconset",
    "mac": {
      "target": "dmg"
    }
  }
}

ビルドする

$ yarn build

おわり

コードはすべてこちらに公開しております。
https://github.com/narikei/Charger-Information-for-Mac

製品版はこちら
https://apps.apple.com/jp/app/wattageviewer/id1548099536?mt=12