1 /*
   2  * Copyright (c) 2012, 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 "LWCToolkit.h"
  27 
  28 /*
  29  * Convert the mode string to the more convinient bits per pixel value
  30  */
  31 static int getBPPFromModeString(CFStringRef mode) 
  32 {
  33     if ((CFStringCompare(mode, CFSTR(kIO30BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)) {
  34         // This is a strange mode, where we using 10 bits per RGB component and pack it into 32 bits
  35         // Java is not ready to work with this mode but we have to specify it as supported
  36         return 30;
  37     }
  38     else if (CFStringCompare(mode, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
  39         return 32;
  40     }
  41     else if (CFStringCompare(mode, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
  42         return 16;
  43     }
  44     else if (CFStringCompare(mode, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
  45         return 8;
  46     }
  47     
  48     return 0;
  49 }
  50 
  51 /*
  52  * Find the best possible match in the list of display modes that we can switch to based on
  53  * the provided parameters.
  54  */
  55 static CGDisplayModeRef getBestModeForParameters(CFArrayRef allModes, int w, int h, int bpp, int refrate) {
  56     CGDisplayModeRef bestGuess = NULL;
  57     CFIndex numModes = CFArrayGetCount(allModes), n;
  58     int thisBpp = 0;
  59     for(n = 0; n < numModes; n++ ) {
  60         CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
  61         if(cRef == NULL) {
  62             continue;
  63         }
  64         CFStringRef modeString = CGDisplayModeCopyPixelEncoding(cRef);
  65         thisBpp = getBPPFromModeString(modeString);
  66         CFRelease(modeString);
  67         if (thisBpp != bpp || (int)CGDisplayModeGetHeight(cRef) != h || (int)CGDisplayModeGetWidth(cRef) != w) {
  68             // One of the key parameters does not match
  69             continue;
  70         }
  71         // Refresh rate might be 0 in display mode and we ask for specific display rate
  72         // but if we do not find exact match then 0 refresh rate might be just Ok
  73         if (CGDisplayModeGetRefreshRate(cRef) == refrate) {
  74             // Exact match
  75             return cRef;
  76         }
  77         if (CGDisplayModeGetRefreshRate(cRef) == 0) {
  78             // Not exactly what was asked for, but may fit our needs if we don't find an exact match
  79             bestGuess = cRef;
  80         }
  81     }
  82     return bestGuess;
  83 }
  84 
  85 /*
  86  * Create a new java.awt.DisplayMode instance based on provided CGDisplayModeRef
  87  */
  88 static jobject createJavaDisplayMode(CGDisplayModeRef mode, JNIEnv *env, jint displayID) {
  89     jobject ret = NULL;
  90     jint h, w, bpp, refrate;
  91     JNF_COCOA_ENTER(env);
  92     CFStringRef currentBPP = CGDisplayModeCopyPixelEncoding(mode);
  93     bpp = getBPPFromModeString(currentBPP);
  94     refrate = CGDisplayModeGetRefreshRate(mode);
  95     h = CGDisplayModeGetHeight(mode);
  96     w = CGDisplayModeGetWidth(mode);
  97     CFRelease(currentBPP);
  98     static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode");
  99     static JNF_CTOR_CACHE(jc_DisplayMode_ctor, jc_DisplayMode, "(IIII)V");
 100     ret = JNFNewObject(env, jc_DisplayMode_ctor, w, h, bpp, refrate);
 101     JNF_COCOA_EXIT(env);
 102     return ret;
 103 }
 104 
 105 
 106 /*
 107  * Class:     sun_awt_CGraphicsDevice
 108  * Method:    nativeGetXResolution
 109  * Signature: (I)D
 110  */
 111 JNIEXPORT jdouble JNICALL
 112 Java_sun_awt_CGraphicsDevice_nativeGetXResolution
 113   (JNIEnv *env, jclass class, jint displayID)
 114 {
 115     // TODO: this is the physically correct answer, but we probably want
 116     // to use NSScreen API instead...
 117     CGSize size = CGDisplayScreenSize(displayID);
 118     CGRect rect = CGDisplayBounds(displayID);
 119     // 1 inch == 25.4 mm
 120     jfloat inches = size.width / 25.4f;
 121     jfloat dpi = rect.size.width / inches;
 122     return dpi;
 123 }
 124 
 125 /*
 126  * Class:     sun_awt_CGraphicsDevice
 127  * Method:    nativeGetYResolution
 128  * Signature: (I)D
 129  */
 130 JNIEXPORT jdouble JNICALL
 131 Java_sun_awt_CGraphicsDevice_nativeGetYResolution
 132   (JNIEnv *env, jclass class, jint displayID)
 133 {
 134     // TODO: this is the physically correct answer, but we probably want
 135     // to use NSScreen API instead...
 136     CGSize size = CGDisplayScreenSize(displayID);
 137     CGRect rect = CGDisplayBounds(displayID);
 138     // 1 inch == 25.4 mm
 139     jfloat inches = size.height / 25.4f;
 140     jfloat dpi = rect.size.height / inches;
 141     return dpi;
 142 }
 143 
 144 /*
 145  * Class:     sun_awt_CGraphicsDevice
 146  * Method:    nativeSetDisplayMode
 147  * Signature: (IIIII)V
 148  */
 149 JNIEXPORT void JNICALL
 150 Java_sun_awt_CGraphicsDevice_nativeSetDisplayMode
 151 (JNIEnv *env, jclass class, jint displayID, jint w, jint h, jint bpp, jint refrate)
 152 {
 153     JNF_COCOA_ENTER(env);
 154     CFArrayRef allModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
 155     CGDisplayModeRef closestMatch = getBestModeForParameters(allModes, (int)w, (int)h, (int)bpp, (int)refrate);
 156     if (closestMatch != NULL) {
 157         [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
 158             CGDisplayConfigRef config;
 159             CGError retCode = CGBeginDisplayConfiguration(&config);
 160             if (retCode == kCGErrorSuccess) {
 161                 CGConfigureDisplayWithDisplayMode(config, displayID, closestMatch, NULL);
 162                 CGCompleteDisplayConfiguration(config, kCGConfigureForAppOnly);
 163                 if (config != NULL) {
 164                     CFRelease(config);
 165                 }
 166             }
 167         }];
 168     }
 169     CFRelease(allModes);
 170     JNF_COCOA_EXIT(env);
 171 }
 172 
 173 /*
 174  * Class:     sun_awt_CGraphicsDevice
 175  * Method:    nativeGetDisplayMode
 176  * Signature: (I)Ljava/awt/DisplayMode
 177  */
 178 JNIEXPORT jobject JNICALL
 179 Java_sun_awt_CGraphicsDevice_nativeGetDisplayMode
 180 (JNIEnv *env, jclass class, jint displayID)
 181 {
 182     jobject ret = NULL;
 183     CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(displayID);
 184     ret = createJavaDisplayMode(currentMode, env, displayID);
 185     CGDisplayModeRelease(currentMode);
 186     return ret;
 187 }
 188 
 189 /*
 190  * Class:     sun_awt_CGraphicsDevice
 191  * Method:    nativeGetDisplayMode
 192  * Signature: (I)[Ljava/awt/DisplayModes
 193  */
 194 JNIEXPORT jobjectArray JNICALL
 195 Java_sun_awt_CGraphicsDevice_nativeGetDisplayModes
 196 (JNIEnv *env, jclass class, jint displayID)
 197 {
 198     jobjectArray jreturnArray = NULL;
 199     JNF_COCOA_ENTER(env);
 200     CFArrayRef allModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
 201     CFIndex numModes = CFArrayGetCount(allModes);
 202     static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode");
 203 
 204     jreturnArray = JNFNewObjectArray(env, &jc_DisplayMode, (jsize) numModes);
 205     if (!jreturnArray) {
 206         NSLog(@"CGraphicsDevice can't create java array of DisplayMode objects");
 207         return nil;
 208     }
 209 
 210     CFIndex n;
 211     for (n=0; n < numModes; n++) {
 212         CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
 213         if (cRef != NULL) {
 214             jobject oneMode = createJavaDisplayMode(cRef, env, displayID);
 215             (*env)->SetObjectArrayElement(env, jreturnArray, n, oneMode);
 216             if ((*env)->ExceptionOccurred(env)) {
 217                 (*env)->ExceptionDescribe(env);
 218                 (*env)->ExceptionClear(env);
 219                 continue;
 220             }
 221             (*env)->DeleteLocalRef(env, oneMode);
 222         }
 223     }
 224     CFRelease(allModes);
 225     JNF_COCOA_EXIT(env);
 226 
 227     return jreturnArray;
 228 }