PopGuard は、作業中に割り込んでくる最前面(TOPMOST)のポップアップウィンドウを、一時的に裏へ送る (または最小化・非表示にする)Windows 向けのタスクトレイ常駐アプリです。
運用・設定の手順は docs/sources/ の管理者ガイド/ユーザーガイドを参照してください。
- 単体の実行ファイル
PopGuard.exe(.NET / WinForms)。ブラウザー拡張やサービスは使いません。 - 各ユーザーのセッション内で一般ユーザー権限のプロセスとして常駐します(サービスではないため、ユーザーのデスクトップのウィンドウ操作とトレイ表示が可能)。
- 抑止対象・方法は設定ファイル
PopGuard.rules.jsonで定義します。 - 抑止は「トレイメニューからの手動(期限付き)」と「Windows 11 のフォーカス セッション連動」で有効化されます。
- 裏へ送ったウィンドウは、抑止終了時・アプリ終了時・異常時に必ず元へ戻します。
| 経路 | 実装 | 役割 |
|---|---|---|
| イベント(即時) | SetWinEventHook(EVENT_OBJECT_CREATE / EVENT_OBJECT_SHOW) |
出現した瞬間に評価。チラ見えを最小化 |
| ポーリング(保険) | 700ms ごとに EnumWindows |
イベントの取りこぼし(表示後に最前面化する等)を補完 |
- イベントは
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESSで購読。メッセージポンプ(Application.Run)が 回っている STA スレッドで登録します。 - トップレベルに絞るため、コールバックで
idObject == OBJID_WINDOW && idChild == 0かつGetAncestor(hwnd, GA_ROOT) == hwndを確認します。 - どちらの経路も最終的に
GuardEngine.Consider(hwnd, appeared)を呼びます(appearedはイベント経路では常に true、 ポーリングでは前回の走査以降に新しく見えたウィンドウなら true)(GuardEngineは_lock/_stateLockで 保護されており、イベント(UI スレッド)とポーリング(スレッドプール)からの並行呼び出しに対して安全)。
- 抑止が有効(
IsActive:手動またはフォーカス連動)。 IsWindowかつIsWindowVisible。- 最前面(
GetWindowLong(GWL_EXSTYLE) & WS_EX_TOPMOST)。ただしtopMostOnly: falseのルールがあれば、 最前面でないウィンドウも表示された瞬間(appeared)に限り候補にする(下記)。 - 自プロセス以外。
- ルール一致(まずプロセス名で安く絞り、その後クラス名・タイトルを取得して照合)。最前面でないウィンドウには
topMostOnly: falseのルールと除外ルールだけが当たり、topMostOnly: trueのルールは読み飛ばして次のルールへ進む。 - モーダル回避に該当しない(下記。一致したルールが
suppressModal: trueなら判定しない)。
一致したら hide に従い処理します。
hide |
実装 |
|---|---|
Bottom(既定) |
SetWindowPos(HWND_NOTOPMOST) → SetWindowPos(HWND_BOTTOM) |
Minimize |
ShowWindowAsync(SW_MINIMIZE) |
Hide |
ShowWindowAsync(SW_HIDE) |
HWND_NOTOPMOST だけでは「非最前面グループの先頭」に残り見た目が変わらないため、明示的に裏送り/最小化/非表示まで行います。
最前面でないウィンドウには HWND_NOTOPMOST を送らず(非最前面グループの先頭へ上がってしまうため)、hide の処理だけを行います。
最前面でないウィンドウは、抑止中に表示された瞬間だけ処理します(WinEvent の CREATE/SHOW、またはポーリングで新規に 見えたとき)。毎回押し戻すとユーザーが前面に戻して使えなくなるため、次のものは対象外です。
- 抑止を開始する前から開いていたウィンドウ。
- 一度押し戻したあと、ユーザーが前面に戻したウィンドウ(最小化から戻した場合も含む)。
同じウィンドウが非表示→再表示された場合(通知ウィンドウの使い回しなど)は再び処理します。 あとから最前面化した場合は通常の最前面ウィンドウとして押し戻し、復元時は最前面に戻します。
モーダルダイアログを裏へ送るとアプリが進められなくなるため、次のいずれかに該当すると対象外にします (企業向けに安全側へ倒す設計)。
- オーナーウィンドウが無効(
GetWindow(GW_OWNER)が無効化されている)=モーダル実行中の典型。 WS_EX_DLGMODALFRAMEを持つ。- ウィンドウクラスが
#32770(標準の Win32 ダイアログボックス。MessageBox/DialogBox由来)。
ルールに suppressModal: true を指定すると、そのルールに一致したウィンドウはモーダル回避を行わず、
モーダル/ダイアログに見えても通常どおり抑止します(通知をダイアログとして出すアプリ向け)。
本物のモーダルを押し戻すと、復元されるまでオーナーウィンドウは無効のまま(アプリが止まったように見える)なので、
process / title / class で対象を十分に絞って使ってください。
タスクバーなども TOPMOST のため、広いルール(例 process: "*")でも巻き込まれないよう、
Windows シェルのウィンドウクラスを既定で対象外にします。
対象クラス:Shell_TrayWnd / Shell_SecondaryTrayWnd(タスクバー)、Progman / WorkerW(デスクトップ)、
NotifyIconOverflowWindow(通知領域のあふれ)。GuardEngine.IsSystemShellWindow で判定。
設定 excludeSystemWindows(既定 true)で有効・無効を切り替え可能(通常は変更不要)。
裏へ送ったウィンドウ(WasTopMost / hide 方法を記録)を追跡し、次のタイミングで RestoreAll() により
隠し方を逆転 → 元が最前面なら HWND_TOPMOST へ戻します。
- 抑止の期限切れ(
CheckExpiry、ポーリングから毎回)。 - 手動停止(
Deactivate)/アプリ終了。 AppDomain.UnhandledException/ProcessExit(取り残し防止)。
- 手動:トレイメニューの時間候補(
durations)→Activate(TimeSpan?)(nullは無制限)。 - フォーカス連動:
Windows.UI.Shell.FocusSessionManager(WinRT)のIsFocusActiveChangedを購読し、 フォーカス セッション中は自動抑止、終了で解除(FocusSessionWatcher)。 - マイク連動:
MicrophoneWatcherが ConsentStore (…\CapabilityAccessManager\ConsentStore\microphone、HKCU/HKLM、NonPackaged配下のデスクトップアプリ含む)の 各アプリのLastUsedTimeStart/LastUsedTimeStopを約 2 秒間隔でポーリングし、LastUsedTimeStop == 0(使用中)が あれば自動抑止、無くなれば解除。レジストリは参照のみ(変更しない)。 - 複数の自動要因:フォーカスとマイクは独立した要因として
GuardEngineの_autoSources集合で管理し、 いずれかが有効な間は抑止を継続、すべて解除されたときに自動解除。手動操作は常に優先(自動で始めたものだけ自動解除)。 - 手動 DND は非対応:
FocusSessionManager.IsFocusActiveはフォーカス セッションのみ反映し、手動の 「応答不可」では変化しません。SHQueryUserNotificationState(QUNS_QUIET_TIME)も Win11 の新 DND を 反映しないため、確実に取得できる公式手段がなく非対応としています(NotificationStateWatcherは検証用で、 Debug ビルドでのみ動作します。Release ではコンパイルされず、notification-state:ログも出力されません)。
- .NET 9 /
TargetFramework = net9.0-windows10.0.22621.0(FocusSessionManagerは 22621 で追加のため)。 SupportedOSPlatformVersion = 10.0.17763.0。実行時にOperatingSystem.IsWindowsVersionAtLeast(10,0,22621)で フォーカス連動をガード(未満の OS では連動スキップ、他機能は動作)。- WinForms(
UseWindowsForms)でトレイ UI。WinRT 射影でFocusSessionManager。 - 動作:Windows 10 1809+ / Windows 11(フォーカス連動は 22H2+)、x64。
PopGuard.exe と同じフォルダーに置き、起動時に一度だけ読み込みます(キー名は大文字小文字非依存)。
{
"rules": [
{ "enabled": true, "process": "SomeNotifier", "title": "*通知*",
"class": "", "hide": "Bottom", "topMostOnly": true }
],
"durations": [
{ "label": { "ja": "30分", "en": "30 min" }, "minutes": 30 }
],
"autoSuppressDuringFocus": true,
"autoSuppressDuringMicrophone": false,
"excludeSystemWindows": true,
"language": "auto"
}rules[]:process(必須・ワイルドカード)、title/class(空で無指定)、hide(Bottom/Minimize/Hide)、enabled、exclude、topMostOnly(既定 true。false で最前面でないウィンドウも表示時に対象。除外ルールでは無視され、 常に最前面かどうかに関係なく適用される)、suppressModal(既定 false。true でモーダル/ダイアログに見えても抑止)。- ワイルドカードは
*?、全体一致・大文字小文字無視。processが空/ワイルドカードのみは無効。 exclude: trueは「どんなときも抑止しない」除外ルール。最初に一致したルールが適用されるため、 除外ルールを広いルール(例process: "*")より上に置くと例外を作れる。
- ワイルドカードは
durations[]:minutes(0 以下=無制限)とlabel。labelは文字列(全言語共通)でも、{ "ja": "…", "en": "…" }の言語別オブジェクトでもよい。表示言語に該当する言語だけが使われ、無い言語はminutesから自動生成される(片方だけ指定した場合、もう一方は自動生成)。label自体を省略すると両言語とも 自動生成。durations省略時は既定候補(30分/1時間/2時間/一日/無制限、言語連動)。autoSuppressDuringFocus:フォーカス セッション連動(既定 true)。autoSuppressDuringMicrophone:マイク使用中(通話・Web 会議など)の連動(既定 false)。excludeSystemWindows:タスクバー等の Windows シェルウィンドウを常に抑止対象外にする(既定 true)。language:auto/ja/en。
設定生成支援(Excel パラメータシート)は、非公開の popguard-enterprise リポジトリの
tools/parameter-sheet/ にあります。
- RESX:
Resources/Strings.resx(既定=英語, ニュートラル)+Strings.ja.resx(日本語サテライト)。 - アクセサは手書き(
Resources/Strings.cs、ResourceManager)。Strings.Cultureで上書き可能。 language設定(ApplyOverride)でja/en/autoを選択。autoはCurrentUICultureに従う。
- 出力先:
%LOCALAPPDATA%\PopGuard\PopGuard.log(ユーザーごと)。 - 書き込みのたびに開閉し、10MB 超で世代ローテーション(
PopGuard_1.log〜_10.log)。 - 複数インスタンス/スレッドに備え名前付き Mutex(
Local\PopGuard.Logger)で直列化。 - Debug ビルドでは
Debug.WriteLineにも出力([Conditional("DEBUG")])。 - 主なログ:
rules:/win-event:/focus-session:/mic-watch:/guardEngine: suppression …/guardEngine: demote|restore|skip:/window toplevel:。
# 開発ビルド / 実行(フレームワーク依存)
dotnet build PopGuard\PopGuard.csproj
dotnet run --project PopGuard\PopGuard.csproj
# 配布用:自己完結発行(.NET ランタイム不要)
dotnet publish PopGuard\PopGuard.csproj -p:PublishProfile=win-x64-selfcontained
# 発行 + インストーラ(Inno Setup)を一括
pwsh -File build.ps1- インストーラは
SetupOutput\PopGuardSetup-<version>.exe(PopGuard.iss、Inno Setup 6)。 - 自己完結のため TFM 配下の
win-x64\publish\を丸ごと同梱(ja\サテライト含む)。
ToastTester は検証用の WinForms アプリです。
- 自前トースト:TOPMOST・非アクティブ化・ツールウィンドウの右下ポップアップ(PopGuard の抑止対象)。
- OS トースト:本物の Windows トースト(PopGuard では抑止できないことの確認用)。
- モーダルウィンドウ:TOPMOST かつモーダル(モーダル回避で
guard skipになることの確認用)。
ルールを process = ToastTester にして PopGuard を起動 → 抑止を有効化すると、自前トーストは裏送り、
モーダルは対象外、OS トーストは無反応、という挙動を確認できます(ルールに suppressModal: true を付けると
モーダルも裏送りされる)。
- 抑止できるのはトップレベルウィンドウのみ(既定では最前面のもの)。Windows 標準のトースト通知は制御不可。
- 一般ユーザー権限で動作するため、管理者権限のアプリや他セッションのウィンドウは対象外(UIPI/セッション分離)。
- 「表示→その後 TOPMOST 化」するアプリでは、次のポーリング(最大 ~700ms)まで裏送りが遅れ、その間見えることがある。
- モーダルダイアログは安全のため既定で対象外(ルール単位の
suppressModal: trueで対象にできる)。 topMostOnly: falseで最前面でないウィンドウを対象にしても、抑止開始前から開いていたウィンドウや、ユーザーが前面に戻したウィンドウは押し戻さない。- 手動 DND 連動は非対応(公式 API で確実に取得できないため)。