1/* xscreensaver, Copyright (c) 2006-2020 Jamie Zawinski <jwz@jwz.org> 2 * 3 * Permission to use, copy, modify, distribute, and sell this software and its 4 * documentation for any purpose is hereby granted without fee, provided that 5 * the above copyright notice appear in all copies and that both that 6 * copyright notice and this permission notice appear in supporting 7 * documentation. No representations are made about the suitability of this 8 * software for any purpose. It is provided "as is" without express or 9 * implied warranty. 10 */ 11 12/* This is a subclass of Apple's ScreenSaverView that knows how to run 13 xscreensaver programs without X11 via the dark magic of the "jwxyz" 14 library. In xscreensaver terminology, this is the replacement for 15 the "screenhack.c" module. 16 */ 17 18#import <QuartzCore/QuartzCore.h> 19#import <sys/mman.h> 20#import <zlib.h> 21#ifdef LOG_STACK 22# include <execinfo.h> 23#endif 24#import "XScreenSaverView.h" 25#import "XScreenSaverConfigSheet.h" 26#import "Updater.h" 27#import "screenhackI.h" 28#import "pow2.h" 29#import "jwxyzI.h" 30#import "jwxyz-cocoa.h" 31#import "jwxyz-timers.h" 32 33#ifdef USE_IPHONE 34// XScreenSaverView.m speaks OpenGL ES just fine, but enableBackbuffer does 35// need (jwzgles_)gluCheckExtension. 36# import "jwzglesI.h" 37#else 38# import <OpenGL/glu.h> 39#endif 40 41/* Garbage collection only exists if we are being compiled against the 42 10.6 SDK or newer, not if we are building against the 10.4 SDK. 43 */ 44#ifndef MAC_OS_X_VERSION_10_6 45# define MAC_OS_X_VERSION_10_6 1060 /* undefined in 10.4 SDK, grr */ 46#endif 47#ifndef MAC_OS_X_VERSION_10_12 48# define MAC_OS_X_VERSION_10_12 101200 49#endif 50#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 && \ 51 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12) 52 /* 10.6 SDK or later, and earlier than 10.12 SDK */ 53# import <objc/objc-auto.h> 54# define DO_GC_HACKERY 55#endif 56 57#undef countof 58#define countof(x) (sizeof((x))/sizeof((*x))) 59 60 61/* Duplicated in xlockmoreI.h and XScreenSaverGLView.m. */ 62extern void clear_gl_error (void); 63extern void check_gl_error (const char *type); 64 65extern struct xscreensaver_function_table *xscreensaver_function_table; 66 67/* Global variables used by the screen savers 68 */ 69const char *progname; 70const char *progclass; 71int mono_p = 0; 72 73 74# ifdef USE_IPHONE 75 76# define NSSizeToCGSize(x) (x) 77 78extern NSDictionary *make_function_table_dict(void); // ios-function-table.m 79 80/* Stub definition of the superclass, for iPhone. 81 */ 82@implementation ScreenSaverView 83{ 84 NSTimeInterval anim_interval; 85 Bool animating_p; 86 NSTimer *anim_timer; 87} 88 89- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview { 90 self = [super initWithFrame:frame]; 91 if (! self) return 0; 92 anim_interval = 1.0/30; 93 return self; 94} 95- (NSTimeInterval)animationTimeInterval { return anim_interval; } 96- (void)setAnimationTimeInterval:(NSTimeInterval)i { anim_interval = i; } 97- (BOOL)hasConfigureSheet { return NO; } 98- (NSWindow *)configureSheet { return nil; } 99- (NSView *)configureView { return nil; } 100- (BOOL)isPreview { return NO; } 101- (BOOL)isAnimating { return animating_p; } 102- (void)animateOneFrame { } 103 104- (void)startAnimation { 105 if (animating_p) return; 106 animating_p = YES; 107 anim_timer = [NSTimer scheduledTimerWithTimeInterval: anim_interval 108 target:self 109 selector:@selector(animateOneFrame) 110 userInfo:nil 111 repeats:YES]; 112} 113 114- (void)stopAnimation { 115 if (anim_timer) { 116 [anim_timer invalidate]; 117 anim_timer = 0; 118 } 119 animating_p = NO; 120} 121@end 122 123# endif // !USE_IPHONE 124 125 126 127@interface XScreenSaverView (Private) 128- (void) stopAndClose; 129- (void) stopAndClose:(Bool)relaunch; 130@end 131 132@implementation XScreenSaverView 133 134// Given a lower-cased saver name, returns the function table for it. 135// If no name, guess the name from the class's bundle name. 136// 137- (struct xscreensaver_function_table *) findFunctionTable:(NSString *)name 138{ 139 NSBundle *nsb = [NSBundle bundleForClass:[self class]]; 140 NSAssert1 (nsb, @"no bundle for class %@", [self class]); 141 142 NSString *path = [nsb bundlePath]; 143 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, 144 (CFStringRef) path, 145 kCFURLPOSIXPathStyle, 146 true); 147 CFBundleRef cfb = CFBundleCreate (kCFAllocatorDefault, url); 148 CFRelease (url); 149 NSAssert1 (cfb, @"no CFBundle for \"%@\"", path); 150 // #### Analyze says "Potential leak of an object stored into cfb" 151 152 if (! name) 153 name = [[path lastPathComponent] stringByDeletingPathExtension]; 154 155 name = [[name lowercaseString] 156 stringByReplacingOccurrencesOfString:@" " 157 withString:@""]; 158 159# ifndef USE_IPHONE 160 // CFBundleGetDataPointerForName doesn't work in "Archive" builds. 161 // I'm guessing that symbol-stripping is mandatory. Fuck. 162 NSString *table_name = [name stringByAppendingString: 163 @"_xscreensaver_function_table"]; 164 void *addr = CFBundleGetDataPointerForName (cfb, (CFStringRef) table_name); 165 CFRelease (cfb); 166 167 if (! addr) 168 NSLog (@"no symbol \"%@\" for \"%@\"", table_name, path); 169 170# else // USE_IPHONE 171 // Depends on the auto-generated "ios-function-table.m" being up to date. 172 if (! function_tables) 173 function_tables = [make_function_table_dict() retain]; 174 NSValue *v = [function_tables objectForKey: name]; 175 void *addr = v ? [v pointerValue] : 0; 176# endif // USE_IPHONE 177 178 return (struct xscreensaver_function_table *) addr; 179} 180 181 182// Add the "Contents/Resources/" subdirectory of this screen saver's .bundle 183// to $PATH for the benefit of savers that include helper shell scripts. 184// 185- (void) setShellPath 186{ 187 NSBundle *nsb = [NSBundle bundleForClass:[self class]]; 188 NSAssert1 (nsb, @"no bundle for class %@", [self class]); 189 190 NSString *nsdir = [nsb resourcePath]; 191 NSAssert1 (nsdir, @"no resourcePath for class %@", [self class]); 192 const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding]; 193 const char *opath = getenv ("PATH"); 194 if (!opath) opath = "/bin"; // $PATH is unset when running under Shark! 195 char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 2); 196 strcpy (npath, dir); 197 strcat (npath, ":"); 198 strcat (npath, opath); 199 if (setenv ("PATH", npath, 1)) { 200 perror ("setenv"); 201 NSAssert1 (0, @"setenv \"PATH=%s\" failed", npath); 202 } 203 204 free (npath); 205} 206 207 208// set an $XSCREENSAVER_CLASSPATH variable so that included shell scripts 209// (e.g., "xscreensaver-text") know how to look up resources. 210// 211- (void) setResourcesEnv:(NSString *) name 212{ 213 NSBundle *nsb = [NSBundle bundleForClass:[self class]]; 214 NSAssert1 (nsb, @"no bundle for class %@", [self class]); 215 216 const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding]; 217 if (setenv ("XSCREENSAVER_CLASSPATH", s, 1)) { 218 perror ("setenv"); 219 NSAssert1 (0, @"setenv \"XSCREENSAVER_CLASSPATH=%s\" failed", s); 220 } 221} 222 223 224- (void) loadCustomFonts 225{ 226# ifndef USE_IPHONE 227 NSBundle *nsb = [NSBundle bundleForClass:[self class]]; 228 NSMutableArray *fonts = [NSMutableArray arrayWithCapacity:20]; 229 for (NSString *ext in @[@"ttf", @"otf"]) { 230 [fonts addObjectsFromArray: [nsb pathsForResourcesOfType:ext 231 inDirectory:NULL]]; 232 } 233 for (NSString *font in fonts) { 234 CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: font]; 235 CFErrorRef err = 0; 236 if (! CTFontManagerRegisterFontsForURL (url, kCTFontManagerScopeProcess, 237 &err)) { 238 // Just ignore errors: 239 // "The file has already been registered in the specified scope." 240 // NSLog (@"loading font: %@ %@", url, err); 241 } 242 } 243# endif // !USE_IPHONE 244} 245 246 247static void 248add_default_options (const XrmOptionDescRec *opts, 249 const char * const *defs, 250 XrmOptionDescRec **opts_ret, 251 const char ***defs_ret) 252{ 253 /* These aren't "real" command-line options (there are no actual command-line 254 options in the Cocoa version); but this is the somewhat kludgey way that 255 the <xscreensaver-text /> and <xscreensaver-image /> tags in the 256 ../hacks/config/\*.xml files communicate with the preferences database. 257 */ 258 static const XrmOptionDescRec default_options [] = { 259 { "-text-mode", ".textMode", XrmoptionSepArg, 0 }, 260 { "-text-literal", ".textLiteral", XrmoptionSepArg, 0 }, 261 { "-text-file", ".textFile", XrmoptionSepArg, 0 }, 262 { "-text-url", ".textURL", XrmoptionSepArg, 0 }, 263 { "-text-program", ".textProgram", XrmoptionSepArg, 0 }, 264 { "-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "True" }, 265 { "-no-grab-desktop", ".grabDesktopImages", XrmoptionNoArg, "False"}, 266 { "-choose-random-images", ".chooseRandomImages",XrmoptionNoArg, "True" }, 267 { "-no-choose-random-images",".chooseRandomImages",XrmoptionNoArg, "False"}, 268 { "-image-directory", ".imageDirectory", XrmoptionSepArg, 0 }, 269 { "-fps", ".doFPS", XrmoptionNoArg, "True" }, 270 { "-no-fps", ".doFPS", XrmoptionNoArg, "False"}, 271 { "-foreground", ".foreground", XrmoptionSepArg, 0 }, 272 { "-fg", ".foreground", XrmoptionSepArg, 0 }, 273 { "-background", ".background", XrmoptionSepArg, 0 }, 274 { "-bg", ".background", XrmoptionSepArg, 0 }, 275 276# ifndef USE_IPHONE 277 // <xscreensaver-updater /> 278 { "-" SUSUEnableAutomaticChecksKey, 279 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "True" }, 280 { "-no-" SUSUEnableAutomaticChecksKey, 281 "." SUSUEnableAutomaticChecksKey, XrmoptionNoArg, "False" }, 282 { "-" SUAutomaticallyUpdateKey, 283 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "True" }, 284 { "-no-" SUAutomaticallyUpdateKey, 285 "." SUAutomaticallyUpdateKey, XrmoptionNoArg, "False" }, 286 { "-" SUSendProfileInfoKey, 287 "." SUSendProfileInfoKey, XrmoptionNoArg,"True" }, 288 { "-no-" SUSendProfileInfoKey, 289 "." SUSendProfileInfoKey, XrmoptionNoArg,"False"}, 290 { "-" SUScheduledCheckIntervalKey, 291 "." SUScheduledCheckIntervalKey, XrmoptionSepArg, 0 }, 292# endif // !USE_IPHONE 293 294 { 0, 0, 0, 0 } 295 }; 296 static const char *default_defaults [] = { 297 298# if defined(USE_IPHONE) && !defined(__OPTIMIZE__) 299 ".doFPS: True", 300# else 301 ".doFPS: False", 302# endif 303 ".doubleBuffer: True", 304 ".multiSample: False", 305 ".textMode: url", 306 ".textLiteral: ", 307 ".textFile: ", 308 ".textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss", 309 ".textProgram: ", 310 ".grabDesktopImages: yes", 311# ifndef USE_IPHONE 312 ".chooseRandomImages: no", 313# else 314 ".chooseRandomImages: yes", 315# endif 316 ".imageDirectory: ~/Pictures", 317 ".relaunchDelay: 2", 318 ".texFontCacheSize: 30", 319 320# ifndef USE_IPHONE 321# define STR1(S) #S 322# define STR(S) STR1(S) 323# define __objc_yes Yes 324# define __objc_no No 325 "." SUSUEnableAutomaticChecksKey ": " STR(SUSUEnableAutomaticChecksDef), 326 "." SUAutomaticallyUpdateKey ": " STR(SUAutomaticallyUpdateDef), 327 "." SUSendProfileInfoKey ": " STR(SUSendProfileInfoDef), 328 "." SUScheduledCheckIntervalKey ": " STR(SUScheduledCheckIntervalDef), 329# undef __objc_yes 330# undef __objc_no 331# undef STR1 332# undef STR 333# endif // USE_IPHONE 334 0 335 }; 336 337 int count = 0, i, j; 338 for (i = 0; default_options[i].option; i++) 339 count++; 340 for (i = 0; opts[i].option; i++) 341 count++; 342 343 XrmOptionDescRec *opts2 = (XrmOptionDescRec *) 344 calloc (count + 1, sizeof (*opts2)); 345 346 i = 0; 347 j = 0; 348 while (default_options[j].option) { 349 opts2[i] = default_options[j]; 350 i++, j++; 351 } 352 j = 0; 353 while (opts[j].option) { 354 opts2[i] = opts[j]; 355 i++, j++; 356 } 357 358 *opts_ret = opts2; 359 360 361 /* now the defaults 362 */ 363 count = 0; 364 for (i = 0; default_defaults[i]; i++) 365 count++; 366 for (i = 0; defs[i]; i++) 367 count++; 368 369 const char **defs2 = (const char **) calloc (count + 1, sizeof (*defs2)); 370 371 i = 0; 372 j = 0; 373 while (default_defaults[j]) { 374 defs2[i] = default_defaults[j]; 375 i++, j++; 376 } 377 j = 0; 378 while (defs[j]) { 379 defs2[i] = defs[j]; 380 i++, j++; 381 } 382 383 *defs_ret = defs2; 384} 385 386 387static void sighandler (int sig) 388{ 389 const char *s = strsignal(sig); 390 if (!s) s = "Unknowng"; 391# ifdef USE_IPHONE 392 jwxyz_abort ("Signal: %s", s); // Throw NSException, show dialog 393# else 394 NSLog (@"Signal: %s", s); // Just make sure it is logged 395 396 // Log stack trace too. 397 // Same info shows up in Library/Logs/DiagnosticReports/ScreenSaverEngine* 398# ifdef LOG_STACK 399 void *stack [20]; 400 int frames = backtrace (stack, countof(stack)); 401 char **strs = backtrace_symbols (stack, frames); 402 NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; 403 for (int i = 2; i < frames; i++) { 404 if (strs[i]) 405 [backtrace addObject:[NSString stringWithUTF8String: strs[i]]]; 406 } 407 // Can't embed newlines in the message for /usr/bin/log 408 NSLog(@"Stack:\\n\t%@", [backtrace componentsJoinedByString:@"\\n\t"]); 409 // free (strs); 410# endif // LOG_STACK 411 412 signal (sig, SIG_DFL); 413 kill (getpid (), sig); 414# endif 415} 416 417static void catch_signals (void) 418{ 419 signal (SIGINT, sighandler); 420 signal (SIGQUIT, sighandler); 421 signal (SIGILL, sighandler); 422 signal (SIGTRAP, sighandler); 423 signal (SIGABRT, sighandler); 424 signal (SIGEMT, sighandler); 425 signal (SIGFPE, sighandler); 426 signal (SIGBUS, sighandler); 427 signal (SIGSEGV, sighandler); 428 signal (SIGSYS, sighandler); 429 signal (SIGTERM, sighandler); 430 signal (SIGXCPU, sighandler); 431 signal (SIGXFSZ, sighandler); 432} 433 434 435- (id) initWithFrame:(NSRect)frame 436 saverName:(NSString *)saverName 437 isPreview:(BOOL)isPreview 438{ 439 if (! (self = [super initWithFrame:frame isPreview:isPreview])) 440 return 0; 441 442 catch_signals(); 443 xsft = [self findFunctionTable: saverName]; 444 if (! xsft) { 445 [self release]; 446 return 0; 447 } 448 449 [self setShellPath]; 450 451 setup_p = YES; 452 if (xsft->setup_cb) 453 xsft->setup_cb (xsft, xsft->setup_arg); 454 455 /* The plist files for these preferences show up in 456 $HOME/Library/Preferences/ByHost/ in a file named like 457 "org.jwz.xscreensaver.<SAVERNAME>.<NUMBERS>.plist" 458 */ 459 NSString *name = [NSString stringWithCString:xsft->progclass 460 encoding:NSISOLatin1StringEncoding]; 461 name = [@"org.jwz.xscreensaver." stringByAppendingString:name]; 462 [self setResourcesEnv:name]; 463 [self loadCustomFonts]; 464 465 XrmOptionDescRec *opts = 0; 466 const char **defs = 0; 467 add_default_options (xsft->options, xsft->defaults, &opts, &defs); 468 prefsReader = [[PrefsReader alloc] 469 initWithName:name xrmKeys:opts defaults:defs]; 470 free (defs); 471 // free (opts); // bah, we need these! #### leak! 472 xsft->options = opts; 473 474 progname = progclass = xsft->progclass; 475 476 next_frame_time = 0; 477 478# if !defined USE_IPHONE && defined JWXYZ_QUARTZ 479 // When the view fills the screen and double buffering is enabled, OS X will 480 // use page flipping for a minor CPU/FPS boost. In windowed mode, double 481 // buffering reduces the frame rate to 1/2 the screen's refresh rate. 482 double_buffered_p = !isPreview; 483# endif 484 485# ifdef USE_IPHONE 486 [self initGestures]; 487 488 // So we can tell when we're docked. 489 [UIDevice currentDevice].batteryMonitoringEnabled = YES; 490 491 [self setBackgroundColor:[NSColor blackColor]]; 492# endif // USE_IPHONE 493 494# ifdef JWXYZ_QUARTZ 495 // Colorspaces and CGContexts only happen with non-GL hacks. 496 colorspace = CGColorSpaceCreateDeviceRGB (); 497# endif 498 499 return self; 500} 501 502 503#ifndef USE_IPHONE 504/* On 10.15, if "use random screen saver" is checked, then startAnimation 505 is never called. This may be related to Apple's buggy code in 506 ScreenSaverEngine calling nonexistent beginExtensionRequestWithUserInfo, 507 but on 10.15 we're not even running in that process: now we're in the 508 not-at-all-ominously-named legacyScreenSaver process. 509 */ 510- (void) viewDidMoveToWindow 511{ 512 if (self.window) 513 [self startAnimation]; 514} 515 516- (void) viewWillMoveToWindow:(NSWindow *)window 517{ 518 if (window == nil) 519 [self stopAnimation]; 520} 521#endif // USE_IPHONE 522 523 524#ifdef USE_TOUCHBAR 525- (id) initWithFrame:(NSRect)frame 526 saverName:(NSString *)saverName 527 isPreview:(BOOL)isPreview 528 isTouchbar:(BOOL)isTouchbar 529{ 530 if (! (self = [self initWithFrame:frame saverName:saverName 531 isPreview:isPreview])) 532 return 0; 533 touchbar_p = isTouchbar; 534 return self; 535} 536#endif // USE_TOUCHBAR 537 538 539#ifdef USE_IPHONE 540+ (Class) layerClass 541{ 542 return [CAEAGLLayer class]; 543} 544#endif 545 546 547- (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p 548{ 549 return [self initWithFrame:frame saverName:0 isPreview:p]; 550} 551 552 553- (void) dealloc 554{ 555 if ([self isAnimating]) 556 [self stopAnimation]; 557 NSAssert(!xdata, @"xdata not yet freed"); 558 NSAssert(!xdpy, @"xdpy not yet freed"); 559 560# ifdef USE_IPHONE 561 [[NSNotificationCenter defaultCenter] removeObserver:self]; 562# endif 563 564# ifdef BACKBUFFER_OPENGL 565# ifndef USE_IPHONE 566 [pixfmt release]; 567# endif // !USE_IPHONE 568 [ogl_ctx release]; 569 // Releasing the OpenGL context should also free any OpenGL objects, 570 // including the backbuffer texture and frame/render/depthbuffers. 571# endif // BACKBUFFER_OPENGL 572 573# if defined JWXYZ_GL && defined USE_IPHONE 574 [ogl_ctx_pixmap release]; 575# endif // JWXYZ_GL 576 577# ifdef JWXYZ_QUARTZ 578 if (colorspace) 579 CGColorSpaceRelease (colorspace); 580# endif // JWXYZ_QUARTZ 581 582 [prefsReader release]; 583 584 // xsft 585 // fpst 586 587 [super dealloc]; 588} 589 590- (PrefsReader *) prefsReader 591{ 592 return prefsReader; 593} 594 595 596#ifdef USE_IPHONE 597- (void) lockFocus { } 598- (void) unlockFocus { } 599#endif // USE_IPHONE 600 601 602 603# ifdef USE_IPHONE 604/* A few seconds after the saver launches, we store the "wasRunning" 605 preference. This is so that if the saver is crashing at startup, 606 we don't launch it again next time, getting stuck in a crash loop. 607 */ 608- (void) allSystemsGo: (NSTimer *) timer 609{ 610 NSAssert (timer == crash_timer, @"crash timer screwed up"); 611 crash_timer = 0; 612 613 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; 614 [prefs setBool:YES forKey:@"wasRunning"]; 615 [prefs synchronize]; 616} 617 618 619- (void) resizeGL 620{ 621 if (!ogl_ctx) 622 return; 623 624 CGSize screen_size = self.bounds.size; 625 double s = self.contentScaleFactor; 626 screen_size.width *= s; 627 screen_size.height *= s; 628 629#if defined JWXYZ_GL 630 GLuint *framebuffer = &xwindow->gl_framebuffer; 631 GLuint *renderbuffer = &xwindow->gl_renderbuffer; 632 xwindow->window.current_drawable = xwindow; 633#elif defined JWXYZ_QUARTZ 634 GLuint *framebuffer = &gl_framebuffer; 635 GLuint *renderbuffer = &gl_renderbuffer; 636#endif // JWXYZ_QUARTZ 637 638 if (*framebuffer) glDeleteFramebuffersOES (1, framebuffer); 639 if (*renderbuffer) glDeleteRenderbuffersOES (1, renderbuffer); 640 641 create_framebuffer (framebuffer, renderbuffer); 642 643 // redundant? 644 // glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, 645 // (int)size.width, (int)size.height); 646 [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES 647 fromDrawable:(CAEAGLLayer*)self.layer]; 648 649 glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, 650 GL_RENDERBUFFER_OES, *renderbuffer); 651 652 [self addExtraRenderbuffers:screen_size]; 653 654 check_framebuffer_status(); 655} 656#endif // USE_IPHONE 657 658 659- (void) startAnimation 660{ 661 if ([self isAnimating]) return; // macOS 10.15 stupidity 662 663 NSAssert(![self isAnimating], @"already animating"); 664 NSAssert(!initted_p && !xdata, @"already initialized"); 665 666 // See comment in render_x11() for why this value is important: 667 [self setAnimationTimeInterval: 1.0 / 240.0]; 668 669 [super startAnimation]; 670 /* We can't draw on the window from this method, so we actually do the 671 initialization of the screen saver (xsft->init_cb) in the first call 672 to animateOneFrame() instead. 673 */ 674 675# ifdef USE_IPHONE 676 if (crash_timer) 677 [crash_timer invalidate]; 678 679 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; 680 [prefs removeObjectForKey:@"wasRunning"]; 681 [prefs synchronize]; 682 683 crash_timer = [NSTimer scheduledTimerWithTimeInterval: 5 684 target:self 685 selector:@selector(allSystemsGo:) 686 userInfo:nil 687 repeats:NO]; 688 689# endif // USE_IPHONE 690 691 // Never automatically turn the screen off if we are docked, 692 // and an animation is running. 693 // 694# ifdef USE_IPHONE 695 [UIApplication sharedApplication].idleTimerDisabled = 696 ([UIDevice currentDevice].batteryState != UIDeviceBatteryStateUnplugged); 697# endif 698 699 xwindow = (Window) calloc (1, sizeof(*xwindow)); 700 xwindow->type = WINDOW; 701 xwindow->window.view = self; 702 CFRetain (xwindow->window.view); // needed for garbage collection? 703 704#ifdef BACKBUFFER_OPENGL 705 CGSize new_backbuffer_size; 706 707 { 708# ifndef USE_IPHONE 709 if (!ogl_ctx) { 710 711 pixfmt = [self getGLPixelFormat]; 712 [pixfmt retain]; 713 714 NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat"); 715 716 // Fun: On OS X 10.7, the second time an OpenGL context is created, after 717 // the preferences dialog is launched in SaverTester, the context only 718 // lasts until the first full GC. Then it turns black. Solution is to 719 // reuse the OpenGL context after this point. 720 // "Analyze" says that both pixfmt and ogl_ctx are leaked. 721 ogl_ctx = [[NSOpenGLContext alloc] initWithFormat:pixfmt 722 shareContext:nil]; 723 724 // Sync refreshes to the vertical blanking interval 725 GLint r = 1; 726 [ogl_ctx setValues:&r forParameter:NSOpenGLCPSwapInterval]; 727// check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early? 728 } 729 730 [ogl_ctx makeCurrentContext]; 731 check_gl_error ("makeCurrentContext"); 732 733 // NSOpenGLContext logs an 'invalid drawable' when this is called 734 // from initWithFrame. 735 [ogl_ctx setView:self]; 736 737 // Get device pixels instead of points. 738 self.wantsBestResolutionOpenGLSurface = YES; 739 740 // This may not be necessary if there's FBO support. 741# ifdef JWXYZ_GL 742 xwindow->window.pixfmt = pixfmt; 743 CFRetain (xwindow->window.pixfmt); 744 xwindow->window.virtual_screen = [ogl_ctx currentVirtualScreen]; 745 xwindow->window.current_drawable = xwindow; 746 NSAssert (ogl_ctx, @"no CGContext"); 747# endif 748 749 // Clear frame buffer ASAP, else there are bits left over from other apps. 750 glClearColor (0, 0, 0, 1); 751 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 752// glFinish (); 753// glXSwapBuffers (mi->dpy, mi->window); 754 755 756 // Enable multi-threading, if possible. This runs most OpenGL commands 757 // and GPU management on a second CPU. 758 { 759# ifndef kCGLCEMPEngine 760# define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4. 761# endif 762 CGLContextObj cctx = CGLGetCurrentContext(); 763 CGLError err = CGLEnable (cctx, kCGLCEMPEngine); 764 if (err != kCGLNoError) { 765 NSLog (@"enabling multi-threaded OpenGL failed: %d", err); 766 } 767 } 768 769 new_backbuffer_size = NSSizeToCGSize ([self bounds].size); 770 771 // Scale factor for desktop retina displays 772 double s = [self hackedContentScaleFactor]; 773 new_backbuffer_size.width *= s; 774 new_backbuffer_size.height *= s; 775 776# else // USE_IPHONE 777 if (!ogl_ctx) { 778 CAEAGLLayer *eagl_layer = (CAEAGLLayer *) self.layer; 779 eagl_layer.opaque = TRUE; 780 eagl_layer.drawableProperties = [self getGLProperties]; 781 782 // Without this, the GL frame buffer is half the screen resolution! 783 eagl_layer.contentsScale = [UIScreen mainScreen].scale; 784 785 ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; 786# ifdef JWXYZ_GL 787 ogl_ctx_pixmap = [[EAGLContext alloc] 788 initWithAPI:kEAGLRenderingAPIOpenGLES1 789 sharegroup:ogl_ctx.sharegroup]; 790# endif // JWXYZ_GL 791 792 eagl_layer.contentsGravity = [self getCAGravity]; 793 } 794 795# ifdef JWXYZ_GL 796 xwindow->window.ogl_ctx_pixmap = ogl_ctx_pixmap; 797# endif // JWXYZ_GL 798 799 [EAGLContext setCurrentContext: ogl_ctx]; 800 801 [self resizeGL]; 802 803 double s = [self hackedContentScaleFactor]; 804 new_backbuffer_size = self.bounds.size; 805 new_backbuffer_size.width *= s; 806 new_backbuffer_size.height *= s; 807 808# endif // USE_IPHONE 809 810# ifdef JWXYZ_GL 811 xwindow->ogl_ctx = ogl_ctx; 812# ifndef USE_IPHONE 813 CFRetain (xwindow->ogl_ctx); 814# endif // USE_IPHONE 815# endif // JWXYZ_GL 816 817 check_gl_error ("startAnimation"); 818 819// NSLog (@"%s / %s / %s\n", glGetString (GL_VENDOR), 820// glGetString (GL_RENDERER), glGetString (GL_VERSION)); 821 822 [self enableBackbuffer:new_backbuffer_size]; 823 } 824#endif // BACKBUFFER_OPENGL 825 826 [self setViewport]; 827 [self createBackbuffer:new_backbuffer_size]; 828 829# ifdef USE_TOUCHBAR 830 if (touchbar_view) [touchbar_view startAnimation]; 831# endif // USE_TOUCHBAR 832} 833 834- (void)stopAnimation 835{ 836 NSAssert([self isAnimating], @"not animating"); 837 838 if (initted_p) { 839 840 [self lockFocus]; // in case something tries to draw from here 841 [self prepareContext]; 842 843 /* All of the xlockmore hacks need to have their release functions 844 called, or launching the same saver twice does not work. Also 845 webcollage-cocoa needs it in order to kill the inferior webcollage 846 processes (since the screen saver framework never generates a 847 SIGPIPE for them). 848 */ 849 if (xdata) 850 xsft->free_cb (xdpy, xwindow, xdata); 851 [self unlockFocus]; 852 853 jwxyz_quartz_free_display (xdpy); 854 xdpy = NULL; 855# if defined JWXYZ_GL && !defined USE_IPHONE 856 CFRelease (xwindow->ogl_ctx); 857# endif 858 CFRelease (xwindow->window.view); 859 free (xwindow); 860 xwindow = NULL; 861 862// setup_p = NO; // #### wait, do we need this? 863 initted_p = NO; 864 xdata = 0; 865 } 866 867# ifdef USE_IPHONE 868 if (crash_timer) 869 [crash_timer invalidate]; 870 crash_timer = 0; 871 NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; 872 [prefs removeObjectForKey:@"wasRunning"]; 873 [prefs synchronize]; 874# endif // USE_IPHONE 875 876 [super stopAnimation]; 877 878 // When an animation is no longer running (e.g., looking at the list) 879 // then it's ok to power off the screen when docked. 880 // 881# ifdef USE_IPHONE 882 [UIApplication sharedApplication].idleTimerDisabled = NO; 883# endif 884 885 // Without this, the GL frame stays on screen when switching tabs 886 // in System Preferences. 887 // (Or perhaps it used to. It doesn't seem to matter on 10.9.) 888 // 889# ifndef USE_IPHONE 890 [NSOpenGLContext clearCurrentContext]; 891# endif // !USE_IPHONE 892 893 clear_gl_error(); // This hack is defunct, don't let this linger. 894 895# ifdef JWXYZ_QUARTZ 896 CGContextRelease (backbuffer); 897 backbuffer = nil; 898 899 if (backbuffer_len) 900 munmap (backbuffer_data, backbuffer_len); 901 backbuffer_data = NULL; 902 backbuffer_len = 0; 903# endif 904 905# ifdef USE_TOUCHBAR 906 if (touchbar_view) { 907 [touchbar_view stopAnimation]; 908 [touchbar_view release]; 909 touchbar_view = nil; 910 } 911# endif 912} 913 914 915- (NSOpenGLContext *) oglContext 916{ 917 return ogl_ctx; 918} 919 920 921// #### maybe this could/should just be on 'lockFocus' instead? 922- (void) prepareContext 923{ 924 if (xwindow) { 925#ifdef USE_IPHONE 926 [EAGLContext setCurrentContext:ogl_ctx]; 927#else // !USE_IPHONE 928 [ogl_ctx makeCurrentContext]; 929// check_gl_error ("makeCurrentContext"); 930#endif // !USE_IPHONE 931 932#ifdef JWXYZ_GL 933 xwindow->window.current_drawable = xwindow; 934#endif 935 } 936} 937 938 939#ifdef USE_TOUCHBAR 940 941static NSString *touchbar_cid = @"org.jwz.xscreensaver.touchbar"; 942static NSString *touchbar_iid = @"org.jwz.xscreensaver.touchbar"; 943 944- (NSTouchBar *) makeTouchBar 945{ 946 NSTouchBar *t = [[NSTouchBar alloc] init]; 947 t.delegate = self; 948 t.customizationIdentifier = touchbar_cid; 949 t.defaultItemIdentifiers = @[touchbar_iid, 950 NSTouchBarItemIdentifierOtherItemsProxy]; 951 t.customizationAllowedItemIdentifiers = @[touchbar_iid]; 952 t.principalItemIdentifier = touchbar_iid; 953 return t; 954} 955 956- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar 957 makeItemForIdentifier:(NSTouchBarItemIdentifier)id 958{ 959 if ([id isEqualToString:touchbar_iid]) 960 { 961 NSRect rect = [self frame]; 962 // #### debugging 963 rect.origin.x = 0; 964 rect.origin.y = 0; 965 rect.size.width = 200; 966 rect.size.height = 40; 967 touchbar_view = [[[self class] alloc] 968 initWithFrame:rect 969 saverName:[NSString stringWithCString:xsft->progclass 970 encoding:NSISOLatin1StringEncoding] 971 isPreview:self.isPreview 972 isTouchbar:True]; 973 [touchbar_view setAutoresizingMask: 974 NSViewWidthSizable|NSViewHeightSizable]; 975 NSCustomTouchBarItem *item = 976 [[NSCustomTouchBarItem alloc] initWithIdentifier:id]; 977 item.view = touchbar_view; 978 item.customizationLabel = touchbar_cid; 979 980 if ([self isAnimating]) 981 // TouchBar was created after animation begun. 982 [touchbar_view startAnimation]; 983 } 984 return nil; 985} 986 987#endif // USE_TOUCHBAR 988 989 990static void 991screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) 992{ 993 fps_compute (fpst, 0, -1); 994 fps_draw (fpst); 995} 996 997 998/* Some of the older X11 savers look bad if a "pixel" is not a thing you can 999 see. They expect big, chunky, luxurious 1990s pixels, and if they use 1000 "device" pixels on a Retina screen, everything just disappears. 1001 1002 Retina iPads have 768x1024 point screens which are 1536x2048 pixels, 1003 2017 iMac screens are 5120x2880 in device pixels. 1004 1005 This method is overridden in XScreenSaverGLView, since this kludge 1006 isn't necessary for GL programs, being resolution independent by 1007 nature. 1008 */ 1009- (CGFloat) hackedContentScaleFactor 1010{ 1011 return [self hackedContentScaleFactor:FALSE]; 1012} 1013 1014- (CGFloat) hackedContentScaleFactor:(BOOL)fonts_p 1015{ 1016# ifdef USE_IPHONE 1017 CGFloat s = self.contentScaleFactor; 1018# else 1019 CGFloat s = self.window.backingScaleFactor; 1020# endif 1021 1022 /* This notion of "scale fonts differently than the viewport seemed 1023 like it made sense for BSOD but it makes -fps text be stupidly 1024 large for all other hacks. So instead let's just make BSOD not 1025 be lowrez. There are no other lowrez hacks that make heavy use 1026 of fonts. */ 1027 fonts_p = 0; 1028 1029 if (_lowrez_p && !fonts_p) { 1030 NSSize b = [self bounds].size; // This is in points, not pixels 1031 CGFloat wh = b.width > b.height ? b.width : b.height; 1032 wh *= s; // points -> pixels 1033 1034 // Scale down to as close to 1024 as we can get without going under, 1035 // while keeping an integral scale factor so that we don't get banding 1036 // artifacts and moire patterns. 1037 // 1038 // Retina sizes: 2208 => 1104, 2224 => 1112, 2732 => 1366, 2880 => 1440. 1039 // 1040 int s2 = wh / 1024; 1041 if (s2) s /= s2; 1042 } 1043 1044 return s; 1045} 1046 1047 1048#ifdef USE_IPHONE 1049 1050double 1051current_device_rotation (void) 1052{ 1053 UIDeviceOrientation o = [[UIDevice currentDevice] orientation]; 1054 1055 /* Sometimes UIDevice doesn't know the proper orientation, or the device is 1056 face up/face down, so in those cases fall back to the status bar 1057 orientation. The SaverViewController tries to set the status bar to the 1058 proper orientation before it creates the XScreenSaverView; see 1059 _storedOrientation in SaverViewController. 1060 */ 1061 if (o == UIDeviceOrientationUnknown || 1062 o == UIDeviceOrientationFaceUp || 1063 o == UIDeviceOrientationFaceDown) { 1064 /* Mind the differences between UIInterfaceOrientation and 1065 UIDeviceOrientation: 1066 1. UIInterfaceOrientation does not include FaceUp and FaceDown. 1067 2. LandscapeLeft and LandscapeRight are swapped between the two. But 1068 converting between device and interface orientation doesn't need to 1069 take this into account, because (from the UIInterfaceOrientation 1070 description): "rotating the device requires rotating the content in 1071 the opposite direction." 1072 */ 1073 /* statusBarOrientation deprecated in iOS 9 */ 1074 o = (UIDeviceOrientation) // from UIInterfaceOrientation 1075 [UIApplication sharedApplication].statusBarOrientation; 1076 } 1077 1078 switch (o) { 1079 case UIDeviceOrientationLandscapeLeft: return -90; break; 1080 case UIDeviceOrientationLandscapeRight: return 90; break; 1081 case UIDeviceOrientationPortraitUpsideDown: return 180; break; 1082 default: return 0; break; 1083 } 1084} 1085 1086 1087- (void) handleException: (NSException *)e 1088{ 1089 NSLog (@"Caught exception: %@", e); 1090 UIAlertController *c = [UIAlertController 1091 alertControllerWithTitle: 1092 [NSString stringWithFormat: @"%s crashed!", 1093 xsft->progclass] 1094 message: [NSString stringWithFormat: 1095 @"The error message was:" 1096 "\n\n%@\n\n" 1097 "If it keeps crashing, try " 1098 "resetting its options.", 1099 e] 1100 preferredStyle:UIAlertControllerStyleAlert]; 1101 1102 [c addAction: [UIAlertAction actionWithTitle: 1103 NSLocalizedString(@"Exit", @"") 1104 style: UIAlertActionStyleDefault 1105 handler: ^(UIAlertAction *a) { 1106 exit (-1); 1107 }]]; 1108 [c addAction: [UIAlertAction actionWithTitle: 1109 NSLocalizedString(@"Keep going", @"") 1110 style: UIAlertActionStyleDefault 1111 handler: ^(UIAlertAction *a) { 1112 [self stopAndClose:NO]; 1113 }]]; 1114 1115 UIViewController *vc = 1116 [UIApplication sharedApplication].keyWindow.rootViewController; 1117 while (vc.presentedViewController) 1118 vc = vc.presentedViewController; 1119 [vc presentViewController:c animated:YES completion:nil]; 1120 [self stopAnimation]; 1121} 1122 1123#endif // USE_IPHONE 1124 1125 1126#ifdef JWXYZ_QUARTZ 1127 1128# ifndef USE_IPHONE 1129 1130struct gl_version 1131{ 1132 // iOS always uses OpenGL ES 1.1. 1133 unsigned major; 1134 unsigned minor; 1135}; 1136 1137static GLboolean 1138gl_check_ver (const struct gl_version *caps, 1139 unsigned gl_major, 1140 unsigned gl_minor) 1141{ 1142 return caps->major > gl_major || 1143 (caps->major == gl_major && caps->minor >= gl_minor); 1144} 1145 1146# endif 1147 1148/* Called during startAnimation before the first call to createBackbuffer. */ 1149- (void) enableBackbuffer:(CGSize)new_backbuffer_size 1150{ 1151# ifndef USE_IPHONE 1152 struct gl_version version; 1153 1154 { 1155 const char *version_str = (const char *)glGetString (GL_VERSION); 1156 1157 /* iPhone is always OpenGL ES 1.1. */ 1158 if (sscanf ((const char *)version_str, "%u.%u", 1159 &version.major, &version.minor) < 2) 1160 { 1161 version.major = 1; 1162 version.minor = 1; 1163 } 1164 } 1165# endif 1166 1167 // The OpenGL extensions in use in here are pretty are pretty much ubiquitous 1168 // on OS X, but it's still good form to check. 1169 const GLubyte *extensions = glGetString (GL_EXTENSIONS); 1170 1171 glGenTextures (1, &backbuffer_texture); 1172 1173 // On really old systems, it would make sense to split the texture 1174 // into subsections 1175# ifndef USE_IPHONE 1176 gl_texture_target = (gluCheckExtension ((const GLubyte *) 1177 "GL_ARB_texture_rectangle", 1178 extensions) 1179 ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D); 1180# else 1181 // OES_texture_npot also provides this, but iOS never provides it. 1182 gl_limited_npot_p = jwzgles_gluCheckExtension 1183 ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions); 1184 gl_texture_target = GL_TEXTURE_2D; 1185# endif 1186 1187 glBindTexture (gl_texture_target, backbuffer_texture); 1188 glTexParameteri (gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 1189 // GL_LINEAR might make sense on Retina iPads. 1190 glTexParameteri (gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 1191 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 1192 glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 1193 1194# ifndef USE_IPHONE 1195 // There isn't much sense in supporting one of these if the other 1196 // isn't present. 1197 gl_apple_client_storage_p = 1198 gluCheckExtension ((const GLubyte *)"GL_APPLE_client_storage", 1199 extensions) && 1200 gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_range", extensions); 1201 1202 if (gl_apple_client_storage_p) { 1203 glTexParameteri (gl_texture_target, GL_TEXTURE_STORAGE_HINT_APPLE, 1204 GL_STORAGE_SHARED_APPLE); 1205 glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE); 1206 } 1207# endif 1208 1209 // If a video adapter suports BGRA textures, then that's probably as fast as 1210 // you're gonna get for getting a texture onto the screen. 1211# ifdef USE_IPHONE 1212 gl_pixel_format = 1213 jwzgles_gluCheckExtension 1214 ((const GLubyte *)"GL_APPLE_texture_format_BGRA8888", extensions) ? 1215 GL_BGRA : 1216 GL_RGBA; 1217 1218 gl_pixel_type = GL_UNSIGNED_BYTE; 1219 // See also OES_read_format. 1220# else 1221 if (gl_check_ver (&version, 1, 2) || 1222 (gluCheckExtension ((const GLubyte *)"GL_EXT_bgra", extensions) && 1223 gluCheckExtension ((const GLubyte *)"GL_APPLE_packed_pixels", 1224 extensions))) { 1225 gl_pixel_format = GL_BGRA; 1226 // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV. 1227 gl_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV; 1228 } else { 1229 gl_pixel_format = GL_RGBA; 1230 gl_pixel_type = GL_UNSIGNED_BYTE; 1231 } 1232 // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more 1233 // sense on PowerPC. 1234# endif 1235 1236 glEnable (gl_texture_target); 1237 glEnableClientState (GL_VERTEX_ARRAY); 1238 glEnableClientState (GL_TEXTURE_COORD_ARRAY); 1239 1240 check_gl_error ("enableBackbuffer"); 1241} 1242 1243 1244#ifdef USE_IPHONE 1245- (BOOL) suppressRotationAnimation 1246{ 1247 return [self ignoreRotation]; // Don't animate if we aren't rotating 1248} 1249 1250- (BOOL) rotateTouches 1251{ 1252 return FALSE; // Adjust event coordinates only if rotating 1253} 1254#endif 1255 1256 1257- (void) setViewport 1258{ 1259# ifdef BACKBUFFER_OPENGL 1260 NSAssert ([NSOpenGLContext currentContext] == 1261 ogl_ctx, @"invalid GL context"); 1262 1263 NSSize new_size = self.bounds.size; 1264 1265# ifdef USE_IPHONE 1266 GLfloat s = self.contentScaleFactor; 1267# else // !USE_IPHONE 1268 const GLfloat s = self.window.backingScaleFactor; 1269# endif 1270 GLfloat hs = self.hackedContentScaleFactor; 1271 1272 // On OS X this almost isn't necessary, except for the ugly aliasing 1273 // artifacts. 1274 glViewport (0, 0, new_size.width * s, new_size.height * s); 1275 1276 glMatrixMode (GL_PROJECTION); 1277 glLoadIdentity(); 1278# ifdef USE_IPHONE 1279 glOrthof 1280# else 1281 glOrtho 1282# endif 1283 (-new_size.width * hs, new_size.width * hs, 1284 -new_size.height * hs, new_size.height * hs, 1285 -1, 1); 1286 1287# ifdef USE_IPHONE 1288 if ([self ignoreRotation]) { 1289 int o = (int) -current_device_rotation(); 1290 glRotatef (o, 0, 0, 1); 1291 } 1292# endif // USE_IPHONE 1293# endif // BACKBUFFER_OPENGL 1294} 1295 1296 1297/* Create a bitmap context into which we render everything. 1298 If the desired size has changed, re-created it. 1299 new_size is in rotated pixels, not points: the same size 1300 and shape as the X11 window as seen by the hacks. 1301 */ 1302- (void) createBackbuffer:(CGSize)new_size 1303{ 1304 CGSize osize = CGSizeZero; 1305 if (backbuffer) { 1306 osize.width = CGBitmapContextGetWidth(backbuffer); 1307 osize.height = CGBitmapContextGetHeight(backbuffer); 1308 } 1309 1310 if (backbuffer && 1311 (int)osize.width == (int)new_size.width && 1312 (int)osize.height == (int)new_size.height) 1313 return; 1314 1315 CGContextRef ob = backbuffer; 1316 void *odata = backbuffer_data; 1317 GLsizei olen = backbuffer_len; 1318 1319# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR 1320 NSLog(@"backbuffer %.0fx%.0f", 1321 new_size.width, new_size.height); 1322# endif 1323 1324 /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in 1325 <https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html>. 1326 1327 iOS uses bog-standard glTexImage2D (for now). 1328 1329 glMapBuffer is the standard way to get data from system RAM to video 1330 memory asynchronously and without a memcpy, but support for 1331 APPLE_client_storage is ubiquitous on OS X (not so for glMapBuffer), 1332 and on iOS GL_PIXEL_UNPACK_BUFFER is only available on OpenGL ES 3 1333 (iPhone 5S or newer). Plus, glMapBuffer doesn't work well with 1334 CGBitmapContext: glMapBuffer can return a different pointer on each 1335 call, but a CGBitmapContext doesn't allow its data pointer to be 1336 changed -- and recreating the context for a new pointer can be 1337 expensive (glyph caches get dumped, for instance). 1338 1339 glMapBufferRange has MAP_FLUSH_EXPLICIT_BIT and MAP_UNSYNCHRONIZED_BIT, 1340 and these seem to allow mapping the buffer and leaving it where it is 1341 in client address space while OpenGL works with the buffer, but it 1342 requires OpenGL 3 Core profile on OS X (and ES 3 on iOS for 1343 GL_PIXEL_UNPACK_BUFFER), so point goes to APPLE_client_storage. 1344 1345 AMD_pinned_buffer provides the same advantage as glMapBufferRange, but 1346 Apple never implemented that one for OS X. 1347 */ 1348 1349 backbuffer_data = NULL; 1350 gl_texture_w = (int)new_size.width; 1351 gl_texture_h = (int)new_size.height; 1352 1353 NSAssert (gl_texture_target == GL_TEXTURE_2D 1354# ifndef USE_IPHONE 1355 || gl_texture_target == GL_TEXTURE_RECTANGLE_EXT 1356# endif 1357 , @"unexpected GL texture target"); 1358 1359# ifndef USE_IPHONE 1360 if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT) 1361# else 1362 if (!gl_limited_npot_p) 1363# endif 1364 { 1365 gl_texture_w = (GLsizei) to_pow2 (gl_texture_w); 1366 gl_texture_h = (GLsizei) to_pow2 (gl_texture_h); 1367 } 1368 1369 GLsizei bytes_per_row = gl_texture_w * 4; 1370 1371# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE) 1372 // APPLE_client_storage requires texture width to be aligned to 32 bytes, or 1373 // it will fall back to a memcpy. 1374 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW24 1375 bytes_per_row = (bytes_per_row + 31) & ~31; 1376# endif // BACKBUFFER_OPENGL && !USE_IPHONE 1377 1378 backbuffer_len = bytes_per_row * gl_texture_h; 1379 if (backbuffer_len) // mmap requires this to be non-zero. 1380 backbuffer_data = mmap (NULL, backbuffer_len, 1381 PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, 1382 -1, 0); 1383 1384 BOOL alpha_first_p, order_little_p; 1385 1386 if (gl_pixel_format == GL_BGRA) { 1387 alpha_first_p = YES; 1388 order_little_p = YES; 1389/* 1390 } else if (gl_pixel_format == GL_ABGR_EXT) { 1391 alpha_first_p = NO; 1392 order_little_p = YES; */ 1393 } else { 1394 NSAssert (gl_pixel_format == GL_RGBA, @"unknown GL pixel format"); 1395 alpha_first_p = NO; 1396 order_little_p = NO; 1397 } 1398 1399#ifdef USE_IPHONE 1400 NSAssert (gl_pixel_type == GL_UNSIGNED_BYTE, @"unknown GL pixel type"); 1401#else 1402 NSAssert (gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8 || 1403 gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV || 1404 gl_pixel_type == GL_UNSIGNED_BYTE, 1405 @"unknown GL pixel type"); 1406 1407#if defined __LITTLE_ENDIAN__ 1408 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8; 1409#elif defined __BIG_ENDIAN__ 1410 const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV; 1411#else 1412# error Unknown byte order. 1413#endif 1414 1415 if (gl_pixel_type == backwards_pixel_type) 1416 order_little_p ^= YES; 1417#endif 1418 1419 CGBitmapInfo bitmap_info = 1420 (alpha_first_p ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaNoneSkipLast) | 1421 (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big); 1422 1423 backbuffer = CGBitmapContextCreate (backbuffer_data, 1424 (int)new_size.width, 1425 (int)new_size.height, 1426 8, 1427 bytes_per_row, 1428 colorspace, 1429 bitmap_info); 1430 NSAssert (backbuffer, @"unable to allocate back buffer"); 1431 1432 // Clear it. 1433 CGRect r; 1434 r.origin.x = r.origin.y = 0; 1435 r.size = new_size; 1436 CGContextSetGrayFillColor (backbuffer, 0, 1); 1437 CGContextFillRect (backbuffer, r); 1438 1439# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE) 1440 if (gl_apple_client_storage_p) 1441 glTextureRangeAPPLE (gl_texture_target, backbuffer_len, backbuffer_data); 1442# endif // BACKBUFFER_OPENGL && !USE_IPHONE 1443 1444 if (ob) { 1445 // Restore old bits, as much as possible, to the X11 upper left origin. 1446 1447 CGRect rect; // pixels, not points 1448 rect.origin.x = 0; 1449 rect.origin.y = (new_size.height - osize.height); 1450 rect.size = osize; 1451 1452 CGImageRef img = CGBitmapContextCreateImage (ob); 1453 CGContextDrawImage (backbuffer, rect, img); 1454 CGImageRelease (img); 1455 CGContextRelease (ob); 1456 1457 if (olen) 1458 // munmap should round len up to the nearest page. 1459 munmap (odata, olen); 1460 } 1461 1462 check_gl_error ("createBackbuffer"); 1463} 1464 1465 1466- (void) drawBackbuffer 1467{ 1468# ifdef BACKBUFFER_OPENGL 1469 1470 NSAssert ([ogl_ctx isKindOfClass:[NSOpenGLContext class]], 1471 @"ogl_ctx is not an NSOpenGLContext"); 1472 1473 NSAssert (! (CGBitmapContextGetBytesPerRow (backbuffer) % 4), 1474 @"improperly-aligned backbuffer"); 1475 1476 // This gets width and height from the backbuffer in case 1477 // APPLE_client_storage is in use. See the note in createBackbuffer. 1478 // This still has to happen every frame even when APPLE_client_storage has 1479 // the video adapter pulling texture data straight from 1480 // XScreenSaverView-owned memory. 1481 glTexImage2D (gl_texture_target, 0, GL_RGBA, 1482 (GLsizei)(CGBitmapContextGetBytesPerRow (backbuffer) / 4), 1483 gl_texture_h, 0, gl_pixel_format, gl_pixel_type, 1484 backbuffer_data); 1485 1486 GLfloat w = xwindow->frame.width, h = xwindow->frame.height; 1487 1488 GLfloat vertices[4][2] = {{-w, h}, {w, h}, {w, -h}, {-w, -h}}; 1489 1490 GLfloat tex_coords[4][2]; 1491 1492# ifndef USE_IPHONE 1493 if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT) 1494# endif // USE_IPHONE 1495 { 1496 w /= gl_texture_w; 1497 h /= gl_texture_h; 1498 } 1499 1500 tex_coords[0][0] = 0; 1501 tex_coords[0][1] = 0; 1502 tex_coords[1][0] = w; 1503 tex_coords[1][1] = 0; 1504 tex_coords[2][0] = w; 1505 tex_coords[2][1] = h; 1506 tex_coords[3][0] = 0; 1507 tex_coords[3][1] = h; 1508 1509 glVertexPointer (2, GL_FLOAT, 0, vertices); 1510 glTexCoordPointer (2, GL_FLOAT, 0, tex_coords); 1511 glDrawArrays (GL_TRIANGLE_FAN, 0, 4); 1512 1513# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR 1514 check_gl_error ("drawBackbuffer"); 1515# endif 1516# endif // BACKBUFFER_OPENGL 1517} 1518 1519#endif // JWXYZ_QUARTZ 1520 1521#ifdef JWXYZ_GL 1522 1523- (void)enableBackbuffer:(CGSize)new_backbuffer_size; 1524{ 1525 jwxyz_set_matrices (new_backbuffer_size.width, new_backbuffer_size.height); 1526 check_gl_error ("enableBackbuffer"); 1527} 1528 1529- (void)createBackbuffer:(CGSize)new_size 1530{ 1531 NSAssert ([NSOpenGLContext currentContext] == 1532 ogl_ctx, @"invalid GL context"); 1533 NSAssert (xwindow->window.current_drawable == xwindow, 1534 @"current_drawable not set properly"); 1535 1536# ifndef USE_IPHONE 1537 /* On iOS, Retina means glViewport gets called with the screen size instead 1538 of the backbuffer/xwindow size. This happens in startAnimation. 1539 1540 The GL screenhacks call glViewport themselves. 1541 */ 1542 glViewport (0, 0, new_size.width, new_size.height); 1543# endif 1544 1545 // TODO: Preserve contents on resize. 1546 glClear (GL_COLOR_BUFFER_BIT); 1547 check_gl_error ("createBackbuffer"); 1548} 1549 1550#endif // JWXYZ_GL 1551 1552 1553- (void)flushBackbuffer 1554{ 1555# ifdef JWXYZ_GL 1556 // Make sure the right context is active: there's two under JWXYZ_GL. 1557 jwxyz_bind_drawable (xwindow, xwindow); 1558# endif // JWXYZ_GL 1559 1560# ifndef USE_IPHONE 1561 1562# ifdef JWXYZ_QUARTZ 1563 // The OpenGL pipeline is not automatically synchronized with the contents 1564 // of the backbuffer, so without glFinish, OpenGL can start rendering from 1565 // the backbuffer texture at the same time that JWXYZ is clearing and 1566 // drawing the next frame in the backing store for the backbuffer texture. 1567 // This is only a concern under JWXYZ_QUARTZ because of 1568 // APPLE_client_storage; JWXYZ_GL doesn't use that. 1569 glFinish(); 1570# endif // JWXYZ_QUARTZ 1571 1572 // If JWXYZ_GL was single-buffered, there would need to be a glFinish (or 1573 // maybe just glFlush?) here, because single-buffered contexts don't always 1574 // update what's on the screen after drawing finishes. (i.e., in safe mode) 1575 1576# ifdef JWXYZ_QUARTZ 1577 // JWXYZ_GL is always double-buffered. 1578 if (double_buffered_p) 1579# endif // JWXYZ_QUARTZ 1580 [ogl_ctx flushBuffer]; // despite name, this actually swaps 1581# else // USE_IPHONE 1582 1583 // jwxyz_bind_drawable() only binds the framebuffer, not the renderbuffer. 1584# ifdef JWXYZ_GL 1585 GLint gl_renderbuffer = xwindow->gl_renderbuffer; 1586# endif 1587 1588 glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer); 1589 [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES]; 1590# endif // USE_IPHONE 1591 1592# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR 1593 // glGetError waits for the OpenGL command pipe to flush, so skip it in 1594 // release builds. 1595 // OpenGL Programming Guide for Mac -> OpenGL Application Design 1596 // Strategies -> Allow OpenGL to Manage Your Resources 1597 // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_designstrategies/opengl_designstrategies.html#//apple_ref/doc/uid/TP40001987-CH2-SW7 1598 check_gl_error ("flushBackbuffer"); 1599# endif 1600} 1601 1602 1603/* Inform X11 that the size of our window has changed. 1604 */ 1605- (void) resize_x11 1606{ 1607 if (!xdpy) return; // early 1608 1609 NSSize new_size; // pixels, not points 1610 1611 new_size = self.bounds.size; 1612 1613# ifdef USE_IPHONE 1614 1615 // If this hack ignores rotation, then that means that it pretends to 1616 // always be in portrait mode. If the View has been resized to a 1617 // landscape shape, swap width and height to keep the backbuffer 1618 // in portrait. 1619 // 1620 double rot = current_device_rotation(); 1621 if ([self ignoreRotation] && (rot == 90 || rot == -90)) { 1622 CGFloat swap = new_size.width; 1623 new_size.width = new_size.height; 1624 new_size.height = swap; 1625 } 1626# endif // USE_IPHONE 1627 1628 double s = self.hackedContentScaleFactor; 1629 new_size.width *= s; 1630 new_size.height *= s; 1631 1632 [self prepareContext]; 1633 [self setViewport]; 1634 1635 // On first resize, xwindow->frame is 0x0. 1636 if (xwindow->frame.width == new_size.width && 1637 xwindow->frame.height == new_size.height) 1638 return; 1639 1640# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE) 1641 [ogl_ctx update]; 1642# endif // BACKBUFFER_OPENGL && !USE_IPHONE 1643 1644 NSAssert (xwindow && xwindow->type == WINDOW, @"not a window"); 1645 xwindow->frame.x = 0; 1646 xwindow->frame.y = 0; 1647 xwindow->frame.width = new_size.width; 1648 xwindow->frame.height = new_size.height; 1649 1650 [self createBackbuffer:CGSizeMake(xwindow->frame.width, 1651 xwindow->frame.height)]; 1652 1653# if defined JWXYZ_QUARTZ 1654 xwindow->cgc = backbuffer; 1655 NSAssert (xwindow->cgc, @"no CGContext"); 1656# elif defined JWXYZ_GL && !defined USE_IPHONE 1657 [ogl_ctx update]; 1658 [ogl_ctx setView:xwindow->window.view]; // (Is this necessary?) 1659# endif // JWXYZ_GL && USE_IPHONE 1660 1661 jwxyz_window_resized (xdpy); 1662 1663# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR 1664 NSLog(@"reshape %.0fx%.0f", new_size.width, new_size.height); 1665# endif 1666 1667 // Next time render_x11 is called, run the saver's reshape_cb. 1668 resized_p = YES; 1669} 1670 1671 1672#ifdef USE_IPHONE 1673 1674/* Called by SaverRunner when the device has changed orientation. 1675 That means we need to generate a resize event, even if the size 1676 has not changed (e.g., from LandscapeLeft to LandscapeRight). 1677 */ 1678- (void) orientationChanged 1679{ 1680 [self setViewport]; 1681 resized_p = YES; 1682 next_frame_time = 0; // Get a new frame on screen quickly 1683} 1684 1685/* A hook run after the 'reshape_' method has been called. Used by 1686 XScreenSaverGLView to adjust the in-scene GL viewport. 1687 */ 1688- (void) postReshape 1689{ 1690} 1691#endif // USE_IPHONE 1692 1693 1694// Only render_x11 should call this. XScreenSaverGLView specializes it. 1695- (void) reshape_x11 1696{ 1697 xsft->reshape_cb (xdpy, xwindow, xdata, 1698 xwindow->frame.width, xwindow->frame.height); 1699} 1700 1701- (void) render_x11 1702{ 1703# ifdef USE_IPHONE 1704 @try { 1705# endif 1706 1707 // jwxyz_make_display needs this. 1708 [self prepareContext]; // resize_x11 also calls this. 1709 1710 if (!initted_p) { 1711 1712 resized_p = NO; 1713 1714 if (! xdpy) { 1715# ifdef JWXYZ_QUARTZ 1716 xwindow->cgc = backbuffer; 1717# endif // JWXYZ_QUARTZ 1718 xdpy = jwxyz_quartz_make_display (xwindow); 1719 1720# if defined USE_IPHONE 1721 /* Some X11 hacks (fluidballs) want to ignore all rotation events. */ 1722 _ignoreRotation = 1723# ifdef JWXYZ_GL 1724 TRUE; // Rotation doesn't work yet. TODO: Make rotation work. 1725# else // !JWXYZ_GL 1726 get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation"); 1727# endif // !JWXYZ_GL 1728# endif // USE_IPHONE 1729 1730 _lowrez_p = get_boolean_resource (xdpy, "lowrez", "Lowrez"); 1731 if (_lowrez_p) { 1732 resized_p = YES; 1733 1734 NSSize b = [self bounds].size; 1735 CGFloat s = self.hackedContentScaleFactor; 1736# ifdef USE_IPHONE 1737 CGFloat o = self.contentScaleFactor; 1738# else 1739 CGFloat o = self.window.backingScaleFactor; 1740# endif 1741 1742# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR 1743 if (o != s) 1744 NSLog(@"lowrez: scaling %.0fx%.0f -> %.0fx%.0f (%.02f)", 1745 b.width * o, b.height * o, 1746 b.width * s, b.height * s, s); 1747# endif 1748 } 1749 1750 [self resize_x11]; 1751 } 1752 1753 if (!setup_p) { 1754 setup_p = YES; 1755 if (xsft->setup_cb) 1756 xsft->setup_cb (xsft, xsft->setup_arg); 1757 } 1758 initted_p = YES; 1759 NSAssert(!xdata, @"xdata already initialized"); 1760 1761 1762# undef ya_rand_init 1763 ya_rand_init (0); 1764 1765 XSetWindowBackground (xdpy, xwindow, 1766 get_pixel_resource (xdpy, 0, 1767 "background", "Background")); 1768 XClearWindow (xdpy, xwindow); 1769 1770# ifndef USE_IPHONE 1771 [[self window] setAcceptsMouseMovedEvents:YES]; 1772# endif 1773 1774 /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz 1775 drawing primitives will run on the GPU instead of the CPU. 1776 It seems like it might make things worse rather than better, 1777 though... Plus it makes us binary-incompatible with 10.4. 1778 1779# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 1780 [[self window] setPreferredBackingLocation: 1781 NSWindowBackingLocationVideoMemory]; 1782# endif 1783 */ 1784 1785 /* Kludge: even though the init_cb functions are declared to take 2 args, 1786 actually call them with 3, for the benefit of xlockmore_init() and 1787 xlockmore_setup(). 1788 */ 1789 void *(*init_cb) (Display *, Window, void *) = 1790 (void *(*) (Display *, Window, void *)) xsft->init_cb; 1791 1792 xdata = init_cb (xdpy, xwindow, xsft->setup_arg); 1793 // NSAssert(xdata, @"no xdata from init"); 1794 if (! xdata) abort(); 1795 1796 if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) { 1797 fpst = fps_init (xdpy, xwindow); 1798 fps_cb = xsft->fps_cb; 1799 if (! fps_cb) fps_cb = screenhack_do_fps; 1800 } else { 1801 fpst = NULL; 1802 fps_cb = 0; 1803 } 1804 1805# ifdef USE_IPHONE 1806 if (current_device_rotation() != 0) // launched while rotated 1807 resized_p = YES; 1808# endif 1809 1810 [self checkForUpdates]; 1811 } 1812 1813 1814 /* I don't understand why we have to do this *every frame*, but we do, 1815 or else the cursor comes back on. 1816 */ 1817# ifndef USE_IPHONE 1818 if (![self isPreview]) 1819 [NSCursor setHiddenUntilMouseMoves:YES]; 1820# endif 1821 1822 1823 if (fpst) 1824 { 1825 /* This is just a guess, but the -fps code wants to know how long 1826 we were sleeping between frames. 1827 */ 1828 long usecs = 1000000 * [self animationTimeInterval]; 1829 usecs -= 200; // caller apparently sleeps for slightly less sometimes... 1830 if (usecs < 0) usecs = 0; 1831 fps_slept (fpst, usecs); 1832 } 1833 1834 1835 /* Run any XtAppAddInput and XtAppAddTimeOut callbacks now. 1836 Do this before delaying for next_frame_time to avoid throttling 1837 timers to the hack's frame rate. 1838 */ 1839 XtAppProcessEvent (XtDisplayToApplicationContext (xdpy), 1840 XtIMTimer | XtIMAlternateInput); 1841 1842 1843 /* It turns out that on some systems (possibly only 10.5 and older?) 1844 [ScreenSaverView setAnimationTimeInterval] does nothing. This means 1845 that we cannot rely on it. 1846 1847 Some of the screen hacks want to delay for long periods, and letting the 1848 framework run the update function at 30 FPS when it really wanted half a 1849 minute between frames would be bad. So instead, we assume that the 1850 framework's animation timer might fire whenever, but we only invoke the 1851 screen hack's "draw frame" method when enough time has expired. 1852 1853 This means two extra calls to gettimeofday() per frame. For fast-cycling 1854 screen savers, that might actually slow them down. Oh well. 1855 1856 A side-effect of this is that it's not possible for a saver to request 1857 an animation interval that is faster than animationTimeInterval. 1858 1859 HOWEVER! On modern systems where setAnimationTimeInterval is *not* 1860 ignored, it's important that it be faster than 30 FPS. 240 FPS is good. 1861 1862 An NSTimer won't fire if the timer is already running the invocation 1863 function from a previous firing. So, if we use a 30 FPS 1864 animationTimeInterval (33333 �s) and a screenhack takes 40000 �s for a 1865 frame, there will be a 26666 �s delay until the next frame, 66666 �s 1866 after the beginning of the current frame. In other words, 25 FPS 1867 becomes 15 FPS. 1868 1869 Frame rates tend to snap to values of 30/N, where N is a positive 1870 integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate 1871 is rounded down from what it would normally be. 1872 1873 So if we set animationTimeInterval to 1/240 instead of 1/30, frame rates 1874 become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate 1875 steps for higher or lower animation time intervals respectively. 1876 */ 1877 struct timeval tv; 1878 gettimeofday (&tv, 0); 1879 double now = tv.tv_sec + (tv.tv_usec / 1000000.0); 1880 if (now < next_frame_time) return; 1881 1882 // [self flushBackbuffer]; 1883 1884 if (resized_p) { 1885 // We do this here instead of in setFrame so that all the 1886 // Xlib drawing takes place under the animation timer. 1887 1888# ifndef USE_IPHONE 1889 if (ogl_ctx) 1890 [ogl_ctx setView:self]; 1891# endif // !USE_IPHONE 1892 1893 [self reshape_x11]; 1894 resized_p = NO; 1895 } 1896 1897 1898 // And finally: 1899 // 1900 // NSAssert(xdata, @"no xdata when drawing"); 1901 if (! xdata) abort(); 1902 unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata); 1903 if (fpst && fps_cb) 1904 fps_cb (xdpy, xwindow, fpst, xdata); 1905 1906 gettimeofday (&tv, 0); 1907 now = tv.tv_sec + (tv.tv_usec / 1000000.0); 1908 next_frame_time = now + (delay / 1000000.0); 1909 1910# ifdef JWXYZ_QUARTZ 1911 [self drawBackbuffer]; 1912# endif 1913 // This can also happen near the beginning of render_x11. 1914 [self flushBackbuffer]; 1915 1916# ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt. 1917 if (delay < [self animationTimeInterval]) 1918 [self setAnimationTimeInterval:(delay / 1000000.0)]; 1919# endif 1920 1921# ifdef DO_GC_HACKERY 1922 /* Current theory is that the 10.6 garbage collector sucks in the 1923 following way: 1924 1925 It only does a collection when a threshold of outstanding 1926 collectable allocations has been surpassed. However, CoreGraphics 1927 creates lots of small collectable allocations that contain pointers 1928 to very large non-collectable allocations: a small CG object that's 1929 collectable referencing large malloc'd allocations (non-collectable) 1930 containing bitmap data. So the large allocation doesn't get freed 1931 until GC collects the small allocation, which triggers its finalizer 1932 to run which frees the large allocation. So GC is deciding that it 1933 doesn't really need to run, even though the process has gotten 1934 enormous. GC eventually runs once pageouts have happened, but by 1935 then it's too late, and the machine's resident set has been 1936 sodomized. 1937 1938 So, we force an exhaustive garbage collection in this process 1939 approximately every 5 seconds whether the system thinks it needs 1940 one or not. 1941 */ 1942 { 1943 static int tick = 0; 1944 if (++tick > 5*30) { 1945 tick = 0; 1946 objc_collect (OBJC_EXHAUSTIVE_COLLECTION); 1947 } 1948 } 1949# endif // DO_GC_HACKERY 1950 1951# ifdef USE_IPHONE 1952 } 1953 @catch (NSException *e) { 1954 [self handleException: e]; 1955 } 1956# endif // USE_IPHONE 1957 1958# if 0 1959 { 1960 static int frame = 0; 1961 if (++frame == 100) { 1962 fprintf(stderr,"BOOM\n"); 1963 int y = 0; 1964 // int aa = *((int*)y); 1965 int x = 30/y; 1966 } 1967 } 1968# endif 1969} 1970 1971 1972/* drawRect always does nothing, and animateOneFrame renders bits to the 1973 screen. This is (now) true of both X11 and GL on both MacOS and iOS. 1974 But this null method needs to exist or things complain. 1975 */ 1976- (void)drawRect:(NSRect)rect 1977{ 1978} 1979 1980 1981- (void) animateOneFrame 1982{ 1983 // Render X11 into the backing store bitmap... 1984 1985# ifdef USE_TOUCHBAR 1986 if (touchbar_p) return; 1987# endif 1988 1989# ifdef JWXYZ_QUARTZ 1990 NSAssert (backbuffer, @"no back buffer"); 1991 1992# ifdef USE_IPHONE 1993 UIGraphicsPushContext (backbuffer); 1994# endif 1995# endif // JWXYZ_QUARTZ 1996 1997 [self render_x11]; 1998 1999# if defined USE_IPHONE && defined JWXYZ_QUARTZ 2000 UIGraphicsPopContext(); 2001# endif 2002 2003# ifdef USE_TOUCHBAR 2004 if (touchbar_view) [touchbar_view animateOneFrame]; 2005# endif 2006} 2007 2008 2009# ifndef USE_IPHONE // Doesn't exist on iOS 2010 2011- (void) setFrame:(NSRect) newRect 2012{ 2013 [super setFrame:newRect]; 2014 2015 if (xwindow) // inform Xlib that the window has changed now. 2016 [self resize_x11]; 2017} 2018 2019- (void) setFrameSize:(NSSize) newSize 2020{ 2021 [super setFrameSize:newSize]; 2022 if (xwindow) 2023 [self resize_x11]; 2024} 2025 2026# else // USE_IPHONE 2027 2028- (void) layoutSubviews 2029{ 2030 [super layoutSubviews]; 2031 [self resizeGL]; 2032 if (xwindow) 2033 [self resize_x11]; 2034} 2035 2036# endif 2037 2038 2039+(BOOL) performGammaFade 2040{ 2041 return YES; 2042} 2043 2044- (BOOL) hasConfigureSheet 2045{ 2046 return YES; 2047} 2048 2049+ (NSString *) decompressXML: (NSData *)data 2050{ 2051 if (! data) return 0; 2052 BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5); 2053 2054 // If it's not already XML, decompress it. 2055 NSAssert (compressed_p, @"xml isn't compressed"); 2056 if (compressed_p) { 2057 NSMutableData *data2 = 0; 2058 int ret = -1; 2059 z_stream zs; 2060 memset (&zs, 0, sizeof(zs)); 2061 ret = inflateInit2 (&zs, 16 + MAX_WBITS); 2062 if (ret == Z_OK) { 2063 UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4); 2064 data2 = [NSMutableData dataWithLength: usize]; 2065 zs.next_in = (Bytef *) data.bytes; 2066 zs.avail_in = (uint) data.length; 2067 zs.next_out = (Bytef *) data2.bytes; 2068 zs.avail_out = (uint) data2.length; 2069 ret = inflate (&zs, Z_FINISH); 2070 inflateEnd (&zs); 2071 } 2072 if (ret == Z_OK || ret == Z_STREAM_END) 2073 data = data2; 2074 else 2075 NSAssert2 (0, @"gunzip error: %d: %s", 2076 ret, (zs.msg ? zs.msg : "<null>")); 2077 } 2078 2079 NSString *s = [[NSString alloc] 2080 initWithData:data encoding:NSUTF8StringEncoding]; 2081 [s autorelease]; 2082 return s; 2083} 2084 2085 2086#ifndef USE_IPHONE 2087- (NSWindow *) configureSheet 2088#else 2089- (UIViewController *) configureView 2090#endif 2091{ 2092 NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 2093 NSString *file = [NSString stringWithCString:xsft->progclass 2094 encoding:NSISOLatin1StringEncoding]; 2095 file = [file lowercaseString]; 2096 NSString *path = [bundle pathForResource:file ofType:@"xml"]; 2097 if (!path) { 2098 NSLog (@"%@.xml does not exist in the application bundle: %@/", 2099 file, [bundle resourcePath]); 2100 return nil; 2101 } 2102 2103# ifdef USE_IPHONE 2104 UIViewController *sheet; 2105 NSString *updater = 0; 2106# else // !USE_IPHONE 2107 NSWindow *sheet; 2108 NSString *updater = [self updaterPath]; 2109# endif // !USE_IPHONE 2110 2111 2112 NSData *xmld = [NSData dataWithContentsOfFile:path]; 2113 NSString *xml = [[self class] decompressXML: xmld]; 2114 sheet = [[XScreenSaverConfigSheet alloc] 2115 initWithXML:[xml dataUsingEncoding:NSUTF8StringEncoding] 2116 options:xsft->options 2117 controller:[prefsReader userDefaultsController] 2118 globalController:[prefsReader globalDefaultsController] 2119 defaults:[prefsReader defaultOptions] 2120 haveUpdater:(updater ? TRUE : FALSE)]; 2121 2122 // #### am I expected to retain this, or not? wtf. 2123 // I thought not, but if I don't do this, we (sometimes) crash. 2124 // #### Analyze says "potential leak of an object stored into sheet" 2125 // [sheet retain]; 2126 2127 return sheet; 2128} 2129 2130 2131- (NSUserDefaultsController *) userDefaultsController 2132{ 2133 return [prefsReader userDefaultsController]; 2134} 2135 2136 2137/* Announce our willingness to accept keyboard input. 2138 */ 2139- (BOOL)acceptsFirstResponder 2140{ 2141 return YES; 2142} 2143 2144 2145- (void) beep 2146{ 2147# ifndef USE_IPHONE 2148 NSBeep(); 2149# else // USE_IPHONE 2150 2151 // There's no way to play a standard system alert sound! 2152 // We'd have to include our own WAV for that. 2153 // 2154 // Or we could vibrate: 2155 // #import <AudioToolbox/AudioToolbox.h> 2156 // AudioServicesPlaySystemSound (kSystemSoundID_Vibrate); 2157 // 2158 // Instead, just flash the screen white, then fade. 2159 // 2160 UIView *v = [[UIView alloc] initWithFrame: [self frame]]; 2161 [v setBackgroundColor: [UIColor whiteColor]]; 2162 [[self window] addSubview:v]; 2163 [UIView animateWithDuration: 0.1 2164 animations:^{ [v setAlpha: 0.0]; } 2165 completion:^(BOOL finished) { [v removeFromSuperview]; } ]; 2166 2167# endif // USE_IPHONE 2168} 2169 2170 2171/* Send an XEvent to the hack. Returns YES if it was handled. 2172 */ 2173- (BOOL) sendEvent: (XEvent *) e 2174{ 2175 if (!initted_p || ![self isAnimating]) // no event handling unless running. 2176 return NO; 2177 2178// [self lockFocus]; // As of 10.14 this causes flicker on mouse motion 2179 [self prepareContext]; 2180 BOOL result = xsft->event_cb (xdpy, xwindow, xdata, e); 2181// [self unlockFocus];cp -Rf ${CONFIGURATION_BUILD_DIR}/BuildOutputPrefPane.prefPane ~/Library/PreferencePanes 2182 return result; 2183} 2184 2185 2186#ifndef USE_IPHONE 2187 2188/* Convert an NSEvent into an XEvent, and pass it along. 2189 Returns YES if it was handled. 2190 */ 2191- (BOOL) convertEvent: (NSEvent *) e 2192 type: (int) type 2193{ 2194 XEvent xe; 2195 memset (&xe, 0, sizeof(xe)); 2196 2197 int state = 0; 2198 2199 int flags = [e modifierFlags]; 2200 if (flags & NSAlphaShiftKeyMask) state |= LockMask; 2201 if (flags & NSShiftKeyMask) state |= ShiftMask; 2202 if (flags & NSControlKeyMask) state |= ControlMask; 2203 if (flags & NSAlternateKeyMask) state |= Mod1Mask; 2204 if (flags & NSCommandKeyMask) state |= Mod2Mask; 2205 2206 NSPoint p = [[[e window] contentView] convertPoint:[e locationInWindow] 2207 toView:self]; 2208 double s = [self hackedContentScaleFactor]; 2209 int x = s * p.x; 2210 int y = s * ([self bounds].size.height - p.y); 2211 2212 xe.xany.type = type; 2213 switch (type) { 2214 case ButtonPress: 2215 case ButtonRelease: 2216 xe.xbutton.x = x; 2217 xe.xbutton.y = y; 2218 xe.xbutton.state = state; 2219 if ([e type] == NSScrollWheel) 2220 xe.xbutton.button = ([e deltaY] > 0 ? Button4 : 2221 [e deltaY] < 0 ? Button5 : 2222 [e deltaX] > 0 ? Button6 : 2223 [e deltaX] < 0 ? Button7 : 2224 0); 2225 else 2226 xe.xbutton.button = (unsigned int) [e buttonNumber] + 1; 2227 break; 2228 case MotionNotify: 2229 xe.xmotion.x = x; 2230 xe.xmotion.y = y; 2231 xe.xmotion.state = state; 2232 break; 2233 case KeyPress: 2234 case KeyRelease: 2235 { 2236 NSString *ns = (([e type] == NSFlagsChanged) ? 0 : 2237 [e charactersIgnoringModifiers]); 2238 KeySym k = 0; 2239 2240 if (!ns || [ns length] == 0) // dead key 2241 { 2242 // Cocoa hides the difference between left and right keys. 2243 // Also we only get KeyPress events for these, no KeyRelease 2244 // (unless we hack the mod state manually. Bleh.) 2245 // 2246 if (flags & NSAlphaShiftKeyMask) k = XK_Caps_Lock; 2247 else if (flags & NSShiftKeyMask) k = XK_Shift_L; 2248 else if (flags & NSControlKeyMask) k = XK_Control_L; 2249 else if (flags & NSAlternateKeyMask) k = XK_Alt_L; 2250 else if (flags & NSCommandKeyMask) k = XK_Meta_L; 2251 } 2252 else if ([ns length] == 1) // real key 2253 { 2254 switch ([ns characterAtIndex:0]) { 2255 case NSLeftArrowFunctionKey: k = XK_Left; break; 2256 case NSRightArrowFunctionKey: k = XK_Right; break; 2257 case NSUpArrowFunctionKey: k = XK_Up; break; 2258 case NSDownArrowFunctionKey: k = XK_Down; break; 2259 case NSPageUpFunctionKey: k = XK_Page_Up; break; 2260 case NSPageDownFunctionKey: k = XK_Page_Down; break; 2261 case NSHomeFunctionKey: k = XK_Home; break; 2262 case NSPrevFunctionKey: k = XK_Prior; break; 2263 case NSNextFunctionKey: k = XK_Next; break; 2264 case NSBeginFunctionKey: k = XK_Begin; break; 2265 case NSEndFunctionKey: k = XK_End; break; 2266 case NSF1FunctionKey: k = XK_F1; break; 2267 case NSF2FunctionKey: k = XK_F2; break; 2268 case NSF3FunctionKey: k = XK_F3; break; 2269 case NSF4FunctionKey: k = XK_F4; break; 2270 case NSF5FunctionKey: k = XK_F5; break; 2271 case NSF6FunctionKey: k = XK_F6; break; 2272 case NSF7FunctionKey: k = XK_F7; break; 2273 case NSF8FunctionKey: k = XK_F8; break; 2274 case NSF9FunctionKey: k = XK_F9; break; 2275 case NSF10FunctionKey: k = XK_F10; break; 2276 case NSF11FunctionKey: k = XK_F11; break; 2277 case NSF12FunctionKey: k = XK_F12; break; 2278 default: 2279 { 2280 const char *ss = 2281 [ns cStringUsingEncoding:NSISOLatin1StringEncoding]; 2282 k = (ss && *ss ? *ss : 0); 2283 } 2284 break; 2285 } 2286 } 2287 2288 if (! k) return YES; // E.g., "KeyRelease XK_Shift_L" 2289 2290 xe.xkey.keycode = k; 2291 xe.xkey.state = state; 2292 break; 2293 } 2294 default: 2295 NSAssert1 (0, @"unknown X11 event type: %d", type); 2296 break; 2297 } 2298 2299 return [self sendEvent: &xe]; 2300} 2301 2302 2303- (void) mouseDown: (NSEvent *) e 2304{ 2305 if (! [self convertEvent:e type:ButtonPress]) 2306 [super mouseDown:e]; 2307} 2308 2309- (void) mouseUp: (NSEvent *) e 2310{ 2311 if (! [self convertEvent:e type:ButtonRelease]) 2312 [super mouseUp:e]; 2313} 2314 2315- (void) otherMouseDown: (NSEvent *) e 2316{ 2317 if (! [self convertEvent:e type:ButtonPress]) 2318 [super otherMouseDown:e]; 2319} 2320 2321- (void) otherMouseUp: (NSEvent *) e 2322{ 2323 if (! [self convertEvent:e type:ButtonRelease]) 2324 [super otherMouseUp:e]; 2325} 2326 2327- (void) mouseMoved: (NSEvent *) e 2328{ 2329 if (! [self convertEvent:e type:MotionNotify]) 2330 [super mouseMoved:e]; 2331} 2332 2333- (void) mouseDragged: (NSEvent *) e 2334{ 2335 if (! [self convertEvent:e type:MotionNotify]) 2336 [super mouseDragged:e]; 2337} 2338 2339- (void) otherMouseDragged: (NSEvent *) e 2340{ 2341 if (! [self convertEvent:e type:MotionNotify]) 2342 [super otherMouseDragged:e]; 2343} 2344 2345- (void) scrollWheel: (NSEvent *) e 2346{ 2347 if (! [self convertEvent:e type:ButtonPress]) 2348 [super scrollWheel:e]; 2349} 2350 2351- (void) keyDown: (NSEvent *) e 2352{ 2353 if (! [self convertEvent:e type:KeyPress]) 2354 [super keyDown:e]; 2355} 2356 2357- (void) keyUp: (NSEvent *) e 2358{ 2359 if (! [self convertEvent:e type:KeyRelease]) 2360 [super keyUp:e]; 2361} 2362 2363- (void) flagsChanged: (NSEvent *) e 2364{ 2365 if (! [self convertEvent:e type:KeyPress]) 2366 [super flagsChanged:e]; 2367} 2368 2369 2370- (NSOpenGLPixelFormat *) getGLPixelFormat 2371{ 2372 NSAssert (prefsReader, @"no prefsReader for getGLPixelFormat"); 2373 2374 NSOpenGLPixelFormatAttribute attrs[40]; 2375 int i = 0; 2376 attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; 2377 2378/* OpenGL's core profile removes a lot of the same stuff that was removed in 2379 OpenGL ES (e.g. glBegin, glDrawPixels), so it might be a possibility. 2380 2381 opengl_core_p = True; 2382 if (opengl_core_p) { 2383 attrs[i++] = NSOpenGLPFAOpenGLProfile; 2384 attrs[i++] = NSOpenGLProfileVersion3_2Core; 2385 } 2386 */ 2387 2388/* Eventually: multisampled pixmaps. May not be supported everywhere. 2389 if (multi_sample_p) { 2390 attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; 2391 attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6; 2392 } 2393 */ 2394 2395# ifdef JWXYZ_QUARTZ 2396 // Under Quartz, we're just blitting a texture. 2397 if (double_buffered_p) 2398 attrs[i++] = NSOpenGLPFADoubleBuffer; 2399# endif 2400 2401# ifdef JWXYZ_GL 2402 /* Under OpenGL, all sorts of drawing commands are being issued, and it might 2403 be a performance problem if this activity occurs on the front buffer. 2404 Also, some screenhacks expect OS X/iOS to always double-buffer. 2405 NSOpenGLPFABackingStore prevents flickering with screenhacks that 2406 don't redraw the entire screen every frame. 2407 */ 2408 attrs[i++] = NSOpenGLPFADoubleBuffer; 2409 attrs[i++] = NSOpenGLPFABackingStore; 2410# endif 2411 2412 attrs[i++] = NSOpenGLPFAWindow; 2413# ifdef JWXYZ_GL 2414 attrs[i++] = NSOpenGLPFAPixelBuffer; 2415 /* ...But not NSOpenGLPFAFullScreen, because that would be for 2416 [NSOpenGLContext setFullScreen]. 2417 */ 2418# endif 2419 2420 /* NSOpenGLPFAFullScreen would go here if initWithFrame's isPreview == NO. 2421 */ 2422 2423 attrs[i] = 0; 2424 2425 NSOpenGLPixelFormat *p = [[NSOpenGLPixelFormat alloc] 2426 initWithAttributes:attrs]; 2427 [p autorelease]; 2428 return p; 2429} 2430 2431#else // USE_IPHONE 2432 2433 2434- (void) stopAndClose 2435{ 2436 [self stopAndClose:NO]; 2437} 2438 2439 2440- (void) stopAndClose:(Bool)relaunch_p 2441{ 2442 if ([self isAnimating]) 2443 [self stopAnimation]; 2444 2445 /* Need to make the SaverListController be the firstResponder again 2446 so that it can continue to receive its own shake events. I 2447 suppose that this abstraction-breakage means that I'm adding 2448 XScreenSaverView to the UINavigationController wrong... 2449 */ 2450// UIViewController *v = [[self window] rootViewController]; 2451// if ([v isKindOfClass: [UINavigationController class]]) { 2452// UINavigationController *n = (UINavigationController *) v; 2453// [[n topViewController] becomeFirstResponder]; 2454// } 2455 [self resignFirstResponder]; 2456 2457 if (relaunch_p) { // Fake a shake on the SaverListController. 2458 [_delegate didShake:self]; 2459 } else { // Not launching another, animate our return to the list. 2460# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR 2461 NSLog (@"fading back to saver list"); 2462# endif 2463 [_delegate wantsFadeOut:self]; 2464 } 2465} 2466 2467 2468/* We distinguish between taps and drags. 2469 2470 - Drags/pans (down, motion, up) are sent to the saver to handle. 2471 - Single-taps are sent to the saver to handle. 2472 - Double-taps are sent to the saver as a "Space" keypress. 2473 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow keys. 2474 - All taps expose the momentary "Close" button. 2475 */ 2476 2477- (void)initGestures 2478{ 2479 UITapGestureRecognizer *dtap = [[UITapGestureRecognizer alloc] 2480 initWithTarget:self 2481 action:@selector(handleDoubleTap)]; 2482 dtap.numberOfTapsRequired = 2; 2483 dtap.numberOfTouchesRequired = 1; 2484 2485 UITapGestureRecognizer *stap = [[UITapGestureRecognizer alloc] 2486 initWithTarget:self 2487 action:@selector(handleTap:)]; 2488 stap.numberOfTapsRequired = 1; 2489 stap.numberOfTouchesRequired = 1; 2490 2491 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] 2492 initWithTarget:self 2493 action:@selector(handlePan:)]; 2494 pan.maximumNumberOfTouches = 1; 2495 pan.minimumNumberOfTouches = 1; 2496 2497 // I couldn't get Swipe to work, but using a second Pan recognizer works. 2498 UIPanGestureRecognizer *pan2 = [[UIPanGestureRecognizer alloc] 2499 initWithTarget:self 2500 action:@selector(handlePan2:)]; 2501 pan2.maximumNumberOfTouches = 2; 2502 pan2.minimumNumberOfTouches = 2; 2503 2504 // Also handle long-touch, and treat that the same as Pan. 2505 // Without this, panning doesn't start until there's motion, so the trick 2506 // of holding down your finger to freeze the scene doesn't work. 2507 // 2508 UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc] 2509 initWithTarget:self 2510 action:@selector(handleLongPress:)]; 2511 hold.numberOfTapsRequired = 0; 2512 hold.numberOfTouchesRequired = 1; 2513 hold.minimumPressDuration = 0.25; /* 1/4th second */ 2514 2515 // Two finger pinch to zoom in on the view. 2516 UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] 2517 initWithTarget:self 2518 action:@selector(handlePinch:)]; 2519 2520 [stap requireGestureRecognizerToFail: dtap]; 2521 [stap requireGestureRecognizerToFail: hold]; 2522 [dtap requireGestureRecognizerToFail: hold]; 2523 [pan requireGestureRecognizerToFail: hold]; 2524 [pan2 requireGestureRecognizerToFail: pinch]; 2525 2526 [self setMultipleTouchEnabled:YES]; 2527 2528 [self addGestureRecognizer: dtap]; 2529 [self addGestureRecognizer: stap]; 2530 [self addGestureRecognizer: pan]; 2531 [self addGestureRecognizer: pan2]; 2532 [self addGestureRecognizer: hold]; 2533 [self addGestureRecognizer: pinch]; 2534 2535 [dtap release]; 2536 [stap release]; 2537 [pan release]; 2538 [pan2 release]; 2539 [hold release]; 2540 [pinch release]; 2541} 2542 2543 2544/* Given a mouse (touch) coordinate in unrotated, unscaled view coordinates, 2545 convert it to what X11 and OpenGL expect. 2546 2547 Getting this crap right is tricky, given the confusion of the various 2548 scale factors, so here's a checklist that I think covers all of the X11 2549 and OpenGL cases. For each of these: rotate to all 4 orientations; 2550 ensure the mouse tracks properly to all 4 corners. 2551 2552 Test it in Xcode 6, because Xcode 5.0.2 can't run the iPhone6+ simulator. 2553 2554 Test hacks must cover: 2555 X11 ignoreRotation = true 2556 X11 ignoreRotation = false 2557 OpenGL (rotation is handled manually, so they never ignoreRotation) 2558 2559 Test devices must cover: 2560 contentScaleFactor = 1, hackedContentScaleFactor = 1 (iPad 2) 2561 contentScaleFactor = 2, hackedContentScaleFactor = 1 (iPad Retina Air) 2562 contentScaleFactor = 2, hackedContentScaleFactor = 2 (iPhone 5 5s 6 6+) 2563 2564 iPad 2: 768x1024 / 1 = 768x1024 2565 iPad Air: 1536x2048 / 2 = 768x1024 (iPad Retina is identical) 2566 iPhone 4s: 640x960 / 2 = 320x480 2567 iPhone 5: 640x1136 / 2 = 320x568 (iPhone 5s and iPhone 6 are identical) 2568 iPhone 6+: 640x1136 / 2 = 320x568 (nativeBounds 960x1704 nativeScale 3) 2569 2570 Tests: 2571 iPad2 iPadAir iPhone4s iPhone5 iPhone6+ 2572 Attraction X yes - - - - Y 2573 Fireworkx X no - - - - Y 2574 Carousel GL yes - - - - Y 2575 Voronoi GL no - - - - - 2576 */ 2577- (void) convertMouse:(CGPoint *)p 2578{ 2579 CGFloat xx = p->x, yy = p->y; 2580 2581# if 0 // TARGET_IPHONE_SIMULATOR 2582 { 2583 XWindowAttributes xgwa; 2584 XGetWindowAttributes (xdpy, xwindow, &xgwa); 2585 NSLog (@"TOUCH %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n", 2586 p->x, p->y, 2587 xgwa.width, xgwa.height, 2588 [self contentScaleFactor], 2589 [self hackedContentScaleFactor], 2590 [self rotateTouches], [self ignoreRotation]); 2591 } 2592# endif // TARGET_IPHONE_SIMULATOR 2593 2594 if ([self rotateTouches]) { 2595 2596 // The XScreenSaverGLView case: 2597 // The X11 window is rotated, as is the framebuffer. 2598 // The device coordinates match the framebuffer dimensions, 2599 // but might have axes swapped... and we need to swap them 2600 // by ratios. 2601 // 2602 int w = [self frame].size.width; 2603 int h = [self frame].size.height; 2604 GLfloat xr = (GLfloat) xx / w; 2605 GLfloat yr = (GLfloat) yy / h; 2606 GLfloat swap; 2607 int o = (int) current_device_rotation(); 2608 switch (o) { 2609 case -90: case 270: swap = xr; xr = 1-yr; yr = swap; break; 2610 case 90: case -270: swap = xr; xr = yr; yr = 1-swap; break; 2611 case 180: case -180: xr = 1-xr; yr = 1-yr; break; 2612 default: break; 2613 } 2614 xx = xr * w; 2615 yy = yr * h; 2616 2617 } else if ([self ignoreRotation]) { 2618 2619 // The X11 case, where the hack has opted not to rotate: 2620 // The X11 window is unrotated, but the framebuffer is rotated. 2621 // The device coordinates match the framebuffer, so they need to 2622 // be de-rotated to match the X11 window. 2623 // 2624 int w = [self frame].size.width; 2625 int h = [self frame].size.height; 2626 int swap; 2627 int o = (int) current_device_rotation(); 2628 switch (o) { 2629 case -90: case 270: swap = xx; xx = h-yy; yy = swap; break; 2630 case 90: case -270: swap = xx; xx = yy; yy = w-swap; break; 2631 case 180: case -180: xx = w-xx; yy = h-yy; break; 2632 default: break; 2633 } 2634 } 2635 2636 double s = [self hackedContentScaleFactor]; 2637 p->x = xx * s; 2638 p->y = yy * s; 2639 2640# if 0 // TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__ 2641 { 2642 XWindowAttributes xgwa; 2643 XGetWindowAttributes (xdpy, xwindow, &xgwa); 2644 NSLog (@"touch %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n", 2645 p->x, p->y, 2646 xgwa.width, xgwa.height, 2647 [self contentScaleFactor], 2648 [self hackedContentScaleFactor], 2649 [self rotateTouches], [self ignoreRotation]); 2650 if (p->x < 0 || p->y < 0 || p->x > xgwa.width || p->y > xgwa.height) 2651 abort(); 2652 } 2653# endif // TARGET_IPHONE_SIMULATOR 2654} 2655 2656 2657/* Single click exits saver. 2658 */ 2659- (void) handleTap:(UIGestureRecognizer *)sender 2660{ 2661 if (!xwindow) 2662 return; 2663 2664 XEvent xe; 2665 memset (&xe, 0, sizeof(xe)); 2666 2667 [self showCloseButton]; 2668 2669 CGPoint p = [sender locationInView:self]; // this is in points, not pixels 2670 [self convertMouse:&p]; 2671 NSAssert (xwindow->type == WINDOW, @"not a window"); 2672 xwindow->window.last_mouse_x = p.x; 2673 xwindow->window.last_mouse_y = p.y; 2674 2675 xe.xany.type = ButtonPress; 2676 xe.xbutton.button = 1; 2677 xe.xbutton.x = p.x; 2678 xe.xbutton.y = p.y; 2679 2680 if (! [self sendEvent: &xe]) 2681 ; //[self beep]; 2682 2683 xe.xany.type = ButtonRelease; 2684 xe.xbutton.button = 1; 2685 xe.xbutton.x = p.x; 2686 xe.xbutton.y = p.y; 2687 2688 [self sendEvent: &xe]; 2689} 2690 2691 2692/* Double click sends Space KeyPress. 2693 */ 2694- (void) handleDoubleTap 2695{ 2696 if (!xsft->event_cb || !xwindow) return; 2697 2698 [self showCloseButton]; 2699 2700 XEvent xe; 2701 memset (&xe, 0, sizeof(xe)); 2702 xe.xkey.keycode = ' '; 2703 xe.xany.type = KeyPress; 2704 BOOL ok1 = [self sendEvent: &xe]; 2705 xe.xany.type = KeyRelease; 2706 BOOL ok2 = [self sendEvent: &xe]; 2707 if (!(ok1 || ok2)) 2708 [self beep]; 2709} 2710 2711 2712/* Drag with one finger down: send MotionNotify. 2713 */ 2714- (void) handlePan:(UIGestureRecognizer *)sender 2715{ 2716 if (!xsft->event_cb || !xwindow) return; 2717 2718 [self showCloseButton]; 2719 2720 XEvent xe; 2721 memset (&xe, 0, sizeof(xe)); 2722 2723 CGPoint p = [sender locationInView:self]; // this is in points, not pixels 2724 [self convertMouse:&p]; 2725 NSAssert (xwindow && xwindow->type == WINDOW, @"not a window"); 2726 xwindow->window.last_mouse_x = p.x; 2727 xwindow->window.last_mouse_y = p.y; 2728 2729 switch (sender.state) { 2730 case UIGestureRecognizerStateBegan: 2731 xe.xany.type = ButtonPress; 2732 xe.xbutton.button = 1; 2733 xe.xbutton.x = p.x; 2734 xe.xbutton.y = p.y; 2735 break; 2736 2737 case UIGestureRecognizerStateEnded: 2738 xe.xany.type = ButtonRelease; 2739 xe.xbutton.button = 1; 2740 xe.xbutton.x = p.x; 2741 xe.xbutton.y = p.y; 2742 break; 2743 2744 case UIGestureRecognizerStateChanged: 2745 xe.xany.type = MotionNotify; 2746 xe.xmotion.x = p.x; 2747 xe.xmotion.y = p.y; 2748 break; 2749 2750 default: 2751 break; 2752 } 2753 2754 BOOL ok = [self sendEvent: &xe]; 2755 if (!ok && xe.xany.type == ButtonRelease) 2756 [self beep]; 2757} 2758 2759 2760/* Hold one finger down: assume we're about to start dragging. 2761 Treat the same as Pan. 2762 */ 2763- (void) handleLongPress:(UIGestureRecognizer *)sender 2764{ 2765 [self handlePan:sender]; 2766} 2767 2768 2769 2770/* Drag with 2 fingers down: send arrow keys. 2771 */ 2772- (void) handlePan2:(UIPanGestureRecognizer *)sender 2773{ 2774 if (!xsft->event_cb || !xwindow) return; 2775 2776 [self showCloseButton]; 2777 2778 if (sender.state != UIGestureRecognizerStateEnded) 2779 return; 2780 2781 XEvent xe; 2782 memset (&xe, 0, sizeof(xe)); 2783 2784 CGPoint p = [sender locationInView:self]; // this is in points, not pixels 2785 [self convertMouse:&p]; 2786 2787 if (fabs(p.x) > fabs(p.y)) 2788 xe.xkey.keycode = (p.x > 0 ? XK_Right : XK_Left); 2789 else 2790 xe.xkey.keycode = (p.y > 0 ? XK_Down : XK_Up); 2791 2792 BOOL ok1 = [self sendEvent: &xe]; 2793 xe.xany.type = KeyRelease; 2794 BOOL ok2 = [self sendEvent: &xe]; 2795 if (!(ok1 || ok2)) 2796 [self beep]; 2797} 2798 2799 2800/* Pinch with 2 fingers: zoom in around the center of the fingers. 2801 */ 2802- (void) handlePinch:(UIPinchGestureRecognizer *)sender 2803{ 2804 if (!xsft->event_cb || !xwindow) return; 2805 2806 [self showCloseButton]; 2807 2808 if (sender.state == UIGestureRecognizerStateBegan) 2809 pinch_transform = self.transform; // Save the base transform 2810 2811 switch (sender.state) { 2812 case UIGestureRecognizerStateBegan: 2813 case UIGestureRecognizerStateChanged: 2814 { 2815 double scale = sender.scale; 2816 2817 if (scale < 1) 2818 return; 2819 2820 self.transform = CGAffineTransformScale (pinch_transform, scale, scale); 2821 2822 CGPoint p = [sender locationInView: self]; 2823 p.x /= self.layer.bounds.size.width; 2824 p.y /= self.layer.bounds.size.height; 2825 2826 CGPoint np = CGPointMake (self.bounds.size.width * p.x, 2827 self.bounds.size.height * p.y); 2828 CGPoint op = CGPointMake (self.bounds.size.width * 2829 self.layer.anchorPoint.x, 2830 self.bounds.size.height * 2831 self.layer.anchorPoint.y); 2832 np = CGPointApplyAffineTransform (np, self.transform); 2833 op = CGPointApplyAffineTransform (op, self.transform); 2834 2835 CGPoint pos = self.layer.position; 2836 pos.x -= op.x; 2837 pos.x += np.x; 2838 pos.y -= op.y; 2839 pos.y += np.y; 2840 self.layer.position = pos; 2841 self.layer.anchorPoint = p; 2842 } 2843 break; 2844 2845 case UIGestureRecognizerStateEnded: 2846 { 2847 // When released, snap back to the default zoom (but animate it). 2848 2849 CABasicAnimation *a1 = [CABasicAnimation 2850 animationWithKeyPath:@"position.x"]; 2851 a1.fromValue = [NSNumber numberWithFloat: self.layer.position.x]; 2852 a1.toValue = [NSNumber numberWithFloat: self.bounds.size.width / 2]; 2853 2854 CABasicAnimation *a2 = [CABasicAnimation 2855 animationWithKeyPath:@"position.y"]; 2856 a2.fromValue = [NSNumber numberWithFloat: self.layer.position.y]; 2857 a2.toValue = [NSNumber numberWithFloat: self.bounds.size.height / 2]; 2858 2859 CABasicAnimation *a3 = [CABasicAnimation 2860 animationWithKeyPath:@"anchorPoint.x"]; 2861 a3.fromValue = [NSNumber numberWithFloat: self.layer.anchorPoint.x]; 2862 a3.toValue = [NSNumber numberWithFloat: 0.5]; 2863 2864 CABasicAnimation *a4 = [CABasicAnimation 2865 animationWithKeyPath:@"anchorPoint.y"]; 2866 a4.fromValue = [NSNumber numberWithFloat: self.layer.anchorPoint.y]; 2867 a4.toValue = [NSNumber numberWithFloat: 0.5]; 2868 2869 CABasicAnimation *a5 = [CABasicAnimation 2870 animationWithKeyPath:@"transform.scale"]; 2871 a5.fromValue = [NSNumber numberWithFloat: sender.scale]; 2872 a5.toValue = [NSNumber numberWithFloat: 1.0]; 2873 2874 CAAnimationGroup *group = [CAAnimationGroup animation]; 2875 group.duration = 0.3; 2876 group.repeatCount = 1; 2877 group.autoreverses = NO; 2878 group.animations = @[ a1, a2, a3, a4, a5 ]; 2879 group.timingFunction = [CAMediaTimingFunction 2880 functionWithName: 2881 kCAMediaTimingFunctionEaseIn]; 2882 [self.layer addAnimation:group forKey:@"unpinch"]; 2883 2884 self.transform = pinch_transform; 2885 self.layer.anchorPoint = CGPointMake (0.5, 0.5); 2886 self.layer.position = CGPointMake (self.bounds.size.width / 2, 2887 self.bounds.size.height / 2); 2888 } 2889 break; 2890 default: 2891 abort(); 2892 } 2893} 2894 2895 2896/* We need this to respond to "shake" gestures 2897 */ 2898- (BOOL)canBecomeFirstResponder 2899{ 2900 return YES; 2901} 2902 2903- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event 2904{ 2905} 2906 2907 2908- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event 2909{ 2910} 2911 2912/* Shake means exit and launch a new saver. 2913 */ 2914- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event 2915{ 2916 [self stopAndClose:YES]; 2917} 2918 2919 2920- (void) showCloseButton 2921{ 2922 double iw = 24; 2923 double ih = iw; 2924 double off = 4; 2925 2926 if (!closeBox) { 2927 int width = self.bounds.size.width; 2928 closeBox = [[UIView alloc] 2929 initWithFrame:CGRectMake(0, 0, width, ih + off)]; 2930 closeBox.backgroundColor = [UIColor clearColor]; 2931 closeBox.autoresizingMask = 2932 UIViewAutoresizingFlexibleBottomMargin | 2933 UIViewAutoresizingFlexibleWidth; 2934 2935 // Add the buttons to the bar 2936 UIImage *img1 = [UIImage imageNamed:@"stop"]; 2937 UIImage *img2 = [UIImage imageNamed:@"settings"]; 2938 2939 UIButton *button = [[UIButton alloc] init]; 2940 [button setFrame: CGRectMake(off, off, iw, ih)]; 2941 [button setBackgroundImage:img1 forState:UIControlStateNormal]; 2942 [button addTarget:self 2943 action:@selector(stopAndClose) 2944 forControlEvents:UIControlEventTouchUpInside]; 2945 [closeBox addSubview:button]; 2946 [button release]; 2947 2948 button = [[UIButton alloc] init]; 2949 [button setFrame: CGRectMake(width - iw - off, off, iw, ih)]; 2950 [button setBackgroundImage:img2 forState:UIControlStateNormal]; 2951 [button addTarget:self 2952 action:@selector(stopAndOpenSettings) 2953 forControlEvents:UIControlEventTouchUpInside]; 2954 button.autoresizingMask = 2955 UIViewAutoresizingFlexibleBottomMargin | 2956 UIViewAutoresizingFlexibleLeftMargin; 2957 [closeBox addSubview:button]; 2958 [button release]; 2959 2960 [self addSubview:closeBox]; 2961 } 2962 2963 // Don't hide the buttons under the iPhone X bezel. 2964 UIEdgeInsets is = { 0, }; 2965 if ([self respondsToSelector:@selector(safeAreaInsets)]) { 2966# pragma clang diagnostic push // "only available on iOS 11.0 or newer" 2967# pragma clang diagnostic ignored "-Wunguarded-availability-new" 2968 is = [self safeAreaInsets]; 2969# pragma clang diagnostic pop 2970 [closeBox setFrame:CGRectMake(is.left, is.top, 2971 self.bounds.size.width - is.right - is.left, 2972 ih + off)]; 2973 } 2974 2975 if (closeBox.layer.opacity <= 0) { // Fade in 2976 2977 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"opacity"]; 2978 anim.duration = 0.2; 2979 anim.repeatCount = 1; 2980 anim.autoreverses = NO; 2981 anim.fromValue = [NSNumber numberWithFloat:0.0]; 2982 anim.toValue = [NSNumber numberWithFloat:1.0]; 2983 [closeBox.layer addAnimation:anim forKey:@"animateOpacity"]; 2984 closeBox.layer.opacity = 1; 2985 } 2986 2987 // Fade out N seconds from now. 2988 if (closeBoxTimer) 2989 [closeBoxTimer invalidate]; 2990 closeBoxTimer = [NSTimer scheduledTimerWithTimeInterval: 3 2991 target:self 2992 selector:@selector(closeBoxOff) 2993 userInfo:nil 2994 repeats:NO]; 2995} 2996 2997 2998- (void)closeBoxOff 2999{ 3000 if (closeBoxTimer) { 3001 [closeBoxTimer invalidate]; 3002 closeBoxTimer = 0; 3003 } 3004 if (!closeBox) 3005 return; 3006 3007 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"opacity"]; 3008 anim.duration = 0.2; 3009 anim.repeatCount = 1; 3010 anim.autoreverses = NO; 3011 anim.fromValue = [NSNumber numberWithFloat: 1]; 3012 anim.toValue = [NSNumber numberWithFloat: 0]; 3013 [closeBox.layer addAnimation:anim forKey:@"animateOpacity"]; 3014 closeBox.layer.opacity = 0; 3015} 3016 3017 3018- (void) stopAndOpenSettings 3019{ 3020 NSString *s = [NSString stringWithCString:xsft->progclass 3021 encoding:NSISOLatin1StringEncoding]; 3022 if ([self isAnimating]) 3023 [self stopAnimation]; 3024 [self resignFirstResponder]; 3025 [_delegate wantsFadeOut:self]; 3026 [_delegate openPreferences: s]; 3027 3028} 3029 3030 3031- (void)setScreenLocked:(BOOL)locked 3032{ 3033 if (screenLocked == locked) return; 3034 screenLocked = locked; 3035 if (locked) { 3036 if ([self isAnimating]) 3037 [self stopAnimation]; 3038 } else { 3039 if (! [self isAnimating]) 3040 [self startAnimation]; 3041 } 3042} 3043 3044- (NSDictionary *)getGLProperties 3045{ 3046 return [NSDictionary dictionaryWithObjectsAndKeys: 3047 kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, 3048# ifdef JWXYZ_GL 3049 /* This could be disabled if we knew the screen would be redrawn 3050 entirely for every frame. 3051 */ 3052 [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, 3053# endif // JWXYZ_GL 3054 nil]; 3055} 3056 3057- (void)addExtraRenderbuffers:(CGSize)size 3058{ 3059 // No extra renderbuffers are needed for 2D screenhacks. 3060} 3061 3062 3063- (NSString *)getCAGravity 3064{ 3065 return kCAGravityCenter; // Looks better in e.g. Compass. 3066// return kCAGravityBottomLeft; 3067} 3068 3069#endif // USE_IPHONE 3070 3071 3072# ifndef USE_IPHONE 3073 3074// Returns the full pathname to the Sparkle updater app. 3075// 3076- (NSString *) updaterPath 3077{ 3078 NSString *updater = @"XScreenSaverUpdater.app"; 3079 3080 // There may be multiple copies of the updater: e.g., one in /Applications 3081 // and one in the mounted installer DMG! It's important that we run the 3082 // one from the disk and not the DMG, so search for the right one. 3083 // 3084 NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; 3085 NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 3086 NSArray *search = 3087 @[[[bundle bundlePath] stringByDeletingLastPathComponent], 3088 [@"~/Library/Screen Savers" stringByExpandingTildeInPath], 3089 @"/Library/Screen Savers", 3090 @"/System/Library/Screen Savers", 3091 @"/Applications", 3092 @"/Applications/Utilities"]; 3093 NSString *app_path = nil; 3094 for (NSString *dir in search) { 3095 NSString *p = [dir stringByAppendingPathComponent:updater]; 3096 if ([[NSFileManager defaultManager] fileExistsAtPath:p]) { 3097 app_path = p; 3098 break; 3099 } 3100 } 3101 3102 if (! app_path) 3103 app_path = [workspace fullPathForApplication:updater]; 3104 3105 if (app_path && [app_path hasPrefix:@"/Volumes/XScreenSaver "]) 3106 app_path = 0; // The DMG version will not do. 3107 3108 return app_path; 3109} 3110# endif // !USE_IPHONE 3111 3112 3113- (void) checkForUpdates 3114{ 3115# ifndef USE_IPHONE 3116 // We only check once at startup, even if there are multiple screens, 3117 // and even if this saver is running for many days. 3118 // (Uh, except this doesn't work because this static isn't shared, 3119 // even if we make it an exported global. Not sure why. Oh well.) 3120 static BOOL checked_p = NO; 3121 if (checked_p) return; 3122 checked_p = YES; 3123 3124 // If it's off, don't bother running the updater. Otherwise, the 3125 // updater will decide if it's time to hit the network. 3126 if (! get_boolean_resource (xdpy, 3127 SUSUEnableAutomaticChecksKey, 3128 SUSUEnableAutomaticChecksKey)) 3129 return; 3130 3131 NSString *app_path = [self updaterPath]; 3132 3133 if (!app_path) { 3134 NSLog(@"Unable to find XScreenSaverUpdater.app"); 3135 return; 3136 } 3137 3138 NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; 3139 NSError *err = nil; 3140 if (! [workspace launchApplicationAtURL:[NSURL fileURLWithPath:app_path] 3141 options:(NSWorkspaceLaunchWithoutAddingToRecents | 3142 NSWorkspaceLaunchWithoutActivation | 3143 NSWorkspaceLaunchAndHide) 3144 configuration:[NSMutableDictionary dictionary] 3145 error:&err]) { 3146 NSLog(@"Unable to launch %@: %@", app_path, err); 3147 } 3148 3149# endif // !USE_IPHONE 3150} 3151 3152 3153@end 3154 3155/* Utility functions... 3156 */ 3157 3158static PrefsReader * 3159get_prefsReader (Display *dpy) 3160{ 3161 if (! dpy) return 0; 3162 XScreenSaverView *view = jwxyz_window_view (XRootWindow (dpy, 0)); 3163 if (!view) return 0; 3164 return [view prefsReader]; 3165} 3166 3167 3168char * 3169get_string_resource (Display *dpy, char *name, char *class) 3170{ 3171 return [get_prefsReader(dpy) getStringResource:name]; 3172} 3173 3174Bool 3175get_boolean_resource (Display *dpy, char *name, char *class) 3176{ 3177 return [get_prefsReader(dpy) getBooleanResource:name]; 3178} 3179 3180int 3181get_integer_resource (Display *dpy, char *name, char *class) 3182{ 3183 return [get_prefsReader(dpy) getIntegerResource:name]; 3184} 3185 3186double 3187get_float_resource (Display *dpy, char *name, char *class) 3188{ 3189 return [get_prefsReader(dpy) getFloatResource:name]; 3190} 3191