1 /*
   2  * Copyright (c) 2010, 2015, 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.monocle;
  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.Pixels;
  32 import com.sun.glass.ui.Robot;
  33 import com.sun.glass.ui.Screen;
  34 import com.sun.glass.ui.Size;
  35 import com.sun.glass.ui.Timer;
  36 import com.sun.glass.ui.View;
  37 import com.sun.glass.ui.Window;
  38 import javafx.collections.SetChangeListener;
  39 
  40 import java.io.File;
  41 import java.lang.reflect.Constructor;
  42 import java.nio.ByteBuffer;
  43 import java.nio.IntBuffer;
  44 import java.security.AccessController;
  45 import java.security.PrivilegedAction;
  46 
  47 public final class MonocleApplication extends Application {
  48 
  49     private final NativePlatform platform =
  50             NativePlatformFactory.getNativePlatform();
  51     private final RunnableProcessor runnableProcessor = platform.getRunnableProcessor();
  52 
  53     /** Bit to indicate that a device has touch support */
  54     private static final int DEVICE_TOUCH = 0;
  55     /** Bit to indicate that a device has multitouch support */
  56     private static final int DEVICE_MULTITOUCH = 1;
  57     /** Bit to indicate that a device has relative motion pointer support */
  58     private static final int DEVICE_POINTER = 2;
  59     /** Bit to indicate that a device has arrow keys and a select key */
  60     private static final int DEVICE_5WAY = 3;
  61     /** Bit to indicate that a device has a full PC keyboard */
  62     private static final int DEVICE_PC_KEYBOARD = 4;
  63     /** Largest bit used in device capability bitmasks */
  64     private static final int DEVICE_MAX = 4;
  65     /** A running count of the numbers of devices with each device capability */
  66     private int[] deviceFlags = new int[DEVICE_MAX + 1];
  67     private Thread shutdownHookThread;
  68     private Runnable renderEndNotifier = () -> platform.getScreen().swapBuffers();
  69 
  70     MonocleApplication() {
  71         for (InputDevice device : platform.getInputDeviceRegistry().getInputDevices()) {
  72             updateDeviceFlags(device, true);
  73         }
  74         platform.getInputDeviceRegistry().getInputDevices().addListener(
  75                 (SetChangeListener<InputDevice>) change -> {
  76                     if (change.wasAdded()) {
  77                         InputDevice device = change.getElementAdded();
  78                         updateDeviceFlags(device, true);
  79                     } else if (change.wasRemoved()) {
  80                         InputDevice device = change.getElementRemoved();
  81                         updateDeviceFlags(device, false);
  82                     }
  83                 }
  84         );
  85     }
  86 
  87     private void updateDeviceFlags(InputDevice device, boolean added) {
  88         int modifier = added ? 1 : -1;
  89         if (device.isTouch()) {
  90             deviceFlags[DEVICE_TOUCH] += modifier;
  91         }
  92         if (device.isMultiTouch()) {
  93             deviceFlags[DEVICE_MULTITOUCH] += modifier;
  94         }
  95         if (device.isRelative()) {
  96             deviceFlags[DEVICE_POINTER] += modifier;
  97             if (deviceFlags[DEVICE_POINTER] >= 1  && added) {
  98                 staticCursor_setVisible(true);
  99             } else if (deviceFlags[DEVICE_POINTER] < 1 && !added) {
 100                 staticCursor_setVisible(false);
 101             }
 102         }
 103         if (device.isFullKeyboard()) {
 104             deviceFlags[DEVICE_PC_KEYBOARD] += modifier;
 105         }
 106         if (device.is5Way()) {
 107             deviceFlags[DEVICE_5WAY] += modifier;
 108         }
 109     }
 110 
 111     @Override
 112     protected void runLoop(Runnable launchable) {
 113         runnableProcessor.invokeLater(launchable);
 114         long stackSize = AccessController.doPrivileged(
 115                 (PrivilegedAction<Long>)
 116                         () -> Long.getLong("monocle.stackSize", 0));
 117         Thread t = new Thread(
 118                 new ThreadGroup("Event"),
 119                 runnableProcessor,
 120                 "Event Thread",
 121                 stackSize);
 122         setEventThread(t);
 123         t.start();
 124         shutdownHookThread = new Thread("Monocle shutdown hook") {
 125             @Override public void run() {
 126             platform.shutdown();
 127             }
 128         };
 129         Runtime.getRuntime().addShutdownHook(shutdownHookThread);
 130     }
 131 
 132     @Override
 133     protected void _invokeAndWait(Runnable runnable) {
 134         runnableProcessor.invokeAndWait(runnable);
 135     }
 136 
 137     @Override
 138     protected void _invokeLater(Runnable runnable) {
 139         runnableProcessor.invokeLater(runnable);
 140     }
 141 
 142     @Override
 143     protected Object _enterNestedEventLoop() {
 144         return runnableProcessor.enterNestedEventLoop();
 145     }
 146 
 147     @Override
 148     protected void _leaveNestedEventLoop(Object retValue) {
 149         runnableProcessor.leaveNestedEventLoop(retValue);
 150     }
 151 
 152     @Override
 153     public Window createWindow(Window owner, Screen screen, int styleMask) {
 154         return new MonocleWindow(owner, screen, styleMask);
 155     }
 156 
 157     @Override
 158     public Window createWindow(long parent) {
 159         return new MonocleWindow(parent);
 160     }
 161 
 162     @Override
 163     public View createView() {
 164         return new MonocleView();
 165     }
 166 
 167     @Override
 168     public Cursor createCursor(int type) {
 169         return new MonocleCursor(type);
 170     }
 171 
 172     @Override
 173     public Cursor createCursor(int x, int y, Pixels pixels) {
 174         return new MonocleCursor(x, y, pixels);
 175     }
 176 
 177     @Override
 178     protected void staticCursor_setVisible(boolean visible) {
 179         NativeCursor cursor = NativePlatformFactory.getNativePlatform().getCursor();
 180         cursor.setVisibility(deviceFlags[DEVICE_POINTER] >= 1 ? visible : false);
 181     }
 182 
 183     @Override
 184     protected Size staticCursor_getBestSize(int width, int height) {
 185         NativeCursor cursor = NativePlatformFactory.getNativePlatform().getCursor();
 186         return cursor.getBestSize();
 187     }
 188 
 189     @Override
 190     public Pixels createPixels(int width, int height, ByteBuffer data) {
 191         return new MonoclePixels(width, height, data);
 192     }
 193 
 194     @Override
 195     public Pixels createPixels(int width, int height, IntBuffer data) {
 196         return new MonoclePixels(width, height, data);
 197     }
 198 
 199     @Override
 200     public Pixels createPixels(int width, int height, IntBuffer data,
 201                                float scale) {
 202         return new MonoclePixels(width, height, data, scale);
 203     }
 204 
 205     @Override
 206     protected int staticPixels_getNativeFormat() {
 207         return platform.getScreen().getNativeFormat();
 208     }
 209 
 210     @Override
 211     public Robot createRobot() {
 212         return new MonocleRobot();
 213     }
 214 
 215     @Override
 216     protected double staticScreen_getVideoRefreshPeriod() {
 217         return 0.0;
 218     }
 219 
 220     @Override
 221     protected Screen[] staticScreen_getScreens() {
 222         Screen screen = null;
 223         try {
 224             NativeScreen ns = platform.getScreen();
 225             Constructor c = AccessController.doPrivileged(
 226                     new PrivilegedAction<Constructor>() {
 227                         @Override
 228                         public Constructor run() {
 229                             try {
 230                                 Constructor c = Screen.class.getDeclaredConstructor(
 231                                         Long.TYPE,
 232                                         Integer.TYPE,
 233                                         Integer.TYPE, Integer.TYPE,
 234                                         Integer.TYPE, Integer.TYPE,
 235                                         Integer.TYPE, Integer.TYPE,
 236                                         Integer.TYPE, Integer.TYPE,
 237                                         Integer.TYPE, Integer.TYPE, Float.TYPE);
 238                                 c.setAccessible(true);
 239                                 return c;
 240                             } catch (Exception e) {
 241                                 e.printStackTrace();
 242                                 return null;
 243                             }
 244                         }
 245                     });
 246             if (c != null) {
 247                 screen = (Screen) c.newInstance(
 248                         1l, // dummy native pointer;
 249                         ns.getDepth(),
 250                         0, 0, ns.getWidth(), ns.getHeight(),
 251                         0, 0, ns.getWidth(), ns.getHeight(),
 252                         ns.getDPI(), ns.getDPI(),
 253                         ns.getScale());
 254                 // Move the cursor to the middle of the screen
 255                 MouseState mouseState = new MouseState();
 256                 mouseState.setX(ns.getWidth() / 2);
 257                 mouseState.setY(ns.getHeight() / 2);
 258                 MouseInput.getInstance().setState(mouseState, false);
 259             }
 260         } catch (Exception e) {
 261             e.printStackTrace();
 262         } catch (UnsatisfiedLinkError e) {
 263             e.printStackTrace();
 264         }
 265         return new Screen[] { screen };
 266     }
 267 
 268     @Override
 269     public Timer createTimer(Runnable runnable) {
 270         return new MonocleTimer(runnable);
 271     }
 272 
 273     @Override
 274     protected int staticTimer_getMinPeriod() {
 275         return MonocleTimer.getMinPeriod_impl();
 276     }
 277 
 278     @Override
 279     protected int staticTimer_getMaxPeriod() {
 280         return MonocleTimer.getMaxPeriod_impl();
 281     }
 282 
 283     public boolean hasWindowManager() {
 284         return false;
 285     }
 286 
 287     @Override
 288     protected FileChooserResult staticCommonDialogs_showFileChooser(
 289             Window owner, String folder, String filename, String title,
 290             int type, boolean multipleMode,
 291             ExtensionFilter[] extensionFilters,
 292             int defaultFilterIndex) {
 293         throw new UnsupportedOperationException();
 294     }
 295 
 296     @Override
 297     protected File staticCommonDialogs_showFolderChooser(Window owner,
 298                                                          String folder,
 299                                                          String title) {
 300         Thread.dumpStack();
 301         throw new UnsupportedOperationException();
 302     }
 303 
 304     @Override
 305     protected long staticView_getMultiClickTime() {
 306         return MonocleView._getMultiClickTime();
 307     }
 308 
 309     @Override
 310     protected int staticView_getMultiClickMaxX() {
 311         return MonocleView._getMultiClickMaxX();
 312     }
 313 
 314     @Override
 315     protected int staticView_getMultiClickMaxY() {
 316         return MonocleView._getMultiClickMaxY();
 317     }
 318 
 319     @Override
 320     protected boolean _supportsTransparentWindows() {
 321         return true;
 322     }
 323 
 324     @Override
 325     protected boolean _supportsUnifiedWindows() {
 326         return false;
 327     }
 328 
 329     @Override
 330     public boolean hasTwoLevelFocus() {
 331         return deviceFlags[DEVICE_PC_KEYBOARD] == 0 && deviceFlags[DEVICE_5WAY] > 0;
 332     }
 333 
 334     @Override
 335     public boolean hasVirtualKeyboard() {
 336         return deviceFlags[DEVICE_PC_KEYBOARD] == 0 && deviceFlags[DEVICE_TOUCH] > 0;
 337     }
 338 
 339     @Override
 340     public boolean hasTouch() {
 341         return deviceFlags[DEVICE_TOUCH] > 0;
 342     }
 343 
 344     @Override
 345     public boolean hasMultiTouch() {
 346         return deviceFlags[DEVICE_MULTITOUCH] > 0;
 347     }
 348 
 349     @Override
 350     public boolean hasPointer() {
 351         return deviceFlags[DEVICE_POINTER] > 0;
 352     }
 353 
 354     @Override
 355     public void notifyRenderingFinished() {
 356         invokeLater(renderEndNotifier);
 357     }
 358 
 359     @Override
 360     protected void finishTerminating() {
 361         //if this method is getting called, we don't need the shutdown hook
 362         Runtime.getRuntime().removeShutdownHook(shutdownHookThread);
 363         setEventThread(null);
 364         platform.shutdown();
 365         super.finishTerminating();
 366     }
 367 
 368     void enterDnDEventLoop() {
 369         _enterNestedEventLoop();
 370     }
 371 
 372     void leaveDndEventLoop() {
 373         _leaveNestedEventLoop(null);
 374     }
 375 
 376     @Override
 377     protected int _getKeyCodeForChar(char c) {
 378         return KeyInput.getInstance().getKeyCodeForChar(c);
 379     }
 380 
 381 }