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