1/*
2 * Copyright (c) 2012, 2014, 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#import "LWCToolkit.h"
27#import "ThreadUtilities.h"
28
29#import <JavaNativeFoundation/JavaNativeFoundation.h>
30
31/*
32 * Convert the mode string to the more convinient bits per pixel value
33 */
34static int getBPPFromModeString(CFStringRef mode)
35{
36    if ((CFStringCompare(mode, CFSTR(kIO30BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)) {
37        // This is a strange mode, where we using 10 bits per RGB component and pack it into 32 bits
38        // Java is not ready to work with this mode but we have to specify it as supported
39        return 30;
40    }
41    else if (CFStringCompare(mode, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
42        return 32;
43    }
44    else if (CFStringCompare(mode, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
45        return 16;
46    }
47    else if (CFStringCompare(mode, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
48        return 8;
49    }
50
51    return 0;
52}
53
54static BOOL isValidDisplayMode(CGDisplayModeRef mode){
55    return (1 < CGDisplayModeGetWidth(mode) && 1 < CGDisplayModeGetHeight(mode));
56}
57
58static CFMutableArrayRef getAllValidDisplayModes(jint displayID){
59    CFArrayRef allModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
60
61    CFIndex numModes = CFArrayGetCount(allModes);
62    CFMutableArrayRef validModes = CFArrayCreateMutable(kCFAllocatorDefault, numModes + 1, &kCFTypeArrayCallBacks);
63
64    CFIndex n;
65    for (n=0; n < numModes; n++) {
66        CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
67        if (cRef != NULL && isValidDisplayMode(cRef)) {
68            CFArrayAppendValue(validModes, cRef);
69        }
70    }
71    CFRelease(allModes);
72    
73    CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(displayID);
74
75    BOOL containsCurrentMode = NO;
76    numModes = CFArrayGetCount(validModes);
77    for (n=0; n < numModes; n++) {
78        if(CFArrayGetValueAtIndex(validModes, n) == currentMode){
79            containsCurrentMode = YES;
80            break;
81        }
82    }
83
84    if (!containsCurrentMode) {
85        CFArrayAppendValue(validModes, currentMode);
86    }
87    CGDisplayModeRelease(currentMode);
88
89    return validModes;
90}
91
92/*
93 * Find the best possible match in the list of display modes that we can switch to based on
94 * the provided parameters.
95 */
96static CGDisplayModeRef getBestModeForParameters(CFArrayRef allModes, int w, int h, int bpp, int refrate) {
97    CGDisplayModeRef bestGuess = NULL;
98    CFIndex numModes = CFArrayGetCount(allModes), n;
99    int thisBpp = 0;
100    for(n = 0; n < numModes; n++ ) {
101        CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
102        if(cRef == NULL) {
103            continue;
104        }
105        CFStringRef modeString = CGDisplayModeCopyPixelEncoding(cRef);
106        thisBpp = getBPPFromModeString(modeString);
107        CFRelease(modeString);
108        if (thisBpp != bpp || (int)CGDisplayModeGetHeight(cRef) != h || (int)CGDisplayModeGetWidth(cRef) != w) {
109            // One of the key parameters does not match
110            continue;
111        }
112
113        if (refrate == 0) { // REFRESH_RATE_UNKNOWN
114            return cRef;
115        }
116
117        // Refresh rate might be 0 in display mode and we ask for specific display rate
118        // but if we do not find exact match then 0 refresh rate might be just Ok
119        if (CGDisplayModeGetRefreshRate(cRef) == refrate) {
120            // Exact match
121            return cRef;
122        }
123        if (CGDisplayModeGetRefreshRate(cRef) == 0) {
124            // Not exactly what was asked for, but may fit our needs if we don't find an exact match
125            bestGuess = cRef;
126        }
127    }
128    return bestGuess;
129}
130
131/*
132 * Create a new java.awt.DisplayMode instance based on provided CGDisplayModeRef
133 */
134static jobject createJavaDisplayMode(CGDisplayModeRef mode, JNIEnv *env, jint displayID) {
135    jobject ret = NULL;
136    jint h, w, bpp, refrate;
137    JNF_COCOA_ENTER(env);
138    CFStringRef currentBPP = CGDisplayModeCopyPixelEncoding(mode);
139    bpp = getBPPFromModeString(currentBPP);
140    refrate = CGDisplayModeGetRefreshRate(mode);
141    h = CGDisplayModeGetHeight(mode);
142    w = CGDisplayModeGetWidth(mode);
143    CFRelease(currentBPP);
144    static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode");
145    static JNF_CTOR_CACHE(jc_DisplayMode_ctor, jc_DisplayMode, "(IIII)V");
146    ret = JNFNewObject(env, jc_DisplayMode_ctor, w, h, bpp, refrate);
147    JNF_COCOA_EXIT(env);
148    return ret;
149}
150
151
152/*
153 * Class:     sun_awt_CGraphicsDevice
154 * Method:    nativeGetXResolution
155 * Signature: (I)D
156 */
157JNIEXPORT jdouble JNICALL
158Java_sun_awt_CGraphicsDevice_nativeGetXResolution
159  (JNIEnv *env, jclass class, jint displayID)
160{
161    // CGDisplayScreenSize can return 0 if displayID is invalid
162    CGSize size = CGDisplayScreenSize(displayID);
163    CGRect rect = CGDisplayBounds(displayID);
164    // 1 inch == 25.4 mm
165    jfloat inches = size.width / 25.4f;
166    return inches > 0 ? rect.size.width / inches : 72;
167}
168
169/*
170 * Class:     sun_awt_CGraphicsDevice
171 * Method:    nativeGetYResolution
172 * Signature: (I)D
173 */
174JNIEXPORT jdouble JNICALL
175Java_sun_awt_CGraphicsDevice_nativeGetYResolution
176  (JNIEnv *env, jclass class, jint displayID)
177{
178    // CGDisplayScreenSize can return 0 if displayID is invalid
179    CGSize size = CGDisplayScreenSize(displayID);
180    CGRect rect = CGDisplayBounds(displayID);
181    // 1 inch == 25.4 mm
182    jfloat inches = size.height / 25.4f;
183    return inches > 0 ? rect.size.height / inches : 72;
184}
185
186/*
187 * Class:     sun_awt_CGraphicsDevice
188 * Method:    nativeGetScreenInsets
189 * Signature: (I)D
190 */
191JNIEXPORT jobject JNICALL
192Java_sun_awt_CGraphicsDevice_nativeGetScreenInsets
193  (JNIEnv *env, jclass class, jint displayID)
194{
195    jobject ret = NULL;
196    __block NSRect frame = NSZeroRect;
197    __block NSRect visibleFrame = NSZeroRect;
198JNF_COCOA_ENTER(env);
199    
200    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
201        NSArray *screens = [NSScreen screens];
202        for (NSScreen *screen in screens) {
203            NSDictionary *screenInfo = [screen deviceDescription];
204            NSNumber *screenID = [screenInfo objectForKey:@"NSScreenNumber"];
205            if ([screenID unsignedIntValue] == displayID){
206                frame = [screen frame];
207                visibleFrame = [screen visibleFrame];
208                break;
209            }
210        }
211    }];
212    // Convert between Cocoa's coordinate system and Java.
213    jint bottom = visibleFrame.origin.y - frame.origin.y;
214    jint top = frame.size.height - visibleFrame.size.height - bottom;
215    jint left = visibleFrame.origin.x - frame.origin.x;
216    jint right = frame.size.width - visibleFrame.size.width - left;
217    
218    static JNF_CLASS_CACHE(jc_Insets, "java/awt/Insets");
219    static JNF_CTOR_CACHE(jc_Insets_ctor, jc_Insets, "(IIII)V");
220    ret = JNFNewObject(env, jc_Insets_ctor, top, left, bottom, right);
221
222JNF_COCOA_EXIT(env);
223
224    return ret;
225}
226
227/*
228 * Class:     sun_awt_CGraphicsDevice
229 * Method:    nativeSetDisplayMode
230 * Signature: (IIIII)V
231 */
232JNIEXPORT void JNICALL
233Java_sun_awt_CGraphicsDevice_nativeSetDisplayMode
234(JNIEnv *env, jclass class, jint displayID, jint w, jint h, jint bpp, jint refrate)
235{
236    JNF_COCOA_ENTER(env);
237    CFArrayRef allModes = getAllValidDisplayModes(displayID);
238    CGDisplayModeRef closestMatch = getBestModeForParameters(allModes, (int)w, (int)h, (int)bpp, (int)refrate);
239    
240    __block CGError retCode = kCGErrorSuccess;
241    if (closestMatch != NULL) {
242        CGDisplayModeRetain(closestMatch);
243        [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
244            CGDisplayConfigRef config;
245            retCode = CGBeginDisplayConfiguration(&config);
246            if (retCode == kCGErrorSuccess) {
247                CGConfigureDisplayWithDisplayMode(config, displayID, closestMatch, NULL);
248                retCode = CGCompleteDisplayConfiguration(config, kCGConfigureForAppOnly);
249            }
250            CGDisplayModeRelease(closestMatch);
251        }];
252    } else {
253        [JNFException raise:env as:kIllegalArgumentException reason:"Invalid display mode"];
254    }
255
256    if (retCode != kCGErrorSuccess){
257        [JNFException raise:env as:kIllegalArgumentException reason:"Unable to set display mode!"];
258    }
259    CFRelease(allModes);
260    JNF_COCOA_EXIT(env);
261}
262/*
263 * Class:     sun_awt_CGraphicsDevice
264 * Method:    nativeGetDisplayMode
265 * Signature: (I)Ljava/awt/DisplayMode
266 */
267JNIEXPORT jobject JNICALL
268Java_sun_awt_CGraphicsDevice_nativeGetDisplayMode
269(JNIEnv *env, jclass class, jint displayID)
270{
271    jobject ret = NULL;
272    CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(displayID);
273    ret = createJavaDisplayMode(currentMode, env, displayID);
274    CGDisplayModeRelease(currentMode);
275    return ret;
276}
277
278/*
279 * Class:     sun_awt_CGraphicsDevice
280 * Method:    nativeGetDisplayMode
281 * Signature: (I)[Ljava/awt/DisplayModes
282 */
283JNIEXPORT jobjectArray JNICALL
284Java_sun_awt_CGraphicsDevice_nativeGetDisplayModes
285(JNIEnv *env, jclass class, jint displayID)
286{
287    jobjectArray jreturnArray = NULL;
288    JNF_COCOA_ENTER(env);
289    CFArrayRef allModes = getAllValidDisplayModes(displayID);
290
291    CFIndex numModes = CFArrayGetCount(allModes);
292    static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode");
293
294    jreturnArray = JNFNewObjectArray(env, &jc_DisplayMode, (jsize) numModes);
295    if (!jreturnArray) {
296        NSLog(@"CGraphicsDevice can't create java array of DisplayMode objects");
297        return nil;
298    }
299
300    CFIndex n;
301    for (n=0; n < numModes; n++) {
302        CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
303        if (cRef != NULL) {
304            jobject oneMode = createJavaDisplayMode(cRef, env, displayID);
305            (*env)->SetObjectArrayElement(env, jreturnArray, n, oneMode);
306            if ((*env)->ExceptionOccurred(env)) {
307                (*env)->ExceptionDescribe(env);
308                (*env)->ExceptionClear(env);
309                continue;
310            }
311            (*env)->DeleteLocalRef(env, oneMode);
312        }
313    }
314    CFRelease(allModes);
315    JNF_COCOA_EXIT(env);
316
317    return jreturnArray;
318}
319
320/*
321 * Class:     sun_awt_CGraphicsDevice
322 * Method:    nativeGetScaleFactor
323 * Signature: (I)D
324 */
325JNIEXPORT jdouble JNICALL
326Java_sun_awt_CGraphicsDevice_nativeGetScaleFactor
327(JNIEnv *env, jclass class, jint displayID)
328{
329    __block jdouble ret = 1.0f;
330
331JNF_COCOA_ENTER(env);
332
333    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
334        NSArray *screens = [NSScreen screens];
335        for (NSScreen *screen in screens) {
336            NSDictionary *screenInfo = [screen deviceDescription];
337            NSNumber *screenID = [screenInfo objectForKey:@"NSScreenNumber"];
338            if ([screenID unsignedIntValue] == displayID){
339                if ([screen respondsToSelector:@selector(backingScaleFactor)]) {
340                    ret = [screen backingScaleFactor];
341                }
342                break;
343            }
344        }
345    }];
346
347JNF_COCOA_EXIT(env);
348    return ret;
349}
350