← На главную

GUI-приложение на Swing с иконкой в трее

Считается, что код, один раз написанный на Java, безо всяких проблем одинаково хорошо работает на любой платформе, под которую есть виртуальная машина. В первом приближении, если не использовать всякие там JNI, это действительно так. Но вот я, можно сказать, лишь недавно начал играться с языком, а уже успел столкнуться с ситуацией, когда на самом деле это нефига не так.

Напишем простейшую программу, использующую Swing, которая создает пустой фрейм. Также программа создает иконку в системном трее с выпадающим меню. У этого меню есть единственный пункт «Exit», завершающий работу программы. Сворачивание в трей мне было влом реализовывать, но если интересно, рецепт можно найти на StackOverflow.

У меня получился такой код:

package me.eax.examples.tray_icon_example; import java.awt.*; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.net.*; public class TrayIconExample { public static final String APPLICATION_NAME = "TrayIconExample"; public static final String ICON_STR = "/images/icon32x32.png"; public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createGUI(); } }); } private static void createGUI() { JFrame frame = new JFrame(APPLICATION_NAME); frame.setMinimumSize(new Dimension(300, 200)); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); setTrayIcon(); } private static void setTrayIcon() { if(! SystemTray.isSupported() ) { return; } PopupMenu trayMenu = new PopupMenu(); MenuItem item = new MenuItem("Exit"); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }); trayMenu.add(item); URL imageURL = TrayIconExample.class.getResource(ICON_STR); Image icon = Toolkit.getDefaultToolkit().getImage(imageURL); TrayIcon trayIcon = new TrayIcon(icon, APPLICATION_NAME, trayMenu); trayIcon.setImageAutoSize(true); SystemTray tray = SystemTray.getSystemTray(); try { tray.add(trayIcon); } catch (AWTException e) { e.printStackTrace(); } trayIcon.displayMessage(APPLICATION_NAME, "Application started!", TrayIcon.MessageType.INFO); } }

Если запустить эту программу под Windows, то все вполне себе чинно-блинно и работает в соответствии с ожиданиям:

GUI-приложение на Swing с иконкой в трее под Windows

А вот то же приложение, запущенное под Ubuntu Linux в дектоп-окружении Unity:

GUI-приложение на Swing с иконкой в трее под Ubuntu Linux

Налицо сразу множество проблем: (1) фон у иконки серый вместо прозрачного, (2) выпадающее меню выглядит не нативно, (3) а всплывающее сообщение выглядит, как говно. И это еще не самый худший вариант! Под Unity на другой машине всплывающее сообщение вовсе не появляется, а выпадающее меню рисуется как бы зачеркнутым, потому что поверх него рендерится кусок таскбара. Под Gnome не появляется иконка, а всплывающее сообщение появляется где-то чуть ниже нижней границы экрана, при условии, что таскбар расположен вверху. Под i3 с его мизерным таскбаром иконка превращается в желтый квадратик с черной точкой, а пункт меню невозможно прочитать, потому что поверх него отображается всплывающая подсказка нашей иконки.

В общем, иконки в трее под Linux абсолютно неюзабельны. Притом, похоже, что проблема известна уже не менее четырех лет, так что на скорое ее исправление надеяться не приходится.

Какими костылями можно это подпереть:

  • Заюзать JNI, отказавшись тем самым от одной из главных фичей Java;
  • Таскать за собой нативные приложения под N поддерживаемых платформ, которые будут рисовать нативные иконки и менюшки, взаимодействуя с ними через stdin/stdout или еще как-то;
  • Не использовать прозрачность и меню, а также при запуске под Linux показывать уведомления через notify-send, плюс предоставить пользователю возможность указать в настройках альтернативное приложение;
  • Под Linux по умолчанию ничего не делать с треем и спрятать соответствующую галочку поглубже в настройках приложения;
  • Никогда и ничего не делать с треем, там и без нас уже полно иконок;

Короче, с кроссплатформенностью в Java не все так здорово, как нас пытаются заверить. И это я только поигрываюсь с Java в свободное время. Более опытные Java-разработчики наверняка без труда назовут еще немало примеров. Невольно начинаешь задумываться, а не обман ли вся эта ваша JIT-компиляция? Мало того, что я, несчастный пользователь, должен устанавливать виртуальную машину, так мне еще приходится тратить процессорное время на то, чтобы скомпилировать программу. Выходит, разработчики прислали мне какой-то полуфабрикат? Самим скомпилировать лень было? Да и с точки зрения разработчиков уж не проще ли самостоятельно собирать нативное приложение под N целевых платформ и ни в чем себя не ограничивать?

Исходники к этой заметке лежат тут. Как можно сделать иконку для трея в Gimp рассказано здесь.

Дополнение: Запустили jar-ник под маками, вроде там все норм.