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