How do I make a Windows screensaver in Godot?

Godot Version

Godot 4

Question

On Windows, screensavers are just renamed executables (scr extension instead of exe). That’s the easy part. However, these screensavers need to parse certain command line arguments to work correctly and this is where I hit the brick wall with trying to make one in Godot. As opposed to other engines, Godot is perfect for making cool 3d screensavers since the exports are a single file (unless you used some external libraries/specialized addons), so it would be nice to know how to make a screensaver work in Godot. Or maybe there is a screensaver export template?

Does this help?

Reading commandline is the easiest part. Actually doing what Windows expects to be done after giving the screensaver specific command arguments is the difficult part.

Have you seen the Lively project by rocksdanister? It will run Godot apps on the desktop or as a screensaver. Take care to use the github version tho, the windows store version will not run user apps. It might be overkill for what you’re trying to do (300 mb d/l, comes with bunch of other screensavers etc), but you may find some information in the source to help you along.

https://github.com/rocksdanister/lively

1 Like

I’ve decided to code my own screensaver bootstrapper in Lazarus (Free Pascal) that would have the actual godot executable and associated PCK in the PE resources and would then unpack it to a temporary directory and run from there. Still figuring out the implementation details but once I get it done, I will share the code on github.

As for Lively, it’s certainly something cool, but an overkill for my taste.

Still, a windows screensaver export template that takes care of handling the screensaver parameter handling would be the best.

1 Like

Seems like you are mainly missing the ability to use GetForegroundWindow as defined in winuser.h

You will probably need to use a small GDExtension to gain access to this function as platform specific functions aren’t normally exposed.

2 Likes

That sounds like what I ended up doing here, I didn’t want to obligate any of my users to installing any additionall applications so i cooked up my own (very) minimal version of the same idea, as an invisible Godot Control that basically acts as a wrapper to a bunch of win32 funcs and a Desktop Wallpaper COM object. So you’d add this control, and if it’s visible in the scene tree, that’s it’s signal to put the app in ‘run as desktop mode’, and if you run the app when it’s already running, it finds other copies of itself and shuts them down, then loads the new copy in standard windowed mode. That approach seems to work pretty well for this idea of an app that disappears from direct user interaction in normal situations, like when its running as the desktop.

I almost got system wide hotkeys working for it, but theres still a couple issues with that and my implementation is a bit hacky.

I’d like to find out more about accomplishing similar kind of desktop / active-desktop style tricks on Linux. If i can make my little ‘stub loader run-as-desktop control’ cross-platform compatible, naturally that’d be far better.

Please post updates as you make progress I’d like to hear more. If you’d like I can share code snippets of what I have here so far maybe it’d help you along (C#)

Sounds like you are doing something different (running as wallpaper instead as a screenshaver). So not really useful for me. I will keep you posted.

Command line arguments you need to handle are:
/s = Run screen saver in full screen (normal start of screen saver)
/c = user clicked the configure button and if your screen saver has any configuration, you should show the configuration screen with the different settings the user can edit.
/p = Run screensaver in preview mode, this is the tiny version you can see when you select the screen saver.
For the /s you will get a second parameter that is the handler for the frame that you will run the preview in. In C# you will do something like this:

First you need to import some standard window methods:

[DllImport(“user32.dll”)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

[DllImport(“user32.dll”)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport(“user32.dll”, SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport(“user32.dll”)]
static extern bool GetClientRect(IntPtr hWnd, out Rectangle lpRect);

Next, get the window handler id from argument 2 after the /p.

string handler = args[1];
IntPtr previewWndHandle = new IntPtr(long.Parse(handler));

Finally adjust your form to be a child of the Windows Screen Saver settings window so it will be terminated along with this. These code lines are how to do it with WinForms. I am not sure how to do this in Godot, but try to Google it.

// Make your window a child of the Windows Screen Saver dialog
SetParent(this.Handle, PreviewWndHandle);
// Make this a child window close when the parent dialog closes
// GWL_STYLE = -16, WS_CHILD = 0x40000000
SetWindowLong(this.Handle, -16, new IntPtr(GetWindowLong(this.Handle, -16) | 0x40000000));
// Place our window inside the parent with the correct position and size
Rectangle ParentRect;
GetClientRect(PreviewWndHandle, out ParentRect);
Size = ParentRect.Size;
Location = new Point(0, 0);

Then your screen saver should run as a small preview inside the Windows Screen Saver selector (if a WinForms application). Happy coding.