1 /* 2 * Copyright (c) 1997, 2015, 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 package sun.awt; 27 28 import java.awt.AWTPermission; 29 import java.awt.DisplayMode; 30 import java.awt.GraphicsEnvironment; 31 import java.awt.GraphicsDevice; 32 import java.awt.GraphicsConfiguration; 33 import java.awt.Rectangle; 34 import java.awt.Window; 35 import java.security.AccessController; 36 import java.security.PrivilegedAction; 37 import java.util.ArrayList; 38 import java.util.HashSet; 39 import java.util.HashMap; 40 41 import sun.java2d.opengl.GLXGraphicsConfig; 42 import sun.java2d.xr.XRGraphicsConfig; 43 import sun.java2d.loops.SurfaceType; 44 45 import sun.awt.util.ThreadGroupUtils; 46 import sun.misc.ManagedLocalsThread; 47 import sun.security.action.GetPropertyAction; 48 49 /** 50 * This is an implementation of a GraphicsDevice object for a single 51 * X11 screen. 52 * 53 * @see GraphicsEnvironment 54 * @see GraphicsConfiguration 55 */ 56 public final class X11GraphicsDevice extends GraphicsDevice 57 implements DisplayChangedListener { 58 int screen; 59 HashMap<SurfaceType, Object> x11ProxyKeyMap = new HashMap<>(); 60 61 private static AWTPermission fullScreenExclusivePermission; 62 private static Boolean xrandrExtSupported; 63 private final Object configLock = new Object(); 64 private SunDisplayChanger topLevels = new SunDisplayChanger(); 65 private DisplayMode origDisplayMode; 66 private boolean shutdownHookRegistered; 67 private int scale; 68 69 public X11GraphicsDevice(int screennum) { 70 this.screen = screennum; 71 this.scale = initScaleFactor(); 72 } 73 74 /* 75 * Initialize JNI field and method IDs for fields that may be 76 * accessed from C. 77 */ 78 private static native void initIDs(); 79 80 static { 81 if (!GraphicsEnvironment.isHeadless()) { 82 initIDs(); 83 } 84 } 85 86 /** 87 * Returns the X11 screen of the device. 88 */ 89 public int getScreen() { 90 return screen; 91 } 92 93 public Object getProxyKeyFor(SurfaceType st) { 94 synchronized (x11ProxyKeyMap) { 95 Object o = x11ProxyKeyMap.get(st); 96 if (o == null) { 97 o = new Object(); 98 x11ProxyKeyMap.put(st, o); 99 } 100 return o; 101 } 102 } 103 104 /** 105 * Returns the X11 Display of this device. 106 * This method is also in MDrawingSurfaceInfo but need it here 107 * to be able to allow a GraphicsConfigTemplate to get the Display. 108 */ 109 public native long getDisplay(); 110 111 /** 112 * Returns the type of the graphics device. 113 * @see #TYPE_RASTER_SCREEN 114 * @see #TYPE_PRINTER 115 * @see #TYPE_IMAGE_BUFFER 116 */ 117 public int getType() { 118 return TYPE_RASTER_SCREEN; 119 } 120 121 /** 122 * Returns the identification string associated with this graphics 123 * device. 124 */ 125 public String getIDstring() { 126 return ":0."+screen; 127 } 128 129 130 GraphicsConfiguration[] configs; 131 GraphicsConfiguration defaultConfig; 132 HashSet<Integer> doubleBufferVisuals; 133 134 /** 135 * Returns all of the graphics 136 * configurations associated with this graphics device. 137 */ 138 public GraphicsConfiguration[] getConfigurations() { 139 if (configs == null) { 140 synchronized (configLock) { 141 makeConfigurations(); 142 } 143 } 144 return configs.clone(); 145 } 146 147 private void makeConfigurations() { 148 if (configs == null) { 149 int i = 1; // Index 0 is always the default config 150 int num = getNumConfigs(screen); 151 GraphicsConfiguration[] ret = new GraphicsConfiguration[num]; 152 if (defaultConfig == null) { 153 ret [0] = getDefaultConfiguration(); 154 } 155 else { 156 ret [0] = defaultConfig; 157 } 158 159 boolean glxSupported = X11GraphicsEnvironment.isGLXAvailable(); 160 boolean xrenderSupported = X11GraphicsEnvironment.isXRenderAvailable(); 161 162 boolean dbeSupported = isDBESupported(); 163 if (dbeSupported && doubleBufferVisuals == null) { 164 doubleBufferVisuals = new HashSet<>(); 165 getDoubleBufferVisuals(screen); 166 } 167 for ( ; i < num; i++) { 168 int visNum = getConfigVisualId(i, screen); 169 int depth = getConfigDepth (i, screen); 170 if (glxSupported) { 171 ret[i] = GLXGraphicsConfig.getConfig(this, visNum); 172 } 173 if (ret[i] == null) { 174 boolean doubleBuffer = 175 (dbeSupported && 176 doubleBufferVisuals.contains(Integer.valueOf(visNum))); 177 178 if (xrenderSupported) { 179 ret[i] = XRGraphicsConfig.getConfig(this, visNum, depth, getConfigColormap(i, screen), 180 doubleBuffer); 181 } else { 182 ret[i] = X11GraphicsConfig.getConfig(this, visNum, depth, 183 getConfigColormap(i, screen), 184 doubleBuffer); 185 } 186 } 187 } 188 configs = ret; 189 } 190 } 191 192 /* 193 * Returns the number of X11 visuals representable as an 194 * X11GraphicsConfig object. 195 */ 196 public native int getNumConfigs(int screen); 197 198 /* 199 * Returns the visualid for the given index of graphics configurations. 200 */ 201 public native int getConfigVisualId (int index, int screen); 202 /* 203 * Returns the depth for the given index of graphics configurations. 204 */ 205 private native int getConfigDepth(int index, int screen); 206 207 /* 208 * Returns the colormap for the given index of graphics configurations. 209 */ 210 private native int getConfigColormap(int index, int screen); 211 212 // Whether or not double-buffering extension is supported 213 static native boolean isDBESupported(); 214 // Callback for adding a new double buffer visual into our set 215 private void addDoubleBufferVisual(int visNum) { 216 doubleBufferVisuals.add(Integer.valueOf(visNum)); 217 } 218 // Enumerates all visuals that support double buffering 219 private native void getDoubleBufferVisuals(int screen); 220 221 /** 222 * Returns the default graphics configuration 223 * associated with this graphics device. 224 */ 225 public GraphicsConfiguration getDefaultConfiguration() { 226 if (defaultConfig == null) { 227 synchronized (configLock) { 228 makeDefaultConfiguration(); 229 } 230 } 231 return defaultConfig; 232 } 233 234 private void makeDefaultConfiguration() { 235 if (defaultConfig == null) { 236 int visNum = getConfigVisualId(0, screen); 237 if (X11GraphicsEnvironment.isGLXAvailable()) { 238 defaultConfig = GLXGraphicsConfig.getConfig(this, visNum); 239 if (X11GraphicsEnvironment.isGLXVerbose()) { 240 if (defaultConfig != null) { 241 System.out.print("OpenGL pipeline enabled"); 242 } else { 243 System.out.print("Could not enable OpenGL pipeline"); 244 } 245 System.out.println(" for default config on screen " + 246 screen); 247 } 248 } 249 if (defaultConfig == null) { 250 int depth = getConfigDepth(0, screen); 251 boolean doubleBuffer = false; 252 if (isDBESupported() && doubleBufferVisuals == null) { 253 doubleBufferVisuals = new HashSet<>(); 254 getDoubleBufferVisuals(screen); 255 doubleBuffer = 256 doubleBufferVisuals.contains(Integer.valueOf(visNum)); 257 } 258 259 if (X11GraphicsEnvironment.isXRenderAvailable()) { 260 if (X11GraphicsEnvironment.isXRenderVerbose()) { 261 System.out.println("XRender pipeline enabled"); 262 } 263 defaultConfig = XRGraphicsConfig.getConfig(this, visNum, 264 depth, getConfigColormap(0, screen), 265 doubleBuffer); 266 } else { 267 defaultConfig = X11GraphicsConfig.getConfig(this, visNum, 268 depth, getConfigColormap(0, screen), 269 doubleBuffer); 270 } 271 } 272 } 273 } 274 275 private static native void enterFullScreenExclusive(long window); 276 private static native void exitFullScreenExclusive(long window); 277 private static native boolean initXrandrExtension(); 278 private static native DisplayMode getCurrentDisplayMode(int screen); 279 private static native void enumDisplayModes(int screen, 280 ArrayList<DisplayMode> modes); 281 private static native void configDisplayMode(int screen, 282 int width, int height, 283 int displayMode); 284 private static native void resetNativeData(int screen); 285 private static native int getNativeScaleFactor(int screen); 286 287 /** 288 * Returns true only if: 289 * - the Xrandr extension is present 290 * - the necessary Xrandr functions were loaded successfully 291 */ 292 private static synchronized boolean isXrandrExtensionSupported() { 293 if (xrandrExtSupported == null) { 294 xrandrExtSupported = 295 Boolean.valueOf(initXrandrExtension()); 296 } 297 return xrandrExtSupported.booleanValue(); 298 } 299 300 @Override 301 public boolean isFullScreenSupported() { 302 boolean fsAvailable = isXrandrExtensionSupported(); 303 if (fsAvailable) { 304 SecurityManager security = System.getSecurityManager(); 305 if (security != null) { 306 if (fullScreenExclusivePermission == null) { 307 fullScreenExclusivePermission = 308 new AWTPermission("fullScreenExclusive"); 309 } 310 try { 311 security.checkPermission(fullScreenExclusivePermission); 312 } catch (SecurityException e) { 313 return false; 314 } 315 } 316 } 317 return fsAvailable; 318 } 319 320 @Override 321 public boolean isDisplayChangeSupported() { 322 return (isFullScreenSupported() 323 && (getFullScreenWindow() != null) 324 && !((X11GraphicsEnvironment) GraphicsEnvironment 325 .getLocalGraphicsEnvironment()).runningXinerama()); 326 } 327 328 private static void enterFullScreenExclusive(Window w) { 329 X11ComponentPeer peer = AWTAccessor.getComponentAccessor().getPeer(w); 330 if (peer != null) { 331 enterFullScreenExclusive(peer.getWindow()); 332 peer.setFullScreenExclusiveModeState(true); 333 } 334 } 335 336 private static void exitFullScreenExclusive(Window w) { 337 X11ComponentPeer peer = AWTAccessor.getComponentAccessor().getPeer(w); 338 if (peer != null) { 339 peer.setFullScreenExclusiveModeState(false); 340 exitFullScreenExclusive(peer.getWindow()); 341 } 342 } 343 344 @Override 345 public synchronized void setFullScreenWindow(Window w) { 346 Window old = getFullScreenWindow(); 347 if (w == old) { 348 return; 349 } 350 351 boolean fsSupported = isFullScreenSupported(); 352 if (fsSupported && old != null) { 353 // enter windowed mode (and restore original display mode) 354 exitFullScreenExclusive(old); 355 if (isDisplayChangeSupported()) { 356 setDisplayMode(origDisplayMode); 357 } 358 } 359 360 super.setFullScreenWindow(w); 361 362 if (fsSupported && w != null) { 363 // save original display mode 364 if (origDisplayMode == null) { 365 origDisplayMode = getDisplayMode(); 366 } 367 368 // enter fullscreen mode 369 enterFullScreenExclusive(w); 370 } 371 } 372 373 private DisplayMode getDefaultDisplayMode() { 374 GraphicsConfiguration gc = getDefaultConfiguration(); 375 Rectangle r = gc.getBounds(); 376 return new DisplayMode(r.width, r.height, 377 DisplayMode.BIT_DEPTH_MULTI, 378 DisplayMode.REFRESH_RATE_UNKNOWN); 379 } 380 381 @Override 382 public synchronized DisplayMode getDisplayMode() { 383 if (isFullScreenSupported()) { 384 DisplayMode mode = getCurrentDisplayMode(screen); 385 if (mode == null) { 386 mode = getDefaultDisplayMode(); 387 } 388 return mode; 389 } else { 390 if (origDisplayMode == null) { 391 origDisplayMode = getDefaultDisplayMode(); 392 } 393 return origDisplayMode; 394 } 395 } 396 397 @Override 398 public synchronized DisplayMode[] getDisplayModes() { 399 if (!isFullScreenSupported()) { 400 return super.getDisplayModes(); 401 } 402 ArrayList<DisplayMode> modes = new ArrayList<DisplayMode>(); 403 enumDisplayModes(screen, modes); 404 DisplayMode[] retArray = new DisplayMode[modes.size()]; 405 return modes.toArray(retArray); 406 } 407 408 @Override 409 public synchronized void setDisplayMode(DisplayMode dm) { 410 if (!isDisplayChangeSupported()) { 411 super.setDisplayMode(dm); 412 return; 413 } 414 Window w = getFullScreenWindow(); 415 if (w == null) { 416 throw new IllegalStateException("Must be in fullscreen mode " + 417 "in order to set display mode"); 418 } 419 if (getDisplayMode().equals(dm)) { 420 return; 421 } 422 if (dm == null || 423 (dm = getMatchingDisplayMode(dm)) == null) 424 { 425 throw new IllegalArgumentException("Invalid display mode"); 426 } 427 428 if (!shutdownHookRegistered) { 429 // register a shutdown hook so that we return to the 430 // original DisplayMode when the VM exits (if the application 431 // is already in the original DisplayMode at that time, this 432 // hook will have no effect) 433 shutdownHookRegistered = true; 434 PrivilegedAction<Void> a = () -> { 435 Runnable r = () -> { 436 Window old = getFullScreenWindow(); 437 if (old != null) { 438 exitFullScreenExclusive(old); 439 if (isDisplayChangeSupported()) { 440 setDisplayMode(origDisplayMode); 441 } 442 } 443 }; 444 String name = "Display-Change-Shutdown-Thread-" + screen; 445 Thread t = new ManagedLocalsThread( 446 ThreadGroupUtils.getRootThreadGroup(), r, name); 447 t.setContextClassLoader(null); 448 Runtime.getRuntime().addShutdownHook(t); 449 return null; 450 }; 451 AccessController.doPrivileged(a); 452 } 453 454 // switch to the new DisplayMode 455 configDisplayMode(screen, 456 dm.getWidth(), dm.getHeight(), 457 dm.getRefreshRate()); 458 459 // update bounds of the fullscreen window 460 w.setBounds(0, 0, dm.getWidth(), dm.getHeight()); 461 462 // configDisplayMode() is synchronous, so the display change will be 463 // complete by the time we get here (and it is therefore safe to call 464 // displayChanged() now) 465 ((X11GraphicsEnvironment) 466 GraphicsEnvironment.getLocalGraphicsEnvironment()).displayChanged(); 467 } 468 469 private synchronized DisplayMode getMatchingDisplayMode(DisplayMode dm) { 470 if (!isDisplayChangeSupported()) { 471 return null; 472 } 473 DisplayMode[] modes = getDisplayModes(); 474 for (DisplayMode mode : modes) { 475 if (dm.equals(mode) || 476 (dm.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN && 477 dm.getWidth() == mode.getWidth() && 478 dm.getHeight() == mode.getHeight() && 479 dm.getBitDepth() == mode.getBitDepth())) 480 { 481 return mode; 482 } 483 } 484 return null; 485 } 486 487 /** 488 * From the DisplayChangedListener interface; called from 489 * X11GraphicsEnvironment when the display mode has been changed. 490 */ 491 public synchronized void displayChanged() { 492 // On X11 the visuals do not change, and therefore we don't need 493 // to reset the defaultConfig, config, doubleBufferVisuals, 494 // neither do we need to reset the native data. 495 496 // pass on to all top-level windows on this screen 497 topLevels.notifyListeners(); 498 } 499 500 /** 501 * From the DisplayChangedListener interface; devices do not need 502 * to react to this event. 503 */ 504 public void paletteChanged() { 505 } 506 507 /** 508 * Add a DisplayChangeListener to be notified when the display settings 509 * are changed. Typically, only top-level containers need to be added 510 * to X11GraphicsDevice. 511 */ 512 public void addDisplayChangedListener(DisplayChangedListener client) { 513 topLevels.add(client); 514 } 515 516 public int getScaleFactor(){ 517 return scale; 518 } 519 520 private int initScaleFactor() { 521 522 boolean hidpiEnabled = "true".equals(AccessController.doPrivileged( 523 new GetPropertyAction("sun.java2d.uiScale.enabled", "true"))); 524 525 if (!hidpiEnabled) { 526 return 1; 527 } 528 529 String hidpiScale = AccessController.doPrivileged( 530 new GetPropertyAction("sun.java2d.uiScale", "-1")); 531 try { 532 int scale = Integer.parseInt(hidpiScale); 533 if (scale >= 1) { 534 return scale; 535 } 536 } catch (NumberFormatException ignore) { 537 } 538 539 int scale = getNativeScaleFactor(screen); 540 return scale >= 1 ? scale : 1; 541 } 542 543 /** 544 * Remove a DisplayChangeListener from this X11GraphicsDevice. 545 */ 546 public void removeDisplayChangedListener(DisplayChangedListener client) { 547 topLevels.remove(client); 548 } 549 550 public String toString() { 551 return ("X11GraphicsDevice[screen="+screen+"]"); 552 } 553 }