aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorIngo Bauersachs <ingo@jitsi.org>2017-01-10 02:04:00 +0100
committerIngo Bauersachs <ingo@jitsi.org>2017-01-10 02:05:03 +0100
commit11d0c16f315806ed7cce19dfe235a1cd7823214f (patch)
tree070f25f7ece6f5555e2240599d5fc2624ef5faf1 /src
parent0b278bf6748cb32b74c1731b83ebe1c0158bfaa7 (diff)
downloadjitsi-11d0c16f315806ed7cce19dfe235a1cd7823214f.zip
jitsi-11d0c16f315806ed7cce19dfe235a1cd7823214f.tar.gz
jitsi-11d0c16f315806ed7cce19dfe235a1cd7823214f.tar.bz2
Add support for AppIndicators
See #192 Doesn't properly work on Debian because of outdated/mismatching GTK dependencies. Fixed in Ubuntu Launchpad bug #1203888, but not imported in Debian. See bug #850769.
Diffstat (limited to 'src')
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java15
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java79
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java21
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf2
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java36
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java2
-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.java94
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java596
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.java68
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java79
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java6
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java21
13 files changed, 1147 insertions, 61 deletions
diff --git a/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java b/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java
index 2d77621..92088fa 100644
--- a/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java
+++ b/src/net/java/sip/communicator/impl/osdependent/jdic/StatusSubMenu.java
@@ -66,7 +66,7 @@ public class StatusSubMenu
* @param swing <tt>true</tt> to represent this instance with a Swing
* <tt>JMenu</tt>; <tt>false</tt> to use an AWT <tt>Menu</tt>
*/
- public StatusSubMenu(boolean swing)
+ public StatusSubMenu(boolean swing, boolean accountMenuSupported)
{
String text = Resources.getString("impl.systray.SET_STATUS");
@@ -86,6 +86,7 @@ public class StatusSubMenu
this.menu = new Menu(text);
}
+ if (accountMenuSupported)
{
String hideAccountStatusSelectorsProperty
= "impl.gui.HIDE_ACCOUNT_STATUS_SELECTORS";
@@ -103,6 +104,10 @@ public class StatusSubMenu
hideAccountStatusSelectorsProperty,
hideAccountStatusSelectors);
}
+ else
+ {
+ hideAccountStatusSelectors = true;
+ }
PresenceStatus offlineStatus = null;
// creates menu item entry for every global status
@@ -116,9 +121,11 @@ public class StatusSubMenu
// initially it is offline
selectItemFromStatus(offlineStatus.getStatus());
- this.addSeparator();
-
- addMenuItem(menu, new GlobalStatusMessageMenu(swing).getMenu());
+ if (accountMenuSupported)
+ {
+ this.addSeparator();
+ addMenuItem(menu, new GlobalStatusMessageMenu(swing).getMenu());
+ }
if(!hideAccountStatusSelectors)
this.addSeparator();
diff --git a/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java b/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java
index 6fd9d8c..fd9d43e 100644
--- a/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java
+++ b/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java
@@ -35,6 +35,7 @@ import net.java.sip.communicator.service.systray.*;
import net.java.sip.communicator.service.systray.event.*;
import net.java.sip.communicator.util.Logger;
+import org.apache.commons.lang3.tuple.Pair;
import org.jitsi.util.*;
import org.osgi.framework.*;
@@ -185,47 +186,34 @@ public class SystrayServiceJdicImpl
return;
}
- menu = TrayMenuFactory.createTrayMenu(this, systray.useSwingPopupMenu());
+ Pair<Object, Object> createdMenu = TrayMenuFactory.createTrayMenu(
+ this,
+ systray.useSwingPopupMenu(),
+ systray.supportsDynamicMenu());
+ menu = createdMenu.getLeft();
boolean isMac = OSUtils.IS_MAC;
- // If we're running under Windows, we use a special icon without
- // background.
- if (OSUtils.IS_WINDOWS)
- {
- logoIcon = Resources.getImage("service.systray.TRAY_ICON_WINDOWS");
- logoIconOffline = Resources.getImage(
- "service.systray.TRAY_ICON_WINDOWS_OFFLINE");
- logoIconAway = Resources.getImage(
- "service.systray.TRAY_ICON_WINDOWS_AWAY");
- logoIconExtendedAway = Resources.getImage(
- "service.systray.TRAY_ICON_WINDOWS_EXTENDED_AWAY");
- logoIconFFC = Resources.getImage(
- "service.systray.TRAY_ICON_WINDOWS_FFC");
- logoIconDND = Resources.getImage(
- "service.systray.TRAY_ICON_WINDOWS_DND");
- }
- /*
- * If we're running under Mac OS X, we use special black and white icons
- * without background.
- */
- else if (isMac)
+ logoIcon = Resources.getImage("service.systray.TRAY_ICON_WINDOWS");
+ logoIconOffline = Resources.getImage(
+ "service.systray.TRAY_ICON_WINDOWS_OFFLINE");
+ logoIconAway = Resources.getImage(
+ "service.systray.TRAY_ICON_WINDOWS_AWAY");
+ logoIconExtendedAway = Resources.getImage(
+ "service.systray.TRAY_ICON_WINDOWS_EXTENDED_AWAY");
+ logoIconFFC = Resources.getImage(
+ "service.systray.TRAY_ICON_WINDOWS_FFC");
+ logoIconDND = Resources.getImage(
+ "service.systray.TRAY_ICON_WINDOWS_DND");
+
+ // If we're running under Mac OS X, we use special black and white
+ // icons without background.
+ if (isMac)
{
logoIcon = Resources.getImage("service.systray.TRAY_ICON_MACOSX");
logoIconWhite = Resources.getImage(
"service.systray.TRAY_ICON_MACOSX_WHITE");
}
- else
- {
- logoIcon = Resources.getImage("service.systray.TRAY_ICON");
- logoIconOffline = Resources.getImage(
- "service.systray.TRAY_ICON_OFFLINE");
- logoIconAway = Resources.getImage("service.systray.TRAY_ICON_AWAY");
- logoIconExtendedAway = Resources.getImage(
- "service.systray.TRAY_ICON_EXTENDED_AWAY");
- logoIconFFC = Resources.getImage("service.systray.TRAY_ICON_FFC");
- logoIconDND = Resources.getImage("service.systray.TRAY_ICON_DND");
- }
/*
* Default to set offline , if any protocols become online will set it
@@ -259,21 +247,15 @@ public class SystrayServiceJdicImpl
}
//Show/hide the contact list when user clicks on the systray.
- trayIcon.addActionListener(
- new ActionListener()
- {
- public void actionPerformed(ActionEvent e)
- {
- UIService uiService
- = OsDependentActivator.getUIService();
- ExportedWindow mainWindow
- = uiService.getExportedWindow(
- ExportedWindow.MAIN_WINDOW);
- boolean setIsVisible = !mainWindow.isVisible();
-
- uiService.setVisible(setIsVisible);
- }
- });
+ final Object defaultActionItem;
+ if (systray.useSwingPopupMenu())
+ {
+ defaultActionItem = ((JMenuItem) createdMenu.getRight());
+ }
+ else
+ {
+ defaultActionItem = ((MenuItem) createdMenu.getRight());
+ }
/*
* Change the Mac OS X icon with the white one when the pop-up menu
@@ -336,6 +318,7 @@ public class SystrayServiceJdicImpl
public void run()
{
systray.addTrayIcon(trayIcon);
+ trayIcon.setDefaultAction(defaultActionItem);
}
});
diff --git a/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java b/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java
index c13a505..05fe771 100644
--- a/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java
+++ b/src/net/java/sip/communicator/impl/osdependent/jdic/TrayMenuFactory.java
@@ -26,6 +26,7 @@ import javax.swing.event.*;
import net.java.sip.communicator.impl.osdependent.*;
import net.java.sip.communicator.service.gui.*;
+import org.apache.commons.lang3.tuple.*;
import org.jitsi.util.*;
/**
@@ -139,12 +140,16 @@ public final class TrayMenuFactory
*
* @param tray the system tray for which we're creating a menu
* @param swing indicates if we should create a Swing or an AWT menu
- * @return a tray menu for the given system tray
+ * @return a tray menu for the given system tray (first) and the default
+ * menu item (second)
*/
- public static Object createTrayMenu(SystrayServiceJdicImpl tray,
- boolean swing)
+ public static Pair<Object, Object> createTrayMenu(
+ SystrayServiceJdicImpl tray,
+ boolean swing,
+ boolean accountMenuSupported
+ )
{
- Object trayMenu = swing ? new JPopupMenu() : new PopupMenu();
+ final Object trayMenu = swing ? new JPopupMenu() : new PopupMenu();
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent event)
@@ -182,7 +187,9 @@ public final class TrayMenuFactory
if (!chatPresenceDisabled.booleanValue())
{
- add(trayMenu, new StatusSubMenu(swing).getMenu());
+ add(
+ trayMenu,
+ new StatusSubMenu(swing, accountMenuSupported).getMenu());
addSeparator(trayMenu);
}
@@ -197,9 +204,11 @@ public final class TrayMenuFactory
showHideIconId = "service.gui.icons.SEARCH_ICON_16x16";
}
else
+ {
showHideName = "service.gui.SHOW";
showHideTextId = "service.gui.SHOW";
showHideIconId = "service.gui.icons.SEARCH_ICON_16x16";
+ }
final Object showHideMenuItem = createTrayMenuItem( showHideName,
showHideTextId,
@@ -241,7 +250,7 @@ public final class TrayMenuFactory
}
});
- return trayMenu;
+ return Pair.of(trayMenu, showHideMenuItem);
}
/**
diff --git a/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf b/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf
index bc56371..91f2791 100644
--- a/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf
+++ b/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf
@@ -27,6 +27,7 @@ Import-Package: com.apple.cocoa.application,
net.java.sip.communicator.plugin.desktoputil.presence,
javax.accessibility,
javax.imageio,
+ javax.imageio.stream,
javax.swing,
javax.swing.border,
javax.swing.event,
@@ -39,6 +40,7 @@ Import-Package: com.apple.cocoa.application,
javax.swing.text.html,
javax.swing.tree,
javax.swing.undo,
+ org.apache.commons.lang3.tuple,
org.jitsi.service.configuration,
org.jitsi.service.resources,
org.jitsi.util,
diff --git a/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java
index 125a190..eeffd29 100644
--- a/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/SystemTray.java
@@ -19,13 +19,22 @@ package net.java.sip.communicator.impl.osdependent.systemtray;
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.util.Logger;
/**
* Base class for all wrappers of <tt>SystemTray</tt> implementations.
*/
public abstract class SystemTray
{
+ private static final String PNMAE_DISABLE_TRY =
+ "net.java.sip.communicator.osdependent.systemtray.DISABLE";
+
+ private static final Logger logger = Logger.getLogger(SystemTray.class);
private static SystemTray systemTray;
/**
@@ -34,8 +43,28 @@ public abstract class SystemTray
*/
public final static SystemTray getSystemTray()
{
+ boolean disable = OsDependentActivator.getConfigurationService()
+ .getBoolean(PNMAE_DISABLE_TRY, false);
+ if (disable)
+ {
+ return null;
+ }
+
if (systemTray == null)
{
+ if (OSUtils.IS_LINUX)
+ {
+ try
+ {
+ systemTray = new AppIndicatorTray();
+ return systemTray;
+ }
+ catch(Exception ex)
+ {
+ logger.info(ex.getMessage());
+ }
+ }
+
if (java.awt.SystemTray.isSupported())
{
systemTray = new AWTSystemTray();
@@ -75,4 +104,11 @@ public abstract class SystemTray
* <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
index 39fa6bd..78df44c 100644
--- a/src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/TrayIcon.java
@@ -29,7 +29,7 @@ import javax.swing.*;
*/
public interface TrayIcon
{
- public void addActionListener(ActionListener listener);
+ public void setDefaultAction(Object menuItem);
public void addBalloonActionListener(ActionListener listener);
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..3f4e267
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTray.java
@@ -0,0 +1,94 @@
+/*
+ * 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 static final String PNMAE_APPINDICATOR_DISABLED =
+ "net.java.sip.communicator.osdependent.systemtray.appindicator.DISABLED";
+ private static final String PNMAE_APPINDICATOR_DYNAMIC_MENU =
+ "net.java.sip.communicator.osdependent.systemtray.appindicator.DYNAMIC_MENU";
+
+ public AppIndicatorTray() throws Exception
+ {
+ boolean disable = OsDependentActivator.getConfigurationService()
+ .getBoolean(PNMAE_APPINDICATOR_DISABLED, false);
+ if (disable)
+ {
+ throw new Exception("AppIndicator is disabled");
+ }
+
+ if (!OSUtils.IS_LINUX)
+ {
+ throw new Exception("Not running Linux, AppIndicator1 is not available");
+ }
+
+ 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 OsDependentActivator.getConfigurationService()
+ .getBoolean(PNMAE_APPINDICATOR_DYNAMIC_MENU, true);
+ }
+}
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..8779312
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/AppIndicatorTrayIcon.java
@@ -0,0 +1,596 @@
+/*
+ * 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;
+
+ 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);
+ }
+ }
+
+ 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;
+
+ @Override
+ public void componentAdded(ContainerEvent e)
+ {
+ AppIndicatorTrayIcon.this.printMenu(popup.getComponents(), 1);
+ gtk.gdk_threads_enter();
+ createGtkMenuItems(this, new Component[]{e.getChild()});
+ gtk.gtk_widget_show_all(popupPeer.gtkMenu);
+ 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();
+ gtk.gtk_widget_destroy(c.gtkMenuItem);
+ gtk.gdk_threads_leave();
+ cleanMenu(c);
+ break;
+ }
+ }
+ }
+ }
+
+ public void createTray()
+ {
+ appIndicator = ai.app_indicator_new(
+ "jitsi",
+ "indicator-messages-new",
+ AppIndicator1.APP_INDICATOR_CATEGORY.COMMUNICATIONS.ordinal());
+
+ ai.app_indicator_set_title(appIndicator, title);
+ setupGtkMenu();
+
+ String path = imageIconToPath(mainIcon);
+ if (path != null)
+ {
+ ai.app_indicator_set_icon_full(appIndicator, path, "Jitsi");
+ }
+
+ ai.app_indicator_set_status(
+ appIndicator,
+ AppIndicator1.APP_INDICATOR_STATUS.ACTIVE.ordinal());
+
+ new Thread()
+ {
+ public void run()
+ {
+ gtk.gtk_main();
+ }
+ }.start();
+ }
+
+ private void setupGtkMenu()
+ {
+ // 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);
+ }
+
+ private void cleanMenu(PopupMenuPeer peer)
+ {
+ 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
+ peer.gtkImageBuffer = null;
+ removeListeners(peer);
+ }
+
+ private void removeListeners(PopupMenuPeer peer)
+ {
+ if (peer.menuItem instanceof JMenu)
+ {
+ ((JMenu)peer.menuItem).removeContainerListener(peer);
+ ((JMenu)peer.menuItem).getPopupMenu().removeContainerListener(peer);
+ }
+
+ for (PopupMenuPeer p : peer.children)
+ {
+ removeListeners(p);
+ }
+ }
+
+ private void createGtkMenuItems(
+ PopupMenuPeer parent,
+ Component[] components)
+ {
+ for (Component em : components)
+ {
+ logger.debug("Creating item for " + em.getClass().getName());
+ PopupMenuPeer peer = new PopupMenuPeer(parent, em);
+ if (em instanceof JPopupMenu.Separator)
+ {
+ peer.gtkMenuItem = gtk.gtk_separator_menu_item_new();
+ }
+
+ if (em instanceof JMenuItem)
+ {
+ JMenuItem m = (JMenuItem)em;
+ logger.debug(" title: " + m.getText());
+ 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;
+ 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);
+ 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);
+ }
+
+ switch (evt.getPropertyName())
+ {
+ case JMenuItem.TEXT_CHANGED_PROPERTY:
+ gtk.gdk_threads_enter();
+ gtk.gtk_menu_item_set_label(
+ peer.gtkMenuItem,
+ evt.getNewValue().toString());
+ 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.gtk_widget_set_sensitive(
+ peer.gtkMenuItem,
+ AccessibleState.ENABLED.equals(evt.getNewValue()) ? 1 : 0);
+ break;
+ }
+ }
+
+ @Override
+ public void stateChanged(ChangeEvent e)
+ {
+ logger.debug(menu.getText() + " -> " + menu.isSelected());
+ gtk.gdk_threads_enter();
+ gtk.gtk_check_menu_item_set_active(
+ peer.gtkMenuItem,
+ menu.isSelected() ? 1 : 0);
+ 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
+ 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()
+ {
+ return (gtk.gtk_check_menu_item_get_active(peer.gtkMenuItem) == 1);
+ }
+ }
+
+ @Override
+ public void setDefaultAction(Object menuItem)
+ {
+ Pointer gtkMenuItem = findMenuItem(popupPeer, menuItem);
+ if (gtkMenuItem != null)
+ {
+ ai.app_indicator_set_secondary_activate_target(
+ appIndicator,
+ gtkMenuItem);
+ }
+ }
+
+ private Pointer findMenuItem(PopupMenuPeer peer, Object menuItem)
+ {
+ if (peer.menuItem == menuItem)
+ {
+ return peer.gtkMenuItem;
+ }
+
+ for (PopupMenuPeer p : peer.children)
+ {
+ Pointer 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
+ {
+ ai.app_indicator_set_icon_full(
+ appIndicator,
+ imageIconToPath(icon),
+ "Jitsi");
+ }
+
+ @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..8a2349e
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gobject.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 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.
+ */
+ void g_signal_connect_data(Pointer instance, String detailed_signal,
+ SignalHandler c_handler, Pointer data, Pointer destroy_data,
+ int connect_flags);
+
+ /**
+ * 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);
+}
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..7b74428
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/appindicator/Gtk.java
@@ -0,0 +1,79 @@
+/*
+ * 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 try 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_menu_item_new();
+ Pointer gtk_menu_item_new_with_label(String label);
+ Pointer gtk_image_menu_item_new_with_mnemonic(String label);
+ Pointer gtk_image_menu_item_new_with_label(String label);
+ Pointer gtk_image_new_from_gicon(Pointer icon, int size);
+ Pointer gtk_image_new_from_stock(String stock_id, int size);
+ Pointer gtk_image_new_from_icon_name(String icon_name, int size);
+ Pointer gtk_image_new_from_file(String filename);
+ 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_container_remove(Pointer container, Pointer widget);
+ void gtk_widget_destroy(Pointer widget);
+ void gtk_widget_show(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);
+
+ Pointer g_file_new_for_uri(String uri);
+ Pointer g_file_icon_new(Pointer file);
+ 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/AWTSystemTray.java b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java
index 2fa25b3..1f211c7 100644
--- a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTSystemTray.java
@@ -69,4 +69,10 @@ public class AWTSystemTray
// 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
index 54d97ec..0aef71b 100644
--- a/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java
+++ b/src/net/java/sip/communicator/impl/osdependent/systemtray/awt/AWTTrayIcon.java
@@ -63,9 +63,26 @@ public class AWTTrayIcon
}
}
- public void addActionListener(ActionListener listener)
+ public void setDefaultAction(Object menuItem)
{
- impl.addActionListener(listener);
+ ActionListener[] listeners;
+ if (menuItem instanceof JMenuItem)
+ {
+ listeners = ((JMenuItem) menuItem).getActionListeners();
+ }
+ else if (menuItem instanceof MenuItem)
+ {
+ listeners = ((MenuItem) menuItem).getActionListeners();
+ }
+ else
+ {
+ return;
+ }
+
+ for (ActionListener l : listeners)
+ {
+ impl.addActionListener(l);
+ }
}
public void addBalloonActionListener(ActionListener listener)