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