1 /* 2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #include "splashscreen_impl.h" 27 28 #import <Cocoa/Cocoa.h> 29 #import <objc/objc-auto.h> 30 31 #include <Security/AuthSession.h> 32 #import <JavaNativeFoundation/JavaNativeFoundation.h> 33 #import "NSApplicationAWT.h" 34 35 #include <sys/time.h> 36 #include <pthread.h> 37 #include <iconv.h> 38 #include <langinfo.h> 39 #include <locale.h> 40 #include <fcntl.h> 41 #include <poll.h> 42 #include <errno.h> 43 #include <sys/types.h> 44 #include <signal.h> 45 #include <unistd.h> 46 #include <dlfcn.h> 47 48 #include <sizecalc.h> 49 #import "ThreadUtilities.h" 50 51 NSString* findScaledImageName(NSString *fileName, 52 NSUInteger dotIndex, 53 NSString *strToAppend); 54 55 static NSScreen* SplashNSScreen() 56 { 57 return [[NSScreen screens] objectAtIndex: 0]; 58 } 59 60 static void SplashCenter(Splash * splash) 61 { 62 NSRect screenFrame = [SplashNSScreen() frame]; 63 64 splash->x = (screenFrame.size.width - splash->width) / 2; 65 splash->y = (screenFrame.size.height - splash->height) / 2 + screenFrame.origin.y; 66 } 67 68 unsigned 69 SplashTime(void) { 70 struct timeval tv; 71 struct timezone tz; 72 unsigned long long msec; 73 74 gettimeofday(&tv, &tz); 75 msec = (unsigned long long) tv.tv_sec * 1000 + 76 (unsigned long long) tv.tv_usec / 1000; 77 78 return (unsigned) msec; 79 } 80 81 /* Could use npt but decided to cut down on linked code size */ 82 char* SplashConvertStringAlloc(const char* in, int* size) { 83 const char *codeset; 84 const char *codeset_out; 85 iconv_t cd; 86 size_t rc; 87 char *buf = NULL, *out; 88 size_t bufSize, inSize, outSize; 89 const char* old_locale; 90 91 if (!in) { 92 return NULL; 93 } 94 old_locale = setlocale(LC_ALL, ""); 95 96 codeset = nl_langinfo(CODESET); 97 if ( codeset == NULL || codeset[0] == 0 ) { 98 goto done; 99 } 100 /* we don't need BOM in output so we choose native BE or LE encoding here */ 101 codeset_out = (platformByteOrder()==BYTE_ORDER_MSBFIRST) ? 102 "UCS-2BE" : "UCS-2LE"; 103 104 cd = iconv_open(codeset_out, codeset); 105 if (cd == (iconv_t)-1 ) { 106 goto done; 107 } 108 inSize = strlen(in); 109 buf = SAFE_SIZE_ARRAY_ALLOC(malloc, inSize, 2); 110 if (!buf) { 111 return NULL; 112 } 113 bufSize = inSize*2; // need 2 bytes per char for UCS-2, this is 114 // 2 bytes per source byte max 115 out = buf; outSize = bufSize; 116 /* linux iconv wants char** source and solaris wants const char**... 117 cast to void* */ 118 rc = iconv(cd, (void*)&in, &inSize, &out, &outSize); 119 iconv_close(cd); 120 121 if (rc == (size_t)-1) { 122 free(buf); 123 buf = NULL; 124 } else { 125 if (size) { 126 *size = (bufSize-outSize)/2; /* bytes to wchars */ 127 } 128 } 129 done: 130 setlocale(LC_ALL, old_locale); 131 return buf; 132 } 133 134 BOOL isSWTRunning() { 135 char envVar[80]; 136 // If this property is present we are running SWT 137 snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid()); 138 return getenv(envVar) != NULL; 139 } 140 141 jboolean SplashGetScaledImageName(const char* jar, const char* file, 142 float *scaleFactor, char *scaledFile, 143 const size_t scaledImageLength) { 144 *scaleFactor = 1; 145 146 if(isSWTRunning()){ 147 return JNI_FALSE; 148 } 149 150 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 151 __block float screenScaleFactor = 1; 152 153 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 154 // initialize NSApplication and AWT stuff 155 [NSApplicationAWT sharedApplication]; 156 screenScaleFactor = [SplashNSScreen() backingScaleFactor]; 157 }]; 158 159 if (screenScaleFactor > 1) { 160 NSString *fileName = [NSString stringWithUTF8String: file]; 161 NSUInteger length = [fileName length]; 162 NSRange range = [fileName rangeOfString: @"." 163 options:NSBackwardsSearch]; 164 NSUInteger dotIndex = range.location; 165 NSString *fileName2x = nil; 166 167 fileName2x = findScaledImageName(fileName, dotIndex, @"@2x"); 168 if(![[NSFileManager defaultManager] 169 fileExistsAtPath: fileName2x]) { 170 fileName2x = findScaledImageName(fileName, dotIndex, @"@200pct"); 171 } 172 if (jar || [[NSFileManager defaultManager] 173 fileExistsAtPath: fileName2x]){ 174 if (strlen([fileName2x UTF8String]) > scaledImageLength) { 175 [pool drain]; 176 return JNI_FALSE; 177 } 178 *scaleFactor = 2; 179 strcpy(scaledFile, [fileName2x UTF8String]); 180 [pool drain]; 181 return JNI_TRUE; 182 } 183 } 184 [pool drain]; 185 return JNI_FALSE; 186 } 187 188 static int isInAquaSession() { 189 // environment variable to bypass the aqua session check 190 char *ev = getenv("AWT_FORCE_HEADFUL"); 191 if (ev && (strncasecmp(ev, "true", 4) == 0)) { 192 // if "true" then tell the caller we're in 193 // an Aqua session without actually checking 194 return 1; 195 } 196 // Is the WindowServer available? 197 SecuritySessionId session_id; 198 SessionAttributeBits session_info; 199 OSStatus status = SessionGetInfo(callerSecuritySession, &session_id, &session_info); 200 if (status == noErr) { 201 if (session_info & sessionHasGraphicAccess) { 202 return 1; 203 } 204 } 205 return 0; 206 } 207 208 int 209 SplashInitPlatform(Splash * splash) { 210 if (!isInAquaSession()) { 211 return 0; 212 } 213 pthread_mutex_init(&splash->lock, NULL); 214 215 splash->maskRequired = 0; 216 217 218 //TODO: the following is too much of a hack but should work in 90% cases. 219 // besides we don't use device-dependent drawing, so probably 220 // that's very fine indeed 221 splash->byteAlignment = 1; 222 initFormat(&splash->screenFormat, 0xff << 8, 223 0xff << 16, 0xff << 24, 0xff << 0); 224 splash->screenFormat.byteOrder = 1 ? BYTE_ORDER_LSBFIRST : BYTE_ORDER_MSBFIRST; 225 splash->screenFormat.depthBytes = 4; 226 227 // If we are running SWT we should not start a runLoop 228 if (!isSWTRunning()) { 229 [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^() { 230 [NSApplicationAWT runAWTLoopWithApp:[NSApplicationAWT sharedApplication]]; 231 }]; 232 } 233 return 1; 234 } 235 236 void 237 SplashCleanupPlatform(Splash * splash) { 238 splash->maskRequired = 0; 239 } 240 241 void 242 SplashDonePlatform(Splash * splash) { 243 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 244 245 pthread_mutex_destroy(&splash->lock); 246 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ 247 if (splash->window) { 248 [splash->window orderOut:nil]; 249 [splash->window release]; 250 } 251 }]; 252 [pool drain]; 253 } 254 255 void 256 SplashLock(Splash * splash) { 257 pthread_mutex_lock(&splash->lock); 258 } 259 260 void 261 SplashUnlock(Splash * splash) { 262 pthread_mutex_unlock(&splash->lock); 263 } 264 265 void 266 SplashInitFrameShape(Splash * splash, int imageIndex) { 267 // No shapes, we rely on alpha compositing 268 } 269 270 void * SplashScreenThread(void *param); 271 void 272 SplashCreateThread(Splash * splash) { 273 pthread_t thr; 274 pthread_attr_t attr; 275 int rc; 276 277 pthread_attr_init(&attr); 278 rc = pthread_create(&thr, &attr, SplashScreenThread, (void *) splash); 279 } 280 281 void 282 SplashRedrawWindow(Splash * splash) { 283 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 284 285 SplashUpdateScreenData(splash); 286 287 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ 288 // NSDeviceRGBColorSpace vs. NSCalibratedRGBColorSpace ? 289 NSBitmapImageRep * rep = [[NSBitmapImageRep alloc] 290 initWithBitmapDataPlanes: (unsigned char**)&splash->screenData 291 pixelsWide: splash->width 292 pixelsHigh: splash->height 293 bitsPerSample: 8 294 samplesPerPixel: 4 295 hasAlpha: YES 296 isPlanar: NO 297 colorSpaceName: NSDeviceRGBColorSpace 298 bitmapFormat: NSAlphaFirstBitmapFormat | NSAlphaNonpremultipliedBitmapFormat 299 bytesPerRow: splash->width * 4 300 bitsPerPixel: 32]; 301 302 NSImage * image = [[NSImage alloc] 303 initWithSize: NSMakeSize(splash->width, splash->height)]; 304 [image setBackgroundColor: [NSColor clearColor]]; 305 306 [image addRepresentation: rep]; 307 float scaleFactor = splash->scaleFactor; 308 if (scaleFactor > 0 && scaleFactor != 1) { 309 NSSize size = [image size]; 310 size.width /= scaleFactor; 311 size.height /= scaleFactor; 312 [image setSize: size]; 313 } 314 315 NSImageView * view = [[NSImageView alloc] init]; 316 317 [view setImage: image]; 318 [view setEditable: NO]; 319 //NOTE: we don't set a 'wait cursor' for the view because: 320 // 1. The Cocoa GUI guidelines suggest to avoid it, and use a progress 321 // bar instead. 322 // 2. There simply isn't an instance of NSCursor that represent 323 // the 'wait cursor'. So that is undoable. 324 325 //TODO: only the first image in an animated gif preserves transparency. 326 // Loos like the splash->screenData contains inappropriate data 327 // for all but the first frame. 328 329 [image release]; 330 [rep release]; 331 332 [splash->window setContentView: view]; 333 [splash->window orderFrontRegardless]; 334 }]; 335 336 [pool drain]; 337 } 338 339 void SplashReconfigureNow(Splash * splash) { 340 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 341 342 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ 343 SplashCenter(splash); 344 345 if (!splash->window) { 346 return; 347 } 348 349 [splash->window orderOut:nil]; 350 [splash->window setFrame: NSMakeRect(splash->x, splash->y, splash->width, splash->height) 351 display: NO]; 352 }]; 353 354 [pool drain]; 355 356 SplashRedrawWindow(splash); 357 } 358 359 void 360 SplashEventLoop(Splash * splash) { 361 362 /* we should have splash _locked_ on entry!!! */ 363 364 while (1) { 365 struct pollfd pfd[1]; 366 int timeout = -1; 367 int ctl = splash->controlpipe[0]; 368 int rc; 369 int pipes_empty; 370 371 pfd[0].fd = ctl; 372 pfd[0].events = POLLIN | POLLPRI; 373 374 errno = 0; 375 if (splash->isVisible>0 && SplashIsStillLooping(splash)) { 376 timeout = splash->time + splash->frames[splash->currentFrame].delay 377 - SplashTime(); 378 if (timeout < 0) { 379 timeout = 0; 380 } 381 } 382 SplashUnlock(splash); 383 rc = poll(pfd, 1, timeout); 384 SplashLock(splash); 385 if (splash->isVisible > 0 && splash->currentFrame >= 0 && 386 SplashTime() >= splash->time + splash->frames[splash->currentFrame].delay) { 387 SplashNextFrame(splash); 388 SplashRedrawWindow(splash); 389 } 390 if (rc <= 0) { 391 errno = 0; 392 continue; 393 } 394 pipes_empty = 0; 395 while(!pipes_empty) { 396 char buf; 397 398 pipes_empty = 1; 399 if (read(ctl, &buf, sizeof(buf)) > 0) { 400 pipes_empty = 0; 401 switch (buf) { 402 case SPLASHCTL_UPDATE: 403 if (splash->isVisible>0) { 404 SplashRedrawWindow(splash); 405 } 406 break; 407 case SPLASHCTL_RECONFIGURE: 408 if (splash->isVisible>0) { 409 SplashReconfigureNow(splash); 410 } 411 break; 412 case SPLASHCTL_QUIT: 413 return; 414 } 415 } 416 } 417 } 418 } 419 420 void * 421 SplashScreenThread(void *param) { 422 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 423 Splash *splash = (Splash *) param; 424 425 SplashLock(splash); 426 pipe(splash->controlpipe); 427 fcntl(splash->controlpipe[0], F_SETFL, 428 fcntl(splash->controlpipe[0], F_GETFL, 0) | O_NONBLOCK); 429 splash->time = SplashTime(); 430 splash->currentFrame = 0; 431 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ 432 SplashCenter(splash); 433 434 splash->window = (void*) [[NSWindow alloc] 435 initWithContentRect: NSMakeRect(splash->x, splash->y, splash->width, splash->height) 436 styleMask: NSBorderlessWindowMask 437 backing: NSBackingStoreBuffered 438 defer: NO 439 screen: SplashNSScreen()]; 440 441 [splash->window setOpaque: NO]; 442 [splash->window setBackgroundColor: [NSColor clearColor]]; 443 }]; 444 fflush(stdout); 445 if (splash->window) { 446 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ 447 [splash->window orderFrontRegardless]; 448 }]; 449 SplashRedrawWindow(splash); 450 SplashEventLoop(splash); 451 } 452 SplashUnlock(splash); 453 SplashDone(splash); 454 455 splash->isVisible=-1; 456 457 [pool drain]; 458 459 return 0; 460 } 461 462 void 463 sendctl(Splash * splash, char code) { 464 if (splash && splash->controlpipe[1]) { 465 write(splash->controlpipe[1], &code, 1); 466 } 467 } 468 469 void 470 SplashClosePlatform(Splash * splash) { 471 sendctl(splash, SPLASHCTL_QUIT); 472 } 473 474 void 475 SplashUpdate(Splash * splash) { 476 sendctl(splash, SPLASHCTL_UPDATE); 477 } 478 479 void 480 SplashReconfigure(Splash * splash) { 481 sendctl(splash, SPLASHCTL_RECONFIGURE); 482 } 483 484 NSString* findScaledImageName(NSString *fileName, NSUInteger dotIndex, NSString *strToAppend) { 485 NSString *fileName2x = nil; 486 if (dotIndex == NSNotFound) { 487 fileName2x = [fileName stringByAppendingString: strToAppend]; 488 } else { 489 fileName2x = [fileName substringToIndex: dotIndex]; 490 fileName2x = [fileName2x stringByAppendingString: strToAppend]; 491 fileName2x = [fileName2x stringByAppendingString: 492 [fileName substringFromIndex: dotIndex]]; 493 } 494 return fileName2x; 495 } 496