diff options
Diffstat (limited to 'src/net/java/sip/communicator/impl/osdependent')
21 files changed, 2215 insertions, 736 deletions
diff --git a/src/net/java/sip/communicator/impl/osdependent/Desktop.java b/src/net/java/sip/communicator/impl/osdependent/Desktop.java index 885dd02..53728e8 100644 --- a/src/net/java/sip/communicator/impl/osdependent/Desktop.java +++ b/src/net/java/sip/communicator/impl/osdependent/Desktop.java @@ -22,6 +22,7 @@ import java.io.*; import java.lang.reflect.*; import java.net.*; +import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray; import net.java.sip.communicator.util.Logger; /** diff --git a/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java b/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java index 7416b75..592e5af 100644 --- a/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java +++ b/src/net/java/sip/communicator/impl/osdependent/PopupMessageHandlerTrayIconImpl.java @@ -19,6 +19,7 @@ package net.java.sip.communicator.impl.osdependent; import java.awt.event.*; +import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon; import net.java.sip.communicator.service.systray.*; import net.java.sip.communicator.service.systray.event.*; diff --git a/src/net/java/sip/communicator/impl/osdependent/SystemTray.java b/src/net/java/sip/communicator/impl/osdependent/SystemTray.java deleted file mode 100644 index 0c0a4a2..0000000 --- a/src/net/java/sip/communicator/impl/osdependent/SystemTray.java +++ /dev/null @@ -1,215 +0,0 @@ -/*
- * 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;
-
-import java.awt.*;
-import java.lang.reflect.*;
-
-import javax.swing.*;
-
-import net.java.sip.communicator.impl.osdependent.TrayIcon.AWTTrayIconPeer;
-import net.java.sip.communicator.impl.osdependent.TrayIcon.TrayIconPeer;
-import net.java.sip.communicator.util.*;
-
-/**
- * @author Lubomir Marinov
- */
-public class SystemTray
-{
- /**
- * The <tt>Logger</tt> used by the <tt>SystemTray</tt> class and its
- * instances for logging output.
- */
- private static final Logger logger = Logger.getLogger(SystemTray.class);
-
- private static SystemTray defaultSystemTray;
-
- public static SystemTray getDefaultSystemTray()
- throws UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- if (defaultSystemTray != null)
- return defaultSystemTray;
-
- Class<?> awtSystemTrayClass = null;
- try
- {
- awtSystemTrayClass = Class.forName("java.awt.SystemTray");
- }
- catch (ClassNotFoundException ex)
- {
- // We'll try org.jdesktop.jdic.tray then.
- }
- SystemTrayPeer peer = null;
- if (awtSystemTrayClass != null)
- try
- {
- peer = new AWTSystemTrayPeer(awtSystemTrayClass);
- }
- catch (Exception ex)
- {
- if(!GraphicsEnvironment.isHeadless())
- logger.error("Failed to initialize java.awt.SystemTray",
- ex);
-
- // We'll try org.jdesktop.jdic.tray then.
- }
- if (peer == null)
- {
- logger.error(
- "Failed to initialize the desktop.tray implementation.");
- throw new UnsupportedOperationException(
- "Failed to initialize the desktop.tray implementation.");
- }
- return (defaultSystemTray = new SystemTray(peer));
- }
-
- private final SystemTrayPeer peer;
-
- private SystemTray(SystemTrayPeer peer)
- {
- this.peer = peer;
- }
-
- public void addTrayIcon(TrayIcon trayIcon)
- throws NullPointerException,
- IllegalArgumentException
- {
- if (peer != null)
- peer.addTrayIcon(trayIcon.getPeer());
- }
-
- SystemTrayPeer getPeer()
- {
- return peer;
- }
-
- public boolean isSwing()
- {
- if (peer != null)
- return getPeer().isSwing();
- return false;
- }
-
- static interface SystemTrayPeer
- {
- void addTrayIcon(TrayIconPeer trayIconPeer)
- throws NullPointerException,
- IllegalArgumentException;
-
- TrayIconPeer createTrayIcon(ImageIcon icon,
- String tooltip,
- Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException;
-
- boolean isSwing();
- }
-
- private static class AWTSystemTrayPeer
- implements SystemTrayPeer
- {
- private final Method addTrayIcon;
-
- private final Object impl;
-
- private final Class<?> trayIconClass;
-
- public AWTSystemTrayPeer(Class<?> clazz)
- throws UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- Method getDefaultSystemTray;
- try
- {
- getDefaultSystemTray =
- clazz.getMethod("getSystemTray", (Class<?>[]) null);
- trayIconClass = Class.forName("java.awt.TrayIcon");
- addTrayIcon = clazz.getMethod("add", new Class<?>[]
- { trayIconClass });
- }
- catch (ClassNotFoundException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (NoSuchMethodException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
-
- try
- {
- impl = getDefaultSystemTray.invoke(null, (Object[]) null);
- }
- catch (IllegalAccessException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (InvocationTargetException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- }
-
- public void addTrayIcon(TrayIconPeer trayIconPeer)
- throws NullPointerException,
- IllegalArgumentException
- {
- try
- {
- addTrayIcon.invoke(impl, new Object[]
- { ((AWTTrayIconPeer) trayIconPeer).getImpl() });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause == null)
- throw new UndeclaredThrowableException(ex);
- if (cause instanceof NullPointerException)
- throw (NullPointerException) cause;
- if (cause instanceof IllegalArgumentException)
- throw (IllegalArgumentException) cause;
- throw new UndeclaredThrowableException(cause);
- }
- }
-
- public TrayIconPeer createTrayIcon(ImageIcon icon, String tooltip,
- Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- return new AWTTrayIconPeer(trayIconClass, (icon == null) ? null
- : icon.getImage(), tooltip, popup);
- }
-
- public boolean isSwing()
- {
- return false;
- }
- }
-}
diff --git a/src/net/java/sip/communicator/impl/osdependent/TrayIcon.java b/src/net/java/sip/communicator/impl/osdependent/TrayIcon.java deleted file mode 100644 index 974ed96..0000000 --- a/src/net/java/sip/communicator/impl/osdependent/TrayIcon.java +++ /dev/null @@ -1,412 +0,0 @@ -/*
- * 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;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.lang.reflect.*;
-
-import javax.swing.*;
-import javax.swing.event.*;
-
-import net.java.sip.communicator.impl.osdependent.SystemTray.SystemTrayPeer;
-
-import org.jitsi.util.*;
-
-/**
- * @author Lubomir Marinov
- */
-public class TrayIcon
-{
- private final TrayIconPeer peer;
-
- public TrayIcon(ImageIcon icon, String tooltip, Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- SystemTrayPeer systemTrayPeer =
- SystemTray.getDefaultSystemTray().getPeer();
- if (systemTrayPeer != null)
- peer = systemTrayPeer.createTrayIcon(icon, tooltip, popup);
- else
- peer = null;
- }
-
- public void addActionListener(ActionListener listener)
- {
- if (peer != null)
- peer.addActionListener(listener);
- }
-
- public void addBalloonActionListener(ActionListener listener)
- {
- if (peer != null)
- peer.addBalloonActionListener(listener);
- }
-
- public void displayMessage(String caption, String text,
- java.awt.TrayIcon.MessageType messageType)
- throws NullPointerException
- {
- if (peer != null)
- peer.displayMessage(caption, text, messageType);
- }
-
- TrayIconPeer getPeer()
- {
- return peer;
- }
-
- public void setIcon(ImageIcon icon) throws NullPointerException
- {
- if (peer != null)
- peer.setIcon(icon);
- }
-
- public void setIconAutoSize(boolean autoSize)
- {
- if (peer != null)
- peer.setIconAutoSize(autoSize);
- }
-
- static interface TrayIconPeer
- {
- void addActionListener(ActionListener listener);
-
- void addBalloonActionListener(ActionListener listener);
-
- void displayMessage(String caption, String text,
- java.awt.TrayIcon.MessageType messageType)
- throws NullPointerException;
-
- void setIcon(ImageIcon icon) throws NullPointerException;
-
- void setIconAutoSize(boolean autoSize);
- }
-
- static class AWTTrayIconPeer
- implements TrayIconPeer
- {
- private final Method addActionListener;
-
- private final Method addMouseListener;
-
- private final Method displayMessage;
-
- private final Object impl;
-
- private final Class<?> messageTypeClass;
-
- private final Method setIcon;
-
- private final Method setIconAutoSize;
-
- public AWTTrayIconPeer(Class<?> clazz, Image image, String tooltip,
- Object popup)
- throws IllegalArgumentException,
- UnsupportedOperationException,
- HeadlessException,
- SecurityException
- {
- Constructor<?> constructor;
- try
- {
- if (popup instanceof JPopupMenu)
- {
- constructor = clazz.getConstructor(new Class<?>[]
- { Image.class, String.class });
- }
- else
- {
- constructor = clazz.getConstructor(new Class<?>[]
- { Image.class, String.class, PopupMenu.class });
- }
- addActionListener =
- clazz.getMethod("addActionListener", new Class<?>[]
- { ActionListener.class });
- addMouseListener =
- clazz.getMethod("addMouseListener", new Class<?>[]
- { MouseListener.class });
- messageTypeClass =
- Class.forName("java.awt.TrayIcon$MessageType");
- displayMessage =
- clazz.getMethod("displayMessage", new Class<?>[]
- { String.class, String.class, messageTypeClass });
- setIcon = clazz.getMethod("setImage", new Class<?>[]
- { Image.class });
- setIconAutoSize =
- clazz.getMethod("setImageAutoSize", new Class<?>[]
- { boolean.class });
- }
- catch (ClassNotFoundException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (NoSuchMethodException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
-
- try
- {
- if (popup instanceof JPopupMenu)
- {
- impl = constructor.newInstance(
- new Object[] { image, tooltip });
- addMouseListener(new AWTMouseAdapter((JPopupMenu) popup));
- }
- else
- {
- impl = constructor.newInstance(
- new Object[] { image, tooltip, popup });
- }
- }
- catch (IllegalAccessException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (InstantiationException ex)
- {
- throw new UnsupportedOperationException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause == null)
- throw new UnsupportedOperationException(ex);
- if (cause instanceof IllegalArgumentException)
- throw (IllegalArgumentException) cause;
- if (cause instanceof UnsupportedOperationException)
- throw (UnsupportedOperationException) cause;
- if (cause instanceof HeadlessException)
- throw (HeadlessException) cause;
- if (cause instanceof SecurityException)
- throw (SecurityException) cause;
- throw new UnsupportedOperationException(cause);
- }
- }
-
- public void addActionListener(ActionListener listener)
- {
- try
- {
- addActionListener.invoke(getImpl(), new Object[]
- { listener });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- public void addMouseListener(MouseListener listener)
- {
- try
- {
- addMouseListener.invoke(getImpl(), new Object[] { listener });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- 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
- {
- try
- {
- displayMessage.invoke(getImpl(), new Object[]
- { caption, text, messageType.name() });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause instanceof NullPointerException)
- throw (NullPointerException) cause;
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- public Object getImpl()
- {
- return impl;
- }
-
- public void setIcon(ImageIcon icon) throws NullPointerException
- {
- try
- {
- setIcon.invoke(getImpl(), new Object[]
- { (icon == null) ? null : icon.getImage() });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- if (cause instanceof NullPointerException)
- throw (NullPointerException) cause;
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
-
- public void setIconAutoSize(boolean autoSize)
- {
- try
- {
- setIconAutoSize.invoke(getImpl(), new Object[]
- { autoSize });
- }
- catch (IllegalAccessException ex)
- {
- throw new UndeclaredThrowableException(ex);
- }
- catch (InvocationTargetException ex)
- {
- Throwable cause = ex.getCause();
- throw new UndeclaredThrowableException((cause == null) ? ex
- : cause);
- }
- }
- }
-
- /**
- * Extended mouse adapter to show the JPopupMenu in Java 6
- * Based on : http://weblogs.java.net/blog/ixmal/archive/2006/05/using_jpopupmen.html
- * And : http://weblogs.java.net/blog/alexfromsun/archive/2008/02/jtrayicon_updat.html
- *
- * Use a hidden JWindow (JDialog for Windows) to manage the JPopupMenu.
- *
- * @author Damien Roth
- */
- private static 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();
- }
- }
- }
- }
-}
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 f774b23..0b40798 100644 --- a/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java +++ b/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java @@ -19,20 +19,25 @@ package net.java.sip.communicator.impl.osdependent.jdic; import java.awt.*; import java.awt.event.*; +import java.awt.image.*; import java.net.*; +import java.util.HashMap; +import java.util.Map; import javax.swing.*; import javax.swing.event.*; import net.java.sip.communicator.impl.osdependent.*; -import net.java.sip.communicator.impl.osdependent.SystemTray; -import net.java.sip.communicator.impl.osdependent.TrayIcon; +import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray; +import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon; +import net.java.sip.communicator.impl.osdependent.windows.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.protocol.*; 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.*; @@ -51,7 +56,6 @@ import com.apple.eawt.*; public class SystrayServiceJdicImpl extends AbstractSystrayService { - /** * The systray. */ @@ -93,10 +97,6 @@ public class SystrayServiceJdicImpl private ImageIcon logoIconWhite; - private ImageIcon envelopeIcon; - - private ImageIcon envelopeIconWhite; - /** * The dock Icons used only in Mac version */ @@ -131,10 +131,9 @@ public class SystrayServiceJdicImpl super(OsDependentActivator.bundleContext); SystemTray systray; - try { - systray = SystemTray.getDefaultSystemTray(); + systray = SystemTray.getSystemTray(); } catch (Throwable t) { @@ -147,10 +146,39 @@ public class SystrayServiceJdicImpl logger.error("Failed to create a systray!", t); } } - this.systray = systray; + this.systray = systray; if (this.systray != null) + { initSystray(); + } + } + + @Override + public Map<String, String> getSystrayModes() + { + return new HashMap<String, String>() + {{ + put("disabled", "service.systray.mode.DISABLED"); + if (java.awt.SystemTray.isSupported()) + { + put("native", "service.systray.mode.NATIVE"); + } + + if (!OSUtils.IS_MAC && !OSUtils.IS_WINDOWS) + { + put("appindicator", + "service.systray.mode.APPINDICATOR"); + put("appindicator_static", + "service.systray.mode.APPINDICATOR_STATIC"); + } + }}; + } + + @Override + public String getActiveSystrayMode() + { + return SystemTray.getSystemTrayMode(); } /** @@ -187,53 +215,33 @@ public class SystrayServiceJdicImpl return; } - menu = TrayMenuFactory.createTrayMenu(this, systray.isSwing()); + 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"); - envelopeIcon = Resources.getImage( - "service.systray.MESSAGE_ICON_WINDOWS"); - } - /* - * 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"); - envelopeIcon = Resources.getImage( - "service.systray.MESSAGE_ICON_MACOSX"); - envelopeIconWhite = Resources.getImage( - "service.systray.MESSAGE_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"); - envelopeIcon = Resources.getImage("service.systray.MESSAGE_ICON"); } /* @@ -243,7 +251,7 @@ public class SystrayServiceJdicImpl currentIcon = isMac ? logoIcon : logoIconOffline; trayIcon - = new TrayIcon( + = systray.createTrayIcon( currentIcon, Resources.getApplicationString( "service.gui.APPLICATION_NAME"), @@ -268,21 +276,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 @@ -296,11 +298,7 @@ public class SystrayServiceJdicImpl { public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - ImageIcon newIcon - = (currentIcon == envelopeIcon) - ? envelopeIconWhite - : logoIconWhite; - + ImageIcon newIcon = logoIconWhite; trayIcon.setIcon(newIcon); currentIcon = newIcon; } @@ -308,11 +306,7 @@ public class SystrayServiceJdicImpl public void popupMenuWillBecomeInvisible( PopupMenuEvent e) { - ImageIcon newIcon - = (currentIcon == envelopeIconWhite) - ? envelopeIcon - : logoIcon; - + ImageIcon newIcon = logoIcon; getTrayIcon().setIcon(newIcon); currentIcon = newIcon; } @@ -353,12 +347,12 @@ public class SystrayServiceJdicImpl public void run() { systray.addTrayIcon(trayIcon); + trayIcon.setDefaultAction(defaultActionItem); } }); initialized = true; - - uiService.setExitOnMainWindowClose(false); + uiService.setMainWindowCanHide(true); } /** @@ -402,12 +396,6 @@ public class SystrayServiceJdicImpl if (!isMac) systrayIconToSet = logoIconDND; break; - case SystrayService.ENVELOPE_IMG_TYPE: - systrayIconToSet - = (isMac && TrayMenuFactory.isVisible(menu)) - ? envelopeIconWhite - : envelopeIcon; - break; } if (systrayIconToSet != null) @@ -469,14 +457,90 @@ public class SystrayServiceJdicImpl } } - private boolean checkInitialized() + @Override + public boolean checkInitialized() { - if (!initialized) - logger.error("Systray not init"); return initialized; } /** + * Set the number of pending notifications to the the application icon + * (Dock on OSX, TaskBar on Windows, nothing on Linux currently). + */ + @Override + public void setNotificationCount(int count) + { + if (OSUtils.IS_MAC) + { + Application application = Application.getApplication(); + application.setDockIconBadge(new Integer(count).toString()); + } + else if (OSUtils.IS_WINDOWS) + { + UIService uiService = OsDependentActivator.getUIService(); + if (uiService == null) + { + return; + } + + ExportedWindow mainWindow = + uiService.getExportedWindow(ExportedWindow.MAIN_WINDOW); + if (mainWindow == null + || !(mainWindow.getSource() instanceof Component)) + { + return; + } + + BufferedImage img = null; + if (count > 0) + { + img = createOverlayImage(new Integer(count).toString()); + } + + try + { + TaskBarList3.getInstance().SetOverlayIcon( + (Component) mainWindow.getSource(), img, null); + } + catch (Exception ex) + { + logger.error("Could not set the notification count.", ex); + } + } + } + + private BufferedImage createOverlayImage(String text) + { + int size = 16; + BufferedImage image = + new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + //background + g.setPaint(new Color(0, 0, 0, 102)); + g.fillRoundRect(0, 0, size, size, size, size); + + //filling + int mainRadius = 14; + g.setPaint(new Color(255, 98, 89)); + g.fillRoundRect(size / 2 - mainRadius / 2, size / 2 - mainRadius / 2, + mainRadius, mainRadius, size, size); + + //text + Font font = g.getFont(); + g.setFont(new Font(font.getName(), Font.BOLD, 9)); + FontMetrics fontMetrics = g.getFontMetrics(); + int textWidth = fontMetrics.stringWidth(text); + g.setColor(Color.white); + g.drawString(text, size / 2 - textWidth / 2, + size / 2 - fontMetrics.getHeight() / 2 + fontMetrics.getAscent()); + + return image; + } + + /** * @return the trayIcon */ public TrayIcon getTrayIcon() @@ -551,7 +615,7 @@ public class SystrayServiceJdicImpl OsDependentActivator.bundleContext.removeServiceListener( this); - if (!initialized) + if (!initialized && systray != null) initSystray(); } } 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 2b5df19..cae3ada 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,16 +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 + ) { - // Enable swing for java 1.6 except for the mac version - if (!swing && !OSUtils.IS_MAC) - swing = true; - - Object trayMenu = swing ? new JPopupMenu() : new PopupMenu(); + final Object trayMenu = swing ? new JPopupMenu() : new PopupMenu(); ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) @@ -184,9 +185,11 @@ public final class TrayMenuFactory + "CHAT_PRESENCE_DISABLED", false); - if (!chatPresenceDisabled.booleanValue()) + if (!chatPresenceDisabled.booleanValue() && accountMenuSupported) { - add(trayMenu, new StatusSubMenu(swing).getMenu()); + add( + trayMenu, + new StatusSubMenu(swing, accountMenuSupported).getMenu()); addSeparator(trayMenu); } @@ -201,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, @@ -245,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 0db99ad..91f2791 100644 --- a/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf +++ b/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf @@ -5,26 +5,29 @@ Bundle-Vendor: jitsi.org Bundle-Version: 0.0.1
Bundle-SymbolicName: net.java.sip.communicator.osdependent
Export-Package: net.java.sip.communicator.service.desktop
-Import-Package: org.osgi.framework,
- com.apple.cocoa.application,
+Import-Package: com.apple.cocoa.application,
com.apple.cocoa.foundation,
com.apple.eawt,
- org.jitsi.service.configuration,
+ com.sun.jna,
+ com.sun.jna.platform.win32,
+ com.sun.jna.platform.win32.COM,
+ com.sun.jna.ptr,
+ com.sun.jna.win32,
net.java.sip.communicator.service.gui,
net.java.sip.communicator.service.gui.event,
net.java.sip.communicator.service.protocol,
net.java.sip.communicator.service.protocol.globalstatus,
net.java.sip.communicator.service.protocol.event,
- org.jitsi.service.resources, net.java.sip.communicator.service.resources,
+ net.java.sip.communicator.service.resources,
net.java.sip.communicator.service.shutdown,
net.java.sip.communicator.service.systray,
net.java.sip.communicator.service.systray.event,
- org.jitsi.util,
net.java.sip.communicator.util,
net.java.sip.communicator.plugin.desktoputil,
net.java.sip.communicator.plugin.desktoputil.presence,
javax.accessibility,
javax.imageio,
+ javax.imageio.stream,
javax.swing,
javax.swing.border,
javax.swing.event,
@@ -36,4 +39,9 @@ Import-Package: org.osgi.framework, javax.swing.text,
javax.swing.text.html,
javax.swing.tree,
- javax.swing.undo
+ javax.swing.undo,
+ org.apache.commons.lang3.tuple,
+ org.jitsi.service.configuration,
+ org.jitsi.service.resources,
+ org.jitsi.util,
+ org.osgi.framework
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 diff --git a/src/net/java/sip/communicator/impl/osdependent/windows/ImageConverter.java b/src/net/java/sip/communicator/impl/osdependent/windows/ImageConverter.java new file mode 100644 index 0000000..99754d3 --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/windows/ImageConverter.java @@ -0,0 +1,172 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Copyright 2000-2016 JetBrains s.r.o. + * 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.windows; + +import java.awt.image.*; +import java.nio.*; + +import com.sun.jna.*; +import com.sun.jna.platform.win32.*; +import com.sun.jna.platform.win32.WinDef.*; + +/** + * Image conversion utilities. + * + * Parts of this code are based on AppIcon.java from IntelliJ community. + * Licensed under Apache 2.0, Copyright 2000-2016 JetBrains s.r.o. + */ +public class ImageConverter +{ + /** + * Converts the <tt>BufferedImage</tt> to an ICONDIR structure. + * @param src The source image to convert + * @return an ICONDIR structure with the data of the passed source image + */ + public static byte[] writeTransparentIcoImage(BufferedImage src) + { + int bitCount = 32; + + int scanline_size = (bitCount * src.getWidth() + 7) / 8; + if ((scanline_size % 4) != 0) + scanline_size += 4 - (scanline_size % 4); // pad scanline to 4 byte + // size. + int t_scanline_size = (src.getWidth() + 7) / 8; + if ((t_scanline_size % 4) != 0) + t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4 + // byte size. + int imageSize = 40 + src.getHeight() * scanline_size + + src.getHeight() * t_scanline_size; + + // sizeof(ICONDIR) + // + sizeof(ICONDIRENTRY) + // + (sizeof(BITMAPINFOHEADER)+data) + ByteBuffer bos = ByteBuffer.allocate(6 + 16 + imageSize); + bos.order(ByteOrder.LITTLE_ENDIAN); + + // ICONDIR + bos.putShort((short) 0); // reserved + bos.putShort((short) 1); // 1=ICO, 2=CUR + bos.putShort((short) 1); // count + + // ICONDIRENTRY + int iconDirEntryWidth = src.getWidth(); + int iconDirEntryHeight = src.getHeight(); + if (iconDirEntryWidth > 255 || iconDirEntryHeight > 255) + { + iconDirEntryWidth = 0; + iconDirEntryHeight = 0; + } + bos.put((byte) iconDirEntryWidth); + bos.put((byte) iconDirEntryHeight); + bos.put((byte) 0); + bos.put((byte) 0); // reserved + bos.putShort((short) 1); // color planes + bos.putShort((short) bitCount); + bos.putInt(imageSize); + bos.putInt(22); // image offset + + // BITMAPINFOHEADER + bos.putInt(40); // size + bos.putInt(src.getWidth()); + bos.putInt(2 * src.getHeight()); + bos.putShort((short) 1); // planes + bos.putShort((short) bitCount); + bos.putInt(0); // compression + bos.putInt(0); // image size + bos.putInt(0); // x pixels per meter + bos.putInt(0); // y pixels per meter + bos.putInt(0); // colors used, 0 = (1 << bitCount) (ignored) + bos.putInt(0); // colors important + + int bit_cache = 0; + int bits_in_cache = 0; + int row_padding = scanline_size - (bitCount * src.getWidth() + 7) / 8; + for (int y = src.getHeight() - 1; y >= 0; y--) + { + for (int x = 0; x < src.getWidth(); x++) + { + int argb = src.getRGB(x, y); + + bos.put((byte) (0xff & argb)); + bos.put((byte) (0xff & (argb >> 8))); + bos.put((byte) (0xff & (argb >> 16))); + bos.put((byte) (0xff & (argb >> 24))); + } + + for (int x = 0; x < row_padding; x++) + bos.put((byte) 0); + } + + int t_row_padding = t_scanline_size - (src.getWidth() + 7) / 8; + for (int y = src.getHeight() - 1; y >= 0; y--) + { + for (int x = 0; x < src.getWidth(); x++) + { + int argb = src.getRGB(x, y); + int alpha = 0xff & (argb >> 24); + bit_cache <<= 1; + if (alpha == 0) + bit_cache |= 1; + bits_in_cache++; + if (bits_in_cache >= 8) + { + bos.put((byte) (0xff & bit_cache)); + bit_cache = 0; + bits_in_cache = 0; + } + } + + if (bits_in_cache > 0) + { + bit_cache <<= (8 - bits_in_cache); + bos.put((byte) (0xff & bit_cache)); + bit_cache = 0; + bits_in_cache = 0; + } + + for (int x = 0; x < t_row_padding; x++) + bos.put((byte) 0); + } + + byte[] result = new byte[bos.position()]; + System.arraycopy(bos.array(), 0, result, 0, bos.position()); + return result; + } + + /** + * Converts an ICONDIR ico to an HICON handle + * @param ico the image data + * @return A Windows HICON handle + */ + public static HICON createIcon(byte[] ico) + { + Memory buffer = new Memory(ico.length); + buffer.write(0, ico, 0, ico.length); + int nSize = 100; + int offset = User32Ex.INSTANCE.LookupIconIdFromDirectoryEx(buffer, true, + nSize, nSize, 0); + if (offset != 0) + { + return User32Ex.INSTANCE.CreateIconFromResourceEx( + buffer.share(offset), new WinDef.DWORD(0), true, + new WinDef.DWORD(0x00030000), nSize, nSize, 0); + } + + return null; + } +} diff --git a/src/net/java/sip/communicator/impl/osdependent/windows/TaskBarList3.java b/src/net/java/sip/communicator/impl/osdependent/windows/TaskBarList3.java new file mode 100644 index 0000000..a21cd8f --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/windows/TaskBarList3.java @@ -0,0 +1,150 @@ +/* + * 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.windows; + +import java.awt.*; +import java.awt.image.*; + +import org.jitsi.util.*; + +import com.sun.jna.*; +import com.sun.jna.platform.win32.*; +import com.sun.jna.platform.win32.COM.*; +import com.sun.jna.platform.win32.Guid.*; +import com.sun.jna.platform.win32.WinDef.*; +import com.sun.jna.platform.win32.WinNT.*; +import com.sun.jna.ptr.*; + +/** + * JNA wrapper for the ITaskBarList3 COM interface. + * https://msdn.microsoft.com/en-us/library/dd391696(v=vs.85).aspx + * + * @author Ingo Bauersachs + */ +public class TaskBarList3 + extends Unknown +{ + private static final GUID CLSID_TaskbarList = + new GUID("{56FDF344-FD6D-11d0-958A-006097C9A090}"); + + private static final GUID IID_ITaskbarList3 = + new GUID("{ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf}"); + + private static TaskBarList3 instance; + + /** + * Gets the ITaskBarList3 interface and initializes it with HrInit + * @return A ready to use TaskBarList3 object. + * @throws COMException when the interface could not be accessed + */ + public static TaskBarList3 getInstance() + { + if (instance == null && OSUtils.IS_WINDOWS) + { + Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, 0); + PointerByReference p = new PointerByReference(); + WinNT.HRESULT hr = + Ole32.INSTANCE.CoCreateInstance(CLSID_TaskbarList, Pointer.NULL, + ObjBase.CLSCTX_ALL, IID_ITaskbarList3, p); + COMUtils.checkRC(hr); + instance = new TaskBarList3(p.getValue()); + } + + return instance; + } + + private TaskBarList3(Pointer p) + { + super(p); + HrInit(); + } + + // VTable + // ------ + // IUnknown: + // 0: AddRef + // 1: QueryInterface + // 2: Release + // + // ITaskBarList: + // 3: HrInit + // 4: AddTab + // 5: DeleteTab + // 6: ActivateTab + // 7: SetActiveAlt + // + // ITaskBarList2 + // 8: MarkFullscreenWindow + // + // ITaskBarList3: + // 9: SetProgressValue + // 10: SetProgressState + // 11: RegisterTab + // 12: UnregisterTab + // 13: SetTabOrder + // 14: SetTabActive + // 15: ThumbBarAddButtons + // 16: ThumbBarAddButtons + // 17: ThumbBarSetImageList + // 18: SetOverlayIcon + // 19: SetThumbnailTooltip + // 20: SetThumbnailClip + // + // ITaskbarList4: + // 21: SetTabProperties + + /** + * https://msdn.microsoft.com/en-us/library/bb774650(v=vs.85).aspx + */ + private void HrInit() + { + int hr = this._invokeNativeInt(3, new Object[] + { this.getPointer() }); + COMUtils.checkRC(new HRESULT(hr)); + } + + /** + * https://msdn.microsoft.com/en-us/library/dd391696(v=vs.85).aspx + */ + private void SetOverlayIcon(HWND hwnd, HICON hIcon, String pszDescription) + { + int hr = this._invokeNativeInt(18, new Object[] + { this.getPointer(), hwnd, hIcon, pszDescription }); + COMUtils.checkRC(new HRESULT(hr)); + } + + /** + * Sets an overlay image to the taskbar icon. + * @param frame The window that should receive the overlay + * @param image The overlay image, can be <tt>null</tt> to clear the overlay + * @param description An optional tooltip text, can be <tt>null</tt> + */ + public void SetOverlayIcon(Component frame, BufferedImage image, + String description) + { + HICON ico = null; + if (image != null) + { + byte[] iconBytes = ImageConverter.writeTransparentIcoImage(image); + ico = ImageConverter.createIcon(iconBytes); + } + + HWND hwnd = new HWND(Native.getComponentPointer(frame)); + SetOverlayIcon(hwnd, ico, description); + } +} diff --git a/src/net/java/sip/communicator/impl/osdependent/windows/User32Ex.java b/src/net/java/sip/communicator/impl/osdependent/windows/User32Ex.java new file mode 100644 index 0000000..59f3a7a --- /dev/null +++ b/src/net/java/sip/communicator/impl/osdependent/windows/User32Ex.java @@ -0,0 +1,47 @@ +/* + * 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.windows; + +import com.sun.jna.*; +import com.sun.jna.platform.win32.*; +import com.sun.jna.win32.*; + +/** + * Extension to missing user32 Windows APIs + * + * @author Ingo Bauersachs + */ +interface User32Ex + extends StdCallLibrary +{ + User32Ex INSTANCE = (User32Ex) Native.loadLibrary("user32", User32Ex.class, + W32APIOptions.DEFAULT_OPTIONS); + + /** + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms648074(v=vs.85).aspx + */ + int LookupIconIdFromDirectoryEx(Memory presbits, boolean fIcon, + int cxDesired, int cyDesired, int Flags); + + /** + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms648061(v=vs.85).aspx + */ + WinDef.HICON CreateIconFromResourceEx(Pointer pbIconBits, + WinDef.DWORD cbIconBits, boolean fIcon, WinDef.DWORD dwVersion, + int cxDesired, int cyDesired, int uFlags); +}
\ No newline at end of file |