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