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