Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2018年12月17日] [更新 2018年12月17日] Delphi 一般・その他
   
起動中のスクリーンセーバーの停止と起動抑止
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 1050]







スクリーンセーバーの停止とスクリーンセーバの起動抑止


起動した,あるいは起動中のスクリーンセーバを停止するサンプルと,スクリーンセーバの起動を抑止するサンプルです.ネット上で,スクリーンセーバを停止させる方法についての記事や質問を見かけたのでテストしてみました.

SystemParametersInfo 関数の SPI_SETSCREENSAVEACTIVE パラメータを使用すれば,自分のアプリ実行中にスクリーンセーバの起動を無効にしたり有効にしたりできます.本ンページのサンプルは,スクリーンセーバの起動を有効にした状態でのテストです.

スクリーンセーバが起動中にマウスの操作やキー操作を行うと,スクリーンセーバが停止すると同時に,スクリーンセーバ起動までの時間,モニタの電源が切断されるまでの時間,システムがスリープ開始するまでの時間がリセットされます.



[備考 1]
[スクリーンセーバーの設定] ダイアログで [再開時にログオン画面に戻る] の設定が有効になっている場合は,本ページのサンプルでスクリーンセーバを停止させることはできません.
[再開時にログオン画面に戻る] の設定が有効になっていると [ロック画面] が表示されます.
アプリケーションが起動中のデスクトップを超えて,この [ロック画面] にマウス操作やキー操作あるいはメッセージの送信等はできません.したがって,[ロック画面] の起動中にアプリケーション側からスクリーンセーバを停止することはできません.


[備考 2]
Windows 10 のスク リーンセーバをプログラムから停止させることはできません.
Windows 10 では,スクリーンセーバの設定は [ロック画面] の設定の一部になっています.

Windows 10 でスクリーンセーバのタイムアウト時間が経過するとロック画面が起動します.したがって,アプリケーション側からスクリーンセーバを停止することはできません.
Windows 8 / 8.1 のスクリーンセーバの仕様については未確認です.


[備考 3]
起動したスクリーンセーバを停止させる場合,コンピュータの画面を眺め続けていない限り,実際にスクリーンセーバが起動したかどうかは確認することはできません.したがって,大抵の状況下ではスク リーンセーバを停止する機能がなくても,スクリーンセーバの起動抑止あるいは起動を無効にすることで十分であると思われます.
スクリーンセーバの起動抑止と起動の無効化との違いは,モニタの電源切断までの時間をリセットするかしないかの違いです.





01_起動中のスクリーンセーバーを停止するサンプル


EXE を実行してボタンをクリックします.しばらくするとスクリーンセーバが起動します.スクリーンセーバが起動してから一定時間後にスクリーンセーバを停止します.停止後にまたタイムアウトの時間が経つとスクリーンセーバが起動します.

本サンプルは,モニタの電源が切断されるまでの時間,システムがスリープ開始するまでの時間をリセットしません.純粋にスクリーンセーバだけを停止して,スクリーンセーバが起動するまでの時間だけをリセットできます.したがって,このサンプルでスクリーンセーバを停止しても,モニタ切断までのタイムアウト時間が経過するとモニタの電源が OFF になります.



[備考]
Windows 10 のスク リーンセーバは,本サンプルで停止させることはできません.
Windows 8 / 8.1 のスクリーンセーバについては未確認です.



図1
設計時の画面
  • TTimer を使用して,一定時間間隔でスクリーンセーバの起動を監視
図2
起動中のスクリーンセーバを停止

リスト1
起動中のスクリーンセーバを検出して停止
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private 宣言 }
    FEnableSaver   : Boolean;
    FSaverWaitTime : Integer;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  プログラムを起動したら現在の設定を取得しておく
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  // スクリーンセーバが起動してから停止するまでの時間(ms)
  Timer1.Enabled := False;
  Timer1.Interval := 3000;

  // スクリーンセーバの起動の可否を退避
  SystemParametersInfo(SPI_GETSCREENSAVEACTIVE,
                       0,
                       @FEnableSaver,
                       0);
  // スクリーンセーバ起動までの時間の設定値を退避
  SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT,
                       0,
                       @FSaverWaitTime,
                       0);
end;

//=============================================================================
//  フォーム破棄時の処理
//  設定を元に戻す
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  // スクリーンセーバの起動の可否
  SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,
                       Ord(FEnableSaver),
                       nil,
                       SPIF_SENDCHANGE);

  // スクリーンセーバ起動までの時間の設定値
  SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT,
                       FSaverWaitTime,
                       nil,
                       SPIF_SENDCHANGE);
end;

//=============================================================================
//  スクリーセーバの起動を有効にする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LWaitTime : Integer;
begin
  Screen.Cursor := crHourGlass;

  // スクリーンセーバの起動を有効にする
  // 起動しないとテストにならない
  SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,
                       Ord(True),
                       nil,
                       SPIF_SENDCHANGE);

  // タイムアウトの時間(スクリーンセーバが起動するまでの時間)をセット
  // テストなので時間を短く設定 (単位は秒)
  LWaitTime := 10;
  SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT,
                       LWaitTime,
                       nil,
                       SPIF_SENDCHANGE);
  Screen.Cursor := crDefault;

  Memo1.Lines.Clear;
  Memo1.Lines.Add('操作開始');

  Timer1.Enabled := True;
end;

//=============================================================================
//  Timer1のOnTimerイベント処理
//  スクリーンセーバが起動してから一定時間後にこの処理を実行
//  スクリーンセーバが起動していたら終了させる
//=============================================================================
procedure TForm1.Timer1Timer(Sender: TObject);
var
  LRunSaver : Integer;
begin
  // スクリーンセーバが起動中かを調べる
  SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, LRunSaver, 0);

  // 起動中ならば停止
  if LRunSaver <> 0 then begin
    Memo1.Lines.Add('スクリーンセーバ起動');
    // このフォームを最前面にすればスクリーンセーバは停止する
    SetForegroundWindow(Handle);
    Memo1.Lines.Add('スクリーンセーバ停止');

    // アクティブにするウインドウは他のウィンドウでもよい
    // 例えば以下でもスクリーンセーバを停止できる
    // SetForegroundWindow(GetDesktopWindow);
  end;
end;

end.


モニタの電源の状態を ON にすることでもスクリーンセーバを停止することができます.



リスト2
procedure TForm1.Timer1Timer(Sender: TObject);
var
  LRunSaver : Integer;
begin
  // スクリーンセーバが起動中かを調べる
  SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, LRunSaver, 0);

  // 起動中ならば停止
  if LRunSaver <> 0 then begin
    // ディスプレイの状態を電源 ON に設定するとスクリーンセーバが終了する 
    PostMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, -1);
  end;
end;




02_スクリーンセーバーの起動抑止 - SetThreadExecutionState 関数


Windows API の SetThreadExecutionState 関数を使用すると,スクリーンセーバの起動を抑止することができます.その結果,スクリーンセーバは起動しなくなります.

本サンプルでは SetThreadExecutionState 関数を TTimer の OnTimer イベント内で定期的に実行することによりスクリーンセーバの起動を抑止します.

このサンプルのコードは,実際にはモニタの電源 OFF までの時間をリセットすることによりスクリーンセーバの起動を抑止します.リセットする時間間隔を,スクリーンセーバが起動するするまでの時間よりも短い時間に設定することでスクリーンセーバの起動を抑止しています.

このサンプルは,システムがスリープ状態になるまでの時間はリセットしません.したがつて,スリープ開始までの時間が経過すると,システムはスリープ状態になります.
マウス操作,あるいはキー操作でモニタの電源を ON にすると,スクリーンセーバが停止して,モニタの電源 OFF までの時間もスリープ開始までの時間もリセットされます.



[備考]
Delphi 10.2.3 には SetThreadExecutionState 関数が実装されています.使用する定数名の定義も必要ありません.



図3
スクリーンセーバーの起動抑止

リスト3
SetThreadExecutionState 関数を使用してスクリーンセーバーの起動を抑止
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Timer1: TTimer;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private 宣言 }
    FEnableSaver   : Boolean;
    FSaverWaitTime : Integer;
  public
    { Public 宣言 }
  end;

function SetThreadExecutionState(esFlags: DWORD): DWORD; stdcall external 'kernel32.dll';

var
  Form1: TForm1;

implementation

{$R *.dfm}

const
  ES_SYSTEM_REQUIRED  = $00000001;
  ES_DISPLAY_REQUIRED = $00000002;
  ES_CONTINUOUS       = $80000000;


//=============================================================================
//  フォーム生成時の処理
//  プログラムを起動したら現在の設定を取得しておく
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Timer1.Enabled := False;

  // スクリーンセーバの起動の可否を退避
  SystemParametersInfo(SPI_GETSCREENSAVEACTIVE,
                       0,
                       @FEnableSaver,
                       0);
  // スクリーンセーバ起動までの時間の設定値を退避
  SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT,
                       0,
                       @FSaverWaitTime,
                       0);
end;

//=============================================================================
//  フォーム破棄時の処理
//  設定を元に戻す
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  SetThreadExecutionState(ES_CONTINUOUS);

  // スクリーンセーバの起動の可否
  SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,
                       Ord(FEnableSaver),
                       nil,
                       SPIF_SENDCHANGE);

  // スクリーンセーバ起動までの時間の設定値
  SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT,
                       FSaverWaitTime,
                       nil,
                       SPIF_SENDCHANGE);
end;

//=============================================================================
//  スクリーセーバの起動を有効にする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LWaitTime : Integer;
begin
  Screen.Cursor := crHourGlass;

  // スクリーンセーバの起動を有効にする
  // 起動しないとテストにならない
  SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,
                       Ord(True),
                       nil,
                       SPIF_SENDCHANGE);

  // タイムアウトの時間(スクリーンセーバが起動するまでの時間)をセット
  // テストなので時間を短く設定(単位は秒)
  LWaitTime := 10;
  SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT,
                       LWaitTime,
                       nil,
                       SPIF_SENDCHANGE);
  Screen.Cursor := crDefault;

  // スクリーンセーバの起動を抑止する間隔(単位は ms)
  // スクリーンセーバのタイムアウトの時間よりも短くする
  Timer1.Interval := LWaitTime * 1000 div 2;
  Timer1.Enabled := True;
end;

//=============================================================================
//  Timer1のOnTimerイベント処理
//  一定間隔で SetThreadExecutionState を実行してスクリーンセーバの起動を抑止
//=============================================================================
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if not CheckBox1.Checked then begin
    SetThreadExecutionState(ES_DISPLAY_REQUIRED);
  end;
end;

end.


SetThreadExecutionState 関数はスクリーンセーバの起動を抑止するものです.以下のように,スクリーンセーバが起動した後に実行しても,スクリーンセーバを停止させることはできません.



リスト4
procedure TForm1.Timer1Timer(Sender: TObject);
var
  LRunSaver : Integer;
begin
  // スクリーンセーバが起動中かを調べる
  SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, LRunSaver, 0);

  // 起動中ならば実行
  if LRunSaver <> 0 then begin
     SetThreadExecutionState(ES_DISPLAY_REQUIRED);
  end;
end;




03_スクリーンセーバーの起動を検出


上のどちらのサンプルも TTimer コンポーネントを使用して,一定の時間間隔でスクリーンセーバの起動を監視しています.WM_SYSCOMMAND メッセージを受信処理すると,以下のようにスクリーンセーバの起動を検出することができます.
ただしこの方法は,メッセージを受け取る自分自身がアクティブになっている必要があります.自分自身がアクティブない時もメッセージを受信するにはグローバルフックにします.



図4
スクリーンセーバーの起動を検出

リスト5
WM_SYSCOMMAND メッセージでスクリーンセーバーの起動を検出
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
  protected
    procedure WmSysCommand(var msg: TWmSysCommand); message WM_SYSCOMMAND;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  WM_SYSCOMMAND メッセージによるスクリーンセーバの起動検出
//=============================================================================
procedure TForm1.WmSysCommand(var msg: TWmSysCommand);
begin
  // スクリーンセーバの起動を検出した場合
  if msg.CmdType = SC_SCREENSAVE then begin
    Memo1.Lines.Add('スクリーンセーバ起動');
    inherited;

    // inherited ではなく,以下のコードを実行するとスクリーンセーバを停止できる
    // msg.Result := 0;
  end else begin
    //通常通りの処理
    inherited;
  end;
end;

end.