1 /*
   2  * Copyright (c) 2011, 2019, 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 
  26 package sun.lwawt.macosx;
  27 
  28 import java.awt.AWTError;
  29 import java.awt.CheckboxMenuItem;
  30 import java.awt.Color;
  31 import java.awt.Component;
  32 import java.awt.Cursor;
  33 import java.awt.Desktop;
  34 import java.awt.Dialog;
  35 import java.awt.Dimension;
  36 import java.awt.Event;
  37 import java.awt.EventQueue;
  38 import java.awt.FileDialog;
  39 import java.awt.Frame;
  40 import java.awt.GraphicsConfiguration;
  41 import java.awt.GraphicsDevice;
  42 import java.awt.GraphicsEnvironment;
  43 import java.awt.HeadlessException;
  44 import java.awt.Image;
  45 import java.awt.Insets;
  46 import java.awt.Menu;
  47 import java.awt.MenuBar;
  48 import java.awt.MenuItem;
  49 import java.awt.Point;
  50 import java.awt.PopupMenu;
  51 import java.awt.RenderingHints;
  52 import java.awt.Robot;
  53 import java.awt.SystemTray;
  54 import java.awt.Taskbar;
  55 import java.awt.Toolkit;
  56 import java.awt.TrayIcon;
  57 import java.awt.Window;
  58 import java.awt.datatransfer.Clipboard;
  59 import java.awt.dnd.DragGestureEvent;
  60 import java.awt.dnd.DragGestureListener;
  61 import java.awt.dnd.DragGestureRecognizer;
  62 import java.awt.dnd.DragSource;
  63 import java.awt.dnd.DropTarget;
  64 import java.awt.dnd.InvalidDnDOperationException;
  65 import java.awt.dnd.MouseDragGestureRecognizer;
  66 import java.awt.dnd.peer.DragSourceContextPeer;
  67 import java.awt.event.InputEvent;
  68 import java.awt.event.InvocationEvent;
  69 import java.awt.event.KeyEvent;
  70 import java.awt.font.TextAttribute;
  71 import java.awt.im.InputMethodHighlight;
  72 import java.awt.im.spi.InputMethodDescriptor;
  73 import java.awt.peer.CheckboxMenuItemPeer;
  74 import java.awt.peer.DesktopPeer;
  75 import java.awt.peer.DialogPeer;
  76 import java.awt.peer.FileDialogPeer;
  77 import java.awt.peer.FontPeer;
  78 import java.awt.peer.MenuBarPeer;
  79 import java.awt.peer.MenuItemPeer;
  80 import java.awt.peer.MenuPeer;
  81 import java.awt.peer.PopupMenuPeer;
  82 import java.awt.peer.RobotPeer;
  83 import java.awt.peer.SystemTrayPeer;
  84 import java.awt.peer.TaskbarPeer;
  85 import java.awt.peer.TrayIconPeer;
  86 import java.lang.reflect.InvocationTargetException;
  87 import java.lang.reflect.UndeclaredThrowableException;
  88 import java.net.MalformedURLException;
  89 import java.net.URL;
  90 import java.security.AccessController;
  91 import java.security.PrivilegedAction;
  92 import java.util.HashMap;
  93 import java.util.Locale;
  94 import java.util.Map;
  95 import java.util.MissingResourceException;
  96 import java.util.Objects;
  97 import java.util.ResourceBundle;
  98 import java.util.concurrent.Callable;
  99 
 100 import javax.swing.UIManager;
 101 
 102 import com.apple.laf.AquaMenuBarUI;
 103 import sun.awt.AWTAccessor;
 104 import sun.awt.AppContext;
 105 import sun.awt.CGraphicsConfig;
 106 import sun.awt.CGraphicsDevice;
 107 import sun.awt.LightweightFrame;
 108 import sun.awt.PlatformGraphicsInfo;
 109 import sun.awt.SunToolkit;
 110 import sun.awt.datatransfer.DataTransferer;
 111 import sun.awt.util.ThreadGroupUtils;
 112 import sun.java2d.opengl.OGLRenderQueue;
 113 import sun.lwawt.LWComponentPeer;
 114 import sun.lwawt.LWCursorManager;
 115 import sun.lwawt.LWToolkit;
 116 import sun.lwawt.LWWindowPeer;
 117 import sun.lwawt.LWWindowPeer.PeerType;
 118 import sun.lwawt.PlatformComponent;
 119 import sun.lwawt.PlatformDropTarget;
 120 import sun.lwawt.PlatformWindow;
 121 import sun.lwawt.SecurityWarningWindow;
 122 
 123 @SuppressWarnings("serial") // JDK implementation class
 124 final class NamedCursor extends Cursor {
 125     NamedCursor(String name) {
 126         super(name);
 127     }
 128 }
 129 
 130 /**
 131  * Mac OS X Cocoa-based AWT Toolkit.
 132  */
 133 public final class LWCToolkit extends LWToolkit {
 134     // While it is possible to enumerate all mouse devices
 135     // and query them for the number of buttons, the code
 136     // that does it is rather complex. Instead, we opt for
 137     // the easy way and just support up to 5 mouse buttons,
 138     // like Windows.
 139     private static final int BUTTONS = 5;
 140 
 141     private static native void initIDs();
 142     private static native void initAppkit(ThreadGroup appKitThreadGroup, boolean headless);
 143     private static CInputMethodDescriptor sInputMethodDescriptor;
 144 
 145     static {
 146         System.err.flush();
 147 
 148         ResourceBundle platformResources = java.security.AccessController.doPrivileged(
 149                 new java.security.PrivilegedAction<ResourceBundle>() {
 150             @Override
 151             public ResourceBundle run() {
 152                 ResourceBundle platformResources = null;
 153                 try {
 154                     platformResources = ResourceBundle.getBundle("sun.awt.resources.awtosx");
 155                 } catch (MissingResourceException e) {
 156                     // No resource file; defaults will be used.
 157                 }
 158 
 159                 System.loadLibrary("awt");
 160                 System.loadLibrary("fontmanager");
 161 
 162                 return platformResources;
 163             }
 164         });
 165 
 166         if (!GraphicsEnvironment.isHeadless() &&
 167             !PlatformGraphicsInfo.isInAquaSession())
 168         {
 169             throw new AWTError("WindowServer is not available");
 170         }
 171 
 172         AWTAccessor.getToolkitAccessor().setPlatformResources(platformResources);
 173 
 174         if (!GraphicsEnvironment.isHeadless()) {
 175             initIDs();
 176         }
 177         inAWT = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 178             @Override
 179             public Boolean run() {
 180                 return !Boolean.parseBoolean(System.getProperty("javafx.embed.singleThread", "false"));
 181             }
 182         });
 183     }
 184 
 185     /*
 186      * If true  we operate in normal mode and nested runloop is executed in JavaRunLoopMode
 187      * If false we operate in singleThreaded FX/AWT interop mode and nested loop uses NSDefaultRunLoopMode
 188      */
 189     private static final boolean inAWT;
 190 
 191     public LWCToolkit() {
 192         final String extraButtons = "sun.awt.enableExtraMouseButtons";
 193         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 194             areExtraMouseButtonsEnabled =
 195                  Boolean.parseBoolean(System.getProperty(extraButtons, "true"));
 196             //set system property if not yet assigned
 197             System.setProperty(extraButtons, ""+areExtraMouseButtonsEnabled);
 198             initAppkit(ThreadGroupUtils.getRootThreadGroup(),
 199                        GraphicsEnvironment.isHeadless());
 200             return null;
 201         });
 202     }
 203 
 204     /*
 205      * System colors with default initial values, overwritten by toolkit if system values differ and are available.
 206      */
 207     private static final int NUM_APPLE_COLORS = 3;
 208     public static final int KEYBOARD_FOCUS_COLOR = 0;
 209     public static final int INACTIVE_SELECTION_BACKGROUND_COLOR = 1;
 210     public static final int INACTIVE_SELECTION_FOREGROUND_COLOR = 2;
 211     private static int[] appleColors = {
 212         0xFF808080, // keyboardFocusColor = Color.gray;
 213         0xFFC0C0C0, // secondarySelectedControlColor
 214         0xFF303030, // controlDarkShadowColor
 215     };
 216 
 217     private native void loadNativeColors(final int[] systemColors, final int[] appleColors);
 218 
 219     @Override
 220     protected void loadSystemColors(final int[] systemColors) {
 221         if (systemColors == null) return;
 222         loadNativeColors(systemColors, appleColors);
 223     }
 224 
 225     @SuppressWarnings("serial") // JDK implementation class
 226     private static class AppleSpecificColor extends Color {
 227         private final int index;
 228         AppleSpecificColor(int index) {
 229             super(appleColors[index]);
 230             this.index = index;
 231         }
 232 
 233         @Override
 234         public int getRGB() {
 235             return appleColors[index];
 236         }
 237     }
 238 
 239     /**
 240      * Returns Apple specific colors that we may expose going forward.
 241      */
 242     public static Color getAppleColor(int color) {
 243         return new AppleSpecificColor(color);
 244     }
 245 
 246     // This is only called from native code.
 247     static void systemColorsChanged() {
 248         EventQueue.invokeLater(() -> {
 249             AccessController.doPrivileged( (PrivilegedAction<Object>) () -> {
 250                 AWTAccessor.getSystemColorAccessor().updateSystemColors();
 251                 return null;
 252             });
 253         });
 254     }
 255 
 256     public static LWCToolkit getLWCToolkit() {
 257         return (LWCToolkit)Toolkit.getDefaultToolkit();
 258     }
 259 
 260     @Override
 261     protected PlatformWindow createPlatformWindow(PeerType peerType) {
 262         if (peerType == PeerType.EMBEDDED_FRAME) {
 263             return new CPlatformEmbeddedFrame();
 264         } else if (peerType == PeerType.VIEW_EMBEDDED_FRAME) {
 265             return new CViewPlatformEmbeddedFrame();
 266         } else if (peerType == PeerType.LW_FRAME) {
 267             return new CPlatformLWWindow();
 268         } else {
 269             assert (peerType == PeerType.SIMPLEWINDOW
 270                     || peerType == PeerType.DIALOG
 271                     || peerType == PeerType.FRAME);
 272             return new CPlatformWindow();
 273         }
 274     }
 275 
 276     LWWindowPeer createEmbeddedFrame(CEmbeddedFrame target) {
 277         PlatformComponent platformComponent = createPlatformComponent();
 278         PlatformWindow platformWindow = createPlatformWindow(PeerType.EMBEDDED_FRAME);
 279         return createDelegatedPeer(target, platformComponent, platformWindow, PeerType.EMBEDDED_FRAME);
 280     }
 281 
 282     LWWindowPeer createEmbeddedFrame(CViewEmbeddedFrame target) {
 283         PlatformComponent platformComponent = createPlatformComponent();
 284         PlatformWindow platformWindow = createPlatformWindow(PeerType.VIEW_EMBEDDED_FRAME);
 285         return createDelegatedPeer(target, platformComponent, platformWindow, PeerType.VIEW_EMBEDDED_FRAME);
 286     }
 287 
 288     private CPrinterDialogPeer createCPrinterDialog(CPrinterDialog target) {
 289         PlatformComponent platformComponent = createPlatformComponent();
 290         PlatformWindow platformWindow = createPlatformWindow(PeerType.DIALOG);
 291         CPrinterDialogPeer peer = new CPrinterDialogPeer(target, platformComponent, platformWindow);
 292         targetCreatedPeer(target, peer);
 293         return peer;
 294     }
 295 
 296     @Override
 297     public DialogPeer createDialog(Dialog target) {
 298         if (target instanceof CPrinterDialog) {
 299             return createCPrinterDialog((CPrinterDialog)target);
 300         }
 301         return super.createDialog(target);
 302     }
 303 
 304     @Override
 305     protected SecurityWarningWindow createSecurityWarning(Window ownerWindow,
 306                                                           LWWindowPeer ownerPeer) {
 307         return new CWarningWindow(ownerWindow, ownerPeer);
 308     }
 309 
 310     @Override
 311     protected PlatformComponent createPlatformComponent() {
 312         return new CPlatformComponent();
 313     }
 314 
 315     @Override
 316     protected PlatformComponent createLwPlatformComponent() {
 317         return new CPlatformLWComponent();
 318     }
 319 
 320     @Override
 321     protected FileDialogPeer createFileDialogPeer(FileDialog target) {
 322         return new CFileDialog(target);
 323     }
 324 
 325     @Override
 326     public MenuPeer createMenu(Menu target) {
 327         MenuPeer peer = new CMenu(target);
 328         targetCreatedPeer(target, peer);
 329         return peer;
 330     }
 331 
 332     @Override
 333     public MenuBarPeer createMenuBar(MenuBar target) {
 334         MenuBarPeer peer = new CMenuBar(target);
 335         targetCreatedPeer(target, peer);
 336         return peer;
 337     }
 338 
 339     @Override
 340     public MenuItemPeer createMenuItem(MenuItem target) {
 341         MenuItemPeer peer = new CMenuItem(target);
 342         targetCreatedPeer(target, peer);
 343         return peer;
 344     }
 345 
 346     @Override
 347     public CheckboxMenuItemPeer createCheckboxMenuItem(CheckboxMenuItem target) {
 348         CheckboxMenuItemPeer peer = new CCheckboxMenuItem(target);
 349         targetCreatedPeer(target, peer);
 350         return peer;
 351     }
 352 
 353     @Override
 354     public PopupMenuPeer createPopupMenu(PopupMenu target) {
 355         PopupMenuPeer peer = new CPopupMenu(target);
 356         targetCreatedPeer(target, peer);
 357         return peer;
 358     }
 359 
 360     @Override
 361     public SystemTrayPeer createSystemTray(SystemTray target) {
 362         return new CSystemTray();
 363     }
 364 
 365     @Override
 366     public TrayIconPeer createTrayIcon(TrayIcon target) {
 367         TrayIconPeer peer = new CTrayIcon(target);
 368         targetCreatedPeer(target, peer);
 369         return peer;
 370     }
 371 
 372     @Override
 373     public DesktopPeer createDesktopPeer(Desktop target) {
 374         return new CDesktopPeer();
 375     }
 376 
 377     @Override
 378     public TaskbarPeer createTaskbarPeer(Taskbar target) {
 379         return new CTaskbarPeer();
 380     }
 381 
 382     @Override
 383     public LWCursorManager getCursorManager() {
 384         return CCursorManager.getInstance();
 385     }
 386 
 387     @Override
 388     public Cursor createCustomCursor(final Image cursor, final Point hotSpot,
 389                                      final String name)
 390             throws IndexOutOfBoundsException, HeadlessException {
 391         return new CCustomCursor(cursor, hotSpot, name);
 392     }
 393 
 394     @Override
 395     public Dimension getBestCursorSize(final int preferredWidth,
 396                                        final int preferredHeight)
 397             throws HeadlessException {
 398         return CCustomCursor.getBestCursorSize(preferredWidth, preferredHeight);
 399     }
 400 
 401     @Override
 402     protected void platformCleanup() {
 403         // TODO Auto-generated method stub
 404     }
 405 
 406     @Override
 407     protected void platformInit() {
 408         // TODO Auto-generated method stub
 409     }
 410 
 411     @Override
 412     protected void platformRunMessage() {
 413         // TODO Auto-generated method stub
 414     }
 415 
 416     @Override
 417     protected void platformShutdown() {
 418         // TODO Auto-generated method stub
 419     }
 420 
 421     class OSXPlatformFont extends sun.awt.PlatformFont
 422     {
 423         OSXPlatformFont(String name, int style)
 424         {
 425             super(name, style);
 426         }
 427         @Override
 428         protected char getMissingGlyphCharacter()
 429         {
 430             // Follow up for real implementation
 431             return (char)0xfff8; // see http://developer.apple.com/fonts/LastResortFont/
 432         }
 433     }
 434     @Override
 435     public FontPeer getFontPeer(String name, int style) {
 436         return new OSXPlatformFont(name, style);
 437     }
 438 
 439     @Override
 440     protected void initializeDesktopProperties() {
 441         super.initializeDesktopProperties();
 442         Map <Object, Object> fontHints = new HashMap<>();
 443         fontHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
 444         desktopProperties.put(SunToolkit.DESKTOPFONTHINTS, fontHints);
 445         desktopProperties.put("awt.mouse.numButtons", BUTTONS);
 446         desktopProperties.put("awt.multiClickInterval", getMultiClickTime());
 447 
 448         // These DnD properties must be set, otherwise Swing ends up spewing NPEs
 449         // all over the place. The values came straight off of MToolkit.
 450         desktopProperties.put("DnD.Autoscroll.initialDelay", Integer.valueOf(50));
 451         desktopProperties.put("DnD.Autoscroll.interval", Integer.valueOf(50));
 452         desktopProperties.put("DnD.Autoscroll.cursorHysteresis", Integer.valueOf(5));
 453 
 454         desktopProperties.put("DnD.isDragImageSupported", Boolean.TRUE);
 455 
 456         // Register DnD cursors
 457         desktopProperties.put("DnD.Cursor.CopyDrop", new NamedCursor("DnD.Cursor.CopyDrop"));
 458         desktopProperties.put("DnD.Cursor.MoveDrop", new NamedCursor("DnD.Cursor.MoveDrop"));
 459         desktopProperties.put("DnD.Cursor.LinkDrop", new NamedCursor("DnD.Cursor.LinkDrop"));
 460         desktopProperties.put("DnD.Cursor.CopyNoDrop", new NamedCursor("DnD.Cursor.CopyNoDrop"));
 461         desktopProperties.put("DnD.Cursor.MoveNoDrop", new NamedCursor("DnD.Cursor.MoveNoDrop"));
 462         desktopProperties.put("DnD.Cursor.LinkNoDrop", new NamedCursor("DnD.Cursor.LinkNoDrop"));
 463     }
 464 
 465     @Override
 466     protected boolean syncNativeQueue(long timeout) {
 467         return nativeSyncQueue(timeout);
 468     }
 469 
 470     @Override
 471     public native void beep();
 472 
 473     @Override
 474     public int getScreenResolution() throws HeadlessException {
 475         return (int) ((CGraphicsDevice) GraphicsEnvironment
 476                 .getLocalGraphicsEnvironment().getDefaultScreenDevice())
 477                 .getXResolution();
 478     }
 479 
 480     @Override
 481     public Insets getScreenInsets(final GraphicsConfiguration gc) {
 482         return ((CGraphicsConfig) gc).getDevice().getScreenInsets();
 483     }
 484 
 485     @Override
 486     public void sync() {
 487         // flush the OGL pipeline (this is a no-op if OGL is not enabled)
 488         OGLRenderQueue.sync();
 489         // setNeedsDisplay() selector was sent to the appropriate CALayer so now
 490         // we have to flush the native selectors queue.
 491         flushNativeSelectors();
 492     }
 493 
 494     @Override
 495     public RobotPeer createRobot(Robot target, GraphicsDevice screen) {
 496         return new CRobot(target, (CGraphicsDevice)screen);
 497     }
 498 
 499     private native boolean isCapsLockOn();
 500 
 501     /*
 502      * NOTE: Among the keys this method is supposed to check,
 503      * only Caps Lock works as a true locking key with OS X.
 504      * There is no Scroll Lock key on modern Apple keyboards,
 505      * and with a PC keyboard plugged in Scroll Lock is simply
 506      * ignored: no LED lights up if you press it.
 507      * The key located at the same position on Apple keyboards
 508      * as Num Lock on PC keyboards is called Clear, doesn't lock
 509      * anything and is used for entirely different purpose.
 510      */
 511     @Override
 512     public boolean getLockingKeyState(int keyCode) throws UnsupportedOperationException {
 513         switch (keyCode) {
 514             case KeyEvent.VK_NUM_LOCK:
 515             case KeyEvent.VK_SCROLL_LOCK:
 516             case KeyEvent.VK_KANA_LOCK:
 517                 throw new UnsupportedOperationException("Toolkit.getLockingKeyState");
 518 
 519             case KeyEvent.VK_CAPS_LOCK:
 520                 return isCapsLockOn();
 521 
 522             default:
 523                 throw new IllegalArgumentException("invalid key for Toolkit.getLockingKeyState");
 524         }
 525     }
 526 
 527     //Is it allowed to generate events assigned to extra mouse buttons.
 528     //Set to true by default.
 529     private static boolean areExtraMouseButtonsEnabled = true;
 530 
 531     @Override
 532     public boolean areExtraMouseButtonsEnabled() throws HeadlessException {
 533         return areExtraMouseButtonsEnabled;
 534     }
 535 
 536     @Override
 537     public int getNumberOfButtons(){
 538         return BUTTONS;
 539     }
 540 
 541     /**
 542      * Returns the double-click time interval in ms.
 543      */
 544     private static native int getMultiClickTime();
 545 
 546     @Override
 547     public boolean isTraySupported() {
 548         return true;
 549     }
 550 
 551     @Override
 552     public DataTransferer getDataTransferer() {
 553         return CDataTransferer.getInstanceImpl();
 554     }
 555 
 556     @Override
 557     public boolean isAlwaysOnTopSupported() {
 558         return true;
 559     }
 560 
 561     private static final String APPKIT_THREAD_NAME = "AppKit Thread";
 562 
 563     // Intended to be called from the LWCToolkit.m only.
 564     private static void installToolkitThreadInJava() {
 565         Thread.currentThread().setName(APPKIT_THREAD_NAME);
 566         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 567             Thread.currentThread().setContextClassLoader(null);
 568             return null;
 569         });
 570     }
 571 
 572     @Override
 573     public boolean isWindowOpacitySupported() {
 574         return true;
 575     }
 576 
 577     @Override
 578     public boolean isFrameStateSupported(int state) throws HeadlessException {
 579         switch (state) {
 580             case Frame.NORMAL:
 581             case Frame.ICONIFIED:
 582             case Frame.MAXIMIZED_BOTH:
 583                 return true;
 584             default:
 585                 return false;
 586         }
 587     }
 588 
 589     @Override
 590     @Deprecated(since = "10")
 591     public int getMenuShortcutKeyMask() {
 592         return Event.META_MASK;
 593     }
 594 
 595     @Override
 596     public int getMenuShortcutKeyMaskEx() {
 597         return InputEvent.META_DOWN_MASK;
 598     }
 599 
 600     @Override
 601     public Image getImage(final String filename) {
 602         final Image nsImage = checkForNSImage(filename);
 603         if (nsImage != null) {
 604             return nsImage;
 605         }
 606 
 607         if (imageCached(filename)) {
 608             return super.getImage(filename);
 609         }
 610 
 611         String filename2x = getScaledImageName(filename);
 612         return (imageExists(filename2x))
 613                 ? getImageWithResolutionVariant(filename, filename2x)
 614                 : super.getImage(filename);
 615     }
 616 
 617     @Override
 618     public Image getImage(URL url) {
 619 
 620         if (imageCached(url)) {
 621             return super.getImage(url);
 622         }
 623 
 624         URL url2x = getScaledImageURL(url);
 625         return (imageExists(url2x))
 626                 ? getImageWithResolutionVariant(url, url2x) : super.getImage(url);
 627     }
 628 
 629     private static final String nsImagePrefix = "NSImage://";
 630     private Image checkForNSImage(final String imageName) {
 631         if (imageName == null) return null;
 632         if (!imageName.startsWith(nsImagePrefix)) return null;
 633         return CImage.getCreator().createImageFromName(imageName.substring(nsImagePrefix.length()));
 634     }
 635 
 636     // Thread-safe Object.equals() called from native
 637     public static boolean doEquals(final Object a, final Object b, Component c) {
 638         if (a == b) return true;
 639 
 640         final boolean[] ret = new boolean[1];
 641 
 642         try {  invokeAndWait(new Runnable() { @Override
 643                                               public void run() { synchronized(ret) {
 644             ret[0] = a.equals(b);
 645         }}}, c); } catch (Exception e) { e.printStackTrace(); }
 646 
 647         synchronized(ret) { return ret[0]; }
 648     }
 649 
 650     public static <T> T invokeAndWait(final Callable<T> callable,
 651                                       Component component) throws Exception {
 652         final CallableWrapper<T> wrapper = new CallableWrapper<>(callable);
 653         invokeAndWait(wrapper, component);
 654         return wrapper.getResult();
 655     }
 656 
 657     static final class CallableWrapper<T> implements Runnable {
 658         final Callable<T> callable;
 659         T object;
 660         Exception e;
 661 
 662         CallableWrapper(final Callable<T> callable) {
 663             this.callable = callable;
 664         }
 665 
 666         @Override
 667         public void run() {
 668             try {
 669                 object = callable.call();
 670             } catch (final Exception e) {
 671                 this.e = e;
 672             }
 673         }
 674 
 675         public T getResult() throws Exception {
 676             if (e != null) throw e;
 677             return object;
 678         }
 679     }
 680 
 681     /**
 682      * Kicks an event over to the appropriate event queue and waits for it to
 683      * finish To avoid deadlocking, we manually run the NSRunLoop while waiting
 684      * Any selector invoked using ThreadUtilities performOnMainThread will be
 685      * processed in doAWTRunLoop The InvocationEvent will call
 686      * LWCToolkit.stopAWTRunLoop() when finished, which will stop our manual
 687      * run loop. Does not dispatch native events while in the loop
 688      */
 689     public static void invokeAndWait(Runnable runnable, Component component)
 690             throws InvocationTargetException {
 691         Objects.requireNonNull(component, "Null component provided to invokeAndWait");
 692 
 693         long mediator = createAWTRunLoopMediator();
 694         InvocationEvent invocationEvent =
 695                 new InvocationEvent(component,
 696                         runnable,
 697                         () -> {
 698                             if (mediator != 0) {
 699                                 stopAWTRunLoop(mediator);
 700                             }
 701                         },
 702                         true);
 703 
 704         AppContext appContext = SunToolkit.targetToAppContext(component);
 705         SunToolkit.postEvent(appContext, invocationEvent);
 706         // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
 707         SunToolkit.flushPendingEvents(appContext);
 708         doAWTRunLoop(mediator, false);
 709 
 710         checkException(invocationEvent);
 711     }
 712 
 713     public static void invokeLater(Runnable event, Component component)
 714             throws InvocationTargetException {
 715         Objects.requireNonNull(component, "Null component provided to invokeLater");
 716 
 717         InvocationEvent invocationEvent = new InvocationEvent(component, event);
 718 
 719         AppContext appContext = SunToolkit.targetToAppContext(component);
 720         SunToolkit.postEvent(SunToolkit.targetToAppContext(component), invocationEvent);
 721         // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
 722         SunToolkit.flushPendingEvents(appContext);
 723 
 724         checkException(invocationEvent);
 725     }
 726 
 727     /**
 728      * Checks if exception occurred while {@code InvocationEvent} was processed and rethrows it as
 729      * an {@code InvocationTargetException}
 730      *
 731      * @param event the event to check for an exception
 732      * @throws InvocationTargetException if exception occurred when event was processed
 733      */
 734     private static void checkException(InvocationEvent event) throws InvocationTargetException {
 735         Throwable eventException = event.getException();
 736         if (eventException == null) return;
 737 
 738         if (eventException instanceof UndeclaredThrowableException) {
 739             eventException = ((UndeclaredThrowableException)eventException).getUndeclaredThrowable();
 740         }
 741         throw new InvocationTargetException(eventException);
 742     }
 743 
 744     /**
 745      * Schedules a {@code Runnable} execution on the Appkit thread after a delay
 746      * @param r a {@code Runnable} to execute
 747      * @param delay a delay in milliseconds
 748      */
 749     static native void performOnMainThreadAfterDelay(Runnable r, long delay);
 750 
 751 // DnD support
 752 
 753     @Override
 754     public DragSourceContextPeer createDragSourceContextPeer(
 755             DragGestureEvent dge) throws InvalidDnDOperationException {
 756         final LightweightFrame f = SunToolkit.getLightweightFrame(dge.getComponent());
 757         if (f != null) {
 758             return f.createDragSourceContextPeer(dge);
 759         }
 760 
 761         return CDragSourceContextPeer.createDragSourceContextPeer(dge);
 762     }
 763 
 764     @Override
 765     @SuppressWarnings("unchecked")
 766     public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
 767             Class<T> abstractRecognizerClass, DragSource ds, Component c,
 768             int srcActions, DragGestureListener dgl) {
 769         final LightweightFrame f = SunToolkit.getLightweightFrame(c);
 770         if (f != null) {
 771             return f.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl);
 772         }
 773 
 774         DragGestureRecognizer dgr = null;
 775 
 776         // Create a new mouse drag gesture recognizer if we have a class match:
 777         if (MouseDragGestureRecognizer.class.equals(abstractRecognizerClass))
 778             dgr = new CMouseDragGestureRecognizer(ds, c, srcActions, dgl);
 779 
 780         return (T)dgr;
 781     }
 782 
 783     @Override
 784     protected PlatformDropTarget createDropTarget(DropTarget dropTarget,
 785                                                   Component component,
 786                                                   LWComponentPeer<?, ?> peer) {
 787         return new CDropTarget(dropTarget, component, peer);
 788     }
 789 
 790     // InputMethodSupport Method
 791     /**
 792      * Returns the default keyboard locale of the underlying operating system
 793      */
 794     @Override
 795     public Locale getDefaultKeyboardLocale() {
 796         Locale locale = CInputMethod.getNativeLocale();
 797 
 798         if (locale == null) {
 799             return super.getDefaultKeyboardLocale();
 800         }
 801 
 802         return locale;
 803     }
 804 
 805     public static boolean isLocaleUSInternationalPC(Locale locale) {
 806         return (locale != null ?
 807             locale.toString().equals("_US_UserDefined_15000") : false);
 808     }
 809 
 810     public static boolean isCharModifierKeyInUSInternationalPC(char ch) {
 811         // 5 characters: APOSTROPHE, QUOTATION MARK, ACCENT GRAVE, SMALL TILDE,
 812         // CIRCUMFLEX ACCENT
 813         final char[] modifierKeys = {'\'', '"', '`', '\u02DC', '\u02C6'};
 814         for (char modKey : modifierKeys) {
 815             if (modKey == ch) {
 816                 return true;
 817             }
 818         }
 819         return false;
 820     }
 821 
 822     @Override
 823     public InputMethodDescriptor getInputMethodAdapterDescriptor() {
 824         if (sInputMethodDescriptor == null)
 825             sInputMethodDescriptor = new CInputMethodDescriptor();
 826 
 827         return sInputMethodDescriptor;
 828     }
 829 
 830     /**
 831      * Returns a map of visual attributes for thelevel description
 832      * of the given input method highlight, or null if no mapping is found.
 833      * The style field of the input method highlight is ignored. The map
 834      * returned is unmodifiable.
 835      * @param highlight input method highlight
 836      * @return style attribute map, or null
 837      * @since 1.3
 838      */
 839     @Override
 840     public Map<TextAttribute, ?> mapInputMethodHighlight(InputMethodHighlight highlight) {
 841         return CInputMethod.mapInputMethodHighlight(highlight);
 842     }
 843 
 844     /**
 845      * Returns key modifiers used by Swing to set up a focus accelerator key
 846      * stroke.
 847      */
 848     @Override
 849     @SuppressWarnings("deprecation")
 850     public int getFocusAcceleratorKeyMask() {
 851         return InputEvent.CTRL_MASK | InputEvent.ALT_MASK;
 852     }
 853 
 854     /**
 855      * Tests whether specified key modifiers mask can be used to enter a
 856      * printable character.
 857      */
 858     @Override
 859     @SuppressWarnings("deprecation")
 860     public boolean isPrintableCharacterModifiersMask(int mods) {
 861         return ((mods & (InputEvent.META_MASK | InputEvent.CTRL_MASK)) == 0);
 862     }
 863 
 864     /**
 865      * Returns whether popup is allowed to be shown above the task bar.
 866      */
 867     @Override
 868     public boolean canPopupOverlapTaskBar() {
 869         return false;
 870     }
 871 
 872     /*
 873      * Returns true if the application (one of its windows) owns keyboard focus.
 874      */
 875     native boolean isApplicationActive();
 876 
 877     /**
 878      * Returns true if AWT toolkit is embedded, false otherwise.
 879      *
 880      * @return true if AWT toolkit is embedded, false otherwise
 881      */
 882     public static native boolean isEmbedded();
 883 
 884     /*
 885      * Activates application ignoring other apps.
 886      */
 887     public native void activateApplicationIgnoringOtherApps();
 888 
 889     /************************
 890      * Native methods section
 891      ************************/
 892 
 893     static native long createAWTRunLoopMediator();
 894     /**
 895      * Method to run a nested run-loop. The nested loop is spinned in the javaRunLoop mode, so selectors sent
 896      * by [JNFRunLoop performOnMainThreadWaiting] are processed.
 897      * @param mediator a native pointer to the mediator object created by createAWTRunLoopMediator
 898      * @param processEvents if true - dispatches event while in the nested loop. Used in DnD.
 899      *                      Additional attention is needed when using this feature as we short-circuit normal event
 900      *                      processing which could break Appkit.
 901      *                      (One known example is when the window is resized with the mouse)
 902      *
 903      *                      if false - all events come after exit form the nested loop
 904      */
 905     static void doAWTRunLoop(long mediator, boolean processEvents) {
 906         doAWTRunLoopImpl(mediator, processEvents, inAWT);
 907     }
 908     private static native void doAWTRunLoopImpl(long mediator, boolean processEvents, boolean inAWT);
 909     static native void stopAWTRunLoop(long mediator);
 910 
 911     private native boolean nativeSyncQueue(long timeout);
 912 
 913     /**
 914      * Just spin a single empty block synchronously.
 915      */
 916     static native void flushNativeSelectors();
 917 
 918     @Override
 919     public Clipboard createPlatformClipboard() {
 920         return new CClipboard("System");
 921     }
 922 
 923     @Override
 924     public boolean isModalExclusionTypeSupported(Dialog.ModalExclusionType exclusionType) {
 925         return (exclusionType == null) ||
 926             (exclusionType == Dialog.ModalExclusionType.NO_EXCLUDE) ||
 927             (exclusionType == Dialog.ModalExclusionType.APPLICATION_EXCLUDE) ||
 928             (exclusionType == Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
 929     }
 930 
 931     @Override
 932     public boolean isModalityTypeSupported(Dialog.ModalityType modalityType) {
 933         //TODO: FileDialog blocks excluded windows...
 934         //TODO: Test: 2 file dialogs, separate AppContexts: a) Dialog 1 blocked, shouldn't be. Frame 4 blocked (shouldn't be).
 935         return (modalityType == null) ||
 936             (modalityType == Dialog.ModalityType.MODELESS) ||
 937             (modalityType == Dialog.ModalityType.DOCUMENT_MODAL) ||
 938             (modalityType == Dialog.ModalityType.APPLICATION_MODAL) ||
 939             (modalityType == Dialog.ModalityType.TOOLKIT_MODAL);
 940     }
 941 
 942     @Override
 943     public boolean isWindowShapingSupported() {
 944         return true;
 945     }
 946 
 947     @Override
 948     public boolean isWindowTranslucencySupported() {
 949         return true;
 950     }
 951 
 952     @Override
 953     public boolean isTranslucencyCapable(GraphicsConfiguration gc) {
 954         return true;
 955     }
 956 
 957     @Override
 958     public boolean isSwingBackbufferTranslucencySupported() {
 959         return true;
 960     }
 961 
 962     @Override
 963     public boolean enableInputMethodsForTextComponent() {
 964         return true;
 965     }
 966 
 967     private static URL getScaledImageURL(URL url) {
 968         try {
 969             String scaledImagePath = getScaledImageName(url.getPath());
 970             return scaledImagePath == null ? null : new URL(url.getProtocol(),
 971                     url.getHost(), url.getPort(), scaledImagePath);
 972         } catch (MalformedURLException e) {
 973             return null;
 974         }
 975     }
 976 
 977     private static String getScaledImageName(String path) {
 978         if (!isValidPath(path)) {
 979             return null;
 980         }
 981 
 982         int slash = path.lastIndexOf('/');
 983         String name = (slash < 0) ? path : path.substring(slash + 1);
 984 
 985         if (name.contains("@2x")) {
 986             return null;
 987         }
 988 
 989         int dot = name.lastIndexOf('.');
 990         String name2x = (dot < 0) ? name + "@2x"
 991                 : name.substring(0, dot) + "@2x" + name.substring(dot);
 992         return (slash < 0) ? name2x : path.substring(0, slash + 1) + name2x;
 993     }
 994 
 995     private static boolean isValidPath(String path) {
 996         return path != null &&
 997                 !path.isEmpty() &&
 998                 !path.endsWith("/") &&
 999                 !path.endsWith(".");
1000     }
1001 
1002     @Override
1003     protected PlatformWindow getPlatformWindowUnderMouse() {
1004         return CPlatformWindow.nativeGetTopmostPlatformWindowUnderMouse();
1005     }
1006 
1007     @Override
1008     public void updateScreenMenuBarUI() {
1009         if (AquaMenuBarUI.getScreenMenuBarProperty())  {
1010             UIManager.put("MenuBarUI", "com.apple.laf.AquaMenuBarUI");
1011         } else {
1012             UIManager.put("MenuBarUI", null);
1013         }
1014     }
1015 }