「スリープ状態への遷移を抑制」のおさらい
Windows 10 には[電源とスリープ]という設定項目があって、そこで指定されている時間が経過してもユーザーによる操作が無かった場合、自動的に Windows がスリープ状態に移行したり、ディスプレイの電源が切れたりします。
でも、アプリによってはそれでは都合が悪いので、そうならないようにするために、アプリ開発者は SetThreadExecutionState を使用することが多いと思います。
具体的には、まず下記のように宣言。(コメント部分、私の解釈がちょっと入ってます。間違いがあったらゴメンなさい)
<Runtime.InteropServices.DllImport("kernel32.dll", SetLastError:=True, CharSet:=Runtime.InteropServices.CharSet.Auto)> _ Private Function SetThreadExecutionState(esFlags As EXECUTION_STATE) As EXECUTION_STATE End Function <FlagsAttribute> _ Public Enum EXECUTION_STATE As UInteger ' 失敗した時の戻り値は 0? Nothing? ''' <summary> ''' スリープ状態になるのを抑止。スリープ状態の解除も可能らしい。 ''' これだけ実行してもシステムのアイドルタイマが一時的にリセットされるだけなので ''' 通常は ES_CONTINUOUS と組み合わせて使用する。</summary> ES_SYSTEM_REQUIRED = &H00000001UI ''' <summary> ''' ディスプレイがオフになるのを抑止。オフ状態からの復帰は無理らしい? ''' ES_CONTINUOUS と合わせて使うことができず(組み合わせると失敗する)、 ''' Windows Vista 以降ではスクリーンセーバーの抑止にならない(ディスプレイオフの抑止にはなる)。 ''' 従って、これに続けて SendInput を呼んでマウスを座標(0,0)に移動させる処理を、 ''' スクリーンセーバー移行の最小時間よりも短い間隔で繰り返し呼び出すのが良いらしい。</summary> ES_DISPLAY_REQUIRED = &H00000002UI ''' <summary> ''' 効果を永続させる。ほかのオプション(といっても今のところ ES_SYSTEM_REQUIRED だけか?)と併用する。 ''' 単独で呼び出すことで、先に他のフラグと組み合わせて実行した抑止(つまり ES_SYSTEM_REQUIRED)を解除する。 ''' ES_DISPLAY_REQUIRED は明示的に解除する必要がないしね。</summary> ES_CONTINUOUS = &H80000000UI ' 3つを組み合わせて1度で呼び出すと変な結果になるようなので避けたほうが良さそう。 End Enum
そして、たとえば ContentRendered(WPF)や Load 等で下記を実行(スリープ状態への移行を停止する命令)。
SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED Or EXECUTION_STATE.ES_CONTINUOUS)
そして、たとえば Closing で下記を実行(元の状態に戻す命令)。
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS)
このやり方で抑止できるのはユーザー操作が無い場合の自動的な移行であって、ユーザーによる意図的な移行(多くの PC では、特にノート PC やタブレットでは、カバーを閉じたり電源ボタンを押す操作)は止められません。
ここまでは、ネット検索してだいたい分かった。
でもここで1つ疑問が。
複数のアプリがこれを同時に実行したらどうなるの?
複数のアプリが同時に実行したらどうなる?
たとえば、
- 上記の処理を行うアプリAが起動したあとで、同じ処理を行うアプリBが起動して、先にアプリBが終了したら?
- 上記の処理を行うアプリAが起動したあとで、同じ処理を行うアプリBが起動して、先にアプリAが終了したら?
どうなる?
実際に試してみましたが、いずれのケースでも、最後まで起動して残っていたほうのアプリによる抑制指示が生きていました。
抑制を指示し続けるアプリが生きてるなら、他のアプリが抑制を解除する命令を出して終了しても、影響されないってことですね。たぶん。
前回のスレッド実行状態を保存して復元したらどうなる?
ところで、SetThreadExecutionState は、成功すると、「前回のスレッド実行状態」を返すらしい。
じゃぁ、アプリで抑制を開始するときに「前回のスレッド実行状態」を記憶しておいて、アプリが終了するときにその状態を復元するようにしたらどうなるのか?
つまり、ContentRendered(WPF)や Load 等で下記を実行(スリープ状態への移行を停止する命令)して、
PreviousVal = SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED Or EXECUTION_STATE.ES_CONTINUOUS)
そして、たとえば Closing で下記を実行(元の状態に戻す命令)したらどうなる?
SetThreadExecutionState(PreviousVal)
同じ処理を行うアプリが複数起動した場合、どのアプリが先に終了するか分からないので、ややこしいことになりそうな気が(?)。
とりあえず、前述の、アプリAとアプリBで試した2通りのやり方でチェックしてみたら、いずれの場合も2つのアプリの PreviousVal が同じ値を返しました。
つまり、先に起動したアプリのシステムに対する抑制指示を、後から起動したアプリが認識してるわけではなくて、アプリごとの抑制指示をシステムが個別に管理してるということですね。たぶん。
ということは・・・ここでまた新たな疑問が。
抑制解除しなくてもアプリが終了したら元に戻るのでは?
前述の実験で、どうやらスリープ状態への遷移を抑制する指示は、システムによってアプリごとに管理されている気がした。
だとすると、抑制指示を出したアプリが、抑制解除の指示を出さないまま終了したら? 抑制指示がアプリごとに管理されているのであれば、システムが「抑制指示を出しているアプリがいなくなった」と認識して、自動的に抑制解除されるのではないか?
結論から言うと、その通りになりました。
つまり、スリープ状態への遷移を抑制する命令を出したアプリが、その抑制を解除せずに終了したとしても、抑制は解除されていました(一定時間経過後、スリープ状態となった)。
まぁ、でも、抑制解除をしないでアプリを終えるのは、たぶん、「お行儀が悪い」と評価されるんでしょうね。
明示的に、後始末はちゃんとしておいたほうが良いでしょう。たぶん。
スリープ状態への遷移を抑制するアプリが起動した後でシステムのスリープ設定をしたら?
もともと PC がスリープ状態に移行しない状態になっているときに、(スリープ状態への移行を抑制する)アプリを起動して、その後でシステムのスリープ状態を(5分間ユーザー操作が無ければスリープするとか)設定したらどうなるかと思ってやってみました。
結果は、アプリの設定が優先されました。
システムを後から設定しても、システムが「あ、スリープ状態への移行を抑制してるアプリが実行中なのね。了解で~す」と納得してくれるようです。