Due to various reasons, I need to find a way how to prevent a Windows
screen saver from activation if, and only if my application is
running. I did some research before posting here but without a luck so
far.
I'm aware of existence of SystemParametersInfo() API that could be
called with SPI_SETSCREENSAVEACTIVE parameter to deliberately
deactivate screen saver but I don't want to change global Windows
settings - I'd rather do it on my application level.
Another possibility is to trap WM_SYSCOMMAND message and get rid of
its default processing if wParam equals to SC_SCREENSAVE. This works
fine as long as my application has focus. I also discovered that this
approach will not work on Windows Vista if the screen saver is
password-protected.
I also found the following web page
that suggests to have a CBT (computer based training) window in order
to prevent screen saver from being started, but as I've never used
hooks before I don't know where to start.
Any ideas will be appreciated.
--
Ivo Bauer [OZM Research]
Well, but it *is* something global you want to change. If you don't
change the registry as well, changing this flag is not permanent.
--
Jens Gruschel
http://www.pegtop.net
Yes, I'm aware of that, but even if I choose not to update the
registry, the new setting will be preserved until next reboot which is
something I don't want to. Yes, I could re-activate the screen saver
upon exit of my application but how can I be sure that this setting
has not been changed explicitely by the user in the mean time?
My intent is to prevent the screen saver from starting if and *only*
if my application is actually running. I usually try to refrain from
changing global system settings but this case is an exception to the rule.
> I'm aware of existence of SystemParametersInfo() API that could
> be called with SPI_SETSCREENSAVEACTIVE parameter to
> deliberately deactivate screen saver but I don't want to change
> global Windows settings - I'd rather do it on my application level.
That is exactly what happens when you use SPI_SETSCREENSAVEACTIVE.
Just make sure that you set SPI_SETSCREENSAVEACTIVE back to FALSE
before you exit the application.
> Another possibility is to trap WM_SYSCOMMAND message and
> get rid of its default processing if wParam equals to SC_SCREENSAVE.
> This works fine as long as my application has focus.
You need to use a global message hook via SetWindowsHookEx() inside a
DLL to address that issue. I did exactly that in an application I
once wrote. That way, the hook can swallow the notification
regardless of which application has the focus.
Gambit
> how can I be sure that this setting has not been changed explicitely
> by the user in the mean time?
Because the user does not have access to change
SPI_SETSCREENSAVEACTIVE in the first place. Only real screen savers
(and apps that try to manipulate screen savers, like yours is) use
SPI_SETSCREENSAVEACTIVE.
If you are really worried about the setting being changed behind your
back, then simply intercept the WM_SETTINGCHANGE message and then
query SPI_GETSCREENSAVEACTIVE to see if the value has changed from
what you are expecting it to be.
> My intent is to prevent the screen saver from starting if
> and *only* if my application is actually running.
You already know how to do that.
Gambit
I didn't know that. Thanks for your explanation.
> If you are really worried about the setting being changed behind your
> back, then simply intercept the WM_SETTINGCHANGE message and then
> query SPI_GETSCREENSAVEACTIVE to see if the value has changed from
> what you are expecting it to be.
That seems to be the way to go. I'll give it a try. Thanks again.
Did you mean actually set it to True, didn't you? Because I would want
to re-active the screen saver if it was previously active before my
application has made the change.
>> Another possibility is to trap WM_SYSCOMMAND message and
>> get rid of its default processing if wParam equals to SC_SCREENSAVE.
>> This works fine as long as my application has focus.
>
> You need to use a global message hook via SetWindowsHookEx() inside a
> DLL to address that issue. I did exactly that in an application I
> once wrote. That way, the hook can swallow the notification
> regardless of which application has the focus.
Please correct me if I am wrong but this approach is not going to work
on Windows Vista and later versions of the OS. Here is a quote from
the MSDN article named "Handling Screen Savers":
"Windows Vista and later: If password protection is enabled by policy,
the screen saver is started regardless of what an application does
with the SC_SCREENSAVE notification."
The same article claims that presence of CBT (computer based training)
window in the system should prevent screen saver from being started.
Do you have any experience with this kind of stuff?
Thanks!
> Did you mean actually set it to True, didn't you?
Nevermind. I got SPI_SETSCREENSAVEACTIVE and SPI_SCREENSAVERRUNNING
confused.
> The same article claims that presence of CBT (computer based
> training) window in the system should prevent screen saver from
> being started.
CBT is just another type of hook that is installed via
SetWindowsHookEx().
> Do you have any experience with this kind of stuff?
I have much experience with SetWindowsHookEx().
Gambit
Great. ;-) Do you have any example (or a web link to one) that would
get me started how to install a CBT hook in order to prevent a screen
saver from being activated?
> Great. ;-) Do you have any example (or a web link to one) that would
> get me started
I have posted many examples of using hooks before. Go to
http://www.deja.com and search through the newsgroup archives.
> how to install a CBT hook
Have you looked at the documentation for SetWindowsHookEx() yet? It
explains everything you need to know.
Gambit
I found the following CBT hook example written by Jim Kuenemann:
http://www.mustangpeak.net/download/cbthook.zip
Apparently, the presence of CBT hook is not sufficient to prevent
screen saver from activation. So I thought I would intercept the
HCBT_CREATEWND message, check if the class name of the window being
created is a screen saver class and if so, then return non-zero value
from CBTProc which should, according to MSDN documentation, cancel the
window creation. This is the code I inserted in the HCBT_CREATEWND
handler inside CBTProc:
if PCBTCreateWnd(Pointer( lParam))^.lpcs.lpszClass =
'WindowsScreenSaverClass' then
begin
Result := 1;
end;
I'm pretty sure that the above boolean expression returns True when
screen saver is about to be activated (I put a call to Windows.Beep
API right after the begin statement and I can hear a beep when it
occurs) but then the screen saver is normally activated as if the
CBTProc was returned zero.
I also tried to bypass a call to CallNextHookEx if the above boolean
condition is True and at first sight it works - the screen saver is
not activated when it should be, but I can see that Windows keeps
trying to activate the screen saver every second which results in
changing mouse cursor to crAppStart until you move the mouse or hit a
key. After couple of minutes this test app crashes the whole system -
the Explorer process needs to be restarted.
What am I doing wrong?
Russ
"Ivo Bauer" <ab...@zom.zc> wrote in message
news:4600fcf0$1...@newsgroups.borland.com...
> Apparently, the presence of CBT hook is not sufficient to
> prevent screen saver from activation. So I thought I would
> intercept the HCBT_CREATEWND message
Try intercepting HCBT_SYSCOMMAND instead.
> check if the class name of the window being created is a screen
saver class
That is not a reliable approach. Not all screen savers use that class
name.
> if PCBTCreateWnd(Pointer( lParam))^.lpcs.lpszClass =
'WindowsScreenSaverClass' then
You can't compare the values like that, as they are not Delphi String
types. You are comparing just the memory addresses being pointed to,
not the actuall data being pointed at. You need to use StrComp() or
similar function instead, ie:
if StrComp(PCBTCreateWnd(Pointer( lParam))^.lpcs.lpszClass,
'WindowsScreenSaverClass') = 0 then
> I'm pretty sure that the above boolean expression returns True
> when screen saver is about to be activated
Not reliably. See above.
Gambit
Thanks for the suggestion, but it did not helped. The test app still
keeps crashing itself and the Explorer as well.
Russ
"Ivo Bauer" <ab...@zom.zc> wrote in message
news:46018eea$1...@newsgroups.borland.com...
I've done that, please find the library source pasted below. When I
run the test app, it no longer crashes but the CBTProc never gets
called with HCBT_SYSCOMMAND code although it seems that the hook is
installed correctly.
Could you please look at it?
>> check if the class name of the window being created is a screen
> saver class
>
> That is not a reliable approach. Not all screen savers use that class
> name.
Aha, I wasn't aware of this before. Thanks for pointing that out.
>> if PCBTCreateWnd(Pointer( lParam))^.lpcs.lpszClass =
> 'WindowsScreenSaverClass' then
>
> You can't compare the values like that, as they are not Delphi String
> types.
Yes, you're right of course.
Source of the CBT hook DLL follows. The test app calls HookCBT in the
main form's constructor and UnHookCBT in the main form's destructor:
library CBTHook;
uses
Windows;
{$R *.RES}
const
CMemoryMappedFileName = 'CBTHookHandle';
function CBTProc(Code: Integer; wParam: WPARAM; lParam: LPARAM):
LRESULT stdcall;
var
LMappedMemoryHandle: THandle;
LHookHandlePtr: PHandle;
begin
Result := 0;
LMappedMemoryHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False,
CMemoryMappedFileName);
if LMappedMemoryHandle <> 0 then
begin
LHookHandlePtr := MapViewOfFile(LMappedMemoryHandle,
FILE_MAP_ALL_ACCESS, 0, 0, 0);
if Assigned(LHookHandlePtr) then
begin
if (Code = HCBT_SYSCOMMAND) and
((wParam and $FFF0) = SC_SCREENSAVE) then
begin
Result := 1;
end;
if (LHookHandlePtr^ <> 0) then
Result := CallNextHookEx(LHookHandlePtr^, Code, wParam,
lParam);
UnMapViewOfFile(LHookHandlePtr);
end;
CloseHandle(LMappedMemoryHandle);
end
end;
procedure HookCBT; stdcall;
var
LMappedMemoryHandle: THandle;
LHookHandlePtr: PHandle;
begin
LMappedMemoryHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False,
CMemoryMappedFileName);
if LMappedMemoryHandle <> 0 then
begin
LHookHandlePtr := MapViewOfFile(LMappedMemoryHandle,
FILE_MAP_ALL_ACCESS, 0, 0, 0);
if Assigned(LHookHandlePtr) then
begin
LHookHandlePtr^ := SetWindowsHookEx(WH_CBT, CBTProc,
hInstance, 0);
UnmapViewOfFile(LHookHandlePtr);
end;
CloseHandle(LMappedMemoryHandle);
end;
end;
procedure UnHookCBT; stdcall;
var
LMappedMemoryHandle: THandle;
LHookHandlePtr: PHandle;
begin
LMappedMemoryHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False,
CMemoryMappedFileName);
if LMappedMemoryHandle <> 0 then
begin
LHookHandlePtr := MapViewOfFile(LMappedMemoryHandle,
FILE_MAP_ALL_ACCESS, 0, 0, 0);
if Assigned(LHookHandlePtr) then
begin
UnHookWindowsHookEx(LHookHandlePtr^);
LHookHandlePtr^ := 0;
UnmapViewOfFile(LHookHandlePtr);
end;
CloseHandle(LMappedMemoryHandle);
end;
end;
exports
HookCBT index 1,
UnHookCBT index 2;
begin
end.
I have simplified the original example. The good news is that the test
app no longer crashes. The bad news is that the hook does not work.
Please see my reply to Remy.
> When I run the test app, it no longer crashes but the CBTProc
> never gets called with HCBT_SYSCOMMAND code
I don't expect it to be called at all, given the code you have shown
now.
> it seems that the hook is installed correctly.
It is not.
> Source of the CBT hook DLL follows.
Your code is not using the hook properly or managing the file mapping
correctly.
> function CBTProc(Code: Integer; wParam: WPARAM; lParam: LPARAM):
> LRESULT stdcall;
You need to export that function. Otherwise, other processes will not
be able to access it.
> LMappedMemoryHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS,
False,
> CMemoryMappedFileName);
You need to use CreateFileMapping() at some point. Otherwise, you are
trying to open a mapping that never exists. You should not be
re-opening the file mapping every time the hook procedure is called.
Create/open it once when the DLL is first loaded, and then release it
when the DLL is unloaded. Every time the DLL is loaded into a new
process, that instance of the DLL needs access to a file mapping that
may or may not already exist. Once created, the exported functions
can access the data block whenever needed without all of that extra
overhead.
> if (LHookHandlePtr^ <> 0) then
> Result := CallNextHookEx(LHookHandlePtr^, Code, wParam,
> lParam);
At no point are you actually allocating any memory to hold the return
value of SetWindowsHookEx().
With that said, try something more like the following instead:
library CBTHook;
uses
Windows;
{$R *.RES}
const
CMemoryMappedFileName = 'CBTHookHandle';
type
PHHOOK = ^HHOOK;
var
MemoryMap: THandle = 0;
HookPtr: PHHOOK = nil;
function CBTProc(Code: Integer; wParam: WPARAM; lParam: LPARAM):
LRESULT; stdcall;
begin
if (Code = HCBT_SYSCOMMAND) and ((wParam and $FFF0) =
SC_SCREENSAVE) then
Result := 1
else
Result := CallNextHookEx(HookPtr^, Code, wParam, lParam);
end;
function HookCBT: BOOL; stdcall;
begin
Result := FALSE;
if (HookPtr <> nil) and (HookPtr^ = 0) then
begin
HookPtr^ := SetWindowsHookEx(WH_CBT, @CBTProc, HInstance,
0);
if HookPtr^ <> 0 then Result := TRUE;
end;
end;
function UnHookCBT: BOOL; stdcall;
begin
Result := FALSE;
if (HookPtr <> nil) and (HookPtr^ <> 0) then
begin
if UnhookWindowsHookEx(HookPtr^) then
begin
HookPtr^ := 0;
Result := TRUE;
end;
end;
end;
procedure MyDllProc(Reason: Integer);
begin
if Reason = DLL_PROCESS_DETACH then
begin
if HookPtr <> nil then
begin
UnmapViewOfFile(HookPtr);
HookPtr = nil;
end;
if MemoryMap <> 0 then
begin
CloseHandle(MemoryMap);
MemoryMap := 0;
end;
end;
end;
exports
HookCBT, UnHookCBT, CBTProc;
begin
DllProc := @MyDllProc;
DisableThreadLibraryCalls(HInstance);
MemoryMap := CreateFileMapping(INVALID_HANDLE_VALUE, nil,
PAGE_READWRITE, 0, SizeOf(HHOOK), CMemoryMappedFileName);
if MemoryMap <> 0 then
begin
HookPtr := PHHOOK(MapViewOfFile(MemoryMap, FILE_MAP_WRITE,
0, 0, 0));
if HookPtr <> nil then
begin
if GetLastError <> ERROR_ALREADY_EXISTS then
HookPtr^ := 0;
end else
begin
CloseHandle(MemoryMap);
MemoryMap := 0;
end;
end;
end.
Gambit
Of course, I didn't realized that before.
> You need to use CreateFileMapping() at some point. Otherwise, you are
> trying to open a mapping that never exists.
I must admit I blindly simplified the example found on a web and
didn't bother to read the documentation regarding the use of memory
mapped files. Shame on me, it won't happen again.
> You should not be
> re-opening the file mapping every time the hook procedure is called.
> Create/open it once when the DLL is first loaded, and then release it
> when the DLL is unloaded.
Will do. Thanks for this suggestion.
> With that said, try something more like the following instead:
I've just tried your code and I'm impressed - it works as I would
expect and it's stable as well. Many thanks for your valuable
feedback, as always!
However, as I feared, it does not work when screen saver is password
protected - not even under Windows XP. I guess I will have to live
with that.
Russ
"Ivo Bauer" <ab...@zom.zc> wrote in message
news:460245ad$1...@newsgroups.borland.com...
I admit I didn't use MMFs myself before but I have seen a lot of
people to use them in their apps. It would break a lot of existing
applications if MMFs weren't work in Vista. I've just checked MSDN
documentation regarding MMFs and it appears that they're supported by
Vista. Do you have a link to that discussion?
I have done some experiments and found a solution/workaround. Rather
than using the demo application main form's constructor/destructor to
install/uninstall the hook, I now use overridden CreateWnd/DestroyWnd
methods for hook setup/teardown instead:
type
TForm1 = class(TForm)
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
end;
procedure TForm1.CreateWnd;
begin
inherited;
HookCBT;
end;
procedure TForm1.DestroyWnd;
begin
UnHookCBT;
inherited;
end;
With the above modification it works as expected, apart from when
there is no user currently loggen on. But this doesn't bother me.
Russ
"Ivo Bauer" <ab...@zom.zc> wrote in message
news:4602d510$1...@newsgroups.borland.com...
I don't have/use Vista yet.
> The thread was in this group. Scroll down to
> march 1st the title is "Memory Mapped files in Vista"
Interesting discussion. Microsoft apparently has an ongoing focus on
how to make developer's life more complicated. Fortunately, I'm not in
*immediate* need of targetting Vista with regard to my applications.
But I definitely do care about the compatiblity of my code with Vista.
Do you have Vista installed? Would you be willing to test my sample
CBT hook application? If so, I'd be sending you the source code so
that you can build both the DLL and the test app yourself.
Thanks!
Russ
"Ivo Bauer" <ab...@zom.zc> wrote in message
news:4602eba9$1...@newsgroups.borland.com...
OK, no problem. I'll try to test my app elsewhere. Thanks anyway.
The Platform SDK for SetThreadExecutionState says, among other things -
"This function does not always stop the screen saver from executing when the
computer is in use. As an alternative, set a timer and periodically call the
SystemParametersInfo function to query and reset the screen saver time-out
value. (Use the user's default settings in case your application terminates
abnormally.)"
Following a hunch, I ran a test on XP and discovered another option -
you can reset the timer by simply calling SystemParametersInfo with
SPI_SETSCREENSAVEACTIVE = TRUE.
If you can overcome any aversion you may have to using timers yourself then
the following code will hold off the screen saver indefinitely. The minimum
time-out allowed in the Control Panel Screen Saver dialog is 1 minute.
Though it can be set to a lower value programmatically it's unlikely that
this would be found in common usage and you could cover all sensible cases
by using a timer Interval of 50,000 (i.e. 50secs).
procedure TForm1.Timer1Timer(Sender: TObject);
var
SSActive: Bool;
begin
Timer1.Enabled := false;
try
if SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, @SSActive, 0)
and SSActive then
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1, nil, 0);
finally
Timer1.Enabled := true;
end;
end;
If the user disables the screen saver the code does nothing, if they
re-enable it the SS timer is endlessly reset before it times-out.
--
Regards,
Chris Luck
Thanks for your suggestion, Chris. I will save it for a future
reference in case the existing solution (based on CBT hook and memory
mapped files) won't work on Vista.
BTW, I've never heard of SetThreadExecutionState API before - it seems
to address my other problem - how do I prevent system from entering
the sleeping mode while my application is running. Thanks for this one
as well!
> how do I prevent system from entering the sleeping mode
> while my application is running.
Intercept the WM_POWERBROADCAST message.
Gambit
Thanks for jumping in, Remy.
Did you mean to respond with BROADCAST_QUERY_DENY when
WM_POWERBROADCAST carries PBT_APMQUERYSUSPEND in the wParam field?
I've checked the MSDN documentation and it says that the support for
PBT_APMQUERYSUSPEND was removed in Vista.
I would certainly prefer intercepting a specific message rather than
continuously telling the OS "I'm working, please don't shut me down"
every once in a while. Sigh.