Windows 11 の音量表示を消す方法と、アプリから隠すときの落とし穴

この記事にはEnglish版もあります。

目次

音量キーを押したときに、再生中の曲の情報を出すアプリ(FlexFlyout)を作っていたら、Windows 標準の音量表示と二重に出てしまいました。 ところが、標準の音量表示を消す公式の方法はありません。同じ種類のアプリ(FluentFlyout・Bolume・Modern Flyouts など)のコードや変更履歴を読んで、Windows 11 の最新の版(26H2)で確かめたことを残しておきます。

Windows 11 の標準の音量表示。画面下の中央に、スピーカーのアイコン・音量のバー・数字が出る

先に結論(消したい・動かしたいだけの人向け)

  • 位置を変えるだけなら、Windows の設定でできます
    • 設定 → システム → 通知 → 一番下の「キーボード操作からのインジケーター」 → 「画面上のインジケーターの位置」
    • 選べるのは「下部中央」(既定)・「左上」・「上部中央」の 3 か所です。明るさなどの表示も一緒に動きます
    • 比較的新しい設定(2025 年に Insider 版で試されていたもの)なので、版によっては無いかもしれません

Windows の設定の「画面上のインジケーターの位置」。下部中央・左上・上部中央から選べる

  • 音量の表示だけを消したいなら、Windhawk の mod「On-Screen Indicator Position」(1.6.0 以降)
    • 表示の種類ごとに「Hidden」を選べます。明るさなどの表示を残したまま、音量の表示だけを消せるのは、今のところこの方法だけです
  • アプリで消す(FluentFlyout・Bolume・FlexFlyout など)
    • 手軽ですが、明るさ・機内モードなどの表示も一緒に消えます(理由は下で)
  • どの方法も Windows の非公開の作りに頼っているので、Windows の更新で動かなくなることがあります

確かめた環境

  • Windows 11 Enterprise 評価版 26H2(ビルド 26300.9457、日本語)。仮想マシンで確認
  • 各ソフトのコードは 2026-11-01 時点の既定のブランチ(FluentFlyout と Windhawk の mod は 2026-11-11 に変更を確かめ直した)
  • 22H2〜24H2 の構成は、自分では確かめておらず、各ソフトのコードから読み取ったものです

標準の音量表示の正体

  • Windows 11 の音量・明るさ・機内モード・カメラ・マイクなどの表示は、Explorer が読み込む Windows.Internal.HardwareConfirmator.dll が出しています
    • 表示する関数は、種類ごとに分かれています(ShowVolumeAsync・ShowBrightnessAsync など)
  • ただし、ウィンドウは全種類で 1 つを共有しています
    • なので、外からこのウィンドウを隠すと、明るさなどの表示も一緒に消えます。アプリで消すと明るさまで消えるのはこのためです
  • ウィンドウは、Explorer の最上位の XamlExplorerHostIslandWindow です
    • 表示していない間もウィンドウは残っていて(非表示)、音量キーを押すと広がって表示され、2〜4 秒で非表示に戻ります
    • ただし、ログオンしてから最初に表示されるまでは、ウィンドウ自体がありません

26H2 の仮想マシンで、音量キーを押す前後のウィンドウを読み取ってみました(読み取るだけのスクリプトで、Explorer には手を加えていません。画面は 1280×800。結果は抜粋して整形しています)。

=== 押す前
hwnd=0x3035A proc=explorer visible=False band=18 size=169x61 exstyle=0x08200088 子=なし
=== 押してから約 0.3 秒後
hwnd=0x3035A proc=explorer visible=True  band=18 size=234x62 exstyle=0x08200088 子=なし
=== 押してから約 3.8 秒後
hwnd=0x3035A proc=explorer visible=False band=18 size=169x61 exstyle=0x08200088 子=なし
  • band は、ウィンドウの重なりの層(バンド)です。18 は ZBID_ABOVELOCK_UX(ロック画面よりも上)。非公開の GetWindowBand で読めます
  • exstyle は WS_EX_NOACTIVATE | WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW | WS_EX_TOPMOST でした

版ごとに構成が変わってきた

  • Windows 10 と Windows 11 の 21H2: NativeHWNDHost の下の DirectUIHWND でした
    • 22H2 でこれが変わり、Modern Flyouts は標準の表示を隠せなくなりました(#920)
  • 22H2〜24H2: XamlExplorerHostIslandWindow の子に Windows.UI.Composition.DesktopWindowContentBridge、孫に Windows.UI.Input.InputSite.WindowClass があった模様です
    • FluentFlyout と HideVolumeOSD は、この子・孫のウィンドウを手がかりに探しています
  • 26H2: 子・孫のウィンドウがありません
    • 子・孫を条件にする探し方では見つかりません(FluentFlyout 自体の 26H2 での動作は確かめていません)
    • 代わりに、「Explorer のウィンドウで、バンドが 18 で、小さい」という条件なら見つかりました。Modern Flyouts はバンドで、Windhawk の mod は大きさで見分けています

隠し方は 3 通り

方法使っているものやり方弱点
画面外へ動かすFluentFlyout自分のパネルを出す間だけ、ウィンドウをクリック透過にして画面外へ動かし、最小化する。パネルを閉じたら戻す戻し忘れると、明るさなどの表示も出なくなる
表示範囲を空にするBolumeSetWindowRgn でウィンドウの表示範囲を空にする。Explorer が表示し直しても、範囲の外なので映らない表示し直しなどで範囲が戻ることがあるので、設定し直す仕組みが要る。明るさなどの表示も消える
Explorer の中で止めるWindhawk の modExplorer に組み込み、表示する関数や ShowWindow などを横取りするWindhawk が要る。関数の名前は Windows の更新で変わりうる
  • 種類ごとに止められるのは、Explorer の中で止める方法だけです。外から操作する 2 つは、どうしても明るさなども巻き込みます
  • Bolume は、範囲が戻されたときに備えて 250ms ごとに確かめて設定し直しています。FlexFlyout では、ウィンドウが表示されたことを知らせるイベント(後述)で設定し直すようにしました

見つけ方

FlexFlyout では、次の条件で見分けています(Rust、windows クレート)。

/// Explorer の音量表示のウィンドウか
fn is_indicator(hwnd: HWND) -> bool {
    // クラス名が XamlExplorerHostIslandWindow か
    if class_name(hwnd) != "XamlExplorerHostIslandWindow" {
        return false;
    }
    // Explorer のものか(デスクトップのシェルのウィンドウと同じプロセス)
    let (mut owner, mut shell) = (0u32, 0u32);
    unsafe {
        GetWindowThreadProcessId(hwnd, Some(&mut owner));
        GetWindowThreadProcessId(GetShellWindow(), Some(&mut shell));
    }
    if owner == 0 || owner != shell {
        return false;
    }
    // バンドが 18(ZBID_ABOVELOCK_UX)か。GetWindowBand が無い版では見ない
    if let Some(band) = window_band(hwnd) && band != 18 {
        return false;
    }
    // 小さいか(同じクラスのほかのウィンドウを除く。Windhawk の mod と同じ 700×250 未満)
    window_rect(hwnd).is_some_and(|r| {
        (1..700).contains(&r.width()) && (1..250).contains(&r.height())
    })
}
  • GetWindowBand は user32.dll にある非公開の関数なので、GetProcAddress で探して呼んでいます
  • 同じクラスのウィンドウは、ほかにもあることがあります(大きさが 0 のもの、仮想デスクトップの切り替えの表示など)。なので、クラス名だけで決めないようにしています

落とし穴

  • FindWindowEx で順に探すときは、前の候補を次の起点に渡す
    • 渡さないと、同じウィンドウが返り続けて無限ループになります。FluentFlyout では、最初の候補が音量の表示でないときに CPU を 1 コア使い続けていました(d2a2978 で修正)
      let mut after = None;
      while let Ok(hwnd) =
          unsafe { FindWindowExW(None, after, w!("XamlExplorerHostIslandWindow"), None) }
      {
          if is_indicator(hwnd) {
              found.push(hwnd);
          }
          after = Some(hwnd); // 次はこのウィンドウの後ろから探す
      }
  • Explorer のウィンドウに SW_MINIMIZE・SW_RESTORE を使わない
    • 前面のアプリのフォーカスが外れます。SW_SHOWMINNOACTIVE・SW_SHOWNOACTIVATE を使います(FluentFlyout の f54e8b4)
  • 隠したままにすると、明るさの表示も出なくなる
    • 同じウィンドウだからです。FluentFlyout は、パネルを閉じるたびに戻すようにしています(a8b0669)
  • 表示範囲を空にしたまま、アプリが落ちると戻らない
    • SetWindowRgn で設定した範囲は、設定したプロセスが終わっても残ります。仮想マシンで、範囲を空にしたスクリプトが終わった後に音量キーを押しても、表示は出ないままでした
    • Explorer を再起動すると、ウィンドウが作り直されて(ハンドルの値も変わって)、表示が戻りました
    • なので、アプリが強制終了されても戻るように、次の起動時に、範囲が空になっている音量表示のウィンドウを見つけたら戻すようにしています
      // 範囲が空か(NULLREGION)を確かめ、空なら範囲ごと外す
      let mut rect = RECT::default();
      if unsafe { GetWindowRgnBox(hwnd, &mut rect) } == NULLREGION {
          unsafe { SetWindowRgn(hwnd, None, true) };
      }
  • 音量キーを横取りするだけでは、標準の表示を止めきれない
    • キーボードの「Consumer」の報告で届くメディアキーは、低レベルのキーボードフック(WH_KEYBOARD_LL)を通りません(Bolume のコメントより)
    • 低レベルのフックは、管理者権限のウィンドウが前面だと呼ばれません
    • メーカーのドライバーが音量キーを SendInput で打ち直していることもあるので、注入されたキー(LLKHF_INJECTED)を一律に無視すると、そういうキーボードで反応しなくなります
    • 表示のきっかけには、音量の変化の通知(IAudioEndpointVolumeCallback)も使うのが確実です。ただし、音量が 0% や 100% のときにキーを押しても、値が変わらないので通知は来ません

消さずに避ける方法

  • 標準の表示を残して、自分の表示を重ならない位置に出す、という手もあります
  • ウィンドウが表示されたことは、SetWinEventHook の EVENT_OBJECT_SHOW で分かります。ポーリングは要りません
    // メッセージループのあるスレッドで登録する(コールバックはこのスレッドで呼ばれる)
    let hook = unsafe {
        SetWinEventHook(
            EVENT_OBJECT_SHOW,
            EVENT_OBJECT_SHOW,
            None,
            Some(on_show),
            0,
            0,
            WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
        )
    };
    • コールバックでは、idObject == OBJID_WINDOW で、上の条件に合うウィンドウだけを扱います
    • 表示された時点では、まだ広がる前の大きさのことがあるので、200ms 後(SetTimer)に GetWindowRect で位置を読んでいます
  • 読み取るだけで Explorer には手を加えないので、Windows の更新で読み取れなくなっても、表示が重なることがあるだけで済みます。明るさなどの表示にも影響しませんし、異常終了したときの後始末も要りません

FlexFlyout ではどうしたか

FlexFlyout の設定の「Windows 標準の音量表示」。残して重ならないように表示する・消す・残して重なっても気にしない、から選ぶ

  • 既定は「残して、重ならないように表示する」(上の消さずに避ける方法)にしました
  • 「消す」を選んだときは、表示範囲を空にする方法で、EVENT_OBJECT_SHOW が来るたびに設定し直します。設定の画面にも「明るさなどの表示も消えます」と書いています
  • 元に戻すのは、正常な終了・パニック・設定の変更・次の起動の 4 か所です
  • Windhawk を使っている人には、FlexFlyout で消すより、On-Screen Indicator Position で音量の表示だけを Hidden にしてもらう方がおすすめです

まとめ

  • Windows 11 の音量表示は、Explorer の中の 1 つのウィンドウで、明るさや機内モードの表示と共有しています。外から隠すと、全部一緒に消えます
  • 位置を変えるだけなら Windows の設定で、音量だけ消すなら Windhawk の mod で、が今のところ一番素直です
  • アプリから操作するなら、Windows の更新で構成が変わる前提で、壊れても困らない作り(既定は読み取るだけ、隠したら必ず戻す)にしておくのが良さそうです。実際、22H2 でも 26H2 でも構成が変わっています

お疲れ様でした!