1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2017 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
17 
18 // Screensaver coordinator.
19 // Alternates between a "default screensaver"
20 // and application graphics for running jobs.
21 // Periods are configurable via config file "ss_config.xml".
22 // See http://boinc.berkeley.edu/trac/wiki/ScreensaverEnhancements
23 
24 #ifdef _WIN32
25 #include "boinc_win.h"
26 #endif
27 
28 #ifdef __APPLE__
29 #include <Carbon/Carbon.h>
30 #include <sys/wait.h>
31 #include <app_ipc.h>
32 #include <malloc/malloc.h>
33 #endif
34 
35 // Common application includes
36 //
37 #include "diagnostics.h"
38 #include "common_defs.h"
39 #include "util.h"
40 #include "common_defs.h"
41 #include "filesys.h"
42 #include "error_numbers.h"
43 #include "gui_rpc_client.h"
44 #include "str_util.h"
45 #include "str_replace.h"
46 #include "screensaver.h"
47 
48 // Platform specific application includes
49 //
50 #if   defined(_WIN32)
51 #include "screensaver_win.h"
52 typedef HANDLE GFXAPP_ID;
53 #define DataMgmtProcType DWORD WINAPI
54 #elif defined(__APPLE__)
55 #include "Mac_Saver_Module.h"
56 typedef int GFXAPP_ID;
57 #define DataMgmtProcType void*
58 #endif
59 
60 
61 #ifdef _WIN32
62 // Allow for Unicode wide characters
63 #define PATH_SEPARATOR (_T("\\"))
64 #define THE_DEFAULT_SS_EXECUTABLE (_T(DEFAULT_SS_EXECUTABLE))
65 #define THE_SS_CONFIG_FILE (_T(SS_CONFIG_FILE))
66 #define DEFAULT_GFX_CANT_CONNECT ERR_CONNECT
67 #else
68 // Using (_T()) here causes compiler errors on Mac
69 #define PATH_SEPARATOR "/"
70 #define THE_DEFAULT_SS_EXECUTABLE DEFAULT_SS_EXECUTABLE
71 #define THE_SS_CONFIG_FILE SS_CONFIG_FILE
72 #define DEFAULT_GFX_CANT_CONNECT (ERR_CONNECT & 0xff)
73 #endif
74 
75 
76 // Flags for testing & debugging
77 #define SIMULATE_NO_GRAPHICS 0
78 
79 
is_same_task(RESULT * taska,RESULT * taskb)80 bool CScreensaver::is_same_task(RESULT* taska, RESULT* taskb) {
81     if ((taska == NULL) || (taskb == NULL)) return false;
82     if (strcmp(taska->name, taskb->name)) return false;
83     if (strcmp(taska->project_url, taskb->project_url)) return false;
84     return true;
85 }
86 
count_active_graphic_apps(RESULTS & res,RESULT * exclude)87 int CScreensaver::count_active_graphic_apps(RESULTS& res, RESULT* exclude) {
88     int i = 0;
89     unsigned int graphics_app_count = 0;
90 
91     // Count the number of active graphics-capable apps excluding the specified result.
92     // If exclude is NULL, don't exclude any results.
93     //
94     for (i = res.results.size()-1; i >=0 ; i--) {
95         BOINCTRACE(_T("count_active_graphic_apps -- active task detected\n"));
96         BOINCTRACE(
97             _T("count_active_graphic_apps -- name = '%s', path = '%s'\n"),
98             res.results[i]->name, res.results[i]->graphics_exec_path
99         );
100 
101         if (!strlen(res.results[i]->graphics_exec_path)) continue;
102         if (is_same_task(res.results[i], exclude)) continue;
103 #ifdef __APPLE__
104         // Remove it from the vector if incompatible with current version of OS X
105         if (isIncompatible(res.results[i]->graphics_exec_path)) {
106             BOINCTRACE(
107                 _T("count_active_graphic_apps -- removing incompatible name = '%s', path = '%s'\n"),
108                 res.results[i]->name, res.results[i]->graphics_exec_path
109             );
110             RESULT *rp = res.results[i];
111             res.results.erase(res.results.begin()+i);
112             delete rp;
113             continue;
114         }
115 #endif
116         BOINCTRACE(_T("count_active_graphic_apps -- active task detected w/graphics\n"));
117 
118         graphics_app_count++;
119     }
120     return graphics_app_count;
121 }
122 
123 
124 // Choose a random graphics application out of the vector.
125 // Exclude the specified result unless it is the only candidate.
126 // If exclude is NULL or an empty string, don't exclude any results.
127 //
get_random_graphics_app(RESULTS & res,RESULT * exclude)128 RESULT* CScreensaver::get_random_graphics_app(
129     RESULTS& res, RESULT* exclude
130 ) {
131     RESULT*      rp = NULL;
132     unsigned int i = 0;
133     unsigned int graphics_app_count = 0;
134     unsigned int random_selection = 0;
135     unsigned int current_counter = 0;
136     RESULT *avoid = exclude;
137 
138     BOINCTRACE(_T("get_random_graphics_app -- Function Start\n"));
139 
140     graphics_app_count = count_active_graphic_apps(res, avoid);
141     BOINCTRACE(_T("get_random_graphics_app -- graphics_app_count = '%d'\n"), graphics_app_count);
142 
143     // If no graphics app found other than the one excluded, count again without excluding any
144     if ((0 == graphics_app_count) && (avoid != NULL)) {
145         avoid = NULL;
146         graphics_app_count = count_active_graphic_apps(res, avoid);
147     }
148 
149     // If no graphics app was found, return NULL
150     if (0 == graphics_app_count) {
151         goto CLEANUP;
152     }
153 
154     // Choose which application to display.
155     //
156     random_selection = (rand() % graphics_app_count) + 1;
157     BOINCTRACE(_T("get_random_graphics_app -- random_selection = '%d'\n"), random_selection);
158 
159     // find the chosen graphics application.
160     //
161     for (i = 0; i < res.results.size(); i++) {
162         if (!strlen(res.results[i]->graphics_exec_path)) continue;
163         if (is_same_task(res.results[i], avoid)) continue;
164 
165         current_counter++;
166         if (current_counter == random_selection) {
167             rp = res.results[i];
168             break;
169         }
170     }
171 
172 CLEANUP:
173     BOINCTRACE(_T("get_random_graphics_app -- Function End\n"));
174 
175     return rp;
176 }
177 
178 
179 #ifdef __APPLE__
markAsIncompatible(char * gfxAppPath)180 void CScreensaver::markAsIncompatible(char *gfxAppPath) {
181     char *buf = (char *)malloc(strlen(gfxAppPath)+1);
182     if (buf) {
183         strlcpy(buf, gfxAppPath, malloc_size(buf));
184         m_vIncompatibleGfxApps.push_back(buf);
185        BOINCTRACE(_T("markAsIncompatible -- path = '%s'\n"), gfxAppPath);
186     }
187 }
188 
isIncompatible(char * appPath)189 bool CScreensaver::isIncompatible(char *appPath) {
190     unsigned int i = 0;
191     for (i = 0; i < m_vIncompatibleGfxApps.size(); i++) {
192         BOINCTRACE(
193             _T("isIncompatible -- comparing incompatible path '%s' to candidate path %s\n"),
194             m_vIncompatibleGfxApps[i], appPath
195         );
196         if (strcmp(m_vIncompatibleGfxApps[i], appPath) == 0) {
197             return true;
198         }
199     }
200     return false;
201 }
202 
203 #endif
204 
205 
206 // Launch a project (science) graphics application
207 //
launch_screensaver(RESULT * rp,GFXAPP_ID & graphics_application)208 int CScreensaver::launch_screensaver(RESULT* rp, GFXAPP_ID& graphics_application) {
209     int retval = 0;
210 
211     if (strlen(rp->graphics_exec_path)) {
212         // V6 Graphics
213 #ifdef __APPLE__
214         // For sandbox security, use gfx_switcher to launch gfx app
215         // as user boinc_project and group boinc_project.
216         //
217         // For unknown reasons, the graphics application exits with
218         // "RegisterProcess failed (error = -50)" unless we pass its
219         // full path twice in the argument list to execv.
220         char* argv[5];
221         argv[0] = "gfx_Switcher";
222         argv[1] = "-launch_gfx";
223         argv[2] = strrchr(rp->slot_path, '/');
224         if (*argv[2]) argv[2]++;    // Point to the slot number in ascii
225 
226         argv[3] = "--fullscreen";
227         argv[4] = 0;
228 
229        retval = run_program(
230             rp->slot_path,
231             m_gfx_Switcher_Path,
232             4,
233             argv,
234             0,
235             graphics_application
236         );
237 
238     if (graphics_application) {
239         launchedGfxApp(rp->graphics_exec_path, graphics_application, rp->slot);
240     }
241 #else
242         char* argv[3];
243         argv[0] = "app_graphics";   // not used
244         argv[1] = "--fullscreen";
245         argv[2] = 0;
246         retval = run_program(
247             rp->slot_path,
248             rp->graphics_exec_path,
249             2,
250             argv,
251             0,
252             graphics_application
253         );
254 #endif
255     }
256     return retval;
257 }
258 
259 
260 // Terminate any screensaver graphics application
261 //
terminate_v6_screensaver(GFXAPP_ID & graphics_application)262 int CScreensaver::terminate_v6_screensaver(GFXAPP_ID& graphics_application) {
263     int retval = 0;
264 
265 #ifdef __APPLE__
266     // Under sandbox security, use gfx_switcher to kill default gfx app
267     // as user boinc_master and group boinc_master.  The man page for
268     // kill() says the user ID of the process sending the signal must
269     // match that of the target process, though in practice that seems
270     // not to be true on the Mac.
271 
272     char current_dir[PATH_MAX];
273     char gfx_pid[16];
274     pid_t thePID;
275     int i;
276 
277     sprintf(gfx_pid, "%d", graphics_application);
278     getcwd( current_dir, sizeof(current_dir));
279 
280     char* argv[4];
281     argv[0] = "gfx_switcher";
282     argv[1] = "-kill_gfx";
283     argv[2] = gfx_pid;
284     argv[3] = 0;
285 
286     retval = run_program(
287         current_dir,
288         m_gfx_Switcher_Path,
289         3,
290         argv,
291         0,
292         thePID
293     );
294 
295     if (graphics_application) {
296         launchedGfxApp("", 0, -1);
297     }
298 
299     for (i=0; i<200; i++) {
300         boinc_sleep(0.01);      // Wait 2 seconds max
301         // Prevent gfx_switcher from becoming a zombie
302         if (waitpid(thePID, 0, WNOHANG) == thePID) {
303             break;
304         }
305     }
306 #endif
307 
308 #ifdef _WIN32
309         HWND hBOINCGraphicsWindow = FindWindow(BOINC_WINDOW_CLASS_NAME, NULL);
310         if (hBOINCGraphicsWindow) {
311             CloseWindow(hBOINCGraphicsWindow);
312             Sleep(1000);
313             hBOINCGraphicsWindow = FindWindow(BOINC_WINDOW_CLASS_NAME, NULL);
314             if (hBOINCGraphicsWindow) {
315                 kill_program(graphics_application);
316             }
317         }
318 #endif
319 
320     // For safety, call kill_program even under Apple sandbox security
321     kill_program(graphics_application);
322     return retval;
323 }
324 
325 
326 // Terminate the project (science) graphics application
327 // TODO: get rid of 2nd arg
328 //
terminate_screensaver(GFXAPP_ID & graphics_application,RESULT *)329 int CScreensaver::terminate_screensaver(GFXAPP_ID& graphics_application, RESULT *) {
330     int retval = 0;
331 
332     if (graphics_application) {
333         // V6 Graphics
334         if (m_bScience_gfx_running) {
335             terminate_v6_screensaver(graphics_application);
336         }
337     }
338     return retval;
339 }
340 
341 
342 // Launch the default graphics application
343 //
launch_default_screensaver(char * dir_path,GFXAPP_ID & graphics_application)344 int CScreensaver::launch_default_screensaver(char *dir_path, GFXAPP_ID& graphics_application) {
345     int retval = 0;
346     int num_args;
347 
348 #ifdef __APPLE__
349     // For sandbox security, use gfx_switcher to launch default
350     // gfx app as user boinc_master and group boinc_master.
351     char* argv[6];
352 
353     argv[0] = "gfx_switcher";
354     argv[1] = "-default_gfx";
355     argv[2] = THE_DEFAULT_SS_EXECUTABLE;    // Will be changed by gfx_switcher
356     argv[3] = "--fullscreen";
357     argv[4] = 0;
358     argv[5] = 0;
359     if (!m_bConnected) {
360         BOINCTRACE(_T("launch_default_screensaver using --retry_connect argument\n"));
361         argv[4] = "--retry_connect";
362         num_args = 5;
363     } else {
364         num_args = 4;
365     }
366 
367     retval = run_program(
368         dir_path,
369         m_gfx_Switcher_Path,
370         num_args,
371         argv,
372         0,
373         graphics_application
374     );
375 
376     if (graphics_application) {
377         launchedGfxApp("boincscr", graphics_application, -1);
378     }
379 
380     BOINCTRACE(_T("launch_default_screensaver returned %d\n"), retval);
381 
382 #else
383     // For unknown reasons, the graphics application exits with
384     // "RegisterProcess failed (error = -50)" unless we pass its
385     // full path twice in the argument list to execv on Macs.
386 
387     char* argv[4];
388     char full_path[1024];
389 
390     strlcpy(full_path, dir_path, sizeof(full_path));
391     strlcat(full_path, PATH_SEPARATOR, sizeof(full_path));
392     strlcat(full_path, THE_DEFAULT_SS_EXECUTABLE, sizeof(full_path));
393 
394     argv[0] = full_path;   // not used
395     argv[1] = "--fullscreen";
396     argv[2] = 0;
397     argv[3] = 0;
398     if (!m_bConnected) {
399         BOINCTRACE(_T("launch_default_screensaver using --retry_connect argument\n"));
400         argv[2] = "--retry_connect";
401         num_args = 3;
402     } else {
403         num_args = 2;
404     }
405 
406     retval = run_program(
407         dir_path,
408         full_path,
409         num_args,
410         argv,
411         0,
412         graphics_application
413     );
414 
415      BOINCTRACE(_T("launch_default_screensaver %s returned %d\n"), full_path, retval);
416 
417 #endif
418      return retval;
419 }
420 
421 
422 // Terminate the default graphics application
423 //
terminate_default_screensaver(GFXAPP_ID & graphics_application)424 int CScreensaver::terminate_default_screensaver(GFXAPP_ID& graphics_application) {
425     int retval = 0;
426 
427     if (! graphics_application) return 0;
428     retval = terminate_v6_screensaver(graphics_application);
429     return retval;
430 }
431 
432 
433 // If we cannot connect to the core client:
434 //   - we retry connecting every 10 seconds
435 //   - we launch the default graphics application with the argument --retry_connect, so
436 //     it will continue running and will also retry connecting every 10 seconds.
437 //
438 // If we successfully connected to the core client, launch the default graphics application
439 // without the argument --retry_connect.  If it can't connect, it will return immediately
440 // with the exit code ERR_CONNECT.  In that case, we assume it was blocked by a firewall
441 // and so we run only project (science) graphics.
442 
DataManagementProc()443 DataMgmtProcType CScreensaver::DataManagementProc() {
444     int             retval                      = 0;
445     int             suspend_reason              = 0;
446     RESULT*         theResult                   = NULL;
447     RESULT*         graphics_app_result_ptr     = NULL;
448     RESULT          previous_result;
449     // previous_result_ptr = &previous_result when previous_result is valid, else NULL
450     RESULT*         previous_result_ptr         = NULL;
451     int             iResultCount                = 0;
452     int             iIndex                      = 0;
453     double          default_phase_start_time    = 0.0;
454     double          science_phase_start_time    = 0.0;
455     double          last_change_time            = 0.0;
456     // If we run default screensaver during science phase because no science graphics
457     // are available, then shorten next default graphics phase by that much time.
458     double          default_saver_start_time_in_science_phase    = 0.0;
459     double          default_saver_duration_in_science_phase      = 0.0;
460 
461     SS_PHASE        ss_phase                    = DEFAULT_SS_PHASE;
462     bool            switch_to_default_gfx       = false;
463     bool            killing_default_gfx         = false;
464     int             exit_status                 = 0;
465 
466     char*           default_ss_dir_path         = NULL;
467     char            full_path[1024];
468 
469     BOINCTRACE(_T("CScreensaver::DataManagementProc - Display screen saver loading message\n"));
470     SetError(TRUE, SCRAPPERR_BOINCSCREENSAVERLOADING);  // No GFX App is running: show moving BOINC logo
471 #ifdef _WIN32
472     m_tThreadCreateTime = time(0);
473 
474     // Set the starting point for iterating through the results
475     m_iLastResultShown = 0;
476     m_tLastResultChangeTime = 0;
477 #endif
478 
479     m_bDefault_ss_exists = false;
480     m_bScience_gfx_running = false;
481     m_bDefault_gfx_running = false;
482     m_bShow_default_ss_first = false;
483 
484 #ifdef __APPLE__
485     m_vIncompatibleGfxApps.clear();
486     default_ss_dir_path = "/Library/Application Support/BOINC Data";
487 #else
488     default_ss_dir_path = (char*)m_strBOINCInstallDirectory.c_str();
489 #endif
490 
491     strlcpy(full_path, default_ss_dir_path, sizeof(full_path));
492     strlcat(full_path, PATH_SEPARATOR, sizeof(full_path));
493     strlcat(full_path, THE_DEFAULT_SS_EXECUTABLE, sizeof(full_path));
494 
495     if (boinc_file_exists(full_path)) {
496         m_bDefault_ss_exists = true;
497     } else {
498         SetError(TRUE, SCRAPPERR_CANTLAUNCHDEFAULTGFXAPP);  // No GFX App is running: show moving BOINC logo
499     }
500 
501     if (m_bDefault_ss_exists && m_bShow_default_ss_first) {
502         ss_phase = DEFAULT_SS_PHASE;
503         default_phase_start_time = dtime();
504         science_phase_start_time = 0;
505         switch_to_default_gfx = true;
506     } else {
507         ss_phase = SCIENCE_SS_PHASE;
508         default_phase_start_time = 0;
509         science_phase_start_time = dtime();
510     }
511 
512     while (true) {
513         for (int i = 0; i < 4; i++) {
514             // ***
515             // *** Things that should be run frequently.
516             // ***   4 times per second.
517             // ***
518 
519             // Are we supposed to exit the screensaver?
520             if (m_bQuitDataManagementProc) {     // If main thread has requested we exit
521                 BOINCTRACE(_T("CScreensaver::DataManagementProc - Thread told to stop\n"));
522                 if (m_hGraphicsApplication || graphics_app_result_ptr) {
523                     if (m_bDefault_gfx_running) {
524                         BOINCTRACE(_T("CScreensaver::DataManagementProc - Terminating default screensaver\n"));
525                         terminate_default_screensaver(m_hGraphicsApplication);
526                     } else {
527                         BOINCTRACE(_T("CScreensaver::DataManagementProc - Terminating screensaver\n"));
528                         terminate_screensaver(m_hGraphicsApplication, graphics_app_result_ptr);
529                     }
530                     graphics_app_result_ptr = NULL;
531                     previous_result_ptr = NULL;
532                     m_hGraphicsApplication = 0;
533                 }
534                 BOINCTRACE(_T("CScreensaver::DataManagementProc - Stopping...\n"));
535                 m_bDataManagementProcStopped = true; // Tell main thread that we exited
536                 return 0;                       // Exit the thread
537             }
538             boinc_sleep(0.25);
539         }
540 
541         // ***
542         // *** Things that should be run less frequently.
543         // *** 1 time per second.
544         // ***
545 
546         // Blank screen saver?
547         if ((m_dwBlankScreen) && (time(0) > m_dwBlankTime) && (m_dwBlankTime > 0)) {
548             BOINCTRACE(_T("CScreensaver::DataManagementProc - Time to blank\n"));
549             SetError(FALSE, SCRAPPERR_SCREENSAVERBLANKED);    // Blanked - hide moving BOINC logo
550             m_bQuitDataManagementProc = true;
551             continue;       // Code above will exit the thread
552         }
553 
554         BOINCTRACE(_T("CScreensaver::DataManagementProc - ErrorMode = '%d', ErrorCode = '%x'\n"), m_bErrorMode, m_hrError);
555 
556         if (!m_bConnected) {
557             HandleRPCError();
558         }
559 
560         if (m_bConnected) {
561             // Do we need to get the core client state?
562             if (m_bResetCoreState) {
563                 // Try and get the current state of the CC
564                 retval = rpc->get_state(state);
565                 if (retval) {
566                     // CC may not yet be running
567                     HandleRPCError();
568                     continue;
569                 } else {
570                     m_bResetCoreState = false;
571                 }
572             }
573 
574             // Update our task list
575             retval = rpc->get_screensaver_tasks(suspend_reason, results);
576             if (retval) {
577                 // rpc call returned error
578                 HandleRPCError();
579                 m_bResetCoreState = true;
580                 continue;
581             }
582         } else {
583             results.clear();
584         }
585 
586         // Time to switch to default graphics phase?
587         if (m_bDefault_ss_exists && (ss_phase == SCIENCE_SS_PHASE) && (m_fGFXDefaultPeriod > 0)) {
588             if (science_phase_start_time && ((dtime() - science_phase_start_time) > m_fGFXSciencePeriod)) {
589                 if (!m_bDefault_gfx_running) {
590                     switch_to_default_gfx = true;
591                 }
592                 ss_phase = DEFAULT_SS_PHASE;
593                 default_phase_start_time = dtime();
594                 science_phase_start_time = 0;
595                 if (m_bDefault_gfx_running && default_saver_start_time_in_science_phase) {
596                     // Remember how long default graphics ran during science phase
597                     default_saver_duration_in_science_phase += (dtime() - default_saver_start_time_in_science_phase);
598                 }
599                 default_saver_start_time_in_science_phase = 0;
600             }
601         }
602 
603         // Time to switch to science graphics phase?
604         if ((ss_phase == DEFAULT_SS_PHASE) && m_bConnected && (m_fGFXSciencePeriod > 0)) {
605             if (default_phase_start_time &&
606                     ((dtime() - default_phase_start_time + default_saver_duration_in_science_phase)
607                     > m_fGFXDefaultPeriod)) {
608                 // BOINCTRACE(_T("CScreensaver::Ending Default phase: now=%f, default_phase_start_time=%f, default_saver_duration_in_science_phase=%f\n"),
609                 // dtime(), default_phase_start_time, default_saver_duration_in_science_phase);
610                 ss_phase = SCIENCE_SS_PHASE;
611                 default_phase_start_time = 0;
612                 default_saver_duration_in_science_phase = 0;
613                 science_phase_start_time = dtime();
614                 if (m_bDefault_gfx_running) {
615                     default_saver_start_time_in_science_phase = science_phase_start_time;
616                 }
617                 switch_to_default_gfx = false;
618             }
619         }
620 
621         // Core client suspended?
622         // We ignore SUSPEND_REASON_CPU_USAGE in SS coordinator, so it won't kill graphics apps for
623         // short-term CPU usage spikes (such as anti-virus.)  Added 9 April 2010
624         if (suspend_reason && !(suspend_reason & (SUSPEND_REASON_CPU_THROTTLE | SUSPEND_REASON_CPU_USAGE))) {
625             if (!m_bDefault_gfx_running) {
626                 SetError(TRUE, m_hrError);          // No GFX App is running: show moving BOINC logo
627                 if (m_bDefault_ss_exists) {
628                     switch_to_default_gfx = true;
629                 }
630             }
631         }
632 
633         if (switch_to_default_gfx) {
634             if (m_bScience_gfx_running) {
635                 if (m_hGraphicsApplication || previous_result_ptr) {
636                     // use previous_result_ptr because graphics_app_result_ptr may no longer be valid
637                     terminate_screensaver(m_hGraphicsApplication, previous_result_ptr);
638                     if (m_hGraphicsApplication == 0) {
639                         graphics_app_result_ptr = NULL;
640                         m_bScience_gfx_running = false;
641                     } else {
642                         // HasProcessExited() test will clear m_hGraphicsApplication and graphics_app_result_ptr
643                     }
644                     previous_result_ptr = NULL;
645                 }
646             } else {
647                 if (!m_bDefault_gfx_running) {
648                     switch_to_default_gfx = false;
649                     retval = launch_default_screensaver(default_ss_dir_path, m_hGraphicsApplication);
650                     if (retval) {
651                         m_hGraphicsApplication = 0;
652                         previous_result_ptr = NULL;
653                         graphics_app_result_ptr = NULL;
654                         m_bDefault_gfx_running = false;
655                         SetError(TRUE, SCRAPPERR_CANTLAUNCHDEFAULTGFXAPP);  // No GFX App is running: show moving BOINC logo
656                    } else {
657                         m_bDefault_gfx_running = true;
658                         if (ss_phase == SCIENCE_SS_PHASE) {
659                             default_saver_start_time_in_science_phase = dtime();
660                         }
661                         SetError(FALSE, SCRAPPERR_BOINCSCREENSAVERLOADING);    // A GFX App is running: hide moving BOINC logo
662                     }
663                 }
664             }
665         }
666 
667         if ((ss_phase == SCIENCE_SS_PHASE) && !switch_to_default_gfx) {
668 
669 #if SIMULATE_NO_GRAPHICS /* FOR TESTING */
670 
671             if (!m_bDefault_gfx_running) {
672                 SetError(TRUE, m_hrError);          // No GFX App is running: show moving BOINC logo
673                 if (m_bDefault_ss_exists) {
674                     switch_to_default_gfx = true;
675                 }
676             }
677 
678 #else                   /* NORMAL OPERATION */
679 
680             if (m_bScience_gfx_running) {
681                 // Is the current graphics app's associated task still running?
682 
683                 if ((m_hGraphicsApplication) || (graphics_app_result_ptr)) {
684                     iResultCount = (int)results.results.size();
685                     graphics_app_result_ptr = NULL;
686 
687                     // Find the current task in the new results vector (if it still exists)
688                     for (iIndex = 0; iIndex < iResultCount; iIndex++) {
689                         theResult = results.results.at(iIndex);
690 
691                         if (is_same_task(theResult, previous_result_ptr)) {
692                             graphics_app_result_ptr = theResult;
693                             previous_result = *theResult;
694                             previous_result_ptr = &previous_result;
695                             break;
696                         }
697                     }
698 
699                     // V6 graphics only: if worker application has stopped running, terminate_screensaver
700                     if ((graphics_app_result_ptr == NULL) && (m_hGraphicsApplication != 0)) {
701                         if (previous_result_ptr) {
702                             BOINCTRACE(_T("CScreensaver::DataManagementProc - %s finished\n"),
703                                 previous_result.graphics_exec_path
704                             );
705                         }
706                         terminate_screensaver(m_hGraphicsApplication, previous_result_ptr);
707                         previous_result_ptr = NULL;
708                         if (m_hGraphicsApplication == 0) {
709                             graphics_app_result_ptr = NULL;
710                             m_bScience_gfx_running = false;
711                             // Save previous_result and previous_result_ptr for get_random_graphics_app() call
712                         } else {
713                             // HasProcessExited() test will clear m_hGraphicsApplication and graphics_app_result_ptr
714                         }
715                     }
716 
717                      if (last_change_time && (m_fGFXChangePeriod > 0) && ((dtime() - last_change_time) > m_fGFXChangePeriod) ) {
718                         if (count_active_graphic_apps(results, previous_result_ptr) > 0) {
719                             if (previous_result_ptr) {
720                                 BOINCTRACE(_T("CScreensaver::DataManagementProc - time to change: %s / %s\n"),
721                                     previous_result.name, previous_result.graphics_exec_path
722                                 );
723                             }
724                             terminate_screensaver(m_hGraphicsApplication, graphics_app_result_ptr);
725                             if (m_hGraphicsApplication == 0) {
726                                 graphics_app_result_ptr = NULL;
727                                 m_bScience_gfx_running = false;
728                                 // Save previous_result and previous_result_ptr for get_random_graphics_app() call
729                             } else {
730                                 // HasProcessExited() test will clear m_hGraphicsApplication and graphics_app_result_ptr
731                             }
732                         }
733                         last_change_time = dtime();
734                     }
735                 }
736             }       // End if (m_bScience_gfx_running)
737 
738             // If no current graphics app, pick an active task at random
739             // and launch its graphics app
740             //
741             if ((m_bDefault_gfx_running || (m_hGraphicsApplication == 0)) && (graphics_app_result_ptr == NULL)) {
742                 if (suspend_reason && !(suspend_reason & (SUSPEND_REASON_CPU_THROTTLE | SUSPEND_REASON_CPU_USAGE))) {
743                     graphics_app_result_ptr = NULL;
744                 } else {
745                     graphics_app_result_ptr = get_random_graphics_app(results, previous_result_ptr);
746                     previous_result_ptr = NULL;
747                 }
748 
749                 if (graphics_app_result_ptr) {
750                     if (m_bDefault_gfx_running) {
751                         terminate_default_screensaver(m_hGraphicsApplication);
752                         killing_default_gfx = true;
753                         // Remember how long default graphics ran during science phase
754                         if (default_saver_start_time_in_science_phase) {
755                             default_saver_duration_in_science_phase += (dtime() - default_saver_start_time_in_science_phase);
756                             //BOINCTRACE(_T("CScreensaver::During Science phase: now=%f, default_saver_start_time=%f, default_saver_duration=%f\n"),
757                             //    dtime(), default_saver_start_time_in_science_phase, default_saver_duration_in_science_phase);
758                         }
759                         default_saver_start_time_in_science_phase = 0;
760                         // HasProcessExited() test will clear
761                         // m_hGraphicsApplication and graphics_app_result_ptr
762                      } else {
763                         retval = launch_screensaver(graphics_app_result_ptr, m_hGraphicsApplication);
764                         if (retval) {
765                             m_hGraphicsApplication = 0;
766                             previous_result_ptr = NULL;
767                             graphics_app_result_ptr = NULL;
768                             m_bScience_gfx_running = false;
769                         } else {
770                             // A GFX App is running: hide moving BOINC logo
771                             //
772                             SetError(FALSE, SCRAPPERR_BOINCSCREENSAVERLOADING);
773                             last_change_time = dtime();
774                             m_bScience_gfx_running = true;
775                             // Make a local copy of current result, since original pointer
776                             // may have been freed by the time we perform later tests
777                             previous_result = *graphics_app_result_ptr;
778                             previous_result_ptr = &previous_result;
779                             if (previous_result_ptr) {
780                                 BOINCTRACE(_T("CScreensaver::DataManagementProc - launching %s\n"),
781                                     previous_result.graphics_exec_path
782                                 );
783                             }
784                         }
785                     }
786                 } else {
787                     if (!m_bDefault_gfx_running) {
788                         // We can't run a science graphics app, so run the default graphics if available
789                         SetError(TRUE, m_hrError);
790                         if (m_bDefault_ss_exists) {
791                             switch_to_default_gfx = true;
792                         }
793                     }
794 
795                 }   // End if no science graphics available
796             }      // End if no current science graphics app is running
797 
798 #endif      // ! SIMULATE_NO_GRAPHICS
799 
800             if (switch_to_default_gfx) {
801                 switch_to_default_gfx = false;
802                 if (!m_bDefault_gfx_running) {
803                     retval = launch_default_screensaver(default_ss_dir_path, m_hGraphicsApplication);
804                     if (retval) {
805                         m_hGraphicsApplication = 0;
806                         previous_result_ptr = NULL;
807                         graphics_app_result_ptr = NULL;
808                         m_bDefault_gfx_running = false;
809                         SetError(TRUE, SCRAPPERR_CANTLAUNCHDEFAULTGFXAPP);
810                         // No GFX App is running: show BOINC logo
811                     } else {
812                         m_bDefault_gfx_running = true;
813                         default_saver_start_time_in_science_phase = dtime();
814                         SetError(FALSE, SCRAPPERR_BOINCSCREENSAVERLOADING);
815                         // Default GFX App is running: hide moving BOINC logo
816                     }
817                 }
818             }
819         }   // End if ((ss_phase == SCIENCE_SS_PHASE) && !switch_to_default_gfx)
820 
821 
822 
823         // Is the graphics app still running?
824         if (m_hGraphicsApplication) {
825             if (HasProcessExited(m_hGraphicsApplication, exit_status)) {
826                 // Something has happened to the previously selected screensaver
827                 //   application. Start a different one.
828                 BOINCTRACE(_T("CScreensaver::DataManagementProc - Graphics application isn't running, start a new one.\n"));
829                 if (m_bDefault_gfx_running) {
830                     // If we were able to connect to core client
831                     // but gfx app can't, don't use it.
832                     //
833                     BOINCTRACE(_T("CScreensaver::DataManagementProc - Default graphics application exited with code %d.\n"), exit_status);
834                     if (!killing_default_gfx) {     // If this is an unexpected exit
835                         if (exit_status == DEFAULT_GFX_CANT_CONNECT) {
836                             SetError(TRUE, SCRAPPERR_DEFAULTGFXAPPCANTCONNECT);
837                             // No GFX App is running: show moving BOINC logo
838                         } else {
839                             SetError(TRUE, SCRAPPERR_DEFAULTGFXAPPCRASHED);
840                             // No GFX App is running: show moving BOINC logo
841                         }
842                         m_bDefault_ss_exists = false;
843                         ss_phase = SCIENCE_SS_PHASE;
844                     }
845                     killing_default_gfx = false;
846                 }
847                 SetError(TRUE, SCRAPPERR_BOINCNOGRAPHICSAPPSEXECUTING);
848                 // No GFX App is running: show moving BOINC logo
849                 m_hGraphicsApplication = 0;
850                 graphics_app_result_ptr = NULL;
851                 m_bDefault_gfx_running = false;
852                 m_bScience_gfx_running = false;
853 #ifdef __APPLE__
854                 launchedGfxApp("", 0, -1);
855 #endif
856                 continue;
857             }
858         }
859     }   // end while(true)
860 }
861 
862 
863 #ifdef _WIN32
HasProcessExited(HANDLE pid_handle,int & exitCode)864 BOOL CScreensaver::HasProcessExited(HANDLE pid_handle, int &exitCode) {
865     unsigned long status = 1;
866     if (GetExitCodeProcess(pid_handle, &status)) {
867         if (status == STILL_ACTIVE) {
868             exitCode = 0;
869             return false;
870         }
871     }
872     exitCode = (int)status;
873     return true;
874 }
875 #else
HasProcessExited(pid_t pid,int & exitCode)876 bool CScreensaver::HasProcessExited(pid_t pid, int &exitCode) {
877     int status;
878     pid_t p;
879 
880     p = waitpid(pid, &status, WNOHANG);
881     exitCode = WEXITSTATUS(status);
882     if (p == pid) return true;     // process has exited
883     if (p == -1) return true;      // PID doesn't exist
884     exitCode = 0;
885     return false;
886 }
887 #endif
888 
889 
GetDefaultDisplayPeriods(struct ss_periods & periods)890 void CScreensaver::GetDefaultDisplayPeriods(struct ss_periods &periods) {
891     char*           default_data_dir_path = NULL;
892     char            buf[1024];
893     FILE*           f;
894     MIOFILE         mf;
895 
896     periods.GFXDefaultPeriod = GFX_DEFAULT_PERIOD;
897     periods.GFXSciencePeriod = GFX_SCIENCE_PERIOD;
898     periods.GFXChangePeriod = GFX_CHANGE_PERIOD;
899     periods.Show_default_ss_first = false;
900 
901 #ifdef __APPLE__
902     default_data_dir_path = "/Library/Application Support/BOINC Data";
903 #else
904     default_data_dir_path = (char*)m_strBOINCDataDirectory.c_str();
905 #endif
906 
907     strlcpy(buf, default_data_dir_path, sizeof(buf));
908     strlcat(buf, PATH_SEPARATOR, sizeof(buf));
909     strlcat(buf, THE_SS_CONFIG_FILE, sizeof(buf));
910 
911     f = boinc_fopen(buf, "r");
912     if (!f) return;
913 
914     mf.init_file(f);
915     XML_PARSER xp(&mf);
916 
917     while (!xp.get_tag()) {
918         if (xp.parse_bool("default_ss_first", periods.Show_default_ss_first)) continue;
919         if (xp.parse_double("default_gfx_duration", periods.GFXDefaultPeriod)) continue;
920         if (xp.parse_double("science_gfx_duration", periods.GFXSciencePeriod)) continue;
921         if (xp.parse_double("science_gfx_change_interval", periods.GFXChangePeriod)) continue;
922     }
923     fclose(f);
924 
925     BOINCTRACE(
926         _T("CScreensaver::GetDefaultDisplayPeriods: m_bShow_default_ss_first=%d, m_fGFXDefaultPeriod=%f, m_fGFXSciencePeriod=%f, m_fGFXChangePeriod=%f\n"),
927         (int)periods.Show_default_ss_first,
928         periods.GFXDefaultPeriod,
929         periods.GFXSciencePeriod,
930         periods.GFXChangePeriod
931     );
932 }
933