xref: /reactos/base/system/winlogon/screensaver.c (revision 845faec4)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Winlogon
4  * FILE:            base/system/winlogon/screensaver.c
5  * PURPOSE:         Screen saver management
6  * PROGRAMMERS:     Herv� Poussineau (hpoussin@reactos.org)
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include "winlogon.h"
12 
13 /* FUNCTIONS ****************************************************************/
14 
15 #ifndef USE_GETLASTINPUTINFO
16 static
17 LRESULT
18 CALLBACK
19 KeyboardActivityProc(
20     IN INT nCode,
21     IN WPARAM wParam,
22     IN LPARAM lParam)
23 {
24     InterlockedExchange((LONG*)&WLSession->LastActivity, ((PKBDLLHOOKSTRUCT)lParam)->time);
25     return CallNextHookEx(NULL, nCode, wParam, lParam);
26 }
27 
28 
29 static
30 LRESULT
31 CALLBACK
32 MouseActivityProc(
33     IN INT nCode,
34     IN WPARAM wParam,
35     IN LPARAM lParam)
36 {
37     InterlockedExchange((LONG*)&WLSession->LastActivity, ((PMSLLHOOKSTRUCT)lParam)->time);
38     return CallNextHookEx(NULL, nCode, wParam, lParam);
39 }
40 #endif
41 
42 
43 static
44 VOID
45 LoadScreenSaverParameters(
46     OUT LPDWORD Timeout)
47 {
48     BOOL Enabled;
49 
50     if (!SystemParametersInfoW(SPI_GETSCREENSAVETIMEOUT, 0, Timeout, 0))
51     {
52         WARN("WL: Unable to get screen saver timeout (error %lu). Disabling it\n", GetLastError());
53         *Timeout = INFINITE;
54     }
55     else if (!SystemParametersInfoW(SPI_GETSCREENSAVEACTIVE, 0, &Enabled, 0))
56     {
57         WARN("WL: Unable to check if screen saver is enabled (error %lu). Disabling it\n", GetLastError());
58         *Timeout = INFINITE;
59     }
60     else if (!Enabled)
61     {
62         TRACE("WL: Screen saver is disabled\n");
63         *Timeout = INFINITE;
64     }
65     else
66     {
67         TRACE("WL: Screen saver timeout: %lu seconds\n", *Timeout);
68         *Timeout *= 1000;
69     }
70 }
71 
72 
73 static
74 DWORD
75 WINAPI
76 ScreenSaverThreadMain(
77     IN LPVOID lpParameter)
78 {
79     PWLSESSION Session = (PWLSESSION)lpParameter;
80     HANDLE HandleArray[3];
81 #ifdef USE_GETLASTINPUTINFO
82     LASTINPUTINFO lastInputInfo;
83 #else
84     DWORD LastActivity;
85 #endif
86     DWORD TimeToWait;
87     DWORD Timeout; /* Timeout before screen saver starts, in milliseconds */
88     DWORD ret;
89 
90     if (!ImpersonateLoggedOnUser(Session->UserToken))
91     {
92         ERR("ImpersonateLoggedOnUser() failed with error %lu\n", GetLastError());
93         return 0;
94     }
95 
96     Session->hUserActivity = CreateEventW(NULL, FALSE, FALSE, NULL);
97     if (!Session->hUserActivity)
98     {
99         ERR("WL: Unable to create event (error %lu)\n", GetLastError());
100         goto cleanup;
101     }
102 
103     Session->hEndOfScreenSaver = CreateEventW(NULL, FALSE, FALSE, NULL);
104     if (!Session->hEndOfScreenSaver)
105     {
106         ERR("WL: Unable to create event (error %lu)\n", GetLastError());
107         goto cleanup;
108     }
109 
110     HandleArray[0] = Session->hEndOfScreenSaverThread;
111     HandleArray[1] = Session->hScreenSaverParametersChanged;
112     HandleArray[2] = Session->hEndOfScreenSaver;
113 
114     LoadScreenSaverParameters(&Timeout);
115 
116 #ifndef USE_GETLASTINPUTINFO
117     InterlockedExchange((LONG*)&Session->LastActivity, GetTickCount());
118 #else
119     lastInputInfo.cbSize = sizeof(LASTINPUTINFO);
120 #endif
121     for (;;)
122     {
123         /* See the time of last activity and calculate a timeout */
124 #ifndef USE_GETLASTINPUTINFO
125         LastActivity = InterlockedCompareExchange((LONG*)&Session->LastActivity, 0, 0);
126         TimeToWait = Timeout - (GetTickCount() - LastActivity);
127 #else
128         if (GetLastInputInfo(&lastInputInfo))
129             TimeToWait = Timeout - (GetTickCount() - lastInputInfo.dwTime);
130         else
131         {
132             WARN("GetLastInputInfo() failed with error %lu\n", GetLastError());
133             TimeToWait = 10; /* Try again in 10 ms */
134         }
135 #endif
136 
137         if (TimeToWait > Timeout)
138         {
139             /* GetTickCount() got back to 0 */
140             TimeToWait = Timeout;
141         }
142 
143         /* Wait for the timeout, or the end of this thread */
144         ret = WaitForMultipleObjects(2, HandleArray, FALSE, TimeToWait);
145         if (ret == WAIT_OBJECT_0)
146             break;
147         else if (ret == WAIT_OBJECT_0 + 1)
148             LoadScreenSaverParameters(&Timeout);
149 
150         /* Check if we didn't had recent activity */
151 #ifndef USE_GETLASTINPUTINFO
152         LastActivity = InterlockedCompareExchange((LONG*)&Session->LastActivity, 0, 0);
153         if (LastActivity + Timeout > GetTickCount())
154             continue;
155 #else
156         if (!GetLastInputInfo(&lastInputInfo))
157         {
158             WARN("GetLastInputInfo() failed with error %lu\n", GetLastError());
159             continue;
160         }
161 
162         if (lastInputInfo.dwTime + Timeout > GetTickCount())
163             continue;
164 #endif
165 
166         /* Run screen saver */
167         PostMessageW(Session->SASWindow, WLX_WM_SAS, WLX_SAS_TYPE_SCRNSVR_TIMEOUT, 0);
168 
169         /* Wait for the end of this thread or of the screen saver */
170         ret = WaitForMultipleObjects(3, HandleArray, FALSE, INFINITE);
171         if (ret == WAIT_OBJECT_0)
172             break;
173         else if (ret == WAIT_OBJECT_0 + 1)
174             LoadScreenSaverParameters(&Timeout);
175         else if (ret == WAIT_OBJECT_0 + 2)
176             SystemParametersInfoW(SPI_SETSCREENSAVERRUNNING, FALSE, NULL, 0);
177     }
178 
179 cleanup:
180     if (Session->hUserActivity)
181         CloseHandle(Session->hUserActivity);
182 
183     if (Session->hEndOfScreenSaver)
184         CloseHandle(Session->hEndOfScreenSaver);
185 
186     RevertToSelf();
187 
188 #ifndef USE_GETLASTINPUTINFO
189     if (Session->KeyboardHook)
190         UnhookWindowsHookEx(Session->KeyboardHook);
191 
192     if (Session->MouseHook)
193         UnhookWindowsHookEx(Session->MouseHook);
194 #endif
195 
196     CloseHandle(Session->hEndOfScreenSaverThread);
197     CloseHandle(Session->hScreenSaverParametersChanged);
198 
199     return 0;
200 }
201 
202 
203 BOOL
204 InitializeScreenSaver(
205     IN OUT PWLSESSION Session)
206 {
207     HANDLE ScreenSaverThread;
208 
209 #ifndef USE_GETLASTINPUTINFO
210     /* Register hooks to detect keyboard and mouse activity */
211     Session->KeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardActivityProc, hAppInstance, 0);
212     if (!Session->KeyboardHook)
213     {
214         ERR("WL: Unable to register keyboard hook\n");
215         return FALSE;
216     }
217 
218     Session->MouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseActivityProc, hAppInstance, 0);
219     if (!Session->MouseHook)
220     {
221         ERR("WL: Unable to register mouse hook\n");
222         return FALSE;
223     }
224 #endif
225 
226     Session->hScreenSaverParametersChanged = CreateEventW(NULL, FALSE, FALSE, NULL);
227     if (!Session->hScreenSaverParametersChanged)
228     {
229         WARN("WL: Unable to create screen saver event (error %lu)\n", GetLastError());
230         return TRUE;
231     }
232 
233     Session->hEndOfScreenSaverThread = CreateEventW(NULL, FALSE, FALSE, NULL);
234     if (!Session->hEndOfScreenSaverThread)
235     {
236         WARN("WL: Unable to create screen saver event (error %lu)\n", GetLastError());
237         CloseHandle(Session->hScreenSaverParametersChanged);
238         return TRUE;
239     }
240 
241     ScreenSaverThread = CreateThread(NULL,
242                                      0,
243                                      ScreenSaverThreadMain,
244                                      Session,
245                                      0,
246                                      NULL);
247     if (ScreenSaverThread)
248         CloseHandle(ScreenSaverThread);
249     else
250         ERR("WL: Unable to start screen saver thread\n");
251 
252     return TRUE;
253 }
254 
255 
256 VOID
257 StartScreenSaver(
258     IN PWLSESSION Session)
259 {
260     HKEY hKey = NULL, hCurrentUser = NULL;
261     WCHAR szApplicationName[MAX_PATH];
262     WCHAR szCommandLine[MAX_PATH + 3];
263     DWORD bufferSize = sizeof(szApplicationName) - sizeof(WCHAR);
264     DWORD dwType;
265     STARTUPINFOW StartupInfo;
266     PROCESS_INFORMATION ProcessInformation;
267     HANDLE HandleArray[2];
268     LONG rc;
269     DWORD Status;
270     BOOL ret = FALSE;
271 
272     if (!ImpersonateLoggedOnUser(Session->UserToken))
273     {
274         ERR("WL: ImpersonateLoggedOnUser() failed with error %lu\n", GetLastError());
275         goto cleanup;
276     }
277 
278     rc = RegOpenCurrentUser(KEY_READ,
279                             &hCurrentUser);
280     if (rc != ERROR_SUCCESS)
281     {
282         ERR("WL: RegOpenCurrentUser error %lu\n", rc);
283         goto cleanup;
284     }
285 
286     rc = RegOpenKeyExW(hCurrentUser,
287                        L"Control Panel\\Desktop",
288                        0,
289                        KEY_QUERY_VALUE,
290                        &hKey);
291     if (rc != ERROR_SUCCESS)
292     {
293         ERR("WL: RegOpenKeyEx error %lu\n", rc);
294         goto cleanup;
295     }
296 
297     rc = RegQueryValueExW(hKey,
298                           L"SCRNSAVE.EXE",
299                           0,
300                           &dwType,
301                           (LPBYTE)szApplicationName,
302                           &bufferSize);
303     if (rc != ERROR_SUCCESS || dwType != REG_SZ)
304     {
305         if (rc != ERROR_FILE_NOT_FOUND)
306             ERR("WL: RegQueryValueEx error %lu\n", rc);
307         goto cleanup;
308     }
309 
310     if (bufferSize == 0)
311     {
312         ERR("WL: Buffer size is NULL!\n");
313         goto cleanup;
314     }
315 
316     szApplicationName[bufferSize / sizeof(WCHAR)] = 0; /* Terminate the string */
317 
318     if (wcslen(szApplicationName) == 0)
319     {
320         ERR("WL: Application Name length is zero!\n");
321         goto cleanup;
322     }
323 
324     wsprintfW(szCommandLine, L"%s /s", szApplicationName);
325     TRACE("WL: Executing %S\n", szCommandLine);
326 
327     ZeroMemory(&StartupInfo, sizeof(STARTUPINFOW));
328     ZeroMemory(&ProcessInformation, sizeof(PROCESS_INFORMATION));
329     StartupInfo.cb = sizeof(STARTUPINFOW);
330     StartupInfo.dwFlags = STARTF_SCRNSAVER;
331 
332     /* FIXME: run the screen saver on the screen saver desktop */
333     ret = CreateProcessW(szApplicationName,
334                          szCommandLine,
335                          NULL,
336                          NULL,
337                          FALSE,
338                          0,
339                          NULL,
340                          NULL,
341                          &StartupInfo,
342                          &ProcessInformation);
343     if (!ret)
344     {
345         ERR("WL: Unable to start %S, error %lu\n", szApplicationName, GetLastError());
346         goto cleanup;
347     }
348 
349     CloseHandle(ProcessInformation.hThread);
350 
351     SystemParametersInfoW(SPI_SETSCREENSAVERRUNNING, TRUE, NULL, 0);
352 
353     CallNotificationDlls(Session, StartScreenSaverHandler);
354 
355     /* Wait the end of the process or some other activity */
356     ResetEvent(Session->hUserActivity);
357     HandleArray[0] = ProcessInformation.hProcess;
358     HandleArray[1] = Session->hUserActivity;
359     Status = WaitForMultipleObjects(2, HandleArray, FALSE, INFINITE);
360     if (Status == WAIT_OBJECT_0 + 1)
361     {
362         /* Kill the screen saver */
363         TerminateProcess(ProcessInformation.hProcess, 0);
364     }
365 
366     SetEvent(Session->hEndOfScreenSaver);
367 
368     CloseHandle(ProcessInformation.hProcess);
369 
370     CallNotificationDlls(Session, StopScreenSaverHandler);
371 
372 cleanup:
373     if (hKey)
374         RegCloseKey(hKey);
375 
376     if (hCurrentUser)
377         RegCloseKey(hCurrentUser);
378 
379     RevertToSelf();
380 
381     if (!ret)
382     {
383         PostMessageW(Session->SASWindow, WLX_WM_SAS, WLX_SAS_TYPE_SCRNSVR_ACTIVITY, 0);
384 #ifndef USE_GETLASTINPUTINFO
385         InterlockedExchange((LONG*)&Session->LastActivity, GetTickCount());
386 #endif
387     }
388 }
389