Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

651 lines
16 KiB

  1. //----------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1997
  5. //
  6. // File: msidle.cpp
  7. //
  8. // Contents: user idle detection
  9. //
  10. // Classes:
  11. //
  12. // Functions:
  13. //
  14. // History: 05-14-1997 darrenmi (Darren Mitchell) Created
  15. //
  16. //----------------------------------------------------------------------------
  17. #include <nt.h>
  18. #include <ntrtl.h>
  19. #include <nturtl.h>
  20. #include <windows.h>
  21. #include "msidle.h"
  22. #include "resource.h"
  23. // useful things...
  24. #ifndef ARRAYSIZE
  25. #define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0]))
  26. #endif
  27. //
  28. // Global unshared variables
  29. //
  30. DWORD g_dwIdleMin = 0; // inactivity minutes before idle
  31. UINT_PTR g_uIdleTimer = 0; // idle timer for this process
  32. BOOL g_fIdleNotify = FALSE; // notify when idle
  33. BOOL g_fBusyNotify = FALSE; // notify when busy
  34. BOOL g_fIsWinNT = FALSE; // which platform?
  35. BOOL g_fIsWinNT5 = FALSE; // are we running on NT5?
  36. BOOL g_fIsWhistler = FALSE; // are we running on Whistler?
  37. HANDLE g_hSageVxd = INVALID_HANDLE_VALUE;
  38. // handle to sage.vxd
  39. DWORD g_dwIdleBeginTicks = 0; // ticks when we became idle
  40. HINSTANCE g_hInst = NULL; // dll instance
  41. _IDLECALLBACK g_pfnCallback = NULL; // function to call back in client
  42. #ifdef MSIDLE_DOWNLEVEL
  43. //
  44. // Global shared variables
  45. //
  46. #pragma data_seg(".shrdata")
  47. HHOOK sg_hKbdHook = NULL, sg_hMouseHook = NULL;
  48. DWORD sg_dwLastTickCount = 0;
  49. POINT sg_pt = {0,0};
  50. #pragma data_seg()
  51. //
  52. // Prototypes
  53. //
  54. LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam);
  55. LRESULT CALLBACK KbdProc(int nCode, WPARAM wParam, LPARAM lParam);
  56. #endif // MSIDLE_DOWNLEVEL
  57. VOID CALLBACK OnIdleTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
  58. //
  59. // From winuser.h, but NT5 only
  60. //
  61. #if (_WIN32_WINNT < 0x0500)
  62. typedef struct tagLASTINPUTINFO {
  63. UINT cbSize;
  64. DWORD dwTime;
  65. } LASTINPUTINFO, * PLASTINPUTINFO;
  66. #endif
  67. //
  68. // NT5 api we dynaload from user32
  69. //
  70. typedef WINUSERAPI BOOL (WINAPI* PFNGETLASTINPUTINFO)(PLASTINPUTINFO plii);
  71. PFNGETLASTINPUTINFO pfnGetLastInputInfo = NULL;
  72. ///////////////////////////////////////////////////////////////////////////
  73. //
  74. // Internal functions
  75. //
  76. ///////////////////////////////////////////////////////////////////////////
  77. #ifdef DEBUG
  78. inline BOOL IsRegMultiSZType(DWORD dwType)
  79. {
  80. return (REG_MULTI_SZ == dwType);
  81. }
  82. inline BOOL IsRegStringType(DWORD dwType)
  83. {
  84. return (REG_SZ == dwType) ||
  85. (REG_EXPAND_SZ == dwType) ||
  86. IsRegMultiSZType(dwType);
  87. }
  88. LONG SafeRegQueryValueEx(
  89. IN HKEY hKey,
  90. IN PCTSTR lpValueName,
  91. IN LPDWORD lpReserved,
  92. OUT LPDWORD lpType,
  93. IN OUT LPBYTE lpData,
  94. IN OUT LPDWORD lpcbData
  95. )
  96. {
  97. DWORD dwType;
  98. DWORD cbData = lpcbData ? *lpcbData : 0;
  99. // We always care about the type even if the caller doesn't
  100. if (!lpType)
  101. {
  102. lpType = &dwType;
  103. }
  104. LONG lResult = RegQueryValueEx(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
  105. // need to make sure we NULL terminate strings.
  106. if ((ERROR_SUCCESS == lResult) && lpData && IsRegStringType(*lpType))
  107. {
  108. if (cbData >= sizeof(TCHAR))
  109. {
  110. TCHAR *psz = (TCHAR *)lpData;
  111. DWORD cch = cbData / sizeof(TCHAR);
  112. psz[cch - 1] = 0;
  113. // and make sure that REG_MULTI_SZ strings are double NULL terminated
  114. if (IsRegMultiSZType(*lpType))
  115. {
  116. if (cbData >= (sizeof(TCHAR) * 2))
  117. {
  118. psz[cch - 2] = 0;
  119. }
  120. }
  121. }
  122. }
  123. return lResult;
  124. }
  125. BOOL ReadRegValue(HKEY hkeyRoot, const TCHAR *pszKey, const TCHAR *pszValue,
  126. void *pData, DWORD dwBytes)
  127. {
  128. long lResult;
  129. HKEY hkey;
  130. DWORD dwType;
  131. lResult = RegOpenKeyEx(hkeyRoot, pszKey, 0, KEY_READ, &hkey);
  132. if (lResult != ERROR_SUCCESS) {
  133. return FALSE;
  134. }
  135. lResult = SafeRegQueryValueEx(hkey, pszValue, NULL, &dwType, (BYTE *)pData, &dwBytes);
  136. RegCloseKey(hkey);
  137. if (lResult != ERROR_SUCCESS)
  138. return FALSE;
  139. return TRUE;
  140. }
  141. TCHAR *g_pszLoggingFile;
  142. BOOL g_fCheckedForLog = FALSE;
  143. DWORD LogEvent(LPTSTR pszFormat, ...)
  144. {
  145. // check registry if necessary
  146. if(FALSE == g_fCheckedForLog) {
  147. TCHAR pszFilePath[MAX_PATH];
  148. if(ReadRegValue(HKEY_CURRENT_USER,
  149. TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\msidle"),
  150. TEXT("LoggingFile"), pszFilePath, sizeof(pszFilePath))) {
  151. g_pszLoggingFile = (TCHAR *)LocalAlloc(LPTR, lstrlen(pszFilePath) + 1);
  152. if(g_pszLoggingFile) {
  153. lstrcpyn(g_pszLoggingFile, pszFilePath, ARRAYSIZE(g_pszLoggingFile));
  154. }
  155. }
  156. g_fCheckedForLog = TRUE;
  157. }
  158. if(g_pszLoggingFile) {
  159. TCHAR pszString[1025];
  160. SYSTEMTIME st;
  161. HANDLE hLog;
  162. DWORD dwWritten;
  163. va_list va;
  164. hLog = CreateFile(g_pszLoggingFile, GENERIC_WRITE, 0, NULL,
  165. OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  166. if(INVALID_HANDLE_VALUE == hLog)
  167. return GetLastError();
  168. // seek to end of file
  169. SetFilePointer(hLog, 0, 0, FILE_END);
  170. // dump time
  171. GetLocalTime(&st);
  172. // Safe to call wsprintf since the buffer is 1025 (wsprintf has a built in 1024 limit)
  173. wsprintf(pszString, "%02d:%02d:%02d [%x] - ", st.wHour, st.wMinute, st.wSecond, GetCurrentThreadId());
  174. WriteFile(hLog, pszString, lstrlen(pszString), &dwWritten, NULL);
  175. OutputDebugString(pszString);
  176. // dump passed in string
  177. va_start(va, pszFormat);
  178. // Safe to call wvsprintf since the buffer is 1025 (wsprintf has a built in 1024 limit)
  179. wvsprintf(pszString, pszFormat, va);
  180. va_end(va);
  181. WriteFile(hLog, pszString, lstrlen(pszString), &dwWritten, NULL);
  182. OutputDebugString(pszString);
  183. // cr
  184. WriteFile(hLog, "\r\n", 2, &dwWritten, NULL);
  185. OutputDebugString("\r\n");
  186. // clean up
  187. CloseHandle(hLog);
  188. }
  189. return 0;
  190. }
  191. #endif // DEBUG
  192. //
  193. // SetIdleTimer - decide how often to poll and set the timer appropriately
  194. //
  195. void SetIdleTimer(void)
  196. {
  197. UINT uInterval = 1000 * 60;
  198. //
  199. // If we're looking for loss of idle, check every 4 seconds
  200. //
  201. if(TRUE == g_fBusyNotify) {
  202. uInterval = 1000 * 4;
  203. }
  204. //
  205. // kill off the old timer
  206. //
  207. if(g_uIdleTimer) {
  208. KillTimer(NULL, g_uIdleTimer);
  209. }
  210. //
  211. // Set the timer
  212. //
  213. g_uIdleTimer = SetTimer(NULL, 0, uInterval, OnIdleTimer);
  214. }
  215. DWORD GetLastActivityTicks(void)
  216. {
  217. DWORD dwLastActivityTicks = 0;
  218. if (g_fIsWhistler) {
  219. dwLastActivityTicks = USER_SHARED_DATA->LastSystemRITEventTickCount;
  220. } else if(g_fIsWinNT5 && pfnGetLastInputInfo) {
  221. // NT5: Use get last input time API
  222. LASTINPUTINFO lii;
  223. memset(&lii, 0, sizeof(lii));
  224. lii.cbSize = sizeof(lii);
  225. (*pfnGetLastInputInfo)(&lii);
  226. dwLastActivityTicks = lii.dwTime;
  227. } else {
  228. // NT4 or Win95: Use sage if it's loaded
  229. if(INVALID_HANDLE_VALUE != g_hSageVxd) {
  230. // query sage.vxd for tick count
  231. DeviceIoControl(g_hSageVxd, 2, &dwLastActivityTicks, sizeof(DWORD),
  232. NULL, 0, NULL, NULL);
  233. }
  234. #ifdef MSIDLE_DOWNLEVEL
  235. else {
  236. // use hooks
  237. dwLastActivityTicks = sg_dwLastTickCount;
  238. }
  239. #endif // MSIDLE_DOWNLEVEL
  240. }
  241. return dwLastActivityTicks;
  242. }
  243. //
  244. // OnIdleTimer - idle timer has gone off
  245. //
  246. VOID CALLBACK OnIdleTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
  247. {
  248. DWORD dwDiff, dwLastActivityTicks;
  249. BOOL fTempBusyNotify = g_fBusyNotify;
  250. BOOL fTempIdleNotify = g_fIdleNotify;
  251. //
  252. // get last activity ticks from sage or shared segment
  253. //
  254. dwLastActivityTicks = GetLastActivityTicks();
  255. #ifdef DEBUG
  256. LogEvent("OnIdleTimer: dwLastActivity=%d, CurrentTicks=%d, dwIdleBegin=%d", dwLastActivityTicks, GetTickCount(), g_dwIdleBeginTicks);
  257. #endif
  258. //
  259. // check to see if we've changed state
  260. //
  261. if(fTempBusyNotify) {
  262. //
  263. // Want to know if we become busy
  264. //
  265. if(dwLastActivityTicks != g_dwIdleBeginTicks) {
  266. // activity since we became idle - stop being idle!
  267. g_fBusyNotify = FALSE;
  268. g_fIdleNotify = TRUE;
  269. // set the timer
  270. SetIdleTimer();
  271. // call back client
  272. #ifdef DEBUG
  273. LogEvent("OnIdleTimer: Idle Ends");
  274. #endif
  275. if(g_pfnCallback)
  276. (g_pfnCallback)(STATE_USER_IDLE_END);
  277. }
  278. }
  279. if(fTempIdleNotify) {
  280. //
  281. // Want to know if we become idle
  282. //
  283. dwDiff = GetTickCount() - dwLastActivityTicks;
  284. if(dwDiff > 1000 * 60 * g_dwIdleMin) {
  285. // Nothing's happened for our threshold time. We're now idle.
  286. g_fIdleNotify = FALSE;
  287. g_fBusyNotify = TRUE;
  288. // save time we became idle
  289. g_dwIdleBeginTicks = dwLastActivityTicks;
  290. // set the timer
  291. SetIdleTimer();
  292. // call back client
  293. #ifdef DEBUG
  294. LogEvent("OnIdleTimer: Idle Begins");
  295. #endif
  296. if(g_pfnCallback)
  297. (g_pfnCallback)(STATE_USER_IDLE_BEGIN);
  298. }
  299. }
  300. }
  301. BOOL LoadSageVxd(void)
  302. {
  303. int inpVXD[3];
  304. if(INVALID_HANDLE_VALUE != g_hSageVxd)
  305. return TRUE;
  306. g_hSageVxd = CreateFile("\\\\.\\sage.vxd", 0, 0, NULL, 0,
  307. FILE_FLAG_DELETE_ON_CLOSE, NULL);
  308. // can't open it? can't use it
  309. if(INVALID_HANDLE_VALUE == g_hSageVxd)
  310. return FALSE;
  311. // start it monitoring
  312. inpVXD[0] = -1; // no window - will query
  313. inpVXD[1] = 0; // unused
  314. inpVXD[2] = 0; // how long to wait between checks
  315. DeviceIoControl(g_hSageVxd, 1, &inpVXD, sizeof(inpVXD), NULL, 0, NULL, NULL);
  316. return TRUE;
  317. }
  318. BOOL UnloadSageVxd(void)
  319. {
  320. if(INVALID_HANDLE_VALUE != g_hSageVxd) {
  321. CloseHandle(g_hSageVxd);
  322. g_hSageVxd = INVALID_HANDLE_VALUE;
  323. }
  324. return TRUE;
  325. }
  326. ///////////////////////////////////////////////////////////////////////////
  327. //
  328. // Externally callable functions
  329. //
  330. ///////////////////////////////////////////////////////////////////////////
  331. //
  332. // LibMain - dll entry point
  333. //
  334. EXTERN_C BOOL WINAPI LibMain(HINSTANCE hInst, ULONG ulReason, LPVOID pvRes)
  335. {
  336. switch (ulReason) {
  337. case DLL_PROCESS_ATTACH:
  338. {
  339. OSVERSIONINFO vi;
  340. DisableThreadLibraryCalls(hInst);
  341. g_hInst = hInst;
  342. vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  343. GetVersionEx(&vi);
  344. if(vi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
  345. g_fIsWinNT = TRUE;
  346. if(vi.dwMajorVersion >= 5) {
  347. if (vi.dwMajorVersion > 5 || vi.dwMinorVersion > 0 || LOWORD(vi.dwBuildNumber) > 2410) {
  348. g_fIsWhistler = TRUE;
  349. } else {
  350. g_fIsWinNT5 = TRUE;
  351. }
  352. }
  353. }
  354. }
  355. break;
  356. }
  357. return TRUE;
  358. }
  359. //
  360. // BeginIdleDetection
  361. //
  362. DWORD BeginIdleDetection(_IDLECALLBACK pfnCallback, DWORD dwIdleMin, DWORD dwReserved)
  363. {
  364. DWORD dwValue = 0;
  365. // make sure reserved is 0
  366. if(dwReserved)
  367. return ERROR_INVALID_DATA;
  368. #ifdef DEBUG
  369. LogEvent("BeginIdleDetection: IdleMin=%d", dwIdleMin);
  370. #endif
  371. // save callback
  372. g_pfnCallback = pfnCallback;
  373. // save minutes
  374. g_dwIdleMin = dwIdleMin;
  375. // call back on idle
  376. g_fIdleNotify = TRUE;
  377. if(FALSE == g_fIsWinNT) {
  378. // try to load sage.vxd
  379. LoadSageVxd();
  380. }
  381. if(g_fIsWinNT5) {
  382. // we need to find our NT5 api in user
  383. HINSTANCE hUser = GetModuleHandle("user32.dll");
  384. if(hUser) {
  385. pfnGetLastInputInfo =
  386. (PFNGETLASTINPUTINFO)GetProcAddress(hUser, "GetLastInputInfo");
  387. }
  388. if(NULL == pfnGetLastInputInfo) {
  389. // not on NT5 - bizarre
  390. g_fIsWinNT5 = FALSE;
  391. }
  392. }
  393. #ifdef MSIDLE_DOWNLEVEL
  394. if(INVALID_HANDLE_VALUE == g_hSageVxd && FALSE == g_fIsWinNT5 && FALSE == g_fIsWhistler) {
  395. // sage vxd not available - do it the hard way
  396. // hook kbd
  397. sg_hKbdHook = SetWindowsHookEx(WH_KEYBOARD, KbdProc, g_hInst, 0);
  398. if(NULL == sg_hKbdHook)
  399. return GetLastError();
  400. // hook mouse
  401. sg_hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);
  402. if(NULL == sg_hMouseHook) {
  403. DWORD dwError = GetLastError();
  404. EndIdleDetection(0);
  405. return dwError;
  406. }
  407. }
  408. #endif // MSIDLE_DOWNLEVEL
  409. // Fire up the timer
  410. SetIdleTimer();
  411. return 0;
  412. }
  413. //
  414. // IdleEnd - stop idle monitoring
  415. //
  416. BOOL EndIdleDetection(DWORD dwReserved)
  417. {
  418. // ensure reserved is 0
  419. if(dwReserved)
  420. return FALSE;
  421. // free up sage if we're using it
  422. UnloadSageVxd();
  423. // kill timer
  424. if(g_uIdleTimer) {
  425. KillTimer(NULL, g_uIdleTimer);
  426. g_uIdleTimer = 0;
  427. }
  428. // callback is no longer valid
  429. g_pfnCallback = NULL;
  430. #ifdef MSIDLE_DOWNLEVEL
  431. // free up hooks
  432. if(sg_hKbdHook) {
  433. UnhookWindowsHookEx(sg_hKbdHook);
  434. sg_hKbdHook = NULL;
  435. }
  436. if(sg_hMouseHook) {
  437. UnhookWindowsHookEx(sg_hMouseHook);
  438. sg_hMouseHook = NULL;
  439. }
  440. #endif // MSIDLE_DOWNLEVEL
  441. return TRUE;
  442. }
  443. //
  444. // SetIdleMinutes - set the timout value and reset idle flag to false
  445. //
  446. // dwMinutes - if non-0, set idle timeout to that many minutes
  447. // fIdleNotify - call back when idle for at least idle minutes
  448. // fBusyNotify - call back on activity since Idle begin
  449. //
  450. BOOL SetIdleTimeout(DWORD dwMinutes, DWORD dwReserved)
  451. {
  452. if(dwReserved)
  453. return FALSE;
  454. #ifdef DEBUG
  455. LogEvent("SetIdleTimeout: dwIdleMin=%d", dwMinutes);
  456. #endif
  457. if(dwMinutes)
  458. g_dwIdleMin = dwMinutes;
  459. return TRUE;
  460. }
  461. //
  462. // SetIdleNotify - set flag to turn on or off idle notifications
  463. //
  464. // fNotify - flag
  465. // dwReserved - must be 0
  466. //
  467. void SetIdleNotify(BOOL fNotify, DWORD dwReserved)
  468. {
  469. #ifdef DEBUG
  470. LogEvent("SetIdleNotify: fNotify=%d", fNotify);
  471. #endif
  472. g_fIdleNotify = fNotify;
  473. }
  474. //
  475. // SetIdleNotify - set flag to turn on or off idle notifications
  476. //
  477. // fNotify - flag
  478. // dwReserved - must be 0
  479. //
  480. void SetBusyNotify(BOOL fNotify, DWORD dwReserved)
  481. {
  482. #ifdef DEBUG
  483. LogEvent("SetBusyNotify: fNotify=%d", fNotify);
  484. #endif
  485. g_fBusyNotify = fNotify;
  486. if(g_fBusyNotify)
  487. g_dwIdleBeginTicks = GetLastActivityTicks();
  488. // set the timer
  489. SetIdleTimer();
  490. }
  491. //
  492. // GetIdleMinutes - return how many minutes since last user activity
  493. //
  494. DWORD GetIdleMinutes(DWORD dwReserved)
  495. {
  496. if(dwReserved)
  497. return 0;
  498. return (GetTickCount() - GetLastActivityTicks()) / 60000;
  499. }
  500. #ifdef MSIDLE_DOWNLEVEL
  501. ///////////////////////////////////////////////////////////////////////////
  502. //
  503. // Hook functions
  504. //
  505. ///////////////////////////////////////////////////////////////////////////
  506. //
  507. // Note: These functions can be called back in any process!
  508. //
  509. LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
  510. {
  511. MOUSEHOOKSTRUCT * pmsh = (MOUSEHOOKSTRUCT *)lParam;
  512. if(nCode >= 0) {
  513. // ignore mouse move messages to the same point as all window
  514. // creations cause these - it doesn't mean the user moved the mouse
  515. if(WM_MOUSEMOVE != wParam || pmsh->pt.x != sg_pt.x || pmsh->pt.y != sg_pt.y) {
  516. sg_dwLastTickCount = GetTickCount();
  517. sg_pt = pmsh->pt;
  518. }
  519. }
  520. return(CallNextHookEx(sg_hMouseHook, nCode, wParam, lParam));
  521. }
  522. LRESULT CALLBACK KbdProc(int nCode, WPARAM wParam, LPARAM lParam)
  523. {
  524. if(nCode >= 0) {
  525. sg_dwLastTickCount = GetTickCount();
  526. }
  527. return(CallNextHookEx(sg_hKbdHook, nCode, wParam, lParam));
  528. }
  529. #endif // MSIDLE_DOWNLEVEL