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