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.InnocuousThread; 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 class X11GraphicsDevice 56 extends GraphicsDevice 57 implements DisplayChangedListener 58 { 59 int screen; 60 HashMap<SurfaceType, Object> x11ProxyKeyMap = new HashMap<>(); 61 62 private static AWTPermission fullScreenExclusivePermission; 63 private static Boolean xrandrExtSupported; 64 private final Object configLock = new Object(); 65 private SunDisplayChanger topLevels = new SunDisplayChanger(); 66 private DisplayMode origDisplayMode; 67 private boolean shutdownHookRegistered; 68 69 public X11GraphicsDevice(int screennum) { 70 this.screen = screennum; 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 public native int getConfigDepth (int index, int screen); 205 206 /* 207 * Returns the colormap for the given index of graphics configurations. 208 */ 209 public native int getConfigColormap (int index, int screen); 210 211 212 // Whether or not double-buffering extension is supported 213 public 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 286 /** 287 * Returns true only if: 288 * - the Xrandr extension is present 289 * - the necessary Xrandr functions were loaded successfully 290 * - XINERAMA is not enabled 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 // REMIND: for now we will only allow fullscreen exclusive mode 303 // on the primary screen; we could change this behavior slightly 304 // in the future by allowing only one screen to be in fullscreen 305 // exclusive mode at any given time... 306 boolean fsAvailable = (screen == 0) && isXrandrExtensionSupported(); 307 if (fsAvailable) { 308 SecurityManager security = System.getSecurityManager(); 309 if (security != null) { 310 if (fullScreenExclusivePermission == null) { 311 fullScreenExclusivePermission = 312 new AWTPermission("fullScreenExclusive"); 313 } 314 try { 315 security.checkPermission(fullScreenExclusivePermission); 316 } catch (SecurityException e) { 317 return false; 318 } 319 } 320 } 321 return fsAvailable; 322 } 323 324 @Override 325 public boolean isDisplayChangeSupported() { 326 return (isFullScreenSupported() && (getFullScreenWindow() != null)); 327 } 328 329 private static void enterFullScreenExclusive(Window w) { 330 X11ComponentPeer peer = AWTAccessor.getComponentAccessor().getPeer(w); 331 if (peer != null) { 332 enterFullScreenExclusive(peer.getContentWindow()); 333 peer.setFullScreenExclusiveModeState(true); 334 } 335 } 336 337 private static void exitFullScreenExclusive(Window w) { 338 X11ComponentPeer peer = AWTAccessor.getComponentAccessor().getPeer(w); 339 if (peer != null) { 340 peer.setFullScreenExclusiveModeState(false); 341 exitFullScreenExclusive(peer.getContentWindow()); 342 } 343 } 344 345 @Override 346 public synchronized void setFullScreenWindow(Window w) { 347 Window old = getFullScreenWindow(); 348 if (w == old) { 349 return; 350 } 351 352 boolean fsSupported = isFullScreenSupported(); 353 if (fsSupported && old != null) { 354 // enter windowed mode (and restore original display mode) 355 exitFullScreenExclusive(old); 356 setDisplayMode(origDisplayMode); 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 setDisplayMode(origDisplayMode); 439 } 440 }; 441 String name = "Display-Change-Shutdown-Thread-" + screen; 442 Thread t; 443 if (System.getSecurityManager() == null) { 444 t = new Thread(ThreadGroupUtils.getRootThreadGroup(), r, name); 445 } else { 446 t = new InnocuousThread(r, name); 447 } 448 t.setContextClassLoader(null); 449 Runtime.getRuntime().addShutdownHook(t); 450 return null; 451 }; 452 AccessController.doPrivileged(a); 453 } 454 455 // switch to the new DisplayMode 456 configDisplayMode(screen, 457 dm.getWidth(), dm.getHeight(), 458 dm.getRefreshRate()); 459 460 // update bounds of the fullscreen window 461 w.setBounds(0, 0, dm.getWidth(), dm.getHeight()); 462 463 // configDisplayMode() is synchronous, so the display change will be 464 // complete by the time we get here (and it is therefore safe to call 465 // displayChanged() now) 466 ((X11GraphicsEnvironment) 467 GraphicsEnvironment.getLocalGraphicsEnvironment()).displayChanged(); 468 } 469 470 private synchronized DisplayMode getMatchingDisplayMode(DisplayMode dm) { 471 if (!isDisplayChangeSupported()) { 472 return null; 473 } 474 DisplayMode[] modes = getDisplayModes(); 475 for (DisplayMode mode : modes) { 476 if (dm.equals(mode) || 477 (dm.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN && 478 dm.getWidth() == mode.getWidth() && 479 dm.getHeight() == mode.getHeight() && 480 dm.getBitDepth() == mode.getBitDepth())) 481 { 482 return mode; 483 } 484 } 485 return null; 486 } 487 488 /** 489 * From the DisplayChangedListener interface; called from 490 * X11GraphicsEnvironment when the display mode has been changed. 491 */ 492 public synchronized void displayChanged() { 493 // On X11 the visuals do not change, and therefore we don't need 494 // to reset the defaultConfig, config, doubleBufferVisuals, 495 // neither do we need to reset the native data. 496 497 // pass on to all top-level windows on this screen 498 topLevels.notifyListeners(); 499 } 500 501 /** 502 * From the DisplayChangedListener interface; devices do not need 503 * to react to this event. 504 */ 505 public void paletteChanged() { 506 } 507 508 /** 509 * Add a DisplayChangeListener to be notified when the display settings 510 * are changed. Typically, only top-level containers need to be added 511 * to X11GraphicsDevice. 512 */ 513 public void addDisplayChangedListener(DisplayChangedListener client) { 514 topLevels.add(client); 515 } 516 517 /** 518 * Remove a DisplayChangeListener from this X11GraphicsDevice. 519 */ 520 public void removeDisplayChangedListener(DisplayChangedListener client) { 521 topLevels.remove(client); 522 } 523 524 public String toString() { 525 return ("X11GraphicsDevice[screen="+screen+"]"); 526 } 527 }