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