Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java SystemTray icon does not always work

I need your help please: I'm working on a little Java application (Java version 7) which has to be minimized into the system tray.

I'm using Class SystemTray, with SystemTray.isSupported(), then

SystemTray systemTray = SystemTray.getSystemTray();
ImageIcon icon = new javax.swing.ImageIcon(getClass().getResource("icon.png"));

[...]

systemTray.add(trayIcon);

(With popup of course)

On Windows, it's working great. On XFCE, Xubuntu, no problem, icon is working with popup. However on KDE and Gnome shell... it doesn't work.

KDE (4.14.1)

(Qt: 4.8.6 Tools Plasma: 4.11.12)

SystemTray.isSupported() = true and when the program arrived at the line: systemTray.add(trayIcon); An exception is caught:

Error during Tray process: java.awt.AWTException: TrayIcon couldn't be displayed.

Thereby the icon is white, and doesn't work when user clicks on it, no popup.

Gnome Shell (3.12.2)

SystemTray.isSupported() = true, the icon is located on notification area at the bottom, but mouse events don't work...

To fix these problem, I thought SWT could be a good idea. But when I implemented it (last version), I've got this warning:

WARNING **: Couldn't connect to accessibility bus: Failed to connect to socket /tmp/[...]

And it doesn't work... Edit: not anymore, I can fix the problem of SWT with an external class. The warning is not caused by SWT, but environment system probably (I had the same warning with other applications in the terminal).


So now, what can I do? I think to check environment system with System.getenv("XDG_CURRENT_DESKTOP") & System.getenv("GDMSESSION") and then enable or disable system tray if it is KDE or Gnome 3... but this solution is not really good because of it is a local solution for multi-platform (in function of OS I mean), and not a global solution (one method for all OS)...

So, other idea? I don't know... is there a way to define an embedded JWindow into the system tray?

like image 627
Drimux Avatar asked May 01 '15 21:05

Drimux


1 Answers

I have run up against this problem myself, and as I recall I ran up against a brick wall in sorting it out with a legitimate solution. I traced the problem to a call to the TrayIcon.addNotify() method randomly failing. I seem to recall this was because of a race condition in the internals where a call to the X11 system was taking too long to complete so the java side was giving up.

But if you have a ninja PC with a decent graphics card you would probably never meet this situation, which is probably why it hasn't been fixed yet. My dev machine is on the slow side so it was happening to me about 50% of the time.

I did hack a quick and dirty solution together, which involves trying to call addNotify repeatedly (with a pause inbetween each attempt) until it succeeds (or has failed a maximum number of times). Unfortunately the only way to do this was via reflection as the addNotify method is package-private.

Code follows:

public class HackyLinuxTrayIconInitialiser extends SwingWorker<Void, TrayIcon> {
    private static final int    MAX_ADD_ATTEMPTS    = 4;
    private static final long   ADD_ICON_DELAY      = 200;
    private static final long   ADD_FAILED_DELAY    = 1000;

    private TrayIcon[]  icons;

    public HackyLinuxTrayIconInitialiser(TrayIcon... ic) {
        icons = ic;
    }

    @Override
    protected Void doInBackground() {
        try {
            Method addNotify = TrayIcon.class.getDeclaredMethod("addNotify", (Class<?>[]) null);
            addNotify.setAccessible(true);
            for (TrayIcon icon : icons) {
                for (int attempt = 1; attempt < MAX_ADD_ATTEMPTS; attempt++) {
                    try {
                        addNotify.invoke(icon, (Object[]) null);
                        publish(icon);
                        pause(ADD_ICON_DELAY);
                        break;
                    } catch (NullPointerException | IllegalAccessException | IllegalArgumentException e) {
                        System.err.println("Failed to add icon. Giving up.");
                        e.printStackTrace();
                        break;
                    } catch (InvocationTargetException e) {
                        System.err.println("Failed to add icon, attempt " + attempt);
                        pause(ADD_FAILED_DELAY);
                    }
                }
            }
        } catch (NoSuchMethodException | SecurityException | NoSuchFieldException e1) {
            Log.err(e1);
        }
        return null;
    }

    private void pause(long delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException e1) {
            Log.err(e1);
        }
    }

    @Override
    protected void process(List<TrayIcon> icons) {
        for (TrayIcon icon : icons) {
            try {
                tray.add(icon);
            } catch (AWTException e) {
                Log.err(e);
            }
        }
    }
}

To use it, just call:

if (<OS is Linux>) {
    new HackyLinuxTrayIconInitialiser(ticon, micon, licon).execute();
} else {
    try {
        tray.add(ticon);
        tray.add(micon);
        tray.add(licon);
    } catch (AWTException e) {
        Log.err(e);
    }
}

I seem to recall at the time I couldn't just keep calling SystemTray.add(icon) as it this would leave "ghost" trayicons behind on the system tray if I did.

Hope this helps.

like image 80
Julian Wright Avatar answered Nov 14 '22 23:11

Julian Wright