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 */
25package sun.awt;
26
27import java.awt.RenderingHints;
28import static java.awt.RenderingHints.*;
29import java.awt.color.ColorSpace;
30import java.awt.image.*;
31import java.security.AccessController;
32import java.security.PrivilegedAction;
33
34import sun.security.action.GetIntegerAction;
35import com.sun.java.swing.plaf.gtk.GTKConstants.TextDirection;
36import sun.java2d.opengl.OGLRenderQueue;
37import sun.security.action.GetPropertyAction;
38
39public 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
61        GtkVersions(int number) {
62            this.number = number;
63        }
64
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
77        public int getNumber() {
78            return number;
79        }
80    };
81
82    private Boolean nativeGTKAvailable;
83    private Boolean nativeGTKLoaded;
84    private BufferedImage tmpImage = null;
85
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
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     */
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
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     */
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     */
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     */
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     */
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
282    private static native boolean check_gtk(int version);
283    private static native boolean load_gtk(int version, boolean verbose);
284    private static native boolean unload_gtk();
285    private native boolean load_gtk_icon(String filename);
286    private native boolean load_stock_icon(int widget_type, String stock_id,
287            int iconSize, int textDirection, String detail);
288
289    private native void nativeSync();
290    private static native int get_gtk_version();
291
292    @Override
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
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
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     */
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
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
394    public static GtkVersions getGtkVersion() {
395        return GtkVersions.getVersion(get_gtk_version());
396    }
397
398    public static boolean isGtkVerbose() {
399        return AccessController.doPrivileged((PrivilegedAction<Boolean>)()
400                -> Boolean.getBoolean("jdk.gtk.verbose"));
401    }
402}
403