forked from dorkbox/SystemTray
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:
parent
fda29df750
commit
e56ed06314
2
LICENSE
2
LICENSE
|
@ -19,5 +19,5 @@
|
|||
|
||||
|
||||
- SLF4J - MIT License
|
||||
http://www.slf4j.org/
|
||||
http://www.slf4j.org
|
||||
Copyright 2004-2008, QOS.ch
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user