From c01b560c06094ad45788d96b325be63b4875c786 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 16 Oct 2016 21:07:30 +0200 Subject: [PATCH] code cleanup, Fixed MacOS SystemTray popup trigger (it now will show on left and right click, but THERE IS NO DISTINCTION between which button caused it to show, it will always say it was left mouse) --- .../systemTray/util/SystemTrayFixes.java | 203 +++++++++++++----- 1 file changed, 153 insertions(+), 50 deletions(-) diff --git a/src/dorkbox/systemTray/util/SystemTrayFixes.java b/src/dorkbox/systemTray/util/SystemTrayFixes.java index 497859f..d16ce2f 100644 --- a/src/dorkbox/systemTray/util/SystemTrayFixes.java +++ b/src/dorkbox/systemTray/util/SystemTrayFixes.java @@ -17,15 +17,15 @@ package dorkbox.systemTray.util; import static dorkbox.systemTray.SystemTray.logger; -import java.awt.Robot; +import java.awt.AWTException; import java.awt.event.KeyEvent; import java.util.Locale; import dorkbox.systemTray.SystemTray; import dorkbox.util.BootStrapClassLoader; -import dorkbox.util.OS; import javassist.ClassPool; import javassist.CtClass; +import javassist.CtField; import javassist.CtMethod; /** @@ -37,10 +37,6 @@ class SystemTrayFixes { // oh my. Java likes to think that ALL windows tray icons are 16x16.... Lets fix that! // https://stackoverflow.com/questions/16378886/java-trayicon-right-click-disabled-on-mac-osx/35919788#35919788 public static void fixWindows() { - if (!OS.isWindows()) { - return; - } - String vendor = System.getProperty("java.vendor").toLowerCase(Locale.US); // spaces at the end to make sure we check for words if (!(vendor.contains("sun ") || vendor.contains("oracle "))) { @@ -100,7 +96,7 @@ class SystemTrayFixes { try { // necessary to initialize sun.awt.windows.WObjectPeer native initIDs() @SuppressWarnings("unused") - Robot robot = new Robot(); + java.awt.Robot robot = new java.awt.Robot(); ClassPool pool = ClassPool.getDefault(); @@ -128,62 +124,62 @@ class SystemTrayFixes { int TRAY_MASK = (ImageUtils.TRAY_SIZE * ImageUtils.TRAY_SIZE) / 8; ctMethodCreate.setBody("{" + - "java.awt.image.BufferedImage bufferedImage = $1;\n" + + "java.awt.image.BufferedImage bufferedImage = $1;" + - "java.awt.image.Raster rasterImage = bufferedImage.getRaster();\n" + - "final byte[] mask = new byte[" + TRAY_MASK + "];\n" + - "final int pixels[] = ((java.awt.image.DataBufferInt)rasterImage.getDataBuffer()).getData();\n" + + "java.awt.image.Raster rasterImage = bufferedImage.getRaster();" + + "final byte[] mask = new byte[" + TRAY_MASK + "];" + + "final int pixels[] = ((java.awt.image.DataBufferInt)rasterImage.getDataBuffer()).getData();" + - "int numberOfPixels = pixels.length;\n" + - "int rasterImageWidth = rasterImage.getWidth();\n" + + "int numberOfPixels = pixels.length;" + + "int rasterImageWidth = rasterImage.getWidth();" + - "for (int i = 0; i < numberOfPixels; i++) {\n" + - " int iByte = i / 8;\n" + - " int augmentMask = 1 << (7 - (i % 8));\n" + - " if ((pixels[i] & 0xFF000000) == 0) {\n" + - " if (iByte < mask.length) {\n" + - " mask[iByte] |= augmentMask;\n" + - " }\n" + - " }\n" + - "}\n" + + "for (int i = 0; i < numberOfPixels; i++) {" + + " int iByte = i / 8;" + + " int augmentMask = 1 << (7 - (i % 8));" + + " if ((pixels[i] & 0xFF000000) == 0) {" + + " if (iByte < mask.length) {" + + " mask[iByte] |= augmentMask;" + + " }" + + " }" + + "}" + - "if (rasterImage instanceof sun.awt.image.IntegerComponentRaster) {\n" + - " rasterImageWidth = ((sun.awt.image.IntegerComponentRaster)rasterImage).getScanlineStride();\n" + - "}\n" + + "if (rasterImage instanceof sun.awt.image.IntegerComponentRaster) {" + + " rasterImageWidth = ((sun.awt.image.IntegerComponentRaster)rasterImage).getScanlineStride();" + + "}" + "setNativeIcon(((java.awt.image.DataBufferInt)bufferedImage.getRaster().getDataBuffer()).getData(), " + - "mask, rasterImageWidth, rasterImage.getWidth(), rasterImage.getHeight());\n" + + "mask, rasterImageWidth, rasterImage.getWidth(), rasterImage.getHeight());" + "}"); ctMethodUpdate.setBody("{" + - "java.awt.Image image = $1;\n" + + "java.awt.Image image = $1;" + - "if (isDisposed()) {\n" + - " return;\n" + - "}\n" + + "if (isDisposed()) {" + + " return;" + + "}" + - "int imageWidth = image.getWidth(observer);\n" + - "int imageHeight = image.getWidth(observer);\n" + + "int imageWidth = image.getWidth(observer);" + + "int imageHeight = image.getWidth(observer);" + - "java.awt.image.BufferedImage trayIcon = new java.awt.image.BufferedImage(imageWidth, imageHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);\n" + - "java.awt.Graphics2D g = trayIcon.createGraphics();\n" + + "java.awt.image.BufferedImage trayIcon = new java.awt.image.BufferedImage(imageWidth, imageHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);" + + "java.awt.Graphics2D g = trayIcon.createGraphics();" + - "if (g != null) {\n" + - " try {\n" + + "if (g != null) {" + + " try {" + // this will render the image "nicely" " g.addRenderingHints(new java.awt.RenderingHints(java.awt.RenderingHints.KEY_RENDERING," + - "java.awt.RenderingHints.VALUE_RENDER_QUALITY));\n" + - " g.drawImage(image, 0, 0, imageWidth, imageHeight, observer);\n" + + "java.awt.RenderingHints.VALUE_RENDER_QUALITY));" + + " g.drawImage(image, 0, 0, imageWidth, imageHeight, observer);" + - " createNativeImage(trayIcon);\n" + + " createNativeImage(trayIcon);" + - " updateNativeIcon(!firstUpdate);\n" + + " updateNativeIcon(!firstUpdate);" + " if (firstUpdate) {" + - " firstUpdate = false;\n" + - " }\n" + - " } finally {\n" + - " g.dispose();\n" + - " }\n" + + " firstUpdate = false;" + + " }" + + " } finally {" + + " g.dispose();" + + " }" + "}" + "}"); @@ -206,10 +202,6 @@ class SystemTrayFixes { // https://stackoverflow.com/questions/16378886/java-trayicon-right-click-disabled-on-mac-osx/35919788#35919788 // https://bugs.openjdk.java.net/browse/JDK-7158615 public static void fixMacOS() { - if (!OS.isWindows()) { - return; - } - String vendor = System.getProperty("java.vendor").toLowerCase(Locale.US); // spaces at the end to make sure we check for words if (!(vendor.contains("sun ") || vendor.contains("oracle "))) { @@ -218,7 +210,6 @@ class SystemTrayFixes { } - boolean isMacSwingTrayLoaded = false; try { @@ -239,6 +230,118 @@ class SystemTrayFixes { if (isMacSwingTrayLoaded) { throw new RuntimeException("Unable to initialize the swing tray in windows, it has already been created!"); } + + try { + java.awt.Robot robot = new java.awt.Robot(); + robot.mousePress(java.awt.event.InputEvent.BUTTON1_DOWN_MASK); + } catch (AWTException e) { + e.printStackTrace(); + } + + ClassPool pool = ClassPool.getDefault(); + byte[] mouseEventBytes; + + try { + CtClass trayClass = pool.get("sun.lwawt.macosx.CTrayIcon"); + // now have to make a new "system tray" (that is null) in order to init/load this class completely + // have to modify the SystemTray.getIconSize as well. + trayClass.setModifiers(trayClass.getModifiers() & javassist.Modifier.PUBLIC); + trayClass.getConstructors()[0].setModifiers(trayClass.getConstructors()[0].getModifiers() & javassist.Modifier.PUBLIC); + + CtClass robotClass = pool.get("java.awt.Robot"); + CtField ctField = new CtField(robotClass, "robot", trayClass); + trayClass.addField(ctField); + + CtMethod ctMethodGet = trayClass.getDeclaredMethod("handleMouseEvent"); + ctMethodGet.setBody("{" + + "sun.lwawt.macosx.NSEvent event = $1;" + + + "sun.awt.SunToolkit toolKit = (sun.awt.SunToolkit)java.awt.Toolkit.getDefaultToolkit();" + + "int button = event.getButtonNumber();" + + + "if ((button <= 2 || toolKit.areExtraMouseButtonsEnabled()) && button <= toolKit.getNumberOfButtons() - 1) {" + + "int eventType = sun.lwawt.macosx.NSEvent.nsToJavaEventType(event.getType());" + + "int jButton = 0;" + + "int jClickCount = 0;" + + + // "java.lang.System.err.println(\"Click \" + button + \" event: \" + eventType);" + + + "if (eventType != 503) {" + + "jButton = sun.lwawt.macosx.NSEvent.nsToJavaButton(button);" + + "jClickCount = event.getClickCount();" + + "}" + + + "int mouseMods = sun.lwawt.macosx.NSEvent.nsToJavaMouseModifiers(button, event.getModifierFlags());" + + // surprisingly, this is false when the popup is showing + "boolean popupTrigger = sun.lwawt.macosx.NSEvent.isPopupTrigger(mouseMods);" + + + "int mouseMask = jButton > 0 ? java.awt.event.MouseEvent.getMaskForButton(jButton) : 0;" + + "long event0 = System.currentTimeMillis();" + + + "if(eventType == 501) {" + + "mouseClickButtons |= mouseMask;" + + "} else if(eventType == 506) {" + + "mouseClickButtons = 0;" + + "}" + + + // have to swallow + re-dispatch events in specific cases. (right click) + "if (eventType == 501 && popupTrigger && button == 1) {" + +// "java.lang.System.err.println(\"HAS POPUP \" + popupTrigger + \" event: \" + eventType);" + + // we use Robot to click where we clicked, in order to "fool" the native part to show the popup + // For what it's worth, this is the only way to get the native bits to behave. + "if (robot == null) {" + + "try {" + + "robot = new java.awt.Robot();" + + "} catch (java.awt.AWTException e) {" + + "e.printStackTrace();" + + "}" + + "}" + + "robot.mousePress(java.awt.event.InputEvent.BUTTON1_DOWN_MASK);" + + "return;" + + "}" + + + + "int mouseX = event.getAbsX();" + + "int mouseY = event.getAbsY();" + + "java.awt.event.MouseEvent mEvent = new java.awt.event.MouseEvent(this.dummyFrame, eventType, event0, mouseMods, mouseX, mouseY, mouseX, mouseY, jClickCount, popupTrigger, jButton);" + + + "mEvent.setSource(this.target);" + + "this.postEvent(mEvent);" + + + // mouse press + "if (eventType == 501) {" + + "if (popupTrigger) {" + + "String event5 = this.target.getActionCommand();" + + "java.awt.event.ActionEvent event6 = new java.awt.event.ActionEvent(this.target, 1001, event5);" + + "this.postEvent(event6);" + + "}" + + "}" + + + // mouse release + "if (eventType == 502) {" + + "if ((mouseClickButtons & mouseMask) != 0) {" + + "java.awt.event.MouseEvent event7 = new java.awt.event.MouseEvent(this.dummyFrame, 500, event0, mouseMods, mouseX, mouseY, mouseX, mouseY, jClickCount, popupTrigger, jButton);" + + + "event7.setSource(this.target);" + + "this.postEvent(event7);" + + "}" + + + "mouseClickButtons &= ~mouseMask;" + + "}" + + "}" + + "}"); + + mouseEventBytes = trayClass.toBytecode(); + + // whoosh, past the classloader and directly into memory. + BootStrapClassLoader.defineClass(mouseEventBytes); + + if (SystemTray.DEBUG) { + logger.debug("Successfully changed mouse trigger"); + } + } catch (Exception e) { + logger.error("Error changing SystemTray mouse trigger.", e); + } } /**