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 
  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      */
  75     public int getScreen() {
  76         return screen;
  77     }
  78 
  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      */
  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
 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
 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
 127     public GraphicsConfiguration[] getConfigurations() {
 128         if (configs == null) {
 129             synchronized (configLock) {
 130                 makeConfigurations();
 131             }
 132         }
 133         return configs.clone();
 134     }
 135 
 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      */
 185     public native int getNumConfigs(int screen);
 186 
 187     /*
 188      * Returns the visualid for the given index of graphics configurations.
 189      */
 190     public native int getConfigVisualId (int index, int screen);
 191     /*
 192      * Returns the depth for the given index of graphics configurations.
 193      */
 194     private native int getConfigDepth(int index, int screen);
 195 
 196     /*
 197      * Returns the colormap for the given index of graphics configurations.
 198      */
 199     private native int getConfigColormap(int index, int screen);
 200 
 201     // Whether or not double-buffering extension is supported
 202     static native boolean isDBESupported();
 203     // Callback for adding a new double buffer visual into our set
 204     private void addDoubleBufferVisual(int visNum) {
 205         doubleBufferVisuals.add(Integer.valueOf(visNum));
 206     }
 207     // Enumerates all visuals that support double buffering
 208     private native void getDoubleBufferVisuals(int screen);
 209 
 210     /**
 211      * Returns the default graphics configuration
 212      * associated with this graphics device.
 213      */
 214     @Override
 215     public GraphicsConfiguration getDefaultConfiguration() {
 216         if (defaultConfig == null) {
 217             synchronized (configLock) {
 218                 makeDefaultConfiguration();
 219             }
 220         }
 221         return defaultConfig;
 222     }
 223 
 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 
 265     private static native void enterFullScreenExclusive(long window);
 266     private static native void exitFullScreenExclusive(long window);
 267     private static native boolean initXrandrExtension();
 268     private static native DisplayMode getCurrentDisplayMode(int screen);
 269     private static native void enumDisplayModes(int screen,
 270                                                 ArrayList<DisplayMode> modes);
 271     private static native void configDisplayMode(int screen,
 272                                                  int width, int height,
 273                                                  int displayMode);
 274     private static native void resetNativeData(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      */
 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
 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
 311     public boolean isDisplayChangeSupported() {
 312         return (isFullScreenSupported()
 313                 && (getFullScreenWindow() != null)
 314                 && !((X11GraphicsEnvironment) GraphicsEnvironment
 315                         .getLocalGraphicsEnvironment()).runningXinerama());
 316     }
 317 
 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 
 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
 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 
 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
 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
 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
 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 
 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
 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
 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      */
 505     public void addDisplayChangedListener(DisplayChangedListener client) {
 506         topLevels.add(client);
 507     }
 508 
 509     public int getScaleFactor() {
 510         return scale;
 511     }
 512 
 513     public int getNativeScale() {
 514         isXrandrExtensionSupported();
 515         return (int)Math.round(getNativeScaleFactor(screen));
 516     }
 517 
 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      */
 537     public void removeDisplayChangedListener(DisplayChangedListener client) {
 538         topLevels.remove(client);
 539     }
 540 
 541     public String toString() {
 542         return ("X11GraphicsDevice[screen="+screen+"]");
 543     }
 544 }