livlif2fullest
livlif2fullest

Reputation: 61

How to tell when the user returns from the Ctrl-Alt-Del screen from Windows

I have an application that displays video using Direct3D written by a 3rd party which is a huge ordeal to get them to change anything so I want to work around that by writing my own code to handle when a user hits Ctrl-Alt-Del to lock their workstation, but doesn't. When this happens the 3rd party library throws an exception with the message 'Direct3D device lost', however, I'm attempting to write error handling logic to reset the 3rd party library once this occurs, the problem is if the user doesn't actually lock their workstation I don't know how to tell when they come back. Situation:

  1. User hits Ctrl-Alt-Del
  2. Windows displays screen for user to choose lock, sign off, change password, etc.
  3. User hits Esc or clicks the Cancel button
  4. Windows displays user's desktop.

I don't know how to tell when the user does the above situation at step #3 and I want to know that the Direct3D device is basically OK to reset the 3rd party library at step #4. I've already hooked into the SystemEvents.SessionEnding and SystemEvents.SessionSwitch events, yet those aren't fired if the user hits Esc or clicks the Cancel button.

Any thoughts on how I can capture the events from #3, 4?

Upvotes: 2

Views: 738

Answers (2)

Mitch
Mitch

Reputation: 22251

This topic was recently covered on the blog The Old New Thing by @RaymondChen. His basic suggestion was if the graphics resource is the thing you are trying to acquire, to just try to acquire it and see if it succeeds (which avoids false positives and TOCTOU).

As an aside, he also answers the original question the same way as @RezaAghaei by suggesting EVENT_SYSTEM_DESKTOP­SWITCH, and gives an option for polling if your thread is on the input desktop:

// From https://devblogs.microsoft.com/oldnewthing/20200429-00/?p=103715
BOOL IsCurrentThreadOnInputDesktop()
{
  BOOL result;
  HDESK desktop = GetThreadDesktop(GetCurrentThreadId());
  return desktop &&
         GetUserObjectInformation(desktop, UOI_IO, &result,
                                  sizeof(result), nullptr) &&
         result;
}

Upvotes: 1

Reza Aghaei
Reza Aghaei

Reputation: 125197

Using SetWinEventHook you can listen to some events from other processes and register a WinEventProc callback method to receive the event when the event raised.

Here we are interested to EVENT_SYSTEM_DESKTOPSWITCH.

Some windows like the Ctrl + Alt + Del Window, or User Account Control Dialog open in another desktop and when those desktops open or when those desktops close and switch back to the original desktop, the EVENT_SYSTEM_DESKTOPSWITCH event will raise.

Here we are interested to that instance of the event which happens when we close the other desktop and come back to the original desktop, so in the event handler, you can check if your window is the active window, then it means you have switched back.

Example

Here is what I tried and worked well:

private const uint WINEVENT_OUTOFCONTEXT = 0;
private const uint WINEVENT_SKIPOWNPROCESS = 0x0002;
private const uint EVENT_SYSTEM_DESKTOPSWITCH = 0x0020;

private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd,
    int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
    hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
    uint idThread, uint dwFlags);

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);

IntPtr hook = IntPtr.Zero;
protected override void OnLoad(EventArgs e) {
    base.OnLoad(e);
    hook = SetWinEventHook(EVENT_SYSTEM_DESKTOPSWITCH, EVENT_SYSTEM_DESKTOPSWITCH,
    IntPtr.Zero, new WinEventDelegate(WinEventProc),
    0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
}
protected override void OnFormClosing(FormClosingEventArgs e) {
    UnhookWinEvent(hook);
    base.OnFormClosing(e);
}
void WinEventProc(IntPtr hWinEventHook, uint eventType,
    IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) {
    if (GetForegroundWindow() == this.Handle)
        MessageBox.Show("Back to the original screen!");
}

Upvotes: 2

Related Questions