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