バックグラウンドに常駐するアプリケーションでメニューバーの高さ値を取得したい場合、各種条件で正確な値を取ることができるシンプルな方法が1つ存在する。ただし、macOSのバージョンによってはこれがうまく動作せず(13.x Ventura)、代替手段でも条件を満たせない可能性がある。
- バックグラウンドプロセスからメニューバーの高さを正しく取得したい
- メニューバーを隠す設定が有効な場合、それを考慮する
- メニューバーの高さはそもそも固定値ではない
- ノッチ無しMacでは、どの解像度でもメニューバーの高さは24pt固定(常時表示の場合/単位はpxではないことに注意)
- ノッチ付きMac (MacBookシリーズ) では、メニューバーの高さが可変的になる
- 設定解像度によってメニューバーの高さが変化する
- 27pt, 29pt, 34pt (33pt), 37pt, 43pt
- ディスプレイ解像度「デフォルト」でメニューバー高さが37ptになる
- 16-inch MacBook Proだと34pt相当は何故か33ptになるらしい
- 参考:Designing macOS menu bar extras
- 設定解像度によってメニューバーの高さが変化する
- メニューバーはひとつとは限らない(マルチディスプレイ、Spaces)
- マルチディスプレイ環境
- 複数個のディスプレイがある環境では、メニューバーを表すウインドウも複数存在する
- ノッチ付きMacにノッチ無しのディスプレイを接続している環境が考えられる(ディスプレイによってメニューバーの高さが異なる)
- macOS Ventura 13.0, Sequoia 15.7
- Studio Display (2022), MacBook Air 14-inch (2025)
- AppKit
結局この方法が一番確実で正確と考えられる。以前確認したmacOS 13.x Venturaにおいてはバックグラウンド時に問題があったが、少なくともmacOS Sequoia 15.7の環境では、これでうまく動作しているように見える。
let height = NSApp.mainMenu?.menuBarHeighthttps://developer.apple.com/documentation/appkit/nsmenu/menubarheight
一つ注意点は、NSMenu().menuBarHeightなど別のインスタンスからだと値が取れないので、NSApplicationのシングルトンから得られるmainMenuに対して行うこと。
Sequoia 15.7環境で以下の条件で確認したが、問題はなかった。
- メニューバーを非表示にする設定が有効
- アプリがバックグラウンドの状態
- LSUIElement=true
一方Venturaではアプリがバックグラウンドの状態でメニューバーが非表示だと、高さ値を取れなかった🫠。いつの時点でこのバグが修正されたのかはわからないが、Sequoiaでは問題ないと言える。
メニューバーを非表示→表示と切り替えた際の表示状態を判別するには次の方法が使えるが、アプリがバックグラウンドにいると正確な値を返さない模様。
let isMenuBarVisible = 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… デスクトップの幅と一致
要注意なのは、確実にメニューバーを1つに絞りきれる保証がないことと、どうやらデスクトップスペース(Spaces)の数だけメニューバーが存在するようで、.optionAllだと1つに定まらない。フルスクリーンウインドウのスペースにもメニューバーが1つあると見做される。メニューバーを自動的に非表示にする設定にあると、Y座標がマイナス値になる。MacBookと外部ディスプレイの環境ではメニューバーの高さがディスプレイごとに変わると考えられる。
onScreenOnlyだと現在表示しているスクリーンに絞り込めるが、メニューバーを自動で隠す設定が有効だと(隠れていると)、そもそもメニューバーの情報が含まれなくなる。
※ 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
}この方法だと、Ventura環境でメニューバーを非表示でも正確な高さ値を得られた。AppleEvent (AppleScript) 頼るコストが生じるが、古いOSでは代替手段として検討すると良いかもしれない。
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}}