1 /* 2 * Copyright (c) 1997, 2011, 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.misc.ThreadGroupUtils; 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 class X11GraphicsDevice 55 extends GraphicsDevice 56 implements DisplayChangedListener 57 { 58 int screen; 59 HashMap 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 68 public X11GraphicsDevice(int screennum) { 69 this.screen = screennum; 70 } 71 72 /* 73 * Initialize JNI field and method IDs for fields that may be 74 * accessed from C. 75 */ 76 private static native void initIDs(); 77 78 static { 79 if (!GraphicsEnvironment.isHeadless()) { 80 initIDs(); 81 } 82 } 83 84 /** 85 * Returns the X11 screen of the device. 86 */ 87 public int getScreen() { 88 return screen; 89 } 90 91 public Object getProxyKeyFor(SurfaceType st) { 92 synchronized (x11ProxyKeyMap) { 93 Object o = x11ProxyKeyMap.get(st); 94 if (o == null) { 95 o = new Object(); 96 x11ProxyKeyMap.put(st, o); 97 } 98 return o; 99 } 100 } 101 102 /** 103 * Returns the X11 Display of this device. 104 * This method is also in MDrawingSurfaceInfo but need it here 105 * to be able to allow a GraphicsConfigTemplate to get the Display. 106 */ 107 public native long getDisplay(); 108 109 /** 110 * Returns the type of the graphics device. 111 * @see #TYPE_RASTER_SCREEN 112 * @see #TYPE_PRINTER 113 * @see #TYPE_IMAGE_BUFFER 114 */ 115 public int getType() { 116 return TYPE_RASTER_SCREEN; 117 } 118 119 /** 120 * Returns the identification string associated with this graphics 121 * device. 122 */ 123 public String getIDstring() { 124 return ":0."+screen; 125 } 126 127 128 GraphicsConfiguration[] configs; 129 GraphicsConfiguration defaultConfig; 130 HashSet doubleBufferVisuals; 131 132 /** 133 * Returns all of the graphics 134 * configurations associated with this graphics device. 135 */ 136 public GraphicsConfiguration[] getConfigurations() { 137 if (configs == null) { 138 synchronized (configLock) { 139 makeConfigurations(); 140 } 141 } 142 return configs.clone(); 143 } 144 145 private void makeConfigurations() { 146 if (configs == null) { 147 int i = 1; // Index 0 is always the default config 148 int num = getNumConfigs(screen); 149 GraphicsConfiguration[] ret = new GraphicsConfiguration[num]; 150 if (defaultConfig == null) { 151 ret [0] = getDefaultConfiguration(); 152 } 153 else { 154 ret [0] = defaultConfig; 155 } 156 157 boolean glxSupported = X11GraphicsEnvironment.isGLXAvailable(); 158 boolean xrenderSupported = X11GraphicsEnvironment.isXRenderAvailable(); 159 160 boolean dbeSupported = isDBESupported(); 161 if (dbeSupported && doubleBufferVisuals == null) { 162 doubleBufferVisuals = new HashSet(); 163 getDoubleBufferVisuals(screen); 164 } 165 for ( ; i < num; i++) { 166 int visNum = getConfigVisualId(i, screen); 167 int depth = getConfigDepth (i, screen); 168 if (glxSupported) { 169 ret[i] = GLXGraphicsConfig.getConfig(this, visNum); 170 } 171 if (ret[i] == null) { 172 boolean doubleBuffer = 173 (dbeSupported && 174 doubleBufferVisuals.contains(Integer.valueOf(visNum))); 175 176 if (xrenderSupported) { 177 ret[i] = XRGraphicsConfig.getConfig(this, visNum, depth, getConfigColormap(i, screen), 178 doubleBuffer); 179 } else { 180 ret[i] = X11GraphicsConfig.getConfig(this, visNum, depth, 181 getConfigColormap(i, screen), 182 doubleBuffer); 183 } 184 } 185 } 186 configs = ret; 187 } 188 } 189 190 /* 191 * Returns the number of X11 visuals representable as an 192 * X11GraphicsConfig object. 193 */ 194 public native int getNumConfigs(int screen); 195 196 /* 197 * Returns the visualid for the given index of graphics configurations. 198 */ 199 public native int getConfigVisualId (int index, int screen); 200 /* 201 * Returns the depth for the given index of graphics configurations. 202 */ 203 public native int getConfigDepth (int index, int screen); 204 205 /* 206 * Returns the colormap for the given index of graphics configurations. 207 */ 208 public native int getConfigColormap (int index, int screen); 209 210 211 // Whether or not double-buffering extension is supported 212 public 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 285 /** 286 * Returns true only if: 287 * - the Xrandr extension is present 288 * - the necessary Xrandr functions were loaded successfully 289 * - XINERAMA is not enabled 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 // REMIND: for now we will only allow fullscreen exclusive mode 302 // on the primary screen; we could change this behavior slightly 303 // in the future by allowing only one screen to be in fullscreen 304 // exclusive mode at any given time... 305 boolean fsAvailable = (screen == 0) && isXrandrExtensionSupported(); 306 if (fsAvailable) { 307 SecurityManager security = System.getSecurityManager(); 308 if (security != null) { 309 if (fullScreenExclusivePermission == null) { 310 fullScreenExclusivePermission = 311 new AWTPermission("fullScreenExclusive"); 312 } 313 try { 314 security.checkPermission(fullScreenExclusivePermission); 315 } catch (SecurityException e) { 316 return false; 317 } 318 } 319 } 320 return fsAvailable; 321 } 322 323 @Override 324 public boolean isDisplayChangeSupported() { 325 return (isFullScreenSupported() && (getFullScreenWindow() != null)); 326 } 327 328 private static void enterFullScreenExclusive(Window w) { 329 X11ComponentPeer peer = (X11ComponentPeer)w.getPeer(); 330 if (peer != null) { 331 enterFullScreenExclusive(peer.getContentWindow()); 332 peer.setFullScreenExclusiveModeState(true); 333 } 334 } 335 336 private static void exitFullScreenExclusive(Window w) { 337 X11ComponentPeer peer = (X11ComponentPeer)w.getPeer(); 338 if (peer != null) { 339 peer.setFullScreenExclusiveModeState(false); 340 exitFullScreenExclusive(peer.getContentWindow()); 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 setDisplayMode(origDisplayMode); 356 } 357 358 super.setFullScreenWindow(w); 359 360 if (fsSupported && w != null) { 361 // save original display mode 362 if (origDisplayMode == null) { 363 origDisplayMode = getDisplayMode(); 364 } 365 366 // enter fullscreen mode 367 enterFullScreenExclusive(w); 368 } 369 } 370 371 private DisplayMode getDefaultDisplayMode() { 372 GraphicsConfiguration gc = getDefaultConfiguration(); 373 Rectangle r = gc.getBounds(); 374 return new DisplayMode(r.width, r.height, 375 DisplayMode.BIT_DEPTH_MULTI, 376 DisplayMode.REFRESH_RATE_UNKNOWN); 377 } 378 379 @Override 380 public synchronized DisplayMode getDisplayMode() { 381 if (isFullScreenSupported()) { 382 return getCurrentDisplayMode(screen); 383 } else { 384 if (origDisplayMode == null) { 385 origDisplayMode = getDefaultDisplayMode(); 386 } 387 return origDisplayMode; 388 } 389 } 390 391 @Override 392 public synchronized DisplayMode[] getDisplayModes() { 393 if (!isFullScreenSupported()) { 394 return super.getDisplayModes(); 395 } 396 ArrayList<DisplayMode> modes = new ArrayList<DisplayMode>(); 397 enumDisplayModes(screen, modes); 398 DisplayMode[] retArray = new DisplayMode[modes.size()]; 399 return modes.toArray(retArray); 400 } 401 402 @Override 403 public synchronized void setDisplayMode(DisplayMode dm) { 404 if (!isDisplayChangeSupported()) { 405 super.setDisplayMode(dm); 406 return; 407 } 408 Window w = getFullScreenWindow(); 409 if (w == null) { 410 throw new IllegalStateException("Must be in fullscreen mode " + 411 "in order to set display mode"); 412 } 413 if (getDisplayMode().equals(dm)) { 414 return; 415 } 416 if (dm == null || 417 (dm = getMatchingDisplayMode(dm)) == null) 418 { 419 throw new IllegalArgumentException("Invalid display mode"); 420 } 421 422 if (!shutdownHookRegistered) { 423 // register a shutdown hook so that we return to the 424 // original DisplayMode when the VM exits (if the application 425 // is already in the original DisplayMode at that time, this 426 // hook will have no effect) 427 shutdownHookRegistered = true; 428 PrivilegedAction<Void> a = () -> { 429 ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup(); 430 Runnable r = () -> { 431 Window old = getFullScreenWindow(); 432 if (old != null) { 433 exitFullScreenExclusive(old); 434 setDisplayMode(origDisplayMode); 435 } 436 }; 437 Thread t = new Thread(rootTG, r,"Display-Change-Shutdown-Thread-"+screen); 438 t.setContextClassLoader(null); 439 Runtime.getRuntime().addShutdownHook(t); 440 return null; 441 }; 442 AccessController.doPrivileged(a); 443 } 444 445 // switch to the new DisplayMode 446 configDisplayMode(screen, 447 dm.getWidth(), dm.getHeight(), 448 dm.getRefreshRate()); 449 450 // update bounds of the fullscreen window 451 w.setBounds(0, 0, dm.getWidth(), dm.getHeight()); 452 453 // configDisplayMode() is synchronous, so the display change will be 454 // complete by the time we get here (and it is therefore safe to call 455 // displayChanged() now) 456 ((X11GraphicsEnvironment) 457 GraphicsEnvironment.getLocalGraphicsEnvironment()).displayChanged(); 458 } 459 460 private synchronized DisplayMode getMatchingDisplayMode(DisplayMode dm) { 461 if (!isDisplayChangeSupported()) { 462 return null; 463 } 464 DisplayMode[] modes = getDisplayModes(); 465 for (DisplayMode mode : modes) { 466 if (dm.equals(mode) || 467 (dm.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN && 468 dm.getWidth() == mode.getWidth() && 469 dm.getHeight() == mode.getHeight() && 470 dm.getBitDepth() == mode.getBitDepth())) 471 { 472 return mode; 473 } 474 } 475 return null; 476 } 477 478 /** 479 * From the DisplayChangedListener interface; called from 480 * X11GraphicsEnvironment when the display mode has been changed. 481 */ 482 public synchronized void displayChanged() { 483 // On X11 the visuals do not change, and therefore we don't need 484 // to reset the defaultConfig, config, doubleBufferVisuals, 485 // neither do we need to reset the native data. 486 487 // pass on to all top-level windows on this screen 488 topLevels.notifyListeners(); 489 } 490 491 /** 492 * From the DisplayChangedListener interface; devices do not need 493 * to react to this event. 494 */ 495 public void paletteChanged() { 496 } 497 498 /** 499 * Add a DisplayChangeListener to be notified when the display settings 500 * are changed. Typically, only top-level containers need to be added 501 * to X11GraphicsDevice. 502 */ 503 public void addDisplayChangedListener(DisplayChangedListener client) { 504 topLevels.add(client); 505 } 506 507 /** 508 * Remove a DisplayChangeListener from this X11GraphicsDevice. 509 */ 510 public void removeDisplayChangedListener(DisplayChangedListener client) { 511 topLevels.remove(client); 512 } 513 514 public String toString() { 515 return ("X11GraphicsDevice[screen="+screen+"]"); 516 } 517 }