1 /*
2 * Copyright (c) 2011, 2016, 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-dependant 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 SplashUpdateScreenData(splash);
261
262 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
263 // NSDeviceRGBColorSpace vs. NSCalibratedRGBColorSpace ?
264 NSBitmapImageRep * rep = [[NSBitmapImageRep alloc]
265 initWithBitmapDataPlanes: (unsigned char**)&splash->screenData
266 pixelsWide: splash->width
267 pixelsHigh: splash->height
268 bitsPerSample: 8
269 samplesPerPixel: 4
270 hasAlpha: YES
271 isPlanar: NO
272 colorSpaceName: NSDeviceRGBColorSpace
273 bitmapFormat: NSAlphaFirstBitmapFormat | NSAlphaNonpremultipliedBitmapFormat
274 bytesPerRow: splash->width * 4
275 bitsPerPixel: 32];
276
277 NSImage * image = [[NSImage alloc]
278 initWithSize: NSMakeSize(splash->width, splash->height)];
279 [image setBackgroundColor: [NSColor clearColor]];
280
281 [image addRepresentation: rep];
282 float scaleFactor = splash->scaleFactor;
283 if (scaleFactor > 0 && scaleFactor != 1) {
284 NSSize size = [image size];
285 size.width /= scaleFactor;
286 size.height /= scaleFactor;
287 [image setSize: size];
288 }
289
290 NSImageView * view = [[NSImageView alloc] init];
291
292 [view setImage: image];
293 [view setEditable: NO];
294 //NOTE: we don't set a 'wait cursor' for the view because:
295 // 1. The Cocoa GUI guidelines suggest to avoid it, and use a progress
296 // bar instead.
297 // 2. There simply isn't an instance of NSCursor that represent
298 // the 'wait cursor'. So that is undoable.
299
300 //TODO: only the first image in an animated gif preserves transparency.
301 // Loos like the splash->screenData contains inappropriate data
302 // for all but the first frame.
303
304 [image release];
305 [rep release];
306
307 [splash->window setContentView: view];
308 [splash->window orderFrontRegardless];
309 }];
310
311 [pool drain];
312 }
313
314 void SplashReconfigureNow(Splash * splash) {
315 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
316
317 [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
318 SplashCenter(splash);
319
320 if (!splash->window) {
321 return;
322 }
323
324 [splash->window orderOut:nil];
325 [splash->window setFrame: NSMakeRect(splash->x, splash->y, splash->width, splash->height)
326 display: NO];
327 }];
328
329 [pool drain];
330
331 SplashRedrawWindow(splash);
332 }
333
334 void
335 SplashEventLoop(Splash * splash) {
336
337 /* we should have splash _locked_ on entry!!! */
338
339 while (1) {
340 struct pollfd pfd[1];
341 int timeout = -1;
342 int ctl = splash->controlpipe[0];
343 int rc;
344 int pipes_empty;
345
346 pfd[0].fd = ctl;
347 pfd[0].events = POLLIN | POLLPRI;
348
349 errno = 0;
350 if (splash->isVisible>0 && SplashIsStillLooping(splash)) {
351 timeout = splash->time + splash->frames[splash->currentFrame].delay
352 - SplashTime();
353 if (timeout < 0) {
354 timeout = 0;
355 }
356 }
357 SplashUnlock(splash);
358 rc = poll(pfd, 1, timeout);
359 SplashLock(splash);
360 if (splash->isVisible > 0 && splash->currentFrame >= 0 &&
361 SplashTime() >= splash->time + splash->frames[splash->currentFrame].delay) {
362 SplashNextFrame(splash);
363 SplashRedrawWindow(splash);
364 }
365 if (rc <= 0) {
366 errno = 0;
367 continue;
368 }
369 pipes_empty = 0;
370 while(!pipes_empty) {
371 char buf;
372
373 pipes_empty = 1;
374 if (read(ctl, &buf, sizeof(buf)) > 0) {
375 pipes_empty = 0;
376 switch (buf) {
377 case SPLASHCTL_UPDATE:
378 if (splash->isVisible>0) {
379 SplashRedrawWindow(splash);
380 }
381 break;
382 case SPLASHCTL_RECONFIGURE:
383 if (splash->isVisible>0) {
384 SplashReconfigureNow(splash);
385 }
386 break;
387 case SPLASHCTL_QUIT:
388 return;
389 }
390 }
391 }
392 }
393 }
394
395 void *
396 SplashScreenThread(void *param) {
397 objc_registerThreadWithCollector();
398
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