Detecting if the screensaver is active and/or the user has locked the screen in Windows
I'm writing an app that at times will send开发者_JS百科 notifications to the user in the form of toaster messages.
If the user is not there, he can't see the notification. So what I wanna do is be able to check if the user has locked the screen or if the screensaver happens to be activated.
Any notification that is triggered while the user cannot see it will be delayed and shown when the user logs back in and resumes his session.
I'm on Windows 7 myself, but I'd prefer a solution that works universally for Windows XP and up.
There is no documented way to find out if the workstation is currently locked. You can however get a notification when it un/locks. Subscribe the SystemEvents.SessionSwitch event, you'll get SessionSwitchReason.SessionLock and Unlock.
The sceen saver is troublesome too. Your main window gets the WM_SYSCOMMAND message, SC_SCREENSAVE when the screen saver turns on. You can pinvoke SystemParametersInfo to check if it running. You'll find sample code for this in my answer in this thread.
There is no good way to find out if the user fell asleep.
I have recently checked this code again from a previous blog post to ensure it works on versions of Windows XP to 7, x86 and x64 and cleaned it up a bit.
Here is the latest minimalist code that checks if the workstation is locked and if the screensaver is running wrapped in two easy to use static methods:
using System;
using System.Runtime.InteropServices;
namespace BrutalDev.Helpers
{
public static class NativeMethods
{
// Used to check if the screen saver is running
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SystemParametersInfo(uint uAction,
uint uParam,
ref bool lpvParam,
int fWinIni);
// Used to check if the workstation is locked
[DllImport("user32", SetLastError = true)]
private static extern IntPtr OpenDesktop(string lpszDesktop,
uint dwFlags,
bool fInherit,
uint dwDesiredAccess);
[DllImport("user32", SetLastError = true)]
private static extern IntPtr OpenInputDesktop(uint dwFlags,
bool fInherit,
uint dwDesiredAccess);
[DllImport("user32", SetLastError = true)]
private static extern IntPtr CloseDesktop(IntPtr hDesktop);
[DllImport("user32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SwitchDesktop(IntPtr hDesktop);
// Check if the workstation has been locked.
public static bool IsWorkstationLocked()
{
const int DESKTOP_SWITCHDESKTOP = 256;
IntPtr hwnd = OpenInputDesktop(0, false, DESKTOP_SWITCHDESKTOP);
if (hwnd == IntPtr.Zero)
{
// Could not get the input desktop, might be locked already?
hwnd = OpenDesktop("Default", 0, false, DESKTOP_SWITCHDESKTOP);
}
// Can we switch the desktop?
if (hwnd != IntPtr.Zero)
{
if (SwitchDesktop(hwnd))
{
// Workstation is NOT LOCKED.
CloseDesktop(hwnd);
}
else
{
CloseDesktop(hwnd);
// Workstation is LOCKED.
return true;
}
}
return false;
}
// Check if the screensaver is busy running.
public static bool IsScreensaverRunning()
{
const int SPI_GETSCREENSAVERRUNNING = 114;
bool isRunning = false;
if (!SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0))
{
// Could not detect screen saver status...
return false;
}
if (isRunning)
{
// Screen saver is ON.
return true;
}
// Screen saver is OFF.
return false;
}
}
}
UPDATE: Code updated based on suggestions in the comments.
When the workstation is locked then the OpenInputDesktop method does not return a handle so we can fall-back on OpenDesktop for a handle to make sure it's locked by trying to switch. If it's not locked then your default desktop will not be activated since OpenInputDesktop will return a valid handle for the desktop you are viewing.
Use SystemParametersInfo to detect whether screen saver is running - the calltype is SPI_GETSCREENSAVERRUNNING. This is supported on Win2000 and above.
There is code from @dan_g on StackOverflow here to check if wksta is locked.
SystemEvents.SessionSwitch += new SessionSwitchEventHandler((sender, e) =>
{
switch (e.Reason)
{
//If Reason is Lock, Turn off the monitor.
case SessionSwitchReason.SessionLock:
//SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOR_OFF);
MessageBox.Show("locked");
break;
case SessionSwitchReason.SessionUnlock:
MessageBox.Show("unlocked");
break;
}
});
This will indicate when the session is Locked and Unlocked.
There are many reason why the user can't see your notifications also for example full screen video playback or that the user is just not there.
I suggest that instead of checking if the notification can be displayed check if the user is there, you can do that by monitoring the keyboard and mouse.
In researching this question I found a couple techniques that support detecting if the workstation is locked. One is really simple:
bool locked = Process.GetProcessesByName("logonui").Any();
This works because the logonui process is only running when the desktop is locked.
Another more complicated approach is to walk the system event log backwards looking for event ids 4800 and 4801. These indicate when a machine is locked and unclocked.
More detail can be found at:
https://superuser.com/questions/1170918/determine-remote-windows-screen-locked-or-unlocked-remotely
http://mctexpert.blogspot.com/2012/10/how-to-determine-if-client-on-your.html
The latter requires audit policies to be set which are the default anyways. I work in an enterprise IT organization so it's not a concern for me as I'm certain those settings are applied.
精彩评论