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 
  72         if (refrate == 0) { // REFRESH_RATE_UNKNOWN
  73             return cRef;
  74         }
  75 
  76         // Refresh rate might be 0 in display mode and we ask for specific display rate
  77         // but if we do not find exact match then 0 refresh rate might be just Ok
  78         if (CGDisplayModeGetRefreshRate(cRef) == refrate) {
  79             // Exact match
  80             return cRef;
  81         }
  82         if (CGDisplayModeGetRefreshRate(cRef) == 0) {
  83             // Not exactly what was asked for, but may fit our needs if we don't find an exact match
  84             bestGuess = cRef;
  85         }
  86     }
  87     return bestGuess;
  88 }
  89 
  90 /*
  91  * Create a new java.awt.DisplayMode instance based on provided CGDisplayModeRef
  92  */
  93 static jobject createJavaDisplayMode(CGDisplayModeRef mode, JNIEnv *env, jint displayID) {
  94     jobject ret = NULL;
  95     jint h, w, bpp, refrate;
  96     JNF_COCOA_ENTER(env);
  97     CFStringRef currentBPP = CGDisplayModeCopyPixelEncoding(mode);
  98     bpp = getBPPFromModeString(currentBPP);
  99     refrate = CGDisplayModeGetRefreshRate(mode);
 100     h = CGDisplayModeGetHeight(mode);
 101     w = CGDisplayModeGetWidth(mode);
 102     CFRelease(currentBPP);
 103     static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode");
 104     static JNF_CTOR_CACHE(jc_DisplayMode_ctor, jc_DisplayMode, "(IIII)V");
 105     ret = JNFNewObject(env, jc_DisplayMode_ctor, w, h, bpp, refrate);
 106     JNF_COCOA_EXIT(env);
 107     return ret;
 108 }
 109 
 110 
 111 /*
 112  * Class:     sun_awt_CGraphicsDevice
 113  * Method:    nativeGetXResolution
 114  * Signature: (I)D
 115  */
 116 JNIEXPORT jdouble JNICALL
 117 Java_sun_awt_CGraphicsDevice_nativeGetXResolution
 118   (JNIEnv *env, jclass class, jint displayID)
 119 {
 120     // TODO: this is the physically correct answer, but we probably want
 121     // to use NSScreen API instead...
 122     CGSize size = CGDisplayScreenSize(displayID);
 123     CGRect rect = CGDisplayBounds(displayID);
 124     // 1 inch == 25.4 mm
 125     jfloat inches = size.width / 25.4f;
 126     jfloat dpi = rect.size.width / inches;
 127     return dpi;
 128 }
 129 
 130 /*
 131  * Class:     sun_awt_CGraphicsDevice
 132  * Method:    nativeGetYResolution
 133  * Signature: (I)D
 134  */
 135 JNIEXPORT jdouble JNICALL
 136 Java_sun_awt_CGraphicsDevice_nativeGetYResolution
 137   (JNIEnv *env, jclass class, jint displayID)
 138 {
 139     // TODO: this is the physically correct answer, but we probably want
 140     // to use NSScreen API instead...
 141     CGSize size = CGDisplayScreenSize(displayID);
 142     CGRect rect = CGDisplayBounds(displayID);
 143     // 1 inch == 25.4 mm
 144     jfloat inches = size.height / 25.4f;
 145     jfloat dpi = rect.size.height / inches;
 146     return dpi;
 147 }
 148 
 149 /*
 150  * Class:     sun_awt_CGraphicsDevice
 151  * Method:    nativeSetDisplayMode
 152  * Signature: (IIIII)V
 153  */
 154 JNIEXPORT void JNICALL
 155 Java_sun_awt_CGraphicsDevice_nativeSetDisplayMode
 156 (JNIEnv *env, jclass class, jint displayID, jint w, jint h, jint bpp, jint refrate)
 157 {
 158     JNF_COCOA_ENTER(env);
 159     CFArrayRef allModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
 160     CGDisplayModeRef closestMatch = getBestModeForParameters(allModes, (int)w, (int)h, (int)bpp, (int)refrate);
 161     if (closestMatch != NULL) {
 162         [JNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
 163             CGDisplayConfigRef config;
 164             CGError retCode = CGBeginDisplayConfiguration(&config);
 165             if (retCode == kCGErrorSuccess) {
 166                 CGConfigureDisplayWithDisplayMode(config, displayID, closestMatch, NULL);
 167                 CGCompleteDisplayConfiguration(config, kCGConfigureForAppOnly);
 168                 if (config != NULL) {
 169                     CFRelease(config);
 170                 }
 171             }
 172         }];
 173     } else {
 174         [JNFException raise:env as:kIllegalArgumentException reason:"Invalid display mode"];
 175     }
 176     
 177     CFRelease(allModes);
 178     JNF_COCOA_EXIT(env);
 179 }
 180 
 181 /*
 182  * Class:     sun_awt_CGraphicsDevice
 183  * Method:    nativeGetDisplayMode
 184  * Signature: (I)Ljava/awt/DisplayMode
 185  */
 186 JNIEXPORT jobject JNICALL
 187 Java_sun_awt_CGraphicsDevice_nativeGetDisplayMode
 188 (JNIEnv *env, jclass class, jint displayID)
 189 {
 190     jobject ret = NULL;
 191     CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(displayID);
 192     ret = createJavaDisplayMode(currentMode, env, displayID);
 193     CGDisplayModeRelease(currentMode);
 194     return ret;
 195 }
 196 
 197 /*
 198  * Class:     sun_awt_CGraphicsDevice
 199  * Method:    nativeGetDisplayMode
 200  * Signature: (I)[Ljava/awt/DisplayModes
 201  */
 202 JNIEXPORT jobjectArray JNICALL
 203 Java_sun_awt_CGraphicsDevice_nativeGetDisplayModes
 204 (JNIEnv *env, jclass class, jint displayID)
 205 {
 206     jobjectArray jreturnArray = NULL;
 207     JNF_COCOA_ENTER(env);
 208     CFArrayRef allModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
 209     CFIndex numModes = CFArrayGetCount(allModes);
 210     static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode");
 211 
 212     jreturnArray = JNFNewObjectArray(env, &jc_DisplayMode, (jsize) numModes);
 213     if (!jreturnArray) {
 214         NSLog(@"CGraphicsDevice can't create java array of DisplayMode objects");
 215         return nil;
 216     }
 217 
 218     CFIndex n;
 219     for (n=0; n < numModes; n++) {
 220         CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n);
 221         if (cRef != NULL) {
 222             jobject oneMode = createJavaDisplayMode(cRef, env, displayID);
 223             (*env)->SetObjectArrayElement(env, jreturnArray, n, oneMode);
 224             if ((*env)->ExceptionOccurred(env)) {
 225                 (*env)->ExceptionDescribe(env);
 226                 (*env)->ExceptionClear(env);
 227                 continue;
 228             }
 229             (*env)->DeleteLocalRef(env, oneMode);
 230         }
 231     }
 232     CFRelease(allModes);
 233     JNF_COCOA_EXIT(env);
 234 
 235     return jreturnArray;
 236 }