diff options
author | Ingo Bauersachs <ingo@jitsi.org> | 2016-05-29 23:13:19 +0200 |
---|---|---|
committer | Ingo Bauersachs <ingo@jitsi.org> | 2016-05-29 23:14:14 +0200 |
commit | 5c24dad320f355f721fc4562fbed7ecf72b66d50 (patch) | |
tree | 9942955907875c28c049bfeec31313eee80adf6b /src | |
parent | 7f08c0d239e1c63ebb44dc025d7507017d5d1add (diff) | |
download | jitsi-5c24dad320f355f721fc4562fbed7ecf72b66d50.zip jitsi-5c24dad320f355f721fc4562fbed7ecf72b66d50.tar.gz jitsi-5c24dad320f355f721fc4562fbed7ecf72b66d50.tar.bz2 |
Show pending notifications on OSX and Windows
Diffstat (limited to 'src')
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 |