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