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