• なんてったってIdle

筆者は、一時、PCのモニターに液晶テレビを使っていたことがある。そのテレビはディスプレイモニターのような待機状態がなく、映像信号が10分間止まると自動で電源オフになる仕組みだった。この省電力機能を解除して常に電源が入ったままにすることもできるが、それでは電気が無駄である。自動でオフになったあとは、リモコン信号でオンにすることができるのだが、電源の制御はオンオフのトグル動作なので、電源オフのときに送ればオンになるが、オンのときに送ってしまうとテレビはオフになってしまう。つまり、PCの画面が消えたあと、テレビが自動オフになっていたときのみ、リモコン信号を送らねばならない。

方法としては2つ。1つは、ディスプレイの状態を調べて、オフかどうかを判定する方法。もう1つは、ユーザー入力がない時間(アイドル時間)を計って映像信号が止まるタイミングを見つけ、そこからの経過時間でテレビがオートオフになったことを推測する方法だ。前者は、かなり面倒な方法だったので、アイドル時間を測定する方法を使うことにした。

画面がオフになるまでの時間は、Windowsの設定アプリやコンロールパネルの電源オプションで調べることができる。頻繁に変更する可能性があるなら、プログラムで毎回調べる必要があるが、管理者権限が必要など結構面倒な処理だ。経験上、デスクトップマシンでは、ほとんど変更することはなかったので、固定値をプログラムにハードコードした。

アイドル時間を調べる

Windowsが測定しているアイドル時間を得るには、Win32APIの「GetLastInputInfo」を使う。APIの情報は以下にある。

・GetLastInputInfo function(英語)
https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getlastinputinfo

このAPIでは、最後にユーザー入力(キーボードやマウス操作)があった時刻を教えてくれる。ただし、その時刻表現は、俗に「Windows時間」と呼ばれる値が使われている。Windows時間はWindowsが再起動してからのミリ秒単位の経過時間を32bitの整数値で表現するもの。ただし、この値はシステムタイマーの割り込み時間間隔(10~16ミリ秒程度)で加算していくソフトウェア処理で作られる。このため、秒以上の時間単位では誤差はあまり目立たないがミリ秒単位で計測すると大きな誤差やばらつきがあるように見える点に注意されたい。

今回は手抜きでWindows PowerShellを使った。一般的にはあまり人気のないWindows PowerShellだが、たいていのことはこれでこなせる。PowerShellには、C#のコードをコンパイルして呼び出す機能があるが、実行はインタプリタで行われる。この方法では、コンパイラもVisual Studioのような開発環境も不要だ。反面、C#で本格的なコードを書いてしまうとそのデバッグはかなり大変である。今回のプログラムは、Win32APIの呼び出し定義と構造体の初期化、構造体のメンバー取り出しだけなので、比較的簡単に作ることができた。

こうしたC#のコードを作るには、.NETのWin32呼び出しに関する情報をまとめたPINVOKE.NETを検索するといい。

・pinvoke.net: GetLastInputInfo (user32)
https://pinvoke.net/default.aspx/user32/GetLastInputInfo.html
・LASTINPUTINFO (Structures)
https://pinvoke.net/default.aspx/Structures/LASTINPUTINFO.html

リスト01がアイドル時間を検出するプログラムだ。前半のヒアストリング(Here-String。@で囲まれた部分)がC#のコードで、Windows PowerShellは、実行時にこれをコンパイルして実行してくれる。後半がPowerShellで書いたC#コードを呼び出す関数定義と起動時のサンプル実行(最後の2行)だ。これをテキストファイルに入れて、拡張子を“.ps1”としておく。PowerShellを起動して、このファイルを読み込むと実例として途中経過表示付きで3秒のアイドル時間を測定する(写真01)。

定義したPowerShellの関数は2つ。1つはWaitIdle関数で、ミリ秒で指定したアイドル時間を検出する。“-Verbose”オプションを付けると途中経過を表示する。指定したアイドル時間を検出すると関数を抜け出してくるのでそこで何かを実行すればよい。アイドル時間が指定した時間以上にならない限り、この関数は終了しない。この関数に、PCが画面オフになる時間を指定することで画面オフのタイミングを検出できる。もう1つはIdleTime関数で、現在のアイドル時間をミリ秒(1秒=1000ミリ秒)で返す関数だ。なお、Windows PowerShellで文字列に日本語を使いたい場合には、ファイルをUnicodeエンコード(UTF-16LE)にする必要があるので注意されたい。

■ リスト01


$du=Add-Type -ErrorAction Ignore @'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace shioda.asb01 {
    public static class UserInput {
        [DllImport("user32.dll", SetLastError=false)]
        private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
        [StructLayout(LayoutKind.Sequential)]
        private struct LASTINPUTINFO {
            public uint cbSize;
            public int dwTime;
        }
        public static int LastInputTime {
            get {
                LASTINPUTINFO myLastInputInfo = new LASTINPUTINFO();
                myLastInputInfo.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
                GetLastInputInfo(ref myLastInputInfo);
                return myLastInputInfo.dwTime;
            }
        }
    }
}
'@
function global:IdleTime() {
    return [System.Environment]::TickCount - [shioda.asb01.UserInput]::LastInputTime;
}
function global:WaitIdle() {
    param ( [int32] $wt, [switch] $Verbose ) ;
    while (($t = IdleTime) -lt $wt ) {
        $w = $wt - $t;
        if($Verbose) {Write-Host ("Idle : {0,8:d} [ms] Wait : {1,8:d} [ms]" -f $t,$w ) }
        Start-Sleep -Milliseconds $w
        }
}
WaitIdle -verbose 3000; 
Write-host "You can excute anything here."
  • 写真01: リスト01をメモ帳などに貼り付けて、DetectIdle.ps1として保存。Windows PowerShellから読み込むと、3秒のアイドル時間の測定が始まる。マウスやキー入力を3秒以上止めると実行を停止する。PowerShell内では、指定したアイドル時間を待つWaitIdle関数と現在のアイドル時間を返すIdleTimeが使えるようになる

PowerShellは、.NET FrameworkやC#のクラスをそのままオブジェクトとして扱える。このプログラムではC#部分は“UserInput”というクラスになっていて、“LastInputTime”というプロパティが定義されている。

Win32APIを呼び出すような、ちょっとした実験にはWindows PowerShellは向いている。C#のコードがそのまま指定できるので、C#向けのWin32APIの情報をインターネット検索などで探すことで、参考にできるコードを見つけることができる。