システムトレイ / インジケータ

#gui #linux #macos

画面の隅に小さなアイコンを常駐させ、クリックでメニューを出したり状態を表示したりするUI。デスクトップアプリを3プラットフォームに出すとき、ウィンドウやメニューバー以上に実装が割れる部分。特にLinuxは「どの規格に乗るか」からして決まっていない。

呼び名が3つある

プラットフォーム 呼び名 場所
macOS メニューバーエクストラ / ステータスアイテム 画面上部メニューバーの右側
Windows 通知領域(notification area)、俗にシステムトレイ タスクバー右端
Linux システムトレイ / インジケータ / ステータスアイコン パネル(DEによる)

「システムトレイ」はWindows由来の俗称がそのまま各所で使われている状態で、Microsoftの公式ドキュメントは “notification area” と呼ぶ。

macOS

NSStatusBar::systemStatusBar()からstatusItemWithLength:でNSStatusItemを1つもらい、それにNSImageとNSMenuを付ける、という単純な話。NSStatusItemを保持している間だけ表示され、解放すると消える。

Dockアイコンを出さずにメニューバーだけに常駐したい場合は、Info.plistのLSUIElementをYESにするか、実行時に activation policy を accessory にする。GPUI Community Edition (gpui-ce)がMacActivationPolicy::Accessoryを足しているのはこの用途。

このジャンルのアプリについてはmacOSメニューバー常駐ユーティリティを参照。

Windows

Shell_NotifyIcon(Unicode版はShell_NotifyIconW)にNOTIFYICONDATAを渡してNIM_ADD / NIM_MODIFY / NIM_DELETEする。アイコン・ツールチップ・コールバックメッセージのID・状態(NIS_HIDDENなど)を構造体で指定し、クリック等はウィンドウメッセージとして飛んでくる。

厄介なのは、既定でオーバーフロー(「隠されているアイコンを表示します」)に入ること。ユーザーが明示的に出さない限りタスクバーに直接は出ない。「アイコンを出したのに見えない」の大半はこれ。

Linuxが一番ややこしい

2つの規格が併存している。

flowchart TD A[アプリがトレイアイコンを出したい] --> B{どの規格?} B -->|旧: XEmbed| C["freedesktop System Tray Protocol<br/>X11のウィンドウを埋め込む"] B -->|新: SNI| D["StatusNotifierItem<br/>D-Bus越しにモデルを渡す"] C --> E["Waylandでは原理的に使えない"] C --> F["GNOME: 公式Status Icons拡張が対応"] D --> G["KDE / Unity 系が採用<br/>GNOMEは非公式のAppIndicator拡張"]
  • XEmbed方式(freedesktop の System Tray Protocol) — アプリ側がX11のウィンドウを作り、パネル側がそれを自分の中に埋め込む。X11の仕組みそのものなのでWaylandでは使えない。
  • StatusNotifierItem (SNI) — KDE発。D-Busでアプリがオブジェクトを公開し、StatusNotifierWatcherに登録する。表示はパネル側の裁量(モデル/ビュー分離)。メニューはcom.canonical.dbusmenuで渡す。D-BusベースなのでWaylandでも動く。freedesktopのwikiに置かれているが、正式な標準として批准されたわけではない。

GNOMEは3.26(2017年)で従来のトレイ表示を削除した。2024年に公式の「Status Icons」拡張(作者はFlorian Müllner)がGNOME Shell Extensionsパッケージに入ったが、これはXEmbedのみ対応でAppIndicator/SNIには対応しない。作者は「もっと良い標準が出てくれば対応するかもしれないが、AppIndicatorsはそれではない」と述べている。しかもこの拡張は既定で有効ではなく、ディストリが同梱するかどうかによる。SNIを使いたい場合はコミュニティ製の「AppIndicator and KStatusNotifierItem Support」拡張を入れることになる。

つまりLinuxでは、規格を1つ選んでも全部のデスクトップ環境で出る保証がない。SNIに乗るのが今の主流だが、素のGNOMEでは拡張なしには出ない。

Rustでの実装

デファクトはtray-icon(Tauriチームが管理。メニュー側は同チームのmudaが担当する)。3プラットフォームのバックエンドはこうなっている(v0.24.2のソースで確認)。

プラットフォーム 使っているもの
macOS NSStatusBar::systemStatusBar().statusItemWithLength()
Windows Shell_NotifyIconW + NOTIFYICONDATAW
Linux/BSD libappindicator(またはlibayatana-appindicator)+ GTK3

Linux側の実装で目を引くのがアイコンをメモリから渡せない点。libappindicatorはアイコンをテーマ内の名前かファイルパスでしか受け取らないので、tray-iconは一時ディレクトリにPNGを書き出し、その親ディレクトリをset_icon_theme_path()に、ファイルをset_icon_full()に渡している。アイコンを差し替えるたびに連番付きの新しいファイルを書く。

イベントループの制約も強い。Windowsではwin32のイベントループ、Linux/FreeBSDではGTKのイベントループが同一スレッドで回っている必要があり、macOSではメインスレッドでイベントループが回っている必要がある。自前のイベントループを持つGUIフレームワークに後付けする場合、ここが最初の障害になる。

もう一つの選択肢がksni。KDE/freedesktopのStatusNotifierItem仕様を純Rust + D-Busで実装していて、GTKにもlibappindicatorにも依存しない。org.kde.StatusNotifierItemとcom.canonical.dbusmenuを自前で喋る。tray-iconのLinuxバックエンドをksniに置き換える提案(Issue #11293)は出ているが、2026年9月時点では採用されていない。

crates.ioのダウンロード数(2026-09時点)。

クレート 最新 total recent
tray-icon 0.24.2 27,243,206 11,788,630
libappindicator 0.9.0 23,581,746 9,182,780
ksni 0.3.6 1,351,007 456,267
tray-item 0.10.0 167,881 16,136

tray-iconとlibappindicatorの数字が近いのは、前者が後者を引いているため。

GPUIには無い

GPUI本体にも、コミュニティフォークのGPUI Community Edition (gpui-ce)にもトレイ対応は無い。NSStatusBar・Shell_NotifyIcon・StatusNotifierItemのいずれの実装も、2026年9月初旬時点でどちらのツリーにも見当たらない。

「Zed本体のスコープ外の機能はgpui-ceへ」という案内の文脈でトレイ対応が例に挙がる(GPUI KitのDiscussion #1856など)ため、gpui-ceにはあると思われがちだが、要望として挙がっただけで実装はされていない。

GPUIアプリにトレイを付けるならtray-iconかksniを別途組み合わせることになるが、上記のイベントループ制約とGPUI自身のイベントループの同居が課題になる。実際に噛み合うかは未検証。

同じく、macOS以外でOSのメニューバーを出す手段もGPUIには無い(GPUI Kitでのクロスプラットフォームなメニュー)。

出典

作成日時: 2026-09-06 01:33 / 更新日時: 2026-09-06 23:14