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