1 /*
   2  * Copyright (c) 2010, 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 com.sun.glass.ui.gtk;
  26 
  27 import com.sun.glass.ui.Application;
  28 import com.sun.glass.ui.CommonDialogs.ExtensionFilter;
  29 import com.sun.glass.ui.CommonDialogs.FileChooserResult;
  30 import com.sun.glass.ui.Cursor;
  31 import com.sun.glass.ui.InvokeLaterDispatcher;
  32 import com.sun.glass.ui.Pixels;
  33 import com.sun.glass.ui.Robot;
  34 import com.sun.glass.ui.Screen;
  35 import com.sun.glass.ui.Size;
  36 import com.sun.glass.ui.Timer;
  37 import com.sun.glass.ui.View;
  38 import com.sun.glass.ui.Window;
  39 import com.sun.javafx.util.Logging;
  40 import com.sun.glass.utils.NativeLibLoader;
  41 import com.sun.prism.impl.PrismSettings;
  42 import com.sun.javafx.logging.PlatformLogger;
  43 
  44 import java.io.File;
  45 import java.lang.reflect.Method;
  46 import java.nio.ByteBuffer;
  47 import java.nio.IntBuffer;
  48 import java.security.AccessController;
  49 import java.security.PrivilegedAction;
  50 import java.util.Map;
  51 import java.util.concurrent.CountDownLatch;
  52 import java.lang.annotation.Native;
  53 
  54 final class GtkApplication extends Application implements
  55                                     InvokeLaterDispatcher.InvokeLaterSubmitter {
  56     private static final String SWT_INTERNAL_CLASS =
  57             "org.eclipse.swt.internal.gtk.OS";
  58     private static final int forcedGtkVersion;
  59 
  60 
  61     static  {
  62         //check for SWT-GTK lib presence
  63         Class<?> OS = AccessController.
  64                 doPrivileged((PrivilegedAction<Class<?>>) () -> {
  65                     try {
  66                         return Class.forName(SWT_INTERNAL_CLASS, true,
  67                                 ClassLoader.getSystemClassLoader());
  68                     } catch (Exception e) {}
  69                     try {
  70                         return Class.forName(SWT_INTERNAL_CLASS, true,
  71                                 Thread.currentThread().getContextClassLoader());
  72                     } catch (Exception e) {}
  73                     return null;
  74                 });
  75         if (OS != null) {
  76             PlatformLogger logger = Logging.getJavaFXLogger();
  77             logger.fine("SWT-GTK library found. Try to obtain GTK version.");
  78             Method method = AccessController.
  79                     doPrivileged((PrivilegedAction<Method>) () -> {
  80                         try {
  81                             return OS.getMethod("gtk_major_version");
  82                         } catch (Exception e) {
  83                             return null;
  84                         }
  85                     });
  86             int ver = 0;
  87             if (method != null) {
  88                 try {
  89                     ver = ((Number)method.invoke(OS)).intValue();
  90                 } catch (Exception e) {
  91                     logger.warning("Method gtk_major_version() of " +
  92                          "the org.eclipse.swt.internal.gtk.OS class " +
  93                          "returns error. SWT GTK version cannot be detected. " +
  94                          "GTK3 will be used as default.");
  95                     ver = 3;
  96                 }
  97             }
  98             if (ver < 2 || ver > 3) {
  99                 logger.warning("SWT-GTK uses unsupported major GTK version "
 100                         + ver + ". GTK3 will be used as default.");
 101                 ver = 3;
 102             }
 103             forcedGtkVersion = ver;
 104         } else {
 105             forcedGtkVersion = 0;
 106         }
 107 
 108         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 109             Application.loadNativeLibrary();
 110             return null;
 111         });
 112     }
 113 
 114     public static  int screen = -1;
 115     public static  long display = 0;
 116     public static  long visualID = 0;
 117 
 118     static float overrideUIScale;
 119 
 120     private final InvokeLaterDispatcher invokeLaterDispatcher;
 121 
 122     private static float getFloat(String propname, float defval, String description) {
 123         String str = System.getProperty(propname);
 124         if (str == null) {
 125             str = System.getenv(propname);
 126         }
 127         if (str == null) {
 128             return defval;
 129         }
 130         str = str.trim();
 131         float val;
 132         if (str.endsWith("%")) {
 133             val = Integer.parseInt(str.substring(0, str.length()-1)) / 100.0f;
 134         } else if (str.endsWith("DPI") || str.endsWith("dpi")) {
 135             val = Integer.parseInt(str.substring(0, str.length()-3)) / 96.0f;
 136         } else {
 137             val = Float.parseFloat(str);
 138         }
 139         if (PrismSettings.verbose) {
 140             System.out.println(description+val);
 141         }
 142         return val;
 143     }
 144 
 145     GtkApplication() {
 146 
 147         final int gtkVersion = forcedGtkVersion == 0 ?
 148             AccessController.doPrivileged((PrivilegedAction<Integer>) () -> {
 149                 String v = System.getProperty("jdk.gtk.version","3");
 150                 int ret = 0;
 151                 if ("3".equals(v) || v.startsWith("3.")) {
 152                     ret = 3;
 153                 } else if ("2".equals(v) || v.startsWith("2.")) {
 154                     ret = 2;
 155                 }
 156                 return ret;
 157             }) : forcedGtkVersion;
 158         boolean gtkVersionVerbose =
 159                 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
 160             return Boolean.getBoolean("jdk.gtk.verbose");
 161         });
 162         if (PrismSettings.allowHiDPIScaling) {
 163             overrideUIScale = AccessController.doPrivileged((PrivilegedAction<Float>) () ->
 164                     getFloat("glass.gtk.uiScale", -1.0f, "Forcing UI scaling factor: "));
 165         } else {
 166             overrideUIScale = -1.0f;
 167         }
 168 
 169         int libraryToLoad = _queryLibrary(gtkVersion, gtkVersionVerbose);
 170 
 171         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 172             if (libraryToLoad == QUERY_NO_DISPLAY) {
 173                 throw new UnsupportedOperationException("Unable to open DISPLAY");
 174             } else if (libraryToLoad == QUERY_USE_CURRENT) {
 175                 if (gtkVersionVerbose) {
 176                     System.out.println("Glass GTK library to load is already loaded");
 177                 }
 178             } else if (libraryToLoad == QUERY_LOAD_GTK2) {
 179                 if (gtkVersionVerbose) {
 180                     System.out.println("Glass GTK library to load is glassgtk2");
 181                 }
 182                 NativeLibLoader.loadLibrary("glassgtk2");
 183             } else if (libraryToLoad == QUERY_LOAD_GTK3) {
 184                 if (gtkVersionVerbose) {
 185                     System.out.println("Glass GTK library to load is glassgtk3");
 186                 }
 187                 NativeLibLoader.loadLibrary("glassgtk3");
 188             } else {
 189                 throw new UnsupportedOperationException("Internal Error");
 190             }
 191             return null;
 192         });
 193 
 194         int version = _initGTK(gtkVersion, gtkVersionVerbose, overrideUIScale);
 195 
 196         if (version == -1) {
 197             throw new RuntimeException("Error loading GTK libraries");
 198         }
 199 
 200         // Embedded in SWT, with shared event thread
 201         boolean isEventThread = AccessController
 202                 .doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("javafx.embed.isEventThread"));
 203         if (!isEventThread) {
 204             invokeLaterDispatcher = new InvokeLaterDispatcher(this);
 205             invokeLaterDispatcher.start();
 206         } else {
 207             invokeLaterDispatcher = null;
 208         }
 209     }
 210 
 211     @Native private static final int QUERY_ERROR = -2;
 212     @Native private static final int QUERY_NO_DISPLAY = -1;
 213     @Native private static final int QUERY_USE_CURRENT = 1;
 214     @Native private static final int QUERY_LOAD_GTK2 = 2;
 215     @Native private static final int QUERY_LOAD_GTK3 = 3;
 216     /*
 217      * check the system and return an indication of which library to load
 218      *  return values are the QUERY_ constants
 219      */
 220     private static native int _queryLibrary(int version, boolean verbose);
 221 
 222     private static native int _initGTK(int version, boolean verbose, float overrideUIScale);
 223 
 224     private void initDisplay() {
 225         Map ds = getDeviceDetails();
 226         if (ds != null) {
 227             Object value;
 228             value = ds.get("XDisplay");
 229             if (value != null) {
 230                 display = (Long)value;
 231             }
 232             value = ds.get("XVisualID");
 233             if (value != null) {
 234                 visualID = (Long)value;
 235             }
 236             value = ds.get("XScreenID");
 237             if (value != null) {
 238                 screen = (Integer)value;
 239             }
 240         }
 241     }
 242 
 243     private void init() {
 244         initDisplay();
 245         long eventProc = 0;
 246         Map map = getDeviceDetails();
 247         if (map != null) {
 248             Long result = (Long) map.get("javafx.embed.eventProc");
 249             eventProc = result == null ? 0 : result;
 250         }
 251 
 252         final boolean disableGrab = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("sun.awt.disablegrab") ||
 253                Boolean.getBoolean("glass.disableGrab"));
 254 
 255         _init(eventProc, disableGrab);
 256     }
 257 
 258     @Override
 259     protected void runLoop(final Runnable launchable) {
 260         // Embedded in SWT, with shared event thread
 261         final boolean isEventThread = AccessController
 262             .doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("javafx.embed.isEventThread"));
 263 
 264         if (isEventThread) {
 265             init();
 266             setEventThread(Thread.currentThread());
 267             launchable.run();
 268             return;
 269         }
 270 
 271         final boolean noErrorTrap = AccessController
 272             .doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("glass.noErrorTrap"));
 273 
 274         final Thread toolkitThread =
 275             AccessController.doPrivileged((PrivilegedAction<Thread>) () -> new Thread(() -> {
 276                 init();
 277                 _runLoop(launchable, noErrorTrap);
 278             }, "GtkNativeMainLoopThread"));
 279         setEventThread(toolkitThread);
 280         toolkitThread.start();
 281     }
 282 
 283     @Override
 284     protected void finishTerminating() {
 285         final Thread toolkitThread = getEventThread();
 286         if (toolkitThread != null) {
 287             _terminateLoop();
 288             setEventThread(null);
 289         }
 290         super.finishTerminating();
 291     }
 292 
 293     @Override public boolean shouldUpdateWindow() {
 294         return true;
 295     }
 296 
 297     private native void _terminateLoop();
 298 
 299     private native void _init(long eventProc, boolean disableGrab);
 300 
 301     private native void _runLoop(Runnable launchable, boolean noErrorTrap);
 302 
 303     @Override
 304     protected void _invokeAndWait(final Runnable runnable) {
 305         if (invokeLaterDispatcher != null) {
 306             invokeLaterDispatcher.invokeAndWait(runnable);
 307         } else {
 308             final CountDownLatch latch = new CountDownLatch(1);
 309             submitForLaterInvocation(() -> {
 310                 if (runnable != null) runnable.run();
 311                 latch.countDown();
 312             });
 313             try {
 314                 latch.await();
 315             } catch (InterruptedException e) {
 316                 //FAIL SILENTLY
 317             }
 318         }
 319     }
 320 
 321     private native void _submitForLaterInvocation(Runnable r);
 322     // InvokeLaterDispatcher.InvokeLaterSubmitter
 323     @Override public void submitForLaterInvocation(Runnable r) {
 324         _submitForLaterInvocation(r);
 325     }
 326 
 327     @Override protected void _invokeLater(Runnable runnable) {
 328         if (invokeLaterDispatcher != null) {
 329             invokeLaterDispatcher.invokeLater(runnable);
 330         } else {
 331             submitForLaterInvocation(runnable);
 332         }
 333     }
 334 
 335     private Object eventLoopExitEnterPassValue;
 336 
 337     private native void enterNestedEventLoopImpl();
 338 
 339     private native void leaveNestedEventLoopImpl();
 340 
 341     @Override
 342     protected Object _enterNestedEventLoop() {
 343         if (invokeLaterDispatcher != null) {
 344             invokeLaterDispatcher.notifyEnteringNestedEventLoop();
 345         }
 346         try {
 347             enterNestedEventLoopImpl();
 348             final Object retValue = eventLoopExitEnterPassValue;
 349             eventLoopExitEnterPassValue = null;
 350             return retValue;
 351         } finally {
 352             if (invokeLaterDispatcher != null) {
 353                 invokeLaterDispatcher.notifyLeftNestedEventLoop();
 354             }
 355         }
 356     }
 357 
 358     @Override
 359     protected void _leaveNestedEventLoop(Object retValue) {
 360         if (invokeLaterDispatcher != null) {
 361             invokeLaterDispatcher.notifyLeavingNestedEventLoop();
 362         }
 363         eventLoopExitEnterPassValue = retValue;
 364         leaveNestedEventLoopImpl();
 365     }
 366 
 367     @Override
 368     public Window createWindow(Window owner, Screen screen, int styleMask) {
 369         return new GtkWindow(owner, screen, styleMask);
 370     }
 371 
 372     @Override
 373     public Window createWindow(long parent) {
 374         return new GtkChildWindow(parent);
 375     }
 376 
 377     @Override
 378     public View createView() {
 379         return new GtkView();
 380     }
 381 
 382     @Override
 383     public Cursor createCursor(int type) {
 384         return new GtkCursor(type);
 385     }
 386 
 387     @Override
 388     public Cursor createCursor(int x, int y, Pixels pixels) {
 389         return new GtkCursor(x, y, pixels);
 390     }
 391 
 392     @Override
 393     protected void staticCursor_setVisible(boolean visible) {
 394     }
 395 
 396     @Override
 397     protected Size staticCursor_getBestSize(int width, int height) {
 398         return GtkCursor._getBestSize(width, height);
 399     }
 400 
 401     @Override
 402     public Pixels createPixels(int width, int height, ByteBuffer data) {
 403         return new GtkPixels(width, height, data);
 404     }
 405 
 406     @Override
 407     public Pixels createPixels(int width, int height, IntBuffer data) {
 408         return new GtkPixels(width, height, data);
 409     }
 410 
 411     @Override
 412     public Pixels createPixels(int width, int height, IntBuffer data, float scalex, float scaley) {
 413         return new GtkPixels(width, height, data, scalex, scaley);
 414     }
 415 
 416     @Override
 417     protected int staticPixels_getNativeFormat() {
 418         return Pixels.Format.BYTE_BGRA_PRE; // TODO
 419     }
 420 
 421     @Override
 422     public Robot createRobot() {
 423         return new GtkRobot();
 424     }
 425 
 426     @Override
 427     public Timer createTimer(Runnable runnable) {
 428         return new GtkTimer(runnable);
 429     }
 430 
 431     @Override
 432     protected native int staticTimer_getMinPeriod();
 433 
 434     @Override
 435     protected native int staticTimer_getMaxPeriod();
 436 
 437     @Override protected double staticScreen_getVideoRefreshPeriod() {
 438         return 0.0;     // indicate millisecond resolution
 439     }
 440 
 441     @Override native protected Screen[] staticScreen_getScreens();
 442 
 443     @Override
 444     protected FileChooserResult staticCommonDialogs_showFileChooser(
 445             Window owner, String folder, String filename, String title,
 446             int type, boolean multipleMode, ExtensionFilter[] extensionFilters, int defaultFilterIndex) {
 447 
 448         return GtkCommonDialogs.showFileChooser(owner, folder, filename, title,
 449                 type, multipleMode, extensionFilters, defaultFilterIndex);
 450     }
 451 
 452     @Override
 453     protected File staticCommonDialogs_showFolderChooser(Window owner, String folder, String title) {
 454         return GtkCommonDialogs.showFolderChooser(owner, folder, title);
 455     }
 456 
 457     @Override
 458     protected native long staticView_getMultiClickTime();
 459 
 460     @Override
 461     protected native int staticView_getMultiClickMaxX();
 462 
 463     @Override
 464     protected native int staticView_getMultiClickMaxY();
 465 
 466     @Override
 467     protected boolean _supportsInputMethods() {
 468         return true;
 469     }
 470 
 471     @Override
 472     protected native boolean _supportsTransparentWindows();
 473 
 474     @Override protected boolean _supportsUnifiedWindows() {
 475         return false;
 476     }
 477 
 478     @Override
 479     protected native int _getKeyCodeForChar(char c);
 480 
 481 }