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.GraphicsDevice;
  30 import java.awt.GraphicsConfiguration;
  31 import java.awt.GraphicsEnvironment;
  32 import java.awt.DisplayMode;
  33 import java.awt.EventQueue;
  34 import java.awt.Frame;
  35 import java.awt.Rectangle;
  36 import java.awt.Window;
  37 import java.awt.event.WindowAdapter;
  38 import java.awt.event.WindowEvent;
  39 import java.awt.event.WindowListener;
  40 import java.awt.geom.Point2D;
  41 import java.awt.image.ColorModel;
  42 import java.util.ArrayList;
  43 import java.util.Vector;
  44 import java.awt.peer.WindowPeer;
  45 import java.security.AccessController;
  46 import sun.awt.windows.WWindowPeer;
  47 import sun.java2d.SunGraphicsEnvironment;
  48 import sun.java2d.opengl.WGLGraphicsConfig;
  49 import sun.java2d.windows.WindowsFlags;
  50 import sun.security.action.GetPropertyAction;
  51 import static sun.awt.Win32GraphicsEnvironment.debugScaleX;
  52 import static sun.awt.Win32GraphicsEnvironment.debugScaleY;
  53 
  54 /**
  55  * This is an implementation of a GraphicsDevice object for a single
  56  * Win32 screen.
  57  *
  58  * @see GraphicsEnvironment
  59  * @see GraphicsConfiguration
  60  */
  61 public class Win32GraphicsDevice extends GraphicsDevice implements
  62  DisplayChangedListener {
  63     int screen;
  64     ColorModel dynamicColorModel;   // updated with dev changes
  65     ColorModel colorModel;          // static for device
  66     protected GraphicsConfiguration[] configs;
  67     protected GraphicsConfiguration defaultConfig;
  68 
  69     private final String idString;
  70     protected String descString;
  71     // Note that we do not synchronize access to this variable - it doesn't
  72     // really matter if a thread does an operation on graphics device which is
  73     // about to become invalid (or already become) - we are prepared to deal
  74     // with this on the native level.
  75     private boolean valid;
  76 
  77     // keep track of top-level windows on this display
  78     private SunDisplayChanger topLevels = new SunDisplayChanger();
  79     // REMIND: we may disable the use of pixel formats for some accelerated
  80     // pipelines which are mutually exclusive with opengl, for which
  81     // pixel formats were added in the first place
  82     protected static boolean pfDisabled;
  83     private static AWTPermission fullScreenExclusivePermission;
  84     // the original display mode we had before entering the fullscreen
  85     // mode
  86     private DisplayMode defaultDisplayMode;
  87     // activation/deactivation listener for the full-screen window
  88     private WindowListener fsWindowListener;
  89 
  90     private float scaleX;
  91     private float scaleY;
  92 
  93     static {
  94 
  95         // 4455041 - Even when ddraw is disabled, ddraw.dll is loaded when
  96         // pixel format calls are made.  This causes problems when a Java app
  97         // is run as an NT service.  To prevent the loading of ddraw.dll
  98         // completely, sun.awt.nopixfmt should be set as well.  Apps which use
  99         // OpenGL w/ Java probably don't want to set this.
 100         String nopixfmt = java.security.AccessController.doPrivileged(
 101             new sun.security.action.GetPropertyAction("sun.awt.nopixfmt"));
 102         pfDisabled = (nopixfmt != null);
 103         initIDs();
 104     }
 105 
 106     private static native void initIDs();
 107 
 108     native void initDevice(int screen);
 109     native void initNativeScale(int screen);
 110     native void setNativeScale(int screen, float scaleX, float scaleY);
 111     native float getNativeScaleX(int screen);
 112     native float getNativeScaleY(int screen);
 113 
 114     public Win32GraphicsDevice(int screennum) {
 115         this.screen = screennum;
 116         // we cache the strings because we want toString() and getIDstring
 117         // to reflect the original screen number (which may change if the
 118         // device is removed)
 119         idString = "\\Display"+screen;
 120         // REMIND: may be should use class name?
 121         descString = "Win32GraphicsDevice[screen=" + screen;
 122         valid = true;
 123 
 124         initDevice(screennum);
 125         initScaleFactors();
 126     }
 127 
 128     /**
 129      * Returns the type of the graphics device.
 130      * @see #TYPE_RASTER_SCREEN
 131      * @see #TYPE_PRINTER
 132      * @see #TYPE_IMAGE_BUFFER
 133      */
 134     public int getType() {
 135         return TYPE_RASTER_SCREEN;
 136     }
 137 
 138     /**
 139      * Returns the Win32 screen of the device.
 140      */
 141     public int getScreen() {
 142         return screen;
 143     }
 144 
 145     public float getDefaultScaleX() {
 146         return scaleX;
 147     }
 148 
 149     public float getDefaultScaleY() {
 150         return scaleY;
 151     }
 152 
 153     private void initScaleFactors() {
 154         if (SunGraphicsEnvironment.isUIScaleEnabled()) {
 155             if (debugScaleX > 0 && debugScaleY > 0) {
 156                 scaleX = debugScaleX;
 157                 scaleY = debugScaleY;
 158                 setNativeScale(screen, scaleX, scaleY);
 159             } else {
 160                 initNativeScale(screen);
 161                 scaleX = getNativeScaleX(screen);
 162                 scaleY = getNativeScaleY(screen);
 163             }
 164         } else {
 165             scaleX = 1;
 166             scaleY = 1;
 167         }
 168     }
 169 
 170     /**
 171      * Returns whether this is a valid devicie. Device can become
 172      * invalid as a result of device removal event.
 173      */
 174     public boolean isValid() {
 175         return valid;
 176     }
 177 
 178     /**
 179      * Called from native code when the device was removed.
 180      *
 181      * @param defaultScreen the current default screen
 182      */
 183     protected void invalidate(int defaultScreen) {
 184         valid = false;
 185         screen = defaultScreen;
 186     }
 187 
 188     /**
 189      * Returns the identification string associated with this graphics
 190      * device.
 191      */
 192     public String getIDstring() {
 193         return idString;
 194     }
 195 
 196 
 197     /**
 198      * Returns all of the graphics
 199      * configurations associated with this graphics device.
 200      */
 201     public GraphicsConfiguration[] getConfigurations() {
 202         if (configs==null) {
 203             if (WindowsFlags.isOGLEnabled() && isDefaultDevice()) {
 204                 defaultConfig = getDefaultConfiguration();
 205                 if (defaultConfig != null) {
 206                     configs = new GraphicsConfiguration[1];
 207                     configs[0] = defaultConfig;
 208                     return configs.clone();
 209                 }
 210             }
 211 
 212             int max = getMaxConfigs(screen);
 213             int defaultPixID = getDefaultPixID(screen);
 214             Vector<GraphicsConfiguration> v = new Vector<>( max );
 215             if (defaultPixID == 0) {
 216                 // Workaround for failing GDI calls
 217                 defaultConfig = Win32GraphicsConfig.getConfig(this,
 218                                                               defaultPixID);
 219                 v.addElement(defaultConfig);
 220             }
 221             else {
 222                 for (int i = 1; i <= max; i++) {
 223                     if (isPixFmtSupported(i, screen)) {
 224                         if (i == defaultPixID) {
 225                             defaultConfig = Win32GraphicsConfig.getConfig(
 226                              this, i);
 227                             v.addElement(defaultConfig);
 228                         }
 229                         else {
 230                             v.addElement(Win32GraphicsConfig.getConfig(
 231                              this, i));
 232                         }
 233                     }
 234                 }
 235             }
 236             configs = new GraphicsConfiguration[v.size()];
 237             v.copyInto(configs);
 238         }
 239         return configs.clone();
 240     }
 241 
 242     /**
 243      * Returns the maximum number of graphics configurations available, or 1
 244      * if PixelFormat calls fail or are disabled.
 245      * This number is less than or equal to the number of graphics
 246      * configurations supported.
 247      */
 248     protected int getMaxConfigs(int screen) {
 249         if (pfDisabled) {
 250             return 1;
 251         } else {
 252             return getMaxConfigsImpl(screen);
 253         }
 254     }
 255 
 256     private native int getMaxConfigsImpl(int screen);
 257 
 258     /**
 259      * Returns whether or not the PixelFormat indicated by index is
 260      * supported.  Supported PixelFormats support drawing to a Window
 261      * (PFD_DRAW_TO_WINDOW), support GDI (PFD_SUPPORT_GDI), and in the
 262      * case of an 8-bit format (cColorBits <= 8) uses indexed colors
 263      * (iPixelType == PFD_TYPE_COLORINDEX).
 264      * We use the index 0 to indicate that PixelFormat calls don't work, or
 265      * are disabled.  Do not call this function with an index of 0.
 266      * @param index a PixelFormat index
 267      */
 268     private native boolean isPixFmtSupported(int index, int screen);
 269 
 270     /**
 271      * Returns the PixelFormatID of the default graphics configuration
 272      * associated with this graphics device, or 0 if PixelFormats calls fail or
 273      * are disabled.
 274      */
 275     protected int getDefaultPixID(int screen) {
 276         if (pfDisabled) {
 277             return 0;
 278         } else {
 279             return getDefaultPixIDImpl(screen);
 280         }
 281     }
 282 
 283     /**
 284      * Returns the default PixelFormat ID from GDI.  Do not call if PixelFormats
 285      * are disabled.
 286      */
 287     private native int getDefaultPixIDImpl(int screen);
 288 
 289     /**
 290      * Returns the default graphics configuration
 291      * associated with this graphics device.
 292      */
 293     public GraphicsConfiguration getDefaultConfiguration() {
 294         if (defaultConfig == null) {
 295             // first try to create a WGLGraphicsConfig if OGL is enabled
 296             // REMIND: the WGL code does not yet work properly in multimon
 297             // situations, so we will fallback on GDI if we are not on the
 298             // default device...
 299             if (WindowsFlags.isOGLEnabled() && isDefaultDevice()) {
 300                 int defPixID = WGLGraphicsConfig.getDefaultPixFmt(screen);
 301                 defaultConfig = WGLGraphicsConfig.getConfig(this, defPixID);
 302                 if (WindowsFlags.isOGLVerbose()) {
 303                     if (defaultConfig != null) {
 304                         System.out.print("OpenGL pipeline enabled");
 305                     } else {
 306                         System.out.print("Could not enable OpenGL pipeline");
 307                     }
 308                     System.out.println(" for default config on screen " +
 309                                        screen);
 310                 }
 311             }
 312 
 313             // Fix for 4669614.  Most apps are not concerned with PixelFormats,
 314             // yet we ALWAYS used them for determining ColorModels and such.
 315             // By passing in 0 as the PixelFormatID here, we signal that
 316             // PixelFormats should not be used, thus avoid loading the opengl
 317             // library.  Apps concerned with PixelFormats can still use
 318             // GraphicsConfiguration.getConfigurations().
 319             // Note that calling native pixel format functions tends to cause
 320             // problems between those functions (which are OpenGL-related)
 321             // and our use of DirectX.  For example, some Matrox boards will
 322             // crash or hang calling these functions when any app is running
 323             // in DirectX fullscreen mode.  So avoiding these calls unless
 324             // absolutely necessary is preferable.
 325             if (defaultConfig == null) {
 326                 defaultConfig = Win32GraphicsConfig.getConfig(this, 0);
 327             }
 328         }
 329         return defaultConfig;
 330     }
 331 
 332     public String toString() {
 333         return valid ? descString + "]" : descString + ", removed]";
 334     }
 335 
 336     /**
 337      * Returns true if this is the default GraphicsDevice for the
 338      * GraphicsEnvironment.
 339      */
 340     private boolean isDefaultDevice() {
 341         return (this ==
 342                 GraphicsEnvironment.
 343                     getLocalGraphicsEnvironment().getDefaultScreenDevice());
 344     }
 345 
 346     private static boolean isFSExclusiveModeAllowed() {
 347         SecurityManager security = System.getSecurityManager();
 348         if (security != null) {
 349             if (fullScreenExclusivePermission == null) {
 350                 fullScreenExclusivePermission =
 351                     new AWTPermission("fullScreenExclusive");
 352             }
 353             try {
 354                 security.checkPermission(fullScreenExclusivePermission);
 355             } catch (SecurityException e) {
 356                 return false;
 357             }
 358         }
 359         return true;
 360     }
 361 
 362     /**
 363      * returns true unless we're not allowed to use fullscreen mode.
 364      */
 365     @Override
 366     public boolean isFullScreenSupported() {
 367         return isFSExclusiveModeAllowed();
 368     }
 369 
 370     @Override
 371     public synchronized void setFullScreenWindow(Window w) {
 372         Window old = getFullScreenWindow();
 373         if (w == old) {
 374             return;
 375         }
 376         if (!isFullScreenSupported()) {
 377             super.setFullScreenWindow(w);
 378             return;
 379         }
 380 
 381         // Enter windowed mode.
 382         if (old != null) {
 383             // restore the original display mode
 384             if (defaultDisplayMode != null) {
 385                 setDisplayMode(defaultDisplayMode);
 386                 // we set the default display mode to null here
 387                 // because the default mode could change during
 388                 // the life of the application (user can change it through
 389                 // the desktop properties dialog, for example), so
 390                 // we need to record it every time prior to
 391                 // entering the fullscreen mode.
 392                 defaultDisplayMode = null;
 393             }
 394             WWindowPeer peer = AWTAccessor.getComponentAccessor().getPeer(old);
 395             if (peer != null) {
 396                 peer.setFullScreenExclusiveModeState(false);
 397                 // we used to destroy the buffers on exiting fs mode, this
 398                 // is no longer needed since fs change will cause a surface
 399                 // data replacement
 400                 synchronized(peer) {
 401                     exitFullScreenExclusive(screen, peer);
 402                 }
 403             }
 404             removeFSWindowListener(old);
 405         }
 406         super.setFullScreenWindow(w);
 407         if (w != null) {
 408             // always record the default display mode prior to going
 409             // fullscreen
 410             defaultDisplayMode = getDisplayMode();
 411             addFSWindowListener(w);
 412             // Enter full screen exclusive mode.
 413             WWindowPeer peer = AWTAccessor.getComponentAccessor().getPeer(w);
 414             if (peer != null) {
 415                 synchronized(peer) {
 416                     enterFullScreenExclusive(screen, peer);
 417                     // Note: removed replaceSurfaceData() call because
 418                     // changing the window size or making it visible
 419                     // will cause this anyway, and both of these events happen
 420                     // as part of switching into fullscreen mode.
 421                 }
 422                 peer.setFullScreenExclusiveModeState(true);
 423             }
 424 
 425             // fix for 4868278
 426             peer.updateGC();
 427         }
 428     }
 429 
 430     // Entering and exiting full-screen mode are done within a
 431     // tree-lock and should never lock on any resources which are
 432     // required by other threads which may have them and may require
 433     // the tree-lock.
 434     // REMIND: in the future these methods may need to become protected so that
 435     // subclasses could override them and use appropriate api other than GDI
 436     // for implementing these functions.
 437     protected native void enterFullScreenExclusive(int screen, WindowPeer w);
 438     protected native void exitFullScreenExclusive(int screen, WindowPeer w);
 439 
 440     @Override
 441     public boolean isDisplayChangeSupported() {
 442         return (isFullScreenSupported() && getFullScreenWindow() != null);
 443     }
 444 
 445     @Override
 446     public synchronized void setDisplayMode(DisplayMode dm) {
 447         if (!isDisplayChangeSupported()) {
 448             super.setDisplayMode(dm);
 449             return;
 450         }
 451         if (dm == null || (dm = getMatchingDisplayMode(dm)) == null) {
 452             throw new IllegalArgumentException("Invalid display mode");
 453         }
 454         if (getDisplayMode().equals(dm)) {
 455             return;
 456         }
 457         Window w = getFullScreenWindow();
 458         if (w != null) {
 459             WWindowPeer peer = AWTAccessor.getComponentAccessor().getPeer(w);
 460             configDisplayMode(screen, peer, dm.getWidth(), dm.getHeight(),
 461                 dm.getBitDepth(), dm.getRefreshRate());
 462             // resize the fullscreen window to the dimensions of the new
 463             // display mode
 464             Rectangle screenBounds = getDefaultConfiguration().getBounds();
 465             w.setBounds(screenBounds.x, screenBounds.y,
 466                         dm.getWidth(), dm.getHeight());
 467             // Note: no call to replaceSurfaceData is required here since
 468             // replacement will be caused by an upcoming display change event
 469         } else {
 470             throw new IllegalStateException("Must be in fullscreen mode " +
 471                                             "in order to set display mode");
 472         }
 473     }
 474 
 475     protected native DisplayMode getCurrentDisplayMode(int screen);
 476     protected native void configDisplayMode(int screen, WindowPeer w, int width,
 477                                           int height, int bitDepth,
 478                                           int refreshRate);
 479     protected native void enumDisplayModes(int screen, ArrayList<DisplayMode> modes);
 480 
 481     @Override
 482     public synchronized DisplayMode getDisplayMode() {
 483         DisplayMode res = getCurrentDisplayMode(screen);
 484         return res;
 485     }
 486 
 487     @Override
 488     public synchronized DisplayMode[] getDisplayModes() {
 489         ArrayList<DisplayMode> modes = new ArrayList<>();
 490         enumDisplayModes(screen, modes);
 491         int listSize = modes.size();
 492         DisplayMode[] retArray = new DisplayMode[listSize];
 493         for (int i = 0; i < listSize; i++) {
 494             retArray[i] = modes.get(i);
 495         }
 496         return retArray;
 497     }
 498 
 499     protected synchronized DisplayMode getMatchingDisplayMode(DisplayMode dm) {
 500         if (!isDisplayChangeSupported()) {
 501             return null;
 502         }
 503         DisplayMode[] modes = getDisplayModes();
 504         for (DisplayMode mode : modes) {
 505             if (dm.equals(mode) ||
 506                 (dm.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN &&
 507                  dm.getWidth() == mode.getWidth() &&
 508                  dm.getHeight() == mode.getHeight() &&
 509                  dm.getBitDepth() == mode.getBitDepth()))
 510             {
 511                 return mode;
 512             }
 513         }
 514         return null;
 515     }
 516 
 517     /*
 518      * From the DisplayChangeListener interface.
 519      * Called from Win32GraphicsEnvironment when the display settings have
 520      * changed.
 521      */
 522     public void displayChanged() {
 523         dynamicColorModel = null;
 524         defaultConfig = null;
 525         configs = null;
 526         initScaleFactors();
 527         // pass on to all top-level windows on this display
 528         topLevels.notifyListeners();
 529     }
 530 
 531     /**
 532      * Part of the DisplayChangedListener interface: devices
 533      * do not need to react to this event
 534      */
 535     public void paletteChanged() {
 536     }
 537 
 538     /*
 539      * Add a DisplayChangeListener to be notified when the display settings
 540      * are changed.  Typically, only top-level containers need to be added
 541      * to Win32GraphicsDevice.
 542      */
 543     public void addDisplayChangedListener(DisplayChangedListener client) {
 544         topLevels.add(client);
 545     }
 546 
 547     /*
 548      * Remove a DisplayChangeListener from this Win32GraphicsDevice
 549      */
 550      public void removeDisplayChangedListener(DisplayChangedListener client) {
 551         topLevels.remove(client);
 552     }
 553 
 554     /**
 555      * Creates and returns the color model associated with this device
 556      */
 557     private native ColorModel makeColorModel (int screen,
 558                                               boolean dynamic);
 559 
 560     /**
 561      * Returns a dynamic ColorModel which is updated when there
 562      * are any changes (e.g., palette changes) in the device
 563      */
 564     public ColorModel getDynamicColorModel() {
 565         if (dynamicColorModel == null) {
 566             dynamicColorModel = makeColorModel(screen, true);
 567         }
 568         return dynamicColorModel;
 569     }
 570 
 571     /**
 572      * Returns the non-dynamic ColorModel associated with this device
 573      */
 574     public ColorModel getColorModel() {
 575         if (colorModel == null)  {
 576             colorModel = makeColorModel(screen, false);
 577         }
 578         return colorModel;
 579     }
 580 
 581     /**
 582      * WindowAdapter class responsible for de/iconifying full-screen window
 583      * of this device.
 584      *
 585      * The listener restores the default display mode when window is iconified
 586      * and sets it back to the one set by the user on de-iconification.
 587      */
 588     private static class Win32FSWindowAdapter extends WindowAdapter {
 589         private Win32GraphicsDevice device;
 590         private DisplayMode dm;
 591 
 592         Win32FSWindowAdapter(Win32GraphicsDevice device) {
 593             this.device = device;
 594         }
 595 
 596         private void setFSWindowsState(Window other, int state) {
 597             GraphicsDevice gds[] =
 598                     GraphicsEnvironment.getLocalGraphicsEnvironment().
 599                     getScreenDevices();
 600             // check if the de/activation was caused by other
 601             // fs window and ignore the event if that's the case
 602             if (other != null) {
 603                 for (GraphicsDevice gd : gds) {
 604                     if (other == gd.getFullScreenWindow()) {
 605                         return;
 606                     }
 607                 }
 608             }
 609             // otherwise apply state to all fullscreen windows
 610             for (GraphicsDevice gd : gds) {
 611                 Window fsw = gd.getFullScreenWindow();
 612                 if (fsw instanceof Frame) {
 613                     ((Frame)fsw).setExtendedState(state);
 614                 }
 615             }
 616         }
 617 
 618         @Override
 619         public void windowDeactivated(WindowEvent e) {
 620             setFSWindowsState(e.getOppositeWindow(), Frame.ICONIFIED);
 621         }
 622 
 623         @Override
 624         public void windowActivated(WindowEvent e) {
 625             setFSWindowsState(e.getOppositeWindow(), Frame.NORMAL);
 626         }
 627 
 628         @Override
 629         public void windowIconified(WindowEvent e) {
 630             // restore the default display mode for this device
 631             DisplayMode ddm = device.defaultDisplayMode;
 632             if (ddm != null) {
 633                 dm = device.getDisplayMode();
 634                 device.setDisplayMode(ddm);
 635             }
 636         }
 637 
 638         @Override
 639         public void windowDeiconified(WindowEvent e) {
 640             // restore the user-set display mode for this device
 641             if (dm != null) {
 642                 device.setDisplayMode(dm);
 643                 dm = null;
 644             }
 645         }
 646     }
 647 
 648     /**
 649      * Adds a WindowListener to be used as
 650      * activation/deactivation listener for the current full-screen window.
 651      *
 652      * @param w full-screen window
 653      */
 654     protected void addFSWindowListener(final Window w) {
 655         // Note: even though we create a listener for Window instances of
 656         // fs windows they will not receive window events.
 657         fsWindowListener = new Win32FSWindowAdapter(this);
 658 
 659         // Fix for 6709453. Using invokeLater to avoid listening
 660         // for the events already posted to the queue.
 661         EventQueue.invokeLater(new Runnable() {
 662             public void run() {
 663                 w.addWindowListener(fsWindowListener);
 664             }
 665         });
 666     }
 667 
 668     /**
 669      * Removes the fs window listener.
 670      *
 671      * @param w full-screen window
 672      */
 673     protected void removeFSWindowListener(Window w) {
 674         w.removeWindowListener(fsWindowListener);
 675         fsWindowListener = null;
 676     }
 677 }