Code polish/cleanup. Added tray icon size adjustments for linux/unix

This commit is contained in:
nathan 2017-06-25 22:59:45 +02:00
parent 2c2e835ab2
commit 69db7215b4

View File

@ -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);
}
}
}