java-tray-icon/

Считается, что код, один раз написанный на 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-ник под маками, вроде там все норм.

Похожая запись

EnglishRussianUkrainian