最近、私のつくるソフトはスクリーンセーバが多いですが、 皆さんの中にはスクリーンセーバってどうやって作るんだろうと思われているかたも結構多いのではないでしょうか。 Visual C++ には Screen Saver Library というのが付属していてこれを使用すれば、セーバーのビジュアル部分だけを記述すればスクリーンセーバが完成するということになっています。
ところが私のスクリーンセーバは DirectX や Direct3D を使っているので中身のよく分からないこのライブラリと一緒に使うことができません。 そのため、トライアンドエラーで調査した結果以下のようにすればいいということを掴みました。(もう何年も前ですが..)
VB でも C でも C++/MFC でも何でもいいからスクリーンセーバにしたい EXE をまず作成する。
拡張子を SCR に変更して Windows ディレクトリにおけばスクリーンセーバとして認識されます。 ただし、起動時のコマンドラインを処理できるようにしておく。 「コントロールパネルのスクリーンセーバの窓に表示する場合」、「動作設定ダイアログを表示する場合」、 「スクリーンセーバが起動される場合」、「パスワードを変更する場合」のそれぞれに異なった起動オプションが引き渡されます。 以下にそのコマンドラインについて説明します。
コマンドライン /c の場合は動作設定ダイアログボックスを出力するようにコーディングする。
コマンドライン /s の場合はスクリーンセーバを起動するようにコーディングする。
コマンドライン /p XXXX の場合はコントロールパネルのスクリーンセーバの窓に表示することを要求されています。
XXXX が表示要求されているウインドウハンドルが 10進数で引き渡されます。 XXXX の WS_CHILD としてウインドウを作成し、DirectXやDirect3Dの場合は Window モードで表示してやればいいでしょう。 面倒な場合は省略したり、スクリーンセーバの動きとはまったく関係ない静止画を表示するようにしたスクリーンセーバも見受けられます。 拙作 Calendar SS では省略してしまいました。(その時は /p が何を意味しているかわかっていなかった。)
/c や /s はスクリーンセーバライブラリ(VisualC++付属、ただし全部英語)に説明があったのですが /p については説明がなかったように思います。 調査している時に /p 1234 の 1234 の意味がわからず、苦慮しているとナワテ君が「親ウインドウのハンドルじゃない?」と言ってくれました。 早速スパイで調査してみると、まさしくそうでした。 最近になってそのことを聞いてみると、当のナワテ君はそういう発言をしたことをまったく覚えていないそうです。(^^;
たまたま、何かの雑誌のCD-ROMに入っていたひらぽんさんの「Simple Screen Saver ver 1.12」 スクリーンセーバのドキュメントを読んでいると、そのことに触れてありました。 「同じことで悩んでいるひともいるんだな。」と思って読んでみると「(情報提供 Languor(河合)さん)」と書いてありました。 さらに、同じCD-ROMに河合さんの「100万回のKISS 1.05」というスクリーンセーバも入っていたので、こちらもドキュメントをよんでみると「"/p ???"の謎を教えてくれた宮崎さんありがとう。」という一文が入っていました。
ん?そういえば、昔、オンラインソフトを作成されている方にこの「/p」の件について質問されたことがあったなとメールのログを読み返すと、河合さんからのものでした。 おおっ、情報は駆けめぐっている(笑)んだな。と思いました。
コマンドライン /a XXXX の場合はパスワード設定を行います。
XXXX は同様に親ウインドウのハンドルを10進であらわしたものです。 一番下にこれを処理する C++ クラスのソースリストを書いておきましたので参照してください。
/a がパスワードの変更を要求しているということはすぐにわかったし、レジストリのどの位置にそれが設定されるかも分かりました。 ただ、そのままレジストリに書いてしまうと全然パスワードの意味がありません。 当然、暗号化した形で格納されるわけです。 ここで、ハタと困ってしまいました。 私のスクリーンセーバは全部自前でやっている関係上、その暗号化、復号化も自前でやる必要があります。 はっきりいってこれはプログラミングとは別の次元の問題ですよね。 暗号化、復号化のロジックをどうやって実現すればいいんだろうと。 パスワードを一文字、一文字変えながら、レジストリに書き込まれた値と比較して法則を推測しようとしばらくやってみましたが、すぐにはわかりませんでした。 それより何より、何か「違法なこと」をやっている(笑)気がして途中でやめてしまいました。
で、どうしたかというとCalendar SS では独自のパスワード機能を実装して誤魔化していました。 (パスワード機能がどうしても必要!というユーザの方もいらっしゃったので...) また、それ以外のスクリーンセーバではパスワード機能を最近まで、省略していました。
最近になって、「やっぱりパスワード機能が要る。」というユーザからのメールを複数いただくようになり、いっちょう調べてみるかということでインターネットを検索しました。 ありました。サッポロワークスのホームページにはこの件に関して詳しく記述されています。 一番下に記述したソースリストはここで得た情報がベースになっています。
WindowsNT4.0の場合はそもそもパスワード機能が Windows95 の場合と異なっていますので、 同一のソースリストで Windows95とWindowsNT4.0に対応しようとした場合にはそれを考慮する必要があります。 つまりWindowsNT4.0では CScrPass クラスのパスワード機能をスルーさせる必要がある。 CScrPass::IsPasswordValid() でパスワード機能がオンになっているかどうかをチェックした時に "ScreenSaveUsePassword" というエントリがないはずですから FALSE が返り、そのままでOKなはずです。 万一、行儀の悪いスクリーンセーバアプリがこのエントリを "1" にしたとしても CScrPass::VerifyPassword(HWND hwnd) で password.cpl をロードできないときに TRUE を返して「パスワードが一致した」ことにしているので問題ありません。 (そこまで考えなくてもいいような気もするが....)
二重起動の防止。
スクリーンセーバ起動のタイミングになると /s でスクリーンセーバが起動されます。 たとえば起動時間を3分にしておくと、3分ごとに /s で起動されてしまうため、スクリーンセーバが複数起動されてしまいます。 これを避けるために、二重起動の防止ロジックを組み込んでおく必要があります。 私の場合は簡単なので ::FindWindow()でチェックしています。 一般的にアプリケーションの二重起動を防ぐテクニックはたくさん存在するようです。 Niftyのどっかのフォーラム(すいません...)でも、いろんな手法が話し合われていました。 FWINDEV あたり?かも知れません。
エクスプローラでスクリーンセーバのファイルを直接右クリックすると「テスト」「設定」「インストール」というのが出てきます。
「テスト」の場合は /s で起動されるので問題ありませんが、「設定」の場合はなぜか /c で起動されず、ノーオプションで起動されます。 そのため、これに対応するためにノーオプションで起動された場合には設定ダイアログを出すようにコーディングしておけばいいと思います。
スクリーンセーバの終了チェック
スクリーンセーバを終了チェックするには WM_MOUSEMOVE WM_KEYDOWN WM_SYSKEYDOWN WM_LBUTTONDOWN WM_RBUTTONDOWN あたりをトリガにすればいいでしょう。 MFCで作る場合は CWnd::OnMove() CWnd::OnKeyDown() CWnd::OnSysKeyDown() CWnd::OnRButtonDown()をオーバライドしてください。 Screen Saver Libraryにはちゃんとどれとどれを処理しろって書いてあったと思いますが忘れました。
まずこのメッセージが来た場合 CScrPass::IsPasswordValid() でパスワード機能がオンになっているかどうかをチェックします。 オフの場合はそのまま終了処理に入ります。 自分の終了処理を実行して、DestroyWindow() または CWnd::OnClose()を呼びだします。 オンになっている場合は、CScrPass::VerifyPassword(HWND hwnd) でパスワードの入力を求めるダイアログボックスを出力します。 返り値によって、継続するか終了するかを決定します。
DirectX/Direct3D でスクリーンセーバを作る場合の注意点(WM_MOUSEMOVE)
DirectX/Direct3Dでスクリーンセーバを作る場合には最初にフルスクリーンモードで解像度を変更するためでしょうか、 起動直後に WM_MOUSEMOVE または CWnd::Move() が呼び出されてしまいます。 そのため、いきなり終了処理に入ってしまいます。 そこで私の場合、起動時のマウスポジションを保存しておき WM_MOUSEMOVE または CWnd::Move() が来た場合に保存した値と異なる場合にのみ終了処理に入るようにしています。
DirectX/Direct3D でスクリーンセーバを作る場合の注意点(WindowsNT4.0)
WindowsNT4.0ではなぜかスクリーンセーバ起動時に WM_CLOSE が送られてきてしまいます。 そのため、通常の処理ではスクリーンセーバがすぐに終了してしまいます。 ただ、コントロールパネルのプレビューからだと、これはなぜか送られてきません。 そのため、プレビューでは動くが本番では動かないという妙なことになってしまいます。 私のスクリーンセーバの場合はこれを避けるために、WM_CLOSE メッセージで return FALSE しています。
ご存知だとはおもいますが.. WM_CLOSE というメッセージはアプリケーションに終了処理のラストチャンスを与えるメッセージです。 return TRUE すると、そのままアプリが終了しますが、return FALSE するとそのまま継続します。 DefWindowProc()では return TRUE された場合に、DestroyWindow()でアプリを終了しています。
こうすることによって一応 WindowsNT4.0 での起動直後、即終了という事態を避けることができます。
DirectX/Direct3D でスクリーンセーバを作る場合の注意点(併用アプリ等)
併用するアプリによっては DirectX/Direct3D のスクリーンセーバがうまく動かない場合があります。 私が確認している具体的な例としては...
Microsoft Visual Basic 動作中にはスクリーンセーバが起動できない。
ULEAD社 iPhote Express 動作中にはスクリーンセーバが起動できない。
Microsoft Office Toolbar 動作中にはスクリーンセーバが起動できない。
などがあります。理由はよく分からないのですがこれらのアプリが動作中には DirectX/Direct3Dのフルスクリーンでの起動ができません。 スクリーンセーバに限ったことではなく、バーチャファイターなどのゲームなどでもうまく起動できません。(この場合はアイコン化されている)
また、解像度を変えることによってそれを検知して勝手なことをするアプリもあります。
Microsoft Visual C++5.0 をフルスクリーンで使用している時にスクリーンセーバを起動します。 たとえば 1024x768 の解像度で Windows を使用中に 640x480に変更すると VC++ も一緒になって 640x480 になってしまいます。 (そういう風にコーディングされているのだと思う。) スクリーンセーバを終了するとデスクトップ自体は 1024x768 に戻りますが VC++ は 680x480のままです。 (ウインドウサイズを変えるんなら大きくなったときも、戻して欲しいですね。)
Windowsのデスクトップアイコン
同じようなことなんですが、デスクトップアイコンを自動配列にしてない場合に、たとえば右側にアイコンを配置していたとすると スクリーンセーバの解像度変更によってその中におさまらないアイコンは、変更した解像度内に収まろうとして移動してきます。 スクリーンセーバが終了した時には元の解像度になっているわけですが、 その時点ではアイコンは解像度内に収まっているため、移動したままになってしまいます。
いまのところ、これらを回避する方法は発見していません。
-----------------------------------------------------------------------------
// ScrPass.h: CScrPass クラスのインターフェイス
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_SCRPASS_H__81CC1884_7D05_11D1_9426_002035E64AFD__INCLUDED_)
#define AFX_SCRPASS_H__81CC1884_7D05_11D1_9426_002035E64AFD__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class CScrPass
{
public:
BOOL IsPasswordValid();
BOOL VerifyPassword(HWND);
void ChangePassword(HWND);
CScrPass();
virtual ~CScrPass();
};
#endif // !defined(AFX_SCRPASS_H__81CC1884_7D05_11D1_9426_002035E64AFD__INCLUDED_)
-----------------------------------------------------------------------------
// ScrPass.cpp: CScrPass クラスのインプリメンテーション
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "ScrPass.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
typedef BOOL (WINAPI *SCRPASSINPUT)(HWND);
typedef void (WINAPI *SCRPASSCHANGE)(LPSTR, HWND, UINT, UINT);
//////////////////////////////////////////////////////////////////////
// 構築/消滅
//////////////////////////////////////////////////////////////////////
CScrPass::CScrPass()
{
}
CScrPass::~CScrPass()
{
}
void CScrPass::ChangePassword(HWND hwnd)
{
HINSTANCE hinstance = ::LoadLibrary("mpr.dll");
if(!hinstance)
return;
SCRPASSCHANGE PassChange = (SCRPASSCHANGE)GetProcAddress(hinstance, "PwdChangePasswordA");
if(!PassChange)
{
FreeLibrary(hinstance);
return;
}
PassChange("SCRSAVE", hwnd, 0, 0);
FreeLibrary(hinstance);
}
BOOL CScrPass::VerifyPassword(HWND hwnd)
{
HINSTANCE hinstance = ::LoadLibrary("password.cpl");
if(!hinstance)
{
// DLL がロードできない場合は
// 一致したことにする。
// WinNT4.0 には password.cpl はない
return TRUE;
}
SCRPASSINPUT PassInput = (SCRPASSINPUT)GetProcAddress(hinstance, "VerifyScreenSavePwd");
if(!PassInput)
{
// 関数エントリが見つからない場合は
// 一致したことにする。
FreeLibrary(hinstance);
return TRUE;
}
BOOL b = PassInput(hwnd);
FreeLibrary(hinstance);
return b;
}
BOOL CScrPass::IsPasswordValid()
{
DWORD Type = REG_DWORD;
DWORD Size = sizeof(DWORD);
HKEY hKeyResult = 0;
BOOL PasswordFlg = 0;
if(ERROR_SUCCESS==RegOpenKeyEx(
HKEY_CURRENT_USER,
"Control Panel\\desktop", 0, KEY_READ, &hKeyResult))
{
RegQueryValueEx(hKeyResult,
"ScreenSaveUsePassword", 0, &Type, (unsigned char *)&PasswordFlg, &Size);
RegCloseKey(hKeyResult);
}
return PasswordFlg;
}
![]() |
Back Home