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