Source code of Windows XP (NT5)
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.

598 lines
14 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. BOOL ReadRegValue(HKEY hkeyRoot, const TCHAR *pszKey, const TCHAR *pszValue,
  79. void *pData, DWORD dwBytes)
  80. {
  81. long lResult;
  82. HKEY hkey;
  83. DWORD dwType;
  84. lResult = RegOpenKey(hkeyRoot, pszKey, &hkey);
  85. if (lResult != ERROR_SUCCESS) {
  86. return FALSE;
  87. }
  88. lResult = RegQueryValueEx(hkey, pszValue, NULL, &dwType, (BYTE *)pData,
  89. &dwBytes);
  90. RegCloseKey(hkey);
  91. if (lResult != ERROR_SUCCESS)
  92. return FALSE;
  93. if(dwType == REG_SZ) {
  94. // null terminate string
  95. ((TCHAR *)pData)[dwBytes] = 0;
  96. }
  97. return TRUE;
  98. }
  99. TCHAR *g_pszLoggingFile;
  100. BOOL g_fCheckedForLog = FALSE;
  101. DWORD LogEvent(LPTSTR pszFormat, ...)
  102. {
  103. // check registry if necessary
  104. if(FALSE == g_fCheckedForLog) {
  105. TCHAR pszFilePath[MAX_PATH];
  106. if(ReadRegValue(HKEY_CURRENT_USER,
  107. TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\msidle"),
  108. TEXT("LoggingFile"), pszFilePath, MAX_PATH)) {
  109. g_pszLoggingFile = (TCHAR *)LocalAlloc(LPTR, lstrlen(pszFilePath) + 1);
  110. if(g_pszLoggingFile) {
  111. lstrcpy(g_pszLoggingFile, pszFilePath);
  112. }
  113. }
  114. g_fCheckedForLog = TRUE;
  115. }
  116. if(g_pszLoggingFile) {
  117. TCHAR pszString[1024];
  118. SYSTEMTIME st;
  119. HANDLE hLog;
  120. DWORD dwWritten;
  121. va_list va;
  122. hLog = CreateFile(g_pszLoggingFile, GENERIC_WRITE, 0, NULL,
  123. OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  124. if(INVALID_HANDLE_VALUE == hLog)
  125. return GetLastError();
  126. // seek to end of file
  127. SetFilePointer(hLog, 0, 0, FILE_END);
  128. // dump time
  129. GetLocalTime(&st);
  130. wsprintf(pszString, "%02d:%02d:%02d [%x] - ", st.wHour, st.wMinute, st.wSecond, GetCurrentThreadId());
  131. WriteFile(hLog, pszString, lstrlen(pszString), &dwWritten, NULL);
  132. OutputDebugString(pszString);
  133. // dump passed in string
  134. va_start(va, pszFormat);
  135. wvsprintf(pszString, pszFormat, va);
  136. va_end(va);
  137. WriteFile(hLog, pszString, lstrlen(pszString), &dwWritten, NULL);
  138. OutputDebugString(pszString);
  139. // cr
  140. WriteFile(hLog, "\r\n", 2, &dwWritten, NULL);
  141. OutputDebugString("\r\n");
  142. // clean up
  143. CloseHandle(hLog);
  144. }
  145. return 0;
  146. }
  147. #endif // DEBUG
  148. //
  149. // SetIdleTimer - decide how often to poll and set the timer appropriately
  150. //
  151. void SetIdleTimer(void)
  152. {
  153. UINT uInterval = 1000 * 60;
  154. //
  155. // If we're looking for loss of idle, check every 4 seconds
  156. //
  157. if(TRUE == g_fBusyNotify) {
  158. uInterval = 1000 * 4;
  159. }
  160. //
  161. // kill off the old timer
  162. //
  163. if(g_uIdleTimer) {
  164. KillTimer(NULL, g_uIdleTimer);
  165. }
  166. //
  167. // Set the timer
  168. //
  169. g_uIdleTimer = SetTimer(NULL, 0, uInterval, OnIdleTimer);
  170. }
  171. DWORD GetLastActivityTicks(void)
  172. {
  173. DWORD dwLastActivityTicks = 0;
  174. if (g_fIsWhistler) {
  175. dwLastActivityTicks = USER_SHARED_DATA->LastSystemRITEventTickCount;
  176. } else if(g_fIsWinNT5 && pfnGetLastInputInfo) {
  177. // NT5: Use get last input time API
  178. LASTINPUTINFO lii;
  179. memset(&lii, 0, sizeof(lii));
  180. lii.cbSize = sizeof(lii);
  181. (*pfnGetLastInputInfo)(&lii);
  182. dwLastActivityTicks = lii.dwTime;
  183. } else {
  184. // NT4 or Win95: Use sage if it's loaded
  185. if(INVALID_HANDLE_VALUE != g_hSageVxd) {
  186. // query sage.vxd for tick count
  187. DeviceIoControl(g_hSageVxd, 2, &dwLastActivityTicks, sizeof(DWORD),
  188. NULL, 0, NULL, NULL);
  189. }
  190. #ifdef MSIDLE_DOWNLEVEL
  191. else {
  192. // use hooks
  193. dwLastActivityTicks = sg_dwLastTickCount;
  194. }
  195. #endif // MSIDLE_DOWNLEVEL
  196. }
  197. return dwLastActivityTicks;
  198. }
  199. //
  200. // OnIdleTimer - idle timer has gone off
  201. //
  202. VOID CALLBACK OnIdleTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
  203. {
  204. DWORD dwDiff, dwLastActivityTicks;
  205. BOOL fTempBusyNotify = g_fBusyNotify;
  206. BOOL fTempIdleNotify = g_fIdleNotify;
  207. //
  208. // get last activity ticks from sage or shared segment
  209. //
  210. dwLastActivityTicks = GetLastActivityTicks();
  211. #ifdef DEBUG
  212. LogEvent("OnIdleTimer: dwLastActivity=%d, CurrentTicks=%d, dwIdleBegin=%d", dwLastActivityTicks, GetTickCount(), g_dwIdleBeginTicks);
  213. #endif
  214. //
  215. // check to see if we've changed state
  216. //
  217. if(fTempBusyNotify) {
  218. //
  219. // Want to know if we become busy
  220. //
  221. if(dwLastActivityTicks != g_dwIdleBeginTicks) {
  222. // activity since we became idle - stop being idle!
  223. g_fBusyNotify = FALSE;
  224. g_fIdleNotify = TRUE;
  225. // set the timer
  226. SetIdleTimer();
  227. // call back client
  228. #ifdef DEBUG
  229. LogEvent("OnIdleTimer: Idle Ends");
  230. #endif
  231. if(g_pfnCallback)
  232. (g_pfnCallback)(STATE_USER_IDLE_END);
  233. }
  234. }
  235. if(fTempIdleNotify) {
  236. //
  237. // Want to know if we become idle
  238. //
  239. dwDiff = GetTickCount() - dwLastActivityTicks;
  240. if(dwDiff > 1000 * 60 * g_dwIdleMin) {
  241. // Nothing's happened for our threshold time. We're now idle.
  242. g_fIdleNotify = FALSE;
  243. g_fBusyNotify = TRUE;
  244. // save time we became idle
  245. g_dwIdleBeginTicks = dwLastActivityTicks;
  246. // set the timer
  247. SetIdleTimer();
  248. // call back client
  249. #ifdef DEBUG
  250. LogEvent("OnIdleTimer: Idle Begins");
  251. #endif
  252. if(g_pfnCallback)
  253. (g_pfnCallback)(STATE_USER_IDLE_BEGIN);
  254. }
  255. }
  256. }
  257. BOOL LoadSageVxd(void)
  258. {
  259. int inpVXD[3];
  260. if(INVALID_HANDLE_VALUE != g_hSageVxd)
  261. return TRUE;
  262. g_hSageVxd = CreateFile("\\\\.\\sage.vxd", 0, 0, NULL, 0,
  263. FILE_FLAG_DELETE_ON_CLOSE, NULL);
  264. // can't open it? can't use it
  265. if(INVALID_HANDLE_VALUE == g_hSageVxd)
  266. return FALSE;
  267. // start it monitoring
  268. inpVXD[0] = -1; // no window - will query
  269. inpVXD[1] = 0; // unused
  270. inpVXD[2] = 0; // how long to wait between checks
  271. DeviceIoControl(g_hSageVxd, 1, &inpVXD, sizeof(inpVXD), NULL, 0, NULL, NULL);
  272. return TRUE;
  273. }
  274. BOOL UnloadSageVxd(void)
  275. {
  276. if(INVALID_HANDLE_VALUE != g_hSageVxd) {
  277. CloseHandle(g_hSageVxd);
  278. g_hSageVxd = INVALID_HANDLE_VALUE;
  279. }
  280. return TRUE;
  281. }
  282. ///////////////////////////////////////////////////////////////////////////
  283. //
  284. // Externally callable functions
  285. //
  286. ///////////////////////////////////////////////////////////////////////////
  287. //
  288. // LibMain - dll entry point
  289. //
  290. EXTERN_C BOOL WINAPI LibMain(HINSTANCE hInst, ULONG ulReason, LPVOID pvRes)
  291. {
  292. switch (ulReason) {
  293. case DLL_PROCESS_ATTACH:
  294. {
  295. OSVERSIONINFO vi;
  296. DisableThreadLibraryCalls(hInst);
  297. g_hInst = hInst;
  298. vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  299. GetVersionEx(&vi);
  300. if(vi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
  301. g_fIsWinNT = TRUE;
  302. if(vi.dwMajorVersion >= 5) {
  303. if (vi.dwMajorVersion > 5 || vi.dwMinorVersion > 0 || LOWORD(vi.dwBuildNumber) > 2410) {
  304. g_fIsWhistler = TRUE;
  305. } else {
  306. g_fIsWinNT5 = TRUE;
  307. }
  308. }
  309. }
  310. }
  311. break;
  312. }
  313. return TRUE;
  314. }
  315. //
  316. // BeginIdleDetection
  317. //
  318. DWORD BeginIdleDetection(_IDLECALLBACK pfnCallback, DWORD dwIdleMin, DWORD dwReserved)
  319. {
  320. DWORD dwValue = 0;
  321. // make sure reserved is 0
  322. if(dwReserved)
  323. return ERROR_INVALID_DATA;
  324. #ifdef DEBUG
  325. LogEvent("BeginIdleDetection: IdleMin=%d", dwIdleMin);
  326. #endif
  327. // save callback
  328. g_pfnCallback = pfnCallback;
  329. // save minutes
  330. g_dwIdleMin = dwIdleMin;
  331. // call back on idle
  332. g_fIdleNotify = TRUE;
  333. if(FALSE == g_fIsWinNT) {
  334. // try to load sage.vxd
  335. LoadSageVxd();
  336. }
  337. if(g_fIsWinNT5) {
  338. // we need to find our NT5 api in user
  339. HINSTANCE hUser = GetModuleHandle("user32.dll");
  340. if(hUser) {
  341. pfnGetLastInputInfo =
  342. (PFNGETLASTINPUTINFO)GetProcAddress(hUser, "GetLastInputInfo");
  343. }
  344. if(NULL == pfnGetLastInputInfo) {
  345. // not on NT5 - bizarre
  346. g_fIsWinNT5 = FALSE;
  347. }
  348. }
  349. #ifdef MSIDLE_DOWNLEVEL
  350. if(INVALID_HANDLE_VALUE == g_hSageVxd && FALSE == g_fIsWinNT5 && FALSE == g_fIsWhistler) {
  351. // sage vxd not available - do it the hard way
  352. // hook kbd
  353. sg_hKbdHook = SetWindowsHookEx(WH_KEYBOARD, KbdProc, g_hInst, 0);
  354. if(NULL == sg_hKbdHook)
  355. return GetLastError();
  356. // hook mouse
  357. sg_hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);
  358. if(NULL == sg_hMouseHook) {
  359. DWORD dwError = GetLastError();
  360. EndIdleDetection(0);
  361. return dwError;
  362. }
  363. }
  364. #endif // MSIDLE_DOWNLEVEL
  365. // Fire up the timer
  366. SetIdleTimer();
  367. return 0;
  368. }
  369. //
  370. // IdleEnd - stop idle monitoring
  371. //
  372. BOOL EndIdleDetection(DWORD dwReserved)
  373. {
  374. // ensure reserved is 0
  375. if(dwReserved)
  376. return FALSE;
  377. // free up sage if we're using it
  378. UnloadSageVxd();
  379. // kill timer
  380. if(g_uIdleTimer) {
  381. KillTimer(NULL, g_uIdleTimer);
  382. g_uIdleTimer = 0;
  383. }
  384. // callback is no longer valid
  385. g_pfnCallback = NULL;
  386. #ifdef MSIDLE_DOWNLEVEL
  387. // free up hooks
  388. if(sg_hKbdHook) {
  389. UnhookWindowsHookEx(sg_hKbdHook);
  390. sg_hKbdHook = NULL;
  391. }
  392. if(sg_hMouseHook) {
  393. UnhookWindowsHookEx(sg_hMouseHook);
  394. sg_hMouseHook = NULL;
  395. }
  396. #endif // MSIDLE_DOWNLEVEL
  397. return TRUE;
  398. }
  399. //
  400. // SetIdleMinutes - set the timout value and reset idle flag to false
  401. //
  402. // dwMinutes - if non-0, set idle timeout to that many minutes
  403. // fIdleNotify - call back when idle for at least idle minutes
  404. // fBusyNotify - call back on activity since Idle begin
  405. //
  406. BOOL SetIdleTimeout(DWORD dwMinutes, DWORD dwReserved)
  407. {
  408. if(dwReserved)
  409. return FALSE;
  410. #ifdef DEBUG
  411. LogEvent("SetIdleTimeout: dwIdleMin=%d", dwMinutes);
  412. #endif
  413. if(dwMinutes)
  414. g_dwIdleMin = dwMinutes;
  415. return TRUE;
  416. }
  417. //
  418. // SetIdleNotify - set flag to turn on or off idle notifications
  419. //
  420. // fNotify - flag
  421. // dwReserved - must be 0
  422. //
  423. void SetIdleNotify(BOOL fNotify, DWORD dwReserved)
  424. {
  425. #ifdef DEBUG
  426. LogEvent("SetIdleNotify: fNotify=%d", fNotify);
  427. #endif
  428. g_fIdleNotify = fNotify;
  429. }
  430. //
  431. // SetIdleNotify - set flag to turn on or off idle notifications
  432. //
  433. // fNotify - flag
  434. // dwReserved - must be 0
  435. //
  436. void SetBusyNotify(BOOL fNotify, DWORD dwReserved)
  437. {
  438. #ifdef DEBUG
  439. LogEvent("SetBusyNotify: fNotify=%d", fNotify);
  440. #endif
  441. g_fBusyNotify = fNotify;
  442. if(g_fBusyNotify)
  443. g_dwIdleBeginTicks = GetLastActivityTicks();
  444. // set the timer
  445. SetIdleTimer();
  446. }
  447. //
  448. // GetIdleMinutes - return how many minutes since last user activity
  449. //
  450. DWORD GetIdleMinutes(DWORD dwReserved)
  451. {
  452. if(dwReserved)
  453. return 0;
  454. return (GetTickCount() - GetLastActivityTicks()) / 60000;
  455. }
  456. #ifdef MSIDLE_DOWNLEVEL
  457. ///////////////////////////////////////////////////////////////////////////
  458. //
  459. // Hook functions
  460. //
  461. ///////////////////////////////////////////////////////////////////////////
  462. //
  463. // Note: These functions can be called back in any process!
  464. //
  465. LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
  466. {
  467. MOUSEHOOKSTRUCT * pmsh = (MOUSEHOOKSTRUCT *)lParam;
  468. if(nCode >= 0) {
  469. // ignore mouse move messages to the same point as all window
  470. // creations cause these - it doesn't mean the user moved the mouse
  471. if(WM_MOUSEMOVE != wParam || pmsh->pt.x != sg_pt.x || pmsh->pt.y != sg_pt.y) {
  472. sg_dwLastTickCount = GetTickCount();
  473. sg_pt = pmsh->pt;
  474. }
  475. }
  476. return(CallNextHookEx(sg_hMouseHook, nCode, wParam, lParam));
  477. }
  478. LRESULT CALLBACK KbdProc(int nCode, WPARAM wParam, LPARAM lParam)
  479. {
  480. if(nCode >= 0) {
  481. sg_dwLastTickCount = GetTickCount();
  482. }
  483. return(CallNextHookEx(sg_hKbdHook, nCode, wParam, lParam));
  484. }
  485. #endif // MSIDLE_DOWNLEVEL