バックグラウンドに常駐するアプリケーションでメニューバーの高さを計算する必要があった。いくつかの方法を試してみたが、冴えたやり方が存在しない模様。(13.x Ventura)
- バックグラウンドプロセスからメニューバーの高さを正しく取得したい
- メニューバーを隠す設定が有効な場合、それを考慮する
- メニューバーの高さはそもそも固定値ではない
- ノッチ無しMacでは、どの解像度でもメニューバーの高さは24pt固定(常時表示の場合/単位はpxではないことに注意)
- ノッチ付きMac (MacBookシリーズ) では、メニューバーの高さが可変的になる
- 設定解像度によってメニューバーの高さが変化する
- 27pt, 29pt, 34pt (33pt), 37pt, 43pt
- 16-inch MacBook Proだと34ptではなく何故か33ptになるらしい
- 参考:Designing macOS menu bar extras
- 設定解像度によってメニューバーの高さが変化する
- メニューバーはひとつとは限らない
- マルチディスプレイ環境
- 複数個のディスプレイがある環境では、メニューバーを表すウインドウも複数存在する
- ノッチ付きMacにノッチ無しのディスプレイを接続している環境が考えられる(ディスプレイによってメニューバーの高さが異なる)
- macOS Ventura 13.0
- Studio Display
- AppKit
アプリケーションがバックグラウンドに移行して、かつメニューバーが自動で隠される設定の場合、うまく値を取れない。
let height = NSApp.mainMenu?.menuBarHeight
let isVisible = NSMenu.menuBarVisible()
NSStatusBar.system.thickness
で得られる値はmacOS Ventura時点で22ptで、これは24ptよりも小さい。昔のmacOSではメニューバーの高さは22ptであったので一致していたが、少なくともBig Sur以降ではずれている。
どうやらAppleは「正しい」としているらしい。 feedback-assistant/reports#140
The default value of this property is 20.0. The status bar returned by the system has a thickness of 22 pixels, which corresponds to the thickness of the menu bar. https://developer.apple.com/documentation/appkit/nsstatusbar/1534591-thickness
以下のようなコードでデスクトップ上のウインドウ一覧の中から、メニューバーっぽいやつを探す。kCGWindowBounds
でフレームが取れるので、height
がそのままメニューバーの高さになると思われる。
if let windowInfo = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as? [Dictionary<CFString, AnyObject>] {
windowInfo.forEach {
if let windowOwnerName = $0[kCGWindowOwnerName] as? String, windowOwnerName == "Window Server",
let windowName = $0[kCGWindowName] as? String, windowName == "Menubar" {
print("\($0)")
}
}
}
絞り込み条件:
kCGWindowOwnerName … "Window Server"
kCGWindowName … "Menubar"
kCGWindowBounds
のwidth
… デスクトップの幅と一致
要注意なのは、確実にメニューバーを絞りきれる保証がないことと、どうやらデスクトップスペースの数だけメニューバーが存在するようで、.optionAll
だと1つに絞りきれないこと。
※ kCGWindowName
は、Menubarに限っては画面収録のパーミッションがなくても得られる。
例えば以下のコードでは 25pt
の値を取得できるが、 メニューバーを隠すオプションを有効にすると常に 1pt
を返す。 (macOS 13.2.1)
1pt を除外すると、それぞれ 24pt
, 0pt
となり、メニューバーの高さを表しているように見える。余分な 1pt は境界線分?
メニューバーを自動で隠すオプションを有効にして、カーソルを重ねて表示してもその高さは1ptを返すことに注意。つまり、 このオプションが有効だと本来の高さ値を確認することはできない。 惜しい。😩
一応バックグラウンドからも正しく取れているように見える。(macOS 13.2.1) ノッチ付きMacでは未検証。
if let screen = NSScreen.main {
// Maybe 25pt, or 1pt
let menubarHeight = screen.frame.maxY - screen.visibleFrame.maxY
// Maybe 24pt, or 0pt
let menubarHeightActual = screen.frame.maxY - screen.visibleFrame.maxY - 1
}
マウスカーソルがいるスクリーンを得るには以下のコードを使う。上記コードの NSScreen.main
の代わりにすると、操作中スクリーンを絞ったユースケースに対応できて良いかもしれない。
import Cocoa
extension NSScreen {
class func screenThatUnderMouse() -> NSScreen? {
NSScreen.screens.first {
NSMouseInRect(NSEvent.mouseLocation, $0.frame, false)
}
}
}
if let screen = NSScreen.screenThatUnderMouse() {
// Maybe 25pt, or 1pt
let menubarHeight = screen.frame.maxY - screen.visibleFrame.maxY
}
この方法だと、メニューバーを隠すオプションを有効にしていても高さ値を得られる。 今のところこの方法が最も現実的と思われる。
tell application "System Events" to tell (process 1 where frontmost is true)
set appName to name
tell menu bar 1
set {menuBarWidth, menuBarHeight} to the size
end tell
end tell
return {appName, {menuBarWidth, menuBarHeight}}
-- result:
-- {"Script Editor", {2560, 24}}
-- {"Finder", {2560, 24}}