//---------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997 // // File: msidle.cpp // // Contents: user idle detection // // Classes: // // Functions: // // History: 05-14-1997 darrenmi (Darren Mitchell) Created // //---------------------------------------------------------------------------- #include #include #include #include #include "msidle.h" #include "resource.h" // useful things... #ifndef ARRAYSIZE #define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0])) #endif // // Global unshared variables // DWORD g_dwIdleMin = 0; // inactivity minutes before idle UINT_PTR g_uIdleTimer = 0; // idle timer for this process BOOL g_fIdleNotify = FALSE; // notify when idle BOOL g_fBusyNotify = FALSE; // notify when busy BOOL g_fIsWinNT = FALSE; // which platform? BOOL g_fIsWinNT5 = FALSE; // are we running on NT5? BOOL g_fIsWhistler = FALSE; // are we running on Whistler? HANDLE g_hSageVxd = INVALID_HANDLE_VALUE; // handle to sage.vxd DWORD g_dwIdleBeginTicks = 0; // ticks when we became idle HINSTANCE g_hInst = NULL; // dll instance _IDLECALLBACK g_pfnCallback = NULL; // function to call back in client #ifdef MSIDLE_DOWNLEVEL // // Global shared variables // #pragma data_seg(".shrdata") HHOOK sg_hKbdHook = NULL, sg_hMouseHook = NULL; DWORD sg_dwLastTickCount = 0; POINT sg_pt = {0,0}; #pragma data_seg() // // Prototypes // LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK KbdProc(int nCode, WPARAM wParam, LPARAM lParam); #endif // MSIDLE_DOWNLEVEL VOID CALLBACK OnIdleTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); // // From winuser.h, but NT5 only // #if (_WIN32_WINNT < 0x0500) typedef struct tagLASTINPUTINFO { UINT cbSize; DWORD dwTime; } LASTINPUTINFO, * PLASTINPUTINFO; #endif // // NT5 api we dynaload from user32 // typedef WINUSERAPI BOOL (WINAPI* PFNGETLASTINPUTINFO)(PLASTINPUTINFO plii); PFNGETLASTINPUTINFO pfnGetLastInputInfo = NULL; /////////////////////////////////////////////////////////////////////////// // // Internal functions // /////////////////////////////////////////////////////////////////////////// #ifdef DEBUG inline BOOL IsRegMultiSZType(DWORD dwType) { return (REG_MULTI_SZ == dwType); } inline BOOL IsRegStringType(DWORD dwType) { return (REG_SZ == dwType) || (REG_EXPAND_SZ == dwType) || IsRegMultiSZType(dwType); } LONG SafeRegQueryValueEx( IN HKEY hKey, IN PCTSTR lpValueName, IN LPDWORD lpReserved, OUT LPDWORD lpType, IN OUT LPBYTE lpData, IN OUT LPDWORD lpcbData ) { DWORD dwType; DWORD cbData = lpcbData ? *lpcbData : 0; // We always care about the type even if the caller doesn't if (!lpType) { lpType = &dwType; } LONG lResult = RegQueryValueEx(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); // need to make sure we NULL terminate strings. if ((ERROR_SUCCESS == lResult) && lpData && IsRegStringType(*lpType)) { if (cbData >= sizeof(TCHAR)) { TCHAR *psz = (TCHAR *)lpData; DWORD cch = cbData / sizeof(TCHAR); psz[cch - 1] = 0; // and make sure that REG_MULTI_SZ strings are double NULL terminated if (IsRegMultiSZType(*lpType)) { if (cbData >= (sizeof(TCHAR) * 2)) { psz[cch - 2] = 0; } } } } return lResult; } BOOL ReadRegValue(HKEY hkeyRoot, const TCHAR *pszKey, const TCHAR *pszValue, void *pData, DWORD dwBytes) { long lResult; HKEY hkey; DWORD dwType; lResult = RegOpenKeyEx(hkeyRoot, pszKey, 0, KEY_READ, &hkey); if (lResult != ERROR_SUCCESS) { return FALSE; } lResult = SafeRegQueryValueEx(hkey, pszValue, NULL, &dwType, (BYTE *)pData, &dwBytes); RegCloseKey(hkey); if (lResult != ERROR_SUCCESS) return FALSE; return TRUE; } TCHAR *g_pszLoggingFile; BOOL g_fCheckedForLog = FALSE; DWORD LogEvent(LPTSTR pszFormat, ...) { // check registry if necessary if(FALSE == g_fCheckedForLog) { TCHAR pszFilePath[MAX_PATH]; if(ReadRegValue(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\msidle"), TEXT("LoggingFile"), pszFilePath, sizeof(pszFilePath))) { g_pszLoggingFile = (TCHAR *)LocalAlloc(LPTR, lstrlen(pszFilePath) + 1); if(g_pszLoggingFile) { lstrcpyn(g_pszLoggingFile, pszFilePath, ARRAYSIZE(g_pszLoggingFile)); } } g_fCheckedForLog = TRUE; } if(g_pszLoggingFile) { TCHAR pszString[1025]; SYSTEMTIME st; HANDLE hLog; DWORD dwWritten; va_list va; hLog = CreateFile(g_pszLoggingFile, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(INVALID_HANDLE_VALUE == hLog) return GetLastError(); // seek to end of file SetFilePointer(hLog, 0, 0, FILE_END); // dump time GetLocalTime(&st); // Safe to call wsprintf since the buffer is 1025 (wsprintf has a built in 1024 limit) wsprintf(pszString, "%02d:%02d:%02d [%x] - ", st.wHour, st.wMinute, st.wSecond, GetCurrentThreadId()); WriteFile(hLog, pszString, lstrlen(pszString), &dwWritten, NULL); OutputDebugString(pszString); // dump passed in string va_start(va, pszFormat); // Safe to call wvsprintf since the buffer is 1025 (wsprintf has a built in 1024 limit) wvsprintf(pszString, pszFormat, va); va_end(va); WriteFile(hLog, pszString, lstrlen(pszString), &dwWritten, NULL); OutputDebugString(pszString); // cr WriteFile(hLog, "\r\n", 2, &dwWritten, NULL); OutputDebugString("\r\n"); // clean up CloseHandle(hLog); } return 0; } #endif // DEBUG // // SetIdleTimer - decide how often to poll and set the timer appropriately // void SetIdleTimer(void) { UINT uInterval = 1000 * 60; // // If we're looking for loss of idle, check every 4 seconds // if(TRUE == g_fBusyNotify) { uInterval = 1000 * 4; } // // kill off the old timer // if(g_uIdleTimer) { KillTimer(NULL, g_uIdleTimer); } // // Set the timer // g_uIdleTimer = SetTimer(NULL, 0, uInterval, OnIdleTimer); } DWORD GetLastActivityTicks(void) { DWORD dwLastActivityTicks = 0; if (g_fIsWhistler) { dwLastActivityTicks = USER_SHARED_DATA->LastSystemRITEventTickCount; } else if(g_fIsWinNT5 && pfnGetLastInputInfo) { // NT5: Use get last input time API LASTINPUTINFO lii; memset(&lii, 0, sizeof(lii)); lii.cbSize = sizeof(lii); (*pfnGetLastInputInfo)(&lii); dwLastActivityTicks = lii.dwTime; } else { // NT4 or Win95: Use sage if it's loaded if(INVALID_HANDLE_VALUE != g_hSageVxd) { // query sage.vxd for tick count DeviceIoControl(g_hSageVxd, 2, &dwLastActivityTicks, sizeof(DWORD), NULL, 0, NULL, NULL); } #ifdef MSIDLE_DOWNLEVEL else { // use hooks dwLastActivityTicks = sg_dwLastTickCount; } #endif // MSIDLE_DOWNLEVEL } return dwLastActivityTicks; } // // OnIdleTimer - idle timer has gone off // VOID CALLBACK OnIdleTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { DWORD dwDiff, dwLastActivityTicks; BOOL fTempBusyNotify = g_fBusyNotify; BOOL fTempIdleNotify = g_fIdleNotify; // // get last activity ticks from sage or shared segment // dwLastActivityTicks = GetLastActivityTicks(); #ifdef DEBUG LogEvent("OnIdleTimer: dwLastActivity=%d, CurrentTicks=%d, dwIdleBegin=%d", dwLastActivityTicks, GetTickCount(), g_dwIdleBeginTicks); #endif // // check to see if we've changed state // if(fTempBusyNotify) { // // Want to know if we become busy // if(dwLastActivityTicks != g_dwIdleBeginTicks) { // activity since we became idle - stop being idle! g_fBusyNotify = FALSE; g_fIdleNotify = TRUE; // set the timer SetIdleTimer(); // call back client #ifdef DEBUG LogEvent("OnIdleTimer: Idle Ends"); #endif if(g_pfnCallback) (g_pfnCallback)(STATE_USER_IDLE_END); } } if(fTempIdleNotify) { // // Want to know if we become idle // dwDiff = GetTickCount() - dwLastActivityTicks; if(dwDiff > 1000 * 60 * g_dwIdleMin) { // Nothing's happened for our threshold time. We're now idle. g_fIdleNotify = FALSE; g_fBusyNotify = TRUE; // save time we became idle g_dwIdleBeginTicks = dwLastActivityTicks; // set the timer SetIdleTimer(); // call back client #ifdef DEBUG LogEvent("OnIdleTimer: Idle Begins"); #endif if(g_pfnCallback) (g_pfnCallback)(STATE_USER_IDLE_BEGIN); } } } BOOL LoadSageVxd(void) { int inpVXD[3]; if(INVALID_HANDLE_VALUE != g_hSageVxd) return TRUE; g_hSageVxd = CreateFile("\\\\.\\sage.vxd", 0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL); // can't open it? can't use it if(INVALID_HANDLE_VALUE == g_hSageVxd) return FALSE; // start it monitoring inpVXD[0] = -1; // no window - will query inpVXD[1] = 0; // unused inpVXD[2] = 0; // how long to wait between checks DeviceIoControl(g_hSageVxd, 1, &inpVXD, sizeof(inpVXD), NULL, 0, NULL, NULL); return TRUE; } BOOL UnloadSageVxd(void) { if(INVALID_HANDLE_VALUE != g_hSageVxd) { CloseHandle(g_hSageVxd); g_hSageVxd = INVALID_HANDLE_VALUE; } return TRUE; } /////////////////////////////////////////////////////////////////////////// // // Externally callable functions // /////////////////////////////////////////////////////////////////////////// // // LibMain - dll entry point // EXTERN_C BOOL WINAPI LibMain(HINSTANCE hInst, ULONG ulReason, LPVOID pvRes) { switch (ulReason) { case DLL_PROCESS_ATTACH: { OSVERSIONINFO vi; DisableThreadLibraryCalls(hInst); g_hInst = hInst; vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&vi); if(vi.dwPlatformId == VER_PLATFORM_WIN32_NT) { g_fIsWinNT = TRUE; if(vi.dwMajorVersion >= 5) { if (vi.dwMajorVersion > 5 || vi.dwMinorVersion > 0 || LOWORD(vi.dwBuildNumber) > 2410) { g_fIsWhistler = TRUE; } else { g_fIsWinNT5 = TRUE; } } } } break; } return TRUE; } // // BeginIdleDetection // DWORD BeginIdleDetection(_IDLECALLBACK pfnCallback, DWORD dwIdleMin, DWORD dwReserved) { DWORD dwValue = 0; // make sure reserved is 0 if(dwReserved) return ERROR_INVALID_DATA; #ifdef DEBUG LogEvent("BeginIdleDetection: IdleMin=%d", dwIdleMin); #endif // save callback g_pfnCallback = pfnCallback; // save minutes g_dwIdleMin = dwIdleMin; // call back on idle g_fIdleNotify = TRUE; if(FALSE == g_fIsWinNT) { // try to load sage.vxd LoadSageVxd(); } if(g_fIsWinNT5) { // we need to find our NT5 api in user HINSTANCE hUser = GetModuleHandle("user32.dll"); if(hUser) { pfnGetLastInputInfo = (PFNGETLASTINPUTINFO)GetProcAddress(hUser, "GetLastInputInfo"); } if(NULL == pfnGetLastInputInfo) { // not on NT5 - bizarre g_fIsWinNT5 = FALSE; } } #ifdef MSIDLE_DOWNLEVEL if(INVALID_HANDLE_VALUE == g_hSageVxd && FALSE == g_fIsWinNT5 && FALSE == g_fIsWhistler) { // sage vxd not available - do it the hard way // hook kbd sg_hKbdHook = SetWindowsHookEx(WH_KEYBOARD, KbdProc, g_hInst, 0); if(NULL == sg_hKbdHook) return GetLastError(); // hook mouse sg_hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0); if(NULL == sg_hMouseHook) { DWORD dwError = GetLastError(); EndIdleDetection(0); return dwError; } } #endif // MSIDLE_DOWNLEVEL // Fire up the timer SetIdleTimer(); return 0; } // // IdleEnd - stop idle monitoring // BOOL EndIdleDetection(DWORD dwReserved) { // ensure reserved is 0 if(dwReserved) return FALSE; // free up sage if we're using it UnloadSageVxd(); // kill timer if(g_uIdleTimer) { KillTimer(NULL, g_uIdleTimer); g_uIdleTimer = 0; } // callback is no longer valid g_pfnCallback = NULL; #ifdef MSIDLE_DOWNLEVEL // free up hooks if(sg_hKbdHook) { UnhookWindowsHookEx(sg_hKbdHook); sg_hKbdHook = NULL; } if(sg_hMouseHook) { UnhookWindowsHookEx(sg_hMouseHook); sg_hMouseHook = NULL; } #endif // MSIDLE_DOWNLEVEL return TRUE; } // // SetIdleMinutes - set the timout value and reset idle flag to false // // dwMinutes - if non-0, set idle timeout to that many minutes // fIdleNotify - call back when idle for at least idle minutes // fBusyNotify - call back on activity since Idle begin // BOOL SetIdleTimeout(DWORD dwMinutes, DWORD dwReserved) { if(dwReserved) return FALSE; #ifdef DEBUG LogEvent("SetIdleTimeout: dwIdleMin=%d", dwMinutes); #endif if(dwMinutes) g_dwIdleMin = dwMinutes; return TRUE; } // // SetIdleNotify - set flag to turn on or off idle notifications // // fNotify - flag // dwReserved - must be 0 // void SetIdleNotify(BOOL fNotify, DWORD dwReserved) { #ifdef DEBUG LogEvent("SetIdleNotify: fNotify=%d", fNotify); #endif g_fIdleNotify = fNotify; } // // SetIdleNotify - set flag to turn on or off idle notifications // // fNotify - flag // dwReserved - must be 0 // void SetBusyNotify(BOOL fNotify, DWORD dwReserved) { #ifdef DEBUG LogEvent("SetBusyNotify: fNotify=%d", fNotify); #endif g_fBusyNotify = fNotify; if(g_fBusyNotify) g_dwIdleBeginTicks = GetLastActivityTicks(); // set the timer SetIdleTimer(); } // // GetIdleMinutes - return how many minutes since last user activity // DWORD GetIdleMinutes(DWORD dwReserved) { if(dwReserved) return 0; return (GetTickCount() - GetLastActivityTicks()) / 60000; } #ifdef MSIDLE_DOWNLEVEL /////////////////////////////////////////////////////////////////////////// // // Hook functions // /////////////////////////////////////////////////////////////////////////// // // Note: These functions can be called back in any process! // LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { MOUSEHOOKSTRUCT * pmsh = (MOUSEHOOKSTRUCT *)lParam; if(nCode >= 0) { // ignore mouse move messages to the same point as all window // creations cause these - it doesn't mean the user moved the mouse if(WM_MOUSEMOVE != wParam || pmsh->pt.x != sg_pt.x || pmsh->pt.y != sg_pt.y) { sg_dwLastTickCount = GetTickCount(); sg_pt = pmsh->pt; } } return(CallNextHookEx(sg_hMouseHook, nCode, wParam, lParam)); } LRESULT CALLBACK KbdProc(int nCode, WPARAM wParam, LPARAM lParam) { if(nCode >= 0) { sg_dwLastTickCount = GetTickCount(); } return(CallNextHookEx(sg_hKbdHook, nCode, wParam, lParam)); } #endif // MSIDLE_DOWNLEVEL