Updated readme to include various options. Fixed GTK tray (not appindicator). Cleaned up code. Cleaned up positioning. Added MUCH better detection of app-indicator for linux.

This commit is contained in:
nathan 2015-01-23 02:52:09 +01:00
parent fda29df750
commit e56ed06314
7 changed files with 155 additions and 63 deletions

View File

@ -19,5 +19,5 @@
- SLF4J - MIT License
http://www.slf4j.org/
http://www.slf4j.org
Copyright 2004-2008, QOS.ch

View File

@ -17,7 +17,16 @@ There are a number of problems on Linux with the Swing (and SWT) system-tray ico
This is for cross-platform use, specifically - linux 32/64, mac 32/64, and windows 32/64. Java 6+
```
To customize the delay (for hiding the popup) when the cursor is "moused out" of the
popup menu, change the value of 'SystemTrayMenuPopup.hidePopupDelay'
Not all system tray icons are the same size (default is 22px), so to properly scale the icon
to fit, change the value of 'SystemTray.TRAY_SIZE'
You might want to specify the root location of the icons used (to make it easier when
specifying icons), change the value of 'SystemTray.ICON_PATH'
```
```
Note: This library does NOT use SWT for system-tray support, only for the purpose
of lessening the jar dependencies. Changing it to be SWT-based is not be

View File

@ -15,8 +15,10 @@
*/
package dorkbox.util.tray;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -52,9 +54,9 @@ public abstract class SystemTray {
protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
/**
* Size of the icon
* Size of the icon WHEN IT'S IN THE TRAY
*/
public static int ICON_SIZE = 22;
public static int TRAY_SIZE = 22;
/**
* Location of the icon
@ -67,13 +69,62 @@ public abstract class SystemTray {
static {
if (OS.isLinux()) {
GtkSupport.init();
String getenv = System.getenv("XDG_CURRENT_DESKTOP");
if (getenv != null && (getenv.equals("Unity") || getenv.equals("KDE"))) {
if (GtkSupport.isSupported) {
trayType = AppIndicatorTray.class;
if (GtkSupport.isSupported) {
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
String getenv = System.getenv("XDG_CURRENT_DESKTOP");
if (getenv != null && getenv.equals("Unity")) {
try {
trayType = AppIndicatorTray.class;
} catch (Exception ignored) {
}
}
} else {
if (GtkSupport.isSupported) {
if (trayType == null) {
BufferedReader bin = null;
try {
// the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator),
// is to look through all /proc/<pid>/status, and first line should be Name:\tindicator-appli
File proc = new File("/proc");
File[] listFiles = proc.listFiles();
if (listFiles != null) {
for (File procs : listFiles) {
String name = procs.getName();
if (!Character.isDigit(name.charAt(0))) {
continue;
}
File status = new File(procs, "status");
if (!status.canRead()) {
continue;
}
try {
bin = new BufferedReader(new FileReader(status));
String readLine = bin.readLine();
if (readLine != null && readLine.contains("indicator-app")) {
trayType = AppIndicatorTray.class;
break;
}
} finally {
if (bin != null) {
bin.close();
bin = null;
}
}
}
}
} catch (Exception ignored) {
} finally {
if (bin != null) {
try {
bin.close();
} catch (IOException ignored) {
}
}
}
}
if (trayType == null) {
trayType = GtkSystemTray.class;
}
}

View File

@ -14,10 +14,17 @@ import dorkbox.util.SwingUtil;
public class SystemTrayMenuPopup extends JPopupMenu {
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 */
public static long hidePopupDelay = 1000L;
private DelayTimer timer;
protected boolean mouseStillOnMenu;
// private JDialog hiddenDialog;
public SystemTrayMenuPopup() {
super();
setFocusable(true);
this.timer = new DelayTimer("PopupMenuHider", true, new DelayTimer.Callback() {
@Override
@ -48,18 +55,35 @@ public class SystemTrayMenuPopup extends JPopupMenu {
SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay());
}
});
}
// Does not work correctly on linux. a window in the taskbar shows up.
/* Initialize the hidden dialog as a headless, titleless dialog window */
// this.hiddenDialog = new JDialog((Frame)null);
// this.hiddenDialog.setEnabled(false);
// this.hiddenDialog.setUndecorated(true);
//
// this.hiddenDialog.setSize(5, 5);
// /* Add the window focus listener to the hidden dialog */
// this.hiddenDialog.addWindowFocusListener(new WindowFocusListener () {
// @Override
// public void windowLostFocus (WindowEvent we ) {
// SystemTrayMenuPopup.this.setVisible(false);
// }
// @Override
// public void windowGainedFocus (WindowEvent we) {}
// });
}
@Override
public void setVisible(boolean b) {
public void setVisible(boolean makeVisible) {
this.timer.cancel();
if (b) {
// if the mouse isn't inside the popup in 5 seconds, close the popup
this.timer.delay(5000L);
if (makeVisible) {
// if the mouse isn't inside the popup in x seconds, close the popup
this.timer.delay(hidePopupDelay);
}
super.setVisible(b);
// this.hiddenDialog.setVisible(makeVisible);
super.setVisible(makeVisible);
}
}

View File

@ -25,6 +25,7 @@ import com.sun.jna.Pointer;
import dorkbox.util.jna.linux.AppIndicator;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction;
@ -127,7 +128,7 @@ public class AppIndicatorTray extends SystemTray {
}
this.connectionStatusItem = null;
libgtk.gtk_main_quit();
GtkSupport.shutdownGTK();
libgtk.gdk_threads_leave();
super.removeTray();

View File

@ -16,6 +16,8 @@
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;
@ -31,6 +33,7 @@ import dorkbox.util.SwingUtil;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.jna.linux.Gtk.GdkEventButton;
import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction;
import dorkbox.util.tray.SystemTrayMenuPopup;
@ -53,55 +56,80 @@ public class GtkSystemTray extends SystemTray {
// need to hang on to these to prevent gc
private final List<Pointer> widgets = new ArrayList<Pointer>(4);
private Gobject.GEventCallback gtkCallback;
public GtkSystemTray() {
}
@Override
public void createTray(String iconName) {
SwingUtil.invokeAndWait(new Runnable() {
@Override
public void run() {
GtkSystemTray.this.jmenu = new SystemTrayMenuPopup();
}
});
libgtk.gdk_threads_enter();
this.trayIcon = libgtk.gtk_status_icon_new();
libgtk.gtk_status_icon_set_from_file(this.trayIcon, iconPath(iconName));
libgtk.gtk_status_icon_set_tooltip(this.trayIcon, this.appName);
libgtk.gtk_status_icon_set_visible(this.trayIcon, true);
Gobject.GEventCallback gtkCallback = new Gobject.GEventCallback() {
// have to make this a field, to prevent GC on this object
this.gtkCallback = new Gobject.GEventCallback() {
@Override
public void callback(Pointer instance, final GdkEventButton event) {
// BUTTON_PRESS only
public void callback(Pointer system_tray, final GdkEventButton event) {
// BUTTON_PRESS only (any mouse click)
if (event.type == 4) {
SwingUtil.invokeLater(new Runnable() {
@Override
public void run() {
// test this using cinnamon (which still uses status icon)
if (GtkSystemTray.this.jmenu.isVisible()) {
GtkSystemTray.this.jmenu.setVisible(false);
} else {
int iconX = (int) (event.x_root - event.x);
int iconY = (int) (event.y_root - event.y);
// System.err.println("x: " + iconX + " y: " + iconY);
// System.err.println("x1: " + event.x_root + " y1: " + event.y_root); // relative to SCREEN
// System.err.println("x2: " + event.x + " y2: " + event.y); // relative to WINDOW
Dimension size = GtkSystemTray.this.jmenu.getPreferredSize();
// do we open at top-right or top-left?
// we ASSUME monitor size is greater than 640x480 AND that our tray icon is IN THE CORNER SOMEWHERE
int x = (int) event.x_root;
int y = (int) event.y_root;
// always put the menu in the middle
iconX -= size.width / 2;
Point point = new Point(x, y);
Rectangle bounds = SwingUtil.getScreenBoundsAt(point);
// y = 2 -> top
// y = 1068 -> bottom
if (iconY > 240) {
iconY -= size.height;
if (y < bounds.y) {
y = bounds.y;
} else if (y + size.height > bounds.y + bounds.height) {
// our menu cannot have the top-edge snap to the mouse
// so we make the bottom-edge snap to the mouse
y -= size.height; // snap to edge of mouse
}
if (x < bounds.x) {
x = bounds.x;
} else if (x + size.width > bounds.x + bounds.width) {
// our menu cannot have the left-edge snap to the mouse
// so we make the right-edge snap to the mouse
x -= size.width; // snap to edge of mouse
}
// SMALL problem, is that on linux, the popup is BEHIND the tray bar!
// to solve the problem, we anchor the popup above (or below) the tray bar
int distanceToEdgeOfTray = (int) event.y;
// System.err.println(" distance: " + distanceToEdgeOfTray);
// we are at the top of the screen
if (y < 100) {
y += distanceToEdgeOfTray + 4;
} else {
// have to account for the icon
iconY += ICON_SIZE;
y -= distanceToEdgeOfTray + 4;
}
GtkSystemTray.this.jmenu.setInvoker(GtkSystemTray.this.jmenu);
GtkSystemTray.this.jmenu.setLocation(iconX, iconY);
GtkSystemTray.this.jmenu.setLocation(x, y);
GtkSystemTray.this.jmenu.setVisible(true);
GtkSystemTray.this.jmenu.requestFocus();
}
}
});
@ -109,14 +137,8 @@ public class GtkSystemTray extends SystemTray {
}
};
// all the clicks. This is because native menu popups are a pain to figure out, so we cheat and use some java bits to do the popup
libgobject.g_signal_connect_data(this.trayIcon, "button_press_event", gtkCallback, null, null, 0);
libgobject.g_signal_connect_data(this.trayIcon, "button_press_event", this.gtkCallback, null, null, 0);
libgtk.gdk_threads_leave();
SwingUtil.invokeAndWait(new Runnable() {
@Override
public void run() {
GtkSystemTray.this.jmenu = new SystemTrayMenuPopup();
}
});
this.active = true;
}
@ -148,7 +170,7 @@ public class GtkSystemTray extends SystemTray {
this.jmenu = null;
this.connectionStatusItem = null;
libgtk.gtk_main_quit();
GtkSupport.shutdownGTK();
libgtk.gdk_threads_leave();
super.removeTray();
@ -194,6 +216,8 @@ public class GtkSystemTray extends SystemTray {
menuEntry.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// SystemTrayMenuPopup source = (SystemTrayMenuPopup) ((JMenuItem)e.getSource()).getParent();
GtkSystemTray.this.callbackExecutor.execute(new Runnable() {
@Override
public void run() {

View File

@ -75,7 +75,7 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
public void run() {
SwingSystemTray.this.tray = SystemTray.getSystemTray();
if (SwingSystemTray.this.tray == null) {
logger.warn("The system tray is not available");
logger.error("The system tray is not available");
} else {
SwingSystemTray.this.jmenu = new SystemTrayMenuPopup();
@ -91,9 +91,6 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
Point point = e.getPoint();
Rectangle bounds = SwingUtil.getScreenBoundsAt(point);
// linux gtk was ICON_SIZE+4
int PADDING = ICON_SIZE/2;
int x = point.x;
int y = point.y;
@ -113,20 +110,6 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
x -= size.width; // snap to edge of mouse
}
// if (x + size.width > bounds.x + bounds.width) {
// // always put the menu in the middle
// x = bounds.x + bounds.width - size.width;
// }
// if (y + size.height > bounds.y + bounds.height) {
// y = bounds.y + bounds.height - size.height - PADDING;
// }
// do we open at top-right or top-left?
// we ASSUME monitor size is greater than 640x480 AND that our tray icon is IN THE CORNER SOMEWHERE
// always put the menu in the horiz. middle
// x -= size.width / 4;
SwingSystemTray.this.jmenu.setInvoker(SwingSystemTray.this.jmenu);
SwingSystemTray.this.jmenu.setLocation(x, y);
SwingSystemTray.this.jmenu.setVisible(true);
@ -148,7 +131,7 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray {
Image newImage(String name) {
String iconPath = iconPath(name);
return new ImageIcon(iconPath).getImage().getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH);
return new ImageIcon(iconPath).getImage().getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
}
@Override