1 /*
   2  * Copyright (c) 2004, 2015, 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 sun.security.action.GetIntegerAction;
  33 import com.sun.java.swing.plaf.gtk.GTKConstants.TextDirection;
  34 import sun.java2d.opengl.OGLRenderQueue;
  35 
  36 public abstract class UNIXToolkit extends SunToolkit
  37 {
  38     /** All calls into GTK should be synchronized on this lock */
  39     public static final Object GTK_LOCK = new Object();
  40 
  41     private static final int[] BAND_OFFSETS = { 0, 1, 2 };
  42     private static final int[] BAND_OFFSETS_ALPHA = { 0, 1, 2, 3 };
  43     private static final int DEFAULT_DATATRANSFER_TIMEOUT = 10000;
  44 
  45     private Boolean nativeGTKAvailable;
  46     private Boolean nativeGTKLoaded;
  47     private BufferedImage tmpImage = null;
  48 
  49     public static int getDatatransferTimeout() {
  50         Integer dt = AccessController.doPrivileged(
  51                 new GetIntegerAction("sun.awt.datatransfer.timeout"));
  52         if (dt == null || dt <= 0) {
  53             return DEFAULT_DATATRANSFER_TIMEOUT;
  54         } else {
  55             return dt;
  56         }
  57     }
  58 
  59     /**
  60      * Returns true if the native GTK libraries are capable of being
  61      * loaded and are expected to work properly, false otherwise.  Note
  62      * that this method will not leave the native GTK libraries loaded if
  63      * they haven't already been loaded.  This allows, for example, Swing's
  64      * GTK L&F to test for the presence of native GTK support without
  65      * leaving the native libraries loaded.  To attempt long-term loading
  66      * of the native GTK libraries, use the loadGTK() method instead.
  67      */
  68     @Override
  69     public boolean isNativeGTKAvailable() {
  70         synchronized (GTK_LOCK) {
  71             if (nativeGTKLoaded != null) {
  72                 // We've already attempted to load GTK, so just return the
  73                 // status of that attempt.
  74                 return nativeGTKLoaded;
  75 
  76             } else if (nativeGTKAvailable != null) {
  77                 // We've already checked the availability of the native GTK
  78                 // libraries, so just return the status of that attempt.
  79                 return nativeGTKAvailable;
  80 
  81             } else {
  82                 boolean success = check_gtk();
  83                 nativeGTKAvailable = success;
  84                 return success;
  85             }
  86         }
  87     }
  88 
  89     /**
  90      * Loads the GTK libraries, if necessary.  The first time this method
  91      * is called, it will attempt to load the native GTK library.  If
  92      * successful, it leaves the library open and returns true; otherwise,
  93      * the library is left closed and returns false.  On future calls to
  94      * this method, the status of the first attempt is returned (a simple
  95      * lightweight boolean check, no native calls required).
  96      */
  97     public boolean loadGTK() {
  98         synchronized (GTK_LOCK) {
  99             if (nativeGTKLoaded == null) {
 100                 nativeGTKLoaded = load_gtk();
 101             }
 102         }
 103         return nativeGTKLoaded;
 104     }
 105 
 106     /**
 107      * Overridden to handle GTK icon loading
 108      */
 109     @Override
 110     protected Object lazilyLoadDesktopProperty(String name) {
 111         if (name.startsWith("gtk.icon.")) {
 112             return lazilyLoadGTKIcon(name);
 113         }
 114         return super.lazilyLoadDesktopProperty(name);
 115     }
 116 
 117     /**
 118      * Load a native Gtk stock icon.
 119      *
 120      * @param longname a desktop property name. This contains icon name, size
 121      *        and orientation, e.g. <code>"gtk.icon.gtk-add.4.rtl"</code>
 122      * @return an <code>Image</code> for the icon, or <code>null</code> if the
 123      *         icon could not be loaded
 124      */
 125     protected Object lazilyLoadGTKIcon(String longname) {
 126         // Check if we have already loaded it.
 127         Object result = desktopProperties.get(longname);
 128         if (result != null) {
 129             return result;
 130         }
 131 
 132         // We need to have at least gtk.icon.<stock_id>.<size>.<orientation>
 133         String str[] = longname.split("\\.");
 134         if (str.length != 5) {
 135             return null;
 136         }
 137 
 138         // Parse out the stock icon size we are looking for.
 139         int size = 0;
 140         try {
 141             size = Integer.parseInt(str[3]);
 142         } catch (NumberFormatException nfe) {
 143             return null;
 144         }
 145 
 146         // Direction.
 147         TextDirection dir = ("ltr".equals(str[4]) ? TextDirection.LTR :
 148                                                     TextDirection.RTL);
 149 
 150         // Load the stock icon.
 151         BufferedImage img = getStockIcon(-1, str[2], size, dir.ordinal(), null);
 152         if (img != null) {
 153             // Create the desktop property for the icon.
 154             setDesktopProperty(longname, img);
 155         }
 156         return img;
 157     }
 158 
 159     /**
 160      * Returns a BufferedImage which contains the Gtk icon requested.  If no
 161      * such icon exists or an error occurs loading the icon the result will
 162      * be null.
 163      *
 164      * @param filename
 165      * @return The icon or null if it was not found or loaded.
 166      */
 167     public BufferedImage getGTKIcon(final String filename) {
 168         if (!loadGTK()) {
 169             return null;
 170 
 171         } else {
 172             // Call the native method to load the icon.
 173             synchronized (GTK_LOCK) {
 174                 if (!load_gtk_icon(filename)) {
 175                     tmpImage = null;
 176                 }
 177             }
 178         }
 179         // Return local image the callback loaded the icon into.
 180         return tmpImage;
 181     }
 182 
 183     /**
 184      * Returns a BufferedImage which contains the Gtk stock icon requested.
 185      * If no such stock icon exists the result will be null.
 186      *
 187      * @param widgetType one of WidgetType values defined in GTKNativeEngine or
 188      * -1 for system default stock icon.
 189      * @param stockId String which defines the stock id of the gtk item.
 190      * For a complete list reference the API at www.gtk.org for StockItems.
 191      * @param iconSize One of the GtkIconSize values defined in GTKConstants
 192      * @param textDirection One of the TextDirection values defined in
 193      * GTKConstants
 194      * @param detail Render detail that is passed to the native engine (feel
 195      * free to pass null)
 196      * @return The stock icon or null if it was not found or loaded.
 197      */
 198     public BufferedImage getStockIcon(final int widgetType, final String stockId,
 199                                 final int iconSize, final int direction,
 200                                 final String detail) {
 201         if (!loadGTK()) {
 202             return null;
 203 
 204         } else {
 205             // Call the native method to load the icon.
 206             synchronized (GTK_LOCK) {
 207                 if (!load_stock_icon(widgetType, stockId, iconSize, direction, detail)) {
 208                     tmpImage = null;
 209                 }
 210             }
 211         }
 212         // Return local image the callback loaded the icon into.
 213         return tmpImage;  // set by loadIconCallback
 214     }
 215 
 216     /**
 217      * This method is used by JNI as a callback from load_stock_icon.
 218      * Image data is passed back to us via this method and loaded into the
 219      * local BufferedImage and then returned via getStockIcon.
 220      *
 221      * Do NOT call this method directly.
 222      */
 223     public void loadIconCallback(byte[] data, int width, int height,
 224             int rowStride, int bps, int channels, boolean alpha) {
 225         // Reset the stock image to null.
 226         tmpImage = null;
 227 
 228         // Create a new BufferedImage based on the data returned from the
 229         // JNI call.
 230         DataBuffer dataBuf = new DataBufferByte(data, (rowStride * height));
 231         // Maybe test # channels to determine band offsets?
 232         WritableRaster raster = Raster.createInterleavedRaster(dataBuf,
 233                 width, height, rowStride, channels,
 234                 (alpha ? BAND_OFFSETS_ALPHA : BAND_OFFSETS), null);
 235         ColorModel colorModel = new ComponentColorModel(
 236                 ColorSpace.getInstance(ColorSpace.CS_sRGB), alpha, false,
 237                 ColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE);
 238 
 239         // Set the local image so we can return it later from
 240         // getStockIcon().
 241         tmpImage = new BufferedImage(colorModel, raster, false, null);
 242     }
 243 
 244     private static native boolean check_gtk();
 245     private static native boolean load_gtk();
 246     private static native boolean unload_gtk();
 247     private native boolean load_gtk_icon(String filename);
 248     private native boolean load_stock_icon(int widget_type, String stock_id,
 249             int iconSize, int textDirection, String detail);
 250 
 251     private native void nativeSync();
 252 
 253     @Override
 254     public void sync() {
 255         // flush the X11 buffer
 256         nativeSync();
 257         // now flush the OGL pipeline (this is a no-op if OGL is not enabled)
 258         OGLRenderQueue.sync();
 259     }
 260 
 261     /*
 262      * This returns the value for the desktop property "awt.font.desktophints"
 263      * It builds this by querying the Gnome desktop properties to return
 264      * them as platform independent hints.
 265      * This requires that the Gnome properties have already been gathered.
 266      */
 267     public static final String FONTCONFIGAAHINT = "fontconfig/Antialias";
 268 
 269     @Override
 270     protected RenderingHints getDesktopAAHints() {
 271 
 272         Object aaValue = getDesktopProperty("gnome.Xft/Antialias");
 273 
 274         if (aaValue == null) {
 275             /* On a KDE desktop running KWin the rendering hint will
 276              * have been set as property "fontconfig/Antialias".
 277              * No need to parse further in this case.
 278              */
 279             aaValue = getDesktopProperty(FONTCONFIGAAHINT);
 280             if (aaValue != null) {
 281                return new RenderingHints(KEY_TEXT_ANTIALIASING, aaValue);
 282             } else {
 283                  return null; // no Gnome or KDE Desktop properties available.
 284             }
 285         }
 286 
 287         /* 0 means off, 1 means some ON. What would any other value mean?
 288          * If we require "1" to enable AA then some new value would cause
 289          * us to default to "OFF". I don't think that's the best guess.
 290          * So if its !=0 then lets assume AA.
 291          */
 292         boolean aa = ((aaValue instanceof Number)
 293                         && ((Number) aaValue).intValue() != 0);
 294         Object aaHint;
 295         if (aa) {
 296             String subpixOrder =
 297                 (String)getDesktopProperty("gnome.Xft/RGBA");
 298 
 299             if (subpixOrder == null || subpixOrder.equals("none")) {
 300                 aaHint = VALUE_TEXT_ANTIALIAS_ON;
 301             } else if (subpixOrder.equals("rgb")) {
 302                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_HRGB;
 303             } else if (subpixOrder.equals("bgr")) {
 304                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_HBGR;
 305             } else if (subpixOrder.equals("vrgb")) {
 306                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_VRGB;
 307             } else if (subpixOrder.equals("vbgr")) {
 308                 aaHint = VALUE_TEXT_ANTIALIAS_LCD_VBGR;
 309             } else {
 310                 /* didn't recognise the string, but AA is requested */
 311                 aaHint = VALUE_TEXT_ANTIALIAS_ON;
 312             }
 313         } else {
 314             aaHint = VALUE_TEXT_ANTIALIAS_DEFAULT;
 315         }
 316         return new RenderingHints(KEY_TEXT_ANTIALIASING, aaHint);
 317     }
 318 
 319     private native boolean gtkCheckVersionImpl(int major, int minor,
 320         int micro);
 321 
 322     /**
 323      * Returns {@code true} if the GTK+ library is compatible with the given
 324      * version.
 325      *
 326      * @param major
 327      *            The required major version.
 328      * @param minor
 329      *            The required minor version.
 330      * @param micro
 331      *            The required micro version.
 332      * @return {@code true} if the GTK+ library is compatible with the given
 333      *         version.
 334      */
 335     public boolean checkGtkVersion(int major, int minor, int micro) {
 336         if (loadGTK()) {
 337             return gtkCheckVersionImpl(major, minor, micro);
 338         }
 339         return false;
 340     }
 341 }