1 /*
2  * Copyright (c) 2004, 2018, 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 package sun.awt;
26 
27 import java.awt.RenderingHints;
28 import static java.awt.RenderingHints.*;
29 import java.awt.color.ColorSpace;
30 import java.awt.image.*;
31 import java.security.AccessController;
32 import java.security.PrivilegedAction;
33 
34 import sun.security.action.GetIntegerAction;
35 import com.sun.java.swing.plaf.gtk.GTKConstants.TextDirection;
36 import sun.java2d.opengl.OGLRenderQueue;
37 import sun.security.action.GetPropertyAction;
38 
39 public abstract class UNIXToolkit extends SunToolkit
40 {
41     /** All calls into GTK should be synchronized on this lock */
42     public static final Object GTK_LOCK = new Object();
43 
44     private static final int[] BAND_OFFSETS = { 0, 1, 2 };
45     private static final int[] BAND_OFFSETS_ALPHA = { 0, 1, 2, 3 };
46     private static final int DEFAULT_DATATRANSFER_TIMEOUT = 10000;
47 
48     // Allowed GTK versions
49     public enum GtkVersions {
50         ANY(0),
51         GTK2(Constants.GTK2_MAJOR_NUMBER),
52         GTK3(Constants.GTK3_MAJOR_NUMBER);
53 
54         static class Constants {
55             static final int GTK2_MAJOR_NUMBER = 2;
56             static final int GTK3_MAJOR_NUMBER = 3;
57         }
58 
59         final int number;
60 
GtkVersions(int number)61         GtkVersions(int number) {
62             this.number = number;
63         }
64 
getVersion(int number)65         public static GtkVersions getVersion(int number) {
66             switch (number) {
67                 case Constants.GTK2_MAJOR_NUMBER:
68                     return GTK2;
69                 case Constants.GTK3_MAJOR_NUMBER:
70                     return GTK3;
71                 default:
72                     return ANY;
73             }
74         }
75 
76         // major GTK version number
getNumber()77         public int getNumber() {
78             return number;
79         }
80     };
81 
82     private Boolean nativeGTKAvailable;
83     private Boolean nativeGTKLoaded;
84     private BufferedImage tmpImage = null;
85 
getDatatransferTimeout()86     public static int getDatatransferTimeout() {
87         Integer dt = AccessController.doPrivileged(
88                 new GetIntegerAction("sun.awt.datatransfer.timeout"));
89         if (dt == null || dt <= 0) {
90             return DEFAULT_DATATRANSFER_TIMEOUT;
91         } else {
92             return dt;
93         }
94     }
95 
96     @Override
getDesktop()97     public String getDesktop() {
98         String gsi = AccessController.doPrivileged(
99                         (PrivilegedAction<String>) ()
100                                 -> System.getenv("GNOME_DESKTOP_SESSION_ID"));
101         return (gsi != null) ? "gnome" : null;
102     }
103 
104     /**
105      * Returns true if the native GTK libraries are capable of being
106      * loaded and are expected to work properly, false otherwise.  Note
107      * that this method will not leave the native GTK libraries loaded if
108      * they haven't already been loaded.  This allows, for example, Swing's
109      * GTK L&F to test for the presence of native GTK support without
110      * leaving the native libraries loaded.  To attempt long-term loading
111      * of the native GTK libraries, use the loadGTK() method instead.
112      */
113     @Override
isNativeGTKAvailable()114     public boolean isNativeGTKAvailable() {
115         synchronized (GTK_LOCK) {
116             if (nativeGTKLoaded != null) {
117                 // We've already attempted to load GTK, so just return the
118                 // status of that attempt.
119                 return nativeGTKLoaded;
120 
121             } else if (nativeGTKAvailable != null) {
122                 // We've already checked the availability of the native GTK
123                 // libraries, so just return the status of that attempt.
124                 return nativeGTKAvailable;
125 
126             } else {
127                 boolean success = check_gtk(getEnabledGtkVersion().getNumber());
128                 nativeGTKAvailable = success;
129                 return success;
130             }
131         }
132     }
133 
134     /**
135      * Loads the GTK libraries, if necessary.  The first time this method
136      * is called, it will attempt to load the native GTK library.  If
137      * successful, it leaves the library open and returns true; otherwise,
138      * the library is left closed and returns false.  On future calls to
139      * this method, the status of the first attempt is returned (a simple
140      * lightweight boolean check, no native calls required).
141      */
loadGTK()142     public boolean loadGTK() {
143         synchronized (GTK_LOCK) {
144             if (nativeGTKLoaded == null) {
145                 nativeGTKLoaded = load_gtk(getEnabledGtkVersion().getNumber(),
146                                                                 isGtkVerbose());
147             }
148         }
149         return nativeGTKLoaded;
150     }
151 
152     /**
153      * Overridden to handle GTK icon loading
154      */
155     @Override
lazilyLoadDesktopProperty(String name)156     protected Object lazilyLoadDesktopProperty(String name) {
157         if (name.startsWith("gtk.icon.")) {
158             return lazilyLoadGTKIcon(name);
159         }
160         return super.lazilyLoadDesktopProperty(name);
161     }
162 
163     /**
164      * Load a native Gtk stock icon.
165      *
166      * @param longname a desktop property name. This contains icon name, size
167      *        and orientation, e.g. {@code "gtk.icon.gtk-add.4.rtl"}
168      * @return an {@code Image} for the icon, or {@code null} if the
169      *         icon could not be loaded
170      */
lazilyLoadGTKIcon(String longname)171     protected Object lazilyLoadGTKIcon(String longname) {
172         // Check if we have already loaded it.
173         Object result = desktopProperties.get(longname);
174         if (result != null) {
175             return result;
176         }
177 
178         // We need to have at least gtk.icon.<stock_id>.<size>.<orientation>
179         String[] str = longname.split("\\.");
180         if (str.length != 5) {
181             return null;
182         }
183 
184         // Parse out the stock icon size we are looking for.
185         int size = 0;
186         try {
187             size = Integer.parseInt(str[3]);
188         } catch (NumberFormatException nfe) {
189             return null;
190         }
191 
192         // Direction.
193         TextDirection dir = ("ltr".equals(str[4]) ? TextDirection.LTR :
194                                                     TextDirection.RTL);
195 
196         // Load the stock icon.
197         BufferedImage img = getStockIcon(-1, str[2], size, dir.ordinal(), null);
198         if (img != null) {
199             // Create the desktop property for the icon.
200             setDesktopProperty(longname, img);
201         }
202         return img;
203     }
204 
205     /**
206      * Returns a BufferedImage which contains the Gtk icon requested.  If no
207      * such icon exists or an error occurs loading the icon the result will
208      * be null.
209      *
210      * @param filename
211      * @return The icon or null if it was not found or loaded.
212      */
getGTKIcon(final String filename)213     public BufferedImage getGTKIcon(final String filename) {
214         if (!loadGTK()) {
215             return null;
216 
217         } else {
218             // Call the native method to load the icon.
219             synchronized (GTK_LOCK) {
220                 if (!load_gtk_icon(filename)) {
221                     tmpImage = null;
222                 }
223             }
224         }
225         // Return local image the callback loaded the icon into.
226         return tmpImage;
227     }
228 
229     /**
230      * Returns a BufferedImage which contains the Gtk stock icon requested.
231      * If no such stock icon exists the result will be null.
232      *
233      * @param widgetType one of WidgetType values defined in GTKNativeEngine or
234      * -1 for system default stock icon.
235      * @param stockId String which defines the stock id of the gtk item.
236      * For a complete list reference the API at www.gtk.org for StockItems.
237      * @param iconSize One of the GtkIconSize values defined in GTKConstants
238      * @param direction One of the TextDirection values defined in
239      * GTKConstants
240      * @param detail Render detail that is passed to the native engine (feel
241      * free to pass null)
242      * @return The stock icon or null if it was not found or loaded.
243      */
getStockIcon(final int widgetType, final String stockId, final int iconSize, final int direction, final String detail)244     public BufferedImage getStockIcon(final int widgetType, final String stockId,
245                                 final int iconSize, final int direction,
246                                 final String detail) {
247         if (!loadGTK()) {
248             return null;
249 
250         } else {
251             // Call the native method to load the icon.
252             synchronized (GTK_LOCK) {
253                 if (!load_stock_icon(widgetType, stockId, iconSize, direction, detail)) {
254                     tmpImage = null;
255                 }
256             }
257         }
258         // Return local image the callback loaded the icon into.
259         return tmpImage;  // set by loadIconCallback
260     }
261 
262     /**
263      * This method is used by JNI as a callback from load_stock_icon.
264      * Image data is passed back to us via this method and loaded into the
265      * local BufferedImage and then returned via getStockIcon.
266      *
267      * Do NOT call this method directly.
268      */
loadIconCallback(byte[] data, int width, int height, int rowStride, int bps, int channels, boolean alpha)269     public void loadIconCallback(byte[] data, int width, int height,
270             int rowStride, int bps, int channels, boolean alpha) {
271         // Reset the stock image to null.
272         tmpImage = null;
273 
274         // Create a new BufferedImage based on the data returned from the
275         // JNI call.
276         DataBuffer dataBuf = new DataBufferByte(data, (rowStride * height));
277         // Maybe test # channels to determine band offsets?
278         WritableRaster raster = Raster.createInterleavedRaster(dataBuf,
279                 width, height, rowStride, channels,
280                 (alpha ? BAND_OFFSETS_ALPHA : BAND_OFFSETS), null);
281         ColorModel colorModel = new ComponentColorModel(
282                 ColorSpace.getInstance(ColorSpace.CS_sRGB), alpha, false,
283                 ColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE);
284 
285         // Set the local image so we can return it later from
286         // getStockIcon().
287         tmpImage = new BufferedImage(colorModel, raster, false, null);
288     }
289 
check_gtk(int version)290     private static native boolean check_gtk(int version);
load_gtk(int version, boolean verbose)291     private static native boolean load_gtk(int version, boolean verbose);
unload_gtk()292     private static native boolean unload_gtk();
load_gtk_icon(String filename)293     private native boolean load_gtk_icon(String filename);
load_stock_icon(int widget_type, String stock_id, int iconSize, int textDirection, String detail)294     private native boolean load_stock_icon(int widget_type, String stock_id,
295             int iconSize, int textDirection, String detail);
296 
nativeSync()297     private native void nativeSync();
get_gtk_version()298     private static native int get_gtk_version();
299 
300     @Override
sync()301     public void sync() {
302         // flush the X11 buffer
303         nativeSync();
304         // now flush the OGL pipeline (this is a no-op if OGL is not enabled)
305         OGLRenderQueue.sync();
306     }
307 
308     /*
309      * This returns the value for the desktop property "awt.font.desktophints"
310      * It builds this by querying the Gnome desktop properties to return
311      * them as platform independent hints.
312      * This requires that the Gnome properties have already been gathered.
313      */
314     public static final String FONTCONFIGAAHINT = "fontconfig/Antialias";
315 
316     @Override
getDesktopAAHints()317     protected RenderingHints getDesktopAAHints() {
318 
319         Object aaValue = getDesktopProperty("gnome.Xft/Antialias");
320 
321         if (aaValue == null) {
322             /* On a KDE desktop running KWin the rendering hint will
323              * have been set as property "fontconfig/Antialias".
324              * No need to parse further in this case.
325              */
326             aaValue = getDesktopProperty(FONTCONFIGAAHINT);
327             if (aaValue != null) {
328                return new RenderingHints(KEY_TEXT_ANTIALIASING, aaValue);
329             } else {
330                  return null; // no Gnome or KDE Desktop properties available.
331             }
332         }
333 
334         /* 0 means off, 1 means some ON. What would any other value mean?
335          * If we require "1" to enable AA then some new value would cause
336          * us to default to "OFF". I don't think that's the best guess.
337          * So if its !=0 then lets assume AA.
338          */
339         boolean aa = ((aaValue instanceof Number)
340                         && ((Number) aaValue).intValue() != 0);
341         Object aaHint;
342         if (aa) {
343             String subpixOrder =
344                 (String)getDesktopProperty("gnome.Xft/RGBA");
345 
346             if (subpixOrder == null || subpixOrder.equals("none")) {
347                 aaHint = VALUE_TEXT_ANTIALIAS_ON;
348             } else if (subpixOrder.equals("rgb")) {
349                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_HRGB;
350             } else if (subpixOrder.equals("bgr")) {
351                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_HBGR;
352             } else if (subpixOrder.equals("vrgb")) {
353                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_VRGB;
354             } else if (subpixOrder.equals("vbgr")) {
355                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_VBGR;
356             } else {
357                 /* didn't recognise the string, but AA is requested */
358                 aaHint = VALUE_TEXT_ANTIALIAS_ON;
359             }
360         } else {
361             aaHint = VALUE_TEXT_ANTIALIAS_DEFAULT;
362         }
363         return new RenderingHints(KEY_TEXT_ANTIALIASING, aaHint);
364     }
365 
gtkCheckVersionImpl(int major, int minor, int micro)366     private native boolean gtkCheckVersionImpl(int major, int minor,
367         int micro);
368 
369     /**
370      * Returns {@code true} if the GTK+ library is compatible with the given
371      * version.
372      *
373      * @param major
374      *            The required major version.
375      * @param minor
376      *            The required minor version.
377      * @param micro
378      *            The required micro version.
379      * @return {@code true} if the GTK+ library is compatible with the given
380      *         version.
381      */
checkGtkVersion(int major, int minor, int micro)382     public boolean checkGtkVersion(int major, int minor, int micro) {
383         if (loadGTK()) {
384             return gtkCheckVersionImpl(major, minor, micro);
385         }
386         return false;
387     }
388 
getEnabledGtkVersion()389     public static GtkVersions getEnabledGtkVersion() {
390         String version = AccessController.doPrivileged(
391                 new GetPropertyAction("jdk.gtk.version"));
392         if (version == null) {
393             return GtkVersions.ANY;
394         } else if (version.startsWith("2")) {
395             return GtkVersions.GTK2;
396         } else if("3".equals(version) ){
397             return GtkVersions.GTK3;
398         }
399         return GtkVersions.ANY;
400     }
401 
getGtkVersion()402     public static GtkVersions getGtkVersion() {
403         return GtkVersions.getVersion(get_gtk_version());
404     }
405 
isGtkVerbose()406     public static boolean isGtkVerbose() {
407         return AccessController.doPrivileged((PrivilegedAction<Boolean>)()
408                 -> Boolean.getBoolean("jdk.gtk.verbose"));
409     }
410 }
411