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 
  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 }