はじまり
HSPにはスクリーンセーバーを作るサンプル ssaver/arusave.hsp
が結構昔から同梱されており現在の最新バージョン(3.7β7)でもちゃんと動作するのですが、このサンプルは標準機能のみで作成されていて、DirectX/OpenGLを使用するHSP3Dish(HGIMG4)で単純に置き換えるとどうもプレビュー画面がうまくいきません。スクリーンセーバーの設定内にあるモニターに描画されなくて別のウィンドウが立ち上がりそこに描画されるのです。
スクリーンセーバーを作るには #packopt type 2
というオプションがありますが、どうやらこれが標準機能だけにしか対応していない感じです。実行ファイル作成時の拡張子は .scr
になりますがプレビュー画面には非対応なのでしょう。HSP3Dish(HGIMG4)はウィンドウは基本1つしか作れないのでサンプルにあるようなウィンドウID0とID2を使った作り方はできません。幸いID0を screen
や bgscr
で初期化すれば設定ウィンドウやフルスクリーンはできたので後はプレビュー画面を自力でなんとかすればいけそうだと思い作り始めました。ついでに高DPI環境やマルチモニターにまたがるスクリーンにも少し対応してみます。
スクリプト
HSP 3.7β7、Windows 11 22H2 環境下でテストしています。
スクリプトはシフトJISで保存・実行してください。
; #use hgimg4
#use hsp3dish, gdi32, user32
#packopt name "dish_ss"
#packopt type 2
#packopt hide 1
#module
#uselib "user32"
#func GetDpiForWindow "GetDpiForWindow" int
#func SetProcessDPIAware "SetProcessDPIAware"
#uselib "shcore"
#func SetProcessDpiAwareness "SetProcessDpiAwareness" int
#deffunc setDpiAware
if varptr(SetProcessDpiAwareness) {
SetProcessDpiAwareness 2/*PROCESS_PER_MONITOR_DPI_AWARE*/
} else : if varptr(SetProcessDPIAware) {
SetProcessDPIAware
}
return
#defcfunc getDpi
if (varptr(GetDpiForWindow)) {
return GetDpiForWindow(hwnd)
}
return GetDeviceCaps(hdc, 88/*LOGPIXELSX*/)
#global
*start
setDpiAware
cmds = dir_cmdline
randomize
setreq SYSREQ_LOGWRITE, 0
switch getpath(strmid(cmds, 1, 1), 16)
case "s" : goto *main
case "p" : goto *preview
default : goto *config
swend
*bye
end
*config
scale = double(getDpi()) / 96 ; 画面の倍率を計算
scrw = int(scale * 320)
scrh = int(scale * 240)
screen 0, scrw, scrh
title "設定ウィンドウ(仮"
gosub *initDraw
font "メイリオ", scale * 24, 17
objmode objmode_usefont
pos scale * 240, scale * 200
objsize scale * 64, scale * 24
button "OK", *bye
repeat
redraw 0
gosub *drawBg
pos scale * 10, scale * 10
rgbcolor $ffffff
mes "設定をファイルに保存するのです", mesopt_shadow
redraw 1
await 30
loop
*preview
getstr ts, cmds, 3 ; コマンドライン "/p xxxxx" xxxxxの部分がプレビューウィンドウのハンドル
pwin = int(ts)
dim rc, 4 ; RECT
GetWindowRect pwin, varptr(rc)
scrw = rc(2) - rc(0)
scrh = rc(3) - rc(1)
bgscr 0, scrw, scrh, screen_hide, rc(0), rc(1)
if (GetWindow(pwin, 5/*GW_CHILD*/)) : goto *bye ; 先客がいたら終了する
SetWindowLong hwnd, -16/*GWL_STYLE*/, $56000000/*WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_VISIBLE*/
SetParent hwnd, pwin
MoveWindow hwnd, 0, 0, scrw, scrh, 0
gosub *initDraw
repeat
redraw 0
gosub *drawBg
redraw 1
await 30
loop
*main
scrw = GetSystemMetrics($4e/*SM_CXVIRTUALSCREEN*/)
scrh = GetSystemMetrics($4f/*SM_CYVIRTUALSCREEN*/)
bgscr 0, scrw, scrh, , GetSystemMetrics($4c/*SM_XVIRTUALSCREEN*/), GetSystemMetrics($4d/*SM_YVIRTUALSCREEN*/)
oncmd gosub *chkDown, $100 ; WM_KEYDOWN
oncmd gosub *chkDown, $201 ; WM_LBUTTONDOWN
oncmd gosub *chkDown, $204 ; WM_RBUTTONDOWN
oncmd gosub *chkDown, $207 ; WM_MBUTTONDOWN
oncmd gosub *chkDown, $20b ; WM_XBUTTONDOWN
; gsel 0, 2 ; 非対応?
SetWindowPos hwnd, -1/*HWND_TOPMOST*/, 0, 0, 0, 0, $3/*SWP_NOMOVE|SWP_NOSIZE*/
mouse -1
fcnt = 0
gosub *initDraw
repeat
gosub *chkMouseMove
redraw 0
gosub *drawBg
redraw 1
await 30
fcnt++
loop
*chkDown
if (fcnt > 10) : goto *bye
return 0
*chkMouseMove
; WM_MOUSEMOVEは飛んでき過ぎるので自力で…
if (fcnt > 10) {
if (mousex != mpx || mousey != mpy) : goto *bye
}
mpx = mousex
mpy = mousey
return
#module mvObj px, py, vx, vy, rot, sz, col
#modinit double _x, double _y, double _s, double _a
px = _x
py = _y
vx = cos(_a) * _s / 150
vy = sin(_a) * _s / 150
col = rnd(192)
sz = _s / 3
rot = 0.01 * rnd(314)
return
#modfunc drawObj double _w, double _h
col = (col + 1) \ 192
rot += 0.01
if (px < 0) : px = -px : vx *= -1
if (py < 0) : py = -py : vy *= -1
if (px > _w) : px = _w * 2 - px : vx *= -1
if (py > _h) : py = _h * 2 - py : vy *= -1
px += vx
py += vy
gmode 5, sz, sz, 8
hsvcolor col, 100, 200
grect px, py, rot
return
#global
*initDraw
scx = scrw / 2
scy = scrh / 2
if (scrw > scrh) {
bs = scrh
} else {
bs = scrw
}
num = 5 * scrw * scrh / (bs * bs)
a = 0.01 * rnd(628)
p = 6.28 / num
repeat num
a += p
newmod obj, mvObj, scx, scy, bs, a
loop
setcls 1 : redraw 0
setcls 0
return
*drawBg
gmode 3, scrw, scrh, 8
color
grect scx, scy
foreach obj
drawObj obj(cnt), scrw, scrh
loop
return
以下、部分的に解説等いれていきます。
インクルード・自動作成オプション
; #use hgimg4
#use hsp3dish, gdi32, user32
#packopt name "dish_ss"
#packopt type 2
#packopt hide 1
#use
は HSP 3.7β7 から導入されたプリプロセッサ命令で #include
を簡潔に書けるようになった命令です。; #use hgimg4
をコメント解除するとHGIMG4を使って実行します。hgimg4
と hsp3dish
は同時に指定可能ですがその場合は hgimg4
を先に指定しないとエラーになります。
今回 #packopt type 2
は実行ファイル作成時に拡張子を .scr
にするためだけに指定しています。指定しない場合は .exe
になりますが手動で .scr
にリネームしても同様のスクリーンセーバーになります。
高DPI対応
#module
#uselib "user32"
#func GetDpiForWindow "GetDpiForWindow" int
#func SetProcessDPIAware "SetProcessDPIAware"
#uselib "shcore"
#func SetProcessDpiAwareness "SetProcessDpiAwareness" int
#deffunc setDpiAware
if varptr(SetProcessDpiAwareness) {
SetProcessDpiAwareness 2/*PROCESS_PER_MONITOR_DPI_AWARE*/
} else : if varptr(SetProcessDPIAware) {
SetProcessDPIAware
}
return
#defcfunc getDpi
if (varptr(GetDpiForWindow)) {
return GetDpiForWindow(hwnd)
}
return GetDeviceCaps(hdc, 88/*LOGPIXELSX*/)
#global
マルチモニターを覆うスクリーンである仮想画面のサイズ計算する手間が省ける他に、設定画面のクッキリ化のためのモジュールです。詳細は省きますが、「このアプリは高DPI対応してるよ!」と宣言することで一部のAPIがDPIに応じた値を返してくれたり、画面拡大時にHSPでは通常ぼやける表示をクッキリ描画できるようにします。
起動後の初期処理・終了処理
*start
setDpiAware
cmds = dir_cmdline
randomize
setreq SYSREQ_LOGWRITE, 0
switch getpath(strmid(cmds, 1, 1), 16)
case "s" : goto *main
case "p" : goto *preview
default : goto *config
swend
*bye
end
getpath(strmid(cmds, 1, 1), 16)
でコマンドライン文字列の2文字目を小文字化、switch
で文字毎に場合分けしています。ここはサンプルとやっていることは同じです。
終了処理、始めは onexit
で飛ばしていたのですが、終了時にエラーが出るなどで泥沼にハマったので止めました。
設定画面
*config
scale = double(getDpi()) / 96 ; 画面の倍率を計算
scrw = int(scale * 320)
scrh = int(scale * 240)
screen 0, scrw, scrh
title "設定ウィンドウ(仮"
gosub *initDraw
font "メイリオ", scale * 24, 17
objmode objmode_usefont
pos scale * 240, scale * 200
objsize scale * 64, scale * 24
button "OK", *bye
repeat
redraw 0
gosub *drawBg
pos scale * 10, scale * 10
rgbcolor $ffffff
mes "設定をファイルに保存するのです", mesopt_shadow
redraw 1
await 30
loop
高DPI対応宣言した場合は自力でウィンドウやフォントのサイズや位置などを計算しないといけないので、倍率を取得してそれぞれの値に掛けていきます。
プレビュー画面
*preview
getstr ts, cmds, 3 ; コマンドライン "/p xxxxx" xxxxxの部分がプレビューウィンドウのハンドル
pwin = int(ts)
dim rc, 4 ; RECT
GetWindowRect pwin, varptr(rc)
scrw = rc(2) - rc(0)
scrh = rc(3) - rc(1)
bgscr 0, scrw, scrh, screen_hide, rc(0), rc(1)
if (GetWindow(pwin, 5/*GW_CHILD*/)) : goto *bye ; 先客がいたら終了する
SetWindowLong hwnd, -16/*GWL_STYLE*/, $56000000/*WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_VISIBLE*/
SetParent hwnd, pwin
MoveWindow hwnd, 0, 0, scrw, scrh, 0
gosub *initDraw
repeat
redraw 0
gosub *drawBg
redraw 1
await 30
loop
今回のメインとなる箇所です。コメントにも書いてありますがコマンドライン文字列にプレビューウィンドウのハンドルが載っているので、そのハンドルからウィンドウの位置サイズを取得して bgscr
でスクリーンを初期化、ウィンドウスタイルを変更してプレビューウィンドウの子ウィンドウにする、という流れです。
ウィンドウスタイルを変更するのは子ウィンドウにするためで、HSPのウィンドウには WS_POPUP
が含まれており子ウィンドウにするための WS_CHILD
とは共存できないからです。
bgscr
の第4パラメータに screen_hide
を指定していますが現バージョンではどうも無効になっているようなので、プレビューウィンドウを覆う形で初期化して誤魔化しています。本当は初めは非表示にしておいて準備できたら表示したいところです。
途中にある先客がいたら~の箇所ですが、プレビューを高速で切り替えると前のプレビューが残ったまま次のプレビューと点滅しながら表示されることがあり、HSP側でうまく終了できていないようなので無理やりですが入れてみました。自分の環境ではうまく終了してくれますがちょっと怪しい感じはします…
フルスクリーン画面
*main
scrw = GetSystemMetrics($4e/*SM_CXVIRTUALSCREEN*/)
scrh = GetSystemMetrics($4f/*SM_CYVIRTUALSCREEN*/)
bgscr 0, scrw, scrh, , GetSystemMetrics($4c/*SM_XVIRTUALSCREEN*/), GetSystemMetrics($4d/*SM_YVIRTUALSCREEN*/)
oncmd gosub *chkDown, $100 ; WM_KEYDOWN
oncmd gosub *chkDown, $201 ; WM_LBUTTONDOWN
oncmd gosub *chkDown, $204 ; WM_RBUTTONDOWN
oncmd gosub *chkDown, $207 ; WM_MBUTTONDOWN
oncmd gosub *chkDown, $20b ; WM_XBUTTONDOWN
; gsel 0, 2 ; 非対応?
SetWindowPos hwnd, -1/*HWND_TOPMOST*/, 0, 0, 0, 0, $3/*SWP_NOMOVE|SWP_NOSIZE*/
mouse -1
fcnt = 0
gosub *initDraw
repeat
gosub *chkMouseMove
redraw 0
gosub *drawBg
redraw 1
await 30
fcnt++
loop
*chkDown
if (fcnt > 10) : goto *bye
return 0
*chkMouseMove
; WM_MOUSEMOVEは飛んでき過ぎるので自力で…
if (fcnt > 10) {
if (mousex != mpx || mousey != mpy) : goto *bye
}
mpx = mousex
mpy = mousey
return
GetSystemMetrics
を使ってマルチモニターにまたがるスクリーンの位置サイズを取得しています。高DPI対応宣言しているとDPIに応じた値を返してくれます。
oncmd
でキーやマウスの押下メッセージを取って終了しています。マウスカーソルの移動だけは自分の環境だと何故か動いてないときも WM_MOUSEMOVE
がすごい勢いで飛んでくるので、ループ内で自力で処理しています。標準機能で作ったスクリーンセーバーだとこの辺の処理も内部でやってくれています。if (fcnt > 10)
である程度猶予をもたせています。起動した瞬間終了すると起動したのか分からないので。
スクリーンの最前面にもってくるため gsel 0, 2
としたかったのですが第2パラメータが非対応っぽいので代わりに SetWindowPos
を使っています。
描画部分
#module mvObj
以下の部分は、回転する四角形の描画部分です。画面端で跳ね返ったり色が徐々に変わったり。画面の大きさによって四角形のサイズや数を変えています。
おわり
モニターが液晶になってからスクリーンセーバーの役目も無くなりつつあるのかもしれません。HSPもこの辺の対応がされていないのもその影響なのかな、と思ったり思わなかったり…