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