一度起動したスクリーンセーバーを停止させる処理を何パターンか作ってみたのですが
Windows7までは動作するもののWindows10では機能しません。
なにか良い方法はないでしょうか?
開発環境はDelphi2007 や XE2です。
よろしくお願いします。
// シフトキーを押して離してみたがだめ SendInputで作ってもだめ
keybd_event(VK_SHIFT, 0, KEYEVENTF_EXTENDEDKEY, 0);
keybd_event(VK_SHIFT, 0, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0);
// マウスを動かしてみたがだめ
GetCursorPos(p);
SetCursorPos(p.X+100,p.Y+100);
SetCursorPos(p.X,p.Y);
// スクリーンセーバーを無効にしても設定は有効だがスクリーンセーバーは止まらない
SystemParametersInfo(SPI_GETSCREENSAVEACTIVE,0,@f,0);
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Ord(False),nil,SPIF_SENDCHANGE);
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Ord(f),nil,SPIF_SENDCHANGE);
// モニタの電源OFFからの復帰は正常に動作します。
SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, -1);
// スクリーンセーバーの起動は出来ますが、欲しいのはこの逆です。
SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_SCREENSAVE, 0);
XPのときは14年前に質問してました
https://www.petitmonte.com/bbs/answers?question_id=1280
あてずっぽうですが、
AttachThreadInput
https://msdn.microsoft.com/ja-jp/library/cc429027.aspx
で入力を奪ってからキーやマウスを操作してもダメですか?
procedure TForm1.WMSYSCOMMAND(var Message: TMessage);
begin
if Message.WParam <> SC_SCREENSAVE then
begin
inherited;
end;
end;
か
procedure TForm1.WMSYSCOMMAND(var Message: TMessage);
begin
if Message.WParam = SC_SCREENSAVE then
begin
Message.WParam := 0;
end;
inherited;
end;
レスありがとうございます。
AAAさんの方式を試したところ起動はしなくなります。
しかしSC_SCREENSAVE を持つメッセージはスクリーンセーバー起動直後の1回しか送られてこないようです。
そのため「一度」起動してしまった後のスクリーンセーバーへはメッセージが送られず
Message.WParam := 0; を設定する処理が出来ないようです。
AttachThreadInput を使用した方法を検討してみたのですが
AttachThreadInputでやりとりする対象先のウインドウハンドルが得られれば
キーやマウスを操作して止められそうです。
しかし自身で起動した場合を除いて、他のアプリのウインドウハンドルを得るにはプロセスIDを取得
する必要があるようです。
ではプロセス一覧を取得して実行ファイルの拡張子が scr だったらそのプロセスを止められたり
キーを模擬で送信できるかと思ったのですが
これらのAPIを使おうとするとSeDebugPrivilegeを使用して権限を昇格させないとだめらしく
管理者権限への昇格確認ダイアログが出てしまいますね。
>他のアプリのウインドウハンドルを得るにはプロセスIDを取
スクリーンセーバが起動すると,おそらくスクリーセーバが最前面のウィンドウです.
ですので GetForegroundWindow で取得できる可能性があります.
念のためクラス名を取得して確認するとよろしいかと.
スクリーンセーバのウィンドウのクラス名は,Windows 7 では D3DSaverWndClass です.
Mr.XRAYさん ありがとうございます。
Windows7環境でスクリーンセーバーを起動させ、その裏でDelphiアプリで
GetForegroundWindow を実行したところ
「D3DSaverWndClass」というクラス名が返ってきました。
強制終了はやりたくないのでフォーカスを移してキーを送信すると
スクリーンセーバーが解除されました。
その状態でWindows10環境にプログラムを持っていって起動すると
なんとGetForegroundWindowがDelphi側のウインドウハンドルを返してきました。
試験したときのコードは下記の通りです。
var
hFG: HWND;
Name: string;
Len : Integer;
begin
hFG := GetForegroundWindow; // 最前面のウインドウハンドルを取得
SetLength(Name, 256); // 文字列型に256バイト分の領域を確保
Len := GetClassName(hFG, PChar(Name), 256); // クラス名とクラス名の長さを取得
SetLength(Name,Len); // 長さの値で成形
if Name <> 'D3DSaverWndClass' then exit; // スクリーンセーバーで無い場合処理しない
SetForegroundWindow(hFG); // スクリーンセーバーにフォーカスを移動
keybd_event(VK_DOWN, 0, 0, 0); // カーソルの下キーを押す
keybd_event(VK_DOWN, 0, KEYEVENTF_KEYUP, 0); // カーソルの下キーを離す
すみません訂正です。
GetForegroundWindow が 0 を返し
そのあとのGetClassName(hFG, PChar(Name), 256); が 0を返していましたが
メモリ初期化の修正が出来ておらずゴミが表示されていただけでした。
どうやら GetForegroundWindowはWindows10では使えないようです。
クラス名がわかっているので
hFG := FindWindow(PChar('D3DSaverWndClass'), nil);
ともしてみましたがやはり0です。
後出しになってしまいましたが Windows10は Home やProではなくEmbedded(エンベデッド)です。
indows 10.0.17134 で,スクリーンセーバが起動したときに,
EnumWindows でウィンドウのクラス名を列挙してみました.
Windows 10 では (もしかしたら Windows 8 から ? ) 仕様が変わっているようです.
D3DSaverWndClass というのはありませんでした.
saver という文字列があるのクラス名もありませんでした.
ForegroundStaging というクラス名のウィンドウがトップに 2 つありました.
もしかしたらこれが関係あるかも知れません.
残念ですが,以上,報告まで.
Windows10の挙動から推測しているだけですが
どうやら管理者権限を持ったアプリ(インストーラー)などには
キーを送ったり、反対にキーの入力をフックして背後で処理するなどが出来ないようになっている感じがします。
スクリーンセーバーもこれに該当しているのかな?
スクリーンセーバーの代わりにモニタの電源オフとソフトからのオンで対応しますので
いったんこちらの質問は解結とさせていただきます。
みなさんありがとうございました。
以下の記事に今回の Windows 10 における内容も書いておきました.
[ 05_起動中のスクリーンセーバーの停止 ]
http://mrxray.on.coocan.jp/Delphi/plSamples/S07_SystemParametersInfo_ScreemSaver.htm#05
今回の「起動済みのスクリーンセーバー」とは趣旨が違いますが,以下のサンプルを追加しておきました.
この掲示板を「スクリーンセーバー」で検索した時に,もしかしたら参考になるかもしれないので.
[ 06_スクリーンセーバーの起動抑止 SetThreadExecutionState 関数 ]
http://mrxray.on.coocan.jp/Delphi/plSamples/S07_SystemParametersInfo_ScreemSaver.htm#06
すみません.サンプルの掲載ページを以下に移動しました.
[ 起動中のスクリーンセーバーの停止 ]
http://mrxray.on.coocan.jp/Delphi/Others/StopScreenSaver.htm