aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorIngo Bauersachs <ingo@jitsi.org>2016-05-29 23:13:19 +0200
committerIngo Bauersachs <ingo@jitsi.org>2016-05-29 23:14:14 +0200
commit5c24dad320f355f721fc4562fbed7ecf72b66d50 (patch)
tree9942955907875c28c049bfeec31313eee80adf6b /src
parent7f08c0d239e1c63ebb44dc025d7507017d5d1add (diff)
downloadjitsi-5c24dad320f355f721fc4562fbed7ecf72b66d50.zip
jitsi-5c24dad320f355f721fc4562fbed7ecf72b66d50.tar.gz
jitsi-5c24dad320f355f721fc4562fbed7ecf72b66d50.tar.bz2
Show pending notifications on OSX and Windows
Diffstat (limited to 'src')
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java79
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf18
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/windows/ImageConverter.java172
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/windows/TaskBarList3.java150
-rw-r--r--src/net/java/sip/communicator/impl/osdependent/windows/User32Ex.java47
5 files changed, 460 insertions, 6 deletions
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 48d0f8c..6fd9d8c 100644
--- a/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java
+++ b/src/net/java/sip/communicator/impl/osdependent/jdic/SystrayServiceJdicImpl.java
@@ -19,6 +19,7 @@ package net.java.sip.communicator.impl.osdependent.jdic;
import java.awt.*;
import java.awt.event.*;
+import java.awt.image.*;
import java.net.*;
import javax.swing.*;
@@ -27,6 +28,7 @@ import javax.swing.event.*;
import net.java.sip.communicator.impl.osdependent.*;
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.*;
@@ -453,6 +455,83 @@ public class SystrayServiceJdicImpl
}
/**
+ * 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()
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..bc56371 100644
--- a/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf
+++ b/src/net/java/sip/communicator/impl/osdependent/osdependent.manifest.mf
@@ -5,21 +5,23 @@ 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,
@@ -36,4 +38,8 @@ Import-Package: org.osgi.framework,
javax.swing.text,
javax.swing.text.html,
javax.swing.tree,
- javax.swing.undo
+ javax.swing.undo,
+ org.jitsi.service.configuration,
+ org.jitsi.service.resources,
+ org.jitsi.util,
+ org.osgi.framework
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