Skip navigation links

Source code for C_DesktopWaiter

Download

The source files for this module are listed below.  You can also download the module C_DesktopWaiter as a zip archive; this archive contains all the source files and documentation.

Description

CDesktopWaiter can make your code wait until the desktop that your application is running on is active again (e.g. the user logged back in from the Windows login screen).

Information

This source code also uses our C_DLLLoader, C_WinVersion, C_Timer and C_SignalSlot classes.  You can also download these at our website.

Windows NT3.51, NT4, XP, 2003 and Vista computers have the concept of multiple desktops.  Generally all applications run on one desktop, and the Windows logon prompt and screensavers also run in their own private desktops.  Sometimes it is necessary to know what desktop the user is looking at; is the user busy in the logon desktop or the screensaver desktop, or is he active on the desktop our application runs on?  The class CDesktopWaiter can wait for the desktop of the currently running application (actually thread) to become active.

Before you decide to wait on the desktop to become active, you must first check with the method CanWait to see if waiting is supported under the current operating system.  Like said above, only the NT lineage supports waiting.  All needed API functions that are used internally are loaded dynamically though, so this class can be used in applications that run on Windows 95, 98 and ME as well.

You can consult with the method OurDesktopActive anytime to check if our desktop is active.  In case you do need to wait for our desktop to become active and do not want to use a polling loop, you can call the StartWaiting method.  This method will return immediately if our desktop is the active one, but starts a poll on a timer (500 msec.) if it isn't and then returns as well.  Once our desktop becomes active again, the CDesktopWaiter object will raise the CUserOnOurDesktopEvent event to notify you our desktop is active.

Notes / todo's

  • This code is as of yet untested on Windows NT 3.51, NT 4, 2003 and Vista.  If you have tested it on these systems, please tell me what you found out!
  • The code now uses a Windows timer based polling mechanism; maybe this could be improved upon by using some other Windows API.
  • When the Windows logon desktop is active ("Winlogon"), this code fails to get a handle to this desktop via OpenInputDesktop, but GetLastError still returns 0.  Relying on this to detect the "Winlogon" desktop makes me a bit uncomfortable, but I see no other way to do it and others also suggest to use this method.

Files

Each file belonging to this source code module is listed below.

DesktopWaiter.h

/*******************************************************************************

  Version: 3
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  http://www.twologs.com/en/resources/sourcecode.asp

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/
#ifndef INCLUDE_TWOLOGS_COMMON_DESKTOPWAITER_H
#define INCLUDE_TWOLOGS_COMMON_DESKTOPWAITER_H

#include <windows.h>
#include "..\C_DLLLoader\DLLLoader.h"
#include "..\C_WinVersion\WinVersion.h"
#include "..\C_Timer\Timer.h"

// Notifies when the user is back on our desktop
struct CUserOnOurDesktopEvent: public CEventTypeNoResult {
};

// The logon waiter
class CDesktopWaiter:
  public CSlot<CTimerWentOffEvent>,
  public CSignal<CUserOnOurDesktopEvent> {
public:
  // Con- & destructor
  CDesktopWaiter();
  virtual ~CDesktopWaiter();

  // Whether we can wait on desktop switches
  bool CanWait();

  // Starts waiting till the user is on our desktop, if not doing so already
  // A timer will be attached to the given window for polling reasons
  // When waiting is done, the CUserOnOurDesktopEvent will be raised
  // Returns if we started waiting (if our desktop is not active yet)
  bool StartWaiting(HWND hWindow);

  // Returns if our own desktop is active
  bool OurDesktopActive();

private:
  // Whether we're waiting
  bool m_bWaiting;

  // Our polling timer
  CTimer m_oTimer;

  // The loaded API function
  static CDLLLoader m_oUser32Dll;
  static bool m_bAPIsLoaded;
  static bool m_bMustInitAPIs;

  // Loads the API's
  static void LoadAPIs();

  // Gets the name of the given desktop
  std::string GetDesktopName(HDESK hDesktop);

  // Events
  bool OnEvent(CTimerWentOffEvent& oEvent);
};

#endif // INCLUDE_TWOLOGS_COMMON_DESKTOPWAITER_H

DesktopWaiter.cpp

#include "DesktopWaiter.h"

// Needed API signatures
typedef HDESK WINAPI FOpenInputDesktop(DWORD dwFlags, BOOL fInherit,
  ACCESS_MASK dwDesiredAccess);
typedef BOOL WINAPI FGetUserObjectInformation(HANDLE hObj, int nIndex,
  PVOID pvInfo, DWORD nLength, LPDWORD lpnLengthNeeded);
typedef BOOL WINAPI FCloseDesktop(HDESK hDesktop);
FOpenInputDesktop* g_fOpenInputDesktop;
FGetUserObjectInformation* g_fGetUserObjectInformation;
FCloseDesktop* g_fCloseDesktop;

// Static class members
CDLLLoader CDesktopWaiter::m_oUser32Dll;
bool CDesktopWaiter::m_bAPIsLoaded = false;
bool CDesktopWaiter::m_bMustInitAPIs = true;

// Con- & destructor
CDesktopWaiter::CDesktopWaiter():
 m_bWaiting(false) {
  // Load the needed API's, if needed
  if (m_bMustInitAPIs) {
    LoadAPIs();
  }

  // And connect to our timer
  CSlot<CTimerWentOffEvent>::Connect(&m_oTimer);
}
CDesktopWaiter::~CDesktopWaiter() {
  // Kill any timer we're waiting on
  m_oTimer.Stop(false);
}

// Whether we can wait on desktop switches
bool CDesktopWaiter::CanWait() {
  return g_oWinVersion.bIsNT;
}

// Starts waiting till the user is on our desktop, if not doing so already
// A timer will be attached to the given window for polling reasons
// When waiting is done, the CUserOnOurDesktopEvent will be raised
// Returns if we started waiting (if our desktop is not active yet)
bool CDesktopWaiter::StartWaiting(HWND hWindow) {
  // Look if our desktop is active
  bool bNeedToWait = !OurDesktopActive();

  // And look if to start waiting
  if (bNeedToWait && !m_bWaiting) {
    // Yes -> init our timer on this window
    m_oTimer.Initialize(hWindow);

    // And start it
    m_oTimer.Start(500);
    m_bWaiting = true;
  }

  // And return if we're waiting
  return bNeedToWait;
}

// Loads the API's
void CDesktopWaiter::LoadAPIs() {
  m_oUser32Dll.Load("user32.dll");
  g_fOpenInputDesktop =
    m_oUser32Dll.GetFunctionByName<FOpenInputDesktop>("OpenInputDesktop");
  g_fGetUserObjectInformation =
    m_oUser32Dll.GetFunctionByName<FGetUserObjectInformation>(
      "GetUserObjectInformationA"
    );
  g_fCloseDesktop =
    m_oUser32Dll.GetFunctionByName<FCloseDesktop>("CloseDesktop");
  m_bAPIsLoaded = g_fOpenInputDesktop != NULL &&
                  g_fGetUserObjectInformation != NULL &&
                  g_fCloseDesktop != NULL;
  m_bMustInitAPIs = false;
}

// Gets the name of the given desktop
std::string CDesktopWaiter::GetDesktopName(HDESK hDesktop) {
  // Get it's name using a small buffer first
  std::string sDesktopName;
  sDesktopName.resize(256);
  DWORD nNeededNameSize;
  BOOL bInfoGotten = g_fGetUserObjectInformation(
    hDesktop,
    UOI_NAME,
    const_cast<char*>(sDesktopName.c_str()),
    255,
    &nNeededNameSize
  );
  if (bInfoGotten) {
    // Done -> return the name
    sDesktopName.at(nNeededNameSize) = '\0';
    sDesktopName.resize(nNeededNameSize);
  } else {
    // Couldn't -> get the full size name
    sDesktopName.resize(nNeededNameSize + 1);
    bInfoGotten = g_fGetUserObjectInformation(
      hDesktop,
      UOI_NAME,
      const_cast<char*>(sDesktopName.c_str()),
      nNeededNameSize,
      &nNeededNameSize
    );
    if (bInfoGotten) {
      // Done -> make sure the name is correctly truncated
      sDesktopName.at(nNeededNameSize) = '\0';
      sDesktopName.resize(nNeededNameSize);
    }
  }

  // And return the found name
  return sDesktopName;
}

// Returns if our own desktop is active
bool CDesktopWaiter::OurDesktopActive() {
  // Look if the API's are loaded
  bool bOurDesktopActive = true;
  if (m_bAPIsLoaded) {
    // Yes -> get the active desktop
    SetLastError(0);
    HDESK hActiveDesktop = g_fOpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
    if (hActiveDesktop == NULL && GetLastError() == 0) {
      // Couldn't, but no error -> must be the Winlogon desktop
      bOurDesktopActive = false;
    } else if (hActiveDesktop != NULL) {
      // Done -> get our desktop
      HDESK hOurDesktop = GetThreadDesktop(GetCurrentThreadId());

      // Get the names of the desktops
      std::string sOurDesktopName = GetDesktopName(hOurDesktop);
      std::string sActiveDesktopName = GetDesktopName(hActiveDesktop);

      // Look if our desktop is active
      bOurDesktopActive =
        0 == strcmp(sOurDesktopName.c_str(), sActiveDesktopName.c_str());

      // And close the active desktop, if needed
      g_fCloseDesktop(hActiveDesktop);
    }
  }

  // And return if our desktop is active
  return bOurDesktopActive;
}

// Events
bool CDesktopWaiter::OnEvent(CTimerWentOffEvent& oEvent) {
  // Look if our desktop is active now
  if (OurDesktopActive()) {
    // Yes -> stop the timer
    m_oTimer.Stop(false);
    m_bWaiting = false;

    // And notify everyone who needs to know
    CUserOnOurDesktopEvent oEvent;
    CSignal<CUserOnOurDesktopEvent>::RaiseEvent(oEvent);
  }
  return true;
}