When you press CTRL+ALT+DEL, Windows switches to a another special virtual desktopa that hosts the winlogon
process that is responsible for user login/logoff/lock etc. actions. By using the WinAPI function SetWinEventHook
with the EVENT_SYSTEM_DESKTOPSWITCH
argument you can set up a callback function that is called whenever such a desktop switch occurs:
//Store the callback in a variable so that it is not GC'd
private static readonly WinEventDelegate callback = EventCallback;
static void StartListeningForDesktopSwitch()
{
SetWinEventHook(EVENT_SYSTEM_DESKTOPSWITCH, EVENT_SYSTEM_DESKTOPSWITCH,
IntPtr.Zero, callback, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNTHREAD);
}
static void EventCallback(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
Console.WriteLine("Desktop switched");
}
Note: If you want to use this in a console application, you have to add a message loop by adding a hidden Form
:
static void Main(string[] args)
{
StartListeningForDesktopSwitch();
// Run message loop
Application.Run(new HiddenForm());
}
private class HiddenForm : Form
{
public HiddenForm()
{
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
}
}
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread,
uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
uint idThread, uint dwFlags);
const uint WINEVENT_OUTOFCONTEXT = 0x0000;
const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
const uint EVENT_SYSTEM_DESKTOPSWITCH = 0x0020;
Further: The desktop switch also occurs when the user pressed Win+L or a UAC window pops up. Thus, we need a way to detect these other cases. The UAC case is rather trivial, it is enough to check if the process consent.exe
is running during the callback function:
var processes = Process.GetProcessesByName("consent");
if (processes.Length == 0)
Console.WriteLine("This is not a UAC prompt");
The other case, unfortunately, is a bit more complicated. I have only managed to detect wheter a user returns from a lock screen, but not whether they enter it (as you said, this is not relevant for you, but I wanted to mention it anyway).
Detecting whether the session is locked can be done by listening for the
SystemEvents.SessionSwitch
event in our HiddenForm
. The SessionSwitchEventArgs.Reason
property is set to SessionSwitchReason.SessionLock
if this is a lock event, and to SessionSwitchReason.SessionUnlock
if the user unlocks. We only can tell whether a desktop switch was not to the lock screen desktop when we are switching back to the default desktop since the switch desktop event callbacks are called before a session lock and after a session unlock. This leads to the following code for a sample console application:
private static readonly WinEventDelegate callback = EventCallback;
static void Main(string[] args)
{
SetWinEventHook(EVENT_SYSTEM_DESKTOPSWITCH,
EVENT_SYSTEM_DESKTOPSWITCH, IntPtr.Zero, callback, 0, 0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNTHREAD);
Application.Run(new HiddenForm());
}
private class HiddenForm : Form
{
public HiddenForm()
{
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
if (e.Reason == SessionSwitchReason.SessionUnlock)
wasUnlocked = true;
}
}
static bool wasUnlocked = false;
static bool wasOpened = false;
static void EventCallback(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
// Check if UAC dialog is being shown
var processes = Process.GetProcessesByName("consent");
if (processes.Length == 0)
{
if (wasOpened)
{
if (!wasUnlocked)
Console.WriteLine("Exited from CTRL+ALT+DEL");
wasUnlocked = false;
wasOpened = false;
}
else
wasOpened = true;
}
}
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread,
uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
uint idThread, uint dwFlags);
const uint WINEVENT_OUTOFCONTEXT = 0x0000;
const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
const uint EVENT_SYSTEM_DESKTOPSWITCH = 0x0020;
a This type of virtual desktop has nothing to do with the newly introduced "virtual desktop" feature in Windows 10