寝るな、と、ctypes.windll.user32.[SG]etCursorPos, ctypes.windll.kernel32.SetThreadExecutionState

「ctypes」と言ってるのからわかるように python ネタではあるのだけれど、本質は python とは関係ない。

「昔 Windows ネイティブなアプリケーションの開発者だった」ことは言ってる。そうはいってもそれってたかだか 2000~2006 年の6年間だけの話であって、さすがに記憶もかなり薄れていて、今日するのは「うーん、昔やったはずなんだよなぁ」というネタの一つ。まぁこのタイプのネタは、ワタシは結構好きで何度も書いてるんだけどね。最近だと、同じく ctypes ネタのこれとかがそうね。

「Windows ネイティブなアプリケーション」にだって色々あるけれど、ワタシが主としてやってたのが、いわゆる「パソコンでテレビを!」という、今ではかなり当たり前になったが当時はまだそこまで「当然でもなかった」タイプのアプリケーションで、なので、まさに「PC に自動で眠られてはこまーる」ようなタイプだったわけね。絶賛「まだテレビ見てるでしょぅが」状態であっても、ユーザの行動は「見てるだけ」、そして PC はそれを知るよしがないので「あー、おまいさん、ずいぶんと長いことアタシに触れておくれないのね、いやよ、きらいよ」としてふて寝してしまうことを、そうしたアプリケーションは是が非でも避けなければならんわけである。

そして今日、なのだが、「まさに是が非でも避けねばならん」局面に再会した…わけではなくて。今のニーズはもっとずっとライトなもの。基本的に「のんびりゆっくり処理してもらっても構わない重量級のバッチタスク」を動かし続けていたいが、裏で(PCでない据え置き型の)テレビを見ている間も進捗確認のための、コンソールがずっと見えるようにしておきたい、けれども時々気にかけてあげないとディスプレイの電源オフになってしまって困る…、てなこと。はっきりいって切迫した事情なんぞ皆無で、「定期的にマウスを振るのがめんどい」てだけのこと。

そう、「知ってたはずなんだがなぁ」て話なわけよ、オレ的に。けどまぁ、びっくりするほど思い出せない。

一つだけはっきり憶えてるのは、「Windows 9x 時代は SetCursorPos で定期的にマウスカーソルを動かしてるだけでディスプレイオフを避けられたんではなかったかしら」という記憶と、実際にそうしてた記憶。製品で使ったかはさすがに憶えてないが、実験をし、うまくいった微かな記憶がある。ただ、こうした「オートメーション」の類はエンドユーザの操作を阻害しかねないものであるため、Windows の進化とともに、プログラムから使うのは段々難しくなっていったという経緯がある。あまりにも行儀が悪いアプリケーションが横行したからだ。一番有名なのが「SetForegroundWindow」で、かつてはこれは「ユーザがいかなる作業真っ最中であろうがその邪魔をしてじゃじゃーんと自己主張する」ように振る舞っていて、もちろん多くのユーザの顰蹙を買っていた。SetCursorPos もそれに近い理由で何か影響受けてた気がするんだよなぁ、と。でも一応やってみるか:

heywin_dontsleep.py (version 1)
 1 # -*- coding: utf-8 -*-
 2 import time
 3 import ctypes
 4 import random
 5 from datetime import datetime
 6 
 7 
 8 class POINT(ctypes.Structure):
 9     _fields_ = [("x", ctypes.c_ulong),
10                 ("y", ctypes.c_ulong)]
11 
12 
13 if __name__ == '__main__':
14     print(" " * 10, datetime.now())
15     try:
16         while True:
17             cur = POINT()
18             ctypes.windll.user32.GetCursorPos(ctypes.byref(cur))
19             ctypes.windll.user32.SetCursorPos(
20                 cur.x + random.randint(-50, 50),
21                 cur.y + random.randint(-50, 50))
22             #ctypes.windll.user32.SetCursorPos(cur.x, cur.y)
23             print(" " * 10, datetime.now(), end="\r", flush=True)
24             time.sleep(1)
25     except KeyboardInterrupt:
26         print("\n'd mornin...")

やってみたが、30分くらい放置してたらディスプレイオフになってしまった。やはりか、今はもう使えない、と。というかまぁ、「昔のうまくいった記憶」の方もあてにはならないけどね、今ではもう試せないし。

だとすると、と。今度は別のかすかな記憶で「通知ハンドラを登録し、そのハンドラが Windows が送ってくる通知メッセージに反応してリジェクトする、みたいな発想じゃなかったっけ?」なんて思い、こんな検索こんな検索で探ってみる。

WM_POWERBROADCASTか? …などとそのキーワードに基づいて徘徊してたら、StackOverflow のこれ(またの名をセルフノリツッコミ)から SetThreadExecutionState に辿り着いた。あぁ、これは記憶にある「通知に反応するアプローチ」じゃなくて、プライオリティの変更と同類の「アタシのリアルタイム性を OS に知らしめる」やつか。これも非常に薄い記憶はあるか? どうだろ、あんま憶えてないなぁ…。

「こどもがまだみてるでしょぅが」をちゃんとやってるものとして、ワタシがすぐに思い付き、オープンソースなものとして MPC-HC があるので、まずそいつがどうしてるか見てみるか、と、ソースコードを入手して、grep してみた:

 1 [me@host: mpc-hc-develop]$ find . -type f -exec grep 'SetThreadExecutionState' '{}' /dev/null \;
 2 ./src/DSUtil/WinAPIUtils.cpp:    SetThreadExecutionState(ES_CONTINUOUS);
 3 ./src/mpc-hc/MainFrm.cpp:                SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
 4 ./src/mpc-hc/MainFrm.cpp:        SetThreadExecutionState(ES_CONTINUOUS);
 5 ./src/mpc-hc/MainFrm.cpp:                SetThreadExecutionState(ES_CONTINUOUS);
 6 ./src/mpc-hc/MainFrm.cpp:        SetThreadExecutionState(iState == PS_PLAY ? ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED : ES_CONTINUOUS);
 7 ./src/mpc-hc/MainFrm.cpp:        SetThreadExecutionState(iState == PS_PLAY ? ES_CONTINUOUS | ES_SYSTEM_REQUIRED : ES_CONTINUOUS);
 8 ./src/thirdparty/unrar/system.cpp:  SetThreadExecutionState(ES_SYSTEM_REQUIRED);
 9 ./src/thirdparty/VirtualDub/h/vd2/system/w32assist.h:EXECUTION_STATE VDSetThreadExecutionStateW32(EXECUTION_STATE esFlags);
10 ./src/thirdparty/VirtualDub/system/source/w32assist.cpp:EXECUTION_STATE VDSetThreadExecutionStateW32(EXECUTION_STATE esFlags) {
11 ./src/thirdparty/VirtualDub/system/source/w32assist.cpp:	// SetThreadExecutionState(): requires Windows 98+/2000+.
12 ./src/thirdparty/VirtualDub/system/source/w32assist.cpp:	typedef EXECUTION_STATE (WINAPI *tSetThreadExecutionState)(EXECUTION_STATE);
13 ./src/thirdparty/VirtualDub/system/source/w32assist.cpp:	static tSetThreadExecutionState pFunc = (tSetThreadExecutionState)GetProcAddress(GetModuleHandle("kernel32"), "SetThreadExecutionState");

あぁほんとだ、これでやってんだ。なるほど。

てわけで:

heywin_dontsleep.py (version 2)
 1 # -*- coding: utf-8 -*-
 2 import time
 3 import ctypes
 4 from datetime import datetime
 5 
 6 
 7 _ES_CONTINUOUS = 0x80000000
 8 _ES_SYSTEM_REQUIRED = 0x00000001
 9 _ES_DISPLAY_REQUIRED = 0x00000002
10 
11 
12 if __name__ == '__main__':
13     print(" " * 10, datetime.now())
14     ctypes.windll.kernel32.SetThreadExecutionState(
15         _ES_CONTINUOUS | _ES_DISPLAY_REQUIRED | _ES_SYSTEM_REQUIRED)
16     try:
17         while True:
18             print(" " * 10, datetime.now(), end="\r", flush=True)
19             time.sleep(1)
20     except KeyboardInterrupt:
21         print("\n'd mornin...")
22         #ctypes.windll.kernel32.SetThreadExecutionState(_ES_CONTINUOUS)

うん、良いような気がする。40分くらい放置も、ディスプレイオフにはならなかったっぽい。

「通知に反応するやつ」タイプのはどうしようか、忘れようか。どっちみちそのアプローチは python からはやりにくいだろうしな、そうね、忘れよう。


一応念の為に注意しておきたい。「出来るから」という理由でやらないこと。マイクロソフトもドキュメントで結構都度都度注意してはいるんだけど、「PC の前に座って作業しているユーザ」の操作に割り込みうるタイプの制御は、「少なくとも下手するとユーザの反感を買う」という想像力を働かせること。ちゃんと自分の中で正当な理由付けをし、必要であればユーザにちゃんと説明出来るようにすること。

あと「みながそうしてるから」というタイプの安直もよく見かけるので注意。こういうので一番よくみたのが「二重起動防止」で、どういうわけだか「これが Windows アプリケーションのマナーである」かのような風潮が見られたことがあった。たぶん初心者向けの書籍とかで「テストに出るよっ!」と色んな場所で何度も何度も紹介された弊害なんだろうなぁと思う。「必ずそうしなければならないものだ、世界はそうなってる」として安直に受け容れないこと。つまり今回の例で言うと、「映像を扱うソフトウェアはディスプレイオフと真っ向から戦うべきである」と思い込んだりしないこと、てこと。実際 MPC-HC のコードをみても、「再生中ならば」という条件付きで ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED してるのがわかるだろう。

なんでこんなことを言わなければいけないかと言うと、こういう「簡単に出来ること」を紹介してしまうと、なぜだか猿のように「無限に真似し続ける」タイプの人って、必ず一定数いるからなの。実際 SetForegroundWindow にまつわる混乱てのも、おおむねそういうメカニズムで起こったものである。

繰り返す。「出来るから」は、それをすることの理由ではないし、そうしてはならない。


ところで、かなり本題と関係ない蛇足なんだけれど、「MPC-HC」って、オリジナルはもう開発がストップしてて、上のリンクの github も「アーカイブ」状態になってるわけね。だから今後どうしたもんかと最近ずっと思ってたんだけど、ふと「本家ではないやつが検索トップに」なってることに気付いた。あら? アクティブだねこれ。数日前の更新もある…。

本気の後継になってるのかな? 少なくともスターの数などなど、かなり活発な模様。期待…していいんだろうか。様子をみておこう。