aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/osdependent/systemtray
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/sip/communicator/impl/osdependent/systemtray')
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java156
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java42
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicator1.java189
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java78
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java695
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java90
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java68
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTMouseAdapter.java124
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java78
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java131
10 files changed, 1651 insertions, 0 deletions
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java
new file mode 100644
index 0000000..818d1e7
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java
@@ -0,0 +1,156 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+import org.jitsi.util.*;
+
+import net.java.sip.communicator.impl.osdependent.*;
+import net.java.sip.communicator.impl.osdependent.systemtray.appindicator.*;
+import net.java.sip.communicator.impl.osdependent.systemtray.awt.*;
+import net.java.sip.communicator.service.systray.*;
+import net.java.sip.communicator.util.Logger;
+
+/**
+ * Base class for all wrappers of <tt>SystemTray</tt> implementations.
+ */
+public abstract class SystemTray
+{
+ private static final Logger logger = Logger.getLogger(SystemTray.class);
+ private static SystemTray systemTray;
+ private static final String DISABLED_TRAY_MODE = "disabled";
+
+ /**
+ * Gets or creates the supported <tt>SystemTray</tt> implementations.
+ * @return a <tt>SystemTray</tt> implementation for the current platform.
+ */
+ public final static SystemTray getSystemTray()
+ {
+ if (systemTray == null)
+ {
+ String mode = getSystemTrayMode();
+ logger.info("Tray for " + mode + " requested");
+ switch (mode)
+ {
+ case DISABLED_TRAY_MODE:
+ return null;
+ case "native":
+ if (java.awt.SystemTray.isSupported())
+ {
+ systemTray = new AWTSystemTray();
+ }
+
+ break;
+ case "appindicator":
+ try
+ {
+ systemTray = new AppIndicatorTray(true);
+ }
+ catch(Exception ex)
+ {
+ logger.error("AppIndicator tray not available", ex);
+ }
+ break;
+ case "appindicator_static":
+ try
+ {
+ systemTray = new AppIndicatorTray(false);
+ }
+ catch(Exception ex)
+ {
+ logger.error("AppIndicator tray not available", ex);
+ }
+
+ break;
+ }
+
+ if (systemTray == null)
+ {
+ OsDependentActivator.getConfigurationService()
+ .setProperty(SystrayService.PNMAE_TRAY_MODE, "disabled");
+ }
+ }
+
+ return systemTray;
+ }
+
+ public static String getSystemTrayMode()
+ {
+ String defaultTrayMode = DISABLED_TRAY_MODE;
+ if (GraphicsEnvironment.isHeadless())
+ {
+ return DISABLED_TRAY_MODE;
+ }
+
+ // setting from cmd-line: request to disable tray in case it failed
+ if (Boolean.getBoolean("disable-tray"))
+ {
+ OsDependentActivator.getConfigurationService().setProperty(
+ SystrayService.PNMAE_TRAY_MODE, DISABLED_TRAY_MODE);
+ }
+
+ if (OSUtils.IS_WINDOWS || OSUtils.IS_MAC)
+ {
+ defaultTrayMode = "native";
+ }
+
+ return OsDependentActivator.getConfigurationService()
+ .getString(SystrayService.PNMAE_TRAY_MODE, defaultTrayMode);
+ }
+
+ /**
+ * Adds a <tt>TrayIcon</tt> to this system tray implementation.
+ *
+ * @param trayIcon the <tt>TrayIcon</tt> to add
+ */
+ public abstract void addTrayIcon(TrayIcon trayIcon);
+
+ /**
+ * Creates an implementation specific <tt>TrayIcon</tt> that can later be
+ * added with {@link #addTrayIcon(TrayIcon)}.
+ *
+ * @param image the <tt>Image</tt> to be used
+ * @param tooltip the string to be used as tooltip text; if the value is
+ * <tt>null</tt> no tooltip is shown
+ * @param popup the menu to be used for the tray icon's popup menu; if the
+ * value is <tt>null</tt> no popup menu is shown
+ * @return a <tt>TrayIcon</tt> instance for this <tt>SystemTray</tt>
+ * implementation.
+ */
+ public abstract TrayIcon createTrayIcon(ImageIcon icon, String tooltip,
+ Object popup);
+
+ /**
+ * Determines if the popup menu for the icon is to be a Swing
+ * <tt>JPopupMenu</tt> or an AWT <tt>PopupMenu</tt>
+ *
+ * @return <tt>true</tt> for a <tt>JPopupMenu</tt>, <tt>false</tt> for a
+ * <tt>PopupMenu</tt>
+ */
+ public abstract boolean useSwingPopupMenu();
+
+ /**
+ * Determines if the tray icon supports dynamic menus.
+ *
+ * @return True if the menu can be changed while running, false otherwise.
+ */
+ public abstract boolean supportsDynamicMenu();
+}
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java
new file mode 100644
index 0000000..78df44c
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java
@@ -0,0 +1,42 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray;
+
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * Interface for all platform specific TrayIcon implementations. See
+ * {@link java.awt.TrayIcon} for a description of the methods.
+ *
+ * @author Lubomir Marinov
+ */
+public interface TrayIcon
+{
+ public void setDefaultAction(Object menuItem);
+
+ public void addBalloonActionListener(ActionListener listener);
+
+ public void displayMessage(String caption, String text,
+ java.awt.TrayIcon.MessageType messageType);
+
+ public void setIcon(ImageIcon icon) throws NullPointerException;
+
+ public void setIconAutoSize(boolean autoSize);
+}
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicator1.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicator1.java
new file mode 100644
index 0000000..85fbd73
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicator1.java
@@ -0,0 +1,189 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.appindicator;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.sun.jna.*;
+
+/**
+ * JNA mappings for libappindicator1.
+ *
+ * @author Ingo Bauersachs
+ */
+interface AppIndicator1 extends Library
+{
+ static final AppIndicator1 INSTANCE =
+ (AppIndicator1) Native.loadLibrary("appindicator", AppIndicator1.class);
+
+ static final String APP_INDICATOR_SIGNAL_NEW_ICON = "new-icon";
+ static final String APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON = "new-attention-icon";
+ static final String APP_INDICATOR_SIGNAL_NEW_STATUS = "new-status";
+ static final String APP_INDICATOR_SIGNAL_NEW_LABEL = "new-label";
+ static final String APP_INDICATOR_SIGNAL_CONNECTION_CHANGED = "connection-changed";
+ static final String APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH = "new-icon-theme-path";
+ static final String APP_INDICATOR_SIGNAL_SCROLL_EVENT = "scroll-event";
+
+ /**
+ * The category provides grouping for the indicators so that users can find
+ * indicators that are similar together.
+ */
+ enum APP_INDICATOR_CATEGORY
+ {
+ /** The indicator is used to display the status of the application. */
+ APPLICATION_STATUS,
+
+ /** The application is used for communication with other people. */
+ COMMUNICATIONS,
+
+ /** A system indicator relating to something in the user's system. */
+ SYSTEM_SERVICES,
+
+ /** An indicator relating to the user's hardware. */
+ HARDWARE,
+
+ /**
+ * Something not defined in this enum, please don't use unless you
+ * really need it.
+ */
+ OTHER
+ }
+
+ /**
+ * These are the states that the indicator can be on in the user's panel.
+ * The indicator by default starts in the state {@link #PASSIVE} and can be
+ * shown by setting it to {@link #ACTIVE}.
+ */
+ enum APP_INDICATOR_STATUS
+ {
+ /** The indicator should not be shown to the user. */
+ PASSIVE,
+
+ /** The indicator should be shown in it's default state. */
+ ACTIVE,
+
+ /** The indicator should show it's attention icon. */
+ ATTENTION
+ }
+
+ class AppIndicatorClass extends Structure
+ {
+ // Parent
+ public /*Gobject.GObjectClass*/ Pointer parent_class;
+
+ // DBus Signals
+ public Pointer new_icon;
+ public Pointer new_attention_icon;
+ public Pointer new_status;
+ public Pointer new_icon_theme_path;
+ public Pointer new_label;
+
+ // Local Signals
+ public Pointer connection_changed;
+ public Pointer scroll_event;
+ public Pointer app_indicator_reserved_ats;
+
+ // Overridable Functions
+ public Pointer fallback;
+ public Pointer unfallback;
+
+ // Reserved
+ public Pointer app_indicator_reserved_1;
+ public Pointer app_indicator_reserved_2;
+ public Pointer app_indicator_reserved_3;
+ public Pointer app_indicator_reserved_4;
+ public Pointer app_indicator_reserved_5;
+ public Pointer app_indicator_reserved_6;
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList(
+ "parent_class",
+ "new_icon",
+ "new_attention_icon",
+ "new_status",
+ "new_icon_theme_path",
+ "new_label",
+
+ "connection_changed",
+ "scroll_event",
+ "app_indicator_reserved_ats",
+
+ "fallback",
+ "unfallback",
+
+ "app_indicator_reserved_1",
+ "app_indicator_reserved_2",
+ "app_indicator_reserved_3",
+ "app_indicator_reserved_4",
+ "app_indicator_reserved_5",
+ "app_indicator_reserved_6");
+ }
+ }
+
+ class AppIndicator extends Structure
+ {
+ public /*Gobject.GObject*/ Pointer parent;
+ public Pointer priv;
+
+ @Override
+ protected List getFieldOrder()
+ {
+ return Arrays.asList("parent", "priv");
+ }
+ }
+
+ // GObject Stuff
+ NativeLong app_indicator_get_type();
+ AppIndicator app_indicator_new(String id, String icon_name, int category);
+ AppIndicator app_indicator_new_with_path(String id, String icon_name, int category, String icon_theme_path);
+
+ // Set properties
+ void app_indicator_set_status(AppIndicator self, int status);
+ void app_indicator_set_attention_icon(AppIndicator self, String icon_name);
+ void app_indicator_set_attention_icon_full(AppIndicator self, String name, String icon_desc);
+ void app_indicator_set_menu(AppIndicator self, Pointer menu);
+ void app_indicator_set_icon(AppIndicator self, String icon_name);
+ void app_indicator_set_icon_full(AppIndicator self, String icon_name, String icon_desc);
+ void app_indicator_set_label(AppIndicator self, String label, String guide);
+ void app_indicator_set_icon_theme_path(AppIndicator self, String icon_theme_path);
+ void app_indicator_set_ordering_index(AppIndicator self, int ordering_index);
+ void app_indicator_set_secondary_activate_target(AppIndicator self, Pointer menuitem);
+ void app_indicator_set_title(AppIndicator self, String title);
+
+ // Get properties
+ String app_indicator_get_id(AppIndicator self);
+ int app_indicator_get_category(AppIndicator self);
+ int app_indicator_get_status(AppIndicator self);
+ String app_indicator_get_icon(AppIndicator self);
+ String app_indicator_get_icon_desc(AppIndicator self);
+ String app_indicator_get_icon_theme_path(AppIndicator self);
+ String app_indicator_get_attention_icon(AppIndicator self);
+ String app_indicator_get_attention_icon_desc(AppIndicator self);
+ String app_indicator_get_title(AppIndicator self);
+
+ Pointer app_indicator_get_menu(AppIndicator self);
+ String app_indicator_get_label(AppIndicator self);
+ String app_indicator_get_label_guide(AppIndicator self);
+ int app_indicator_get_ordering_index(AppIndicator self);
+ Pointer app_indicator_get_secondary_activate_target(AppIndicator self, Pointer widget);
+
+ // Helpers
+ void app_indicator_build_menu_from_desktop(AppIndicator self, String desktop_file, String destkop_profile);
+} \ No newline at end of file
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java
new file mode 100644
index 0000000..90c949a
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java
@@ -0,0 +1,78 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.appindicator;
+
+import javax.swing.*;
+
+import org.jitsi.util.*;
+
+import net.java.sip.communicator.impl.osdependent.*;
+import net.java.sip.communicator.impl.osdependent.systemtray.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Jitsi system tray abstraction for libappindicator.
+ *
+ * @author Ingo Bauersachs
+ */
+public class AppIndicatorTray extends SystemTray
+{
+ private boolean dynamicMenu;
+
+ public AppIndicatorTray(boolean dynamicMenu) throws Exception
+ {
+ this.dynamicMenu = dynamicMenu;
+ try
+ {
+ // pre-initialize the JNA libraries before attempting to use them
+ AppIndicator1.INSTANCE.toString();
+ Gtk.INSTANCE.toString();
+ Gobject.INSTANCE.toString();
+ Gtk.INSTANCE.gtk_init(0, null);
+ }
+ catch (Throwable t)
+ {
+ throw new Exception("AppIndicator1 tray icon not available", t);
+ }
+ }
+
+ @Override
+ public void addTrayIcon(TrayIcon trayIcon)
+ {
+ ((AppIndicatorTrayIcon) trayIcon).createTray();
+ }
+
+ @Override
+ public TrayIcon createTrayIcon(ImageIcon icon, String tooltip, Object popup)
+ {
+ return new AppIndicatorTrayIcon(icon, tooltip, (JPopupMenu) popup);
+ }
+
+ @Override
+ public boolean useSwingPopupMenu()
+ {
+ // we want icons
+ return true;
+ }
+
+ @Override
+ public boolean supportsDynamicMenu()
+ {
+ return dynamicMenu;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java
new file mode 100644
index 0000000..12334f3
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java
@@ -0,0 +1,695 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.appindicator;
+
+import java.awt.*;
+import java.awt.TrayIcon.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.beans.*;
+import java.io.*;
+import java.net.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.List;
+import java.util.Timer;
+
+import javax.accessibility.*;
+import javax.imageio.*;
+import javax.imageio.stream.*;
+import javax.print.attribute.standard.*;
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.jitsi.util.*;
+import org.jitsi.util.Logger;
+
+import com.sun.jna.*;
+
+import net.java.sip.communicator.impl.osdependent.*;
+import net.java.sip.communicator.impl.osdependent.systemtray.*;
+import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon;
+import net.java.sip.communicator.impl.osdependent.systemtray.appindicator.Gobject.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * System tray icon implementation based on libappindicator1.
+ *
+ * @author Ingo Bauersachs
+ */
+class AppIndicatorTrayIcon implements TrayIcon
+{
+ private static final Logger logger =
+ Logger.getLogger(AppIndicatorTrayIcon.class);
+
+ // shortcuts
+ private static Gobject gobject = Gobject.INSTANCE;
+ private static Gtk gtk = Gtk.INSTANCE;
+ private static AppIndicator1 ai = AppIndicator1.INSTANCE;
+
+ // references to the root menu and the native icon
+ private ImageIcon mainIcon;
+ private String title;
+ private JPopupMenu popup;
+ private Map<String, String> extractedFiles = new HashMap<>();
+ private PopupMenuPeer popupPeer;
+ private AppIndicator1.AppIndicator appIndicator;
+
+ private PopupMenuPeer defaultMenuPeer;
+
+ public AppIndicatorTrayIcon(ImageIcon mainIcon, String title,
+ JPopupMenu popup)
+ {
+ this.mainIcon = mainIcon;
+ this.title = title;
+ this.popup = popup;
+ this.popupPeer = null;
+ }
+
+ /**
+ * Combines the references of each swing menu item with the GTK counterpart
+ */
+ private class PopupMenuPeer implements ContainerListener
+ {
+ public PopupMenuPeer(PopupMenuPeer parent, Component em)
+ {
+ menuItem = em;
+
+ // if this menu item is a submenu, add ourselves as listener to
+ // add or remove the native counterpart
+ if (em instanceof JMenu)
+ {
+ ((JMenu)em).getPopupMenu().addContainerListener(this);
+ ((JMenu)em).addContainerListener(this);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable
+ {
+ super.finalize();
+ if (isDefaultMenuItem)
+ {
+ gobject.g_object_unref(gtkMenuItem);
+ }
+ }
+
+ public List<PopupMenuPeer> children = new ArrayList<>();
+ public Pointer gtkMenuItem;
+ public Pointer gtkMenu;
+ public Pointer gtkImage;
+ public Memory gtkImageBuffer;
+ public Pointer gtkPixbuf;
+ public Component menuItem;
+ public MenuItemSignalHandler signalHandler;
+ public long gtkSignalHandler;
+ public boolean isDefaultMenuItem;
+
+ @Override
+ public void componentAdded(ContainerEvent e)
+ {
+ AppIndicatorTrayIcon.this.printMenu(popup.getComponents(), 1);
+ gtk.gdk_threads_enter();
+ try
+ {
+ createGtkMenuItems(this, new Component[]{e.getChild()});
+ gtk.gtk_widget_show_all(popupPeer.gtkMenu);
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+ }
+
+ @Override
+ public void componentRemoved(ContainerEvent e)
+ {
+ AppIndicatorTrayIcon.this.printMenu(popup.getComponents(), 1);
+ for (PopupMenuPeer c : children)
+ {
+ if (c.menuItem == e.getChild())
+ {
+ gtk.gdk_threads_enter();
+ try
+ {
+ cleanMenu(c);
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+
+ children.remove(c);
+ break;
+ }
+ }
+ }
+ }
+
+ public void createTray()
+ {
+ gtk.gdk_threads_enter();
+ try
+ {
+ setupGtkMenu();
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+
+ new Thread()
+ {
+ public void run()
+ {
+ gtk.gtk_main();
+ }
+ }.start();
+ }
+
+ private void setupGtkMenu()
+ {
+ File iconFile = new File(imageIconToPath(mainIcon));
+ appIndicator = ai.app_indicator_new_with_path(
+ "jitsi",
+ iconFile.getName().replaceFirst("[.][^.]+$", ""),
+ AppIndicator1.APP_INDICATOR_CATEGORY.COMMUNICATIONS.ordinal(),
+ iconFile.getParent());
+
+ ai.app_indicator_set_title(appIndicator, title);
+ ai.app_indicator_set_icon_full(
+ appIndicator,
+ iconFile.getAbsolutePath(),
+ "Jitsi");
+
+ // create root menu
+ popupPeer = new PopupMenuPeer(null, popup);
+ popupPeer.gtkMenu = gtk.gtk_menu_new();
+
+ // transfer everything in the swing menu to the gtk menu
+ createGtkMenuItems(popupPeer, popup.getComponents());
+ gtk.gtk_widget_show_all(popupPeer.gtkMenu);
+
+ // attach the menu to the indicator
+ ai.app_indicator_set_menu(appIndicator, popupPeer.gtkMenu);
+ ai.app_indicator_set_status(
+ appIndicator,
+ AppIndicator1.APP_INDICATOR_STATUS.ACTIVE.ordinal());
+ }
+
+ private void cleanMenu(PopupMenuPeer peer)
+ {
+ assert !peer.isDefaultMenuItem;
+ for (PopupMenuPeer p : peer.children)
+ {
+ cleanMenu(p);
+ }
+
+ // - the root menu is released when it's unset from the indicator
+ // - gtk auto-frees menu item, submenu, image, and pixbuf
+ // - the imagebuffer was jna allocated, GC should take care of freeing
+ if (peer.gtkSignalHandler > 0)
+ {
+ gobject.g_signal_handler_disconnect(
+ peer.gtkMenuItem,
+ peer.gtkSignalHandler);
+ }
+
+ gtk.gtk_widget_destroy(peer.gtkMenuItem);
+ peer.gtkImageBuffer = null;
+ if (peer.menuItem instanceof JMenu)
+ {
+ ((JMenu)peer.menuItem).removeContainerListener(peer);
+ ((JMenu)peer.menuItem).getPopupMenu().removeContainerListener(peer);
+ }
+ }
+
+ private void createGtkMenuItems(
+ PopupMenuPeer parent,
+ Component[] components)
+ {
+ for (Component em : components)
+ {
+ PopupMenuPeer peer = new PopupMenuPeer(parent, em);
+ if (em instanceof JPopupMenu.Separator)
+ {
+ logger.debug("Creating separator");
+ peer.gtkMenuItem = gtk.gtk_separator_menu_item_new();
+ }
+
+ if (em instanceof JMenuItem)
+ {
+ createGtkMenuItem(peer);
+ }
+
+ if (em instanceof JMenu && peer.gtkMenuItem != null)
+ {
+ JMenu m = (JMenu)em;
+ logger.debug("Creating submenu on " + m.getText());
+ peer.gtkMenu = gtk.gtk_menu_new();
+ createGtkMenuItems(peer, m.getMenuComponents());
+ gtk.gtk_menu_item_set_submenu(peer.gtkMenuItem, peer.gtkMenu);
+ }
+
+ if (peer.gtkMenuItem != null)
+ {
+ parent.children.add(peer);
+ gtk.gtk_menu_shell_append(parent.gtkMenu, peer.gtkMenuItem);
+ }
+ }
+ }
+
+ private void createGtkMenuItem(PopupMenuPeer peer)
+ {
+ JMenuItem m = (JMenuItem)peer.menuItem;
+ logger.debug("Creating item for " + m.getClass().getName() + ": "
+ + m.getText());
+ if (m instanceof JCheckBoxMenuItem)
+ {
+ peer.gtkMenuItem = gtk.gtk_check_menu_item_new_with_label(
+ m.getText());
+ JCheckBoxMenuItem cb = (JCheckBoxMenuItem)m;
+ gtk.gtk_check_menu_item_set_active(
+ peer.gtkMenuItem,
+ cb.isSelected() ? 1 : 0);
+ }
+ else
+ {
+ peer.gtkMenuItem = gtk.gtk_image_menu_item_new_with_label(
+ m.getText());
+ if (m.getIcon() instanceof ImageIcon)
+ {
+ ImageIcon ii = ((ImageIcon) m.getIcon());
+ imageIconToGtkWidget(peer, ii);
+ if (peer.gtkImage != null)
+ {
+ gtk.gtk_image_menu_item_set_image(
+ peer.gtkMenuItem,
+ peer.gtkImage);
+ gtk.gtk_image_menu_item_set_always_show_image(
+ peer.gtkMenuItem,
+ 1);
+ }
+ }
+ }
+
+ if (peer.gtkMenuItem == null)
+ {
+ logger.debug("Could not create menu item for " + m.getText());
+ return;
+ }
+
+ MenuItemChangeListener micl = new MenuItemChangeListener(peer);
+ m.addPropertyChangeListener(micl);
+ m.addChangeListener(micl);
+
+ // skip GTK events if it's a submenu
+ if (!(m instanceof JMenu))
+ {
+ gtk.gtk_widget_set_sensitive(
+ peer.gtkMenuItem,
+ m.isEnabled() ? 1 : 0);
+ peer.signalHandler = new MenuItemSignalHandler(peer);
+ peer.gtkSignalHandler = gobject.g_signal_connect_data(
+ peer.gtkMenuItem,
+ "activate",
+ peer.signalHandler,
+ null,
+ null,
+ 0);
+ }
+ }
+
+ private String imageIconToPath(ImageIcon ii)
+ {
+ if (ii.getDescription() != null)
+ {
+ String path = extractedFiles.get(ii.getDescription());
+ if (path != null)
+ {
+ return path;
+ }
+ }
+
+ try
+ {
+ File f = File.createTempFile("jitsi-appindicator", ".png");
+ f.deleteOnExit();
+ try (FileImageOutputStream fios = new FileImageOutputStream(f))
+ {
+ if (!ImageIO.write(getBufferedImage(ii), "png", fios))
+ {
+ return null;
+ }
+
+ if (ii.getDescription() != null)
+ {
+ extractedFiles.put(
+ ii.getDescription(),
+ f.getAbsolutePath());
+ }
+
+ return f.getAbsolutePath();
+ }
+ }
+ catch (IOException e)
+ {
+ logger.debug("Failed to extract image: " + ii.getDescription(), e);
+ }
+
+ return null;
+ }
+
+ BufferedImage getBufferedImage(ImageIcon ii)
+ {
+ Image img = ii.getImage();
+ if (img == null)
+ {
+ return null;
+ }
+
+ if (img instanceof BufferedImage)
+ {
+ return (BufferedImage) img;
+ }
+
+ BufferedImage bi = new BufferedImage(
+ img.getWidth(null),
+ img.getHeight(null),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = bi.createGraphics();
+ g.drawImage(img, 0, 0, null);
+ g.dispose();
+ return bi;
+ }
+
+ private void imageIconToGtkWidget(PopupMenuPeer peer, ImageIcon ii)
+ {
+ BufferedImage bi = getBufferedImage(ii);
+ if (bi == null)
+ {
+ return;
+ }
+
+ int[] pixels = bi.getRGB(
+ 0,
+ 0,
+ bi.getWidth(),
+ bi.getHeight(),
+ null,
+ 0,
+ bi.getWidth());
+
+ peer.gtkImageBuffer = new Memory(pixels.length * 4);
+ for (int i = 0; i < pixels.length; i++)
+ {
+ // convert from argb (big endian) -> rgba (little endian) => abgr
+ peer.gtkImageBuffer.setInt(i * 4, (pixels[i] & 0xFF000000) |
+ (pixels[i] << 16) |
+ (pixels[i] & 0xFF00) |
+ (pixels[i] >>> 16 & 0xFF));
+ }
+
+ peer.gtkPixbuf = gtk.gdk_pixbuf_new_from_data(
+ peer.gtkImageBuffer,
+ 0,
+ 1,
+ 8,
+ bi.getWidth(),
+ bi.getHeight(),
+ bi.getWidth() * 4,
+ null,
+ null);
+ peer.gtkImage = gtk.gtk_image_new_from_pixbuf(peer.gtkPixbuf);
+
+ // Now that the image ref's the buffer, we can release our own ref and
+ // the buffer will be free'd along with the image
+ gobject.g_object_unref(peer.gtkPixbuf);
+ }
+
+ private static class MenuItemChangeListener
+ implements PropertyChangeListener, ChangeListener
+ {
+ private PopupMenuPeer peer;
+ private JMenuItem menu;
+
+ public MenuItemChangeListener(PopupMenuPeer peer)
+ {
+ this.peer = peer;
+ this.menu = (JMenuItem)peer.menuItem;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(menu.getText() + "::" + evt.getPropertyName());
+ }
+
+ switch (evt.getPropertyName())
+ {
+ case JMenuItem.TEXT_CHANGED_PROPERTY:
+ gtk.gdk_threads_enter();
+ try
+ {
+ gtk.gtk_menu_item_set_label(
+ peer.gtkMenuItem,
+ evt.getNewValue().toString());
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+
+ break;
+// case JMenuItem.ICON_CHANGED_PROPERTY:
+// gtk.gtk_image_menu_item_set_image(gtkMenuItem, image);
+// break;
+ case AccessibleContext.ACCESSIBLE_STATE_PROPERTY:
+ gtk.gdk_threads_enter();
+ try
+ {
+ gtk.gtk_widget_set_sensitive(
+ peer.gtkMenuItem,
+ AccessibleState.ENABLED.equals(
+ evt.getNewValue()) ? 1 : 0);
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void stateChanged(ChangeEvent e)
+ {
+ logger.debug(menu.getText() + " -> " + menu.isSelected());
+ gtk.gdk_threads_enter();
+ try
+ {
+ gtk.gtk_check_menu_item_set_active(
+ peer.gtkMenuItem,
+ menu.isSelected() ? 1 : 0);
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+ }
+ }
+
+ private static class MenuItemSignalHandler
+ implements SignalHandler, Runnable
+ {
+ private PopupMenuPeer peer;
+
+ MenuItemSignalHandler(PopupMenuPeer peer)
+ {
+ this.peer = peer;
+ }
+
+ @Override
+ public void signal(Pointer widget, Pointer data)
+ {
+ SwingUtilities.invokeLater(this);
+ }
+
+ @Override
+ public void run()
+ {
+ JMenuItem menu = (JMenuItem)peer.menuItem;
+ if (menu instanceof JCheckBoxMenuItem)
+ {
+ // Ignore GTK callback events if the menu state is
+ // already the same. Setting the selected state on the
+ // GTK sends the "activate" event, and would cause
+ // a loop
+ logger.debug("Checking selected state on: " + menu.getText());
+ if (menu.isSelected() == isGtkSelected())
+ {
+ return;
+ }
+ }
+
+ for (ActionListener l : menu.getActionListeners())
+ {
+ logger.debug("Invoking " + l + " on " + menu.getText());
+ l.actionPerformed(new ActionEvent(menu, 0, "activate"));
+ }
+ }
+
+ private boolean isGtkSelected()
+ {
+ gtk.gdk_threads_enter();
+ try
+ {
+ return gtk.gtk_check_menu_item_get_active(peer.gtkMenuItem) == 1;
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+ }
+ }
+
+ @Override
+ public void setDefaultAction(Object menuItem)
+ {
+ // It shouldn't be necessary that we hold a reference to the
+ // default item, it is contained in the menu. It might even create
+ // a memory leak. But if not set, the indicator loses track of it
+ // (at least on Debian). Unref an existing item, then ref the newly
+ // set
+ if (defaultMenuPeer != null)
+ {
+ gobject.g_object_unref(defaultMenuPeer.gtkMenuItem);
+ }
+
+ PopupMenuPeer peer = findMenuItem(popupPeer, menuItem);
+ if (peer != null && peer.gtkMenuItem != null)
+ {
+ gtk.gdk_threads_enter();
+ try
+ {
+ defaultMenuPeer = peer;
+ gobject.g_object_ref(peer.gtkMenuItem);
+ ai.app_indicator_set_secondary_activate_target(
+ appIndicator,
+ peer.gtkMenuItem);
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+ }
+ }
+
+ private PopupMenuPeer findMenuItem(PopupMenuPeer peer, Object menuItem)
+ {
+ if (peer.menuItem == menuItem)
+ {
+ logger.debug("Setting default action to: "
+ + ((JMenuItem)menuItem).getText()
+ + " @" + peer.gtkMenuItem);
+ return peer;
+ }
+
+ for (PopupMenuPeer p : peer.children)
+ {
+ PopupMenuPeer found = findMenuItem(p, menuItem);
+ if (found != null)
+ {
+ return found;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void addBalloonActionListener(ActionListener listener)
+ {
+ // not supported
+ }
+
+ @Override
+ public void displayMessage(String caption, String text,
+ MessageType messageType)
+ {
+ // not supported
+ }
+
+ @Override
+ public void setIcon(ImageIcon icon) throws NullPointerException
+ {
+ mainIcon = icon;
+ if (appIndicator != null)
+ {
+ gtk.gdk_threads_enter();
+ try
+ {
+ ai.app_indicator_set_icon(
+ appIndicator,
+ imageIconToPath(icon));
+ }
+ finally
+ {
+ gtk.gdk_threads_leave();
+ }
+ }
+ }
+
+ @Override
+ public void setIconAutoSize(boolean autoSize)
+ {
+ // nothing to do
+ }
+
+ private void printMenu(Component[] components, int indent)
+ {
+ if (!logger.isDebugEnabled())
+ {
+ return;
+ }
+
+ String p = String.format("%0" + indent * 4 + "d", 0).replace('0', ' ');
+ for (Component em : components)
+ {
+ if (em instanceof JPopupMenu.Separator)
+ {
+ logger.debug(p + "-----------------------");
+ }
+
+ if (em instanceof JMenuItem)
+ {
+ JMenuItem m = (JMenuItem) em;
+ logger.debug(p + em.getClass().getName() + ": " + m.getText());
+ }
+
+ if (em instanceof JMenu)
+ {
+ JMenu m = (JMenu) em;
+ printMenu(m.getMenuComponents(), indent + 1);
+ }
+ }
+ }
+}; \ No newline at end of file
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java
new file mode 100644
index 0000000..cfc7805
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java
@@ -0,0 +1,90 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.appindicator;
+
+import java.util.*;
+
+import com.sun.jna.*;
+
+/**
+ * JNA mappings for GTK GObject types that are required for the tray menu.
+ *
+ * @author Ingo Bauersachs
+ */
+interface Gobject extends Library
+{
+ static final Gobject INSTANCE =
+ (Gobject) Native.loadLibrary("gobject-2.0", Gobject.class);
+
+ interface SignalHandler extends Callback
+ {
+ void signal(Pointer widget, Pointer data);
+ }
+
+ /**
+ * Connects a GCallback function to a signal for a particular object.
+ * Similar to g_signal_connect(), but allows to provide a GClosureNotify for
+ * the data which will be called when the signal handler is disconnected and
+ * no longer used. Specify connect_flags if you need ..._after() or
+ * ..._swapped() variants of this function.
+ *
+ * @param instance the instance to connect to.
+ * @param detailed_signal a string of the form "signal-name::detail".
+ * @param c_handler the GCallback to connect.
+ * @param data data to pass to c_handler calls.
+ * @param destroy_data a GClosureNotify for data.
+ * @param connect_flags a combination of GConnectFlags.
+ *
+ * @return the handler id (always greater than 0 for successful connections)
+ */
+ long g_signal_connect_data(Pointer instance, String detailed_signal,
+ SignalHandler c_handler, Pointer data, Pointer destroy_data,
+ int connect_flags);
+
+ /**
+ * Disconnects a handler from an instance so it will not be called during
+ * any future or currently ongoing emissions of the signal it has been
+ * connected to. The handler_id becomes invalid and may be reused. The
+ * handler_id has to be a valid signal handler id, connected to a signal of
+ * instance .
+ *
+ * @param instance The instance to remove the signal handler from.
+ * @param handler_id Handler id of the handler to be disconnected.
+ */
+ void g_signal_handler_disconnect(Pointer instance, long handler_id);
+
+ /**
+ * Decreases the reference count of object. When its reference count drops
+ * to 0, the object is finalized (i.e. its memory is freed). If the pointer
+ * to the GObject may be reused in future (for example, if it is an instance
+ * variable of another object), it is recommended to clear the pointer to
+ * NULL rather than retain a dangling pointer to a potentially invalid
+ * GObject instance. Use g_clear_object() for this.
+ *
+ * @param object a GObject.
+ */
+ void g_object_unref(Pointer object);
+
+ /**
+ * Increases the reference count of object.
+ *
+ * @param object a GObject.
+ * @return the same object.
+ */
+ Pointer g_object_ref(Pointer object);
+}
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java
new file mode 100644
index 0000000..b66d59f
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java
@@ -0,0 +1,68 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.appindicator;
+
+import com.sun.jna.*;
+
+/**
+ * JNA mappings for the gtk2 library. Only functions required for the tray menu
+ * are defined.
+ *
+ * @author Ingo Bauersachs
+ */
+interface Gtk extends Library
+{
+ static final Gtk INSTANCE =
+ (Gtk) Native.loadLibrary("gtk-x11-2.0", Gtk.class);
+
+ public enum GtkIconSize
+ {
+ INVALID,
+ MENU,
+ SMALL_TOOLBAR,
+ LARGE_TOOLBAR,
+ BUTTON,
+ DND,
+ DIALOG
+ }
+
+ void gtk_init(int argc, String[] argv);
+ void gtk_main();
+ Pointer gtk_menu_new();
+ Pointer gtk_image_menu_item_new_with_label(String label);
+ Pointer gtk_separator_menu_item_new();
+ void gtk_menu_item_set_submenu(Pointer menu_item, Pointer submenu);
+ void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image);
+ void gtk_image_menu_item_set_always_show_image(Pointer image_menu_item, int always_show);
+ void gtk_menu_item_set_label(Pointer menu_item, String label);
+ void gtk_menu_shell_append(Pointer menu_shell, Pointer child);
+ void gtk_widget_set_sensitive(Pointer widget, int sesitive);
+ void gtk_widget_show_all(Pointer widget);
+ void gtk_widget_destroy(Pointer widget);
+ Pointer gtk_check_menu_item_new_with_label(String label);
+ int gtk_check_menu_item_get_active(Pointer check_menu_item);
+ void gtk_check_menu_item_set_active(Pointer check_menu_item, int is_active);
+
+ void gdk_threads_enter();
+ void gdk_threads_leave();
+
+ Pointer gdk_pixbuf_new_from_data(Pointer data, int colorspace, int has_alpha,
+ int bits_per_sample, int width, int height, int rowstride,
+ Pointer destroy_fn, Pointer destroy_fn_data);
+ Pointer gtk_image_new_from_pixbuf(Pointer pixbuf);
+}
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTMouseAdapter.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTMouseAdapter.java
new file mode 100644
index 0000000..eb4a773
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTMouseAdapter.java
@@ -0,0 +1,124 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.awt;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+import org.jitsi.util.*;
+
+/**
+ * Extended mouse adapter to show the JPopupMenu in Java 6. Based on: <a href=
+ * "https://community.oracle.com/blogs/ixmal/2006/05/03/using-jpopupmenu-trayicon">
+ * Using JPopupMenu in TrayIcon Blog</a> and <a href=
+ * "https://community.oracle.com/blogs/alexfromsun/2008/02/14/jtrayicon-update">
+ * JTrayIcon update Blog</a>.
+ *
+ * Use a hidden JWindow (JDialog for Windows) to manage the JPopupMenu.
+ *
+ * @author Damien Roth
+ */
+class AWTMouseAdapter
+ extends MouseAdapter
+{
+ private JPopupMenu popup = null;
+ private Window hiddenWindow = null;
+
+ public AWTMouseAdapter(JPopupMenu p)
+ {
+ this.popup = p;
+ this.popup.addPopupMenuListener(new PopupMenuListener()
+ {
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e)
+ {}
+
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
+ {
+ if (hiddenWindow != null)
+ {
+ hiddenWindow.dispose();
+ hiddenWindow = null;
+ }
+ }
+
+ public void popupMenuCanceled(PopupMenuEvent e)
+ {
+ if (hiddenWindow != null)
+ {
+ hiddenWindow.dispose();
+ hiddenWindow = null;
+ }
+ }
+ });
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e)
+ {
+ showPopupMenu(e);
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e)
+ {
+ showPopupMenu(e);
+ }
+
+ private void showPopupMenu(MouseEvent e)
+ {
+ if (e.isPopupTrigger() && popup != null)
+ {
+ if (hiddenWindow == null)
+ {
+ if (OSUtils.IS_WINDOWS)
+ {
+ hiddenWindow = new JDialog((Frame) null);
+ ((JDialog) hiddenWindow).setUndecorated(true);
+ }
+ else
+ hiddenWindow = new JWindow((Frame) null);
+
+ hiddenWindow.setAlwaysOnTop(true);
+ Dimension size = popup.getPreferredSize();
+
+ Point centerPoint = GraphicsEnvironment
+ .getLocalGraphicsEnvironment()
+ .getCenterPoint();
+
+ if(e.getY() > centerPoint.getY())
+ hiddenWindow
+ .setLocation(e.getX(), e.getY() - size.height);
+ else
+ hiddenWindow
+ .setLocation(e.getX(), e.getY());
+
+ hiddenWindow.setVisible(true);
+
+ popup.show(
+ ((RootPaneContainer)hiddenWindow).getContentPane(),
+ 0, 0);
+
+ // popup works only for focused windows
+ hiddenWindow.toFront();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java
new file mode 100644
index 0000000..1f211c7
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java
@@ -0,0 +1,78 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.awt;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+import org.jitsi.util.*;
+
+import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray;
+import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon;
+
+/**
+ * Wrapper of the AWT SystemTray class.
+ */
+public class AWTSystemTray
+ extends SystemTray
+{
+ private final java.awt.SystemTray impl;
+
+ /**
+ * Creates a new instance of this class.
+ */
+ public AWTSystemTray()
+ {
+ impl = java.awt.SystemTray.getSystemTray();
+ }
+
+ @Override
+ public void addTrayIcon(TrayIcon trayIcon)
+ throws IllegalArgumentException
+ {
+ try
+ {
+ impl.add(((AWTTrayIcon) trayIcon).getImpl());
+ }
+ catch (AWTException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public TrayIcon createTrayIcon(ImageIcon icon, String tooltip,
+ Object popup)
+ {
+ return new AWTTrayIcon(icon.getImage(), tooltip, popup);
+ }
+
+ @Override
+ public boolean useSwingPopupMenu()
+ {
+ // enable swing for Java 1.6 except for the mac version
+ return !OSUtils.IS_MAC;
+ }
+
+ @Override
+ public boolean supportsDynamicMenu()
+ {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java
new file mode 100644
index 0000000..2fa0318
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java
@@ -0,0 +1,131 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.osdependent.systemtray.awt;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon;
+
+/**
+ * Wrapper of the AWT TrayIcon class.
+ */
+public class AWTTrayIcon
+ implements TrayIcon
+{
+ private final java.awt.TrayIcon impl;
+
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param image the <tt>Image</tt> to be used
+ * @param tooltip the string to be used as tooltip text; if the value is
+ * <tt>null</tt> no tooltip is shown
+ * @param popup the menu to be used for the tray icon's popup menu; if the
+ * value is <tt>null</tt> no popup menu is shown
+ */
+ public AWTTrayIcon(Image image, String tooltip,
+ Object popup)
+ {
+ if (popup instanceof JPopupMenu)
+ {
+ impl = new java.awt.TrayIcon(image, tooltip);
+ impl.addMouseListener(new AWTMouseAdapter((JPopupMenu)popup));
+ }
+ else if (popup instanceof PopupMenu)
+ {
+ impl = new java.awt.TrayIcon(image, tooltip, (PopupMenu)popup);
+ }
+ else if (popup == null)
+ {
+ impl = new java.awt.TrayIcon(image, tooltip);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid popup menu type");
+ }
+ }
+
+ public void setDefaultAction(final Object menuItem)
+ {
+ // clear all previous listeners
+ ActionListener[] previous = impl.getActionListeners();
+ for (ActionListener l : previous)
+ {
+ impl.removeActionListener(l);
+ }
+
+ // get the new handlers
+ final ActionListener[] listeners;
+ if (menuItem instanceof JMenuItem)
+ {
+ listeners = ((JMenuItem) menuItem).getActionListeners();
+ }
+ else if (menuItem instanceof MenuItem)
+ {
+ listeners = ((MenuItem) menuItem).getActionListeners();
+ }
+ else
+ {
+ return;
+ }
+
+ // create a custom handler to fake that the source is the menu item
+ impl.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ for (ActionListener l : listeners)
+ {
+ l.actionPerformed(new ActionEvent(menuItem,
+ e.getID(), e.getActionCommand()));
+ }
+ }
+ });
+ }
+
+ public void addBalloonActionListener(ActionListener listener)
+ {
+ // java.awt.TrayIcon doesn't support addBalloonActionListener()
+ }
+
+ public void displayMessage(String caption, String text,
+ java.awt.TrayIcon.MessageType messageType)
+ throws NullPointerException
+ {
+ impl.displayMessage(caption, text, messageType);
+ }
+
+ public void setIcon(ImageIcon icon) throws NullPointerException
+ {
+ impl.setImage(icon.getImage());
+ }
+
+ public void setIconAutoSize(boolean autoSize)
+ {
+ impl.setImageAutoSize(autoSize);
+ }
+
+ java.awt.TrayIcon getImpl()
+ {
+ return impl;
+ }
+} \ No newline at end of file