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 #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 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 int thisBpp = getBPPFromModeString(modeString); 119 CFRelease(modeString); 120 int thisH = (int)CGDisplayModeGetHeight(cRef); 121 int thisW = (int)CGDisplayModeGetWidth(cRef); 122 if (thisBpp != bpp || thisH != h || thisW != 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 int thisRefrate = (int)CGDisplayModeGetRefreshRate(cRef); 134 if (thisRefrate == refrate) { 135 // Exact match 136 return cRef; 137 } 138 if (thisRefrate == 0) { 139 // Not exactly what was asked for, but may fit our needs if we don't find an exact match 140 bestGuess = cRef; 141 } 142 } 143 return bestGuess; 144 } 145 146 /* 147 * Create a new java.awt.DisplayMode instance based on provided 148 * CGDisplayModeRef, if CGDisplayModeRef is NULL, then some stub is returned. 149 */ 150 static jobject createJavaDisplayMode(CGDisplayModeRef mode, JNIEnv *env) { 151 jobject ret = NULL; 152 jint h = DEFAULT_DEVICE_HEIGHT, w = DEFAULT_DEVICE_WIDTH, bpp = 0, refrate = 0; 153 JNF_COCOA_ENTER(env); 154 if (mode) { 155 CFStringRef currentBPP = CGDisplayModeCopyPixelEncoding(mode); 156 bpp = getBPPFromModeString(currentBPP); 157 refrate = CGDisplayModeGetRefreshRate(mode); 158 h = CGDisplayModeGetHeight(mode); 159 w = CGDisplayModeGetWidth(mode); 160 CFRelease(currentBPP); 161 } 162 static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode"); 163 static JNF_CTOR_CACHE(jc_DisplayMode_ctor, jc_DisplayMode, "(IIII)V"); 164 ret = JNFNewObject(env, jc_DisplayMode_ctor, w, h, bpp, refrate); 165 JNF_COCOA_EXIT(env); 166 return ret; 167 } 168 169 170 /* 171 * Class: sun_awt_CGraphicsDevice 172 * Method: nativeGetXResolution 173 * Signature: (I)D 174 */ 175 JNIEXPORT jdouble JNICALL 176 Java_sun_awt_CGraphicsDevice_nativeGetXResolution 177 (JNIEnv *env, jclass class, jint displayID) 178 { 179 // CGDisplayScreenSize can return 0 if displayID is invalid 180 CGSize size = CGDisplayScreenSize(displayID); 181 CGRect rect = CGDisplayBounds(displayID); 182 // 1 inch == 25.4 mm 183 jfloat inches = size.width / 25.4f; 184 return inches > 0 ? rect.size.width / inches : DEFAULT_DEVICE_DPI; 185 } 186 187 /* 188 * Class: sun_awt_CGraphicsDevice 189 * Method: nativeGetYResolution 190 * Signature: (I)D 191 */ 192 JNIEXPORT jdouble JNICALL 193 Java_sun_awt_CGraphicsDevice_nativeGetYResolution 194 (JNIEnv *env, jclass class, jint displayID) 195 { 196 // CGDisplayScreenSize can return 0 if displayID is invalid 197 CGSize size = CGDisplayScreenSize(displayID); 198 CGRect rect = CGDisplayBounds(displayID); 199 // 1 inch == 25.4 mm 200 jfloat inches = size.height / 25.4f; 201 return inches > 0 ? rect.size.height / inches : DEFAULT_DEVICE_DPI; 202 } 203 204 /* 205 * Class: sun_awt_CGraphicsDevice 206 * Method: nativeGetBounds 207 * Signature: (I)Ljava/awt/Rectangle; 208 */ 209 JNIEXPORT jobject JNICALL 210 Java_sun_awt_CGraphicsDevice_nativeGetBounds 211 (JNIEnv *env, jclass class, jint displayID) 212 { 213 CGRect rect = CGDisplayBounds(displayID); 214 if (rect.size.width == 0) { 215 rect.size.width = DEFAULT_DEVICE_WIDTH; 216 } 217 if (rect.size.height == 0) { 218 rect.size.height = DEFAULT_DEVICE_HEIGHT; 219 } 220 return CGToJavaRect(env, rect); 221 } 222 223 /* 224 * Class: sun_awt_CGraphicsDevice 225 * Method: nativeGetScreenInsets 226 * Signature: (I)D 227 */ 228 JNIEXPORT jobject JNICALL 229 Java_sun_awt_CGraphicsDevice_nativeGetScreenInsets 230 (JNIEnv *env, jclass class, jint displayID) 231 { 232 jobject ret = NULL; 233 __block NSRect frame = NSZeroRect; 234 __block NSRect visibleFrame = NSZeroRect; 235 JNF_COCOA_ENTER(env); 236 237 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 238 NSArray *screens = [NSScreen screens]; 239 for (NSScreen *screen in screens) { 240 NSDictionary *screenInfo = [screen deviceDescription]; 241 NSNumber *screenID = [screenInfo objectForKey:@"NSScreenNumber"]; 242 if ([screenID unsignedIntValue] == displayID){ 243 frame = [screen frame]; 244 visibleFrame = [screen visibleFrame]; 245 break; 246 } 247 } 248 }]; 249 // Convert between Cocoa's coordinate system and Java. 250 jint bottom = visibleFrame.origin.y - frame.origin.y; 251 jint top = frame.size.height - visibleFrame.size.height - bottom; 252 jint left = visibleFrame.origin.x - frame.origin.x; 253 jint right = frame.size.width - visibleFrame.size.width - left; 254 255 static JNF_CLASS_CACHE(jc_Insets, "java/awt/Insets"); 256 static JNF_CTOR_CACHE(jc_Insets_ctor, jc_Insets, "(IIII)V"); 257 ret = JNFNewObject(env, jc_Insets_ctor, top, left, bottom, right); 258 259 JNF_COCOA_EXIT(env); 260 261 return ret; 262 } 263 264 /* 265 * Class: sun_awt_CGraphicsDevice 266 * Method: nativeSetDisplayMode 267 * Signature: (IIIII)V 268 */ 269 JNIEXPORT void JNICALL 270 Java_sun_awt_CGraphicsDevice_nativeSetDisplayMode 271 (JNIEnv *env, jclass class, jint displayID, jint w, jint h, jint bpp, jint refrate) 272 { 273 JNF_COCOA_ENTER(env); 274 CFArrayRef allModes = getAllValidDisplayModes(displayID); 275 CGDisplayModeRef closestMatch = getBestModeForParameters(allModes, (int)w, (int)h, (int)bpp, (int)refrate); 276 277 __block CGError retCode = kCGErrorSuccess; 278 if (closestMatch != NULL) { 279 CGDisplayModeRetain(closestMatch); 280 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 281 CGDisplayConfigRef config; 282 retCode = CGBeginDisplayConfiguration(&config); 283 if (retCode == kCGErrorSuccess) { 284 CGConfigureDisplayWithDisplayMode(config, displayID, closestMatch, NULL); 285 retCode = CGCompleteDisplayConfiguration(config, kCGConfigureForAppOnly); 286 } 287 CGDisplayModeRelease(closestMatch); 288 }]; 289 } else { 290 [JNFException raise:env as:kIllegalArgumentException reason:"Invalid display mode"]; 291 } 292 293 if (retCode != kCGErrorSuccess){ 294 [JNFException raise:env as:kIllegalArgumentException reason:"Unable to set display mode!"]; 295 } 296 CFRelease(allModes); 297 JNF_COCOA_EXIT(env); 298 } 299 /* 300 * Class: sun_awt_CGraphicsDevice 301 * Method: nativeGetDisplayMode 302 * Signature: (I)Ljava/awt/DisplayMode 303 */ 304 JNIEXPORT jobject JNICALL 305 Java_sun_awt_CGraphicsDevice_nativeGetDisplayMode 306 (JNIEnv *env, jclass class, jint displayID) 307 { 308 jobject ret = NULL; 309 // CGDisplayCopyDisplayMode can return NULL if displayID is invalid 310 CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(displayID); 311 ret = createJavaDisplayMode(currentMode, env); 312 CGDisplayModeRelease(currentMode); 313 return ret; 314 } 315 316 /* 317 * Class: sun_awt_CGraphicsDevice 318 * Method: nativeGetDisplayMode 319 * Signature: (I)[Ljava/awt/DisplayModes 320 */ 321 JNIEXPORT jobjectArray JNICALL 322 Java_sun_awt_CGraphicsDevice_nativeGetDisplayModes 323 (JNIEnv *env, jclass class, jint displayID) 324 { 325 jobjectArray jreturnArray = NULL; 326 JNF_COCOA_ENTER(env); 327 CFArrayRef allModes = getAllValidDisplayModes(displayID); 328 329 CFIndex numModes = allModes ? CFArrayGetCount(allModes): 0; 330 static JNF_CLASS_CACHE(jc_DisplayMode, "java/awt/DisplayMode"); 331 332 jreturnArray = JNFNewObjectArray(env, &jc_DisplayMode, (jsize) numModes); 333 if (!jreturnArray) { 334 NSLog(@"CGraphicsDevice can't create java array of DisplayMode objects"); 335 return nil; 336 } 337 338 CFIndex n; 339 for (n=0; n < numModes; n++) { 340 CGDisplayModeRef cRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, n); 341 if (cRef != NULL) { 342 jobject oneMode = createJavaDisplayMode(cRef, env); 343 (*env)->SetObjectArrayElement(env, jreturnArray, n, oneMode); 344 if ((*env)->ExceptionOccurred(env)) { 345 (*env)->ExceptionDescribe(env); 346 (*env)->ExceptionClear(env); 347 continue; 348 } 349 (*env)->DeleteLocalRef(env, oneMode); 350 } 351 } 352 if (allModes) { 353 CFRelease(allModes); 354 } 355 JNF_COCOA_EXIT(env); 356 357 return jreturnArray; 358 } 359 360 /* 361 * Class: sun_awt_CGraphicsDevice 362 * Method: nativeGetScaleFactor 363 * Signature: (I)D 364 */ 365 JNIEXPORT jdouble JNICALL 366 Java_sun_awt_CGraphicsDevice_nativeGetScaleFactor 367 (JNIEnv *env, jclass class, jint displayID) 368 { 369 __block jdouble ret = 1.0f; 370 371 JNF_COCOA_ENTER(env); 372 373 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 374 NSArray *screens = [NSScreen screens]; 375 for (NSScreen *screen in screens) { 376 NSDictionary *screenInfo = [screen deviceDescription]; 377 NSNumber *screenID = [screenInfo objectForKey:@"NSScreenNumber"]; 378 if ([screenID unsignedIntValue] == displayID){ 379 if ([screen respondsToSelector:@selector(backingScaleFactor)]) { 380 ret = [screen backingScaleFactor]; 381 } 382 break; 383 } 384 } 385 }]; 386 387 JNF_COCOA_EXIT(env); 388 return ret; 389 }