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