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 //
19 //  mac_saver_module.cpp
20 //  BOINC_Saver_Module
21 //
22 
23 #include <IOKit/IOKitLib.h>
24 #include <Carbon/Carbon.h>
25 #include <CoreFoundation/CoreFoundation.h>
26 
27 #ifdef __cplusplus
28 extern "C" {
29 #endif
30 #include <IOKit/ps/IOPowerSources.h>
31 #include <IOKit/ps/IOPSKeys.h>
32 #ifdef __cplusplus
33 }    // extern "C"
34 #endif
35 
36 #include <stdio.h>
37 #include <time.h>
38 #include <sys/types.h>
39 #include <sys/ipc.h>
40 #include <sys/shm.h>
41 #include <unistd.h>
42 #include <limits.h>
43 #include <sys/stat.h>
44 #include <sys/param.h>  // for MAXPATHLEN
45 #include <pthread.h>
46 
47 #include "gui_rpc_client.h"
48 #include "common_defs.h"
49 #include "util.h"
50 #include "Mac_Saver_Module.h"
51 #include "screensaver.h"
52 #include "diagnostics.h"
53 #include "str_replace.h"
54 #include "mac_util.h"
55 
56 //#include <drivers/event_status_driver.h>
57 
58 // Flags for testing & debugging
59 #define CREATE_LOG 0
60 #define USE_SPECIAL_LOG_FILE 1
61 
62 #define TEXTLOGOFREQUENCY 60 /* Number of times per second to update moving logo with text */
63 #define NOTEXTLOGOFREQUENCY 4 /* Times per second to call animateOneFrame if no moving logo with text */
64 #define GFX_STARTING_MSG_DURATION 45 /* seconds to show ScreenSaverAppStartingMsg */
65 #define RPC_RETRY_INTERVAL 10 /* # of seconds between retries connecting to core client */
66 #define BATTERY_CHECK_INTERVAL 3 /* # of seconds between checks of whether running on batteries */
67 
68 enum SaverState {
69     SaverState_Idle,
70     SaverState_LaunchingCoreClient,
71     SaverState_CoreClientRunning,
72     SaverState_RelaunchCoreClient,
73     SaverState_ConnectedToCoreClient,
74 
75     SaverState_CantLaunchCoreClient,
76     SaverState_ControlPanelTestMode,
77     SaverState_UnrecoverableError
78 };
79 
80 
81 static CScreensaver* gspScreensaver = NULL;
82 
83 extern int gGoToBlank;      // True if we are to blank the screen
84 extern int gBlankingTime;   // Delay in minutes before blanking the screen
85 extern CFStringRef gPathToBundleResources;
86 
87 static SaverState saverState = SaverState_Idle;
88 // int gQuitCounter = 0;
89 
90 static bool IsDualGPUMacbook = false;
91 static io_connect_t GPUSelectConnect = IO_OBJECT_NULL;
92 static bool OKToRunOnBatteries = false;
93 static bool RunningOnBattery = true;
94 static time_t ScreenSaverStartTime = 0;
95 static bool ScreenIsBlanked = false;
96 static int retryCount = 0;
97 
98 const char *  CantLaunchCCMsg = "Unable to launch BOINC application.";
99 const char *  LaunchingCCMsg = "Launching BOINC application.";
100 const char *  ConnectingCCMsg = "Connecting to BOINC application.";
101 const char *  ConnectedCCMsg = "Communicating with BOINC application.";
102 const char *  BOINCUnrecoverableErrorMsg = "Sorry, an unrecoverable error occurred";
103 const char *  BOINCTestModeMsg = "BOINC screensaver test: success.";
104 const char *  ScreenSaverAppStartingMsg = "Starting screensaver graphics.\nPlease wait ...";
105 const char *  CantLaunchDefaultGFXAppMsg = "Can't launch default screensaver module. Please reinstall BOINC";
106 const char *  DefaultGFXAppCantRPCMsg = "Default screensaver module couldn't connect to BOINC application";
107 const char *  DefaultGFXAppCrashedMsg = "Default screensaver module had an unrecoverable error";
108 const char *  RunningOnBatteryMsg = "Computing and screensaver disabled while running on battery power.";
109 const char *  IncompatibleMsg = " is not compatible with this version of OS X.";
110 
111 //const char *  BOINCExitedSaverMode = "BOINC is no longer in screensaver mode.";
112 
113 
114 // If there are multiple displays, this may get called
115 // multiple times (once for each display), so we need to guard
116 // against any problems that may cause.
initBOINCSaver()117 void initBOINCSaver() {
118     diagnostics_init(
119         BOINC_DIAG_PERUSERLOGFILES |
120         BOINC_DIAG_REDIRECTSTDOUT |
121         BOINC_DIAG_REDIRECTSTDERR |
122         BOINC_DIAG_TRACETOSTDOUT,
123         "stdoutscr", "stderrscr"
124         );
125 
126     if (gspScreensaver == NULL) {
127         gspScreensaver = new CScreensaver();
128     }
129 }
130 
131 
startBOINCSaver()132 int startBOINCSaver() {
133     if (gspScreensaver) {
134         return gspScreensaver->Create();
135     }
136     return TEXTLOGOFREQUENCY;
137 }
138 
139 
getSSMessage(char ** theMessage,int * coveredFreq)140 int getSSMessage(char **theMessage, int* coveredFreq) {
141     if (gspScreensaver) {
142         return gspScreensaver->getSSMessage(theMessage, coveredFreq);
143     } else {
144         *theMessage = "";
145         *coveredFreq = 0;
146         return NOTEXTLOGOFREQUENCY;
147     }
148 };
149 
150 
windowIsCovered()151 void windowIsCovered() {
152     if (gspScreensaver) {
153         gspScreensaver->windowIsCovered();
154     }
155 }
156 
157 
drawPreview(CGContextRef myContext)158 void drawPreview(CGContextRef myContext) {
159     if (gspScreensaver) {
160         gspScreensaver->drawPreview(myContext);
161     }
162 };
163 
164 
165 // If there are multiple displays, this may get called
166 // multiple times (once for each display), so we need to guard
167 // against any problems that may cause.
closeBOINCSaver()168 void closeBOINCSaver() {
169     if (gspScreensaver) {
170         gspScreensaver->ShutdownSaver();
171         delete gspScreensaver;
172         gspScreensaver = NULL;
173     }
174 }
175 
176 
incompatibleGfxApp(char * appPath,pid_t pid,int slot)177 void incompatibleGfxApp(char * appPath, pid_t pid, int slot){
178     char *p;
179     static char buf[1024];
180     static double msgstartTime = 0.0;
181     int retval;
182     bool gotAppName = false;
183     int exitStatus;
184 
185     if (gspScreensaver) {
186         if (msgstartTime == 0.0) {
187             msgstartTime = getDTime();
188             buf[0] = '\0';
189 
190             if (gspScreensaver->HasProcessExited(pid, exitStatus)) {
191                 return;
192             }
193 
194             retval = gspScreensaver->rpc->get_state(gspScreensaver->state);
195             if (!retval) {
196                 strlcpy(buf, "Screensaver ", sizeof(buf));
197                 for (int i=0; i<gspScreensaver->state.results.size(); i++) {
198                     RESULT* r = gspScreensaver->state.results[i];
199                     if (r->slot == slot) {
200                         if (r->app) {
201                             if (r->app->user_friendly_name[0]) {
202                                 strlcat(buf, "of application ", sizeof(buf));
203                                 strlcat(buf, r->app->user_friendly_name, sizeof(buf));
204                                 gotAppName = true;
205                             }
206                         }
207                     }
208                 }
209             } // if (!retval)
210 
211             if (!gotAppName) {
212                 p = strrchr(appPath, '/');
213                 if (!p) p = appPath;
214                 strlcat(buf, "\"", sizeof(buf));
215                 strlcat(buf, p+1, sizeof(buf));
216                 strlcat(buf, "\"", sizeof(buf));
217             }
218             strlcat(buf, IncompatibleMsg, sizeof(buf));
219             gspScreensaver->setSSMessageText(buf);
220             gspScreensaver->SetError(0, SCRAPPERR_GFXAPPINCOMPATIBLE);
221         }   // End if (msgstartTime == 0.0)
222 
223         if (msgstartTime && (getDTime() - msgstartTime > 5.0)) {
224             gspScreensaver->markAsIncompatible(appPath);
225             launchedGfxApp("", 0, -1);
226             msgstartTime = 0.0;
227             gspScreensaver->terminate_screensaver(pid, NULL);
228         }
229     }
230 }
231 
232 
getGFXDefaultPeriod()233 double getGFXDefaultPeriod() {
234     if (gspScreensaver) {
235         return gspScreensaver->m_fGFXDefaultPeriod;
236     }
237     return 0;
238 }
239 
240 
getGFXSciencePeriod()241 double getGFXSciencePeriod() {
242     if (gspScreensaver) {
243         return gspScreensaver->m_fGFXSciencePeriod;
244     }
245     return 0;
246 }
247 
248 
getGGFXChangePeriod()249 double getGGFXChangePeriod() {
250     if (gspScreensaver) {
251         return gspScreensaver->m_fGFXChangePeriod;
252     }
253     return 0;
254 }
255 
256 
setGFXDefaultPeriod(double value)257 void setGFXDefaultPeriod(double value) {
258     if (gspScreensaver) {
259         gspScreensaver->m_fGFXDefaultPeriod = value;
260     }
261 }
262 
263 
setGFXSciencePeriod(double value)264 void setGFXSciencePeriod(double value) {
265     if (gspScreensaver) {
266         gspScreensaver->m_fGFXSciencePeriod = value;
267     }
268 }
269 
270 
setGGFXChangePeriod(double value)271 void setGGFXChangePeriod(double value) {
272     if (gspScreensaver) {
273         gspScreensaver->m_fGFXChangePeriod = value;
274     }
275 }
276 
277 
getDTime()278 double getDTime() {
279     return dtime();
280 }
281 
282 
doBoinc_Sleep(double seconds)283 void doBoinc_Sleep(double seconds) {
284     boinc_sleep(seconds);
285 }
286 
287 
CScreensaver()288 CScreensaver::CScreensaver() {
289     struct ss_periods periods;
290 
291     m_dwBlankScreen = 0;
292     m_dwBlankTime = 0;
293     m_bErrorMode = false;
294     m_hrError = 0;
295     // Display first status update after 5 seconds
296     m_iGraphicsStartingMsgCounter = 0;
297     saverState = SaverState_Idle;
298     m_wasAlreadyRunning = false;
299     m_CoreClientPID = nil;
300     setSSMessageText(0);
301     m_CurrentBannerMessage = 0;
302     m_bQuitDataManagementProc = false;
303     m_bDataManagementProcStopped = false;
304     m_BrandText = "BOINC";
305 
306     m_hDataManagementThread = NULL;
307     m_hGraphicsApplication = NULL;
308     m_bResetCoreState = true;
309     rpc = 0;
310     m_bConnected = false;
311 
312     // Get project-defined default values for GFXDefaultPeriod, GFXSciencePeriod, GFXChangePeriod
313     GetDefaultDisplayPeriods(periods);
314     m_bShow_default_ss_first = periods.Show_default_ss_first;
315 
316 
317     m_fGFXDefaultPeriod = periods.GFXDefaultPeriod;
318     m_fGFXSciencePeriod = periods.GFXSciencePeriod;
319     m_fGFXChangePeriod = periods.GFXChangePeriod;
320 }
321 
322 
Create()323 int CScreensaver::Create() {
324     OSStatus err;
325 
326     // Ugly workaround for a problem with the System Preferences app
327     // For an unknown reason, when this screensaver is run using the
328     // Test button in the System Prefs Screensaver control panel, the
329     // control panel calls our stopAnimation function as soon as the
330     // science application opens a GLUT window.  This problem does not
331     // occur when the screensaver is run normally (from the screensaver
332     // engine.)  So we just display a message and don't access the core
333     // client.
334     // With V6 graphics when using gfx_switcher, the graphics application
335     // fails to run and stderr shows the message:
336     // "The process has forked and you cannot use this CoreFoundation
337     // functionality safely. You MUST exec()"
338     pid_t SystemPrefsPID = getPidIfRunning("com.apple.systempreferences");
339     if (SystemPrefsPID == getpid()) {
340         saverState = SaverState_ControlPanelTestMode;
341     }
342 
343     // Calculate the estimated blank time by adding the starting
344     //  time and and the user-specified time which is in minutes
345     // On dual-GPU Macbok Pros, the CScreensaver class will be
346     // constructed and destructed each time we switch beteen
347     // battery and AC power, so we need to get the starting time
348     // only once.
349     if (!ScreenSaverStartTime) {
350         ScreenSaverStartTime = time(0);
351     }
352 
353     m_dwBlankScreen = gGoToBlank;
354     if (gGoToBlank && (gBlankingTime > 0))
355         m_dwBlankTime = ScreenSaverStartTime + (gBlankingTime * 60);
356     else
357         m_dwBlankTime = 0;
358 
359     // If there are multiple displays, initBOINCSaver may get called
360     // multiple times (once for each display), so we need to guard
361     // against launching multiple instances of the core client
362     if (saverState == SaverState_Idle) {
363         CFStringGetCString(gPathToBundleResources, m_gfx_Switcher_Path, sizeof(m_gfx_Switcher_Path), kCFStringEncodingMacRoman);
364         strlcat(m_gfx_Switcher_Path, "/gfx_switcher", sizeof(m_gfx_Switcher_Path));
365 
366         err = initBOINCApp();
367 
368         CGDisplayHideCursor(kCGNullDirectDisplay);
369 
370         if (saverState == SaverState_LaunchingCoreClient)
371         {
372             SetError(FALSE, 0);
373             m_bQuitDataManagementProc = false;
374             m_bDataManagementProcStopped = false;
375             if (rpc == NULL) {
376                 rpc = new RPC_CLIENT;
377             }
378         }
379     }
380 
381     if (!IsDualGPUMacbook) {
382         SetDiscreteGPU(false);
383         if (IsDualGPUMacbook && (GPUSelectConnect != IO_OBJECT_NULL)) {
384             IOServiceClose(GPUSelectConnect);
385             GPUSelectConnect = IO_OBJECT_NULL;
386         }
387     }
388 
389     return TEXTLOGOFREQUENCY;
390 }
391 
392 
initBOINCApp()393 OSStatus CScreensaver::initBOINCApp() {
394     char boincPath[2048];
395     pid_t myPid;
396     int status;
397     OSStatus err;
398     long brandId = 0;
399 
400     saverState = SaverState_CantLaunchCoreClient;
401 
402     brandId = GetBrandID();
403     switch(brandId) {
404     case 1:
405         m_BrandText = "GridRepublic Desktop";
406          break;
407     case 2:
408         m_BrandText = "Progress Thru Processors Desktop";
409          break;
410     case 3:
411         m_BrandText = "Charity Engine Desktop";
412          break;
413     default:
414         m_BrandText = "BOINC";
415         break;
416     }
417 
418     m_CoreClientPID = FindProcessPID("boinc", 0);
419     if (m_CoreClientPID) {
420         m_wasAlreadyRunning = true;
421         saverState = SaverState_LaunchingCoreClient;
422         retryCount = 0;
423         return noErr;
424     }
425 
426     m_wasAlreadyRunning = false;
427 
428     if (++retryCount > 3)   // Limit to 3 relaunches to prevent thrashing
429         return -1;
430 
431     // Find boinc client within BOINCManager.app
432     // First, try default path
433     strcpy(boincPath, "/Applications/");
434     if (brandId) {
435         strcat(boincPath, m_BrandText);
436     } else {
437         strcat(boincPath, "BOINCManager");
438     }
439     strcat(boincPath, ".app/Contents/Resources/boinc");
440 
441     // If not at default path, search for it by creator code and bundle identifier
442     if (!boinc_file_exists(boincPath)) {
443         err = GetPathToAppFromID('BNC!', CFSTR("edu.berkeley.boinc"),  boincPath, sizeof(boincPath));
444         if (err) {
445             saverState = SaverState_CantLaunchCoreClient;
446             return err;
447         } else {
448             strcat(boincPath, "/Contents/Resources/boinc");
449         }
450     }
451 
452     if ( (myPid = fork()) < 0)
453         return -1;
454     else if (myPid == 0)			// child
455     {
456       // We don't customize BOINC Data directory name for branding
457 #if 0   // Code for separate data in each user's private directory
458         char buf[256];
459         safe_strcpy(buf, getenv("HOME"));
460         safe_strcat(buf, "/Library/Application Support/BOINC Data");
461         status = chdir(buf);
462 #else   // All users share the same data
463         status = chdir("/Library/Application Support/BOINC Data");
464 #endif
465         if (status) {
466             perror("chdir");
467             fflush(NULL);
468             _exit(status);
469         }
470 
471         status = execl(boincPath, boincPath, "-redirectio", "-saver", (char *) 0);
472         fflush(NULL);
473         _exit(127);         // execl error (execl should never return)
474     } else {
475         m_CoreClientPID = myPid;		// make this available globally
476         saverState = SaverState_LaunchingCoreClient;
477     }
478 
479     return noErr;
480 }
481 
482 
483 // Returns new desired Animation Frequency (per second) or 0 for no change
getSSMessage(char ** theMessage,int * coveredFreq)484 int CScreensaver::getSSMessage(char **theMessage, int* coveredFreq) {
485     int newFrequency = TEXTLOGOFREQUENCY;
486     *coveredFreq = 0;
487     pid_t myPid;
488     CC_STATE ccstate;
489     OSStatus err;
490 
491     if (ScreenIsBlanked) {
492         setSSMessageText(0);   // No text message
493         *theMessage = m_MessageText;
494         return NOTEXTLOGOFREQUENCY;
495     }
496 
497     CheckDualGPUStatus();
498 
499     switch (saverState) {
500     case SaverState_RelaunchCoreClient:
501         err = initBOINCApp();
502         break;
503 
504     case  SaverState_LaunchingCoreClient:
505         if (m_wasAlreadyRunning) {
506             setSSMessageText(ConnectingCCMsg);
507         } else {
508             setSSMessageText(LaunchingCCMsg);
509         }
510 
511         myPid = FindProcessPID(NULL, m_CoreClientPID);
512         if (myPid) {
513             saverState = SaverState_CoreClientRunning;
514             if (!rpc->init(NULL)) {     // Initialize communications with Core Client
515                 m_bConnected = true;
516                 if (IsDualGPUMacbook) {
517                     ccstate.clear();
518                     ccstate.global_prefs.init_bools();
519                     int result = rpc->get_state(ccstate);
520                     if (!result) {
521                         OKToRunOnBatteries = ccstate.global_prefs.run_on_batteries;
522                     } else {
523                         OKToRunOnBatteries = false;
524                     }
525 
526                     if (OKToRunOnBatteries) {
527                         SetDiscreteGPU(true);
528                     }
529                 }
530             }
531 
532             // Set up a separate thread for communicating with Core Client
533             // and running screensaver graphics
534             CreateDataManagementThread();
535             // ToDo: Add a timeout after which we display error message
536         } else
537             // Take care of the possible race condition where the Core Client was in the
538             // process of shutting down just as ScreenSaver started, so initBOINCApp()
539             // found it already running but now it has shut down.
540             if (m_wasAlreadyRunning) { // If we launched it, then just wait for it to start
541                 saverState = SaverState_RelaunchCoreClient;
542             }
543          break;
544 
545     case SaverState_CoreClientRunning:
546         if (IsDualGPUMacbook && RunningOnBattery && !OKToRunOnBatteries) {
547             setSSMessageText(RunningOnBatteryMsg);
548             break;
549         }
550 
551         // RPC called in DataManagementProc()
552         setSSMessageText(ConnectingCCMsg);
553 
554         if (! m_bResetCoreState) {
555             saverState = SaverState_ConnectedToCoreClient;
556         }
557     break;
558 
559     case SaverState_ConnectedToCoreClient:
560         if (IsDualGPUMacbook && RunningOnBattery && !OKToRunOnBatteries) {
561             setSSMessageText(RunningOnBatteryMsg);
562             break;
563         }
564 
565         switch (m_hrError) {
566         case 0:
567             setSSMessageText(ConnectedCCMsg);
568             break;  // No status response yet from DataManagementProc
569         case SCRAPPERR_SCREENSAVERBLANKED:
570             setSSMessageText(0);   // No text message
571             ScreenIsBlanked = true;
572             if (IsDualGPUMacbook && (GPUSelectConnect != IO_OBJECT_NULL)) {
573                 IOServiceClose(GPUSelectConnect);
574                 GPUSelectConnect = IO_OBJECT_NULL;
575             }
576             break;
577 #if 0   // Not currently used
578         case SCRAPPERR_QUITSCREENSAVERREQUESTED:
579 //            setSSMessageText(BOINCExitedSaverMode);
580             // Wait 1 second to allow ScreenSaver engine to close us down
581             if (++gQuitCounter > (m_MessageText[0] ? TEXTLOGOFREQUENCY : NOTEXTLOGOFREQUENCY)) {
582                 closeBOINCSaver();
583                 KillScreenSaver(); // Stop the ScreenSaver Engine
584             }
585             break;
586 #endif
587         case SCRAPPERR_CANTLAUNCHDEFAULTGFXAPP:
588             setSSMessageText(CantLaunchDefaultGFXAppMsg);
589             break;
590         case SCRAPPERR_DEFAULTGFXAPPCANTCONNECT:
591             setSSMessageText(DefaultGFXAppCantRPCMsg);
592             break;
593          case SCRAPPERR_DEFAULTGFXAPPCRASHED:
594             setSSMessageText(DefaultGFXAppCrashedMsg);
595             break;
596             case SCRAPPERR_GFXAPPINCOMPATIBLE:
597                 // Message was set in incompatibleGfxApp()
598             break;
599         default:
600             // m_bErrorMode is TRUE if we should display moving logo (no graphics app is running)
601             // m_bErrorMode is FALSE if a graphics app was launched and has not exit
602             if (! m_bErrorMode) {
603                 // NOTE: My tests seem to confirm that the top window is always the first
604                 // window returned by NSWindowList under OS 10.5 and the second window
605                 // returned by NSWindowList under OS 10.3.9 and OS 10.4.  However, Apple's
606                 // documentation is unclear whether we can depend on this.  So I have
607                 // added some safety by doing two things:
608                 // [1] Only use the NSWindowList test when we have started project or default
609                 //      graphics.
610                 // [2] Assume that our window is covered 45 seconds after starting project
611                 //     graphics even if the NSWindowList test did not indicate that is so.
612                 //
613                 // The -animateOneFrame method in Mac_SaverModuleView.m does the NSWindowList test
614                 // only if we return a non-zero value for coveredFreq.
615                 //
616                 // Tell the calling routine to set the frame rate to NOTEXTLOGOFREQUENCY if
617                 // NSWindowList indicates that science app graphics window has covered our window.
618                 *coveredFreq = NOTEXTLOGOFREQUENCY;
619 
620                 if (m_iGraphicsStartingMsgCounter > 0) {
621                     // Show ScreenSaverAppStartingMsg for GFX_STARTING_MSG_DURATION seconds or until
622                     // NSWindowList indicates that science app graphics window has covered our window
623                     setSSMessageText(ScreenSaverAppStartingMsg);
624                     m_iGraphicsStartingMsgCounter--;
625                 } else {
626                     // Don't waste CPU cycles when the science app is drawing over our window
627                     setSSMessageText(0);   // No text message
628                 }
629             }           // End if (! m_bErrorMode)
630             break;      // End default case of switch (m_hrError)
631 
632         }       // end switch (m_hrError)
633         break;  // End case SaverState_ConnectedToCoreClient of switch (saverState)
634 
635     case SaverState_ControlPanelTestMode:
636         setSSMessageText(BOINCTestModeMsg);
637         break;
638 
639     case SaverState_UnrecoverableError:
640         setSSMessageText(BOINCUnrecoverableErrorMsg);
641         break;
642 
643     case SaverState_CantLaunchCoreClient:
644         if (IsDualGPUMacbook && RunningOnBattery && !OKToRunOnBatteries) {
645             setSSMessageText(RunningOnBatteryMsg);
646             break;
647         }
648 
649         setSSMessageText(CantLaunchCCMsg);
650 
651         // Set up a separate thread for running screensaver graphics
652         // even if we can't communicate with core client
653         CreateDataManagementThread();
654         break;
655 
656     case SaverState_Idle:
657         break;      // Should never get here; fixes compiler warning
658     }           // end switch (saverState)
659 
660     if (IsDualGPUMacbook && RunningOnBattery && !OKToRunOnBatteries) {
661         if ((m_dwBlankScreen) && (time(0) > m_dwBlankTime) && (m_dwBlankTime > 0)) {
662             setSSMessageText(0);   // No text message
663             ScreenIsBlanked = true;
664         }
665     }
666 
667     if (m_MessageText[0]) {
668         newFrequency = TEXTLOGOFREQUENCY;
669     } else {
670         newFrequency = NOTEXTLOGOFREQUENCY;
671     }
672 
673     *theMessage = m_MessageText;
674     return newFrequency;
675 }
676 
677 
windowIsCovered()678 void CScreensaver::windowIsCovered() {
679     m_iGraphicsStartingMsgCounter = 0;
680 }
681 
682 
drawPreview(CGContextRef myContext)683 void CScreensaver::drawPreview(CGContextRef myContext) {
684     // For possible future use
685 }
686 
687 
ShutdownSaver()688 void CScreensaver::ShutdownSaver() {
689     DestroyDataManagementThread();
690 
691     if (rpc) {
692 #if 0       // OS X calls closeBOINCSaver() when energy saver puts display
693             // to sleep, but we want to keep crunching.  So don't kill it.
694             // Code in core client now quits on user activity if screen
695             // saver launched it (2/28/07).
696             // Also, under sandbox security, screensaver doesn't have access
697             // to rpc password in gui_rpc_auth.cfg file, so core client won't
698             // accept rpc->quit from screensaver.
699         if (m_CoreClientPID && (!m_wasAlreadyRunning)) {
700             rpc->quit();    // Kill core client if we launched it
701         }
702 #endif
703         delete rpc;
704         rpc = NULL;
705     }
706 
707     setSSMessageText(0);
708 
709     m_CoreClientPID = 0;
710 //    gQuitCounter = 0;
711     m_wasAlreadyRunning = false;
712     m_bQuitDataManagementProc = false;
713     saverState = SaverState_Idle;
714     retryCount = 0;
715 }
716 
717 
718 // This function forwards to DataManagementProc, which has access to the
719 //       "this" pointer.
720 //
DataManagementProcStub(void * param)721 void * CScreensaver::DataManagementProcStub(void* param) {
722     return gspScreensaver->DataManagementProc();
723 }
724 
725 
HandleRPCError()726 void CScreensaver::HandleRPCError() {
727     static time_t last_RPC_retry = 0;
728     time_t now = time(0);
729 
730     // Attempt to restart BOINC Client if needed, reinitialize the RPC client and state
731     rpc->close();
732     m_bConnected = false;
733 
734     if (saverState == SaverState_CantLaunchCoreClient) {
735         if ((now - last_RPC_retry) < RPC_RETRY_INTERVAL) {
736             return;
737         }
738         last_RPC_retry = now;
739     } else {
740         // There is a possible race condition where the Core Client was in the
741         // process of shutting down just as ScreenSaver started, so initBOINCApp()
742         // found it already running but now it has shut down.  This code takes
743         // care of that and other situations where the Core Client quits unexpectedy.
744         // Code in initBOINC_App() limits # launch retries to 3 to prevent thrashing.
745         if (FindProcessPID("boinc", 0) == 0) {
746             saverState = SaverState_RelaunchCoreClient;
747             m_bResetCoreState = true;
748          }
749     }
750 
751     // If Core Client is hung, it might cause RPCs to hang, preventing us from
752     // shutting down the Data Management Thread, so don't reinitialize the RPC
753     // client if we have told the Data Management Thread to exit.
754     if (m_bQuitDataManagementProc) {
755         return;
756     }
757 
758     // Otherwise just reinitialize the RPC client and state and keep trying
759     if (!rpc->init(NULL)) {
760         m_bConnected = true;
761     }
762     // Error message after timeout?
763 }
764 
CreateDataManagementThread()765 bool CScreensaver::CreateDataManagementThread() {
766     int retval;
767 
768     // On dual-GPU Macbook Pros, our OpenGL scrensaver
769     // applications trigger a switch to the power-hungry
770     // discrete GPU. To extend battery life, don't run
771     // them when on battery power.
772     if (IsDualGPUMacbook && RunningOnBattery && !OKToRunOnBatteries) return true;
773 
774     if (m_hDataManagementThread == NULL) {
775         retval = pthread_create(&m_hDataManagementThread, NULL, DataManagementProcStub, 0);
776         if (retval) {
777             saverState = SaverState_UnrecoverableError;
778             return false;
779         }
780         pthread_detach(m_hDataManagementThread);
781     }
782     return true;
783 }
784 
785 
DestroyDataManagementThread()786 bool CScreensaver::DestroyDataManagementThread() {
787     m_bQuitDataManagementProc = true;  // Tell DataManagementProc thread to exit
788     if (!m_hDataManagementThread) return true;
789 
790     for (int i=0; i<10; i++) {  // Wait up to 1 second for DataManagementProc thread to exit
791         if (m_bDataManagementProcStopped) return true;
792         boinc_sleep(0.1);
793     }
794 
795     if (rpc) {
796         rpc->close();    // In case DataManagementProc is hung waiting for RPC
797     }
798     m_hDataManagementThread = NULL; // Don't delay more if this routine is called again.
799     if (m_hGraphicsApplication) {
800         terminate_screensaver(m_hGraphicsApplication, NULL);
801         m_hGraphicsApplication = 0;
802     }
803 
804     return true;
805 }
806 
807 
808 //
SetError(bool bErrorMode,unsigned int hrError)809 bool CScreensaver::SetError(bool bErrorMode, unsigned int hrError) {
810     // bErrorMode is TRUE if we should display moving logo (no graphics app is running)
811     // bErrorMode is FALSE if a graphics app was launched and has not exit
812     m_bErrorMode = bErrorMode;
813     m_hrError = hrError;
814 
815     if (bErrorMode) {
816         // Reset our timer for showing ScreenSaverAppStartingMsg to
817         // GFX_STARTING_MSG_DURATION seconds
818         m_iGraphicsStartingMsgCounter = GFX_STARTING_MSG_DURATION * TEXTLOGOFREQUENCY;
819     }
820     return true;
821 }
822 
setSSMessageText(const char * msg)823 void CScreensaver::setSSMessageText(const char * msg) {
824     if (msg == 0)
825         m_MessageText[0] = 0;
826 
827     if (m_CurrentBannerMessage != msg)
828         updateSSMessageText((char *)msg);
829 }
830 
831 
updateSSMessageText(char * msg)832 void CScreensaver::updateSSMessageText(char *msg) {
833     char *p, *s;
834 
835     m_CurrentBannerMessage = msg;
836 
837    if (msg) {
838         s = msg;
839         m_MessageText[0] = '\0';
840         do {
841             p = strstr(s, "BOINC");
842             if (p == NULL) {
843                 strcat(m_MessageText, s);
844             } else {
845                 strncat(m_MessageText, s, p - s);
846                 strcat(m_MessageText, m_BrandText);
847                 s = p + 5;  // s = p + strlen("BOINC");
848             }
849         } while (p);
850 
851     }
852 }
853 
854 
GetBrandID()855 int CScreensaver::GetBrandID()
856 {
857     char buf[1024];
858     long iBrandId;
859     OSErr err;
860 
861     iBrandId = 0;   // Default value
862 
863     // The installer put a copy of Branding file in the BOINC Data Directory
864     FILE *f = fopen("/Library/Application Support/BOINC Data/Branding", "r");
865     if (f == NULL) {
866        // If we couldn't find our Branding file in the BOINC Data Directory,
867        // look in our application bundle
868         err = GetPathToAppFromID('BNC!', CFSTR("edu.berkeley.boinc"),  buf, sizeof(buf));
869         if (err == noErr) {
870             strcat(buf, "/Contents/Resources/Branding");
871             f = fopen(buf, "r");
872         }
873     }
874     if (f) {
875         fscanf(f, "BrandId=%ld\n", &iBrandId);
876         fclose(f);
877     }
878 
879     return iBrandId;
880 }
881 
882 
PersistentFGets(char * buf,size_t buflen,FILE * f)883 char * CScreensaver::PersistentFGets(char *buf, size_t buflen, FILE *f) {
884     char *p = buf;
885     size_t len = buflen;
886     size_t datalen = 0;
887 
888     *buf = '\0';
889     while (datalen < (buflen - 1)) {
890         fgets(p, len, f);
891         if (feof(f)) break;
892         if (ferror(f) && (errno != EINTR)) break;
893         if (strchr(buf, '\n')) break;
894         datalen = strlen(buf);
895         p = buf + datalen;
896         len -= datalen;
897     }
898     return (buf[0] ? buf : NULL);
899 }
900 
901 
FindProcessPID(char * name,pid_t thePID)902 pid_t CScreensaver::FindProcessPID(char* name, pid_t thePID)
903 {
904     FILE *f;
905     char buf[1024];
906     size_t n = 0;
907     pid_t aPID;
908 
909     if (name != NULL)     // Search ny name
910         n = strlen(name);
911 
912     f = popen("ps -a -x -c -o command,pid", "r");
913     if (f == NULL)
914         return 0;
915 
916     while (PersistentFGets(buf, sizeof(buf), f))
917     {
918         if (name != NULL) {     // Search ny name
919             if (strncmp(buf, name, n) == 0)
920             {
921                 aPID = atol(buf+16);
922                 pclose(f);
923                 return aPID;
924             }
925         } else {      // Search by PID
926             aPID = atol(buf+16);
927             if (aPID == thePID) {
928                 pclose(f);
929                 return aPID;
930             }
931         }
932     }
933     pclose(f);
934     return 0;
935 }
936 
937 
938 // Send a Quit AppleEvent to the process which called this module
939 // (i.e., tell the ScreenSaver engine to quit)
KillScreenSaver()940 int CScreensaver::KillScreenSaver() {
941     pid_t                       thisPID;
942     int                         retval;
943 
944     thisPID = getpid();
945     retval = kill(thisPID, SIGABRT);   // SIGINT
946     return retval;
947 }
948 
949 
Host_is_running_on_batteries()950 bool CScreensaver::Host_is_running_on_batteries() {
951     CFDictionaryRef pSource = NULL;
952     CFStringRef psState;
953     int i;
954     bool retval = false;
955 
956     CFTypeRef blob = IOPSCopyPowerSourcesInfo();
957     CFArrayRef list = IOPSCopyPowerSourcesList(blob);
958 
959     for (i=0; i<CFArrayGetCount(list); i++) {
960         pSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(list, i));
961         if(!pSource) break;
962         psState = (CFStringRef)CFDictionaryGetValue(pSource, CFSTR(kIOPSPowerSourceStateKey));
963         if(!CFStringCompare(psState,CFSTR(kIOPSBatteryPowerValue),0))
964         retval = true;
965     }
966 
967     CFRelease(blob);
968     CFRelease(list);
969 
970     return retval;
971 }
972 
973 
974 // On Dual-GPU Macbook Pros, Apple's Screensaver Engine
975 // will detect any GPU change and call stopAnimation,
976 // then initWithFrame and startAnimation.
977 //
978 // When we launch boincscr or a project screensaver
979 // app which uses OpenGL, that will trigger a switch to
980 // the discrete GPU, causing the Screensaver Engine to
981 // call stopAnimation, which will then shut down boincscr
982 // or the project screensaver.  This will then release
983 // the discrete GPU, triggering a switch to the intrinsic
984 // GPU, which will again cause a call to stopAnimation,
985 // and so forth in an infinite loop.
986 //
987 // The solution is to request the discrete GPU ourselves
988 // before launching boincscr or a project screensaver so
989 // the OpenGL app does not cause a GPU switch.
990 //
991 // We initially call this with setDiscrete = false to
992 // test whether we are running on a Dual-GPU Macbook Pro.
993 //
SetDiscreteGPU(bool setDiscrete)994 void CScreensaver::SetDiscreteGPU(bool setDiscrete) {
995     kern_return_t kernResult = 0;
996     io_service_t service = IO_OBJECT_NULL;
997 
998     if (GPUSelectConnect == IO_OBJECT_NULL) {
999         service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleGraphicsControl"));
1000         if (service != IO_OBJECT_NULL) {
1001             kernResult = IOServiceOpen(service, mach_task_self(), setDiscrete ? 1 : 0, &GPUSelectConnect);
1002             if (kernResult == KERN_SUCCESS) {
1003                 IsDualGPUMacbook = true;
1004             }
1005         }
1006     }
1007 }
1008 
1009 
1010 // On Dual-GPU Macbook Pros only:
1011 // switch to intrinsic GPU if running on battery
1012 // switch to discrete GPU if running on AC power
1013 //
1014 // Apple's Screensaver Engine will detect the GPU change and
1015 // call stopAnimation, then initWithFrame and startAnimation.
CheckDualGPUStatus()1016 void CScreensaver::CheckDualGPUStatus() {
1017     static double lastBatteryCheckTime = 0;
1018     double currentTime;
1019     bool nowOnBattery;
1020 
1021     if (!IsDualGPUMacbook) return;
1022     if (OKToRunOnBatteries) return;
1023 
1024     currentTime = dtime();
1025     if (currentTime < lastBatteryCheckTime + BATTERY_CHECK_INTERVAL) return;
1026     lastBatteryCheckTime = currentTime;
1027 
1028     nowOnBattery = Host_is_running_on_batteries();
1029     if (nowOnBattery == RunningOnBattery) return;
1030 
1031     RunningOnBattery = nowOnBattery;
1032     if (nowOnBattery) {
1033         if (GPUSelectConnect != IO_OBJECT_NULL) {
1034             IOServiceClose(GPUSelectConnect);
1035             GPUSelectConnect = IO_OBJECT_NULL;
1036         }
1037         // If an OpenGL screensaver app is running, we must shut it down
1038         // to release its claim on the discrete GPU to save battery power.
1039         DestroyDataManagementThread();
1040     } else {
1041         SetDiscreteGPU(true);
1042     }
1043 }
1044 
1045 
print_to_log_file(const char * format,...)1046 void print_to_log_file(const char *format, ...) {
1047 #if CREATE_LOG
1048     va_list args;
1049     char buf[256];
1050     time_t t;
1051 #if USE_SPECIAL_LOG_FILE
1052     safe_strcpy(buf, getenv("HOME"));
1053     safe_strcat(buf, "/Documents/test_log.txt");
1054     FILE *f;
1055     f = fopen(buf, "a");
1056     if (!f) return;
1057 
1058 //  freopen(buf, "a", stdout);
1059 //  freopen(buf, "a", stderr);
1060 #else
1061     #define f stderr
1062 #endif
1063     time(&t);
1064     safe_strcpy(buf, asctime(localtime(&t)));
1065     strip_cr(buf);
1066 
1067     fputs(buf, f);
1068     fputs("   ", f);
1069 
1070     va_start(args, format);
1071     vfprintf(f, format, args);
1072     va_end(args);
1073 
1074     fputs("\n", f);
1075 #if USE_SPECIAL_LOG_FILE
1076     fflush(f);
1077     fclose(f);
1078 #endif
1079 #endif
1080 }
1081 
1082 #if CREATE_LOG
strip_cr(char * buf)1083 void strip_cr(char *buf)
1084 {
1085     char *theCR;
1086 
1087     theCR = strrchr(buf, '\n');
1088     if (theCR)
1089         *theCR = '\0';
1090     theCR = strrchr(buf, '\r');
1091     if (theCR)
1092         *theCR = '\0';
1093 }
1094 #endif	// CREATE_LOG
1095 
PrintBacktrace(void)1096 void PrintBacktrace(void) {
1097 // Dummy routine to satisfy linker
1098 }
1099 
1100