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 }