2026/02/26(木) [n年前の日記]
#1 [golang] Go言語でスクリーンセーバを作れないか実験中
Go言語(golang)でWindows用のスクリーンセーバを作れそうかどうか実験しているところ。環境は Windows11 x64 25H2 + Go 1.25.7 64bit。
Windows用スクリーンセーバには3つのモードがある。コマンドラインオプションに応じて動作を変えないといけない。
今回は以下のような処理にする。
たぶんこの構成(?)で実現できるのではないかなあと思っているのだけど…。
プレビュー画面モードだけがちょっと面倒臭い。HWND(ウインドウハンドル)を利用して子ウインドウを作成するというのがちょっと…。このあたりの処理をするだけでも、結構ソースが長くなってしまう…。
Windows用スクリーンセーバには3つのモードがある。コマンドラインオプションに応じて動作を変えないといけない。
- /s : フルスクリーン表示モード。
- /c or /c:HWND or コマンドラインオプション無し : 設定画面モード。
- /p HWND : プレビュー画面モード。
今回は以下のような処理にする。
- フルスクリーン表示モード : Go言語で2Dゲームを制作できると謳う Ebitengine を使用。
- 設定画面モード : Windows API の MessageBox を呼んで「設定項目は無いよ」と表示。
- プレビュー画面モード : Windows API で、与えられたHWNDを親ウインドウとした子ウインドウを作成して、その中にbmp画像を表示するだけにする。
たぶんこの構成(?)で実現できるのではないかなあと思っているのだけど…。
プレビュー画面モードだけがちょっと面倒臭い。HWND(ウインドウハンドル)を利用して子ウインドウを作成するというのがちょっと…。このあたりの処理をするだけでも、結構ソースが長くなってしまう…。
◎ リソースファイル作成で悩んだ :
リソースファイルにはアイコンやファイルバージョン等を含めることができるけれど、Windows用スクリーンセーバを作る場合、リソースファイルに STRINGTABLE なる種類のデータを用意して、そこでスクリーンセーバ名を記述しないといけない。
_第128章 スクリーンセーバー その2
_スクリーンセーバーを作ったが、名前を設定できずにファイル名になってしまう
_よしいずの雑記帳 スクリーンセーバーの作り方
また、今回作ろうとしているスクリーンセーバは、プレビュー画面モード用にbmp画像を内包しないといけないので、そのあたりもリソースファイルに記述したい。
MinGW等に付属している windres を使えばそういった記述が可能なのだけど…。今回、go-winres というツールを使って済ませられないかと思ってしまって、そこから悩み始めてしまった。書き方が分からない…。
_tc-hib/go-winres: Command line tool for adding Windows resources to executable files
go-winres は windres と違って、winres.json というjsonファイルにリソースについて記述していくのだけど…。
bmp画像については、おそらく "RT_BITMAP" という指定でいいのかなと…。github上のサンプル(?)にもそういう記述が見えるし…。
_go-winres/_testdata/test.json at main - tc-hib/go-winres
ただ、STRINGTABLE はどう書けばいいのか分からない…。いくらググっても使用事例が見つからない…。AI(Google Gemini)に尋ねてもエラーが出る書き方ばかり提示してくる。学習元となる情報がおそらく存在しないから嘘を言ってくるのだろう…。
解決策が見つからなくて、結局 MinGW の windres を使ってリソースを書くことにしてしまった。しかしこれだと、ビルド時には go.exe 以外にも MinGW が必要になってしまう…。
_第128章 スクリーンセーバー その2
_スクリーンセーバーを作ったが、名前を設定できずにファイル名になってしまう
_よしいずの雑記帳 スクリーンセーバーの作り方
また、今回作ろうとしているスクリーンセーバは、プレビュー画面モード用にbmp画像を内包しないといけないので、そのあたりもリソースファイルに記述したい。
MinGW等に付属している windres を使えばそういった記述が可能なのだけど…。今回、go-winres というツールを使って済ませられないかと思ってしまって、そこから悩み始めてしまった。書き方が分からない…。
_tc-hib/go-winres: Command line tool for adding Windows resources to executable files
go-winres は windres と違って、winres.json というjsonファイルにリソースについて記述していくのだけど…。
bmp画像については、おそらく "RT_BITMAP" という指定でいいのかなと…。github上のサンプル(?)にもそういう記述が見えるし…。
_go-winres/_testdata/test.json at main - tc-hib/go-winres
ただ、STRINGTABLE はどう書けばいいのか分からない…。いくらググっても使用事例が見つからない…。AI(Google Gemini)に尋ねてもエラーが出る書き方ばかり提示してくる。学習元となる情報がおそらく存在しないから嘘を言ってくるのだろう…。
解決策が見つからなくて、結局 MinGW の windres を使ってリソースを書くことにしてしまった。しかしこれだと、ビルド時には go.exe 以外にも MinGW が必要になってしまう…。
◎ Ebitengineの動作が怪しい :
一応、Go言語でWindows用スクリーンセーバを作れたような感じになってきたのだけど、動作確認をしてみたら Ebitengine を利用してフルスクリーン表示をしているあたりで問題が発生した。
GPUリセットをしないと画面が出てこないスクリーンセーバなんて致命的過ぎる…。
キーを押したら、デスクトップ画面が表示されたり、マウスカーソルが表示された。つまり、プログラムの終了条件チェック処理は動いている。
ということは、Ebitengine の update() はちゃんと呼ばれ続けている。update() の中でキー入力チェックやマウス関係のチェックをしてプログラムを終了するようにしてあるので、もし update() が呼ばれていない状態に陥っていたら終了することすらできないはず。
となると、Ebitengine が描画関連で不具合を発生させていた、ということになりそうだけど…。
AI(Google Gemini)に尋ねたら、フルスクリーン表示を指定するあたりが怪しいと言い出した。ebiten.SetFullscreen(true) を呼んで済ませずに、ebiten.SetWindowDecorated(false) を呼んでウインドウの枠無しをあえて指定して、ウインドウサイズをディスプレイの解像度に設定してみたらどうか、と言い出したので、ダメ元でそういう処理にしてみた。これって効果あるのかなあ…。
Ebitengine の issue を眺めていたら、クラッシュ報告が結構多いことにも気づいた。Windowsの場合はDirectX12を使おうとしてそこで不具合が発生するからDirectX11にした、という話も見かけたし、DirectX より OpenGL のほうがパフォーマンスも安定性も高いというユーザ報告も見かけた。
OpenGLの使用を強制してみようか…。os.Setenv("EBITENGINE_GRAPHICS_LIBRARY", "opengl") を呼んで、環境変数 EBITENGINE_GRAPHICS_LIBRARY に opengl を設定。本来ここは auto になっていて、OSに応じて適切なものを選んでくれるらしいけど…。
テクスチャの解放もしてなかったので、プログラム終了時にそのあたりも処理するようにした。Go言語には defer という命令があって、これを使うとその関数から抜ける際にその処理をしてくれるらしいので、テクスチャの解放処理を defer で指定しておけばいい。便利だな…。
_Go言語のdeferを正しく理解する | How defer in Golang works #初心者 - Qiita
ただ、os.exit() で強制終了したり、log.Fatal() で処理を打ち切った時は defer で指定した処理は行われないから注意しないといけない。らしい。
_log.Fatalは使わないようにしよう
_【Go】log.Fatalは気軽に使わない #ログ - Qiita
_Go言語のエラーハンドリングについて 〜panic編〜 #Go - Qiita
- 1回目の起動時はそれっぽく動いてくれた。
- 2回目の起動時はスプライト群が表示されたもののピクリとも動かない。画面が固まっている…。
- 3回目の起動時は真っ暗な画面しか出てこない…。Win + Ctrl + Shift + B を叩いてGPUをリセットしないとデスクトップ画面が出てこなかった。
GPUリセットをしないと画面が出てこないスクリーンセーバなんて致命的過ぎる…。
キーを押したら、デスクトップ画面が表示されたり、マウスカーソルが表示された。つまり、プログラムの終了条件チェック処理は動いている。
ということは、Ebitengine の update() はちゃんと呼ばれ続けている。update() の中でキー入力チェックやマウス関係のチェックをしてプログラムを終了するようにしてあるので、もし update() が呼ばれていない状態に陥っていたら終了することすらできないはず。
となると、Ebitengine が描画関連で不具合を発生させていた、ということになりそうだけど…。
AI(Google Gemini)に尋ねたら、フルスクリーン表示を指定するあたりが怪しいと言い出した。ebiten.SetFullscreen(true) を呼んで済ませずに、ebiten.SetWindowDecorated(false) を呼んでウインドウの枠無しをあえて指定して、ウインドウサイズをディスプレイの解像度に設定してみたらどうか、と言い出したので、ダメ元でそういう処理にしてみた。これって効果あるのかなあ…。
Ebitengine の issue を眺めていたら、クラッシュ報告が結構多いことにも気づいた。Windowsの場合はDirectX12を使おうとしてそこで不具合が発生するからDirectX11にした、という話も見かけたし、DirectX より OpenGL のほうがパフォーマンスも安定性も高いというユーザ報告も見かけた。
OpenGLの使用を強制してみようか…。os.Setenv("EBITENGINE_GRAPHICS_LIBRARY", "opengl") を呼んで、環境変数 EBITENGINE_GRAPHICS_LIBRARY に opengl を設定。本来ここは auto になっていて、OSに応じて適切なものを選んでくれるらしいけど…。
テクスチャの解放もしてなかったので、プログラム終了時にそのあたりも処理するようにした。Go言語には defer という命令があって、これを使うとその関数から抜ける際にその処理をしてくれるらしいので、テクスチャの解放処理を defer で指定しておけばいい。便利だな…。
_Go言語のdeferを正しく理解する | How defer in Golang works #初心者 - Qiita
ただ、os.exit() で強制終了したり、log.Fatal() で処理を打ち切った時は defer で指定した処理は行われないから注意しないといけない。らしい。
_log.Fatalは使わないようにしよう
_【Go】log.Fatalは気軽に使わない #ログ - Qiita
_Go言語のエラーハンドリングについて 〜panic編〜 #Go - Qiita
[ ツッコむ ]
以上です。