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