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