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