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.