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