Blocking the Windows Screen Saver in Delphi

Sometimes your program needs to block the screen saver from automatically kicking in. My use case was that the program was recording data and whenever the screen saver was active, the data was lost (No idea why, it probably had something to do with the way HID is implemented in Windows.)
So I was looking for a way to fix that without forcing the user to turn off the screen saver. The methods that used to work under Windows XP no longer work in Windows 7 and later (I don’t care about Vista), so I googled and found this question on StackOverflow. The Windows API functions PowerCreateRequest + PowerSetRequest mentioned in the highest voted answer looked promising. Unfortunately they don’t seem to be available in Delphi (Delphi 2007, which I used for that project, is too old to know them, but I couldn’t find them in Delphi 10.3 either). The first task was therefore to get a function declaration for Delphi. Google didn’t help here which meant that I had to create them myself. Not a big deal:

type
  TPowerCreateRequest = function(_Context: PReasonContext): THandle; stdcall;
  TPowerSetRequest = function(_Handle: THandle; _RequestType: TPowerRequestType): LongBool; stdcall;
  TPowerClearRequest = function(_Handle: THandle; _RequestType: TPowerRequestType): LongBool; stdcall;

I prefer loading such functions at runtime rather than the program not starting because some external reference is not avaiable. These functions are exported by kernel32.dll.

  FDllHandle := SafeLoadLibrary(kernel32);
  PowerCreateRequest := GetProcAddress(FDllHandle, 'PowerCreateRequest');
  PowerSetRequest := GetProcAddress(FDllHandle, 'PowerSetRequest');
  PowerClearRequest := GetProcAddress(FDllHandle, 'PowerClearRequest');
  if not Assigned(PowerCreateRequest) or not Assigned(PowerSetRequest) or not Assigned(PowerClearRequest) then
    raise EOsFunc.Create(_('Could not initialize the PowerXxxxRequest functions from kernel32.'));

Usage is not without its own problems. First, I had to declare the constants and parameters:

const
  POWER_REQUEST_CONTEXT_VERSION = 0;
  POWER_REQUEST_CONTEXT_DETAILED_STRING = 2;
  POWER_REQUEST_CONTEXT_SIMPLE_STRING = 1;
type
  PReasonContext = ^TReasonContext;
  TReasonContext = record
    Version: ULONG;
    Flags: DWORD;
    case Boolean of
      False: (
        SimpleReasonString: PWideChar;
        );
      True: (
        Detailed: record
          LocalizedReasonModule: HMODULE;
          LocalizedReasonId: ULONG;
          ReasonStringCount: ULONG;
          ReasonStrings: PPWideChar;
        end;
        );
  end;
type
  {$MinEnumSize 4}
  TPowerRequestType = (
    PowerRequestDisplayRequired = 0,
    PowerRequestSystemRequired = 1,
    PowerRequestAwayModeRequired = 2,
    PowerRequestExecutionRequired = 3);

Now, how do these functions work?

The first thing to do is creating a power request with PowerCreateRequest. This function requires a PReasonContext pointer which must be initialized correctly. The Version and Flags fields are simple: Assign one of the POWER_REQUEST_CONTEXT_xxx constants declared above. But what about the other fields? I decided to go with the simple case, that is: Set Flags to POWER_REQUEST_CONTEXT_SIMPLE_STRING and provide a value for SimpleReasonString.

var
  FRequestHandle: THandle;
  FContext: TReasonContext;
  FReason: array[0..255] of WideChar;
  // [...]
  FContext.Version := POWER_REQUEST_CONTEXT_VERSION;
  FContext.Flags := POWER_REQUEST_CONTEXT_SIMPLE_STRING;
  FContext.SimpleReasonString := @FReason;
  FRequestHandle := PowerCreateRequest(@FContext);
  if FRequestHandle = INVALID_HANDLE_VALUE then
    RaiseLastOSError;

Where FReason is an array of WideChar. My tests showed that the TReasonContext record and the reason string it points to must be available through the lifetime of the reason context. If it isn’t, the reason displayed by the powercfg tool (see below) will be corrupted. Therefore I did not use a WideString but a static array.

After the power request has been created, calls to PowerSetRequest and PowerClearRequest are possible.

  Win32Check(PowerSetRequest(FRequestHandle, PowerRequestDisplayRequired));

This call prevents the screen saver from starting automatically. A call to PowerClearRequest supposedly turns that off again (but I haven’t tested it).

I mentioned the powercfg tool above. It’s a Windows command line tool that among other functionality can display processes that have active power requests. e.g.

powercfg /requests
DISPLAY:
[PROCESS] \Device\HarddiskVolume2\Source\dzlib\tests\BlockScreenSaver\BlockScreenSaver.exe
test

SYSTEM:
None.

AWAYMODE:
None.

EXECUTION:
None.

PERFBOOST:
None.

The string “test” is the reason I passed to PowerCreateRequests.

I mentioned that failing to preserver the reason string results in a corrupted message in the display. It looked like this:

powercfg /requests
DISPLAY:
[PROCESS] \Device\HarddiskVolume2\Source\dzlib\tests\BlockScreenSaver\BlockScreenSaver.exe
?a?E?I???↑?E?↑?E?↑?E?↑?E?↑

Note that this tool requires administrator privileges (but power requests don’t).

I have added this code to my dzlib. It’s in u_dzOsUtils. There is also a simple test / demo program BlockScreenSaver.

If you would like to comment on this, go to this post in the international Delphi Praxis forum.