Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/proteo5/Prevent-Screen-Saver/llms.txt

Use this file to discover all available pages before exploring further.

Prevent Screen Saver keeps the screen saver at bay by making a single Windows API call when protection is activated. There are no timers firing every few seconds, no synthetic keystrokes, and no cursor movement — the operating system simply receives a flag telling it that the display should be kept active, and it respects that request for as long as the app holds it.

The Windows API Approach

The core of the app is a P/Invoke declaration for SetThreadExecutionState, an API exported by kernel32.dll. This function lets an application signal Windows that it needs the display or system to remain active — the same mechanism used by media players to suppress the screen saver while a video is playing. Two flags govern the behavior:

ES_CONTINUOUS (0x80000000)

Tells Windows that the requested execution state should persist until explicitly changed. Without this flag, the state would only apply to the current system idle check and then reset.

ES_DISPLAY_REQUIRED (0x00000002)

Informs Windows that the display is actively needed, preventing the screen saver from activating and typically also preventing display sleep triggered by the idle timeout.
When the user activates protection, both flags are combined and passed in a single call. When the user deactivates protection, only ES_CONTINUOUS is passed — this releases the ES_DISPLAY_REQUIRED requirement and lets Windows resume normal idle behaviour immediately.
private const uint EsContinuous = 0x80000000;
private const uint EsDisplayRequired = 0x00000002;

public bool StartProtection()
{
    uint result = SetThreadExecutionState(EsContinuous | EsDisplayRequired);

    if (result == 0)
    {
        return false;
    }

    protectionActive = true;
    return true;
}

public void StopProtection()
{
    if (!protectionActive)
    {
        return;
    }

    SetThreadExecutionState(EsContinuous);
    protectionActive = false;
}

[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint SetThreadExecutionState(uint esFlags);
If SetThreadExecutionState returns 0, the call has failed. The app surfaces this immediately as an activation error so the user is never silently left unprotected.
Simulating input was considered and explicitly rejected during the design of this app. The implementation plan evaluated three input-based alternatives before settling on SetThreadExecutionState:
  • Keyboard simulation sends periodic key events (such as a harmless modifier key press) to reset the idle timer. The problem is that those events are delivered to whichever application currently has focus — they can interrupt typing, trigger shortcuts, or interfere with games, terminals, and other input-sensitive programs in unpredictable ways.
  • Mouse movement shifts the cursor slightly and then returns it to its original position. This produces visible jitter that is immediately noticeable during precision work — graphic design, video editing, CAD tools — and it can nudge tooltips, hover states, or drag operations in ways the user did not intend.
  • SetThreadExecutionState is the Windows-sanctioned path for exactly this use case. It communicates intent directly to the power management subsystem, produces no visible artefacts, affects no other application, and is fully reversible with a single follow-up call. It is the same API a media player uses to suppress the screen saver while streaming a film.

Protection Lifecycle

The protection state transitions are intentionally simple — one API call in, one API call out.
1

Activation

The user clicks the tray icon, selects Activate from the tray menu, or presses the configured global hotkey. StartProtection() calls SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED). Windows records the request against the calling thread and suppresses screen saver activation from that point forward.
2

Active state

While protection is active, no periodic API calls are necessary. The execution state flag is held by the thread and Windows honours it continuously until it is explicitly released or the process exits.
3

Deactivation

The user deactivates protection via the tray icon, menu, or hotkey. StopProtection() calls SetThreadExecutionState(ES_CONTINUOUS) — passing only the continuous flag without ES_DISPLAY_REQUIRED releases the display requirement. Normal Windows idle behaviour resumes immediately after this call returns.
4

Exit and session end

StopProtection() is always called as part of application shutdown, whether the user selects Exit from the tray menu, logs off, or Windows initiates a session-ending event. The app never leaves a dangling execution state request behind.
5

Startup restore

If the user had protection active when they last closed the app, the saved LastProtectionActive setting is read on the next launch and StartProtection() is called automatically — so protection resumes without any manual action.

Single-Instance Guard

The app uses a named Windows Mutex to ensure only one copy runs at a time.
private const string MutexName = "PreventScreenSaver.SingleInstance";

using Mutex singleInstanceMutex = new(initiallyOwned: true, MutexName, out bool createdNew);

if (!createdNew)
{
    return;
}
If a second launch is attempted while the app is already running — for example, if the user double-clicks the installer shortcut again — the new process detects that createdNew is false and exits silently. The existing instance continues running undisturbed.
ES_DISPLAY_REQUIRED is designed to suppress both screen saver activation and display sleep that is driven by the same idle timeout. However, Windows distinguishes between the screen saver timeout (configured under Screen saver settings) and the turn off the display power plan setting. If your monitor goes dark but the screen saver itself is not involved, that sleep transition may be governed by the power plan rather than the screen saver idle timer. ES_DISPLAY_REQUIRED typically covers both, but the exact behaviour can vary depending on how your power plan is configured.

Build docs developers (and LLMs) love