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
26package sun.awt;
27
28import java.awt.AWTPermission;
29import java.awt.GraphicsDevice;
30import java.awt.GraphicsConfiguration;
31import java.awt.GraphicsEnvironment;
32import java.awt.DisplayMode;
33import java.awt.EventQueue;
34import java.awt.Frame;
35import java.awt.Rectangle;
36import java.awt.Window;
37import java.awt.event.WindowAdapter;
38import java.awt.event.WindowEvent;
39import java.awt.event.WindowListener;
40import java.awt.geom.Point2D;
41import java.awt.image.ColorModel;
42import java.util.ArrayList;
43import java.util.Vector;
44import java.awt.peer.WindowPeer;
45import java.security.AccessController;
46import sun.awt.windows.WWindowPeer;
47import sun.java2d.SunGraphicsEnvironment;
48import sun.java2d.opengl.WGLGraphicsConfig;
49import sun.java2d.windows.WindowsFlags;
50import sun.security.action.GetPropertyAction;
51import static sun.awt.Win32GraphicsEnvironment.debugScaleX;
52import 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 */
61public 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}
678