ゲームプレイ中のスクリーンセーバやディスプレイ休止を回避する方法

主にゲームパッドのみでプレイできる PC ゲームでは,ゲームプレイ中にスクリーンセーバが起動したりディスプレイの電源が切れたりして「むがー!」ということがあります.

これはプログラム側の対応である程度防止できますので,その辺のメモでも.
もっとも保守的で無難な方法は,ウィンドウメッセージのトラップです.WM_SYSCOMMAND メッセージのうち SC_MONITORPOWER と SC_SCREENSAVE を DefWindowProc に渡さないようにします.

Once a screen saver is chosen, Windows monitors keystrokes and mouse movements and then starts the screen saver after a period of inactivity. However, Windows does not start the screen saver if any of the following conditions exist:

  • The active application is not a Windows-based application.
  • A computer-based training (CBT) window is present.
  • The active application receives the WM_SYSCOMMAND message with the wParam parameter set to the SC_SCREENSAVE value, but it does not pass the message to the DefWindowProc function.

Windows Vista and later: If password protection is enabled by policy, the screen saver is started regardless of what an application does with the SC_SCREENSAVE notification.

case WM_SYSCOMMAND:
    switch( ( wParam & 0xFFF0 ) )
    {
        case SC_MONITORPOWER:
        case SC_SCREENSAVE:
            return 0; // DefWindowProc は呼ばない
            break;
        
        ...以下略

これだけでかなりのケースは対応できます.以下各論.

ゲームパッドの操作でディスプレイ休止は阻止できることもある

これは手元の Windows Vista SP1 (x86) + Xbox 360 パッド (純正ドライバ) で確認したケースですが,Xbox 360 パッドの操作中はディスプレイの休止は発生せず,またディスプレイ休止状態に Xbox 360 パッドを操作するとディスプレイ休止状態から回復することを確認しています.
ただややこしいことに,スクリーンセーバには効果がないようでした.

SetThreadExecutionState API ではスクリーンセーバを阻止中断できない

This function does not stop the screen saver from executing either.

(2009年10月12日追記)

  • 一度実行してしまったスクリーンセーバを SetThreadExecutionState で中断することはできない.
  • スクリーンセーバ起動防止に使うためには,SetThreadExecutionState(ES_DISPLAY_REQUIRED) のように ES_DISPLAY_REQUIRED 単独で指定する必要がある.ES_DISPLAY_REQUIRED | ES_CONTINUOUS 等は効果がない.

参考:

WM_SYSCOMMAND の wParam の扱い

本来 0xfff0 と & を取るのが正しい……はず.

In WM_SYSCOMMAND messages, the four low-order bits of the wParam parameter are used internally by the system. To obtain the correct result when testing the value of wParam, an application must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator.

でも KB とか見ると結構適当.

2. あるアプリケーションが起動中にスクリーンセーバを起動させない方法

スクリーンセーバの機動を止めるには WM_SYSCOMMAND の wParam == SC_SCREENSAVE の時 return 1 を返してください。

case WM_SYSCOMMAND:
    if ( wParam == SC_SCREENSAVE )
    {
        return 1;
    }
    return (DefWindowProc(hWnd, message, wParam, lParam));

何も考えずに wParam == SC_SCREENSAVE とかやってますけどいいんすかねこれ.みんなコピペ大好きですよ?

WM_SYSCOMMAND トラップ時の返値は 0 にすべきか 1 にすべきか

2. あるアプリケーションが起動中にスクリーンセーバを起動させない方法

スクリーンセーバの機動を止めるには WM_SYSCOMMAND の wParam == SC_SCREENSAVE の時 return 1 を返してください。

case WM_SYSCOMMAND:
    if ( wParam == SC_SCREENSAVE )
    {
        return 1;
    }
    return (DefWindowProc(hWnd, message, wParam, lParam));

ここだけ読むと 1 を返すことが重要なように読めますが……

Return Value

An application should return zero if it processes this message.

Once a screen saver is chosen, Windows monitors keystrokes and mouse movements and then starts the screen saver after a period of inactivity. However, Windows does not start the screen saver if any of the following conditions exist:

  • The active application is not a Windows-based application.
  • A computer-based training (CBT) window is present.
  • The active application receives the WM_SYSCOMMAND message with the wParam parameter set to the SC_SCREENSAVE value, but it does not pass the message to the DefWindowProc function.

戻り値としては 0 の方が適切だし,スクリーンセーバ避けには DefWindowProc を呼ばないことの方が重要にも読めます.一方で,昔の Windows だと違った系の可能性も.

Windows Vista 以降,スクリーンセーバのパスワードロックが有効だと,メッセージトラップでスクリーンセーバを阻止できない

Windows Vista and later: If password protection is enabled by policy, the screen saver is started regardless of what an application does with the SC_SCREENSAVE notification.


Windows Vista 以降,ここにチェックが入っていると,SC_SCREENSAVE トラップを行ってもスクリーンセーバは起動します.
これを気にするのであれば,ダミーのキーイベントかマウスイベントを発生させることを検討した方が良いのかもしれず.