From 69db7215b422cca5ffe1bd1299c63cdd595105fd Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 25 Jun 2017 22:59:45 +0200 Subject: [PATCH] Code polish/cleanup. Added tray icon size adjustments for linux/unix --- .../systemTray/util/SystemTrayFixes.java | 373 +++++++----------- 1 file changed, 144 insertions(+), 229 deletions(-) diff --git a/src/dorkbox/systemTray/util/SystemTrayFixes.java b/src/dorkbox/systemTray/util/SystemTrayFixes.java index 44d653e..b2889d9 100644 --- a/src/dorkbox/systemTray/util/SystemTrayFixes.java +++ b/src/dorkbox/systemTray/util/SystemTrayFixes.java @@ -16,6 +16,7 @@ package dorkbox.systemTray.util; import static dorkbox.systemTray.SystemTray.logger; +import static javassist.bytecode.Opcode.BIPUSH; import java.awt.AWTException; import java.util.Locale; @@ -24,11 +25,18 @@ import dorkbox.systemTray.SystemTray; import dorkbox.util.BootStrapClassLoader; import dorkbox.util.OS; import javassist.ClassPool; +import javassist.CtBehavior; import javassist.CtClass; import javassist.CtField; import javassist.CtMethod; import javassist.CtNewMethod; import javassist.Modifier; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.InstructionPrinter; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Mnemonic; /* @@ -134,10 +142,13 @@ class SystemTrayFixes { // 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); - CtMethod ctMethodGet = trayClass.getDeclaredMethod("getTrayIconSize"); - ctMethodGet.setBody("{" + - "return new java.awt.Dimension(" + trayIconSize + ", " + trayIconSize + ");" + - "}"); + + + CtBehavior methodInfos[] = new CtBehavior[]{ + trayClass.getDeclaredMethod("getTrayIconSize") + }; + + fixTraySize(methodInfos, trayIconSize); trayBytes = trayClass.toBytecode(); } @@ -159,17 +170,17 @@ class SystemTrayFixes { "int rasterImageWidth = rasterImage.getWidth();" + "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;" + - " }" + - " }" + + "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) {" + - " rasterImageWidth = ((sun.awt.image.IntegerComponentRaster)rasterImage).getScanlineStride();" + + "rasterImageWidth = ((sun.awt.image.IntegerComponentRaster)rasterImage).getScanlineStride();" + "}" + "setNativeIcon(((java.awt.image.DataBufferInt)bufferedImage.getRaster().getDataBuffer()).getData(), " + @@ -180,7 +191,7 @@ class SystemTrayFixes { "java.awt.Image image = $1;" + "if (isDisposed()) {" + - " return;" + + "return;" + "}" + "int imageWidth = image.getWidth(observer);" + @@ -190,21 +201,21 @@ class SystemTrayFixes { "java.awt.Graphics2D g = trayIcon.createGraphics();" + "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));" + - " g.drawImage(image, 0, 0, imageWidth, imageHeight, observer);" + + "try {" + + // this will render the image "nicely" + "g.addRenderingHints(new java.awt.RenderingHints(java.awt.RenderingHints.KEY_RENDERING," + + "java.awt.RenderingHints.VALUE_RENDER_QUALITY));" + + "g.drawImage(image, 0, 0, imageWidth, imageHeight, observer);" + - " createNativeImage(trayIcon);" + + "createNativeImage(trayIcon);" + - " updateNativeIcon(!firstUpdate);" + - " if (firstUpdate) {" + - " firstUpdate = false;" + - " }" + - " } finally {" + - " g.dispose();" + - " }" + + "updateNativeIcon(!firstUpdate);" + + "if (firstUpdate) {" + + "firstUpdate = false;" + + "}" + + "} finally {" + + "g.dispose();" + + "}" + "}" + "}"); @@ -406,7 +417,7 @@ class SystemTrayFixes { * Spectacularly enough, because this uses X11, it works on any X backend -- regardless of GtkStatusIcon or AppIndicator support. This * actually provides **more** support than GtkStatusIcons or AppIndicators, since this will ALWAYS work. * - * Additionally, the size of the tray is hard-coded to be 24 -- so we want to fix that as well. + * Additionally, the size of the tray is hard-coded to be 24. * * * The down side, is that there is a "grey" box -- so hack around this issue by getting the color of a pixel in the notification area 1 @@ -422,11 +433,8 @@ class SystemTrayFixes { * http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/solaris/classes/sun/awt/X11/XSystemTrayPeer.java */ public static - void fixLinux() { + void fixLinux(int trayIconSize) { // linux/mac doesn't have transparent backgrounds for "swing" system tray icons - // TODO Fix the tray icon size to something other than (hardcoded) 24. This will be a lot of work, since this value is in the - // constructor, and there is a significant amount of code and anonymous classes there - and javassist does not support non-staic - // anonymous classes. if (isOracleVM()) { // not fixing things that are not broken. @@ -445,22 +453,13 @@ class SystemTrayFixes { String className = "sun.awt.X11.XTrayIconPeer"; byte[] eFrameBytes; byte[] trayIconBytes; + byte[] trayPeerBytes; { CtClass trayIconClass = pool.get(className); - - ctField = new CtField(pool.get("java.awt.Robot"), "robot", trayIconClass); - ctField.setModifiers(Modifier.STATIC); - trayIconClass.addField(ctField); - - ctField = new CtField(pool.get("java.awt.Color"), "color", trayIconClass); - ctField.setModifiers(Modifier.STATIC); - trayIconClass.addField(ctField); - - trayIconBytes = trayIconClass.toBytecode(); - - CtClass eFrameClass = null; + CtClass trayPeerClass; + CtClass[] nestedClasses = trayIconClass.getNestedClasses(); for (CtClass nestedClass : nestedClasses) { String name = nestedClass.getName(); @@ -474,12 +473,41 @@ class SystemTrayFixes { throw new RuntimeException("Unable to find required classes to fix. Unable to continue initialization."); } + + ctField = new CtField(pool.get("java.awt.Robot"), "robot", trayIconClass); + ctField.setModifiers(Modifier.STATIC); + trayIconClass.addField(ctField); + + ctField = new CtField(pool.get("java.awt.Color"), "color", trayIconClass); + ctField.setModifiers(Modifier.STATIC); + trayIconClass.addField(ctField); + + + // fix other classes for icon size. + trayPeerClass = pool.get("sun.awt.X11.XSystemTrayPeer"); + + // now we have to replace ALL instances (in the constructor and other methods), where the icon size is set (default is 24). + // Since, looking at the source code, there is NO other case where the number 24 is used (except for size), we just + // bytecode replace 24 with our specified size. + + CtBehavior methodInfos[] = new CtBehavior[]{ + trayIconClass.getDeclaredConstructors()[0], // only 1 constructor + trayIconClass.getDeclaredMethod("getBounds"), + trayPeerClass.getDeclaredMethod("getTrayIconSize") + }; + + fixTraySize(methodInfos, trayIconSize); + + trayIconBytes = trayIconClass.toBytecode(); + trayPeerBytes = trayPeerClass.toBytecode(); + + // gets the pixel color just to the side of the icon. The CRITICAL thing to notice, is that this happens before the // AWT window is positioned, so there can be a different system tray icon at this position (at this exact point in - // time. This means we cannot take a screen shot, because before the window is placed, another icon is in this spot, - // and when the window is placed, it's too late to take a screenshot. The second best option is to take a sample of - // the pixel color, so at least we can fake transparency. This only works if the notification area is a solid color, - // and not an image or gradient. + // time). This means we cannot take a screen shot because before the window is placed, another icon is in this + // spot; and when the window is placed, it's too late to take a screenshot. The second best option is to take a sample of + // the pixel color, so at least we can fake transparency (this is what we do). This only works if the notification area + // is a solid color, and not an image or gradient. CtMethod methodVisible = CtNewMethod.make( "public void setVisible(boolean b) " + "{ " + @@ -490,198 +518,19 @@ class SystemTrayFixes { "java.awt.Point loc = getPeer().getLocationOnScreen();" + className + ".color = " + className + ".robot.getPixelColor(loc.x-1, loc.y-1);" + - // this sets the background of the native component, NOT THE ICON (otherwise weird "grey" flashes occur + // this sets the background of the native component, NOT THE ICON (otherwise weird "grey" flashes occur) "setBackground(" + className + ".color);" + "}" + "super.setVisible(b);" + - " }", - eFrameClass); + " }", eFrameClass); eFrameClass.addMethod(methodVisible); - eFrameBytes = eFrameClass.toBytecode(); - - -// CtMethod ctMethodRepaintImage = iconCanvasClass.getDeclaredMethod("repaintImage"); - - -// MethodInfo methodInfo = ctMethodRepaintImage.getMethodInfo(); -// ConstPool pool2 = methodInfo.getConstPool(); -// CodeIterator ci = methodInfo.getCodeAttribute().iterator(); -// int lineNumber = -1; -// -// while (ci.hasNext()) { -// int index = ci.next(); -// lineNumber = methodInfo.getLineNumber(index); -// int opcode = ci.byteAt(index); -// -// String op = Mnemonic.OPCODE[opcode]; -// switch (opcode) { -// case INVOKEVIRTUAL: { -// int i = ci.u16bitAt(index + 1); -// -// // specifically inject the pixel grabbing code JUST before update happens. -// if (pool2.getMethodrefClassName(i) -// .equals("sun.awt.X11.XTrayIconPeer$IconCanvas") && -// -// pool2.getMethodrefName(i) -// .equals("update") && -// -// pool2.getMethodrefType(i) -// .equals("(Ljava/awt/Graphics;)V")) { -// -// -//// " if (robot == null) {" + -//// " robot = new java.awt.Robot();" + -//// " }" + -//// " if (doClear) {" + - -//// " java.awt.Point loc = getLocationOnScreen();" + -//// " color = robot.getPixelColor(loc.x-1, loc.y-1);" + -// -// -// System.out.println(op + " " + "#" + i + " = Method " + pool2.getMethodrefClassName(i) + "." + -// pool2.getMethodrefName(i) + "(" + pool2.getMethodrefType(i) + ")"); -// } -// } -// } -// -//// System.out.println(lineNumber + " * " + Mnemonic.OPCODE[op] + " "); -// System.out.println(lineNumber + " * " + InstructionPrinter.instructionString(ci, index, pool2)); -// } - -//new Canvas().update(); - -// CtMethod methodUpdate = iconCanvasClass.getMethod("update", "(Ljava/awt/Graphics;)V"); - - -// for(CtMethod method:iconCanvasClass.getDeclaredMethods()){ -// System.err.println("MEthod: " + method.getName()); -// method.insertBefore("System.out.println(\"Before every method call....\");"); -// } -// -// for (CtMethod method : iconCanvasClass.getMethods()) { -//// System.err.println("Method: " + method.getName()); -// if (method.getName() -// .equals("getBackground")) { -// -// System.err.println("found " + method.getName()); -//// method.insertBefore("System.err.println(\"Before every method call....\");"); -// method.setBody("return color;"); -// } -// } - - -// StringBuilder collector = new StringBuilder(); -// int lastLine = -1; -// -// while (ci.hasNext()) { -// int index = ci.next(); -// lineNumber = methodInfo.getLineNumber(index); -// int op = ci.byteAt(index); -// -// if (lastLine == -1) { -// lastLine = lineNumber; -// } -// -// if (lineNumber != lastLine) { -// if (collector.length() > 0) { -// System.out.println(lastLine + " : " + collector); -// } -// lastLine = lineNumber; -// collector.delete(0, collector.length()); -// } -// -// collector.append(Mnemonic.OPCODE[op]) -// .append(" "); -// -//// System.out.println(lineNumber + " * " + Mnemonic.OPCODE[op] + " "); -// System.out.println(lineNumber + " * " + InstructionPrinter.instructionString(ci, index, pool2)); -// } -// -// if (collector.length() > 0) { -// System.out.println(lineNumber + " : " + collector); -// } - - - - - - - - -// String body = "{" + -// "boolean doClear = $1;" + -// -// "java.lang.System.err.println(\"update image: \" + doClear);" + -// -// "java.awt.Graphics g = getGraphics();" + -// "if (g != null) {" + -// " try {" + -// " if (isVisible()) {" + -// " if (robot == null) {" + -// " robot = new java.awt.Robot();" + -// " }" + -// " if (doClear) {" + -// // gets the pixel color just to the side of the icon. The CRITICAL thing to notice, is that this happens before the -// // AWT window is positioned, so there can be a different system tray icon at this position (at this exact point in -// // time. This means we cannot take a screen shot, because before the window is placed, another icon is in this spot, -// // and when the window is placed, it's too late to take a screenshot. The second best option is to take a sample of -// // the pixel color, so at least we can fake transparency. This only works if the notification area is a solid color, -// // and not an image or gradient. -// " java.awt.Point loc = getLocationOnScreen();" + -// " color = robot.getPixelColor(loc.x-1, loc.y-1);" + -// " update(g);" + -// " } else {" + -// " paint(g);" + -// " }" + -// " }" + -// " } finally {" + -// " g.dispose();" + -// " }" + -// "}" + -// "}"; -// ctMethodRepaintImage.setBody(body); - -// -// CtMethod ctMethodPaint = iconCanvasClass.getDeclaredMethod("paint"); -// body = "{" + -// "java.awt.Graphics g = $1;" + -// -// "if (g != null && curW > 0 && curH > 0) {" + -// " java.awt.image.BufferedImage bufImage = new java.awt.image.BufferedImage(curW, curH, java.awt.image.BufferedImage.TYPE_INT_ARGB);" + -// " java.awt.Graphics2D gr = bufImage.createGraphics();" + -// " if (gr != null) {" + -// " try {" + -// -// // this will render the image "nicely" -// " gr.addRenderingHints(new java.awt.RenderingHints(java.awt.RenderingHints.KEY_RENDERING," + -// " java.awt.RenderingHints.VALUE_RENDER_QUALITY));" + -// -// // Have to replace the color with the correct pixel color to simulate transparency -// " gr.setColor(color);" + -// " gr.fillRect(0, 0, curW, curH);" + -// " gr.drawImage(image, 0, 0, curW, curH, observer);" + -// " gr.dispose();" + -// " g.drawImage(bufImage, 0, 0, curW, curH, null);" + -// " } finally {" + -// " g.dispose();" + -// " }" + -// " }" + -// "}" + -// "}"; -// ctMethodPaint.setBody(body); - - -// iconCanvasClass.writeFile("/tmp/modifiedClassesFolder"); - - - - } // whoosh, past the classloader and directly into memory. BootStrapClassLoader.defineClass(trayIconBytes); + BootStrapClassLoader.defineClass(trayPeerBytes); BootStrapClassLoader.defineClass(eFrameBytes); if (SystemTray.DEBUG) { @@ -691,4 +540,70 @@ class SystemTrayFixes { logger.error("Error setting tray icon background color", e); } } + + private static + void fixTraySize(final CtBehavior[] behaviors, final int traySize) { + for (CtBehavior behavior : behaviors) { + CodeIterator methodIterator = behavior.getMethodInfo().getCodeAttribute().iterator(); + + while (methodIterator.hasNext()) { + int index; + try { + index = methodIterator.next(); + int opcode = methodIterator.byteAt(index); + + switch (opcode) { + case BIPUSH: { + int i = methodIterator.byteAt(index + 1); + + if (i == 24) { + // re-write this to be our custom size. + methodIterator.writeByte((byte) traySize, index + 1); + } + } + } + } catch (BadBytecode badBytecode) { + badBytecode.printStackTrace(); + } + } + } + } + + private static + void showMethodBytecode(final CtBehavior constructorOrMethod) throws BadBytecode { + MethodInfo methodInfo = constructorOrMethod.getMethodInfo(); // only 1 constructor + ConstPool pool2 = methodInfo.getConstPool(); + CodeIterator ci = methodInfo.getCodeAttribute().iterator(); + int lineNumber = -1; + StringBuilder collector = new StringBuilder(); + int lastLine = -1; + + while (ci.hasNext()) { + int index = ci.next(); + lineNumber = methodInfo.getLineNumber(index); + int op = ci.byteAt(index); + + if (lastLine == -1) { + lastLine = lineNumber; + } + + if (lineNumber != lastLine) { + if (collector.length() > 0) { + System.err.println(lastLine + " : " + collector); + } + lastLine = lineNumber; + collector.delete(0, collector.length()); + } + + collector.append(Mnemonic.OPCODE[op]) + .append(" "); + + System.out.println(lineNumber + " * " + Mnemonic.OPCODE[op] + " "); + System.out.println(lineNumber + " * " + InstructionPrinter.instructionString(ci, index, pool2)); + } + + if (collector.length() > 0) { + System.err.println(lineNumber + " : " + collector); + } + } }