Fixed issues with loading libappindicator (for incorrect versions), now falls back to GTK. Compiled as java6.

This commit is contained in:
nathan 2015-06-28 01:47:02 +02:00
parent 2ff2366325
commit c692059150
8 changed files with 242 additions and 166 deletions

View File

@ -15,6 +15,7 @@
*/ */
package dorkbox.util.tray; package dorkbox.util.tray;
public interface FailureCallback { public
public void createTrayFailed(); interface FailureCallback {
void createTrayFailed();
} }

View File

@ -15,13 +15,17 @@
*/ */
package dorkbox.util.tray; package dorkbox.util.tray;
import java.io.BufferedReader; import dorkbox.util.NamedThreadFactory;
import java.io.File; import dorkbox.util.OS;
import java.io.FileOutputStream; import dorkbox.util.jna.linux.AppIndicator;
import java.io.FileReader; import dorkbox.util.jna.linux.GtkSupport;
import java.io.IOException; import dorkbox.util.tray.linux.AppIndicatorTray;
import java.io.InputStream; import dorkbox.util.tray.linux.GtkSystemTray;
import java.io.OutputStream; import dorkbox.util.tray.swing.SwingSystemTray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -32,21 +36,12 @@ import java.security.SecureRandom;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dorkbox.util.NamedThreadFactory;
import dorkbox.util.OS;
import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.tray.linux.AppIndicatorTray;
import dorkbox.util.tray.linux.GtkSystemTray;
import dorkbox.util.tray.swing.SwingSystemTray;
/** /**
* Interface for system tray implementations. * Interface for system tray implementations.
*/ */
public abstract class SystemTray { public abstract
class SystemTray {
private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final Charset UTF_8 = Charset.forName("UTF-8");
private static MessageDigest digest; private static MessageDigest digest;
@ -80,7 +75,7 @@ public abstract class SystemTray {
if (getenv != null && getenv.equals("Unity")) { if (getenv != null && getenv.equals("Unity")) {
try { try {
trayType = AppIndicatorTray.class; trayType = AppIndicatorTray.class;
} catch (Exception ignored) { } catch (Throwable ignored) {
} }
} }
@ -107,7 +102,13 @@ public abstract class SystemTray {
bin = new BufferedReader(new FileReader(status)); bin = new BufferedReader(new FileReader(status));
String readLine = bin.readLine(); String readLine = bin.readLine();
if (readLine != null && readLine.contains("indicator-app")) { if (readLine != null && readLine.contains("indicator-app")) {
trayType = AppIndicatorTray.class; // make sure we can also load the library (it might be the wrong version)
try {
final AppIndicator instance = AppIndicator.INSTANCE;
trayType = AppIndicatorTray.class;
} catch (Throwable ignored) {
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
}
break; break;
} }
} finally { } finally {
@ -118,7 +119,7 @@ public abstract class SystemTray {
} }
} }
} }
} catch (Exception ignored) { } catch (Throwable ignored) {
} finally { } finally {
if (bin != null) { if (bin != null) {
try { try {
@ -129,6 +130,7 @@ public abstract class SystemTray {
} }
} }
if (trayType == null) { if (trayType == null) {
trayType = GtkSystemTray.class; trayType = GtkSystemTray.class;
} }
@ -143,7 +145,8 @@ public abstract class SystemTray {
if (trayType == null) { if (trayType == null) {
// unsupported tray // unsupported tray
logger.error("Unsupported tray type!"); logger.error("Unsupported tray type!");
} else { }
else {
try { try {
digest = MessageDigest.getInstance("MD5"); digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
@ -159,7 +162,8 @@ public abstract class SystemTray {
protected volatile boolean active = false; protected volatile boolean active = false;
protected String appName; protected String appName;
public static SystemTray create(String appName) { public static
SystemTray create(String appName) {
if (trayType != null) { if (trayType != null) {
try { try {
SystemTray newInstance = trayType.newInstance(); SystemTray newInstance = trayType.newInstance();
@ -167,7 +171,7 @@ public abstract class SystemTray {
newInstance.setAppName(appName); newInstance.setAppName(appName);
} }
return newInstance; return newInstance;
} catch (Exception e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -176,34 +180,43 @@ public abstract class SystemTray {
return null; return null;
} }
private void setAppName(String appName) { private
void setAppName(String appName) {
this.appName = appName; this.appName = appName;
} }
public abstract void createTray(String iconName); public abstract
void createTray(String iconName);
public void removeTray() { public
void removeTray() {
SystemTray.this.callbackExecutor.shutdown(); SystemTray.this.callbackExecutor.shutdown();
} }
public abstract void setStatus(String infoString, String iconName); public abstract
void setStatus(String infoString, String iconName);
public abstract void addMenuEntry(String menuText, SystemTrayMenuAction callback); public abstract
void addMenuEntry(String menuText, SystemTrayMenuAction callback);
public abstract void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback); public abstract
void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback);
protected String iconPath(String fileName) { protected
String iconPath(String fileName) {
// is file sitting on drive // is file sitting on drive
File iconTest; File iconTest;
if (ICON_PATH.isEmpty()) { if (ICON_PATH.isEmpty()) {
iconTest = new File(fileName); iconTest = new File(fileName);
} else { }
else {
iconTest = new File(ICON_PATH, fileName); iconTest = new File(ICON_PATH, fileName);
} }
if (iconTest.isFile() && iconTest.canRead()) { if (iconTest.isFile() && iconTest.canRead()) {
return iconTest.getAbsolutePath(); return iconTest.getAbsolutePath();
} else { }
else {
if (!ICON_PATH.isEmpty()) { if (!ICON_PATH.isEmpty()) {
fileName = ICON_PATH + "/" + fileName; fileName = ICON_PATH + "/" + fileName;
} }
@ -291,11 +304,13 @@ public abstract class SystemTray {
throw new RuntimeException(message); throw new RuntimeException(message);
} }
public final void setFailureCallback(FailureCallback failureCallback) { public final
void setFailureCallback(FailureCallback failureCallback) {
this.failureCallback = failureCallback; this.failureCallback = failureCallback;
} }
public final boolean isActive() { public final
boolean isActive() {
return this.active; return this.active;
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package dorkbox.util.tray; package dorkbox.util.tray;
public interface SystemTrayMenuAction { public
interface SystemTrayMenuAction {
void onClick(SystemTray systemTray); void onClick(SystemTray systemTray);
} }

View File

@ -1,46 +1,50 @@
package dorkbox.util.tray; package dorkbox.util.tray;
import java.awt.Dimension;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JPopupMenu;
import dorkbox.util.DelayTimer; import dorkbox.util.DelayTimer;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
public class SystemTrayMenuPopup extends JPopupMenu { import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public
class SystemTrayMenuPopup extends JPopupMenu {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** Allows you to customize the delay (for hiding the popup) when the cursor is "moused out" of the popup menu */ /**
* Allows you to customize the delay (for hiding the popup) when the cursor is "moused out" of the popup menu
*/
public static long hidePopupDelay = 1000L; public static long hidePopupDelay = 1000L;
private DelayTimer timer; private DelayTimer timer;
protected boolean mouseStillOnMenu; // protected boolean mouseStillOnMenu;
// private JDialog hiddenDialog; // private JDialog hiddenDialog;
public SystemTrayMenuPopup() { public
SystemTrayMenuPopup() {
super(); super();
setFocusable(true); setFocusable(true);
this.timer = new DelayTimer("PopupMenuHider", true, new DelayTimer.Callback() { this.timer = new DelayTimer("PopupMenuHider", true, new DelayTimer.Callback() {
@Override @Override
public void execute() { public
void execute() {
SwingUtil.invokeLater(new Runnable() { SwingUtil.invokeLater(new Runnable() {
@Override @Override
public void run() { public
void run() {
Point location = MouseInfo.getPointerInfo().getLocation(); Point location = MouseInfo.getPointerInfo().getLocation();
Point locationOnScreen = getLocationOnScreen(); Point locationOnScreen = getLocationOnScreen();
Dimension size = getSize(); Dimension size = getSize();
if (location.x >= locationOnScreen.x && location.x < locationOnScreen.x + size.width if (location.x >= locationOnScreen.x && location.x < locationOnScreen.x + size.width &&
&& location.y >= locationOnScreen.y && location.y < locationOnScreen.y + size.height) { location.y >= locationOnScreen.y && location.y < locationOnScreen.y + size.height) {
SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay());
} else { }
else {
setVisible(false); setVisible(false);
} }
} }
@ -50,7 +54,8 @@ public class SystemTrayMenuPopup extends JPopupMenu {
addMouseListener(new MouseAdapter() { addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseExited(MouseEvent event) { public
void mouseExited(MouseEvent event) {
// wait before checking if mouse is still on the menu // wait before checking if mouse is still on the menu
SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay());
} }
@ -75,7 +80,8 @@ public class SystemTrayMenuPopup extends JPopupMenu {
} }
@Override @Override
public void setVisible(boolean makeVisible) { public
void setVisible(boolean makeVisible) {
this.timer.cancel(); this.timer.cancel();
if (makeVisible) { if (makeVisible) {

View File

@ -15,13 +15,7 @@
*/ */
package dorkbox.util.tray.linux; package dorkbox.util.tray.linux;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.util.jna.linux.AppIndicator; import dorkbox.util.jna.linux.AppIndicator;
import dorkbox.util.jna.linux.Gobject; import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gtk; import dorkbox.util.jna.linux.Gtk;
@ -29,16 +23,22 @@ import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.tray.SystemTray; import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction; import dorkbox.util.tray.SystemTrayMenuAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* Class for handling all system tray interactions. * Class for handling all system tray interactions.
* * <p/>
* specialization for using app indicators in ubuntu unity * specialization for using app indicators in ubuntu unity
* * <p/>
* Heavily modified from * Heavily modified from
* * <p/>
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc. * Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
*/ */
public class AppIndicatorTray extends SystemTray { public
class AppIndicatorTray extends SystemTray {
private static final AppIndicator libappindicator = AppIndicator.INSTANCE; private static final AppIndicator libappindicator = AppIndicator.INSTANCE;
private static final Gobject libgobject = Gobject.INSTANCE; private static final Gobject libgobject = Gobject.INSTANCE;
private static final Gtk libgtk = Gtk.INSTANCE; private static final Gtk libgtk = Gtk.INSTANCE;
@ -54,29 +54,33 @@ public class AppIndicatorTray extends SystemTray {
private final List<Pointer> widgets = new ArrayList<Pointer>(4); private final List<Pointer> widgets = new ArrayList<Pointer>(4);
public AppIndicatorTray() { public
AppIndicatorTray() {
} }
@Override @Override
public void createTray(String iconName) { public
void createTray(String iconName) {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
this.appIndicator = this.appIndicator = libappindicator.app_indicator_new(this.appName, "indicator-messages-new",
libappindicator.app_indicator_new(this.appName, "indicator-messages-new", AppIndicator.CATEGORY_APPLICATION_STATUS); AppIndicator.CATEGORY_APPLICATION_STATUS);
/* /*
* basically a hack -- we should subclass the AppIndicator type and override the fallback entry in the 'vtable', instead we just * basically a hack -- we should subclass the AppIndicator type and override the fallback entry in the 'vtable', instead we just
* hack the app indicator class itself. Not an issue unless we need other appindicators. * hack the app indicator class itself. Not an issue unless we need other appindicators.
*/ */
AppIndicator.AppIndicatorClassStruct aiclass = AppIndicator.AppIndicatorClassStruct aiclass = new AppIndicator.AppIndicatorClassStruct(
new AppIndicator.AppIndicatorClassStruct(this.appIndicator.parent.g_type_instance.g_class); this.appIndicator.parent.g_type_instance.g_class);
aiclass.fallback = new AppIndicator.Fallback() { aiclass.fallback = new AppIndicator.Fallback() {
@Override @Override
public Pointer callback(final AppIndicator.AppIndicatorInstanceStruct self) { public
Pointer callback(final AppIndicator.AppIndicatorInstanceStruct self) {
AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { AppIndicatorTray.this.callbackExecutor.execute(new Runnable() {
@Override @Override
public void run() { public
void run() {
logger.warn("Failed to create appindicator system tray."); logger.warn("Failed to create appindicator system tray.");
if (AppIndicatorTray.this.failureCallback != null) { if (AppIndicatorTray.this.failureCallback != null) {
@ -101,7 +105,8 @@ public class AppIndicatorTray extends SystemTray {
} }
@Override @Override
public void removeTray() { public
void removeTray() {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
for (Pointer widget : this.widgets) { for (Pointer widget : this.widgets) {
libgtk.gtk_widget_destroy(widget); libgtk.gtk_widget_destroy(widget);
@ -135,14 +140,16 @@ public class AppIndicatorTray extends SystemTray {
} }
@Override @Override
public void setStatus(String infoString, String iconName) { public
void setStatus(String infoString, String iconName) {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
if (this.connectionStatusItem == null) { if (this.connectionStatusItem == null) {
this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label(infoString); this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label(infoString);
this.widgets.add(this.connectionStatusItem); this.widgets.add(this.connectionStatusItem);
libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE); libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE);
libgtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem); libgtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem);
} else { }
else {
libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString); libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString);
} }
@ -156,7 +163,8 @@ public class AppIndicatorTray extends 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
*/ */
@Override @Override
public void addMenuEntry(String menuText, final SystemTrayMenuAction callback) { public
void addMenuEntry(String menuText, final SystemTrayMenuAction callback) {
synchronized (this.menuEntries) { synchronized (this.menuEntries) {
MenuEntry menuEntry = this.menuEntries.get(menuText); MenuEntry menuEntry = this.menuEntries.get(menuText);
@ -168,10 +176,12 @@ public class AppIndicatorTray extends SystemTray {
// have to watch out! These can get garbage collected! // have to watch out! These can get garbage collected!
Gobject.GCallback gtkCallback = new Gobject.GCallback() { Gobject.GCallback gtkCallback = new Gobject.GCallback() {
@Override @Override
public void callback(Pointer instance, Pointer data) { public
void callback(Pointer instance, Pointer data) {
AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { AppIndicatorTray.this.callbackExecutor.execute(new Runnable() {
@Override @Override
public void run() { public
void run() {
callback.onClick(AppIndicatorTray.this); callback.onClick(AppIndicatorTray.this);
} }
}); });
@ -189,7 +199,8 @@ public class AppIndicatorTray extends SystemTray {
menuEntry.gtkCallback = gtkCallback; menuEntry.gtkCallback = gtkCallback;
this.menuEntries.put(menuText, menuEntry); this.menuEntries.put(menuText, menuEntry);
} else { }
else {
updateMenuEntry(menuText, menuText, callback); updateMenuEntry(menuText, menuText, callback);
} }
} }
@ -199,7 +210,8 @@ public class AppIndicatorTray extends SystemTray {
* Will update an already existing menu entry (or add a new one, if it doesn't exist) * Will update an already existing menu entry (or add a new one, if it doesn't exist)
*/ */
@Override @Override
public void updateMenuEntry(String origMenuText, String newMenuText, final SystemTrayMenuAction newCallback) { public
void updateMenuEntry(String origMenuText, String newMenuText, final SystemTrayMenuAction newCallback) {
synchronized (this.menuEntries) { synchronized (this.menuEntries) {
MenuEntry menuEntry = this.menuEntries.get(origMenuText); MenuEntry menuEntry = this.menuEntries.get(origMenuText);
@ -210,10 +222,12 @@ public class AppIndicatorTray extends SystemTray {
// have to watch out! These can get garbage collected! // have to watch out! These can get garbage collected!
menuEntry.gtkCallback = new Gobject.GCallback() { menuEntry.gtkCallback = new Gobject.GCallback() {
@Override @Override
public void callback(Pointer instance, Pointer data) { public
void callback(Pointer instance, Pointer data) {
AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { AppIndicatorTray.this.callbackExecutor.execute(new Runnable() {
@Override @Override
public void run() { public
void run() {
newCallback.onClick(AppIndicatorTray.this); newCallback.onClick(AppIndicatorTray.this);
} }
}); });
@ -224,7 +238,8 @@ public class AppIndicatorTray extends SystemTray {
libgtk.gtk_widget_show_all(menuEntry.dashboardItem); libgtk.gtk_widget_show_all(menuEntry.dashboardItem);
libgtk.gdk_threads_leave(); libgtk.gdk_threads_leave();
} else { }
else {
addMenuEntry(origMenuText, newCallback); addMenuEntry(origMenuText, newCallback);
} }
} }

View File

@ -15,20 +15,7 @@
*/ */
package dorkbox.util.tray.linux; package dorkbox.util.tray.linux;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JMenuItem;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
import dorkbox.util.jna.linux.Gobject; import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gtk; import dorkbox.util.jna.linux.Gtk;
@ -38,12 +25,22 @@ import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction; import dorkbox.util.tray.SystemTrayMenuAction;
import dorkbox.util.tray.SystemTrayMenuPopup; import dorkbox.util.tray.SystemTrayMenuPopup;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* Class for handling all system tray interactions via GTK. * Class for handling all system tray interactions via GTK.
* * <p/>
* This is the "old" way to do it, and does not work with some desktop environments. * This is the "old" way to do it, and does not work with some desktop environments.
*/ */
public class GtkSystemTray extends SystemTray { public
class GtkSystemTray extends SystemTray {
private static final Gobject libgobject = Gobject.INSTANCE; private static final Gobject libgobject = Gobject.INSTANCE;
private static final Gtk libgtk = Gtk.INSTANCE; private static final Gtk libgtk = Gtk.INSTANCE;
@ -58,14 +55,17 @@ public class GtkSystemTray extends SystemTray {
private final List<Pointer> widgets = new ArrayList<Pointer>(4); private final List<Pointer> widgets = new ArrayList<Pointer>(4);
private Gobject.GEventCallback gtkCallback; private Gobject.GEventCallback gtkCallback;
public GtkSystemTray() { public
GtkSystemTray() {
} }
@Override @Override
public void createTray(String iconName) { public
void createTray(String iconName) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
GtkSystemTray.this.jmenu = new SystemTrayMenuPopup(); GtkSystemTray.this.jmenu = new SystemTrayMenuPopup();
} }
}); });
@ -80,28 +80,32 @@ public class GtkSystemTray extends SystemTray {
// have to make this a field, to prevent GC on this object // have to make this a field, to prevent GC on this object
this.gtkCallback = new Gobject.GEventCallback() { this.gtkCallback = new Gobject.GEventCallback() {
@Override @Override
public void callback(Pointer system_tray, final GdkEventButton event) { public
void callback(Pointer system_tray, final GdkEventButton event) {
// BUTTON_PRESS only (any mouse click) // BUTTON_PRESS only (any mouse click)
if (event.type == 4) { if (event.type == 4) {
SwingUtil.invokeLater(new Runnable() { SwingUtil.invokeLater(new Runnable() {
@Override @Override
public void run() { public
void run() {
// test this using cinnamon (which still uses status icon) // test this using cinnamon (which still uses status icon)
if (GtkSystemTray.this.jmenu.isVisible()) { if (GtkSystemTray.this.jmenu.isVisible()) {
GtkSystemTray.this.jmenu.setVisible(false); GtkSystemTray.this.jmenu.setVisible(false);
} else { }
else {
Dimension size = GtkSystemTray.this.jmenu.getPreferredSize(); Dimension size = GtkSystemTray.this.jmenu.getPreferredSize();
int x = (int) event.x_root; int x = (int) event.x_root;
int y = (int) event.y_root; int y = (int) event.y_root;
Point point = new Point(x, y); Point point = new Point(x, y);
Rectangle bounds = SwingUtil.getScreenBoundsAt(point); Rectangle bounds = SwingUtil.getScreenBoundsAt(point);
if (y < bounds.y) { if (y < bounds.y) {
y = bounds.y; y = bounds.y;
} else if (y + size.height > bounds.y + bounds.height) { }
else if (y + size.height > bounds.y + bounds.height) {
// our menu cannot have the top-edge snap to the mouse // our menu cannot have the top-edge snap to the mouse
// so we make the bottom-edge snap to the mouse // so we make the bottom-edge snap to the mouse
y -= size.height; // snap to edge of mouse y -= size.height; // snap to edge of mouse
@ -109,7 +113,8 @@ public class GtkSystemTray extends SystemTray {
if (x < bounds.x) { if (x < bounds.x) {
x = bounds.x; x = bounds.x;
} else if (x + size.width > bounds.x + bounds.width) { }
else if (x + size.width > bounds.x + bounds.width) {
// our menu cannot have the left-edge snap to the mouse // our menu cannot have the left-edge snap to the mouse
// so we make the right-edge snap to the mouse // so we make the right-edge snap to the mouse
x -= size.width; // snap to edge of mouse x -= size.width; // snap to edge of mouse
@ -122,7 +127,8 @@ public class GtkSystemTray extends SystemTray {
// we are at the top of the screen // we are at the top of the screen
if (y < 100) { if (y < 100) {
y += distanceToEdgeOfTray + 4; y += distanceToEdgeOfTray + 4;
} else { }
else {
y -= distanceToEdgeOfTray + 4; y -= distanceToEdgeOfTray + 4;
} }
@ -144,7 +150,8 @@ public class GtkSystemTray extends SystemTray {
} }
@Override @Override
public void removeTray() { public
void removeTray() {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
for (Pointer widget : this.widgets) { for (Pointer widget : this.widgets) {
libgtk.gtk_widget_destroy(widget); libgtk.gtk_widget_destroy(widget);
@ -177,15 +184,18 @@ public class GtkSystemTray extends SystemTray {
} }
@Override @Override
public void setStatus(final String infoString, String iconName) { public
void setStatus(final String infoString, String iconName) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
if (GtkSystemTray.this.connectionStatusItem == null) { if (GtkSystemTray.this.connectionStatusItem == null) {
GtkSystemTray.this.connectionStatusItem = new JMenuItem(infoString); GtkSystemTray.this.connectionStatusItem = new JMenuItem(infoString);
GtkSystemTray.this.connectionStatusItem.setEnabled(false); GtkSystemTray.this.connectionStatusItem.setEnabled(false);
GtkSystemTray.this.jmenu.add(GtkSystemTray.this.connectionStatusItem); GtkSystemTray.this.jmenu.add(GtkSystemTray.this.connectionStatusItem);
} else { }
else {
GtkSystemTray.this.connectionStatusItem.setText(infoString); GtkSystemTray.this.connectionStatusItem.setText(infoString);
} }
} }
@ -200,10 +210,12 @@ public class GtkSystemTray extends 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
*/ */
@Override @Override
public void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) { public
void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
Map<String, JMenuItem> menuEntries2 = GtkSystemTray.this.menuEntries; Map<String, JMenuItem> menuEntries2 = GtkSystemTray.this.menuEntries;
synchronized (menuEntries2) { synchronized (menuEntries2) {
@ -215,12 +227,14 @@ public class GtkSystemTray extends SystemTray {
menuEntry = new JMenuItem(menuText); menuEntry = new JMenuItem(menuText);
menuEntry.addActionListener(new ActionListener() { menuEntry.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public
void actionPerformed(ActionEvent e) {
// SystemTrayMenuPopup source = (SystemTrayMenuPopup) ((JMenuItem)e.getSource()).getParent(); // SystemTrayMenuPopup source = (SystemTrayMenuPopup) ((JMenuItem)e.getSource()).getParent();
GtkSystemTray.this.callbackExecutor.execute(new Runnable() { GtkSystemTray.this.callbackExecutor.execute(new Runnable() {
@Override @Override
public void run() { public
void run() {
callback.onClick(GtkSystemTray.this); callback.onClick(GtkSystemTray.this);
} }
}); });
@ -229,7 +243,8 @@ public class GtkSystemTray extends SystemTray {
menu.add(menuEntry); menu.add(menuEntry);
menuEntries2.put(menuText, menuEntry); menuEntries2.put(menuText, menuEntry);
} else { }
else {
updateMenuEntry(menuText, menuText, callback); updateMenuEntry(menuText, menuText, callback);
} }
} }
@ -241,10 +256,12 @@ public class GtkSystemTray extends SystemTray {
* Will update an already existing menu entry (or add a new one, if it doesn't exist) * Will update an already existing menu entry (or add a new one, if it doesn't exist)
*/ */
@Override @Override
public void updateMenuEntry(final String origMenuText, final String newMenuText, final SystemTrayMenuAction newCallback) { public
void updateMenuEntry(final String origMenuText, final String newMenuText, final SystemTrayMenuAction newCallback) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
Map<String, JMenuItem> menuEntries2 = GtkSystemTray.this.menuEntries; Map<String, JMenuItem> menuEntries2 = GtkSystemTray.this.menuEntries;
synchronized (menuEntries2) { synchronized (menuEntries2) {
@ -258,10 +275,12 @@ public class GtkSystemTray extends SystemTray {
menuEntry.addActionListener(new ActionListener() { menuEntry.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public
void actionPerformed(ActionEvent e) {
GtkSystemTray.this.callbackExecutor.execute(new Runnable() { GtkSystemTray.this.callbackExecutor.execute(new Runnable() {
@Override @Override
public void run() { public
void run() {
newCallback.onClick(GtkSystemTray.this); newCallback.onClick(GtkSystemTray.this);
} }
}); });
@ -269,7 +288,8 @@ public class GtkSystemTray extends SystemTray {
}); });
menuEntry.setText(newMenuText); menuEntry.setText(newMenuText);
menuEntry.revalidate(); menuEntry.revalidate();
} else { }
else {
addMenuEntry(origMenuText, newCallback); addMenuEntry(origMenuText, newCallback);
} }
} }

View File

@ -16,7 +16,6 @@
package dorkbox.util.tray.linux; package dorkbox.util.tray.linux;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.util.jna.linux.Gobject.GCallback; import dorkbox.util.jna.linux.Gobject.GCallback;
/** /**
@ -28,18 +27,21 @@ class MenuEntry {
public Pointer dashboardItem; public Pointer dashboardItem;
public GCallback gtkCallback; public GCallback gtkCallback;
public MenuEntry() { public
MenuEntry() {
long time = System.nanoTime(); long time = System.nanoTime();
this.hashCode = (int) (time ^ time >>> 32); this.hashCode = (int) (time ^ time >>> 32);
} }
@Override @Override
public int hashCode() { public
int hashCode() {
return this.hashCode; return this.hashCode;
} }
@Override @Override
public boolean equals(Object obj) { public
boolean equals(Object obj) {
if (this == obj) { if (this == obj) {
return true; return true;
} }

View File

@ -15,13 +15,12 @@
*/ */
package dorkbox.util.tray.swing; package dorkbox.util.tray.swing;
import java.awt.AWTException; import dorkbox.util.SwingUtil;
import java.awt.Dimension; import dorkbox.util.tray.SystemTrayMenuAction;
import java.awt.Image; import dorkbox.util.tray.SystemTrayMenuPopup;
import java.awt.Point;
import java.awt.Rectangle; import javax.swing.*;
import java.awt.SystemTray; import java.awt.*;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
@ -29,17 +28,11 @@ import java.awt.event.MouseEvent;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import dorkbox.util.SwingUtil;
import dorkbox.util.tray.SystemTrayMenuAction;
import dorkbox.util.tray.SystemTrayMenuPopup;
/** /**
* Class for handling all system tray interaction, via SWING * Class for handling all system tray interaction, via SWING
*/ */
public class SwingSystemTray extends dorkbox.util.tray.SystemTray { public
class SwingSystemTray extends dorkbox.util.tray.SystemTray {
private final Map<String, JMenuItem> menuEntries = new HashMap<String, JMenuItem>(2); private final Map<String, JMenuItem> menuEntries = new HashMap<String, JMenuItem>(2);
@ -52,14 +45,18 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
/** /**
* Creates a new system tray handler class. * Creates a new system tray handler class.
*/ */
public SwingSystemTray() {} public
SwingSystemTray() {
}
@Override @Override
public void removeTray() { public
void removeTray() {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
SwingSystemTray.this.tray.remove(SwingSystemTray.this.trayIcon); SwingSystemTray.this.tray.remove(SwingSystemTray.this.trayIcon);
SwingSystemTray.this.menuEntries.clear(); SwingSystemTray.this.menuEntries.clear();
} }
@ -69,14 +66,17 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
} }
@Override @Override
public void createTray(final String iconName) { public
void createTray(final String iconName) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
SwingSystemTray.this.tray = SystemTray.getSystemTray(); SwingSystemTray.this.tray = SystemTray.getSystemTray();
if (SwingSystemTray.this.tray == null) { if (SwingSystemTray.this.tray == null) {
logger.error("The system tray is not available"); logger.error("The system tray is not available");
} else { }
else {
SwingSystemTray.this.jmenu = new SystemTrayMenuPopup(); SwingSystemTray.this.jmenu = new SystemTrayMenuPopup();
Image trayImage = newImage(iconName); Image trayImage = newImage(iconName);
@ -85,7 +85,8 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
SwingSystemTray.this.trayIcon.addMouseListener(new MouseAdapter() { SwingSystemTray.this.trayIcon.addMouseListener(new MouseAdapter() {
@Override @Override
public void mousePressed(MouseEvent e) { public
void mousePressed(MouseEvent e) {
Dimension size = SwingSystemTray.this.jmenu.getPreferredSize(); Dimension size = SwingSystemTray.this.jmenu.getPreferredSize();
Point point = e.getPoint(); Point point = e.getPoint();
@ -96,7 +97,8 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
if (y < bounds.y) { if (y < bounds.y) {
y = bounds.y; y = bounds.y;
} else if (y + size.height > bounds.y + bounds.height) { }
else if (y + size.height > bounds.y + bounds.height) {
// our menu cannot have the top-edge snap to the mouse // our menu cannot have the top-edge snap to the mouse
// so we make the bottom-edge snap to the mouse // so we make the bottom-edge snap to the mouse
y -= size.height; // snap to edge of mouse y -= size.height; // snap to edge of mouse
@ -104,7 +106,8 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
if (x < bounds.x) { if (x < bounds.x) {
x = bounds.x; x = bounds.x;
} else if (x + size.width > bounds.x + bounds.width) { }
else if (x + size.width > bounds.x + bounds.width) {
// our menu cannot have the left-edge snap to the mouse // our menu cannot have the left-edge snap to the mouse
// so we make the right-edge snap to the mouse // so we make the right-edge snap to the mouse
x -= size.width; // snap to edge of mouse x -= size.width; // snap to edge of mouse
@ -135,15 +138,18 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
} }
@Override @Override
public void setStatus(final String infoString, final String iconName) { public
void setStatus(final String infoString, final String iconName) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
if (SwingSystemTray.this.connectionStatusItem == null) { if (SwingSystemTray.this.connectionStatusItem == null) {
SwingSystemTray.this.connectionStatusItem = new JMenuItem(infoString); SwingSystemTray.this.connectionStatusItem = new JMenuItem(infoString);
SwingSystemTray.this.connectionStatusItem.setEnabled(false); SwingSystemTray.this.connectionStatusItem.setEnabled(false);
SwingSystemTray.this.jmenu.add(SwingSystemTray.this.connectionStatusItem); SwingSystemTray.this.jmenu.add(SwingSystemTray.this.connectionStatusItem);
} else { }
else {
SwingSystemTray.this.connectionStatusItem.setText(infoString); SwingSystemTray.this.connectionStatusItem.setText(infoString);
} }
} }
@ -157,10 +163,12 @@ public class SwingSystemTray extends dorkbox.util.tray.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
*/ */
@Override @Override
public void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) { public
void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
Map<String, JMenuItem> menuEntries2 = SwingSystemTray.this.menuEntries; Map<String, JMenuItem> menuEntries2 = SwingSystemTray.this.menuEntries;
synchronized (menuEntries2) { synchronized (menuEntries2) {
@ -172,10 +180,12 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
menuEntry = new JMenuItem(menuText); menuEntry = new JMenuItem(menuText);
menuEntry.addActionListener(new ActionListener() { menuEntry.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public
void actionPerformed(ActionEvent e) {
SwingSystemTray.this.callbackExecutor.execute(new Runnable() { SwingSystemTray.this.callbackExecutor.execute(new Runnable() {
@Override @Override
public void run() { public
void run() {
callback.onClick(SwingSystemTray.this); callback.onClick(SwingSystemTray.this);
} }
}); });
@ -184,7 +194,8 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
menu.add(menuEntry); menu.add(menuEntry);
menuEntries2.put(menuText, menuEntry); menuEntries2.put(menuText, menuEntry);
} else { }
else {
updateMenuEntry(menuText, menuText, callback); updateMenuEntry(menuText, menuText, callback);
} }
} }
@ -196,10 +207,12 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
* Will update an already existing menu entry (or add a new one, if it doesn't exist) * Will update an already existing menu entry (or add a new one, if it doesn't exist)
*/ */
@Override @Override
public void updateMenuEntry(final String origMenuText, final String newMenuText, final SystemTrayMenuAction newCallback) { public
void updateMenuEntry(final String origMenuText, final String newMenuText, final SystemTrayMenuAction newCallback) {
SwingUtil.invokeAndWait(new Runnable() { SwingUtil.invokeAndWait(new Runnable() {
@Override @Override
public void run() { public
void run() {
Map<String, JMenuItem> menuEntries2 = SwingSystemTray.this.menuEntries; Map<String, JMenuItem> menuEntries2 = SwingSystemTray.this.menuEntries;
synchronized (menuEntries2) { synchronized (menuEntries2) {
@ -213,10 +226,12 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
menuEntry.addActionListener(new ActionListener() { menuEntry.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public
void actionPerformed(ActionEvent e) {
SwingSystemTray.this.callbackExecutor.execute(new Runnable() { SwingSystemTray.this.callbackExecutor.execute(new Runnable() {
@Override @Override
public void run() { public
void run() {
newCallback.onClick(SwingSystemTray.this); newCallback.onClick(SwingSystemTray.this);
} }
}); });
@ -224,7 +239,8 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
}); });
menuEntry.setText(newMenuText); menuEntry.setText(newMenuText);
menuEntry.revalidate(); menuEntry.revalidate();
} else { }
else {
addMenuEntry(origMenuText, newCallback); addMenuEntry(origMenuText, newCallback);
} }
} }