1 /*
   2  * Copyright (c) 2011, 2020, 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.AWTException;
  30 import java.awt.CheckboxMenuItem;
  31 import java.awt.Color;
  32 import java.awt.Component;
  33 import java.awt.Cursor;
  34 import java.awt.Desktop;
  35 import java.awt.Dialog;
  36 import java.awt.Dimension;
  37 import java.awt.Event;
  38 import java.awt.EventQueue;
  39 import java.awt.FileDialog;
  40 import java.awt.Frame;
  41 import java.awt.GraphicsConfiguration;
  42 import java.awt.GraphicsDevice;
  43 import java.awt.GraphicsEnvironment;
  44 import java.awt.HeadlessException;
  45 import java.awt.Image;
  46 import java.awt.Insets;
  47 import java.awt.Menu;
  48 import java.awt.MenuBar;
  49 import java.awt.MenuItem;
  50 import java.awt.Point;
  51 import java.awt.PopupMenu;
  52 import java.awt.RenderingHints;
  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(GraphicsDevice screen) throws AWTException {
 496         if (screen instanceof CGraphicsDevice) {
 497             return new CRobot((CGraphicsDevice) screen);
 498         }
 499         return super.createRobot(screen);
 500     }
 501 
 502     private native boolean isCapsLockOn();
 503 
 504     /*
 505      * NOTE: Among the keys this method is supposed to check,
 506      * only Caps Lock works as a true locking key with OS X.
 507      * There is no Scroll Lock key on modern Apple keyboards,
 508      * and with a PC keyboard plugged in Scroll Lock is simply
 509      * ignored: no LED lights up if you press it.
 510      * The key located at the same position on Apple keyboards
 511      * as Num Lock on PC keyboards is called Clear, doesn't lock
 512      * anything and is used for entirely different purpose.
 513      */
 514     @Override
 515     public boolean getLockingKeyState(int keyCode) throws UnsupportedOperationException {
 516         switch (keyCode) {
 517             case KeyEvent.VK_NUM_LOCK:
 518             case KeyEvent.VK_SCROLL_LOCK:
 519             case KeyEvent.VK_KANA_LOCK:
 520                 throw new UnsupportedOperationException("Toolkit.getLockingKeyState");
 521 
 522             case KeyEvent.VK_CAPS_LOCK:
 523                 return isCapsLockOn();
 524 
 525             default:
 526                 throw new IllegalArgumentException("invalid key for Toolkit.getLockingKeyState");
 527         }
 528     }
 529 
 530     //Is it allowed to generate events assigned to extra mouse buttons.
 531     //Set to true by default.
 532     private static boolean areExtraMouseButtonsEnabled = true;
 533 
 534     @Override
 535     public boolean areExtraMouseButtonsEnabled() throws HeadlessException {
 536         return areExtraMouseButtonsEnabled;
 537     }
 538 
 539     @Override
 540     public int getNumberOfButtons(){
 541         return BUTTONS;
 542     }
 543 
 544     /**
 545      * Returns the double-click time interval in ms.
 546      */
 547     private static native int getMultiClickTime();
 548 
 549     @Override
 550     public boolean isTraySupported() {
 551         return true;
 552     }
 553 
 554     @Override
 555     public DataTransferer getDataTransferer() {
 556         return CDataTransferer.getInstanceImpl();
 557     }
 558 
 559     @Override
 560     public boolean isAlwaysOnTopSupported() {
 561         return true;
 562     }
 563 
 564     private static final String APPKIT_THREAD_NAME = "AppKit Thread";
 565 
 566     // Intended to be called from the LWCToolkit.m only.
 567     private static void installToolkitThreadInJava() {
 568         Thread.currentThread().setName(APPKIT_THREAD_NAME);
 569         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 570             Thread.currentThread().setContextClassLoader(null);
 571             return null;
 572         });
 573     }
 574 
 575     @Override
 576     public boolean isWindowOpacitySupported() {
 577         return true;
 578     }
 579 
 580     @Override
 581     public boolean isFrameStateSupported(int state) throws HeadlessException {
 582         switch (state) {
 583             case Frame.NORMAL:
 584             case Frame.ICONIFIED:
 585             case Frame.MAXIMIZED_BOTH:
 586                 return true;
 587             default:
 588                 return false;
 589         }
 590     }
 591 
 592     @Override
 593     @Deprecated(since = "10")
 594     public int getMenuShortcutKeyMask() {
 595         return Event.META_MASK;
 596     }
 597 
 598     @Override
 599     public int getMenuShortcutKeyMaskEx() {
 600         return InputEvent.META_DOWN_MASK;
 601     }
 602 
 603     @Override
 604     public Image getImage(final String filename) {
 605         final Image nsImage = checkForNSImage(filename);
 606         if (nsImage != null) {
 607             return nsImage;
 608         }
 609 
 610         if (imageCached(filename)) {
 611             return super.getImage(filename);
 612         }
 613 
 614         String filename2x = getScaledImageName(filename);
 615         return (imageExists(filename2x))
 616                 ? getImageWithResolutionVariant(filename, filename2x)
 617                 : super.getImage(filename);
 618     }
 619 
 620     @Override
 621     public Image getImage(URL url) {
 622 
 623         if (imageCached(url)) {
 624             return super.getImage(url);
 625         }
 626 
 627         URL url2x = getScaledImageURL(url);
 628         return (imageExists(url2x))
 629                 ? getImageWithResolutionVariant(url, url2x) : super.getImage(url);
 630     }
 631 
 632     private static final String nsImagePrefix = "NSImage://";
 633     private Image checkForNSImage(final String imageName) {
 634         if (imageName == null) return null;
 635         if (!imageName.startsWith(nsImagePrefix)) return null;
 636         return CImage.getCreator().createImageFromName(imageName.substring(nsImagePrefix.length()));
 637     }
 638 
 639     // Thread-safe Object.equals() called from native
 640     public static boolean doEquals(final Object a, final Object b, Component c) {
 641         if (a == b) return true;
 642 
 643         final boolean[] ret = new boolean[1];
 644 
 645         try {  invokeAndWait(new Runnable() { @Override
 646                                               public void run() { synchronized(ret) {
 647             ret[0] = a.equals(b);
 648         }}}, c); } catch (Exception e) { e.printStackTrace(); }
 649 
 650         synchronized(ret) { return ret[0]; }
 651     }
 652 
 653     public static <T> T invokeAndWait(final Callable<T> callable,
 654                                       Component component) throws Exception {
 655         final CallableWrapper<T> wrapper = new CallableWrapper<>(callable);
 656         invokeAndWait(wrapper, component);
 657         return wrapper.getResult();
 658     }
 659 
 660     static final class CallableWrapper<T> implements Runnable {
 661         final Callable<T> callable;
 662         T object;
 663         Exception e;
 664 
 665         CallableWrapper(final Callable<T> callable) {
 666             this.callable = callable;
 667         }
 668 
 669         @Override
 670         public void run() {
 671             try {
 672                 object = callable.call();
 673             } catch (final Exception e) {
 674                 this.e = e;
 675             }
 676         }
 677 
 678         public T getResult() throws Exception {
 679             if (e != null) throw e;
 680             return object;
 681         }
 682     }
 683 
 684     /**
 685      * Kicks an event over to the appropriate event queue and waits for it to
 686      * finish To avoid deadlocking, we manually run the NSRunLoop while waiting
 687      * Any selector invoked using ThreadUtilities performOnMainThread will be
 688      * processed in doAWTRunLoop The InvocationEvent will call
 689      * LWCToolkit.stopAWTRunLoop() when finished, which will stop our manual
 690      * run loop. Does not dispatch native events while in the loop
 691      */
 692     public static void invokeAndWait(Runnable runnable, Component component)
 693             throws InvocationTargetException {
 694         Objects.requireNonNull(component, "Null component provided to invokeAndWait");
 695 
 696         long mediator = createAWTRunLoopMediator();
 697         InvocationEvent invocationEvent =
 698                 new InvocationEvent(component,
 699                         runnable,
 700                         () -> {
 701                             if (mediator != 0) {
 702                                 stopAWTRunLoop(mediator);
 703                             }
 704                         },
 705                         true);
 706 
 707         AppContext appContext = SunToolkit.targetToAppContext(component);
 708         SunToolkit.postEvent(appContext, invocationEvent);
 709         // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
 710         SunToolkit.flushPendingEvents(appContext);
 711         doAWTRunLoop(mediator, false);
 712 
 713         checkException(invocationEvent);
 714     }
 715 
 716     public static void invokeLater(Runnable event, Component component)
 717             throws InvocationTargetException {
 718         Objects.requireNonNull(component, "Null component provided to invokeLater");
 719 
 720         InvocationEvent invocationEvent = new InvocationEvent(component, event);
 721 
 722         AppContext appContext = SunToolkit.targetToAppContext(component);
 723         SunToolkit.postEvent(SunToolkit.targetToAppContext(component), invocationEvent);
 724         // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
 725         SunToolkit.flushPendingEvents(appContext);
 726 
 727         checkException(invocationEvent);
 728     }
 729 
 730     /**
 731      * Checks if exception occurred while {@code InvocationEvent} was processed and rethrows it as
 732      * an {@code InvocationTargetException}
 733      *
 734      * @param event the event to check for an exception
 735      * @throws InvocationTargetException if exception occurred when event was processed
 736      */
 737     private static void checkException(InvocationEvent event) throws InvocationTargetException {
 738         Throwable eventException = event.getException();
 739         if (eventException == null) return;
 740 
 741         if (eventException instanceof UndeclaredThrowableException) {
 742             eventException = ((UndeclaredThrowableException)eventException).getUndeclaredThrowable();
 743         }
 744         throw new InvocationTargetException(eventException);
 745     }
 746 
 747     /**
 748      * Schedules a {@code Runnable} execution on the Appkit thread after a delay
 749      * @param r a {@code Runnable} to execute
 750      * @param delay a delay in milliseconds
 751      */
 752     static native void performOnMainThreadAfterDelay(Runnable r, long delay);
 753 
 754 // DnD support
 755 
 756     @Override
 757     public DragSourceContextPeer createDragSourceContextPeer(
 758             DragGestureEvent dge) throws InvalidDnDOperationException {
 759         final LightweightFrame f = SunToolkit.getLightweightFrame(dge.getComponent());
 760         if (f != null) {
 761             return f.createDragSourceContextPeer(dge);
 762         }
 763 
 764         return CDragSourceContextPeer.createDragSourceContextPeer(dge);
 765     }
 766 
 767     @Override
 768     @SuppressWarnings("unchecked")
 769     public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
 770             Class<T> abstractRecognizerClass, DragSource ds, Component c,
 771             int srcActions, DragGestureListener dgl) {
 772         final LightweightFrame f = SunToolkit.getLightweightFrame(c);
 773         if (f != null) {
 774             return f.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl);
 775         }
 776 
 777         DragGestureRecognizer dgr = null;
 778 
 779         // Create a new mouse drag gesture recognizer if we have a class match:
 780         if (MouseDragGestureRecognizer.class.equals(abstractRecognizerClass))
 781             dgr = new CMouseDragGestureRecognizer(ds, c, srcActions, dgl);
 782 
 783         return (T)dgr;
 784     }
 785 
 786     @Override
 787     protected PlatformDropTarget createDropTarget(DropTarget dropTarget,
 788                                                   Component component,
 789                                                   LWComponentPeer<?, ?> peer) {
 790         return new CDropTarget(dropTarget, component, peer);
 791     }
 792 
 793     // InputMethodSupport Method
 794     /**
 795      * Returns the default keyboard locale of the underlying operating system
 796      */
 797     @Override
 798     public Locale getDefaultKeyboardLocale() {
 799         Locale locale = CInputMethod.getNativeLocale();
 800 
 801         if (locale == null) {
 802             return super.getDefaultKeyboardLocale();
 803         }
 804 
 805         return locale;
 806     }
 807 
 808     public static boolean isLocaleUSInternationalPC(Locale locale) {
 809         return (locale != null ?
 810             locale.toString().equals("_US_UserDefined_15000") : false);
 811     }
 812 
 813     public static boolean isCharModifierKeyInUSInternationalPC(char ch) {
 814         // 5 characters: APOSTROPHE, QUOTATION MARK, ACCENT GRAVE, SMALL TILDE,
 815         // CIRCUMFLEX ACCENT
 816         final char[] modifierKeys = {'\'', '"', '`', '\u02DC', '\u02C6'};
 817         for (char modKey : modifierKeys) {
 818             if (modKey == ch) {
 819                 return true;
 820             }
 821         }
 822         return false;
 823     }
 824 
 825     @Override
 826     public InputMethodDescriptor getInputMethodAdapterDescriptor() {
 827         if (sInputMethodDescriptor == null)
 828             sInputMethodDescriptor = new CInputMethodDescriptor();
 829 
 830         return sInputMethodDescriptor;
 831     }
 832 
 833     /**
 834      * Returns a map of visual attributes for thelevel description
 835      * of the given input method highlight, or null if no mapping is found.
 836      * The style field of the input method highlight is ignored. The map
 837      * returned is unmodifiable.
 838      * @param highlight input method highlight
 839      * @return style attribute map, or null
 840      * @since 1.3
 841      */
 842     @Override
 843     public Map<TextAttribute, ?> mapInputMethodHighlight(InputMethodHighlight highlight) {
 844         return CInputMethod.mapInputMethodHighlight(highlight);
 845     }
 846 
 847     /**
 848      * Returns key modifiers used by Swing to set up a focus accelerator key
 849      * stroke.
 850      */
 851     @Override
 852     @SuppressWarnings("deprecation")
 853     public int getFocusAcceleratorKeyMask() {
 854         return InputEvent.CTRL_MASK | InputEvent.ALT_MASK;
 855     }
 856 
 857     /**
 858      * Tests whether specified key modifiers mask can be used to enter a
 859      * printable character.
 860      */
 861     @Override
 862     @SuppressWarnings("deprecation")
 863     public boolean isPrintableCharacterModifiersMask(int mods) {
 864         return ((mods & (InputEvent.META_MASK | InputEvent.CTRL_MASK)) == 0);
 865     }
 866 
 867     /**
 868      * Returns whether popup is allowed to be shown above the task bar.
 869      */
 870     @Override
 871     public boolean canPopupOverlapTaskBar() {
 872         return false;
 873     }
 874 
 875     /*
 876      * Returns true if the application (one of its windows) owns keyboard focus.
 877      */
 878     native boolean isApplicationActive();
 879 
 880     /**
 881      * Returns true if AWT toolkit is embedded, false otherwise.
 882      *
 883      * @return true if AWT toolkit is embedded, false otherwise
 884      */
 885     public static native boolean isEmbedded();
 886 
 887     /*
 888      * Activates application ignoring other apps.
 889      */
 890     public native void activateApplicationIgnoringOtherApps();
 891 
 892     /************************
 893      * Native methods section
 894      ************************/
 895 
 896     static native long createAWTRunLoopMediator();
 897     /**
 898      * Method to run a nested run-loop. The nested loop is spinned in the javaRunLoop mode, so selectors sent
 899      * by [JNFRunLoop performOnMainThreadWaiting] are processed.
 900      * @param mediator a native pointer to the mediator object created by createAWTRunLoopMediator
 901      * @param processEvents if true - dispatches event while in the nested loop. Used in DnD.
 902      *                      Additional attention is needed when using this feature as we short-circuit normal event
 903      *                      processing which could break Appkit.
 904      *                      (One known example is when the window is resized with the mouse)
 905      *
 906      *                      if false - all events come after exit form the nested loop
 907      */
 908     static void doAWTRunLoop(long mediator, boolean processEvents) {
 909         doAWTRunLoopImpl(mediator, processEvents, inAWT);
 910     }
 911     private static native void doAWTRunLoopImpl(long mediator, boolean processEvents, boolean inAWT);
 912     static native void stopAWTRunLoop(long mediator);
 913 
 914     private native boolean nativeSyncQueue(long timeout);
 915 
 916     /**
 917      * Just spin a single empty block synchronously.
 918      */
 919     static native void flushNativeSelectors();
 920 
 921     @Override
 922     public Clipboard createPlatformClipboard() {
 923         return new CClipboard("System");
 924     }
 925 
 926     @Override
 927     public boolean isModalExclusionTypeSupported(Dialog.ModalExclusionType exclusionType) {
 928         return (exclusionType == null) ||
 929             (exclusionType == Dialog.ModalExclusionType.NO_EXCLUDE) ||
 930             (exclusionType == Dialog.ModalExclusionType.APPLICATION_EXCLUDE) ||
 931             (exclusionType == Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
 932     }
 933 
 934     @Override
 935     public boolean isModalityTypeSupported(Dialog.ModalityType modalityType) {
 936         //TODO: FileDialog blocks excluded windows...
 937         //TODO: Test: 2 file dialogs, separate AppContexts: a) Dialog 1 blocked, shouldn't be. Frame 4 blocked (shouldn't be).
 938         return (modalityType == null) ||
 939             (modalityType == Dialog.ModalityType.MODELESS) ||
 940             (modalityType == Dialog.ModalityType.DOCUMENT_MODAL) ||
 941             (modalityType == Dialog.ModalityType.APPLICATION_MODAL) ||
 942             (modalityType == Dialog.ModalityType.TOOLKIT_MODAL);
 943     }
 944 
 945     @Override
 946     public boolean isWindowShapingSupported() {
 947         return true;
 948     }
 949 
 950     @Override
 951     public boolean isWindowTranslucencySupported() {
 952         return true;
 953     }
 954 
 955     @Override
 956     public boolean isTranslucencyCapable(GraphicsConfiguration gc) {
 957         return true;
 958     }
 959 
 960     @Override
 961     public boolean isSwingBackbufferTranslucencySupported() {
 962         return true;
 963     }
 964 
 965     @Override
 966     public boolean enableInputMethodsForTextComponent() {
 967         return true;
 968     }
 969 
 970     private static URL getScaledImageURL(URL url) {
 971         try {
 972             String scaledImagePath = getScaledImageName(url.getPath());
 973             return scaledImagePath == null ? null : new URL(url.getProtocol(),
 974                     url.getHost(), url.getPort(), scaledImagePath);
 975         } catch (MalformedURLException e) {
 976             return null;
 977         }
 978     }
 979 
 980     private static String getScaledImageName(String path) {
 981         if (!isValidPath(path)) {
 982             return null;
 983         }
 984 
 985         int slash = path.lastIndexOf('/');
 986         String name = (slash < 0) ? path : path.substring(slash + 1);
 987 
 988         if (name.contains("@2x")) {
 989             return null;
 990         }
 991 
 992         int dot = name.lastIndexOf('.');
 993         String name2x = (dot < 0) ? name + "@2x"
 994                 : name.substring(0, dot) + "@2x" + name.substring(dot);
 995         return (slash < 0) ? name2x : path.substring(0, slash + 1) + name2x;
 996     }
 997 
 998     private static boolean isValidPath(String path) {
 999         return path != null &&
1000                 !path.isEmpty() &&
1001                 !path.endsWith("/") &&
1002                 !path.endsWith(".");
1003     }
1004 
1005     @Override
1006     protected PlatformWindow getPlatformWindowUnderMouse() {
1007         return CPlatformWindow.nativeGetTopmostPlatformWindowUnderMouse();
1008     }
1009 
1010     @Override
1011     public void updateScreenMenuBarUI() {
1012         if (AquaMenuBarUI.getScreenMenuBarProperty())  {
1013             UIManager.put("MenuBarUI", "com.apple.laf.AquaMenuBarUI");
1014         } else {
1015             UIManager.put("MenuBarUI", null);
1016         }
1017     }
1018 }