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