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_ModuleView.m 20// BOINC_Saver_Module 21// 22 23#define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED 1 24 25#import "Mac_Saver_ModuleView.h" 26#include <Carbon/Carbon.h> 27#include <AppKit/AppKit.h> 28#include <IOKit/hidsystem/IOHIDLib.h> 29#include <IOKit/hidsystem/IOHIDParameter.h> 30#include <IOKit/hidsystem/event_status_driver.h> 31#import <OpenGL/gl.h> 32#import <GLKit/GLKit.h> 33#include <servers/bootstrap.h> 34//#import <IOSurface/IOSurface.h> 35//#import <OpenGL/gl3.h> 36//#import <OpenGL/CGLIOSurface.h> 37 38#include "mac_util.h" 39#import "MultiGPUMig.h" 40#import "MultiGPUMigServer.h" 41 42#ifndef NSInteger 43#if __LP64__ || NS_BUILD_32_LIKE_64 44typedef long NSInteger; 45#else 46typedef int NSInteger; 47#endif 48#endif 49 50#ifndef CGFLOAT_DEFINED 51typedef float CGFloat; 52#endif 53 54// NSCompositeSourceOver is deprecated in OS 10.12 and is replaced by 55// NSCompositingOperationSourceOver, which is not defined before OS 10.12 56#ifndef NSCompositingOperationSourceOver 57#define NSCompositingOperationSourceOver NSCompositeSourceOver 58#endif 59 60// NSCompositeCopy is deprecated in OS 10.12 and is replaced by 61// NSCompositingOperationCopy, which is not defined before OS 10.12 62#ifndef NSCompositingOperationCopy 63#define NSCompositingOperationCopy NSCompositeCopy 64#endif 65 66// NSCriticalAlertStyle is deprecated in OS 10.12 and is replaced by 67// NSAlertStyleCritical, which is not defined before OS 10.12 68#ifndef NSAlertStyleCritical 69#define NSAlertStyleCritical NSCriticalAlertStyle 70#endif 71 72static double gSS_StartTime = 0.0; 73mach_port_t gEventHandle = 0; 74 75int gGoToBlank; // True if we are to blank the screen 76int gBlankingTime; // Delay in minutes before blanking the screen 77NSString *gPathToBundleResources = NULL; 78NSString *mBundleID = NULL; // our bundle ID 79NSImage *gBOINC_Logo = NULL; 80NSImage *gPreview_Image = NULL; 81 82int gTopWindowListIndex = -1; 83NSInteger myWindowNumber; 84 85NSRect gMovingRect; 86float gImageXIndent; 87float gTextBoxHeight; 88CGFloat gActualTextBoxHeight; 89NSPoint gCurrentPosition; 90NSPoint gCurrentDelta; 91 92CGContextRef myContext; 93bool isErased; 94 95static SharedGraphicsController *mySharedGraphicsController; 96static bool runningSharedGraphics; 97static bool useCGWindowList; 98static pid_t childPid; 99static int gfxAppWindowNum; 100static NSView *imageView; 101static char gfxAppPath[MAXPATHLEN]; 102static int taskSlot; 103static NSRunningApplication *childApp; 104static double gfxAppStartTime; 105static bool UseSharedOffscreenBuffer(void); 106 107 108#define TEXTBOXMINWIDTH 400.0 109#define MINTEXTBOXHEIGHT 40.0 110#define MAXTEXTBOXHEIGHT 300.0 111#define TEXTBOXTOPBORDER 15 112#define SAFETYBORDER 20.0 113#define MINDELTA 8 114#define MAXDELTA 16 115 116// On OS 10.13+, assume graphics app is not compatible if no MachO connection after 5 seconds 117#define MAXWAITFORCONNECTION 5.0 118 119int signof(float x) { 120 return (x > 0.0 ? 1 : -1); 121} 122 123void launchedGfxApp(char * appPath, pid_t thePID, int slot) { 124 strlcpy(gfxAppPath, appPath, sizeof(gfxAppPath)); 125 childPid = thePID; 126 taskSlot = slot; 127 gfxAppStartTime = getDTime(); 128 if (thePID == 0) { 129 useCGWindowList = false; 130 gfxAppStartTime = 0.0; 131 if (imageView) { 132 [imageView removeFromSuperview]; // Releases imageView 133 imageView = nil; 134 } 135 } 136} 137 138@implementation BOINC_Saver_ModuleView 139 140- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview { 141 self = [ super initWithFrame:frame isPreview:isPreview ]; 142 return self; 143} 144 145// If there are multiple displays, this may get called 146// multiple times (once for each display), so we need to guard 147// against any problems that may cause. 148- (void)startAnimation { 149 NSBundle * myBundle; 150 int newFrequency; 151 int period; 152 153 gEventHandle = NXOpenEventStatus(); 154 155 initBOINCSaver(); 156 157 if (gBOINC_Logo == NULL) { 158 if (self) { 159 myBundle = [ NSBundle bundleForClass:[self class]]; 160 // grab the screensaver defaults 161 if (mBundleID == NULL) { 162 mBundleID = [ myBundle bundleIdentifier ]; 163 } 164 165 // Path to our copy of switcher utility application in this screensaver bundle 166 if (gPathToBundleResources == NULL) { 167 gPathToBundleResources = [ myBundle resourcePath ]; 168 } 169 170 ScreenSaverDefaults *defaults = [ ScreenSaverDefaults defaultsForModuleWithName:mBundleID ]; 171 172 // try to load the version key, used to see if we have any saved settings 173 mVersion = [defaults floatForKey:@"version"]; 174 if (!mVersion) { 175 // no previous settings so define our defaults 176 gGoToBlank = NO; 177 gBlankingTime = 1; 178 179 // write out the defaults 180 [ defaults setInteger:gGoToBlank forKey:@"GoToBlank" ]; 181 [ defaults setInteger:gBlankingTime forKey:@"BlankingTime" ]; 182 } 183 184 if (mVersion < 2) { 185 mVersion = 2; 186 187 [ defaults setInteger:mVersion forKey:@"version" ]; 188 period = getGFXDefaultPeriod() / 60; 189 [ defaults setInteger:period forKey:@"DefaultPeriod" ]; 190 period = getGFXSciencePeriod() / 60; 191 [ defaults setInteger:period forKey:@"SciencePeriod" ]; 192 period = getGGFXChangePeriod() / 60; 193 [ defaults setInteger:period forKey:@"ChangePeriod" ]; 194 195 // synchronize 196 [defaults synchronize]; 197 } 198 199 // get defaults... 200 gGoToBlank = [ defaults integerForKey:@"GoToBlank" ]; 201 gBlankingTime = [ defaults integerForKey:@"BlankingTime" ]; 202 period = [ defaults integerForKey:@"DefaultPeriod" ]; 203 setGFXDefaultPeriod((double)(period * 60)); 204 period = [ defaults integerForKey:@"SciencePeriod" ]; 205 setGFXSciencePeriod((double)(period * 60)); 206 period = [ defaults integerForKey:@"ChangePeriod" ]; 207 setGGFXChangePeriod((double)(period * 60)); 208 209 [ self setAutoresizesSubviews:YES ]; // make sure the subview resizes. 210 211 NSString *fileName = [[ NSBundle bundleForClass:[ self class ]] pathForImageResource:@"boinc_ss_logo" ]; 212 if (! fileName) { 213 // What should we do in this case? 214 return; 215 } 216 217 gBOINC_Logo = [[ NSImage alloc ] initWithContentsOfFile:fileName ]; 218 gMovingRect.origin.x = 0.0; 219 gMovingRect.origin.y = 0.0; 220 gMovingRect.size = [gBOINC_Logo size]; 221 222 if (gMovingRect.size.width < TEXTBOXMINWIDTH) { 223 gImageXIndent = (TEXTBOXMINWIDTH - gMovingRect.size.width) / 2; 224 gMovingRect.size.width = TEXTBOXMINWIDTH; 225 } else { 226 gImageXIndent = 0.0; 227 } 228 gTextBoxHeight = MINTEXTBOXHEIGHT; 229 gMovingRect.size.height += gTextBoxHeight; 230 gCurrentPosition.x = SAFETYBORDER + 1; 231 gCurrentPosition.y = SAFETYBORDER + 1 + gTextBoxHeight; 232 gCurrentDelta.x = 1.0; 233 gCurrentDelta.y = 1.0; 234 235 gActualTextBoxHeight = MINTEXTBOXHEIGHT; 236 237 [ self setAnimationTimeInterval:1/8.0 ]; 238 } 239 } 240 241 // Path to our copy of switcher utility application in this screensaver bundle 242 if (gPathToBundleResources == NULL) { 243 gPathToBundleResources = [ myBundle resourcePath ]; 244 } 245 246 [ super startAnimation ]; 247 248 if ( [ self isPreview ] ) { 249 [ self setAnimationTimeInterval:1.0/8.0 ]; 250 return; 251 } 252 253 newFrequency = startBOINCSaver(); 254 if (newFrequency) 255 [ self setAnimationTimeInterval:1.0/newFrequency ]; 256 257 gSS_StartTime = getDTime(); 258} 259 260// If there are multiple displays, this may get called 261// multiple times (once for each display), so we need to guard 262// against any problems that may cause. 263- (void)stopAnimation { 264 [ super stopAnimation ]; 265 266 if ( ! [ self isPreview ] ) { 267 closeBOINCSaver(); 268 } 269 270 gTopWindowListIndex = -1; 271 272// if (gBOINC_Logo) { 273// [ gBOINC_Logo release ]; 274// } 275 gBOINC_Logo = NULL; 276 277 // gPathToBundleResources has been released by autorelease 278 gPathToBundleResources = NULL; 279} 280 281// If there are multiple displays, this may get called 282// multiple times (once for each display), so we need to guard 283// against any problems that may cause. 284- (void)drawRect:(NSRect)rect { 285 [ super drawRect:rect ]; 286 287// optionally draw here 288} 289 290// If there are multiple displays, this may get called 291// multiple times (once for each display), so we need to guard 292// against any problems that may cause. 293- (void)animateOneFrame { 294 int newFrequency = 0; 295 int coveredFreq = 0; 296 NSRect theFrame = [ self frame ]; 297 NSUInteger n; 298 NSRect currentDrawingRect, eraseRect; 299 NSPoint imagePosition; 300 char *msg; 301 CFStringRef cf_msg; 302 double timeToBlock, frameStartTime = getDTime(); 303 double idleTime = 0; 304 HIThemeTextInfo textInfo; 305 306 if ([ self isPreview ]) { 307#if 1 // Currently drawRect just draws our logo in the preview window 308 if (gPreview_Image == NULL) { 309 NSString *fileName = [[ NSBundle bundleForClass:[ self class ]] pathForImageResource:@"boinc" ]; 310 if (fileName) { 311 gPreview_Image = [[ NSImage alloc ] initWithContentsOfFile:fileName ]; 312 } 313 } 314 if (gPreview_Image) { 315 [ gPreview_Image setSize:theFrame.size ]; 316 [ gPreview_Image drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 ]; 317 } 318 [ self setAnimationTimeInterval:1/1.0 ]; 319#else // Code for possible future use if we want to draw more in preview 320 myContext = [[NSGraphicsContext currentContext] graphicsPort]; 321 drawPreview(myContext); 322 [ self setAnimationTimeInterval:1/30.0 ]; 323#endif 324 return; 325 } else { 326 NSWindow *myWindow = [ self window ]; 327 NSRect windowFrame = [ myWindow frame ]; 328 if ( (windowFrame.origin.x == 0) && (windowFrame.origin.y == 0) ) { // Main screen 329 // On OS 10.13 or later, use MachO comunication and IOSurfaceBuffer to 330 // display the graphics output of our child graphics apps in our window. 331 if (UseSharedOffscreenBuffer() && !mySharedGraphicsController) { 332 mySharedGraphicsController = [[SharedGraphicsController alloc] init:self] ; 333 } 334 } 335 } 336 337 // For unkown reasons, OS 10.7 Lion screensaver and later delay several seconds 338 // after user activity before calling stopAnimation, so we check user activity here 339 if ((compareOSVersionTo(10, 7) >= 0) && ((getDTime() - gSS_StartTime) > 2.0)) { 340 idleTime = CGEventSourceSecondsSinceLastEventType 341 (kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType); 342 if (idleTime < 1.5) { 343 [ NSApp terminate:nil ]; 344 } 345 } 346 347 myContext = [[NSGraphicsContext currentContext] graphicsPort]; 348// [myContext retain]; 349 350 NSWindow *myWindow = [ self window ]; 351 NSRect windowFrame = [ myWindow frame ]; 352 if ( (windowFrame.origin.x != 0) || (windowFrame.origin.y != 0) ) { 353 // Hide window on second display to aid in debugging 354#ifdef _DEBUG 355 // This technique no longer works on newer versions of OS X 356 [ myWindow setLevel:kCGMinimumWindowLevel ]; 357 NSInteger alpha = 0; 358 [ myWindow setAlphaValue:alpha ]; // For OS 10.6 359 [ myWindow orderOut:self]; 360#endif 361 return; // We draw only to main screen 362 } 363 364 // On OS 10.13 or later, use MachO comunication and IOSurfaceBuffer to 365 // display the graphics output of our child graphics apps in our window. 366 // Graphics apps linked with our current libraries have support for 367 // MachO comunication and IOSurfaceBuffer. 368 // 369 // For graphics apps linked with older libraries, use the API 370 // CGWindowListCreateImage to copy the graphic app window's image, 371 // but this is far slower because it does not take advantage of GPU 372 // acceleration, so it uses more CPU and animation may not appear smooth. 373 // 374 if (runningSharedGraphics || useCGWindowList ) { 375 // Since ScreensaverEngine.app is running in the foreground, our child 376 // graphics app may not get enough CPU cycles for good animation. 377 // Calling [ NSApp activateIgnoringOtherApps:YES ] frequently from the 378 // child doesn't help. But activating our child frequently from the 379 // front process (this screensaver plugin) does appear to guarantee 380 // good animation. 381 // 382 // An alternate approach that also works is to have the child process 383 // tell the kernel it has real time constraints by calling 384 // thread_policy_set() with thread_policy_flavor_t set to 385 // THREAD_TIME_CONSTRAINT_POLICY as described in 386 // <https://developer.apple.com/library/content/technotes/tn2169>. 387 // 388 // But different graphics apps may have different time requirements, 389 // so it is difficult to know the best values to set in the 390 // thread_time_constraint_policy_data_t struct. If the graphics app asks 391 // for too much time, the worker apps will get less time, and if it asks 392 // for too little time the animation won't be smooth. 393 // 394 // So frequently activating the child app here seems to be best. 395 // 396 if (childApp) { 397 if (![ childApp activateWithOptions:NSApplicationActivateIgnoringOtherApps ]) { 398 launchedGfxApp("", 0, -1); // Graphics app is no longer running 399 } 400 if (useCGWindowList) { 401 CGImageRef windowImage = CGWindowListCreateImage(CGRectNull, 402 kCGWindowListOptionIncludingWindow, 403 gfxAppWindowNum, 404 kCGWindowImageBoundsIgnoreFraming); 405 if (windowImage) { 406 // Create a bitmap rep from the image... 407 NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:windowImage]; 408 // Create an NSImage and add the bitmap rep to it... 409 NSImage *image = [[NSImage alloc] init]; 410 [image addRepresentation:bitmapRep]; 411 [image drawInRect:[self frame]]; 412 CGImageRelease(windowImage); 413 } 414 } 415 } 416 isErased = false; 417 return; 418 } 419 420 NSRect viewBounds = [self bounds]; 421 422 newFrequency = getSSMessage(&msg, &coveredFreq); 423 424 if (UseSharedOffscreenBuffer()) { 425 // If runningSharedGraphics is still false after MAXWAITFORCONNECTION, 426 // assume graphics app is not compatible with OS 10.13+ and kill it. 427 if (gfxAppStartTime) { 428 if ((getDTime() - gfxAppStartTime)> MAXWAITFORCONNECTION) { 429 gfxAppStartTime = 0.0; 430 if ([self setUpToUseCGWindowList] == false) { 431 incompatibleGfxApp(gfxAppPath, childPid, taskSlot); 432 } 433 } 434 } 435 // As of OS 10.13, app windows can no longer appear on top of screensaver 436 // window, but we still use this method on older versions of OS X for 437 // compatibility with older project graphics apps (those which have not 438 // yet been relinked with the updated libboinc_graphics2.a.) 439 } else { 440 // NOTE: My tests seem to confirm that the top window is always the first 441 // window returned by [NSWindow windowNumbersWithOptions:] However, Apple's 442 // documentation is unclear whether we can depend on this. So I have 443 // added some safety by doing two things: 444 // [1] Only use the windowNumbersWithOptions test when we have started 445 // project graphics. 446 // [2] Assume that our window is covered 45 seconds after starting project 447 // graphics even if the windowNumbersWithOptions test did not indicate 448 // that is so. 449 // 450 // getSSMessage() returns a non-zero value for coveredFreq only if we have started 451 // project graphics. 452 // 453 // If we should use a different frequency when our window is covered by another 454 // window, then check whether there is a window at a higher z-level than ours. 455 456 // Assuming our window(s) are initially the top window(s), determine our position 457 // in the window list when no graphics applications have covered us. 458 if (gTopWindowListIndex < 0) { 459 NSArray *theWindowList = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllApplications]; 460 myWindowNumber = [ myWindow windowNumber ]; 461 gTopWindowListIndex = [theWindowList indexOfObjectIdenticalTo:[NSNumber numberWithInt:myWindowNumber]]; 462 } 463 464 if (coveredFreq) { 465 if ( (msg != NULL) && (msg[0] != '\0') ) { 466 NSArray *theWindowList = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllApplications]; 467 n = [theWindowList count]; 468 if (gTopWindowListIndex < n) { 469 if ([(NSNumber*)[theWindowList objectAtIndex:gTopWindowListIndex] integerValue] != myWindowNumber) { 470 // Project graphics application has a window open above ours 471 // Don't waste CPU cycles since our window is obscured by application graphics 472 newFrequency = coveredFreq; 473 msg = NULL; 474 windowIsCovered(); 475 } 476 } 477 } else { 478 newFrequency = coveredFreq; 479 } 480 } 481 } 482 483 // Draw our moving BOINC logo and screensaver status text 484 485 // Clear the previous drawing area 486 currentDrawingRect = gMovingRect; 487 currentDrawingRect.origin.x = (float) ((int)gCurrentPosition.x); 488 currentDrawingRect.origin.y += (float) ((int)gCurrentPosition.y - gTextBoxHeight); 489 490 if ( (msg != NULL) && (msg[0] != '\0') ) { 491 492 // Set direction of motion to "bounce" off edges of screen 493 if (currentDrawingRect.origin.x <= SAFETYBORDER) { 494 gCurrentDelta.x = (float)SSRandomIntBetween(MINDELTA, MAXDELTA) / 16.; 495 gCurrentDelta.y = (float)(SSRandomIntBetween(MINDELTA, MAXDELTA) * signof(gCurrentDelta.y)) / 16.; 496 } 497 if ( (currentDrawingRect.origin.x + currentDrawingRect.size.width) >= 498 (viewBounds.origin.x + viewBounds.size.width - SAFETYBORDER) ) { 499 gCurrentDelta.x = -(float)SSRandomIntBetween(MINDELTA, MAXDELTA) / 16.; 500 gCurrentDelta.y = (float)(SSRandomIntBetween(MINDELTA, MAXDELTA) * signof(gCurrentDelta.y)) / 16.; 501 } 502 if (currentDrawingRect.origin.y + gTextBoxHeight - gActualTextBoxHeight <= SAFETYBORDER) { 503 gCurrentDelta.y = (float)SSRandomIntBetween(MINDELTA, MAXDELTA) / 16.; 504 gCurrentDelta.x = (float)(SSRandomIntBetween(MINDELTA, MAXDELTA) * signof(gCurrentDelta.x)) / 16.; 505 } 506 if ( (currentDrawingRect.origin.y + currentDrawingRect.size.height) >= 507 (viewBounds.origin.y + viewBounds.size.height - SAFETYBORDER) ) { 508 gCurrentDelta.y = -(float)SSRandomIntBetween(MINDELTA, MAXDELTA) / 16.; 509 gCurrentDelta.x = (float)(SSRandomIntBetween(MINDELTA, MAXDELTA) * signof(gCurrentDelta.x)) / 16.; 510 } 511#if 0 512 // For testing 513 gCurrentDelta.x = 0; 514 gCurrentDelta.y = 0; 515#endif 516 517 if (!isErased) { 518 [[NSColor blackColor] set]; 519 520 // Erasing only 2 small rectangles reduces screensaver's CPU usage by about 25% 521 imagePosition.x = (float) ((int)gCurrentPosition.x + gImageXIndent); 522 imagePosition.y = (float) (int)gCurrentPosition.y; 523 eraseRect.origin.y = imagePosition.y; 524 eraseRect.size.height = currentDrawingRect.size.height - gTextBoxHeight; 525 526 if (gCurrentDelta.x > 0) { 527 eraseRect.origin.x = imagePosition.x - 1; 528 eraseRect.size.width = gCurrentDelta.x + 1; 529 } else { 530 eraseRect.origin.x = currentDrawingRect.origin.x + currentDrawingRect.size.width - gImageXIndent + gCurrentDelta.x - 1; 531 eraseRect.size.width = -gCurrentDelta.x + 1; 532 } 533 534 eraseRect = NSInsetRect(eraseRect, -1, -1); 535 NSRectFill(eraseRect); 536 537 eraseRect.origin.x = imagePosition.x; 538 eraseRect.size.width = currentDrawingRect.size.width - gImageXIndent - gImageXIndent; 539 540 if (gCurrentDelta.y > 0) { 541 eraseRect.origin.y = imagePosition.y; 542 eraseRect.size.height = gCurrentDelta.y + 1; 543 } else { 544 eraseRect.origin.y = imagePosition.y + currentDrawingRect.size.height - gTextBoxHeight - 1; 545 eraseRect.size.height = -gCurrentDelta.y + 1; 546 } 547 eraseRect = NSInsetRect(eraseRect, -1, -1); 548 NSRectFill(eraseRect); 549 550 eraseRect = currentDrawingRect; 551 eraseRect.size.height = gTextBoxHeight; 552 eraseRect = NSInsetRect(eraseRect, -1, -1); 553 NSRectFill(eraseRect); 554 555 isErased = true; 556 } 557 558 // Get the new drawing area 559 gCurrentPosition.x += gCurrentDelta.x; 560 gCurrentPosition.y += gCurrentDelta.y; 561 562 imagePosition.x = (float) ((int)gCurrentPosition.x + gImageXIndent); 563 imagePosition.y = (float) (int)gCurrentPosition.y; 564 565 [ gBOINC_Logo drawAtPoint:imagePosition fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0 ]; 566 567 if ( (msg != NULL) && (msg[0] != '\0') ) { 568 cf_msg = CFStringCreateWithCString(NULL, msg, kCFStringEncodingMacRoman); 569 570 CGRect bounds = CGRectMake((float) ((int)gCurrentPosition.x), 571 viewBounds.size.height - imagePosition.y + TEXTBOXTOPBORDER, 572 gMovingRect.size.width, 573 MAXTEXTBOXHEIGHT 574 ); 575 576 CGContextSaveGState (myContext); 577 CGContextTranslateCTM (myContext, 0, viewBounds.origin.y + viewBounds.size.height); 578 CGContextScaleCTM (myContext, 1.0f, -1.0f); 579 580 CTFontRef myFont = CTFontCreateWithName(CFSTR("Helvetica"), 20, NULL); 581 582 HIThemeTextInfo theTextInfo = {kHIThemeTextInfoVersionOne, kThemeStateActive, kThemeSpecifiedFont, 583 kHIThemeTextHorizontalFlushLeft, kHIThemeTextVerticalFlushTop, 584 kHIThemeTextBoxOptionNone, kHIThemeTextTruncationNone, 0, false, 585 0, myFont 586 }; 587 textInfo = theTextInfo; 588 589 HIThemeGetTextDimensions(cf_msg, (float)gMovingRect.size.width, &textInfo, NULL, &gActualTextBoxHeight, NULL); 590 gActualTextBoxHeight += TEXTBOXTOPBORDER; 591 592 CGFloat myWhiteComponents[] = {1.0, 1.0, 1.0, 1.0}; 593 CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB (); 594 CGColorRef myTextColor = CGColorCreate(myColorSpace, myWhiteComponents); 595 596 CGContextSetFillColorWithColor(myContext, myTextColor); 597 598 HIThemeDrawTextBox(cf_msg, &bounds, &textInfo, myContext, kHIThemeOrientationNormal); 599 600 CGColorRelease(myTextColor); 601 CGColorSpaceRelease(myColorSpace); 602 CGContextRestoreGState (myContext); 603 CFRelease(cf_msg); 604 } 605 606 gTextBoxHeight = MAXTEXTBOXHEIGHT + TEXTBOXTOPBORDER; 607 gMovingRect.size.height = [gBOINC_Logo size].height + gTextBoxHeight; 608 609 isErased = false; 610 611 } else { // Empty or NULL message 612 if (!isErased) { 613 eraseRect = NSInsetRect(currentDrawingRect, -1, -1); 614 [[NSColor blackColor] set]; 615 isErased = true; 616 NSRectFill(eraseRect); 617 gTextBoxHeight = MAXTEXTBOXHEIGHT; 618 gMovingRect.size.height = [gBOINC_Logo size].height + gTextBoxHeight; 619 } 620 } 621 622 if (newFrequency) { 623 [ self setAnimationTimeInterval:(1.0/newFrequency) ]; 624 // setAnimationTimeInterval does not seem to be working, so we 625 // throttle the screensaver directly here. 626 timeToBlock = (1.0/newFrequency) - (getDTime() - frameStartTime); 627 if (timeToBlock > 0.0) { 628 doBoinc_Sleep(timeToBlock); 629 } 630 } 631 632 // Check for a new graphics app sending us data 633 if (UseSharedOffscreenBuffer() && gfxAppStartTime) { 634 [mySharedGraphicsController testConnection]; 635 } 636} 637 638- (BOOL)hasConfigureSheet { 639 return YES; 640} 641 642// Display the configuration sheet for the user to choose their settings 643- (NSWindow*)configureSheet 644{ 645 int period; 646 647 // if we haven't loaded our configure sheet, load the nib named MyScreenSaver.nib 648 if (!mConfigureSheet) { 649 if ([[ NSBundle bundleForClass:[ self class ]] respondsToSelector: @selector(loadNibNamed: owner: topLevelObjects:)]) { 650 651#pragma clang diagnostic push 652#pragma clang diagnostic ignored "-Wobjc-method-access" 653 // [NSBundle loadNibNamed: owner: topLevelObjects:] is not available before OS 10.8 654 [ [ NSBundle bundleForClass:[ self class ]] loadNibNamed:@"BOINCSaver" owner:self topLevelObjects:NULL ]; 655#pragma clang diagnostic pop 656 } 657#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1080 658 else { 659#pragma clang diagnostic push 660#pragma clang diagnostic ignored "-Wdeprecated-declarations" 661 // [NSBundle loadNibNamed: owner:] is deprecated in OS 10.8 662 [ NSBundle loadNibNamed:@"BOINCSaver" owner:self ]; 663#pragma clang diagnostic pop 664 } 665#endif 666 } 667 // set the UI state 668 [ mGoToBlankCheckbox setState:gGoToBlank ]; 669 670 mBlankingTimeString = [[ NSString alloc ] initWithFormat:@"%d", gBlankingTime ]; 671 [ mBlankingTimeTextField setStringValue:mBlankingTimeString ]; 672 673 period = getGFXDefaultPeriod() / 60; 674 mDefaultPeriodString = [[ NSString alloc ] initWithFormat:@"%d", period ]; 675 [ mDefaultPeriodTextField setStringValue:mDefaultPeriodString ]; 676 677 period = getGFXSciencePeriod() / 60; 678 mSciencePeriodString = [[ NSString alloc ] initWithFormat:@"%d", period ]; 679 [ mSciencePeriodTextField setStringValue:mSciencePeriodString ]; 680 681 period = getGGFXChangePeriod() / 60; 682 mChangePeriodString = [[ NSString alloc ] initWithFormat:@"%d", period ]; 683 [ mChangePeriodTextField setStringValue:mChangePeriodString ]; 684 685 return mConfigureSheet; 686} 687 688// Called when the user clicked the SAVE button 689- (IBAction) closeSheetSave:(id) sender 690{ 691 int period = 0; 692 693 NSScanner *scanner, *scanner2; 694 695 // get the defaults 696 ScreenSaverDefaults *defaults = [ ScreenSaverDefaults defaultsForModuleWithName:mBundleID ]; 697 698 // save the UI state 699 gGoToBlank = [ mGoToBlankCheckbox state ]; 700 mBlankingTimeString = [ mBlankingTimeTextField stringValue ]; 701 gBlankingTime = [ mBlankingTimeString intValue ]; 702 scanner = [ NSScanner scannerWithString:mBlankingTimeString]; 703 if (![ scanner scanInt:&period ]) goto Bad; 704 if (![ scanner isAtEnd ]) goto Bad; 705 if ((period < 0) || (period > 999)) goto Bad; 706 gBlankingTime = period; 707 708 mDefaultPeriodString = [ mDefaultPeriodTextField stringValue ]; 709 scanner2 = [ scanner initWithString:mDefaultPeriodString]; 710 if (![ scanner2 scanInt:&period ]) goto Bad; 711 if (![ scanner2 isAtEnd ]) goto Bad; 712 if ((period < 0) || (period > 999)) goto Bad; 713 setGFXDefaultPeriod((double)(period * 60)); 714 715 mSciencePeriodString = [ mSciencePeriodTextField stringValue ]; 716 scanner2 = [ scanner initWithString:mSciencePeriodString]; 717 if (![ scanner2 scanInt:&period ]) goto Bad; 718 if (![ scanner2 isAtEnd ]) goto Bad; 719 if ((period < 0) || (period > 999)) goto Bad; 720 setGFXSciencePeriod((double)(period * 60)); 721 722 mChangePeriodString = [ mChangePeriodTextField stringValue ]; 723 scanner2 = [ scanner initWithString:mChangePeriodString]; 724 if (![ scanner2 scanInt:&period ]) goto Bad; 725 if (![ scanner2 isAtEnd ]) goto Bad; 726 if ((period < 0) || (period > 999)) goto Bad; 727 setGGFXChangePeriod((double)(period * 60)); 728 729 // write the defaults 730 [ defaults setInteger:gGoToBlank forKey:@"GoToBlank" ]; 731 [ defaults setInteger:gBlankingTime forKey:@"BlankingTime" ]; 732 period = getGFXDefaultPeriod() / 60; 733 [ defaults setInteger:period forKey:@"DefaultPeriod" ]; 734 period = getGFXSciencePeriod() / 60; 735 [ defaults setInteger:period forKey:@"SciencePeriod" ]; 736 period = getGGFXChangePeriod() / 60; 737 [ defaults setInteger:period forKey:@"ChangePeriod" ]; 738 739 // synchronize 740 [ defaults synchronize ]; 741 742 // end the sheet 743 [ NSApp endSheet:mConfigureSheet ]; 744 return; 745Bad: 746; // Empty statement is needed to prevent compiler error 747 NSAlert *alert = [[NSAlert alloc] init]; 748 [alert addButtonWithTitle:@"OK"]; 749 [alert setMessageText:@"Please enter a number between 0 and 999."]; 750 [alert setAlertStyle:NSCriticalAlertStyle]; 751 752 if ([alert respondsToSelector: @selector(beginSheetModalForWindow: completionHandler:)]){ 753#pragma clang diagnostic push 754#pragma clang diagnostic ignored "-Wobjc-method-access" 755 // [NSAlert beginSheetModalForWindow: completionHandler:] is not available before OS 10.9 756 [alert beginSheetModalForWindow:mConfigureSheet completionHandler:^(NSModalResponse returnCode){}]; 757#pragma clang diagnostic pop 758 } 759#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1090 760 else { 761#pragma clang diagnostic push 762#pragma clang diagnostic ignored "-Wdeprecated-declarations" 763 // [NSAlert beginSheetModalForWindow: modalDelegate: didEndSelector: contextInfo:] is deprecated in OS 10.9 764 [alert beginSheetModalForWindow:mConfigureSheet modalDelegate:self didEndSelector:nil contextInfo:nil]; 765#pragma clang diagnostic pop 766 } 767#endif 768} 769 770// Called when the user clicked the CANCEL button 771- (IBAction) closeSheetCancel:(id) sender 772{ 773 // nothing to configure 774 [ NSApp endSheet:mConfigureSheet ]; 775} 776 777// Find the gtaphics app's window number (window ID) 778- (bool) setUpToUseCGWindowList 779{ 780 NSArray *windowList = (__bridge NSArray*)CGWindowListCopyWindowInfo( 781 kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, 782 kCGNullWindowID); 783 for (int i=[windowList count]-1; i>=0; i--) { 784 NSDictionary *dict = (NSDictionary*)(windowList[i]); 785 NSString * pidString = dict[(id)kCGWindowOwnerPID]; 786 if ((pid_t)[pidString intValue] == childPid) { 787 NSString * windowNumString = dict[(id)kCGWindowNumber]; 788 gfxAppWindowNum = (int)[windowNumString intValue]; 789 useCGWindowList = true; 790 childApp = [NSRunningApplication runningApplicationWithProcessIdentifier:childPid]; 791 if (imageView == nil) { 792 imageView = [[NSView alloc] initWithFrame:[self frame]]; 793 [self addSubview:imageView]; 794 } 795 return true; // Success 796 } 797 } 798 return false; // Not found 799} 800 801@end 802 803// On OS 10.13 or later, use MachO comunication and IOSurfaceBuffer to 804// display the graphics output of our child graphics apps in our window. 805// All code past this point is for that implementation. 806 807// Adapted from Apple Developer Tech Support Sample Code MutiGPUIOSurface: 808// <https://developer.apple.com/library/content/samplecode/MultiGPUIOSurface> 809 810#define NUM_IOSURFACE_BUFFERS 2 811 812@interface SharedGraphicsController() 813{ 814 NSMachPort *serverPort; 815 NSMachPort *localPort; 816 817 uint32_t serverPortName; 818 uint32_t localPortName; 819 820 int32_t clientIndex; 821 uint32_t nextFrameIndex; 822 823 NSView *screenSaverView; 824 saverOpenGLView *openGLView; 825 826 IOSurfaceRef _ioSurfaceBuffers[NUM_IOSURFACE_BUFFERS]; 827 mach_port_t _ioSurfaceMachPorts[NUM_IOSURFACE_BUFFERS]; 828 GLuint _textureNames[NUM_IOSURFACE_BUFFERS]; 829} 830@end 831 832static bool okToDraw; 833 834@implementation SharedGraphicsController 835 836- (instancetype)init:(NSView*)saverView { 837 screenSaverView = saverView; 838 839 [[NSNotificationCenter defaultCenter] addObserver:self 840 selector:@selector(portDied:) name:NSPortDidBecomeInvalidNotification object:nil]; 841 842 [self testConnection]; 843 844 return self; 845} 846 847 848- (void) testConnection 849{ 850 mach_port_t servicePortNum = MACH_PORT_NULL; 851 kern_return_t machErr; 852 char *portName = "edu.berkeley.boincsaver"; 853 854 // Try to check in with master. 855// NSMachBootstrapServer is deprecated in OS 10.13, so use bootstrap_look_up 856// serverPort = [(NSMachPort *)([[NSMachBootstrapServer sharedInstance] portForName:@"edu.berkeley.boincsaver"]) retain]; 857 machErr = bootstrap_look_up(bootstrap_port, portName, &servicePortNum); 858 if (machErr == KERN_SUCCESS) { 859 serverPort = (NSMachPort*)[NSMachPort portWithMachPort:servicePortNum]; 860 } else { 861 serverPort = MACH_PORT_NULL; 862 } 863 864 if(serverPort != MACH_PORT_NULL) 865 { 866 // Create our own local port. 867 localPort = [[NSMachPort alloc] init]; 868 869 // Retrieve raw mach port names. 870 serverPortName = [serverPort machPort]; 871 localPortName = [localPort machPort]; 872 873 // Register our local port with the current runloop. 874 [localPort setDelegate:self]; 875 [localPort scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 876 877 // Check in with server. 878 int kr; 879 kr = _MGCCheckinClient(serverPortName, localPortName, &clientIndex); 880 if(kr != 0) 881 [NSApp terminate:nil]; 882 883 openGLView = [[saverOpenGLView alloc] initWithFrame:[screenSaverView frame]]; 884 885 [screenSaverView addSubview:openGLView]; 886 887 runningSharedGraphics = true; 888 889 if (childPid) { 890 gfxAppStartTime = 0.0; 891 childApp = [NSRunningApplication runningApplicationWithProcessIdentifier:childPid]; 892 } 893 } 894} 895 896- (void)portDied:(NSNotification *)notification 897{ 898 NSPort *port = [notification object]; 899 if(port == serverPort) { 900 childApp = nil; 901 gfxAppStartTime = 0.0; 902 gfxAppPath[0] = '\0'; 903 904 if ([serverPort isValid]) { 905 [serverPort invalidate]; 906// [serverPort release]; 907 } 908 serverPort = nil; 909 [localPort removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 910 911 if ([localPort isValid]) { 912 [localPort invalidate]; 913 } 914// [localPort release]; 915 localPort = nil; 916 917 int i; 918 for(i = 0; i < NUM_IOSURFACE_BUFFERS; i++) { 919 if (_ioSurfaceBuffers[i]) { 920 CFRelease(_ioSurfaceBuffers[i]); 921 _ioSurfaceBuffers[i] = nil; 922 } 923 924 // if (glIsTexture(_textureNames[i])) { 925 // glDeleteTextures(1, _textureNames[i]); 926 // } 927 _textureNames[i] = 0; 928 929 if (_ioSurfaceMachPorts[i] != MACH_PORT_NULL) { 930 mach_port_deallocate(mach_task_self(), _ioSurfaceMachPorts[i]); 931 _ioSurfaceMachPorts[i] = MACH_PORT_NULL; 932 } 933 } 934 935 if ((serverPort == nil) && (localPort == nil)) { 936 runningSharedGraphics = false; 937 [openGLView removeFromSuperview]; // Releases openGLView 938 } 939 } 940} 941- (void)handleMachMessage:(void *)msg 942{ 943 union __ReplyUnion___MGCMGSServer_subsystem reply; 944 945 mach_msg_header_t *reply_header = (void *)&reply; 946 kern_return_t kr; 947 948 if(MGSServer_server(msg, reply_header) && reply_header->msgh_remote_port != MACH_PORT_NULL) 949 { 950 kr = mach_msg(reply_header, MACH_SEND_MSG, reply_header->msgh_size, 0, MACH_PORT_NULL, 951 0, MACH_PORT_NULL); 952 if(kr != 0) 953 [NSApp terminate:nil]; 954 } 955} 956 957- (kern_return_t)displayFrame:(int32_t)frameIndex surfacemachport:(mach_port_t)iosurface_port 958{ 959 nextFrameIndex = frameIndex; 960 961 if(!_ioSurfaceBuffers[frameIndex]) 962 { 963 _ioSurfaceBuffers[frameIndex] = IOSurfaceLookupFromMachPort(iosurface_port); 964 _ioSurfaceMachPorts[frameIndex] = iosurface_port; 965 } 966 if(!_textureNames[frameIndex]) 967 { 968 _textureNames[frameIndex] = [openGLView setupIOSurfaceTexture:_ioSurfaceBuffers[frameIndex]]; 969 } 970 971 okToDraw = true; // Tell drawRect that we have real data to display 972 973 [openGLView setNeedsDisplay:YES]; 974 [openGLView display]; 975 976 return 0; 977} 978 979// For the MachO client, this is a no-op. 980kern_return_t _MGSCheckinClient(mach_port_t server_port, mach_port_t client_port, 981 int32_t *client_index) 982{ 983 return 0; 984} 985 986kern_return_t _MGSDisplayFrame(mach_port_t server_port, int32_t frame_index, mach_port_t iosurface_port) 987{ 988 return [mySharedGraphicsController displayFrame:frame_index surfacemachport:iosurface_port]; 989} 990 991- (GLuint)currentTextureName 992{ 993 return _textureNames[nextFrameIndex]; 994} 995 996@end 997 998@implementation saverOpenGLView 999 1000- (instancetype)initWithFrame:(NSRect)frame { 1001 NSOpenGLPixelFormatAttribute attribs [] = 1002 { 1003// NSOpenGLPFAWindow, 1004 NSOpenGLPFADoubleBuffer, 1005 NSOpenGLPFAAccelerated, 1006 NSOpenGLPFANoRecovery, 1007 NSOpenGLPFAColorSize, (NSOpenGLPixelFormatAttribute)32, 1008 NSOpenGLPFAAlphaSize, (NSOpenGLPixelFormatAttribute)8, 1009 NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)24, 1010 (NSOpenGLPixelFormatAttribute) 0 1011 }; 1012 1013 NSOpenGLPixelFormat *pix_fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; 1014 1015 if(!pix_fmt) 1016 [ NSApp terminate:nil]; 1017 1018 self = [super initWithFrame:frame pixelFormat:pix_fmt]; 1019 1020 1021 [[self openGLContext] makeCurrentContext]; 1022 1023 // drawRect is apparently called due to the above code, causing the 1024 // screen to flash unless we prevent any actual drawing, so tell 1025 // drawRect that we do not yet have real data to display 1026 okToDraw = false; 1027 1028 return self; 1029} 1030 1031- (void)prepareOpenGL 1032{ 1033 [super prepareOpenGL]; 1034} 1035 1036- (void)update 1037{ 1038 // Override to do nothing. 1039} 1040 1041// Create an IOSurface backed texture 1042- (GLuint)setupIOSurfaceTexture:(IOSurfaceRef)ioSurfaceBuffer 1043{ 1044 GLuint name; 1045 CGLContextObj cgl_ctx = (CGLContextObj)[[self openGLContext] CGLContextObj]; 1046 1047 glGenTextures(1, &name); 1048 1049 glBindTexture(GL_TEXTURE_RECTANGLE, name); 1050 // At the moment, CGLTexImageIOSurface2D requires the GL_TEXTURE_RECTANGLE target 1051 CGLTexImageIOSurface2D(cgl_ctx, GL_TEXTURE_RECTANGLE, GL_RGBA, (GLsizei)self.bounds.size.width, (GLsizei)self.bounds.size.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 1052 ioSurfaceBuffer, 0); 1053 1054 glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 1055 glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 1056 glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 1057 glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 1058 1059 return name; 1060} 1061 1062- (BOOL)isOpaque 1063{ 1064 return YES; 1065} 1066 1067// Render a quad with the the IOSurface backed texture 1068- (void)renderTextureFromIOSurfaceWithWidth:(GLsizei)logoWidth height:(GLsizei)logoHeight 1069{ 1070 GLfloat quad[] = { 1071 //x, y s, t 1072 (GLfloat)logoWidth, 0.0f, 0.0f, 0.0f, 1073 0.0f, (GLfloat)logoHeight, 0.0f, 0.0f, 1074 0.0f, 0.0f, 1.0f, 0.0f, 1075 0.0f, 0.0f, 0.0f, 1.0f 1076 }; 1077 1078 GLint saveMatrixMode; 1079 1080 glGetIntegerv(GL_MATRIX_MODE, &saveMatrixMode); 1081 glMatrixMode(GL_TEXTURE); 1082 glPushMatrix(); 1083 glLoadMatrixf(quad); 1084 glMatrixMode(saveMatrixMode); 1085 1086 glBindTexture(GL_TEXTURE_RECTANGLE, [mySharedGraphicsController currentTextureName]); 1087 glEnable(GL_TEXTURE_RECTANGLE); 1088 1089 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 1090 1091 //Draw textured quad 1092 glBegin(GL_QUADS); 1093 glTexCoord2f(0.0, 0.0); 1094 glVertex3f(-1.0, -1.0, 0.0); 1095 glTexCoord2f(1.0, 0.0); 1096 glVertex3f(1.0, -1.0, 0.0); 1097 glTexCoord2f(1.0, 1.0); 1098 glVertex3f(1.0, 1.0, 0.0); 1099 glTexCoord2f(0.0, 1.0); 1100 glVertex3f(-1.0, 1.0, 0.0); 1101 glEnd(); 1102 1103 glDisable(GL_TEXTURE_RECTANGLE); 1104 1105 glGetIntegerv(GL_MATRIX_MODE, &saveMatrixMode); 1106 glMatrixMode(GL_TEXTURE); 1107 glPopMatrix(); 1108 glMatrixMode(saveMatrixMode); 1109 1110} 1111 1112- (void)drawRect:(NSRect)theRect 1113{ 1114 glViewport(0, 0, (GLint)theRect.size.width, (GLint)theRect.size.height); 1115 1116 glClearColor(0.0, 0.0, 0.0, 0.0); 1117 1118 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 1119 1120 // drawRect is apparently called before we have real data to display, 1121 // causing the screen to flash unless we prevent any actual drawing. 1122 if (!okToDraw) { 1123 [[self openGLContext] flushBuffer]; 1124 return; 1125} 1126 1127 // MachO client draws with current IO surface contents as texture 1128 [self renderTextureFromIOSurfaceWithWidth:(GLsizei)self.bounds.size.width height:(GLsizei)self.bounds.size.height]; 1129 1130 [[self openGLContext] flushBuffer]; 1131} 1132 1133@end 1134 1135 1136// On OS 10.13 or later, use MachO comunication and IOSurfaceBuffer to 1137// display the graphics output of our child graphics apps in our window. 1138static bool UseSharedOffscreenBuffer() { 1139 static bool alreadyTested = false; 1140 static bool needSharedGfxBuffer = false; 1141 1142//return true; // FOR TESTING ONLY 1143 if (alreadyTested) { 1144 return needSharedGfxBuffer; 1145 } 1146 alreadyTested = true; 1147 if (compareOSVersionTo(10, 13) >= 0) { 1148 needSharedGfxBuffer = true; 1149 return true; 1150 } 1151 return false; 1152} 1153 1154 1155