mirror of https://github.com/BOINC/boinc.git
1075 lines
41 KiB
C++
1075 lines
41 KiB
C++
// This file is part of BOINC.
|
|
// http://boinc.berkeley.edu
|
|
// Copyright (C) 2020 University of California
|
|
//
|
|
// BOINC is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License
|
|
// as published by the Free Software Foundation,
|
|
// either version 3 of the License, or (at your option) any later version.
|
|
//
|
|
// BOINC is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// Screensaver coordinator.
|
|
// Alternates between a "default screensaver"
|
|
// and application graphics for running jobs.
|
|
// Periods are configurable via config file "ss_config.xml".
|
|
// See http://boinc.berkeley.edu/trac/wiki/ScreensaverEnhancements
|
|
|
|
#ifdef _WIN32
|
|
#include "boinc_win.h"
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
#define VERBOSE 0
|
|
|
|
#include <Carbon/Carbon.h>
|
|
#include <sys/wait.h>
|
|
#include <app_ipc.h>
|
|
#include <malloc/malloc.h>
|
|
#include <pthread.h>
|
|
#include <sys/stat.h>
|
|
|
|
extern pthread_mutex_t saver_mutex;
|
|
#endif
|
|
|
|
// Common application includes
|
|
//
|
|
#include "diagnostics.h"
|
|
#include "common_defs.h"
|
|
#include "util.h"
|
|
#include "common_defs.h"
|
|
#include "filesys.h"
|
|
#include "error_numbers.h"
|
|
#include "gui_rpc_client.h"
|
|
#include "str_util.h"
|
|
#include "str_replace.h"
|
|
#include "screensaver.h"
|
|
|
|
#ifdef __APPLE__
|
|
#undef BOINCTRACE
|
|
#if VERBOSE
|
|
#define BOINCTRACE print_to_log_file
|
|
#else
|
|
#define BOINCTRACE(...)
|
|
#endif
|
|
|
|
#define _T
|
|
#endif
|
|
|
|
// Platform specific application includes
|
|
//
|
|
#if defined(_WIN32)
|
|
#include "screensaver_win.h"
|
|
typedef HANDLE GFXAPP_ID;
|
|
#define DataMgmtProcType DWORD WINAPI
|
|
#elif defined(__APPLE__)
|
|
#include "Mac_Saver_Module.h"
|
|
#include "shmem.h"
|
|
typedef int GFXAPP_ID;
|
|
#define DataMgmtProcType void*
|
|
#endif
|
|
|
|
|
|
#ifdef _WIN32
|
|
// Allow for Unicode wide characters
|
|
#define PATH_SEPARATOR (_T("\\"))
|
|
#define THE_DEFAULT_SS_EXECUTABLE (_T(DEFAULT_SS_EXECUTABLE))
|
|
#define THE_SS_CONFIG_FILE (_T(SS_CONFIG_FILE))
|
|
#define DEFAULT_GFX_CANT_CONNECT ERR_CONNECT
|
|
#else
|
|
// Using (_T()) here causes compiler errors on Mac
|
|
#define PATH_SEPARATOR "/"
|
|
#define THE_DEFAULT_SS_EXECUTABLE DEFAULT_SS_EXECUTABLE
|
|
#define THE_SS_CONFIG_FILE SS_CONFIG_FILE
|
|
#define DEFAULT_GFX_CANT_CONNECT (ERR_CONNECT & 0xff)
|
|
#endif
|
|
|
|
|
|
// Flags for testing & debugging
|
|
#define SIMULATE_NO_GRAPHICS 0
|
|
|
|
RESULT* graphics_app_result_ptr = NULL;
|
|
|
|
#ifdef __APPLE__
|
|
pid_t* pid_from_shmem = NULL;
|
|
#endif
|
|
|
|
bool CScreensaver::is_same_task(RESULT* taska, RESULT* taskb) {
|
|
if ((taska == NULL) || (taskb == NULL)) return false;
|
|
if (strcmp(taska->name, taskb->name)) return false;
|
|
if (strcmp(taska->project_url, taskb->project_url)) return false;
|
|
return true;
|
|
}
|
|
|
|
int CScreensaver::count_active_graphic_apps(RESULTS& res, RESULT* exclude) {
|
|
int i = 0;
|
|
unsigned int graphics_app_count = 0;
|
|
|
|
// Count the number of active graphics-capable apps excluding the specified result.
|
|
// If exclude is NULL, don't exclude any results.
|
|
//
|
|
for (i = res.results.size()-1; i >=0 ; i--) {
|
|
BOINCTRACE(_T("count_active_graphic_apps -- active task detected\n"));
|
|
BOINCTRACE(
|
|
_T("count_active_graphic_apps -- name = '%s', path = '%s'\n"),
|
|
res.results[i]->name, res.results[i]->graphics_exec_path
|
|
);
|
|
|
|
if (!strlen(res.results[i]->graphics_exec_path)) continue;
|
|
if (is_same_task(res.results[i], exclude)) continue;
|
|
#ifdef __APPLE__
|
|
// Remove it from the vector if incompatible with current version of OS X
|
|
if (isIncompatible(res.results[i]->graphics_exec_path)) {
|
|
BOINCTRACE(
|
|
_T("count_active_graphic_apps -- removing incompatible name = '%s', path = '%s'\n"),
|
|
res.results[i]->name, res.results[i]->graphics_exec_path
|
|
);
|
|
RESULT *rp = res.results[i];
|
|
res.results.erase(res.results.begin()+i);
|
|
delete rp;
|
|
continue;
|
|
}
|
|
#endif
|
|
BOINCTRACE(_T("count_active_graphic_apps -- active task detected w/graphics\n"));
|
|
|
|
graphics_app_count++;
|
|
}
|
|
return graphics_app_count;
|
|
}
|
|
|
|
|
|
// Choose a random graphics application out of the vector.
|
|
// Exclude the specified result unless it is the only candidate.
|
|
// If exclude is NULL or an empty string, don't exclude any results.
|
|
//
|
|
RESULT* CScreensaver::get_random_graphics_app(
|
|
RESULTS& res, RESULT* exclude
|
|
) {
|
|
RESULT* rp = NULL;
|
|
unsigned int i = 0;
|
|
unsigned int graphics_app_count = 0;
|
|
unsigned int random_selection = 0;
|
|
unsigned int current_counter = 0;
|
|
RESULT *avoid = exclude;
|
|
|
|
BOINCTRACE(_T("get_random_graphics_app -- Function Start\n"));
|
|
|
|
graphics_app_count = count_active_graphic_apps(res, avoid);
|
|
BOINCTRACE(_T("get_random_graphics_app -- graphics_app_count = '%d'\n"), graphics_app_count);
|
|
|
|
// If no graphics app found other than the one excluded, count again without excluding any
|
|
if ((0 == graphics_app_count) && (avoid != NULL)) {
|
|
avoid = NULL;
|
|
graphics_app_count = count_active_graphic_apps(res, avoid);
|
|
}
|
|
|
|
// If no graphics app was found, return NULL
|
|
if (0 == graphics_app_count) {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// Choose which application to display.
|
|
//
|
|
random_selection = (rand() % graphics_app_count) + 1;
|
|
BOINCTRACE(_T("get_random_graphics_app -- random_selection = '%d'\n"), random_selection);
|
|
|
|
// find the chosen graphics application.
|
|
//
|
|
for (i = 0; i < res.results.size(); i++) {
|
|
if (!strlen(res.results[i]->graphics_exec_path)) continue;
|
|
if (is_same_task(res.results[i], avoid)) continue;
|
|
|
|
current_counter++;
|
|
if (current_counter == random_selection) {
|
|
rp = res.results[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
CLEANUP:
|
|
BOINCTRACE(_T("get_random_graphics_app -- Function End\n"));
|
|
|
|
return rp;
|
|
}
|
|
|
|
|
|
#ifdef __APPLE__
|
|
void CScreensaver::markAsIncompatible(char *gfxAppPath) {
|
|
char *buf = (char *)malloc(strlen(gfxAppPath)+1);
|
|
if (buf) {
|
|
strlcpy(buf, gfxAppPath, malloc_size(buf));
|
|
m_vIncompatibleGfxApps.push_back(buf);
|
|
BOINCTRACE(_T("markAsIncompatible -- path = '%s'\n"), gfxAppPath);
|
|
}
|
|
}
|
|
|
|
bool CScreensaver::isIncompatible(char *appPath) {
|
|
unsigned int i = 0;
|
|
for (i = 0; i < m_vIncompatibleGfxApps.size(); i++) {
|
|
BOINCTRACE(
|
|
_T("isIncompatible -- comparing incompatible path '%s' to candidate path %s\n"),
|
|
m_vIncompatibleGfxApps[i], appPath
|
|
);
|
|
if (strcmp(m_vIncompatibleGfxApps[i], appPath) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// Launch a project (science) graphics application
|
|
//
|
|
int CScreensaver::launch_screensaver(RESULT* rp, GFXAPP_ID& graphics_application) {
|
|
int retval = 0;
|
|
|
|
if (strlen(rp->graphics_exec_path)) {
|
|
// V6 Graphics
|
|
#ifdef __APPLE__
|
|
if (gIsCatalina) {
|
|
// As of OS 10.15 (Catalina) screensavers can no longer:
|
|
// - launch apps that run setuid or setgid
|
|
// - launch apps downloaded from the Internet which have not been
|
|
// specifically approved by the user via Gatekeeper.
|
|
// So instead of launching graphics apps via gfx_switcher, we send an
|
|
// RPC to the client asking the client to launch them via gfx_switcher.
|
|
// See comments in gfx_switcher.cpp for a more detailed explanation.
|
|
// We have tested this on OS 10.13 High Sierra and it works there, too
|
|
//
|
|
retval = rpc->run_graphics_app("runfullscreen", rp->slot, gUserName);
|
|
for (int i=0; i<800; i++) {
|
|
boinc_sleep(0.01); // Wait 8 seconds max
|
|
if (*pid_from_shmem != 0) {
|
|
graphics_application = *pid_from_shmem;
|
|
break;
|
|
}
|
|
}
|
|
// fprintf(stderr, "launch_screensaver got pid %d\n", graphics_application);
|
|
// Inform our helper app what we launched
|
|
fprintf(m_gfx_Cleanup_IPC, "%d\n", graphics_application);
|
|
fflush(m_gfx_Cleanup_IPC);
|
|
} else {
|
|
// For sandbox security, use gfx_switcher to launch gfx app
|
|
// as user boinc_project and group boinc_project.
|
|
//
|
|
// For unknown reasons, the graphics application exits with
|
|
// "RegisterProcess failed (error = -50)" unless we pass its
|
|
// full path twice in the argument list to execv.
|
|
char* argv[5];
|
|
argv[0] = "gfx_Switcher";
|
|
argv[1] = "-launch_gfx";
|
|
argv[2] = strrchr(rp->slot_path, '/');
|
|
if (*argv[2]) argv[2]++; // Point to the slot number in ascii
|
|
|
|
argv[3] = "--fullscreen";
|
|
argv[4] = 0;
|
|
|
|
retval = run_program(
|
|
rp->slot_path,
|
|
m_gfx_Switcher_Path,
|
|
4,
|
|
argv,
|
|
0,
|
|
graphics_application
|
|
);
|
|
}
|
|
|
|
if (graphics_application) {
|
|
launchedGfxApp(rp->graphics_exec_path, graphics_application, rp->slot);
|
|
}
|
|
#else
|
|
char* argv[3];
|
|
argv[0] = rp->graphics_exec_path;
|
|
argv[1] = "--fullscreen";
|
|
argv[2] = 0;
|
|
retval = run_program(
|
|
rp->slot_path,
|
|
rp->graphics_exec_path,
|
|
2,
|
|
argv,
|
|
0,
|
|
graphics_application
|
|
);
|
|
#endif
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
|
|
// Terminate any screensaver graphics application
|
|
//
|
|
int CScreensaver::terminate_v6_screensaver(GFXAPP_ID& graphics_application, RESULT* rp) {
|
|
int retval = 0;
|
|
int i;
|
|
|
|
#ifdef __APPLE__
|
|
pid_t thePID;
|
|
|
|
if (gIsCatalina) {
|
|
// As of OS 10.15 (Catalina) screensavers can no longer launch apps
|
|
// that run setuid or setgid. So instead of killing graphics apps
|
|
// via gfx_switcher, we send an RPC to the client asking the client
|
|
// to kill them via switcher.
|
|
// We have tested this on OS 10.13 High Sierra and it works there, too
|
|
//
|
|
int ignore;
|
|
|
|
if (graphics_application == 0) return 0;
|
|
|
|
// MUTEX may help prevent crashes when terminating an older gfx app which
|
|
// we were displaying using CGWindowListCreateImage under OS X >= 10.13
|
|
// Also prevents reentry when called from our other thread
|
|
pthread_mutex_lock(&saver_mutex);
|
|
|
|
thePID = graphics_application;
|
|
// fprintf(stderr, "stopping pid %d\n", thePID);
|
|
retval = rpc->run_graphics_app("stop", thePID, gUserName);
|
|
//kill_program(graphics_application);
|
|
|
|
// Inform our helper app that we have stopped current graphics app
|
|
fprintf(m_gfx_Cleanup_IPC, "0\n");
|
|
fflush(m_gfx_Cleanup_IPC);
|
|
|
|
launchedGfxApp("", 0, -1);
|
|
|
|
for (i=0; i<200; i++) {
|
|
boinc_sleep(0.01); // Wait 2 seconds max
|
|
if (HasProcessExited(graphics_application, ignore)) {
|
|
break;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&saver_mutex);
|
|
|
|
} else {
|
|
// Under sandbox security, use gfx_switcher to kill default gfx app
|
|
// as user boinc_master and group boinc_master (for default gfx app)
|
|
// or user boinc_project and group boinc_project (for project gfx
|
|
// apps.) The man page for kill() says the user ID of the process
|
|
// sending the signal must match that of the target process, though
|
|
// in practice that seems not to be true on the Mac.
|
|
|
|
char current_dir[PATH_MAX];
|
|
char gfx_pid[16];
|
|
|
|
if (graphics_application == 0) return 0;
|
|
|
|
// MUTEX may help prevent crashes when terminating an older gfx app which
|
|
// we were displaying using CGWindowListCreateImage under OS X >= 10.13
|
|
// Also prevents reentry when called from our other thread
|
|
pthread_mutex_lock(&saver_mutex);
|
|
|
|
sprintf(gfx_pid, "%d", graphics_application);
|
|
getcwd( current_dir, sizeof(current_dir));
|
|
|
|
char* argv[4];
|
|
argv[0] = "gfx_switcher";
|
|
argv[1] = "-kill_gfx";
|
|
argv[2] = gfx_pid;
|
|
argv[3] = 0;
|
|
|
|
retval = run_program(
|
|
current_dir,
|
|
m_gfx_Switcher_Path,
|
|
3,
|
|
argv,
|
|
0,
|
|
thePID
|
|
);
|
|
|
|
if (graphics_application) {
|
|
launchedGfxApp("", 0, -1);
|
|
}
|
|
|
|
for (i=0; i<200; i++) {
|
|
boinc_sleep(0.01); // Wait 2 seconds max
|
|
// Prevent gfx_switcher from becoming a zombie
|
|
if (waitpid(thePID, 0, WNOHANG) == thePID) {
|
|
break;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&saver_mutex);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
HWND hBOINCGraphicsWindow = FindWindow(BOINC_WINDOW_CLASS_NAME, NULL);
|
|
if (hBOINCGraphicsWindow) {
|
|
CloseWindow(hBOINCGraphicsWindow);
|
|
Sleep(1000);
|
|
hBOINCGraphicsWindow = FindWindow(BOINC_WINDOW_CLASS_NAME, NULL);
|
|
if (hBOINCGraphicsWindow) {
|
|
kill_program(graphics_application);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// For safety, call kill_program even under Apple sandbox security
|
|
kill_program(graphics_application);
|
|
return retval;
|
|
}
|
|
|
|
|
|
// Terminate the project (science) graphics application
|
|
//
|
|
int CScreensaver::terminate_screensaver(GFXAPP_ID& graphics_application, RESULT* rp) {
|
|
int retval = 0;
|
|
|
|
if (graphics_application) {
|
|
// V6 Graphics
|
|
if (m_bScience_gfx_running) {
|
|
terminate_v6_screensaver(graphics_application, rp);
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
|
|
// Launch the default graphics application
|
|
//
|
|
int CScreensaver::launch_default_screensaver(char *dir_path, GFXAPP_ID& graphics_application) {
|
|
int retval = 0;
|
|
int num_args;
|
|
|
|
#ifdef __APPLE__
|
|
if (gIsCatalina) {
|
|
// As of OS 10.15 (Catalina) screensavers can no longer:
|
|
// - launch apps that run setuid or setgid
|
|
// - launch apps downloaded from the Internet which have not been
|
|
// specifically approved by the user via Gatekeeper.
|
|
// So instead of launching graphics apps via gfx_switcher, we send an
|
|
// RPC to the client asking the client to launch them via gfx_switcher.
|
|
// See comments in gfx_switcher.cpp for a more detailed explanation.
|
|
// We have tested this on OS 10.13 High Sierra and it works there, too
|
|
//
|
|
int thePID = -1;
|
|
retval = rpc->run_graphics_app("runfullscreen", thePID, gUserName);
|
|
for (int i=0; i<800; i++) {
|
|
boinc_sleep(0.01); // Wait 8 seconds max
|
|
if (*pid_from_shmem != 0) {
|
|
graphics_application = *pid_from_shmem;
|
|
break;
|
|
}
|
|
}
|
|
// fprintf(stderr, "launch_screensaver got pid %d\n", graphics_application);
|
|
// Inform our helper app what we launched
|
|
fprintf(m_gfx_Cleanup_IPC, "%d\n", graphics_application);
|
|
fflush(m_gfx_Cleanup_IPC);
|
|
} else {
|
|
// For sandbox security, use gfx_switcher to launch default
|
|
// gfx app as user boinc_master and group boinc_master.
|
|
char* argv[6];
|
|
|
|
argv[0] = "gfx_switcher";
|
|
argv[1] = "-default_gfx";
|
|
argv[2] = THE_DEFAULT_SS_EXECUTABLE; // Will be changed by gfx_switcher
|
|
argv[3] = "--fullscreen";
|
|
argv[4] = 0;
|
|
argv[5] = 0;
|
|
if (!m_bConnected) {
|
|
BOINCTRACE(_T("launch_default_screensaver using --retry_connect argument\n"));
|
|
argv[4] = "--retry_connect";
|
|
num_args = 5;
|
|
} else {
|
|
num_args = 4;
|
|
}
|
|
|
|
retval = run_program(
|
|
dir_path,
|
|
m_gfx_Switcher_Path,
|
|
num_args,
|
|
argv,
|
|
0,
|
|
graphics_application
|
|
);
|
|
}
|
|
|
|
if (graphics_application) {
|
|
launchedGfxApp("boincscr", graphics_application, -1);
|
|
}
|
|
|
|
BOINCTRACE(_T("launch_default_screensaver returned %d\n"), retval);
|
|
|
|
#else
|
|
char* argv[4];
|
|
char full_path[1024];
|
|
|
|
strlcpy(full_path, dir_path, sizeof(full_path));
|
|
strlcat(full_path, PATH_SEPARATOR, sizeof(full_path));
|
|
strlcat(full_path, THE_DEFAULT_SS_EXECUTABLE, sizeof(full_path));
|
|
|
|
argv[0] = full_path;
|
|
argv[1] = "--fullscreen";
|
|
argv[2] = 0;
|
|
argv[3] = 0;
|
|
if (!m_bConnected) {
|
|
BOINCTRACE(_T("launch_default_screensaver using --retry_connect argument\n"));
|
|
argv[2] = "--retry_connect";
|
|
num_args = 3;
|
|
} else {
|
|
num_args = 2;
|
|
}
|
|
|
|
retval = run_program(
|
|
dir_path,
|
|
full_path,
|
|
num_args,
|
|
argv,
|
|
0,
|
|
graphics_application
|
|
);
|
|
|
|
BOINCTRACE(_T("launch_default_screensaver %s returned %d\n"), full_path, retval);
|
|
|
|
#endif
|
|
return retval;
|
|
}
|
|
|
|
|
|
// Terminate the default graphics application
|
|
//
|
|
int CScreensaver::terminate_default_screensaver(GFXAPP_ID& graphics_application) {
|
|
int retval = 0;
|
|
|
|
if (! graphics_application) return 0;
|
|
retval = terminate_v6_screensaver(graphics_application, NULL);
|
|
return retval;
|
|
}
|
|
|
|
|
|
// If we cannot connect to the core client:
|
|
// - we retry connecting every 10 seconds
|
|
// - we launch the default graphics application with the argument --retry_connect, so
|
|
// it will continue running and will also retry connecting every 10 seconds.
|
|
//
|
|
// If we successfully connected to the core client, launch the default graphics application
|
|
// without the argument --retry_connect. If it can't connect, it will return immediately
|
|
// with the exit code ERR_CONNECT. In that case, we assume it was blocked by a firewall
|
|
// and so we run only project (science) graphics.
|
|
|
|
DataMgmtProcType CScreensaver::DataManagementProc() {
|
|
int retval = 0;
|
|
int suspend_reason = 0;
|
|
RESULT* theResult = NULL;
|
|
RESULT previous_result;
|
|
// previous_result_ptr = &previous_result when previous_result is valid, else NULL
|
|
RESULT* previous_result_ptr = NULL;
|
|
int iResultCount = 0;
|
|
int iIndex = 0;
|
|
double default_phase_start_time = 0.0;
|
|
double science_phase_start_time = 0.0;
|
|
double last_change_time = 0.0;
|
|
// If we run default screensaver during science phase because no science graphics
|
|
// are available, then shorten next default graphics phase by that much time.
|
|
double default_saver_start_time_in_science_phase = 0.0;
|
|
double default_saver_duration_in_science_phase = 0.0;
|
|
|
|
SS_PHASE ss_phase = DEFAULT_SS_PHASE;
|
|
bool switch_to_default_gfx = false;
|
|
bool killing_default_gfx = false;
|
|
int exit_status = 0;
|
|
|
|
char* default_ss_dir_path = NULL;
|
|
char full_path[1024];
|
|
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Display screen saver loading message\n"));
|
|
SetError(TRUE, SCRAPPERR_BOINCSCREENSAVERLOADING); // No GFX App is running: show moving BOINC logo
|
|
#ifdef _WIN32
|
|
m_tThreadCreateTime = time(0);
|
|
|
|
// Set the starting point for iterating through the results
|
|
m_iLastResultShown = 0;
|
|
m_tLastResultChangeTime = 0;
|
|
#endif
|
|
|
|
m_bDefault_ss_exists = false;
|
|
m_bScience_gfx_running = false;
|
|
m_bDefault_gfx_running = false;
|
|
m_bShow_default_ss_first = false;
|
|
graphics_app_result_ptr = NULL;
|
|
|
|
#ifdef __APPLE__
|
|
m_vIncompatibleGfxApps.clear();
|
|
default_ss_dir_path = "/Library/Application Support/BOINC Data";
|
|
if (gIsCatalina) {
|
|
char shmem_name[MAXPATHLEN];
|
|
snprintf(shmem_name, sizeof(shmem_name), "/tmp/boinc_ss_%s", gUserName);
|
|
retval = create_shmem_mmap(shmem_name, sizeof(int), (void**)&pid_from_shmem);
|
|
// make sure user/group RW permissions are set, but not other.
|
|
//
|
|
if (retval == 0) {
|
|
chmod(shmem_name, 0666);
|
|
retval = attach_shmem_mmap(shmem_name, (void**)&pid_from_shmem);
|
|
}
|
|
if (retval == 0) {
|
|
*pid_from_shmem = 0;
|
|
}
|
|
}
|
|
#else
|
|
default_ss_dir_path = (char*)m_strBOINCInstallDirectory.c_str();
|
|
#endif
|
|
|
|
strlcpy(full_path, default_ss_dir_path, sizeof(full_path));
|
|
strlcat(full_path, PATH_SEPARATOR, sizeof(full_path));
|
|
strlcat(full_path, THE_DEFAULT_SS_EXECUTABLE, sizeof(full_path));
|
|
|
|
if (boinc_file_exists(full_path)) {
|
|
m_bDefault_ss_exists = true;
|
|
} else {
|
|
SetError(TRUE, SCRAPPERR_CANTLAUNCHDEFAULTGFXAPP); // No GFX App is running: show moving BOINC logo
|
|
}
|
|
|
|
if (m_bDefault_ss_exists && m_bShow_default_ss_first) {
|
|
ss_phase = DEFAULT_SS_PHASE;
|
|
default_phase_start_time = dtime();
|
|
science_phase_start_time = 0;
|
|
switch_to_default_gfx = true;
|
|
} else {
|
|
ss_phase = SCIENCE_SS_PHASE;
|
|
default_phase_start_time = 0;
|
|
science_phase_start_time = dtime();
|
|
}
|
|
|
|
while (true) {
|
|
for (int i = 0; i < 4; i++) {
|
|
// ***
|
|
// *** Things that should be run frequently.
|
|
// *** 4 times per second.
|
|
// ***
|
|
|
|
// Are we supposed to exit the screensaver?
|
|
if (m_bQuitDataManagementProc) { // If main thread has requested we exit
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Thread told to stop\n"));
|
|
if (m_hGraphicsApplication || graphics_app_result_ptr) {
|
|
if (m_bDefault_gfx_running) {
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Terminating default screensaver\n"));
|
|
terminate_default_screensaver(m_hGraphicsApplication);
|
|
} else {
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Terminating screensaver\n"));
|
|
terminate_screensaver(m_hGraphicsApplication, graphics_app_result_ptr);
|
|
}
|
|
graphics_app_result_ptr = NULL;
|
|
previous_result_ptr = NULL;
|
|
m_hGraphicsApplication = 0;
|
|
}
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Stopping...\n"));
|
|
m_bDataManagementProcStopped = true; // Tell main thread that we exited
|
|
return 0; // Exit the thread
|
|
}
|
|
boinc_sleep(0.25);
|
|
}
|
|
|
|
// ***
|
|
// *** Things that should be run less frequently.
|
|
// *** 1 time per second.
|
|
// ***
|
|
|
|
// Blank screen saver?
|
|
if ((m_dwBlankScreen) && (time(0) > m_dwBlankTime) && (m_dwBlankTime > 0)) {
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Time to blank\n"));
|
|
SetError(FALSE, SCRAPPERR_SCREENSAVERBLANKED); // Blanked - hide moving BOINC logo
|
|
m_bQuitDataManagementProc = true;
|
|
continue; // Code above will exit the thread
|
|
}
|
|
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - ErrorMode = '%d', ErrorCode = '%x'\n"), m_bErrorMode, m_hrError);
|
|
|
|
if (!m_bConnected) {
|
|
HandleRPCError();
|
|
}
|
|
|
|
if (m_bConnected) {
|
|
// Do we need to get the core client state?
|
|
if (m_bResetCoreState) {
|
|
// Try and get the current state of the CC
|
|
retval = rpc->get_state(state);
|
|
if (retval) {
|
|
// CC may not yet be running
|
|
HandleRPCError();
|
|
continue;
|
|
} else {
|
|
m_bResetCoreState = false;
|
|
}
|
|
}
|
|
|
|
// Update our task list
|
|
retval = rpc->get_screensaver_tasks(suspend_reason, results);
|
|
if (retval) {
|
|
// rpc call returned error
|
|
HandleRPCError();
|
|
m_bResetCoreState = true;
|
|
continue;
|
|
}
|
|
} else {
|
|
results.clear();
|
|
}
|
|
|
|
// Time to switch to default graphics phase?
|
|
if (m_bDefault_ss_exists && (ss_phase == SCIENCE_SS_PHASE) && (m_fGFXDefaultPeriod > 0)) {
|
|
if (science_phase_start_time && ((dtime() - science_phase_start_time) > m_fGFXSciencePeriod)) {
|
|
if (!m_bDefault_gfx_running) {
|
|
switch_to_default_gfx = true;
|
|
}
|
|
ss_phase = DEFAULT_SS_PHASE;
|
|
default_phase_start_time = dtime();
|
|
science_phase_start_time = 0;
|
|
if (m_bDefault_gfx_running && default_saver_start_time_in_science_phase) {
|
|
// Remember how long default graphics ran during science phase
|
|
default_saver_duration_in_science_phase += (dtime() - default_saver_start_time_in_science_phase);
|
|
}
|
|
default_saver_start_time_in_science_phase = 0;
|
|
}
|
|
}
|
|
|
|
// Time to switch to science graphics phase?
|
|
if ((ss_phase == DEFAULT_SS_PHASE) && m_bConnected && (m_fGFXSciencePeriod > 0)) {
|
|
if (default_phase_start_time &&
|
|
((dtime() - default_phase_start_time + default_saver_duration_in_science_phase)
|
|
> m_fGFXDefaultPeriod)) {
|
|
// BOINCTRACE(_T("CScreensaver::Ending Default phase: now=%f, default_phase_start_time=%f, default_saver_duration_in_science_phase=%f\n"),
|
|
// dtime(), default_phase_start_time, default_saver_duration_in_science_phase);
|
|
ss_phase = SCIENCE_SS_PHASE;
|
|
default_phase_start_time = 0;
|
|
default_saver_duration_in_science_phase = 0;
|
|
science_phase_start_time = dtime();
|
|
if (m_bDefault_gfx_running) {
|
|
default_saver_start_time_in_science_phase = science_phase_start_time;
|
|
}
|
|
switch_to_default_gfx = false;
|
|
}
|
|
}
|
|
|
|
// Core client suspended?
|
|
// We ignore SUSPEND_REASON_CPU_USAGE in SS coordinator, so it won't kill graphics apps for
|
|
// short-term CPU usage spikes (such as anti-virus.) Added 9 April 2010
|
|
if (suspend_reason && !(suspend_reason & (SUSPEND_REASON_CPU_THROTTLE | SUSPEND_REASON_CPU_USAGE))) {
|
|
if (!m_bDefault_gfx_running) {
|
|
SetError(TRUE, m_hrError); // No GFX App is running: show moving BOINC logo
|
|
if (m_bDefault_ss_exists) {
|
|
switch_to_default_gfx = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (switch_to_default_gfx) {
|
|
if (m_bScience_gfx_running) {
|
|
if (m_hGraphicsApplication || previous_result_ptr) {
|
|
// use previous_result_ptr because graphics_app_result_ptr may no longer be valid
|
|
terminate_screensaver(m_hGraphicsApplication, previous_result_ptr);
|
|
if (m_hGraphicsApplication == 0) {
|
|
graphics_app_result_ptr = NULL;
|
|
m_bScience_gfx_running = false;
|
|
} else {
|
|
// HasProcessExited() test will clear m_hGraphicsApplication and graphics_app_result_ptr
|
|
}
|
|
previous_result_ptr = NULL;
|
|
}
|
|
} else {
|
|
if (!m_bDefault_gfx_running) {
|
|
switch_to_default_gfx = false;
|
|
retval = launch_default_screensaver(default_ss_dir_path, m_hGraphicsApplication);
|
|
if (retval) {
|
|
m_hGraphicsApplication = 0;
|
|
previous_result_ptr = NULL;
|
|
graphics_app_result_ptr = NULL;
|
|
m_bDefault_gfx_running = false;
|
|
SetError(TRUE, SCRAPPERR_CANTLAUNCHDEFAULTGFXAPP); // No GFX App is running: show moving BOINC logo
|
|
} else {
|
|
m_bDefault_gfx_running = true;
|
|
if (ss_phase == SCIENCE_SS_PHASE) {
|
|
default_saver_start_time_in_science_phase = dtime();
|
|
}
|
|
SetError(FALSE, SCRAPPERR_BOINCSCREENSAVERLOADING); // A GFX App is running: hide moving BOINC logo
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((ss_phase == SCIENCE_SS_PHASE) && !switch_to_default_gfx) {
|
|
|
|
#if SIMULATE_NO_GRAPHICS /* FOR TESTING */
|
|
|
|
if (!m_bDefault_gfx_running) {
|
|
SetError(TRUE, m_hrError); // No GFX App is running: show moving BOINC logo
|
|
if (m_bDefault_ss_exists) {
|
|
switch_to_default_gfx = true;
|
|
}
|
|
}
|
|
|
|
#else /* NORMAL OPERATION */
|
|
|
|
if (m_bScience_gfx_running) {
|
|
// Is the current graphics app's associated task still running?
|
|
|
|
if ((m_hGraphicsApplication) || (graphics_app_result_ptr)) {
|
|
iResultCount = (int)results.results.size();
|
|
graphics_app_result_ptr = NULL;
|
|
|
|
// Find the current task in the new results vector (if it still exists)
|
|
for (iIndex = 0; iIndex < iResultCount; iIndex++) {
|
|
theResult = results.results.at(iIndex);
|
|
|
|
if (is_same_task(theResult, previous_result_ptr)) {
|
|
graphics_app_result_ptr = theResult;
|
|
previous_result = *theResult;
|
|
previous_result_ptr = &previous_result;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// V6 graphics only: if worker application has stopped running, terminate_screensaver
|
|
if ((graphics_app_result_ptr == NULL) && (m_hGraphicsApplication != 0)) {
|
|
if (previous_result_ptr) {
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - %s finished\n"),
|
|
previous_result.graphics_exec_path
|
|
);
|
|
}
|
|
terminate_screensaver(m_hGraphicsApplication, previous_result_ptr);
|
|
previous_result_ptr = NULL;
|
|
if (m_hGraphicsApplication == 0) {
|
|
graphics_app_result_ptr = NULL;
|
|
m_bScience_gfx_running = false;
|
|
// Save previous_result and previous_result_ptr for get_random_graphics_app() call
|
|
} else {
|
|
// HasProcessExited() test will clear m_hGraphicsApplication and graphics_app_result_ptr
|
|
}
|
|
}
|
|
|
|
if (last_change_time && (m_fGFXChangePeriod > 0) && ((dtime() - last_change_time) > m_fGFXChangePeriod) ) {
|
|
if (count_active_graphic_apps(results, previous_result_ptr) > 0) {
|
|
if (previous_result_ptr) {
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - time to change: %s / %s\n"),
|
|
previous_result.name, previous_result.graphics_exec_path
|
|
);
|
|
}
|
|
terminate_screensaver(m_hGraphicsApplication, graphics_app_result_ptr);
|
|
if (m_hGraphicsApplication == 0) {
|
|
graphics_app_result_ptr = NULL;
|
|
m_bScience_gfx_running = false;
|
|
// Save previous_result and previous_result_ptr for get_random_graphics_app() call
|
|
} else {
|
|
// HasProcessExited() test will clear m_hGraphicsApplication and graphics_app_result_ptr
|
|
}
|
|
}
|
|
last_change_time = dtime();
|
|
}
|
|
}
|
|
} // End if (m_bScience_gfx_running)
|
|
|
|
// If no current graphics app, pick an active task at random
|
|
// and launch its graphics app
|
|
//
|
|
if ((m_bDefault_gfx_running || (m_hGraphicsApplication == 0)) && (graphics_app_result_ptr == NULL)) {
|
|
if (suspend_reason && !(suspend_reason & (SUSPEND_REASON_CPU_THROTTLE | SUSPEND_REASON_CPU_USAGE))) {
|
|
graphics_app_result_ptr = NULL;
|
|
} else {
|
|
graphics_app_result_ptr = get_random_graphics_app(results, previous_result_ptr);
|
|
previous_result_ptr = NULL;
|
|
}
|
|
|
|
if (graphics_app_result_ptr) {
|
|
if (m_bDefault_gfx_running) {
|
|
terminate_default_screensaver(m_hGraphicsApplication);
|
|
killing_default_gfx = true;
|
|
// Remember how long default graphics ran during science phase
|
|
if (default_saver_start_time_in_science_phase) {
|
|
default_saver_duration_in_science_phase += (dtime() - default_saver_start_time_in_science_phase);
|
|
//BOINCTRACE(_T("CScreensaver::During Science phase: now=%f, default_saver_start_time=%f, default_saver_duration=%f\n"),
|
|
// dtime(), default_saver_start_time_in_science_phase, default_saver_duration_in_science_phase);
|
|
}
|
|
default_saver_start_time_in_science_phase = 0;
|
|
// HasProcessExited() test will clear
|
|
// m_hGraphicsApplication and graphics_app_result_ptr
|
|
} else {
|
|
retval = launch_screensaver(graphics_app_result_ptr, m_hGraphicsApplication);
|
|
if (retval) {
|
|
m_hGraphicsApplication = 0;
|
|
previous_result_ptr = NULL;
|
|
graphics_app_result_ptr = NULL;
|
|
m_bScience_gfx_running = false;
|
|
} else {
|
|
// A GFX App is running: hide moving BOINC logo
|
|
//
|
|
SetError(FALSE, SCRAPPERR_BOINCSCREENSAVERLOADING);
|
|
last_change_time = dtime();
|
|
m_bScience_gfx_running = true;
|
|
// Make a local copy of current result, since original pointer
|
|
// may have been freed by the time we perform later tests
|
|
previous_result = *graphics_app_result_ptr;
|
|
previous_result_ptr = &previous_result;
|
|
if (previous_result_ptr) {
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - launching %s\n"),
|
|
previous_result.graphics_exec_path
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (!m_bDefault_gfx_running) {
|
|
// We can't run a science graphics app, so run the default graphics if available
|
|
SetError(TRUE, m_hrError);
|
|
if (m_bDefault_ss_exists) {
|
|
switch_to_default_gfx = true;
|
|
}
|
|
}
|
|
|
|
} // End if no science graphics available
|
|
} // End if no current science graphics app is running
|
|
|
|
#endif // ! SIMULATE_NO_GRAPHICS
|
|
|
|
if (switch_to_default_gfx) {
|
|
switch_to_default_gfx = false;
|
|
if (!m_bDefault_gfx_running) {
|
|
retval = launch_default_screensaver(default_ss_dir_path, m_hGraphicsApplication);
|
|
if (retval) {
|
|
m_hGraphicsApplication = 0;
|
|
previous_result_ptr = NULL;
|
|
graphics_app_result_ptr = NULL;
|
|
m_bDefault_gfx_running = false;
|
|
SetError(TRUE, SCRAPPERR_CANTLAUNCHDEFAULTGFXAPP);
|
|
// No GFX App is running: show BOINC logo
|
|
} else {
|
|
m_bDefault_gfx_running = true;
|
|
default_saver_start_time_in_science_phase = dtime();
|
|
SetError(FALSE, SCRAPPERR_BOINCSCREENSAVERLOADING);
|
|
// Default GFX App is running: hide moving BOINC logo
|
|
}
|
|
}
|
|
}
|
|
} // End if ((ss_phase == SCIENCE_SS_PHASE) && !switch_to_default_gfx)
|
|
|
|
|
|
|
|
// Is the graphics app still running?
|
|
if (m_hGraphicsApplication) {
|
|
if (HasProcessExited(m_hGraphicsApplication, exit_status)) {
|
|
// Something has happened to the previously selected screensaver
|
|
// application. Start a different one.
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Graphics application isn't running, start a new one.\n"));
|
|
if (m_bDefault_gfx_running) {
|
|
// If we were able to connect to core client
|
|
// but gfx app can't, don't use it.
|
|
//
|
|
BOINCTRACE(_T("CScreensaver::DataManagementProc - Default graphics application exited with code %d.\n"), exit_status);
|
|
if (!killing_default_gfx) { // If this is an unexpected exit
|
|
if (exit_status == DEFAULT_GFX_CANT_CONNECT) {
|
|
SetError(TRUE, SCRAPPERR_DEFAULTGFXAPPCANTCONNECT);
|
|
// No GFX App is running: show moving BOINC logo
|
|
} else {
|
|
SetError(TRUE, SCRAPPERR_DEFAULTGFXAPPCRASHED);
|
|
// No GFX App is running: show moving BOINC logo
|
|
}
|
|
m_bDefault_ss_exists = false;
|
|
ss_phase = SCIENCE_SS_PHASE;
|
|
}
|
|
killing_default_gfx = false;
|
|
}
|
|
SetError(TRUE, SCRAPPERR_BOINCNOGRAPHICSAPPSEXECUTING);
|
|
// No GFX App is running: show moving BOINC logo
|
|
m_hGraphicsApplication = 0;
|
|
graphics_app_result_ptr = NULL;
|
|
m_bDefault_gfx_running = false;
|
|
m_bScience_gfx_running = false;
|
|
#ifdef __APPLE__
|
|
launchedGfxApp("", 0, -1);
|
|
#endif
|
|
continue;
|
|
}
|
|
}
|
|
} // end while(true)
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
BOOL CScreensaver::HasProcessExited(HANDLE pid_handle, int &exitCode) {
|
|
unsigned long status = 1;
|
|
if (GetExitCodeProcess(pid_handle, &status)) {
|
|
if (status == STILL_ACTIVE) {
|
|
exitCode = 0;
|
|
return false;
|
|
}
|
|
}
|
|
exitCode = (int)status;
|
|
return true;
|
|
}
|
|
#else
|
|
bool CScreensaver::HasProcessExited(pid_t pid, int &exitCode) {
|
|
int status;
|
|
pid_t p;
|
|
|
|
if (gIsCatalina) {
|
|
// Only the process which launched an app can use waitpid() to test
|
|
// whether that app is still running. If we sent an RPC to the client
|
|
// asking the client to launch a graphics app via switcher, we must
|
|
// send another RPC to the client to call waitpid() for that app.
|
|
//
|
|
if (pid_from_shmem) {
|
|
//fprintf(stderr, "screensaver HasProcessExited got pid_from_shmem = %d\n", *pid_from_shmem);
|
|
if (*pid_from_shmem != 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
p = waitpid(pid, &status, WNOHANG);
|
|
exitCode = WEXITSTATUS(status);
|
|
if (p == pid) return true; // process has exited
|
|
if (p == -1) return true; // PID doesn't exist
|
|
exitCode = 0;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
|
|
void CScreensaver::GetDefaultDisplayPeriods(struct ss_periods &periods) {
|
|
char* default_data_dir_path = NULL;
|
|
char buf[1024];
|
|
FILE* f;
|
|
MIOFILE mf;
|
|
|
|
periods.GFXDefaultPeriod = GFX_DEFAULT_PERIOD;
|
|
periods.GFXSciencePeriod = GFX_SCIENCE_PERIOD;
|
|
periods.GFXChangePeriod = GFX_CHANGE_PERIOD;
|
|
periods.Show_default_ss_first = false;
|
|
|
|
#ifdef __APPLE__
|
|
default_data_dir_path = "/Library/Application Support/BOINC Data";
|
|
#else
|
|
default_data_dir_path = (char*)m_strBOINCDataDirectory.c_str();
|
|
#endif
|
|
|
|
strlcpy(buf, default_data_dir_path, sizeof(buf));
|
|
strlcat(buf, PATH_SEPARATOR, sizeof(buf));
|
|
strlcat(buf, THE_SS_CONFIG_FILE, sizeof(buf));
|
|
|
|
f = boinc_fopen(buf, "r");
|
|
if (!f) return;
|
|
|
|
mf.init_file(f);
|
|
XML_PARSER xp(&mf);
|
|
|
|
while (!xp.get_tag()) {
|
|
if (xp.parse_bool("default_ss_first", periods.Show_default_ss_first)) continue;
|
|
if (xp.parse_double("default_gfx_duration", periods.GFXDefaultPeriod)) continue;
|
|
if (xp.parse_double("science_gfx_duration", periods.GFXSciencePeriod)) continue;
|
|
if (xp.parse_double("science_gfx_change_interval", periods.GFXChangePeriod)) continue;
|
|
}
|
|
fclose(f);
|
|
|
|
BOINCTRACE(
|
|
_T("CScreensaver::GetDefaultDisplayPeriods: m_bShow_default_ss_first=%d, m_fGFXDefaultPeriod=%f, m_fGFXSciencePeriod=%f, m_fGFXChangePeriod=%f\n"),
|
|
(int)periods.Show_default_ss_first,
|
|
periods.GFXDefaultPeriod,
|
|
periods.GFXSciencePeriod,
|
|
periods.GFXChangePeriod
|
|
);
|
|
}
|