Image scaling + caching + error-icon if problems, fix for windows
'auto' scaling
This commit is contained in:
parent
a985827f5b
commit
ad066c6e42
|
@ -1,241 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray;
|
||||
|
||||
import dorkbox.util.LocationResolver;
|
||||
import dorkbox.util.OS;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public
|
||||
class ImageUtil {
|
||||
|
||||
public static final File TEMP_DIR = new File(System.getProperty("java.io.tmpdir"));
|
||||
|
||||
private static MessageDigest digest;
|
||||
|
||||
private static final Map<String, String> resourceToFilePath = new HashMap<String, String>();
|
||||
private static final long runtimeRandom = new SecureRandom().nextLong();
|
||||
|
||||
public static synchronized
|
||||
void init() throws NoSuchAlgorithmException {
|
||||
ImageUtil.digest = MessageDigest.getInstance("MD5");
|
||||
}
|
||||
|
||||
/**
|
||||
* appIndicator/gtk require strings (which is the path)
|
||||
* swing version loads as an image (which can be stream or path, we use path)
|
||||
*/
|
||||
public static synchronized
|
||||
String iconPath(String fileName) {
|
||||
// if we already have this fileName, reuse it
|
||||
final String cachedFile = resourceToFilePath.get(fileName);
|
||||
if (cachedFile != null) {
|
||||
return cachedFile;
|
||||
}
|
||||
|
||||
// is file sitting on drive
|
||||
File iconTest = new File(fileName);
|
||||
if (iconTest.isFile() && iconTest.canRead()) {
|
||||
final String absolutePath = iconTest.getAbsolutePath();
|
||||
|
||||
resourceToFilePath.put(fileName, absolutePath);
|
||||
return absolutePath;
|
||||
}
|
||||
else {
|
||||
// suck it out of a URL/Resource (with debugging if necessary)
|
||||
final URL systemResource = LocationResolver.getResource(fileName);
|
||||
final String filePath = makeFileViaUrl(systemResource);
|
||||
resourceToFilePath.put(fileName, filePath);
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* appIndicator/gtk require strings (which is the path)
|
||||
* swing version loads as an image (which can be stream or path, we use path)
|
||||
*/
|
||||
public static synchronized
|
||||
String iconPath(final URL fileResource) {
|
||||
// if we already have this fileName, reuse it
|
||||
final String cachedFile = resourceToFilePath.get(fileResource.getPath());
|
||||
if (cachedFile != null) {
|
||||
return cachedFile;
|
||||
}
|
||||
|
||||
final String filePath = makeFileViaUrl(fileResource);
|
||||
resourceToFilePath.put(fileResource.getPath(), filePath);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* appIndicator/gtk require strings (which is the path)
|
||||
* swing version loads as an image (which can be stream or path, we use path)
|
||||
*/
|
||||
public static synchronized
|
||||
String iconPath(final String cacheName, final InputStream fileStream) {
|
||||
// if we already have this fileName, reuse it
|
||||
final String cachedFile = resourceToFilePath.get(cacheName);
|
||||
if (cachedFile != null) {
|
||||
return cachedFile;
|
||||
}
|
||||
|
||||
final String filePath = makeFileViaStream(cacheName, fileStream);
|
||||
resourceToFilePath.put(cacheName, filePath);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* NO CACHING OF INPUTSTREAM!
|
||||
*
|
||||
* appIndicator/gtk require strings (which is the path)
|
||||
* swing version loads as an image (which can be stream or path, we use path)
|
||||
*/
|
||||
@Deprecated
|
||||
public static synchronized
|
||||
String iconPathNoCache(final InputStream fileStream) {
|
||||
return makeFileViaStream(Long.toString(System.currentTimeMillis()), fileStream);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param resourceUrl the url to copy to a file on disk
|
||||
* @return the full path of the resource copied to disk, or null if invalid
|
||||
*/
|
||||
private static
|
||||
String makeFileViaUrl(final URL resourceUrl) {
|
||||
if (resourceUrl == null) {
|
||||
throw new RuntimeException("resourceUrl is null");
|
||||
}
|
||||
|
||||
InputStream inStream;
|
||||
try {
|
||||
inStream = resourceUrl.openStream();
|
||||
} catch (IOException e) {
|
||||
String message = "Unable to open icon at '" + resourceUrl + "'";
|
||||
SystemTray.logger.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
|
||||
// suck it out of a URL/Resource (with debugging if necessary)
|
||||
String cacheName = resourceUrl.getPath();
|
||||
return makeFileViaStream(cacheName, inStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cacheName needs name+extension for the resource
|
||||
* @param resourceStream the resource to copy to a file on disk
|
||||
*
|
||||
* @return the full path of the resource copied to disk, or null if invalid
|
||||
*/
|
||||
private static
|
||||
String makeFileViaStream(final String cacheName, final InputStream resourceStream) {
|
||||
if (cacheName == null) {
|
||||
throw new RuntimeException("cacheName is null");
|
||||
}
|
||||
if (resourceStream == null) {
|
||||
throw new RuntimeException("resourceStream is null");
|
||||
}
|
||||
|
||||
// figure out the fileName
|
||||
byte[] bytes = cacheName.getBytes(OS.UTF_8);
|
||||
File newFile;
|
||||
|
||||
// can be wimpy, only one at a time
|
||||
String hash = hashName(bytes);
|
||||
|
||||
String extension = getExtension(cacheName);
|
||||
newFile = new File(TEMP_DIR, "SYSTRAY_" + hash + '.' + extension).getAbsoluteFile();
|
||||
if (SystemTray.isKDE) {
|
||||
// KDE is unique per run, so this prevents buildup
|
||||
newFile.deleteOnExit();
|
||||
}
|
||||
|
||||
// copy out to a temp file, as a hash of the file name
|
||||
|
||||
OutputStream outStream = null;
|
||||
try {
|
||||
outStream = new FileOutputStream(newFile);
|
||||
|
||||
byte[] buffer = new byte[2048];
|
||||
int read;
|
||||
while ((read = resourceStream.read(buffer)) > 0) {
|
||||
outStream.write(buffer, 0, read);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Send up exception
|
||||
String message = "Unable to copy icon '" + cacheName + "' to temporary location: '" + newFile.getAbsolutePath() + "'";
|
||||
SystemTray.logger.error(message, e);
|
||||
throw new RuntimeException(message, e);
|
||||
} finally {
|
||||
try {
|
||||
resourceStream.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return newFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
public static
|
||||
String getExtension(final String fileName) {
|
||||
|
||||
String extension = "";
|
||||
int dot = fileName.lastIndexOf('.');
|
||||
if (dot > -1) {
|
||||
extension = fileName.substring(dot + 1);
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
// must be called from synchronized block
|
||||
private static
|
||||
String hashName(byte[] nameChars) {
|
||||
digest.reset();
|
||||
digest.update(nameChars);
|
||||
|
||||
// For KDE4, it must also be unique across runs
|
||||
if (SystemTray.isKDE) {
|
||||
byte[] longBytes = new byte[8];
|
||||
ByteBuffer wrap = ByteBuffer.wrap(longBytes);
|
||||
wrap.putLong(runtimeRandom);
|
||||
digest.update(longBytes);
|
||||
}
|
||||
|
||||
// convert to alpha-numeric. see https://stackoverflow.com/questions/29183818/why-use-tostring32-and-not-tostring36
|
||||
return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ import java.io.FileReader;
|
|||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
@ -40,8 +39,11 @@ import dorkbox.systemTray.linux.GtkSystemTray;
|
|||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.swing.SwingSystemTray;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.systemTray.util.JavaFX;
|
||||
import dorkbox.systemTray.util.Swt;
|
||||
import dorkbox.systemTray.util.WindowsSystemTraySwing;
|
||||
import dorkbox.util.CacheUtil;
|
||||
import dorkbox.util.OS;
|
||||
import dorkbox.util.Property;
|
||||
import dorkbox.util.process.ShellProcessBuilder;
|
||||
|
@ -50,22 +52,49 @@ import dorkbox.util.process.ShellProcessBuilder;
|
|||
/**
|
||||
* Factory and base-class for system tray implementations.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "Duplicates"})
|
||||
@SuppressWarnings({"unused", "Duplicates", "DanglingJavadoc", "WeakerAccess"})
|
||||
public abstract
|
||||
class SystemTray {
|
||||
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
|
||||
|
||||
public static final int LINUX_GTK = 1;
|
||||
public static final int LINUX_APP_INDICATOR = 2;
|
||||
public static final int SWING_INDICATOR = 3;
|
||||
public static final int TYPE_AUTO_DETECT = 0;
|
||||
public static final int TYPE_GTKSTATUSICON = 1;
|
||||
public static final int TYPE_APP_INDICATOR = 2;
|
||||
public static final int TYPE_SWING = 3;
|
||||
|
||||
@Property
|
||||
/** How long to wait when updating menu entries before the request times-out */
|
||||
public static final int TIMEOUT = 2;
|
||||
|
||||
@Property
|
||||
/** Size of the tray, so that the icon can properly scale based on OS. (if it's not exact) */
|
||||
public static int TRAY_SIZE = 22;
|
||||
/** Enables auto-detection for the system tray. This should be mostly successful.
|
||||
* <p>
|
||||
* Auto-detection will use DEFAULT_WINDOWS_SIZE or DEFAULT_LINUX_SIZE as a 'base-line' for determining what size to use. On Linux,
|
||||
* `gsettings get org.gnome.desktop.interface scaling-factor` is used to determine the scale factor (for HiDPI configurations).
|
||||
* <p>
|
||||
* If auto-detection fails and the incorrect size is detected or used, disable this and specify the correct DEFAULT_WINDOWS_SIZE or
|
||||
* DEFAULT_LINUX_SIZE to use them instead
|
||||
*/
|
||||
public static boolean AUTO_TRAY_SIZE = true;
|
||||
|
||||
@Property
|
||||
/**
|
||||
* Size of the tray, so that the icon can be properly scaled based on OS.
|
||||
* - Windows will automatically scale up/down.
|
||||
* <p>
|
||||
* You will experience WEIRD graphical glitches if this is NOT a power of 2.
|
||||
*/
|
||||
public static int DEFAULT_WINDOWS_SIZE = 32;
|
||||
|
||||
@Property
|
||||
/**
|
||||
* Size of the tray, so that the icon can be properly scaled based on OS.
|
||||
* - GtkStatusIcon will usually automatically scale up/down
|
||||
* - AppIndicators will not always automatically scale (it will sometimes display whatever is specified here)
|
||||
* <p>
|
||||
* You will experience WEIRD graphical glitches if this is NOT a power of 2.
|
||||
*/
|
||||
public static int DEFAULT_LINUX_SIZE = 16;
|
||||
|
||||
@Property
|
||||
/** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */
|
||||
|
@ -73,7 +102,7 @@ class SystemTray {
|
|||
|
||||
@Property
|
||||
/** Forces the system tray detection to be Automatic (0), GTK (1), AppIndicator (2), or Swing (3). This is an advanced feature. */
|
||||
public static int FORCE_LINUX_TYPE = 0;
|
||||
public static int FORCE_TRAY_TYPE = 1;
|
||||
|
||||
@Property
|
||||
/**
|
||||
|
@ -90,7 +119,6 @@ class SystemTray {
|
|||
|
||||
|
||||
private static volatile SystemTray systemTray = null;
|
||||
static boolean isKDE = false;
|
||||
|
||||
public final static boolean isJavaFxLoaded;
|
||||
public final static boolean isSwtLoaded;
|
||||
|
@ -135,6 +163,7 @@ class SystemTray {
|
|||
|
||||
Class<? extends SystemTray> trayType = null;
|
||||
|
||||
boolean isKDE = false;
|
||||
|
||||
if (DEBUG) {
|
||||
logger.debug("is JavaFX detected? {}", isJavaFxLoaded);
|
||||
|
@ -142,16 +171,18 @@ class SystemTray {
|
|||
}
|
||||
|
||||
// kablooie if SWT is not configured in a way that works with us.
|
||||
if (FORCE_LINUX_TYPE != SWING_INDICATOR && OS.isLinux()) {
|
||||
if (FORCE_TRAY_TYPE != TYPE_SWING && OS.isLinux()) {
|
||||
if (isSwtLoaded) {
|
||||
// Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
|
||||
// System.setProperty("SWT_GTK3", "0");
|
||||
|
||||
// was SWT forced?
|
||||
boolean isSwt_GTK3 = !System.getProperty("SWT_GTK3").equals("0");
|
||||
String swt_gtk3 = System.getProperty("SWT_GTK3");
|
||||
boolean isSwt_GTK3 = swt_gtk3 != null && !swt_gtk3.equals("0");
|
||||
if (!isSwt_GTK3) {
|
||||
// check a different property
|
||||
isSwt_GTK3 = !System.getProperty("org.eclipse.swt.internal.gtk.version").startsWith("2.");
|
||||
String property = System.getProperty("org.eclipse.swt.internal.gtk.version");
|
||||
isSwt_GTK3 = property != null && !property.startsWith("2.");
|
||||
}
|
||||
|
||||
if (isSwt_GTK3 && FORCE_GTK2) {
|
||||
|
@ -200,7 +231,7 @@ class SystemTray {
|
|||
}
|
||||
|
||||
if (DEBUG) {
|
||||
switch (FORCE_LINUX_TYPE) {
|
||||
switch (FORCE_TRAY_TYPE) {
|
||||
case 1: logger.debug("Forced tray type: GtkStatusIcon"); break;
|
||||
case 2: logger.debug("Forced tray type: AppIndicator"); break;
|
||||
case 3: logger.debug("Forced tray type: Swing"); break;
|
||||
|
@ -210,17 +241,11 @@ class SystemTray {
|
|||
logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
|
||||
}
|
||||
|
||||
|
||||
// Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on
|
||||
// mouseover or something, but I don't know how to do that. It seems that tooltips for app-indicators are a custom job, as
|
||||
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all.
|
||||
|
||||
if (OS.isWindows()) {
|
||||
// the tray icon size in windows is DIFFERENT than on Mac (TODO: test on mac with retina stuff. Also check HiDpi setups).
|
||||
TRAY_SIZE -= 4;
|
||||
}
|
||||
|
||||
if (FORCE_LINUX_TYPE != SWING_INDICATOR && OS.isLinux()) {
|
||||
if (FORCE_TRAY_TYPE != TYPE_SWING && OS.isLinux()) {
|
||||
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
|
||||
|
||||
// For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python.
|
||||
|
@ -236,7 +261,7 @@ class SystemTray {
|
|||
}
|
||||
}
|
||||
|
||||
if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_GTK) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTKSTATUSICON) {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
} catch (Throwable e1) {
|
||||
|
@ -245,7 +270,7 @@ class SystemTray {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_APP_INDICATOR) {
|
||||
else if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) {
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
} catch (Throwable e1) {
|
||||
|
@ -261,6 +286,7 @@ class SystemTray {
|
|||
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
|
||||
String XDG = System.getenv("XDG_CURRENT_DESKTOP");
|
||||
|
||||
|
||||
// BLEH. if gnome-shell is running, IT'S REALLY GNOME!
|
||||
// we must ALWAYS do this check!!
|
||||
boolean isReallyGnome = false;
|
||||
|
@ -306,14 +332,54 @@ class SystemTray {
|
|||
}
|
||||
}
|
||||
else if ("xfce".equalsIgnoreCase(XDG)) {
|
||||
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
||||
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
|
||||
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
||||
|
||||
// XFCE4 is OK to use appindicator, <XFCE4 we use GTKStatusIcon. God i wish there was an easy way to do this.
|
||||
boolean isNewXFCE = false;
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||
|
||||
// ps aux | grep [x]fce
|
||||
final ShellProcessBuilder shell = new ShellProcessBuilder(outputStream);
|
||||
shell.setExecutable("ps");
|
||||
shell.addArgument("aux");
|
||||
shell.start();
|
||||
|
||||
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||
// should last us the next 20 years or so. XFCE development is glacially slow.
|
||||
isNewXFCE = output.contains("/xfce4/") || output.contains("/xfce5/") ||
|
||||
output.contains("/xfce6/") || output.contains("/xfce7/");
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize AppIndicatorTray", e);
|
||||
logger.error("Cannot detect what version of XFCE is running", e);
|
||||
}
|
||||
}
|
||||
|
||||
// we can fail on AppIndicator, so this is the fallback
|
||||
if (DEBUG) {
|
||||
logger.error("Is 'new' version of XFCE? {}", isNewXFCE);
|
||||
}
|
||||
|
||||
if (isNewXFCE) {
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize AppIndicatorTray", e);
|
||||
}
|
||||
|
||||
// we can fail on AppIndicator, so this is the fallback
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
} catch (Throwable e1) {
|
||||
|
@ -497,6 +563,13 @@ class SystemTray {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// this has to happen BEFORE any sort of swing system tray stuff is accessed
|
||||
if (OS.isWindows()) {
|
||||
// windows is funky, and is hardcoded to 16x16. We fix that.
|
||||
WindowsSystemTraySwing.fix();
|
||||
}
|
||||
|
||||
// this is windows OR mac
|
||||
if (trayType == null && java.awt.SystemTray.isSupported()) {
|
||||
try {
|
||||
|
@ -519,9 +592,16 @@ class SystemTray {
|
|||
else {
|
||||
SystemTray systemTray_ = null;
|
||||
|
||||
try {
|
||||
ImageUtil.init();
|
||||
/*
|
||||
* appIndicator/gtk require strings (which is the path)
|
||||
* swing version loads as an image (which can be stream or path, we use path)
|
||||
*
|
||||
* For KDE4, it must also be unique across runs
|
||||
*/
|
||||
CacheUtil.setUniqueCachePerRun = isKDE;
|
||||
CacheUtil.tempDir = "SysTray";
|
||||
|
||||
try {
|
||||
if (OS.isLinux() &&
|
||||
trayType == AppIndicatorTray.class &&
|
||||
Gtk.isGtk2 &&
|
||||
|
@ -545,8 +625,6 @@ class SystemTray {
|
|||
systemTray_ = (SystemTray) trayType.getConstructors()[0].newInstance();
|
||||
|
||||
logger.info("Successfully Loaded: {}", trayType.getSimpleName());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("Unsupported hashing algorithm!");
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
|
||||
}
|
||||
|
@ -653,7 +731,7 @@ class SystemTray {
|
|||
void setStatus(String statusText);
|
||||
|
||||
protected abstract
|
||||
void setIcon_(String iconPath);
|
||||
void setIcon_(File iconPath);
|
||||
|
||||
/**
|
||||
* Changes the tray icon used.
|
||||
|
@ -665,8 +743,7 @@ class SystemTray {
|
|||
*/
|
||||
public
|
||||
void setIcon(String imagePath) {
|
||||
final String fullPath = ImageUtil.iconPath(imagePath);
|
||||
setIcon_(fullPath);
|
||||
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -679,8 +756,7 @@ class SystemTray {
|
|||
*/
|
||||
public
|
||||
void setIcon(URL imageUrl) {
|
||||
final String fullPath = ImageUtil.iconPath(imageUrl);
|
||||
setIcon_(fullPath);
|
||||
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -694,8 +770,7 @@ class SystemTray {
|
|||
*/
|
||||
public
|
||||
void setIcon(String cacheName, InputStream imageStream) {
|
||||
final String fullPath = ImageUtil.iconPath(cacheName, imageStream);
|
||||
setIcon_(fullPath);
|
||||
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -704,17 +779,11 @@ class SystemTray {
|
|||
* Because the cross-platform, underlying system uses a file path to load icons for the system tray, this will copy the contents of
|
||||
* the imageStream to a temporary location on disk.
|
||||
*
|
||||
* This method **DOES NOT CACHE** the result, so multiple lookups for the same inputStream result in new files every time. This is
|
||||
* also NOT RECOMMENDED, but is provided for simplicity.
|
||||
*
|
||||
* @param imageStream the InputStream of the icon to use
|
||||
*/
|
||||
@Deprecated
|
||||
public
|
||||
void setIcon(InputStream imageStream) {
|
||||
@SuppressWarnings("deprecation")
|
||||
final String fullPath = ImageUtil.iconPathNoCache(imageStream);
|
||||
setIcon_(fullPath);
|
||||
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageStream));
|
||||
}
|
||||
|
||||
|
||||
|
@ -764,9 +833,6 @@ class SystemTray {
|
|||
/**
|
||||
* Adds a menu entry to the tray icon with text + image
|
||||
*
|
||||
* This method **DOES NOT CACHE** the result, so multiple lookups for the same inputStream result in new files every time. This is
|
||||
* also NOT RECOMMENDED, but is provided for simplicity.
|
||||
*
|
||||
* @param menuText string of the text you want to appear
|
||||
* @param imageStream the InputStream of the image to use. If null, no image will be used
|
||||
* @param callback callback that will be executed when this menu entry is clicked
|
||||
|
@ -783,7 +849,7 @@ class SystemTray {
|
|||
* @param newMenuText the new menu text (this will replace the original menu text)
|
||||
*/
|
||||
public final
|
||||
void updateMenuEntry_Text(final String origMenuText, final String newMenuText) {
|
||||
void updateMenuEntry(final String origMenuText, final String newMenuText) {
|
||||
// have to wait for the value
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||
|
@ -822,13 +888,13 @@ class SystemTray {
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates (or changes) the menu entry's text.
|
||||
* Updates (or changes) the menu entry's image (as a String).
|
||||
*
|
||||
* @param origMenuText the original menu text
|
||||
* @param imagePath the new path for the image to use or null to delete the image
|
||||
*/
|
||||
public final
|
||||
void updateMenuEntry_Image(final String origMenuText, final String imagePath) {
|
||||
void updateMenuEntry_AsImage(final String origMenuText, final String imagePath) {
|
||||
// have to wait for the value
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||
|
@ -873,7 +939,7 @@ class SystemTray {
|
|||
* @param imageUrl the new URL for the image to use or null to delete the image
|
||||
*/
|
||||
public final
|
||||
void updateMenuEntry_Image(final String origMenuText, final URL imageUrl) {
|
||||
void updateMenuEntry(final String origMenuText, final URL imageUrl) {
|
||||
// have to wait for the value
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||
|
@ -919,7 +985,7 @@ class SystemTray {
|
|||
* @param imageStream the InputStream of the image to use or null to delete the image
|
||||
*/
|
||||
public final
|
||||
void updateMenuEntry_Image(final String origMenuText, final String cacheName, final InputStream imageStream) {
|
||||
void updateMenuEntry(final String origMenuText, final String cacheName, final InputStream imageStream) {
|
||||
// have to wait for the value
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||
|
@ -960,14 +1026,11 @@ class SystemTray {
|
|||
/**
|
||||
* Updates (or changes) the menu entry's text.
|
||||
*
|
||||
* This method **DOES NOT CACHE** the result, so multiple lookups for the same inputStream result in new files every time. This is
|
||||
* also NOT RECOMMENDED, but is provided for simplicity.
|
||||
*
|
||||
* @param origMenuText the original menu text
|
||||
* @param imageStream the new path for the image to use or null to delete the image
|
||||
*/
|
||||
public final
|
||||
void updateMenuEntry_Image(final String origMenuText, final InputStream imageStream) {
|
||||
void updateMenuEntry(final String origMenuText, final InputStream imageStream) {
|
||||
// have to wait for the value
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||
|
@ -1013,7 +1076,7 @@ class SystemTray {
|
|||
* @param newCallback the new callback (this will replace the original callback)
|
||||
*/
|
||||
public final
|
||||
void updateMenuEntry_Callback(final String origMenuText, final SystemTrayMenuAction newCallback) {
|
||||
void updateMenuEntry(final String origMenuText, final SystemTrayMenuAction newCallback) {
|
||||
// have to wait for the value
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||
|
|
|
@ -15,14 +15,17 @@
|
|||
*/
|
||||
package dorkbox.systemTray.linux;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions.
|
||||
|
@ -42,9 +45,9 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
|||
|
||||
public
|
||||
AppIndicatorTray() {
|
||||
if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_GTK) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTKSTATUSICON) {
|
||||
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||
throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_LINUX_TYPE' is set to GTK");
|
||||
throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_TRAY_TYPE' is set to GTK");
|
||||
}
|
||||
|
||||
Gtk.startGui();
|
||||
|
@ -56,6 +59,10 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
|||
appIndicator = AppIndicator.app_indicator_new(System.nanoTime() + "DBST", "", AppIndicator.CATEGORY_APPLICATION_STATUS);
|
||||
}
|
||||
});
|
||||
|
||||
super.waitForStartup();
|
||||
|
||||
ImageUtils.determineIconSize(SystemTray.TYPE_APP_INDICATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,12 +88,12 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
|||
|
||||
@Override
|
||||
protected
|
||||
void setIcon_(final String iconPath) {
|
||||
void setIcon_(final File iconFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
AppIndicator.app_indicator_set_icon(appIndicator, iconPath);
|
||||
AppIndicator.app_indicator_set_icon(appIndicator, iconFile.getAbsolutePath());
|
||||
|
||||
if (!isActive) {
|
||||
isActive = true;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package dorkbox.systemTray.linux;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -22,12 +23,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.ImageUtil;
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.linux.jna.GCallback;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
class GtkMenuEntry implements MenuEntry, GCallback {
|
||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
||||
|
@ -48,18 +49,18 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkMenuEntry(final String label, final String imagePath, final SystemTrayMenuAction callback, final GtkTypeSystemTray parent) {
|
||||
GtkMenuEntry(final String label, final File imagePath, final SystemTrayMenuAction callback, final GtkTypeSystemTray parent) {
|
||||
this.parent = parent;
|
||||
this.text = label;
|
||||
this.callback = callback;
|
||||
|
||||
menuItem = Gtk.gtk_image_menu_item_new_with_label(label);
|
||||
|
||||
if (imagePath != null && !imagePath.isEmpty()) {
|
||||
// NOTE: XFCE uses appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
||||
if (imagePath != null) {
|
||||
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
||||
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
|
||||
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
||||
image = Gtk.gtk_image_new_from_file(imagePath);
|
||||
image = Gtk.gtk_image_new_from_file(imagePath.getAbsolutePath());
|
||||
|
||||
Gtk.gtk_image_menu_item_set_image(menuItem, image);
|
||||
// must always re-set always-show after setting the image
|
||||
|
@ -108,7 +109,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
}
|
||||
|
||||
private
|
||||
void setImage_(final String imagePath) {
|
||||
void setImage_(final File imagePath) {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
|
@ -120,8 +121,8 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
|
||||
Gtk.gtk_widget_show_all(menuItem);
|
||||
|
||||
if (imagePath != null && !imagePath.isEmpty()) {
|
||||
image = Gtk.gtk_image_new_from_file(imagePath);
|
||||
if (imagePath != null) {
|
||||
image = Gtk.gtk_image_new_from_file(imagePath.getAbsolutePath());
|
||||
Gtk.gtk_image_menu_item_set_image(menuItem, image);
|
||||
Gobject.g_object_ref_sink(image);
|
||||
|
||||
|
@ -141,7 +142,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPath(imagePath));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +153,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPath(imageUrl));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,7 +164,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPath(cacheName, imageStream));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +176,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPathNoCache(imageStream));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,10 +15,9 @@
|
|||
*/
|
||||
package dorkbox.systemTray.linux;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.sun.jna.NativeLong;
|
||||
|
@ -29,7 +28,7 @@ import dorkbox.systemTray.linux.jna.GEventCallback;
|
|||
import dorkbox.systemTray.linux.jna.GdkEventButton;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.JavaFX;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions via GTK.
|
||||
|
@ -56,37 +55,12 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
|||
super();
|
||||
Gtk.startGui();
|
||||
|
||||
final CountDownLatch blockUntilStarted = new CountDownLatch(1);
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
final Pointer trayIcon_ = Gtk.gtk_status_icon_new();
|
||||
Gtk.gtk_status_icon_set_visible(trayIcon_, false); // immediately set false visibility
|
||||
trayIcon = trayIcon_;
|
||||
}
|
||||
});
|
||||
|
||||
// we have to be able to set our name/title, otherwise the gnome-shell extension WILL NOT work
|
||||
// prevent these from happening...
|
||||
// Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
|
||||
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
|
||||
// If you change "SystemTray" to something else, make sure to change it in extension.js as well
|
||||
|
||||
// necessary for gnome icon detection/placement because we move tray icons around by name. The name is hardcoded
|
||||
// in extension.js, so don't change it
|
||||
Gtk.gtk_status_icon_set_title(trayIcon, "SystemTray");
|
||||
|
||||
// ALSO necessary to make sure our gnome-shell extension has the correct name/title! (sometimes it does not)
|
||||
// can cause
|
||||
// Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
|
||||
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
|
||||
Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
|
||||
|
||||
final GEventCallback gtkCallback = new GEventCallback() {
|
||||
@Override
|
||||
|
@ -104,37 +78,31 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
|||
// have to do this to prevent GC on these objects
|
||||
gtkCallbacks.add(gtkCallback);
|
||||
gtkCallbacks.add(button_press_event);
|
||||
|
||||
blockUntilStarted.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
super.waitForStartup();
|
||||
|
||||
if (SystemTray.isJavaFxLoaded) {
|
||||
if (!JavaFX.isEventThread()) {
|
||||
try {
|
||||
blockUntilStarted.await(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ImageUtils.determineIconSize(SystemTray.TYPE_GTKSTATUSICON);
|
||||
|
||||
// we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
|
||||
// If you change "SystemTray" to something else, make sure to change it in extension.js as well
|
||||
|
||||
// necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded
|
||||
// in extension.js, so don't change it
|
||||
Gtk.gtk_status_icon_set_title(trayIcon, "SystemTray");
|
||||
|
||||
// can cause
|
||||
// Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
|
||||
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
|
||||
// Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
|
||||
}
|
||||
} else if (SystemTray.isSwtLoaded) {
|
||||
if (SystemTray.FORCE_LINUX_TYPE != SystemTray.LINUX_GTK) {
|
||||
// GTK system tray has threading issues if we block here (because it is likely in the event thread)
|
||||
// AppIndicator version doesn't have this problem
|
||||
try {
|
||||
blockUntilStarted.await(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
blockUntilStarted.await(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -163,12 +131,12 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
|||
|
||||
@Override
|
||||
protected
|
||||
void setIcon_(final String iconPath) {
|
||||
void setIcon_(final File iconFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Gtk.gtk_status_icon_set_from_file(trayIcon, iconPath);
|
||||
Gtk.gtk_status_icon_set_from_file(trayIcon, iconFile.getAbsolutePath());
|
||||
|
||||
if (!isActive) {
|
||||
isActive = true;
|
||||
|
|
|
@ -16,16 +16,20 @@
|
|||
|
||||
package dorkbox.systemTray.linux;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.ImageUtil;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.systemTray.util.JavaFX;
|
||||
|
||||
/**
|
||||
* Derived from
|
||||
|
@ -44,6 +48,45 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
Gtk.dispatch(runnable);
|
||||
}
|
||||
|
||||
protected
|
||||
void waitForStartup() {
|
||||
final CountDownLatch blockUntilStarted = new CountDownLatch(1);
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
blockUntilStarted.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
if (SystemTray.isJavaFxLoaded) {
|
||||
if (!JavaFX.isEventThread()) {
|
||||
try {
|
||||
blockUntilStarted.await(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else if (SystemTray.isSwtLoaded) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE != SystemTray.TYPE_GTKSTATUSICON) {
|
||||
// GTK system tray has threading issues if we block here (because it is likely in the event thread)
|
||||
// AppIndicator version doesn't have this problem
|
||||
try {
|
||||
blockUntilStarted.await(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
blockUntilStarted.await(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void shutdown() {
|
||||
|
@ -215,7 +258,7 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
}
|
||||
|
||||
private
|
||||
void addMenuEntry_(final String menuText, final String imagePath, final SystemTrayMenuAction callback) {
|
||||
void addMenuEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction callback) {
|
||||
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
|
||||
// see: https://bugs.launchpad.net/glipper/+bug/1203888
|
||||
|
||||
|
@ -252,7 +295,7 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPath(imagePath), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath), callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,7 +306,7 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPath(imageUrl), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl), callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,7 +317,7 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPath(cacheName, imageStream), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream), callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,7 +329,7 @@ class GtkTypeSystemTray extends SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPathNoCache(imageStream), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, imageStream), callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class AppIndicator {
|
|||
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3.
|
||||
// appindicator3 doesn't support menu icons via GTK2!!
|
||||
|
||||
if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_GTK) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTKSTATUSICON) {
|
||||
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||
if (LIBRARY_DEBUG) {
|
||||
logger.error("Forcing GTK tray, not using appindicator");
|
||||
|
|
|
@ -69,7 +69,7 @@ class Gtk {
|
|||
String gtk3LibName = "libgtk-3.so.0";
|
||||
|
||||
// we can force the system to use the swing indicator, which WORKS, but doesn't support transparency in the icon.
|
||||
if (SystemTray.FORCE_LINUX_TYPE == SystemTray.SWING_INDICATOR) {
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_SWING) {
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,31 +16,22 @@
|
|||
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
import dorkbox.systemTray.ImageUtil;
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.UIManager;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
class SwingMenuEntry implements MenuEntry {
|
||||
private static final String tempDirPath = ImageUtil.TEMP_DIR.getAbsolutePath();
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
class SwingMenuEntry implements MenuEntry {
|
||||
private final SwingSystemTrayMenuPopup parent;
|
||||
private final SystemTray systemTray;
|
||||
private final JMenuItem menuItem;
|
||||
|
@ -49,12 +40,8 @@ class SwingMenuEntry implements MenuEntry {
|
|||
private volatile String text;
|
||||
private volatile SystemTrayMenuAction callback;
|
||||
|
||||
private int iconHeight = -1;
|
||||
|
||||
|
||||
|
||||
|
||||
SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
|
||||
// this is ALWAYS called on the EDT.
|
||||
SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final File imagePath, final SystemTrayMenuAction callback,
|
||||
final SystemTray systemTray) {
|
||||
this.parent = parentMenu;
|
||||
this.text = label;
|
||||
|
@ -73,7 +60,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
menuItem = new JMenuItem(label);
|
||||
menuItem.addActionListener(swingCallback);
|
||||
|
||||
if (imagePath != null && !imagePath.isEmpty()) {
|
||||
if (imagePath != null) {
|
||||
setImageIcon(imagePath);
|
||||
}
|
||||
|
||||
|
@ -109,7 +96,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
}
|
||||
|
||||
private
|
||||
void setImage_(final String imagePath) {
|
||||
void setImage_(final File imagePath) {
|
||||
SwingUtil.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
|
@ -119,58 +106,11 @@ class SwingMenuEntry implements MenuEntry {
|
|||
});
|
||||
}
|
||||
|
||||
// always called on the EDT
|
||||
private
|
||||
void setImageIcon(final String imagePath) {
|
||||
if (imagePath != null && !imagePath.isEmpty()) {
|
||||
|
||||
if (iconHeight != 0) {
|
||||
// this will (and should) be the correct size for the system. On the systems tested, it was 16
|
||||
// see: http://en-human-begin.blogspot.de/2007/11/javas-icons-by-default.html
|
||||
Icon icon = UIManager.getIcon("FileView.fileIcon");
|
||||
iconHeight = icon.getIconHeight();
|
||||
}
|
||||
|
||||
ImageIcon origIcon = new ImageIcon(imagePath);
|
||||
int origIconHeight = origIcon.getIconHeight();
|
||||
int origIconWidth = origIcon.getIconWidth();
|
||||
|
||||
int savedIconHeight = this.iconHeight;
|
||||
|
||||
// it is necessary to resize this icon, so that it matches what our preferred size is for icons
|
||||
if (origIconHeight != savedIconHeight && savedIconHeight != 0) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
Dimension scaledDimension = getScaledDimension(origIconWidth, origIconHeight, savedIconHeight, savedIconHeight);
|
||||
|
||||
Image image = origIcon.getImage();
|
||||
|
||||
// scale it the smoothly
|
||||
Image newImage = image.getScaledInstance(scaledDimension.width, scaledDimension.height, java.awt.Image.SCALE_SMOOTH);
|
||||
origIcon = new ImageIcon(newImage);
|
||||
|
||||
// save it to temp spot on disk (so we don't have to KEEP on doing this). (but it MUST be the temp location, otherwise
|
||||
// it's always 'on the fly')
|
||||
if (imagePath.startsWith(tempDirPath)) {
|
||||
// have to delete the old one
|
||||
File file = new File(imagePath);
|
||||
boolean delete = file.delete();
|
||||
|
||||
if (delete) {
|
||||
// now write out the new one
|
||||
String extension = ImageUtil.getExtension(imagePath);
|
||||
if (extension.equals("")) {
|
||||
extension = "png"; // made up
|
||||
}
|
||||
BufferedImage bufferedImage = getBufferedImage(image);
|
||||
try {
|
||||
ImageIO.write(bufferedImage, extension, file);
|
||||
} catch (IOException e) {
|
||||
// this shouldn't happen, but you never know...
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setImageIcon(final File imagePath) {
|
||||
if (imagePath != null) {
|
||||
ImageIcon origIcon = new ImageIcon(imagePath.getAbsolutePath());
|
||||
menuItem.setIcon(origIcon);
|
||||
}
|
||||
else {
|
||||
|
@ -185,7 +125,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPath(imagePath));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +136,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPath(imageUrl));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,7 +147,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPath(cacheName, imageStream));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,7 +159,7 @@ class SwingMenuEntry implements MenuEntry {
|
|||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtil.iconPathNoCache(imageStream));
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,48 +181,4 @@ class SwingMenuEntry implements MenuEntry {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static
|
||||
Dimension getScaledDimension(int originalWidth, int originalHeight, int boundWidth, int boundHeight) {
|
||||
//this function comes from http://stackoverflow.com/questions/10245220/java-image-resize-maintain-aspect-ratio
|
||||
|
||||
int newWidth = originalWidth;
|
||||
int newHeight = originalHeight;
|
||||
|
||||
// first check if we need to scale width
|
||||
if (originalWidth > boundWidth) {
|
||||
//scale width to fit
|
||||
newWidth = boundWidth;
|
||||
|
||||
//scale height to maintain aspect ratio
|
||||
newHeight = (newWidth * originalHeight) / originalWidth;
|
||||
}
|
||||
|
||||
// then check if we need to scale even with the new height
|
||||
if (newHeight > boundHeight) {
|
||||
//scale height to fit instead
|
||||
newHeight = boundHeight;
|
||||
|
||||
//scale width to maintain aspect ratio
|
||||
newWidth = (newHeight * originalWidth) / originalHeight;
|
||||
}
|
||||
|
||||
return new Dimension(newWidth, newHeight);
|
||||
}
|
||||
|
||||
private static
|
||||
BufferedImage getBufferedImage(Image image) {
|
||||
if (image instanceof BufferedImage) {
|
||||
return (BufferedImage) image;
|
||||
}
|
||||
|
||||
BufferedImage bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D bGr = bimage.createGraphics();
|
||||
bGr.drawImage(image, 0, 0, null);
|
||||
bGr.dispose();
|
||||
|
||||
// Return the buffered image
|
||||
return bimage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,16 @@ import java.awt.SystemTray;
|
|||
import java.awt.TrayIcon;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.ImageUtil;
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.util.ScreenUtil;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
|
@ -45,6 +46,7 @@ import dorkbox.util.SwingUtil;
|
|||
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6453521
|
||||
* https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028
|
||||
*/
|
||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||
public
|
||||
class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
||||
volatile SwingSystemTrayMenuPopup menu;
|
||||
|
@ -63,6 +65,9 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
public
|
||||
SwingSystemTray() {
|
||||
super();
|
||||
|
||||
ImageUtils.determineIconSize(dorkbox.systemTray.SystemTray.TYPE_SWING);
|
||||
|
||||
SwingUtil.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
|
@ -141,21 +146,23 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
|
||||
@Override
|
||||
protected
|
||||
void setIcon_(final String iconPath) {
|
||||
void setIcon_(final File iconFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
SwingSystemTray tray = SwingSystemTray.this;
|
||||
|
||||
// stupid java won't scale it right away, so we have to do this twice to get the correct size
|
||||
final Image trayImage = new ImageIcon(iconFile.getAbsolutePath()).getImage();
|
||||
trayImage.flush();
|
||||
|
||||
synchronized (tray) {
|
||||
if (!isActive) {
|
||||
// here we init. everything
|
||||
isActive = true;
|
||||
|
||||
menu = new SwingSystemTrayMenuPopup();
|
||||
Image trayImage = new ImageIcon(iconPath).getImage()
|
||||
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
|
||||
trayImage.flush();
|
||||
trayIcon = new TrayIcon(trayImage);
|
||||
|
||||
// appindicators don't support this, so we cater to the lowest common denominator
|
||||
|
@ -207,9 +214,6 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
logger.error("TrayIcon could not be added.", e);
|
||||
}
|
||||
} else {
|
||||
Image trayImage = new ImageIcon(iconPath).getImage()
|
||||
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
|
||||
trayImage.flush();
|
||||
tray.trayIcon.setImage(trayImage);
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +225,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
* Will add a new menu entry, or update one if it already exists
|
||||
*/
|
||||
private
|
||||
void addMenuEntry_(final String menuText, final String imagePath, final SystemTrayMenuAction callback) {
|
||||
void addMenuEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction callback) {
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
@ -255,7 +259,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPath(imagePath), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath), callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +270,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPath(imageUrl), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl), callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,7 +281,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPath(cacheName, imageStream), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream), callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,7 +293,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
|||
addMenuEntry_(menuText, null, callback);
|
||||
}
|
||||
else {
|
||||
addMenuEntry_(menuText, ImageUtil.iconPathNoCache(imageStream), callback);
|
||||
addMenuEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.SIZE, imageStream), callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
611
src/dorkbox/systemTray/util/ImageUtils.java
Normal file
611
src/dorkbox/systemTray/util/ImageUtils.java
Normal file
|
@ -0,0 +1,611 @@
|
|||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.util;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.URL;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.util.CacheUtil;
|
||||
import dorkbox.util.FileUtil;
|
||||
import dorkbox.util.LocationResolver;
|
||||
import dorkbox.util.OS;
|
||||
import dorkbox.util.process.ShellProcessBuilder;
|
||||
|
||||
public
|
||||
class ImageUtils {
|
||||
|
||||
private static final File TEMP_DIR = new File(CacheUtil.TEMP_DIR, "ResizedImages");
|
||||
|
||||
// tray/menu-entry size.
|
||||
public static volatile int SIZE = 0;
|
||||
|
||||
/**
|
||||
* @param trayType
|
||||
* LINUX_GTK = 1;
|
||||
* LINUX_APP_INDICATOR = 2;
|
||||
* SWING_INDICATOR = 3;
|
||||
*/
|
||||
public static
|
||||
void determineIconSize(int trayType) {
|
||||
if (SystemTray.AUTO_TRAY_SIZE) {
|
||||
if (OS.isWindows()) {
|
||||
// windows will automatically scale the tray size
|
||||
SIZE = SystemTray.DEFAULT_WINDOWS_SIZE;
|
||||
} else {
|
||||
// GtkStatusIcon will USUALLY automatically scale the icon
|
||||
// AppIndicator will NOT scale the icon
|
||||
if (trayType == SystemTray.TYPE_SWING || trayType == SystemTray.TYPE_GTKSTATUSICON) {
|
||||
// swing or GtkStatusIcon on linux/mac? use the default settings
|
||||
SIZE = SystemTray.DEFAULT_LINUX_SIZE;
|
||||
} else {
|
||||
int uiScalingFactor = 0;
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||
|
||||
// gsettings get org.gnome.desktop.interface scaling-factor
|
||||
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
|
||||
shellVersion.setExecutable("gsettings");
|
||||
shellVersion.addArgument("get");
|
||||
shellVersion.addArgument("org.gnome.desktop.interface");
|
||||
shellVersion.addArgument("scaling-factor");
|
||||
shellVersion.start();
|
||||
|
||||
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||
|
||||
if (!output.isEmpty()) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.info("Checking scaling factor for GTK environment, should start with 'uint32', value: '{}'", output);
|
||||
}
|
||||
|
||||
// DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well.
|
||||
// should be: uint32 0 or something
|
||||
if (output.startsWith("uint32")) {
|
||||
String value = output.substring(output.indexOf(" ")+1, output.length()-1);
|
||||
uiScalingFactor = Integer.parseInt(value);
|
||||
|
||||
// 0 is disabled (no scaling)
|
||||
// 1 is enabled (default scale)
|
||||
// 2 is 2x scale
|
||||
// 3 is 3x scale
|
||||
// etc
|
||||
|
||||
|
||||
// A setting of 2, 3, etc, which is all you can do with scaling-factor
|
||||
// To enable HiDPI, use gsettings:
|
||||
// gsettings set org.gnome.desktop.interface scaling-factor 2
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.error("Cannot check scaling factor", e);
|
||||
}
|
||||
}
|
||||
|
||||
// the DEFAULT scale is 16
|
||||
if (uiScalingFactor > 1) {
|
||||
SIZE = SystemTray.DEFAULT_LINUX_SIZE * uiScalingFactor;
|
||||
} else {
|
||||
SIZE = SystemTray.DEFAULT_LINUX_SIZE;
|
||||
}
|
||||
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.info("uiScaling factor is '{}', auto tray size is '{}'.", uiScalingFactor, SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (OS.isWindows()) {
|
||||
SIZE = SystemTray.DEFAULT_WINDOWS_SIZE;
|
||||
} else {
|
||||
SIZE = SystemTray.DEFAULT_LINUX_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static
|
||||
File getErrorImage(final String cacheName) {
|
||||
try {
|
||||
File save = CacheUtil.save(cacheName, ImageUtils.class.getResource("error_32.png"));
|
||||
// since it's the error file, we want to delete it on exit!
|
||||
save.deleteOnExit();
|
||||
return save;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Serious problems! Unable to extract error image, this should NEVER happen!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static
|
||||
File getIfCachedOrError(final String cacheName) {
|
||||
try {
|
||||
File check = CacheUtil.check(cacheName);
|
||||
if (check != null) {
|
||||
return check;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
SystemTray.logger.error("Error checking cache for information. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static synchronized
|
||||
File resizeAndCache(final int size, final String fileName) {
|
||||
// check if we already have this file information saved to disk, based on size
|
||||
String cacheName = size + "_" + fileName;
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File check = getIfCachedOrError(cacheName);
|
||||
if (check != null) {
|
||||
return check;
|
||||
}
|
||||
|
||||
// no cached file, so we resize then save the new one.
|
||||
String newFileOnDisk;
|
||||
try {
|
||||
newFileOnDisk = resizeFile(size, fileName);
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error resizing image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
|
||||
try {
|
||||
return CacheUtil.save(cacheName, newFileOnDisk);
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error caching image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public static synchronized
|
||||
File resizeAndCache(final int size, final URL imageUrl) {
|
||||
String cacheName = size + "_" + imageUrl.getPath();
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File check = getIfCachedOrError(cacheName);
|
||||
if (check != null) {
|
||||
return check;
|
||||
}
|
||||
|
||||
// no cached file, so we resize then save the new one.
|
||||
boolean needsResize = true;
|
||||
try {
|
||||
InputStream inputStream = imageUrl.openStream();
|
||||
Dimension imageSize = getImageSize(inputStream);
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
if (size == ((int) imageSize.getWidth()) && size == ((int) imageSize.getHeight())) {
|
||||
// we can reuse this URL (it's the correct size).
|
||||
needsResize = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error resizing image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
|
||||
if (needsResize) {
|
||||
// we have to hop through hoops.
|
||||
try {
|
||||
File resizedFile = resizeFileNoCheck(size, imageUrl);
|
||||
|
||||
// now cache that file
|
||||
try {
|
||||
return CacheUtil.save(cacheName, resizedFile);
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error caching image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error resizing image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
|
||||
} else {
|
||||
// no resize necessary, just cache as is.
|
||||
try {
|
||||
return CacheUtil.save(cacheName, imageUrl);
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error caching image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public static synchronized
|
||||
File resizeAndCache(final int size, String cacheName, final InputStream imageStream) {
|
||||
if (cacheName == null) {
|
||||
cacheName = CacheUtil.createNameAsHash(imageStream);
|
||||
}
|
||||
|
||||
// check if we already have this file information saved to disk, based on size
|
||||
cacheName = size + "_" + cacheName;
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
File check = getIfCachedOrError(cacheName);
|
||||
if (check != null) {
|
||||
return check;
|
||||
}
|
||||
|
||||
// no cached file, so we resize then save the new one.
|
||||
boolean needsResize = true;
|
||||
try {
|
||||
Dimension imageSize = getImageSize(imageStream);
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
if (size == ((int) imageSize.getWidth()) && size == ((int) imageSize.getHeight())) {
|
||||
// we can reuse this URL (it's the correct size).
|
||||
needsResize = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error resizing image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
|
||||
if (needsResize) {
|
||||
// we have to hop through hoops.
|
||||
try {
|
||||
File resizedFile = resizeFileNoCheck(size, imageStream);
|
||||
|
||||
// now cache that file
|
||||
try {
|
||||
return CacheUtil.save(cacheName, resizedFile);
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error caching image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error resizing image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
|
||||
} else {
|
||||
// no resize necessary, just cache as is.
|
||||
try {
|
||||
return CacheUtil.save(cacheName, imageStream);
|
||||
} catch (IOException e) {
|
||||
// have to serve up the error image instead.
|
||||
SystemTray.logger.error("Error caching image. Using error icon instead", e);
|
||||
return getErrorImage(cacheName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
File resizeAndCache(final int size, final InputStream imageStream) {
|
||||
return resizeAndCache(size, null, imageStream);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// static void asdasd () {
|
||||
//
|
||||
// ImageUtils.resizeAndCache(imagePath);
|
||||
//
|
||||
// Image trayImage1 = new ImageIcon(iconPath).getImage().getScaledInstance(dorkbox.systemTray.SystemTray.TRAY_SIZE, -1,
|
||||
// Image.SCALE_SMOOTH);
|
||||
// trayImage1.flush();
|
||||
//
|
||||
//
|
||||
// Dimension imageSize = null;
|
||||
// try {
|
||||
// imageSize = ImageUtils.getImageSize(imagePath);
|
||||
// } catch (IOException e) {
|
||||
// SystemTray.logger.error("Unable to get the image size for '{}'. Unable to set image for menu entry.", imagePath, e);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// int origIconHeight = (int) imageSize.getHeight();
|
||||
// int origIconWidth = (int) imageSize.getWidth();
|
||||
//
|
||||
// int savedIconHeight = this.iconHeight;
|
||||
//
|
||||
// // it is necessary to resize this icon, so that it matches what our preferred size is for icons
|
||||
// if (origIconHeight != savedIconHeight && savedIconHeight != 0) {
|
||||
// //noinspection SuspiciousNameCombination
|
||||
// Dimension newDimension = getScaledDimension(origIconWidth, origIconHeight, savedIconHeight, savedIconHeight);
|
||||
//
|
||||
// ImageIcon origIcon = new ImageIcon(imagePath);
|
||||
// Image image = origIcon.getImage();
|
||||
//
|
||||
// // scale it the smoothly
|
||||
// Image newImage = image.getScaledInstance(newDimension.width, newDimension.height, java.awt.Image.SCALE_SMOOTH);
|
||||
// origIcon = new ImageIcon(newImage);
|
||||
//
|
||||
// // save it to temp spot on disk (so we don't have to KEEP on doing this). (but it MUST be the temp location, otherwise
|
||||
// // it's always 'on the fly')
|
||||
// if (imagePath.startsWith(tempDirPath)) {
|
||||
// // have to delete the old one
|
||||
// File file = new File(imagePath);
|
||||
// boolean delete = file.delete();
|
||||
//
|
||||
// if (delete) {
|
||||
// // now write out the new one
|
||||
// String extension = CacheUtil.getExtension(imagePath);
|
||||
// if (extension.equals("")) {
|
||||
// extension = "png"; // made up
|
||||
// }
|
||||
// BufferedImage bufferedImage = getBufferedImage(image);
|
||||
// try {
|
||||
// ImageIO.write(bufferedImage, extension, file);
|
||||
// } catch (IOException e) {
|
||||
// // this shouldn't happen, but you never know...
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// private static
|
||||
// Dimension getScaledDimension(int originalWidth, int originalHeight, int boundWidth, int boundHeight) {
|
||||
// //this function comes from http://stackoverflow.com/questions/10245220/java-image-resize-maintain-aspect-ratio
|
||||
//
|
||||
// int newWidth = originalWidth;
|
||||
// int newHeight = originalHeight;
|
||||
//
|
||||
// // first check if we need to scale width
|
||||
// if (originalWidth > boundWidth) {
|
||||
// //scale width to fit
|
||||
// newWidth = boundWidth;
|
||||
//
|
||||
// //scale height to maintain aspect ratio
|
||||
// newHeight = (newWidth * originalHeight) / originalWidth;
|
||||
// }
|
||||
//
|
||||
// // then check if we need to scale even with the new height
|
||||
// if (newHeight > boundHeight) {
|
||||
// //scale height to fit instead
|
||||
// newHeight = boundHeight;
|
||||
//
|
||||
// //scale width to maintain aspect ratio
|
||||
// newWidth = (newHeight * originalWidth) / originalHeight;
|
||||
// }
|
||||
//
|
||||
// return new Dimension(newWidth, newHeight);
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Resizes the given URL to the specified size. No checks are performed if it's the correct size to begin with.
|
||||
*
|
||||
* @return the file on disk that is the resized icon
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private static
|
||||
File resizeFileNoCheck(final int size, final URL fileUrl) throws IOException {
|
||||
InputStream inputStream = fileUrl.openStream();
|
||||
|
||||
// have to resize the file (and return the new path)
|
||||
|
||||
// now have to resize this file.
|
||||
File newFile = new File(TEMP_DIR, "temp_resize").getAbsoluteFile();
|
||||
Image image;
|
||||
|
||||
|
||||
// resize the image, keep aspect
|
||||
image = new ImageIcon(ImageIO.read(inputStream)).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
|
||||
image.flush();
|
||||
|
||||
// have to do this twice, so that it will finish loading the image (weird callback stuff is required if we don't do this)
|
||||
image = new ImageIcon(image).getImage();
|
||||
image.flush();
|
||||
|
||||
// make whatever dirs we need to.
|
||||
newFile.getParentFile().mkdirs();
|
||||
|
||||
// if it's already there, we have to delete it
|
||||
newFile.delete();
|
||||
|
||||
|
||||
// now write out the new one
|
||||
String extension = FileUtil.getExtension(fileUrl.getPath());
|
||||
if (extension.equals("")) {
|
||||
extension = "png"; // made up
|
||||
}
|
||||
BufferedImage bufferedImage = getBufferedImage(image);
|
||||
ImageIO.write(bufferedImage, extension, newFile);
|
||||
|
||||
return newFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the given URL to the specified size. No checks are performed if it's the correct size to begin with.
|
||||
*
|
||||
* @return the file on disk that is the resized icon
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private static
|
||||
File resizeFileNoCheck(final int size, InputStream inputStream) throws IOException {
|
||||
// have to resize the file (and return the new path)
|
||||
|
||||
// now have to resize this file.
|
||||
File newFile = new File(TEMP_DIR, "temp_resize").getAbsoluteFile();
|
||||
Image image;
|
||||
|
||||
|
||||
// resize the image, keep aspect
|
||||
image = new ImageIcon(ImageIO.read(inputStream)).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
|
||||
image.flush();
|
||||
|
||||
// have to do this twice, so that it will finish loading the image (weird callback stuff is required if we don't do this)
|
||||
image = new ImageIcon(image).getImage();
|
||||
image.flush();
|
||||
|
||||
// make whatever dirs we need to.
|
||||
newFile.getParentFile().mkdirs();
|
||||
|
||||
// if it's already there, we have to delete it
|
||||
newFile.delete();
|
||||
|
||||
// now write out the new one
|
||||
BufferedImage bufferedImage = getBufferedImage(image);
|
||||
ImageIO.write(bufferedImage, "png", newFile); // made up extension
|
||||
|
||||
return newFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resizes the image (as a FILE on disk, or as a RESOURCE name), saves it as a file on disk. This file will be OVER-WRITTEN by any
|
||||
* operation that calls this method.
|
||||
*
|
||||
* @return the file string on disk that is the resized icon
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private static
|
||||
String resizeFile(final int size, final String fileName) throws IOException {
|
||||
FileInputStream fileInputStream = new FileInputStream(fileName);
|
||||
|
||||
Dimension imageSize = getImageSize(fileInputStream);
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
if (size == ((int) imageSize.getWidth()) && size == ((int) imageSize.getHeight())) {
|
||||
// we can reuse this file.
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// have to resize the file (and return the new path)
|
||||
|
||||
// now have to resize this file.
|
||||
File newFile = new File(TEMP_DIR, "temp_resize").getAbsoluteFile();
|
||||
Image image;
|
||||
|
||||
// is file sitting on drive
|
||||
File iconTest = new File(fileName);
|
||||
if (iconTest.isFile() && iconTest.canRead()) {
|
||||
final String absolutePath = iconTest.getAbsolutePath();
|
||||
|
||||
// resize the image, keep aspect
|
||||
image = new ImageIcon(absolutePath).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
|
||||
image.flush();
|
||||
}
|
||||
else {
|
||||
// suck it out of a URL/Resource (with debugging if necessary)
|
||||
final URL systemResource = LocationResolver.getResource(fileName);
|
||||
|
||||
// resize the image, keep aspect
|
||||
image = new ImageIcon(systemResource).getImage().getScaledInstance(size, -1, Image.SCALE_SMOOTH);
|
||||
image.flush();
|
||||
}
|
||||
|
||||
// have to do this twice, so that it will finish loading the image (weird callback stuff is required if we don't do this)
|
||||
image = new ImageIcon(image).getImage();
|
||||
image.flush();
|
||||
|
||||
// make whatever dirs we need to.
|
||||
newFile.getParentFile().mkdirs();
|
||||
|
||||
// if it's already there, we have to delete it
|
||||
newFile.delete();
|
||||
|
||||
|
||||
// now write out the new one
|
||||
String extension = FileUtil.getExtension(fileName);
|
||||
if (extension.equals("")) {
|
||||
extension = "png"; // made up
|
||||
}
|
||||
BufferedImage bufferedImage = getBufferedImage(image);
|
||||
ImageIO.write(bufferedImage, extension, newFile);
|
||||
|
||||
return newFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
private static
|
||||
BufferedImage getBufferedImage(Image image) {
|
||||
if (image instanceof BufferedImage) {
|
||||
return (BufferedImage) image;
|
||||
}
|
||||
|
||||
BufferedImage bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D bGr = bimage.createGraphics();
|
||||
bGr.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY));
|
||||
bGr.drawImage(image, 0, 0, null);
|
||||
bGr.dispose();
|
||||
|
||||
// Return the buffered image
|
||||
return bimage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the image size information from the specified file, without loading the entire file.
|
||||
*
|
||||
* @param fileStream the input stream of the file
|
||||
*
|
||||
* @return the image size dimensions. IOException if it could not be read
|
||||
*/
|
||||
private static
|
||||
Dimension getImageSize(InputStream fileStream) throws IOException {
|
||||
ImageInputStream in = null;
|
||||
ImageReader reader = null;
|
||||
try {
|
||||
in = ImageIO.createImageInputStream(fileStream);
|
||||
|
||||
final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
|
||||
if (readers.hasNext()) {
|
||||
reader = readers.next();
|
||||
reader.setInput(in);
|
||||
|
||||
return new Dimension(reader.getWidth(0), reader.getHeight(0));
|
||||
}
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (reader != null) {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("Unable to read file inputStream for image size data.");
|
||||
}
|
||||
}
|
203
src/dorkbox/systemTray/util/WindowsSystemTraySwing.java
Normal file
203
src/dorkbox/systemTray/util/WindowsSystemTraySwing.java
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright 2016 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.util;
|
||||
|
||||
import static dorkbox.systemTray.SystemTray.logger;
|
||||
|
||||
import java.awt.Robot;
|
||||
import java.util.Locale;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.util.BootStrapClassLoader;
|
||||
import dorkbox.util.OS;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtMethod;
|
||||
|
||||
/**
|
||||
* Fixes issues with some java runtimes
|
||||
*/
|
||||
public
|
||||
class WindowsSystemTraySwing {
|
||||
|
||||
// oh my. Java likes to think that ALL windows tray icons are 16x16.... Lets fix that!
|
||||
public static void fix() {
|
||||
// if we are using swing (in windows only) the icon size is usually incorrect. Here we have to fix that.
|
||||
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 "))) {
|
||||
// not fixing things that are not broken.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
boolean isWindowsSwingTrayLoaded = false;
|
||||
|
||||
try {
|
||||
// this is important to use reflection, because if JavaFX is not being used, calling getToolkit() will initialize it...
|
||||
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||
m.setAccessible(true);
|
||||
ClassLoader cl = ClassLoader.getSystemClassLoader();
|
||||
|
||||
// if we are using swing (in windows only) the icon size is usually incorrect. We cannot fix that if it's already loaded.
|
||||
isWindowsSwingTrayLoaded = (null != m.invoke(cl, "sun.awt.windows.WTrayIconPeer")) ||
|
||||
(null != m.invoke(cl, "java.awt.SystemTray"));
|
||||
} catch (Throwable e) {
|
||||
if (SystemTray.DEBUG) {
|
||||
logger.debug("Error detecting javaFX/SWT mode", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (isWindowsSwingTrayLoaded) {
|
||||
throw new RuntimeException("Unable to initialize the swing tray in windows, it has already been created!");
|
||||
}
|
||||
|
||||
/*
|
||||
* When DISTRIBUTING the JRE/JDK by Sun/Oracle, the license agreement states that we cannot create/modify specific files.
|
||||
*
|
||||
************* (when DISTRIBUTING the JRE/JDK...)
|
||||
* C. Java Technology Restrictions. You may not create, modify, or change the behavior of, or authorize your licensees to create, modify,
|
||||
* or change the behavior of, classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun" or similar
|
||||
* convention as specified by Oracle in any naming convention designation.
|
||||
*************
|
||||
*
|
||||
* Since we are not distributing a modified file, it does not apply to us.
|
||||
*
|
||||
* Again, just to be ABSOLUTELY CLEAR. This is for DISTRIBUTING the runtime.
|
||||
*
|
||||
* ************************************
|
||||
* To follow the license for DISTRIBUTION, these files themselves CANNOT BE MODIFIED in any way,
|
||||
* and if they are modified THEY CANNOT BE DISTRIBUTED.
|
||||
* ************************************
|
||||
*
|
||||
* Important distinction: We are not DISTRIBUTING java, nor modifying the distribution class files.
|
||||
*
|
||||
* What we are doing is modifying what is already present, post-distribution, and it is impossible to distribute is modified
|
||||
*
|
||||
* To see what files we need to fix...
|
||||
* http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/windows/native/sun/windows/awt_TrayIcon.cpp
|
||||
* http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/windows/classes/sun/awt/windows/WTrayIconPeer.java
|
||||
*/
|
||||
|
||||
try {
|
||||
// necessary to initialize sun.awt.windows.WObjectPeer native initIDs()
|
||||
@SuppressWarnings("unused")
|
||||
Robot robot = new Robot();
|
||||
|
||||
|
||||
ClassPool pool = ClassPool.getDefault();
|
||||
byte[] trayBytes;
|
||||
byte[] trayIconBytes;
|
||||
|
||||
{
|
||||
CtClass trayClass = pool.get("sun.awt.windows.WSystemTrayPeer");
|
||||
// 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);
|
||||
CtMethod ctMethodGet = trayClass.getDeclaredMethod("getTrayIconSize");
|
||||
ctMethodGet.setBody("{" +
|
||||
"return new java.awt.Dimension(" + ImageUtils.SIZE + ", " + ImageUtils.SIZE + ");" +
|
||||
"}");
|
||||
|
||||
trayBytes = trayClass.toBytecode();
|
||||
}
|
||||
|
||||
{
|
||||
CtClass trayIconClass = pool.get("sun.awt.windows.WTrayIconPeer");
|
||||
CtMethod ctMethodCreate = trayIconClass.getDeclaredMethod("createNativeImage");
|
||||
CtMethod ctMethodUpdate = trayIconClass.getDeclaredMethod("updateNativeImage");
|
||||
|
||||
int TRAY_MASK = (ImageUtils.SIZE * ImageUtils.SIZE) / 8;
|
||||
ctMethodCreate.setBody("{" +
|
||||
"java.awt.image.BufferedImage bufferedImage = $1;\n" +
|
||||
|
||||
"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" +
|
||||
|
||||
"int numberOfPixels = pixels.length;\n" +
|
||||
"int rasterImageWidth = rasterImage.getWidth();\n" +
|
||||
|
||||
"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" +
|
||||
|
||||
"if (rasterImage instanceof sun.awt.image.IntegerComponentRaster) {\n" +
|
||||
" rasterImageWidth = ((sun.awt.image.IntegerComponentRaster)rasterImage).getScanlineStride();\n" +
|
||||
"}\n" +
|
||||
|
||||
"setNativeIcon(((java.awt.image.DataBufferInt)bufferedImage.getRaster().getDataBuffer()).getData(), " +
|
||||
"mask, rasterImageWidth, rasterImage.getWidth(), rasterImage.getHeight());\n" +
|
||||
"}");
|
||||
|
||||
ctMethodUpdate.setBody("{" +
|
||||
"java.awt.Image image = $1;\n" +
|
||||
|
||||
"if (isDisposed()) {\n" +
|
||||
" return;\n" +
|
||||
"}\n" +
|
||||
|
||||
"int imageWidth = image.getWidth(observer);\n" +
|
||||
"int imageHeight = image.getWidth(observer);\n" +
|
||||
|
||||
"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" +
|
||||
|
||||
"if (g != null) {\n" +
|
||||
" try {\n" +
|
||||
// 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" +
|
||||
|
||||
" createNativeImage(trayIcon);\n" +
|
||||
|
||||
" updateNativeIcon(!firstUpdate);\n" +
|
||||
" if (firstUpdate) {" +
|
||||
" firstUpdate = false;\n" +
|
||||
" }\n" +
|
||||
" } finally {\n" +
|
||||
" g.dispose();\n" +
|
||||
" }\n" +
|
||||
"}" +
|
||||
"}");
|
||||
|
||||
trayIconBytes = trayIconClass.toBytecode();
|
||||
}
|
||||
|
||||
// whoosh, past the classloader and directly into memory.
|
||||
BootStrapClassLoader.defineClass(trayBytes);
|
||||
BootStrapClassLoader.defineClass(trayIconBytes);
|
||||
|
||||
if (SystemTray.DEBUG) {
|
||||
logger.info("Successfully changed tray icon size to: {}", ImageUtils.SIZE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error setting tray icon size to: {}", ImageUtils.SIZE, e);
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/dorkbox/systemTray/util/error_32.png
Normal file
BIN
src/dorkbox/systemTray/util/error_32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 980 B |
Loading…
Reference in New Issue
Block a user