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