D言語で作るスクリーンセーバー

# はじめに

D言語で、スクリーンセーバーを作成することができます。
以下の環境があれば、とても簡単です。

# 環境

* Windows 10
* [Visual C++ ビルドツール 2019](https://visualstudio.microsoft.com/ja/downloads/)　→　[インストール手順](https://www.kkaneko.jp/tools/win/buildtool.html)
* [dmd](https://dlang.org/download.html)

# ひな形（その１）

まずは、ひな形のスクリーンセーバーを作成します。
スクリーンセーバー実行時に黒い画面を表示するだけです。設定画面はありません。

```d:base1.d
import core.sys.windows.windows;

pragma(lib, "ScrnSave.Lib");
pragma(lib, "Gdi32.lib");
pragma(lib, "Kernel32.lib");
pragma(lib, "User32.lib");
pragma(lib, "Advapi32.lib");
pragma(lib, "Comctl32.lib");
pragma(lib, "libcmt.lib");

extern (Windows) nothrow @nogc {
    LRESULT DefScreenSaverProc(HWND, UINT, WPARAM, LPARAM);
}

extern(Windows) nothrow
BOOL RegisterDialogClasses(HANDLE hInst)
{
	return ( true );
}

extern(Windows) nothrow
BOOL ScreenSaverConfigureDialog(HWND hDlg, UINT message, UINT wParam,LONG lParam)
{
	return ( false );
}

extern(Windows) nothrow
LRESULT ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch ( message ){
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		default:
			break;
	}
	return ( DefScreenSaverProc(hWnd, message, wParam, lParam) );
}
```

# ソースコード補足説明１

`pragma(lib, "ScrnSave.Lib")`で、スクリーンセーバー作成に必要なライブラリを指定します。
`pragma(lib, "Gdi32.lib")`以下は、`ScrnSave.Lib`がリンク時に必要とするライブラリです。

`ScrnSave.Lib` を使う場合、以下の３つの関数を開発者が作成する必要があります。

## ScreenSaverProc
[ScreenSaverProc](https://docs.microsoft.com/en-us/windows/win32/api/scrnsave/nf-scrnsave-screensaverproc) は、スクリーンセーバー実行時やプレビュー時に呼び出される関数で、描画などの必要なイベントの処理を記述します。
ひな形では、スクリーンセーバー終了時に発生する`WM_DESTROY`イベントで`PostQuitMessage`を、それ以外のイベントでは`DefScreenSaverProc`を呼び出すだけです。
[DefScreenSaverProc](https://docs.microsoft.com/ja-JP/windows/win32/api/scrnsave/nf-scrnsave-defscreensaverproc)は、`core.sys.windows.windows`の`import`では定義されないため、以下を記述します。

```d:
extern (Windows) nothrow @nogc {
    LRESULT DefScreenSaverProc(HWND, UINT, WPARAM, LPARAM);
}
```

## ScreenSaverConfigureDialog
[ScreenSaverConfigureDialog](https://docs.microsoft.com/ja-jp/windows/win32/api/scrnsave/nf-scrnsave-screensaverconfiguredialog) は、スクリーンセーバー設定時に表示されるダイアログボックスを処理する関数です。
ひな形では、ダイアログボックスを作成しないので、`false`を返すだけです。

## RegisterDialogClasses
[RegisterDialogClasses](https://docs.microsoft.com/ja-jp/windows/win32/api/scrnsave/nf-scrnsave-registerdialogclasses) は、スクリーンセーバー設定用ダイアログボックスの作成前に呼び出される関数です。
開発者が作成したコントロールをダイアログボックスで使う場合に、必要なコントロールの登録処理を記述します。
ひな形では、`true`を返すだけです。

# コンパイル、実行
```text:
D:\Dev> dmd -m64 -L/SUBSYSTEM:WINDOWS -of=base.scr base1.d
D:\Dev> copy base.scr C:\Windows\System32
```
作成された`base.scr`を`C:\Windows\System32`にコピーします。管理者権限が必要です。
スクリーンセーバーの設定画面で`base`が選択可能になります。
設定画面はありませんが、プレビューは実行可能です。

![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/b6017c85-d0b0-ed5e-4059-923e51799e6b.png)

# ひな形（その２）

スクリーンセーバーの設定画面を実装します。

```d:base2.d
import core.sys.windows.windows;

pragma(lib, "ScrnSave.Lib");
pragma(lib, "Gdi32.lib");
pragma(lib, "Kernel32.lib");
pragma(lib, "User32.lib");
pragma(lib, "Advapi32.lib");
pragma(lib, "Comctl32.lib");
pragma(lib, "libcmt.lib");

extern (Windows) nothrow @nogc {
    LRESULT DefScreenSaverProc(HWND, UINT, WPARAM, LPARAM);
}

extern(Windows) nothrow
BOOL RegisterDialogClasses(HANDLE hInst)
{
	return ( true );
}

extern(Windows) nothrow
BOOL ScreenSaverConfigureDialog(HWND hDlg, UINT message, UINT wParam,LONG lParam)
{
	switch ( message ){
		case WM_INITDIALOG:
			return ( true );
		case WM_COMMAND:
			switch ( LOWORD(wParam) ){
				case IDOK:
				case IDCANCEL:
					EndDialog(hDlg,wParam);
					return ( true );
				default:
					break;
			}
			break;
		default:
			break;
	}
	return ( false );
}

extern(Windows) nothrow
LRESULT ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch ( message ){
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		default:
			break;
	}
	return ( DefScreenSaverProc(hWnd, message, wParam, lParam) );
}
```

```text:basedlg.rc
#include <windows.h>
#include <scrnsave.h>

DLG_SCRNSAVECONFIGURE DIALOG DISCARDABLE  50,50,200,150
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "スクリーンセーバー設定画面"
FONT 10, "system"
BEGIN
    CTEXT			"設定項目",-1,10,15,180,10
    DEFPUSHBUTTON	"OK",IDOK,40,130,50,14,WS_GROUP
    PUSHBUTTON		"ｷｬﾝｾﾙ",IDCANCEL,110,130,50,14,WS_GROUP
END
```

# ソースコード補足説明２

スクリーンセーバー設定画面では、`DLG_SCRNSAVECONFIGURE`のリソースが使用されるため、
リソースファイル`basedlg.rc`を用意しました。

また、[ScreenSaverConfigureDialog](https://docs.microsoft.com/ja-jp/windows/win32/api/scrnsave/nf-scrnsave-screensaverconfiguredialog)にダイアログボックス内のボタンを押した場合の処理を記述しました。
といっても、`EndDialog`を呼び出してダイアログボックスを終了させるだけです。

# コンパイル、実行
```text:
d:\Dev> rc basedlg.rc
Microsoft (R) Windows (R) Resource Compiler Version 10.0.10011.16384
Copyright (C) Microsoft Corporation.  All rights reserved.

d:\Dev> dmd -m64 -L/SUBSYSTEM:WINDOWS -of=base.scr base2.d basedlg.res

D:\Dev> copy base.scr C:\Windows\System32
C:\Windows\System32\base.scr を上書きしますか? (Yes/No/All): y
        1 個のファイルをコピーしました。
```
「VS2019用 x64 Native Tools コマンドプロンプト」を[起動します](https://docs.microsoft.com/ja-jp/cpp/build/building-on-the-command-line?view=vs-2019)。
作成された`base.scr`を`C:\Windows\System32`にコピーします。管理者権限が必要です。
スクリーンセーバーの設定画面で`base`が選択可能になります。
「設定(T)...」ボタンを押すと作成した設定画面が開きます。
![b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/1937702d-a659-6799-6640-ffc0d76aa479.png)

# 参考情報
[ScreenSaverProc](https://docs.microsoft.com/en-us/windows/win32/api/scrnsave/nf-scrnsave-screensaverproc) 
[ScreenSaverConfigureDialog](https://docs.microsoft.com/ja-jp/windows/win32/api/scrnsave/nf-scrnsave-screensaverconfiguredialog)
[RegisterDialogClasses](https://docs.microsoft.com/ja-jp/windows/win32/api/scrnsave/nf-scrnsave-registerdialogclasses) 

[DefScreenSaverProc](https://docs.microsoft.com/ja-JP/windows/win32/api/scrnsave/nf-scrnsave-defscreensaverproc)