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.InputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.CountDownLatch;
|
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.AppIndicator;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.systemTray.swing.SwingSystemTray;
|
import dorkbox.systemTray.swing.SwingSystemTray;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
import dorkbox.systemTray.util.JavaFX;
|
import dorkbox.systemTray.util.JavaFX;
|
||||||
import dorkbox.systemTray.util.Swt;
|
import dorkbox.systemTray.util.Swt;
|
||||||
|
import dorkbox.systemTray.util.WindowsSystemTraySwing;
|
||||||
|
import dorkbox.util.CacheUtil;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.Property;
|
import dorkbox.util.Property;
|
||||||
import dorkbox.util.process.ShellProcessBuilder;
|
import dorkbox.util.process.ShellProcessBuilder;
|
||||||
|
@ -50,22 +52,49 @@ import dorkbox.util.process.ShellProcessBuilder;
|
||||||
/**
|
/**
|
||||||
* Factory and base-class for system tray implementations.
|
* Factory and base-class for system tray implementations.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "Duplicates"})
|
@SuppressWarnings({"unused", "Duplicates", "DanglingJavadoc", "WeakerAccess"})
|
||||||
public abstract
|
public abstract
|
||||||
class SystemTray {
|
class SystemTray {
|
||||||
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
|
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
|
||||||
|
|
||||||
public static final int LINUX_GTK = 1;
|
public static final int TYPE_AUTO_DETECT = 0;
|
||||||
public static final int LINUX_APP_INDICATOR = 2;
|
public static final int TYPE_GTKSTATUSICON = 1;
|
||||||
public static final int SWING_INDICATOR = 3;
|
public static final int TYPE_APP_INDICATOR = 2;
|
||||||
|
public static final int TYPE_SWING = 3;
|
||||||
|
|
||||||
@Property
|
@Property
|
||||||
/** How long to wait when updating menu entries before the request times-out */
|
/** How long to wait when updating menu entries before the request times-out */
|
||||||
public static final int TIMEOUT = 2;
|
public static final int TIMEOUT = 2;
|
||||||
|
|
||||||
@Property
|
@Property
|
||||||
/** Size of the tray, so that the icon can properly scale based on OS. (if it's not exact) */
|
/** Enables auto-detection for the system tray. This should be mostly successful.
|
||||||
public static int TRAY_SIZE = 22;
|
* <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
|
@Property
|
||||||
/** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */
|
/** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */
|
||||||
|
@ -73,7 +102,7 @@ class SystemTray {
|
||||||
|
|
||||||
@Property
|
@Property
|
||||||
/** Forces the system tray detection to be Automatic (0), GTK (1), AppIndicator (2), or Swing (3). This is an advanced feature. */
|
/** 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
|
@Property
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +119,6 @@ class SystemTray {
|
||||||
|
|
||||||
|
|
||||||
private static volatile SystemTray systemTray = null;
|
private static volatile SystemTray systemTray = null;
|
||||||
static boolean isKDE = false;
|
|
||||||
|
|
||||||
public final static boolean isJavaFxLoaded;
|
public final static boolean isJavaFxLoaded;
|
||||||
public final static boolean isSwtLoaded;
|
public final static boolean isSwtLoaded;
|
||||||
|
@ -135,6 +163,7 @@ class SystemTray {
|
||||||
|
|
||||||
Class<? extends SystemTray> trayType = null;
|
Class<? extends SystemTray> trayType = null;
|
||||||
|
|
||||||
|
boolean isKDE = false;
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
logger.debug("is JavaFX detected? {}", isJavaFxLoaded);
|
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.
|
// 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) {
|
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
|
// 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");
|
// System.setProperty("SWT_GTK3", "0");
|
||||||
|
|
||||||
// was SWT forced?
|
// 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) {
|
if (!isSwt_GTK3) {
|
||||||
// check a different property
|
// 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) {
|
if (isSwt_GTK3 && FORCE_GTK2) {
|
||||||
|
@ -200,7 +231,7 @@ class SystemTray {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
switch (FORCE_LINUX_TYPE) {
|
switch (FORCE_TRAY_TYPE) {
|
||||||
case 1: logger.debug("Forced tray type: GtkStatusIcon"); break;
|
case 1: logger.debug("Forced tray type: GtkStatusIcon"); break;
|
||||||
case 2: logger.debug("Forced tray type: AppIndicator"); break;
|
case 2: logger.debug("Forced tray type: AppIndicator"); break;
|
||||||
case 3: logger.debug("Forced tray type: Swing"); break;
|
case 3: logger.debug("Forced tray type: Swing"); break;
|
||||||
|
@ -210,17 +241,11 @@ class SystemTray {
|
||||||
logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
|
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
|
// 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
|
// 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.
|
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all.
|
||||||
|
|
||||||
if (OS.isWindows()) {
|
if (FORCE_TRAY_TYPE != TYPE_SWING && OS.isLinux()) {
|
||||||
// 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()) {
|
|
||||||
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
|
// 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.
|
// 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 {
|
try {
|
||||||
trayType = GtkSystemTray.class;
|
trayType = GtkSystemTray.class;
|
||||||
} catch (Throwable e1) {
|
} 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 {
|
try {
|
||||||
trayType = AppIndicatorTray.class;
|
trayType = AppIndicatorTray.class;
|
||||||
} catch (Throwable e1) {
|
} 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
|
// 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");
|
String XDG = System.getenv("XDG_CURRENT_DESKTOP");
|
||||||
|
|
||||||
|
|
||||||
// BLEH. if gnome-shell is running, IT'S REALLY GNOME!
|
// BLEH. if gnome-shell is running, IT'S REALLY GNOME!
|
||||||
// we must ALWAYS do this check!!
|
// we must ALWAYS do this check!!
|
||||||
boolean isReallyGnome = false;
|
boolean isReallyGnome = false;
|
||||||
|
@ -306,14 +332,54 @@ class SystemTray {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ("xfce".equalsIgnoreCase(XDG)) {
|
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 {
|
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) {
|
} catch (Throwable e) {
|
||||||
if (DEBUG) {
|
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 {
|
try {
|
||||||
trayType = GtkSystemTray.class;
|
trayType = GtkSystemTray.class;
|
||||||
} catch (Throwable e1) {
|
} 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
|
// this is windows OR mac
|
||||||
if (trayType == null && java.awt.SystemTray.isSupported()) {
|
if (trayType == null && java.awt.SystemTray.isSupported()) {
|
||||||
try {
|
try {
|
||||||
|
@ -519,9 +592,16 @@ class SystemTray {
|
||||||
else {
|
else {
|
||||||
SystemTray systemTray_ = null;
|
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() &&
|
if (OS.isLinux() &&
|
||||||
trayType == AppIndicatorTray.class &&
|
trayType == AppIndicatorTray.class &&
|
||||||
Gtk.isGtk2 &&
|
Gtk.isGtk2 &&
|
||||||
|
@ -545,8 +625,6 @@ class SystemTray {
|
||||||
systemTray_ = (SystemTray) trayType.getConstructors()[0].newInstance();
|
systemTray_ = (SystemTray) trayType.getConstructors()[0].newInstance();
|
||||||
|
|
||||||
logger.info("Successfully Loaded: {}", trayType.getSimpleName());
|
logger.info("Successfully Loaded: {}", trayType.getSimpleName());
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
logger.error("Unsupported hashing algorithm!");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
|
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
|
||||||
}
|
}
|
||||||
|
@ -653,7 +731,7 @@ class SystemTray {
|
||||||
void setStatus(String statusText);
|
void setStatus(String statusText);
|
||||||
|
|
||||||
protected abstract
|
protected abstract
|
||||||
void setIcon_(String iconPath);
|
void setIcon_(File iconPath);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the tray icon used.
|
* Changes the tray icon used.
|
||||||
|
@ -665,8 +743,7 @@ class SystemTray {
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
void setIcon(String imagePath) {
|
void setIcon(String imagePath) {
|
||||||
final String fullPath = ImageUtil.iconPath(imagePath);
|
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath));
|
||||||
setIcon_(fullPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -679,8 +756,7 @@ class SystemTray {
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
void setIcon(URL imageUrl) {
|
void setIcon(URL imageUrl) {
|
||||||
final String fullPath = ImageUtil.iconPath(imageUrl);
|
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl));
|
||||||
setIcon_(fullPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -694,8 +770,7 @@ class SystemTray {
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
void setIcon(String cacheName, InputStream imageStream) {
|
void setIcon(String cacheName, InputStream imageStream) {
|
||||||
final String fullPath = ImageUtil.iconPath(cacheName, imageStream);
|
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream));
|
||||||
setIcon_(fullPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
* 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.
|
* 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
|
* @param imageStream the InputStream of the icon to use
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public
|
public
|
||||||
void setIcon(InputStream imageStream) {
|
void setIcon(InputStream imageStream) {
|
||||||
@SuppressWarnings("deprecation")
|
setIcon_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageStream));
|
||||||
final String fullPath = ImageUtil.iconPathNoCache(imageStream);
|
|
||||||
setIcon_(fullPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -764,9 +833,6 @@ class SystemTray {
|
||||||
/**
|
/**
|
||||||
* Adds a menu entry to the tray icon with text + image
|
* 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 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 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
|
* @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)
|
* @param newMenuText the new menu text (this will replace the original menu text)
|
||||||
*/
|
*/
|
||||||
public final
|
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
|
// have to wait for the value
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
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 origMenuText the original menu text
|
||||||
* @param imagePath the new path for the image to use or null to delete the image
|
* @param imagePath the new path for the image to use or null to delete the image
|
||||||
*/
|
*/
|
||||||
public final
|
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
|
// have to wait for the value
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
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
|
* @param imageUrl the new URL for the image to use or null to delete the image
|
||||||
*/
|
*/
|
||||||
public final
|
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
|
// have to wait for the value
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
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
|
* @param imageStream the InputStream of the image to use or null to delete the image
|
||||||
*/
|
*/
|
||||||
public final
|
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
|
// have to wait for the value
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||||
|
@ -960,14 +1026,11 @@ class SystemTray {
|
||||||
/**
|
/**
|
||||||
* Updates (or changes) the menu entry's text.
|
* 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 origMenuText the original menu text
|
||||||
* @param imageStream the new path for the image to use or null to delete the image
|
* @param imageStream the new path for the image to use or null to delete the image
|
||||||
*/
|
*/
|
||||||
public final
|
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
|
// have to wait for the value
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||||
|
@ -1013,7 +1076,7 @@ class SystemTray {
|
||||||
* @param newCallback the new callback (this will replace the original callback)
|
* @param newCallback the new callback (this will replace the original callback)
|
||||||
*/
|
*/
|
||||||
public final
|
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
|
// have to wait for the value
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
final AtomicBoolean hasValue = new AtomicBoolean(true);
|
||||||
|
|
|
@ -15,14 +15,17 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.systemTray.linux;
|
package dorkbox.systemTray.linux;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||||
import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct;
|
import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for handling all system tray interactions.
|
* Class for handling all system tray interactions.
|
||||||
|
@ -42,9 +45,9 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
||||||
|
|
||||||
public
|
public
|
||||||
AppIndicatorTray() {
|
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
|
// 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();
|
Gtk.startGui();
|
||||||
|
@ -56,6 +59,10 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
||||||
appIndicator = AppIndicator.app_indicator_new(System.nanoTime() + "DBST", "", AppIndicator.CATEGORY_APPLICATION_STATUS);
|
appIndicator = AppIndicator.app_indicator_new(System.nanoTime() + "DBST", "", AppIndicator.CATEGORY_APPLICATION_STATUS);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
super.waitForStartup();
|
||||||
|
|
||||||
|
ImageUtils.determineIconSize(SystemTray.TYPE_APP_INDICATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -81,12 +88,12 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected
|
protected
|
||||||
void setIcon_(final String iconPath) {
|
void setIcon_(final File iconFile) {
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
AppIndicator.app_indicator_set_icon(appIndicator, iconPath);
|
AppIndicator.app_indicator_set_icon(appIndicator, iconFile.getAbsolutePath());
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
isActive = true;
|
isActive = true;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.systemTray.linux;
|
package dorkbox.systemTray.linux;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
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.NativeLong;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
import dorkbox.systemTray.ImageUtil;
|
|
||||||
import dorkbox.systemTray.MenuEntry;
|
import dorkbox.systemTray.MenuEntry;
|
||||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||||
import dorkbox.systemTray.linux.jna.GCallback;
|
import dorkbox.systemTray.linux.jna.GCallback;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
|
|
||||||
class GtkMenuEntry implements MenuEntry, GCallback {
|
class GtkMenuEntry implements MenuEntry, GCallback {
|
||||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
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!
|
* 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
|
* 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.parent = parent;
|
||||||
this.text = label;
|
this.text = label;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
|
||||||
menuItem = Gtk.gtk_image_menu_item_new_with_label(label);
|
menuItem = Gtk.gtk_image_menu_item_new_with_label(label);
|
||||||
|
|
||||||
if (imagePath != null && !imagePath.isEmpty()) {
|
if (imagePath != null) {
|
||||||
// NOTE: XFCE uses appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
// 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://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
|
// 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);
|
Gtk.gtk_image_menu_item_set_image(menuItem, image);
|
||||||
// must always re-set always-show after setting the image
|
// must always re-set always-show after setting the image
|
||||||
|
@ -108,7 +109,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private
|
||||||
void setImage_(final String imagePath) {
|
void setImage_(final File imagePath) {
|
||||||
Gtk.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
|
@ -120,8 +121,8 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
||||||
|
|
||||||
Gtk.gtk_widget_show_all(menuItem);
|
Gtk.gtk_widget_show_all(menuItem);
|
||||||
|
|
||||||
if (imagePath != null && !imagePath.isEmpty()) {
|
if (imagePath != null) {
|
||||||
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);
|
Gtk.gtk_image_menu_item_set_image(menuItem, image);
|
||||||
Gobject.g_object_ref_sink(image);
|
Gobject.g_object_ref_sink(image);
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setImage_(ImageUtil.iconPath(imagePath));
|
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setImage_(ImageUtil.iconPath(imageUrl));
|
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +164,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setImage_(ImageUtil.iconPath(cacheName, imageStream));
|
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +176,7 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setImage_(ImageUtil.iconPathNoCache(imageStream));
|
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageStream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.systemTray.linux;
|
package dorkbox.systemTray.linux;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import com.sun.jna.NativeLong;
|
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.GdkEventButton;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
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.
|
* Class for handling all system tray interactions via GTK.
|
||||||
|
@ -56,37 +55,12 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||||
super();
|
super();
|
||||||
Gtk.startGui();
|
Gtk.startGui();
|
||||||
|
|
||||||
final CountDownLatch blockUntilStarted = new CountDownLatch(1);
|
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
final Pointer trayIcon_ = Gtk.gtk_status_icon_new();
|
final Pointer trayIcon_ = Gtk.gtk_status_icon_new();
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon_, false); // immediately set false visibility
|
|
||||||
trayIcon = trayIcon_;
|
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() {
|
final GEventCallback gtkCallback = new GEventCallback() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -104,37 +78,31 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||||
// have to do this to prevent GC on these objects
|
// have to do this to prevent GC on these objects
|
||||||
gtkCallbacks.add(gtkCallback);
|
gtkCallbacks.add(gtkCallback);
|
||||||
gtkCallbacks.add(button_press_event);
|
gtkCallbacks.add(button_press_event);
|
||||||
|
|
||||||
blockUntilStarted.countDown();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
super.waitForStartup();
|
||||||
|
|
||||||
if (SystemTray.isJavaFxLoaded) {
|
ImageUtils.determineIconSize(SystemTray.TYPE_GTKSTATUSICON);
|
||||||
if (!JavaFX.isEventThread()) {
|
|
||||||
try {
|
// we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work
|
||||||
blockUntilStarted.await(10, TimeUnit.SECONDS);
|
dispatch(new Runnable() {
|
||||||
} catch (InterruptedException e) {
|
@Override
|
||||||
e.printStackTrace();
|
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
|
@Override
|
||||||
protected
|
protected
|
||||||
void setIcon_(final String iconPath) {
|
void setIcon_(final File iconFile) {
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
Gtk.gtk_status_icon_set_from_file(trayIcon, iconPath);
|
Gtk.gtk_status_icon_set_from_file(trayIcon, iconFile.getAbsolutePath());
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
isActive = true;
|
isActive = true;
|
||||||
|
|
|
@ -16,16 +16,20 @@
|
||||||
|
|
||||||
package dorkbox.systemTray.linux;
|
package dorkbox.systemTray.linux;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
import dorkbox.systemTray.ImageUtil;
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
|
import dorkbox.systemTray.util.JavaFX;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derived from
|
* Derived from
|
||||||
|
@ -44,6 +48,45 @@ class GtkTypeSystemTray extends SystemTray {
|
||||||
Gtk.dispatch(runnable);
|
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
|
@Override
|
||||||
public
|
public
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
|
@ -215,7 +258,7 @@ class GtkTypeSystemTray extends SystemTray {
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
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.
|
// 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
|
// see: https://bugs.launchpad.net/glipper/+bug/1203888
|
||||||
|
|
||||||
|
@ -252,7 +295,7 @@ class GtkTypeSystemTray extends SystemTray {
|
||||||
addMenuEntry_(menuText, null, callback);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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.
|
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3.
|
||||||
// appindicator3 doesn't support menu icons via GTK2!!
|
// 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 we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||||
if (LIBRARY_DEBUG) {
|
if (LIBRARY_DEBUG) {
|
||||||
logger.error("Forcing GTK tray, not using appindicator");
|
logger.error("Forcing GTK tray, not using appindicator");
|
||||||
|
|
|
@ -69,7 +69,7 @@ class Gtk {
|
||||||
String gtk3LibName = "libgtk-3.so.0";
|
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.
|
// 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;
|
isLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,31 +16,22 @@
|
||||||
|
|
||||||
package dorkbox.systemTray.swing;
|
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.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
class SwingMenuEntry implements MenuEntry {
|
import javax.swing.ImageIcon;
|
||||||
private static final String tempDirPath = ImageUtil.TEMP_DIR.getAbsolutePath();
|
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 SwingSystemTrayMenuPopup parent;
|
||||||
private final SystemTray systemTray;
|
private final SystemTray systemTray;
|
||||||
private final JMenuItem menuItem;
|
private final JMenuItem menuItem;
|
||||||
|
@ -49,12 +40,8 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
private volatile String text;
|
private volatile String text;
|
||||||
private volatile SystemTrayMenuAction callback;
|
private volatile SystemTrayMenuAction callback;
|
||||||
|
|
||||||
private int iconHeight = -1;
|
// this is ALWAYS called on the EDT.
|
||||||
|
SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final File imagePath, final SystemTrayMenuAction callback,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
|
|
||||||
final SystemTray systemTray) {
|
final SystemTray systemTray) {
|
||||||
this.parent = parentMenu;
|
this.parent = parentMenu;
|
||||||
this.text = label;
|
this.text = label;
|
||||||
|
@ -73,7 +60,7 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
menuItem = new JMenuItem(label);
|
menuItem = new JMenuItem(label);
|
||||||
menuItem.addActionListener(swingCallback);
|
menuItem.addActionListener(swingCallback);
|
||||||
|
|
||||||
if (imagePath != null && !imagePath.isEmpty()) {
|
if (imagePath != null) {
|
||||||
setImageIcon(imagePath);
|
setImageIcon(imagePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +96,7 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private
|
||||||
void setImage_(final String imagePath) {
|
void setImage_(final File imagePath) {
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
|
@ -119,58 +106,11 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// always called on the EDT
|
||||||
private
|
private
|
||||||
void setImageIcon(final String imagePath) {
|
void setImageIcon(final File imagePath) {
|
||||||
if (imagePath != null && !imagePath.isEmpty()) {
|
if (imagePath != null) {
|
||||||
|
ImageIcon origIcon = new ImageIcon(imagePath.getAbsolutePath());
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menuItem.setIcon(origIcon);
|
menuItem.setIcon(origIcon);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -185,7 +125,7 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setImage_(ImageUtil.iconPath(imagePath));
|
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imagePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +136,7 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setImage_(ImageUtil.iconPath(imageUrl));
|
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, imageUrl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +147,7 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setImage_(ImageUtil.iconPath(cacheName, imageStream));
|
setImage_(ImageUtils.resizeAndCache(ImageUtils.SIZE, cacheName, imageStream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +159,7 @@ class SwingMenuEntry implements MenuEntry {
|
||||||
setImage_(null);
|
setImage_(null);
|
||||||
}
|
}
|
||||||
else {
|
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.TrayIcon;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
|
|
||||||
import dorkbox.systemTray.ImageUtil;
|
|
||||||
import dorkbox.systemTray.MenuEntry;
|
import dorkbox.systemTray.MenuEntry;
|
||||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
import dorkbox.util.ScreenUtil;
|
import dorkbox.util.ScreenUtil;
|
||||||
import dorkbox.util.SwingUtil;
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ import dorkbox.util.SwingUtil;
|
||||||
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6453521
|
* 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
|
* https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||||
public
|
public
|
||||||
class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
||||||
volatile SwingSystemTrayMenuPopup menu;
|
volatile SwingSystemTrayMenuPopup menu;
|
||||||
|
@ -63,6 +65,9 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
||||||
public
|
public
|
||||||
SwingSystemTray() {
|
SwingSystemTray() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
ImageUtils.determineIconSize(dorkbox.systemTray.SystemTray.TYPE_SWING);
|
||||||
|
|
||||||
SwingUtil.invokeAndWait(new Runnable() {
|
SwingUtil.invokeAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
|
@ -141,21 +146,23 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected
|
protected
|
||||||
void setIcon_(final String iconPath) {
|
void setIcon_(final File iconFile) {
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
SwingSystemTray tray = SwingSystemTray.this;
|
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) {
|
synchronized (tray) {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
// here we init. everything
|
// here we init. everything
|
||||||
isActive = true;
|
isActive = true;
|
||||||
|
|
||||||
menu = new SwingSystemTrayMenuPopup();
|
menu = new SwingSystemTrayMenuPopup();
|
||||||
Image trayImage = new ImageIcon(iconPath).getImage()
|
|
||||||
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
|
|
||||||
trayImage.flush();
|
|
||||||
trayIcon = new TrayIcon(trayImage);
|
trayIcon = new TrayIcon(trayImage);
|
||||||
|
|
||||||
// appindicators don't support this, so we cater to the lowest common denominator
|
// 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);
|
logger.error("TrayIcon could not be added.", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Image trayImage = new ImageIcon(iconPath).getImage()
|
|
||||||
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
|
|
||||||
trayImage.flush();
|
|
||||||
tray.trayIcon.setImage(trayImage);
|
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
|
* Will add a new menu entry, or update one if it already exists
|
||||||
*/
|
*/
|
||||||
private
|
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) {
|
if (menuText == null) {
|
||||||
throw new NullPointerException("Menu text cannot be null");
|
throw new NullPointerException("Menu text cannot be null");
|
||||||
}
|
}
|
||||||
|
@ -255,7 +259,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
|
||||||
addMenuEntry_(menuText, null, callback);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
addMenuEntry_(menuText, null, callback);
|
||||||
}
|
}
|
||||||
else {
|
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