Alternative way to check if CTRL+ALT+DEL screen is visible

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