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