| /* |
| * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| #include "splashscreen_impl.h" |
| |
| #import <Cocoa/Cocoa.h> |
| #import <objc/objc-auto.h> |
| |
| #import <JavaNativeFoundation/JavaNativeFoundation.h> |
| #import "NSApplicationAWT.h" |
| |
| #include <sys/time.h> |
| #include <pthread.h> |
| #include <iconv.h> |
| #include <langinfo.h> |
| #include <locale.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <errno.h> |
| #include <sys/types.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <dlfcn.h> |
| |
| #include <sizecalc.h> |
| #import "ThreadUtilities.h" |
| |
| static NSScreen* SplashNSScreen() |
| { |
| return [[NSScreen screens] objectAtIndex: 0]; |
| } |
| |
| static void SplashCenter(Splash * splash) |
| { |
| NSRect screenFrame = [SplashNSScreen() frame]; |
| |
| splash->x = (screenFrame.size.width - splash->width) / 2; |
| splash->y = (screenFrame.size.height - splash->height) / 2 + screenFrame.origin.y; |
| } |
| |
| unsigned |
| SplashTime(void) { |
| struct timeval tv; |
| struct timezone tz; |
| unsigned long long msec; |
| |
| gettimeofday(&tv, &tz); |
| msec = (unsigned long long) tv.tv_sec * 1000 + |
| (unsigned long long) tv.tv_usec / 1000; |
| |
| return (unsigned) msec; |
| } |
| |
| /* Could use npt but decided to cut down on linked code size */ |
| char* SplashConvertStringAlloc(const char* in, int* size) { |
| const char *codeset; |
| const char *codeset_out; |
| iconv_t cd; |
| size_t rc; |
| char *buf = NULL, *out; |
| size_t bufSize, inSize, outSize; |
| const char* old_locale; |
| |
| if (!in) { |
| return NULL; |
| } |
| old_locale = setlocale(LC_ALL, ""); |
| |
| codeset = nl_langinfo(CODESET); |
| if ( codeset == NULL || codeset[0] == 0 ) { |
| goto done; |
| } |
| /* we don't need BOM in output so we choose native BE or LE encoding here */ |
| codeset_out = (platformByteOrder()==BYTE_ORDER_MSBFIRST) ? |
| "UCS-2BE" : "UCS-2LE"; |
| |
| cd = iconv_open(codeset_out, codeset); |
| if (cd == (iconv_t)-1 ) { |
| goto done; |
| } |
| inSize = strlen(in); |
| buf = SAFE_SIZE_ARRAY_ALLOC(malloc, inSize, 2); |
| if (!buf) { |
| return NULL; |
| } |
| bufSize = inSize*2; // need 2 bytes per char for UCS-2, this is |
| // 2 bytes per source byte max |
| out = buf; outSize = bufSize; |
| /* linux iconv wants char** source and solaris wants const char**... |
| cast to void* */ |
| rc = iconv(cd, (void*)&in, &inSize, &out, &outSize); |
| iconv_close(cd); |
| |
| if (rc == (size_t)-1) { |
| free(buf); |
| buf = NULL; |
| } else { |
| if (size) { |
| *size = (bufSize-outSize)/2; /* bytes to wchars */ |
| } |
| } |
| done: |
| setlocale(LC_ALL, old_locale); |
| return buf; |
| } |
| |
| BOOL isSWTRunning() { |
| char envVar[80]; |
| // If this property is present we are running SWT |
| snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid()); |
| return getenv(envVar) != NULL; |
| } |
| |
| char* SplashGetScaledImageName(const char* jar, const char* file, |
| float *scaleFactor) { |
| *scaleFactor = 1; |
| |
| if(isSWTRunning()){ |
| return nil; |
| } |
| |
| NSAutoreleasePool *pool = [NSAutoreleasePool new]; |
| char* scaledFile = nil; |
| __block float screenScaleFactor = 1; |
| |
| [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ |
| // initialize NSApplication and AWT stuff |
| [NSApplicationAWT sharedApplication]; |
| screenScaleFactor = [SplashNSScreen() backingScaleFactor]; |
| }]; |
| |
| if (screenScaleFactor > 1) { |
| NSString *fileName = [NSString stringWithUTF8String: file]; |
| NSUInteger length = [fileName length]; |
| NSRange range = [fileName rangeOfString: @"." |
| options:NSBackwardsSearch]; |
| NSUInteger dotIndex = range.location; |
| NSString *fileName2x = nil; |
| |
| if (dotIndex == NSNotFound) { |
| fileName2x = [fileName stringByAppendingString: @"@2x"]; |
| } else { |
| fileName2x = [fileName substringToIndex: dotIndex]; |
| fileName2x = [fileName2x stringByAppendingString: @"@2x"]; |
| fileName2x = [fileName2x stringByAppendingString: |
| [fileName substringFromIndex: dotIndex]]; |
| } |
| |
| if ((fileName2x != nil) && (jar || [[NSFileManager defaultManager] |
| fileExistsAtPath: fileName2x])){ |
| *scaleFactor = 2; |
| scaledFile = strdup([fileName2x UTF8String]); |
| } |
| } |
| [pool drain]; |
| return scaledFile; |
| } |
| |
| void |
| SplashInitPlatform(Splash * splash) { |
| pthread_mutex_init(&splash->lock, NULL); |
| |
| splash->maskRequired = 0; |
| |
| |
| //TODO: the following is too much of a hack but should work in 90% cases. |
| // besides we don't use device-dependant drawing, so probably |
| // that's very fine indeed |
| splash->byteAlignment = 1; |
| initFormat(&splash->screenFormat, 0xff << 8, |
| 0xff << 16, 0xff << 24, 0xff << 0); |
| splash->screenFormat.byteOrder = 1 ? BYTE_ORDER_LSBFIRST : BYTE_ORDER_MSBFIRST; |
| splash->screenFormat.depthBytes = 4; |
| |
| // If we are running SWT we should not start a runLoop |
| if (!isSWTRunning()) { |
| [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^() { |
| [NSApplicationAWT runAWTLoopWithApp:[NSApplicationAWT sharedApplication]]; |
| }]; |
| } |
| } |
| |
| void |
| SplashCleanupPlatform(Splash * splash) { |
| splash->maskRequired = 0; |
| } |
| |
| void |
| SplashDonePlatform(Splash * splash) { |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| |
| pthread_mutex_destroy(&splash->lock); |
| [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ |
| if (splash->window) { |
| [splash->window orderOut:nil]; |
| [splash->window release]; |
| } |
| }]; |
| [pool drain]; |
| } |
| |
| void |
| SplashLock(Splash * splash) { |
| pthread_mutex_lock(&splash->lock); |
| } |
| |
| void |
| SplashUnlock(Splash * splash) { |
| pthread_mutex_unlock(&splash->lock); |
| } |
| |
| void |
| SplashInitFrameShape(Splash * splash, int imageIndex) { |
| // No shapes, we rely on alpha compositing |
| } |
| |
| void * SplashScreenThread(void *param); |
| void |
| SplashCreateThread(Splash * splash) { |
| pthread_t thr; |
| pthread_attr_t attr; |
| int rc; |
| |
| pthread_attr_init(&attr); |
| rc = pthread_create(&thr, &attr, SplashScreenThread, (void *) splash); |
| } |
| |
| void |
| SplashRedrawWindow(Splash * splash) { |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| |
| SplashUpdateScreenData(splash); |
| |
| [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ |
| // NSDeviceRGBColorSpace vs. NSCalibratedRGBColorSpace ? |
| NSBitmapImageRep * rep = [[NSBitmapImageRep alloc] |
| initWithBitmapDataPlanes: (unsigned char**)&splash->screenData |
| pixelsWide: splash->width |
| pixelsHigh: splash->height |
| bitsPerSample: 8 |
| samplesPerPixel: 4 |
| hasAlpha: YES |
| isPlanar: NO |
| colorSpaceName: NSDeviceRGBColorSpace |
| bitmapFormat: NSAlphaFirstBitmapFormat | NSAlphaNonpremultipliedBitmapFormat |
| bytesPerRow: splash->width * 4 |
| bitsPerPixel: 32]; |
| |
| NSImage * image = [[NSImage alloc] |
| initWithSize: NSMakeSize(splash->width, splash->height)]; |
| [image setBackgroundColor: [NSColor clearColor]]; |
| |
| [image addRepresentation: rep]; |
| float scaleFactor = splash->scaleFactor; |
| if (scaleFactor > 0 && scaleFactor != 1) { |
| [image setScalesWhenResized:YES]; |
| NSSize size = [image size]; |
| size.width /= scaleFactor; |
| size.height /= scaleFactor; |
| [image setSize: size]; |
| } |
| |
| NSImageView * view = [[NSImageView alloc] init]; |
| |
| [view setImage: image]; |
| [view setEditable: NO]; |
| //NOTE: we don't set a 'wait cursor' for the view because: |
| // 1. The Cocoa GUI guidelines suggest to avoid it, and use a progress |
| // bar instead. |
| // 2. There simply isn't an instance of NSCursor that represent |
| // the 'wait cursor'. So that is undoable. |
| |
| //TODO: only the first image in an animated gif preserves transparency. |
| // Loos like the splash->screenData contains inappropriate data |
| // for all but the first frame. |
| |
| [image release]; |
| [rep release]; |
| |
| [splash->window setContentView: view]; |
| [splash->window orderFrontRegardless]; |
| }]; |
| |
| [pool drain]; |
| } |
| |
| void SplashReconfigureNow(Splash * splash) { |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| |
| [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ |
| SplashCenter(splash); |
| |
| if (!splash->window) { |
| return; |
| } |
| |
| [splash->window orderOut:nil]; |
| [splash->window setFrame: NSMakeRect(splash->x, splash->y, splash->width, splash->height) |
| display: NO]; |
| }]; |
| |
| [pool drain]; |
| |
| SplashRedrawWindow(splash); |
| } |
| |
| void |
| SplashEventLoop(Splash * splash) { |
| |
| /* we should have splash _locked_ on entry!!! */ |
| |
| while (1) { |
| struct pollfd pfd[1]; |
| int timeout = -1; |
| int ctl = splash->controlpipe[0]; |
| int rc; |
| int pipes_empty; |
| |
| pfd[0].fd = ctl; |
| pfd[0].events = POLLIN | POLLPRI; |
| |
| errno = 0; |
| if (splash->isVisible>0 && SplashIsStillLooping(splash)) { |
| timeout = splash->time + splash->frames[splash->currentFrame].delay |
| - SplashTime(); |
| if (timeout < 0) { |
| timeout = 0; |
| } |
| } |
| SplashUnlock(splash); |
| rc = poll(pfd, 1, timeout); |
| SplashLock(splash); |
| if (splash->isVisible > 0 && splash->currentFrame >= 0 && |
| SplashTime() >= splash->time + splash->frames[splash->currentFrame].delay) { |
| SplashNextFrame(splash); |
| SplashRedrawWindow(splash); |
| } |
| if (rc <= 0) { |
| errno = 0; |
| continue; |
| } |
| pipes_empty = 0; |
| while(!pipes_empty) { |
| char buf; |
| |
| pipes_empty = 1; |
| if (read(ctl, &buf, sizeof(buf)) > 0) { |
| pipes_empty = 0; |
| switch (buf) { |
| case SPLASHCTL_UPDATE: |
| if (splash->isVisible>0) { |
| SplashRedrawWindow(splash); |
| } |
| break; |
| case SPLASHCTL_RECONFIGURE: |
| if (splash->isVisible>0) { |
| SplashReconfigureNow(splash); |
| } |
| break; |
| case SPLASHCTL_QUIT: |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| void * |
| SplashScreenThread(void *param) { |
| objc_registerThreadWithCollector(); |
| |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| Splash *splash = (Splash *) param; |
| |
| SplashLock(splash); |
| pipe(splash->controlpipe); |
| fcntl(splash->controlpipe[0], F_SETFL, |
| fcntl(splash->controlpipe[0], F_GETFL, 0) | O_NONBLOCK); |
| splash->time = SplashTime(); |
| splash->currentFrame = 0; |
| [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ |
| SplashCenter(splash); |
| |
| splash->window = (void*) [[NSWindow alloc] |
| initWithContentRect: NSMakeRect(splash->x, splash->y, splash->width, splash->height) |
| styleMask: NSBorderlessWindowMask |
| backing: NSBackingStoreBuffered |
| defer: NO |
| screen: SplashNSScreen()]; |
| |
| [splash->window setOpaque: NO]; |
| [splash->window setBackgroundColor: [NSColor clearColor]]; |
| }]; |
| fflush(stdout); |
| if (splash->window) { |
| [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ |
| [splash->window orderFrontRegardless]; |
| }]; |
| SplashRedrawWindow(splash); |
| SplashEventLoop(splash); |
| } |
| SplashUnlock(splash); |
| SplashDone(splash); |
| |
| splash->isVisible=-1; |
| |
| [pool drain]; |
| |
| return 0; |
| } |
| |
| void |
| sendctl(Splash * splash, char code) { |
| if (splash && splash->controlpipe[1]) { |
| write(splash->controlpipe[1], &code, 1); |
| } |
| } |
| |
| void |
| SplashClosePlatform(Splash * splash) { |
| sendctl(splash, SPLASHCTL_QUIT); |
| } |
| |
| void |
| SplashUpdate(Splash * splash) { |
| sendctl(splash, SPLASHCTL_UPDATE); |
| } |
| |
| void |
| SplashReconfigure(Splash * splash) { |
| sendctl(splash, SPLASHCTL_RECONFIGURE); |
| } |
| |