From 787e16b4e001cfd165d36c3e6a60d65954a93369 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 11 Jul 2017 01:39:24 +0200 Subject: [PATCH] WIP Windows Native implementation --- .../systemTray/jna/windows/COLORREF.java | 29 + src/dorkbox/systemTray/jna/windows/GDI32.java | 148 ++++ .../jna/windows/GetLastErrorException.java | 33 + .../systemTray/jna/windows/HBITMAPWrap.java | 104 +++ .../systemTray/jna/windows/HICONWrap.java | 72 ++ .../systemTray/jna/windows/Kernel32.java | 43 + .../systemTray/jna/windows/Listener.java | 28 + src/dorkbox/systemTray/jna/windows/MSG.java | 39 + .../systemTray/jna/windows/MsImg32.java | 40 + .../systemTray/jna/windows/Parameter.java | 17 + .../systemTray/jna/windows/Shell32.java | 36 + .../systemTray/jna/windows/User32.java | 239 ++++++ .../systemTray/jna/windows/User32_32.java | 175 ++++ .../systemTray/jna/windows/User32_64.java | 173 ++++ .../systemTray/jna/windows/WNDPROC.java | 28 + .../jna/windows/WindowsEventDispatch.java | 207 +++++ .../jna/windows/structs/BLENDFUNCTION.java | 45 ++ .../jna/windows/structs/DRAWITEMSTRUCT.java | 74 ++ .../jna/windows/structs/ICONINFO.java | 50 ++ .../jna/windows/structs/LOGFONT.java | 77 ++ .../windows/structs/MEASUREITEMSTRUCT.java | 62 ++ .../jna/windows/structs/MENUITEMINFO.java | 89 +++ .../jna/windows/structs/NONCLIENTMETRICS.java | 88 +++ .../jna/windows/structs/NOTIFYICONDATA.java | 116 +++ .../nativeUI/WindowsBaseMenuItem.java | 177 +++++ .../systemTray/nativeUI/WindowsMenu.java | 744 ++++++++++++++++++ .../systemTray/nativeUI/WindowsMenuItem.java | 94 +++ .../nativeUI/WindowsMenuItemCheckbox.java | 153 ++++ .../nativeUI/WindowsMenuItemSeparator.java | 45 ++ .../nativeUI/WindowsMenuItemStatus.java | 71 ++ .../nativeUI/_WindowsNativeTray.java | 272 +++++++ 31 files changed, 3568 insertions(+) create mode 100644 src/dorkbox/systemTray/jna/windows/COLORREF.java create mode 100644 src/dorkbox/systemTray/jna/windows/GDI32.java create mode 100644 src/dorkbox/systemTray/jna/windows/GetLastErrorException.java create mode 100644 src/dorkbox/systemTray/jna/windows/HBITMAPWrap.java create mode 100644 src/dorkbox/systemTray/jna/windows/HICONWrap.java create mode 100644 src/dorkbox/systemTray/jna/windows/Kernel32.java create mode 100644 src/dorkbox/systemTray/jna/windows/Listener.java create mode 100644 src/dorkbox/systemTray/jna/windows/MSG.java create mode 100644 src/dorkbox/systemTray/jna/windows/MsImg32.java create mode 100644 src/dorkbox/systemTray/jna/windows/Parameter.java create mode 100644 src/dorkbox/systemTray/jna/windows/Shell32.java create mode 100644 src/dorkbox/systemTray/jna/windows/User32.java create mode 100644 src/dorkbox/systemTray/jna/windows/User32_32.java create mode 100644 src/dorkbox/systemTray/jna/windows/User32_64.java create mode 100644 src/dorkbox/systemTray/jna/windows/WNDPROC.java create mode 100644 src/dorkbox/systemTray/jna/windows/WindowsEventDispatch.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/BLENDFUNCTION.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/DRAWITEMSTRUCT.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/ICONINFO.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/LOGFONT.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/MEASUREITEMSTRUCT.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/MENUITEMINFO.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/NONCLIENTMETRICS.java create mode 100644 src/dorkbox/systemTray/jna/windows/structs/NOTIFYICONDATA.java create mode 100644 src/dorkbox/systemTray/nativeUI/WindowsBaseMenuItem.java create mode 100644 src/dorkbox/systemTray/nativeUI/WindowsMenu.java create mode 100644 src/dorkbox/systemTray/nativeUI/WindowsMenuItem.java create mode 100644 src/dorkbox/systemTray/nativeUI/WindowsMenuItemCheckbox.java create mode 100644 src/dorkbox/systemTray/nativeUI/WindowsMenuItemSeparator.java create mode 100644 src/dorkbox/systemTray/nativeUI/WindowsMenuItemStatus.java create mode 100644 src/dorkbox/systemTray/nativeUI/_WindowsNativeTray.java diff --git a/src/dorkbox/systemTray/jna/windows/COLORREF.java b/src/dorkbox/systemTray/jna/windows/COLORREF.java new file mode 100644 index 0000000..791b778 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/COLORREF.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.WinNT; + +public class COLORREF extends WinNT.HANDLE { + public COLORREF() { + + } + + public COLORREF(Pointer p) { + super(p); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/GDI32.java b/src/dorkbox/systemTray/jna/windows/GDI32.java new file mode 100644 index 0000000..95f080a --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/GDI32.java @@ -0,0 +1,148 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HBITMAP; +import static com.sun.jna.platform.win32.WinDef.HDC; +import static com.sun.jna.platform.win32.WinDef.HFONT; +import static com.sun.jna.platform.win32.WinDef.RECT; +import static com.sun.jna.platform.win32.WinGDI.BITMAPINFO; +import static com.sun.jna.platform.win32.WinNT.HANDLE; + +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.WinUser; +import com.sun.jna.ptr.PointerByReference; +import com.sun.jna.win32.W32APIOptions; + +import dorkbox.systemTray.jna.windows.structs.LOGFONT; + +public +class GDI32 { + static { + Native.register(NativeLibrary.getInstance("GDI32", W32APIOptions.DEFAULT_OPTIONS)); + } + + public static final int ETO_OPAQUE = 2; + public static final int SRCCOPY = 0xCC0020; + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd144938(v=vs.85).aspx + */ + public static native + boolean GetTextExtentPoint32(HDC hdc, String lpString, int c, WinUser.SIZE lpSize); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd145093(v=vs.85).aspx + */ + public static native + COLORREF SetTextColor(HDC hdc, COLORREF crColor); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd162964(v=vs.85).aspx + */ + public static native + COLORREF SetBkColor(HDC hdc, COLORREF crColor); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd162713(v=vs.85).aspx + */ + public static native + boolean ExtTextOut(HDC hdc, int X, int Y, int fuOptions, RECT lprc, String lpString, int cbCount, int[] lpDx); + + /** + * https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd183500(v=vs.85).aspx + */ + public static native + HFONT CreateFontIndirect(LOGFONT l); + + /** + * The SelectObject function selects an object into the specified device context (DC). + * The new object replaces the previous object of the same type. + * + * @param hDC Handle to the DC. + * @param hGDIObj Handle to the object to be selected. + * + * @return If the selected object is not a region and the function succeeds, the return value + * is a handle to the object being replaced. If the selected object is a region and the + * function succeeds, the return value is one of the REGION values. + */ + public static native + HANDLE SelectObject(HDC hDC, HANDLE hGDIObj); + + /** + * The CreateCompatibleDC function creates a memory device context (DC) compatible with the specified device. + * + * @param hDC Handle to an existing DC. If this handle is NULL, the function creates a memory DC compatible with the + * application's current screen. + * + * @return If the function succeeds, the return value is the handle to a memory DC. + * If the function fails, the return value is NULL. + * To get extended error information, call GetLastError. + */ + public static native + HDC CreateCompatibleDC(HDC hDC); + + /** + * The DeleteDC function deletes the specified device context (DC). + * + * @param hDC Handle to the device context. + * + * @return If the function succeeds, the return value is nonzero. + * If the function fails, the return value is zero. + * To get extended error information, call GetLastError. + */ + public static native + boolean DeleteDC(HDC hDC); + + /** + * The DeleteObject function deletes a logical pen, brush, font, bitmap, region, or palette, + * freeing all system resources associated with the object. After the object is deleted, the + * specified handle is no longer valid. + * + * @param hObject Handle to a logical pen, brush, font, bitmap, region, or palette. + * + * @return If the function succeeds, the return value is nonzero. + * If the specified handle is not valid or is currently selected into a DC, the return value is zero. + * To get extended error information, call GetLastError. + */ + public static native + boolean DeleteObject(HANDLE hObject); + + /** + * The CreateDIBSection function creates a DIB that applications can write to directly. + * The function gives you a pointer to the location of the bitmap bit values. You can supply + * a handle to a file-mapping object that the function will use to create the bitmap, or you + * can let the system allocate the memory for the bitmap. + * + * @param hDC Handle to a device context. If the value of iUsage is DIB_PAL_COLORS, the function uses this + * device context's logical palette to initialize the DIB colors. + * @param pbmi Pointer to a BITMAPINFO structure that specifies various attributes of the DIB, including + * the bitmap dimensions and colors. + * @param iUsage Specifies the type of data contained in the bmiColors array member of the BITMAPINFO structure + * pointed to by pbmi (either logical palette indexes or literal RGB values). + * @param ppvBits Pointer to a variable that receives a pointer to the location of the DIB bit values. + * @param hSection Handle to a file-mapping object that the function will use to create the DIB. This parameter can be NULL. + * @param dwOffset Specifies the offset from the beginning of the file-mapping object referenced by hSection where storage + * for the bitmap bit values is to begin. + * + * @return Specifies the offset from the beginning of the file-mapping object referenced by hSection where storage + * for the bitmap bit values is to begin. + */ + public static native + HBITMAP CreateDIBSection(HDC hDC, BITMAPINFO pbmi, int iUsage, PointerByReference ppvBits, Pointer hSection, int dwOffset); +} diff --git a/src/dorkbox/systemTray/jna/windows/GetLastErrorException.java b/src/dorkbox/systemTray/jna/windows/GetLastErrorException.java new file mode 100644 index 0000000..0eea5bb --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/GetLastErrorException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +public +class GetLastErrorException extends RuntimeException { + private static final long serialVersionUID = 3980497906900380359L; + + private String message; + + public + GetLastErrorException() { + message = Kernel32.getLastErrorMessage(); + } + + public + String toString() { + return message; + } +} diff --git a/src/dorkbox/systemTray/jna/windows/HBITMAPWrap.java b/src/dorkbox/systemTray/jna/windows/HBITMAPWrap.java new file mode 100644 index 0000000..b90be31 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/HBITMAPWrap.java @@ -0,0 +1,104 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HBITMAP; +import static com.sun.jna.platform.win32.WinDef.HDC; + +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.Raster; + +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.WinGDI; +import com.sun.jna.ptr.PointerByReference; + +public class HBITMAPWrap extends HBITMAP { + + // https://github.com/twall/jna/blob/master/contrib/alphamaskdemo/com/sun/jna/contrib/demo/AlphaMaskDemo.java + private static + HBITMAP createBitmap(BufferedImage image) { + int w = image.getWidth(null); + int h = image.getHeight(null); + HDC screenDC = User32.IMPL.GetDC(null); + HDC memDC = GDI32.CreateCompatibleDC(screenDC); + HBITMAP hBitmap = null; + + try { + BufferedImage buf = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); + Graphics2D g = (Graphics2D) buf.getGraphics(); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + g.drawImage(image, 0, 0, w, h, null); + + WinGDI.BITMAPINFO bmi = new WinGDI.BITMAPINFO(); + bmi.bmiHeader.biWidth = w; + bmi.bmiHeader.biHeight = h; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = WinGDI.BI_RGB; + bmi.bmiHeader.biSizeImage = w * h * 4; + + PointerByReference ppbits = new PointerByReference(); + hBitmap = GDI32.CreateDIBSection(memDC, bmi, WinGDI.DIB_RGB_COLORS, ppbits, null, 0); + Pointer pbits = ppbits.getValue(); + + Raster raster = buf.getData(); + int[] pixel = new int[4]; + int[] bits = new int[w * h]; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + raster.getPixel(x, h - y - 1, pixel); + int red = (pixel[2] & 0xFF) << 0; + int green = (pixel[1] & 0xFF) << 8; + int blue = (pixel[0] & 0xFF) << 16; + int alpha = (pixel[3] & 0xFF) << 24; + bits[x + y * w] = alpha | red | green | blue; + } + } + pbits.write(0, bits, 0, bits.length); + return hBitmap; + } finally { + User32.IMPL.ReleaseDC(null, screenDC); + GDI32.DeleteDC(memDC); + } + } + + BufferedImage img; + + public HBITMAPWrap(BufferedImage img) { + setPointer(createBitmap(img).getPointer()); + + this.img = img; + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + public void close() { + if (Pointer.nativeValue(getPointer()) != 0) { + GDI32.DeleteObject(this); + setPointer(new Pointer(0)); + } + } + + public BufferedImage getImage() { + return img; + } +} diff --git a/src/dorkbox/systemTray/jna/windows/HICONWrap.java b/src/dorkbox/systemTray/jna/windows/HICONWrap.java new file mode 100644 index 0000000..a641a8c --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/HICONWrap.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HBITMAP; +import static com.sun.jna.platform.win32.WinDef.HICON; + +import com.sun.jna.Pointer; + +import dorkbox.systemTray.jna.windows.structs.ICONINFO; + +/** + * http://www.pinvoke.net/default.aspx/user32.createiconindirect + */ +public class HICONWrap extends HICON { + + static HICON createIconIndirect(HBITMAP bm) { + ICONINFO info = new ICONINFO(); + info.IsIcon = true; + info.MaskBitmap = bm; + info.ColorBitmap = bm; + + HICON hicon = User32.IMPL.CreateIconIndirect(info); + if (hicon == null) { + throw new GetLastErrorException(); + } + + return hicon; + } + + private HBITMAPWrap bm; + + public HICONWrap() { + } + + public HICONWrap(Pointer p) { + super(p); + } + + public HICONWrap(HBITMAPWrap bm) { + this.bm = bm; + setPointer(createIconIndirect(bm).getPointer()); + } + + public void close() { + bm.close(); + + if (Pointer.nativeValue(getPointer()) != 0) { + GDI32.DeleteObject(this); + setPointer(new Pointer(0)); + } + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/Kernel32.java b/src/dorkbox/systemTray/jna/windows/Kernel32.java new file mode 100644 index 0000000..d2f5a89 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/Kernel32.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import com.sun.jna.Memory; +import com.sun.jna.ptr.PointerByReference; + +public +class Kernel32 { + public static + String getLastErrorMessage() { + // has to be Kernel32.INSTANCE, otherwise it will crash the JVM + int hresult = com.sun.jna.platform.win32.Kernel32.INSTANCE.GetLastError(); + if (hresult == 0) { + return "HRESULT: 0x0 [No Error]"; + } else { + Memory memory = new Memory(1024); + PointerByReference reference = new PointerByReference(memory); + + // Must be Kernel32.INSTANCE because of how it pulls in variety arguments. + com.sun.jna.platform.win32.Kernel32.INSTANCE.FormatMessage(com.sun.jna.platform.win32.Kernel32.FORMAT_MESSAGE_FROM_SYSTEM, null, hresult, 0, reference, (int) memory.size(), null); + + String memoryMessage = reference.getPointer() + .getString(0, true); + memoryMessage = memoryMessage.trim(); + + return String.format("HRESULT: 0x%08x [%s]", hresult, memoryMessage); + } + } +} diff --git a/src/dorkbox/systemTray/jna/windows/Listener.java b/src/dorkbox/systemTray/jna/windows/Listener.java new file mode 100644 index 0000000..2771514 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/Listener.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HWND; +import static com.sun.jna.platform.win32.WinDef.LPARAM; +import static com.sun.jna.platform.win32.WinDef.WPARAM; + +public +class Listener { + public + void run(HWND hWnd, WPARAM wParam, LPARAM lParam) { + + } +} diff --git a/src/dorkbox/systemTray/jna/windows/MSG.java b/src/dorkbox/systemTray/jna/windows/MSG.java new file mode 100644 index 0000000..34d2f67 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/MSG.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +public +class MSG extends Structure { + public Pointer hWnd; + public int message; + public Parameter wParam; + public Parameter lParam; + public int time; + public int x; + public int y; + + @Override + protected + List getFieldOrder() { + return Arrays.asList("hWnd", "message", "wParam", "lParam", "time", "x", "y"); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/MsImg32.java b/src/dorkbox/systemTray/jna/windows/MsImg32.java new file mode 100644 index 0000000..f60c4d5 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/MsImg32.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.win32.W32APIOptions; + +import dorkbox.systemTray.jna.windows.structs.BLENDFUNCTION; + +public +class MsImg32 { + static { + Native.register(NativeLibrary.getInstance("Msimg32", W32APIOptions.DEFAULT_OPTIONS)); + } + + public static final int ETO_OPAQUE = 2; + public static final int SRCCOPY = 0xCC0020; + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd183351(v=vs.85).aspx + */ + public static native + boolean AlphaBlend(WinDef.HDC hdcDest, int xoriginDest, int yoriginDest, int wDest, int hDest, WinDef.HDC hdcSrc, int xoriginSrc, + int yoriginSrc, int wSrc, int hSrc, BLENDFUNCTION.ByValue ftn); +} diff --git a/src/dorkbox/systemTray/jna/windows/Parameter.java b/src/dorkbox/systemTray/jna/windows/Parameter.java new file mode 100644 index 0000000..1588eb6 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/Parameter.java @@ -0,0 +1,17 @@ +package dorkbox.systemTray.jna.windows; + +import com.sun.jna.IntegerType; +import com.sun.jna.Pointer; + +public +class Parameter extends IntegerType { + public + Parameter() { + this(0); + } + + public + Parameter(long value) { + super(Pointer.SIZE, value); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/Shell32.java b/src/dorkbox/systemTray/jna/windows/Shell32.java new file mode 100644 index 0000000..288eba0 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/Shell32.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; +import com.sun.jna.win32.W32APIOptions; + +import dorkbox.systemTray.jna.windows.structs.NOTIFYICONDATA; + +public +class Shell32 { + static { + Native.register(NativeLibrary.getInstance("shell32", W32APIOptions.DEFAULT_OPTIONS)); + } + + static public final int NIM_ADD = 0x0; + static public final int NIM_MODIFY = 0x1; + static public final int NIM_DELETE = 0x2; + + public static native + boolean Shell_NotifyIcon(int dwMessage, NOTIFYICONDATA lpdata); +} diff --git a/src/dorkbox/systemTray/jna/windows/User32.java b/src/dorkbox/systemTray/jna/windows/User32.java new file mode 100644 index 0000000..80c81ed --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/User32.java @@ -0,0 +1,239 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HDC; +import static com.sun.jna.platform.win32.WinDef.HICON; +import static com.sun.jna.platform.win32.WinDef.HINSTANCE; +import static com.sun.jna.platform.win32.WinDef.HMENU; +import static com.sun.jna.platform.win32.WinDef.HWND; +import static com.sun.jna.platform.win32.WinDef.LPARAM; +import static com.sun.jna.platform.win32.WinDef.LRESULT; +import static com.sun.jna.platform.win32.WinDef.POINT; +import static com.sun.jna.platform.win32.WinDef.RECT; +import static com.sun.jna.platform.win32.WinDef.WPARAM; + +import com.sun.jna.Callback; +import com.sun.jna.Pointer; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.WinNT; + +import dorkbox.systemTray.jna.windows.structs.ICONINFO; +import dorkbox.systemTray.jna.windows.structs.MENUITEMINFO; +import dorkbox.systemTray.jna.windows.structs.NONCLIENTMETRICS; +import dorkbox.util.OS; + +@SuppressWarnings("WeakerAccess") +public +interface User32 { + User32 IMPL = OS.is64bit() ? new User32_64() : new User32_32(); + + int WM_QUIT = 18; + + int SPI_GETNONCLIENTMETRICS = 0x0029; + int COLOR_MENU = 4; + int COLOR_MENUTEXT = 7; + int COLOR_HIGHLIGHTTEXT = 14; + int COLOR_HIGHLIGHT = 13; + int COLOR_GRAYTEXT = 17; + + int GWL_WNDPROC = -4; + + int WM_LBUTTONUP = 0x202; + int WM_RBUTTONUP = 0x205; + + int MF_BYPOSITION = 0x400; + + /** + * This is overridden by the 64-bit version to be SetWindowLongPtr instead. + */ + int SetWindowLong(HWND hWnd, int nIndex, Callback procedure); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms647626(v=vs.85).aspx + */ + HMENU CreatePopupMenu(); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms647616(v=vs.85).aspx + */ + boolean AppendMenu(HMENU hMenu, int uFlags, int uIDNewItem, String lpNewItem); + + /** + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms647629(v=vs.85).aspx + */ + boolean DeleteMenu(HMENU hMenu, int uPosition, int uFlags); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms647631(v=vs.85).aspx + */ + boolean DestroyMenu(HMENU hMenu); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms648002(v=vs.85).aspx + */ + boolean TrackPopupMenu(HMENU hMenu, int uFlags, int x, int y, int nReserved, HWND hWnd, RECT prcRect); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms648001(v=vs.85).aspx + */ + boolean SetMenuItemInfo(HMENU hMenu, int uItem, boolean fByPosition, MENUITEMINFO lpmii); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms647980(v=vs.85).aspx + */ + boolean GetMenuItemInfo(HMENU hMenu, int uItem, boolean fByPosition, MENUITEMINFO lpmii); + + /** + * Brings the thread that created the specified window into the foreground + * and activates the window. Keyboard input is directed to the window, and + * various visual cues are changed for the user. The system assigns a + * slightly higher priority to the thread that created the foreground window + * than it does to other threads. + * + * @param hWnd A handle to the window that should be activated and brought to + * the foreground. + * + * @return If the window was brought to the foreground, the return value is + * nonzero. + */ + boolean SetForegroundWindow(HWND hWnd); + + /** + * The GetSystemMetrics function retrieves various system metrics (widths + * and heights of display elements) and system configuration settings. All + * dimensions retrieved by GetSystemMetrics are in pixels. + * + * @param nIndex System metric or configuration setting to retrieve. This + * parameter can be one of the following values. Note that all + * SM_CX* values are widths and all SM_CY* values are heights. + * Also note that all settings designed to return Boolean data + * represent TRUE as any nonzero value, and FALSE as a zero + * value. + * + * @return If the function succeeds, the return value is the requested + * system metric or configuration setting. If the function fails, + * the return value is zero. GetLastError does not provide extended + * error information. + */ + int GetSystemMetrics(int nIndex); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms633500(v=vs.85).aspx + */ + HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter, String lpszClass, String lpszWindow); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx + */ + LRESULT SendMessage(HWND hWnd, int Msg, WPARAM wParam, LPARAM lParam); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms648062(v=vs.85).aspx + */ + HICON CreateIconIndirect(ICONINFO piconinfo); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms632682(v=vs.85).aspx + */ + boolean DestroyWindow(HWND hWnd); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx + */ + boolean SystemParametersInfo(int uiAction, int uiParam, NONCLIENTMETRICS pvParam, int fWinIni); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms724371(v=vs.85).aspx + */ + COLORREF GetSysColor(int nIndex); + + /** + * This function places a message in the message queue associated with the + * thread that created the specified window and then returns without waiting + * for the thread to process the message. Messages in a message queue are + * retrieved by calls to the GetMessage or PeekMessage function. + * + * @param hWnd Handle to the window whose window procedure is to receive the + * message. + * @param msg Specifies the message to be posted. + * @param wParam Specifies additional message-specific information. + * @param lParam Specifies additional message-specific information. + */ + void PostMessage(HWND hWnd, int msg, WPARAM wParam, LPARAM lParam); + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx + */ + HWND CreateWindowEx(int dwExStyle, + String lpClassName, + String lpWindowName, + int dwStyle, + int x, + int y, + int nWidth, + int nHeight, + HWND hWndParent, + HMENU hMenu, + HINSTANCE hInstance, + WinNT.HANDLE lpParam); + + + /** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms644947(v=vs.85).aspx + */ + LRESULT DefWindowProc(HWND hWnd, int Msg, WPARAM wParam, LPARAM lParam); + + boolean GetMessage(MSG lpMsg, Pointer hWnd, int wMsgFilterMin, int wMsgFilterMax); + + boolean TranslateMessage(MSG lpMsg); + + boolean DispatchMessage(MSG lpMsg); + + int RegisterWindowMessage(WString lpString); + + /** + * This function retrieves a handle to a display device context (DC) for the + * client area of the specified window. The display device context can be + * used in subsequent graphics display interface (GDI) functions to draw in + * the client area of the window. + * + * @param hWnd Handle to the window whose device context is to be retrieved. + * If this value is NULL, GetDC retrieves the device context for + * the entire screen. + * + * @return The handle the device context for the specified window's client + * area indicates success. NULL indicates failure. To get extended + * error information, call GetLastError. + */ + HDC GetDC(HWND hWnd); + + /** + * This function releases a device context (DC), freeing it for use by other + * applications. The effect of ReleaseDC depends on the type of device + * context. + * + * @param hWnd Handle to the window whose device context is to be released. + * @param hDC Handle to the device context to be released. + * + * @return The return value specifies whether the device context is + * released. 1 indicates that the device context is released. Zero + * indicates that the device context is not released. + */ + int ReleaseDC(HWND hWnd, HDC hDC); + + boolean GetCursorPos(POINT point); +} diff --git a/src/dorkbox/systemTray/jna/windows/User32_32.java b/src/dorkbox/systemTray/jna/windows/User32_32.java new file mode 100644 index 0000000..1b28201 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/User32_32.java @@ -0,0 +1,175 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HDC; +import static com.sun.jna.platform.win32.WinDef.HICON; +import static com.sun.jna.platform.win32.WinDef.HINSTANCE; +import static com.sun.jna.platform.win32.WinDef.HMENU; +import static com.sun.jna.platform.win32.WinDef.HWND; +import static com.sun.jna.platform.win32.WinDef.LPARAM; +import static com.sun.jna.platform.win32.WinDef.LRESULT; +import static com.sun.jna.platform.win32.WinDef.POINT; +import static com.sun.jna.platform.win32.WinDef.RECT; +import static com.sun.jna.platform.win32.WinDef.WPARAM; + +import com.sun.jna.Callback; +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; +import com.sun.jna.Pointer; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.win32.W32APIOptions; + +import dorkbox.systemTray.jna.windows.structs.ICONINFO; +import dorkbox.systemTray.jna.windows.structs.MENUITEMINFO; +import dorkbox.systemTray.jna.windows.structs.NONCLIENTMETRICS; + +/** + * On first glance, this appears to be unnecessary to have a DirectMapping class implement an interface - however this is so different + * methods can be overridden by the correct 64bit versions, otherwise multiple copies of this library would have to be loaded (one for + * "normal", and another for the "special case"). + * + * Doing it this way greatly simplifies the API while maintaining Direct Mapping, at the cost of a slightly more complex code hierarchy. + */ +public +class User32_32 implements User32 { + static { + Native.register(NativeLibrary.getInstance("user32", W32APIOptions.DEFAULT_OPTIONS)); + } + + // is replaced by the 64bit version + @Override + public native + int SetWindowLong(HWND hWnd, int nIndex, Callback procedure); + + @Override + public native + HMENU CreatePopupMenu(); + + @Override + public native + boolean AppendMenu(final HMENU hMenu, final int uFlags, final int uIDNewItem, final String lpNewItem); + + @Override + public native + boolean DeleteMenu(final HMENU hMenu, final int uPosition, final int uFlags); + + @Override + public native + boolean DestroyMenu(final HMENU hMenu); + + @Override + public native + boolean TrackPopupMenu(final HMENU hMenu, + final int uFlags, + final int x, + final int y, + final int nReserved, + final HWND hWnd, + final RECT prcRect); + + @Override + public native + boolean SetMenuItemInfo(final HMENU hMenu, final int uItem, final boolean fByPosition, final MENUITEMINFO lpmii); + + @Override + public native + boolean GetMenuItemInfo(final HMENU hMenu, final int uItem, final boolean fByPosition, final MENUITEMINFO lpmii); + + @Override + public native + boolean SetForegroundWindow(final HWND hWnd); + + @Override + public native + int GetSystemMetrics(final int nIndex); + + @Override + public native + HWND FindWindowEx(final HWND hwndParent, final HWND hwndChildAfter, final String lpszClass, final String lpszWindow); + + @Override + public native + LRESULT SendMessage(final HWND hWnd, final int Msg, final WPARAM wParam, final LPARAM lParam); + + @Override + public native + HICON CreateIconIndirect(final ICONINFO piconinfo); + + @Override + public native + boolean DestroyWindow(final HWND hWnd); + + @Override + public native + boolean SystemParametersInfo(final int uiAction, final int uiParam, final NONCLIENTMETRICS pvParam, final int fWinIni); + + @Override + public native + COLORREF GetSysColor(final int nIndex); + + @Override + public native + void PostMessage(final HWND hWnd, final int msg, final WPARAM wParam, final LPARAM lParam); + + @Override + public native + HWND CreateWindowEx(final int dwExStyle, + final String lpClassName, + final String lpWindowName, + final int dwStyle, + final int x, + final int y, + final int nWidth, + final int nHeight, + final HWND hWndParent, + final HMENU hMenu, + final HINSTANCE hInstance, + final WinNT.HANDLE lpParam); + + @Override + public native + LRESULT DefWindowProc(final HWND hWnd, final int Msg, final WPARAM wParam, final LPARAM lParam); + + @Override + public native + boolean GetMessage(final MSG lpMsg, final Pointer hWnd, final int wMsgFilterMin, final int wMsgFilterMax); + + @Override + public native + boolean TranslateMessage(final MSG lpMsg); + + @Override + public native + boolean DispatchMessage(final MSG lpMsg); + + @Override + public native + int RegisterWindowMessage(final WString lpString); + + @Override + public native + HDC GetDC(final HWND hWnd); + + @Override + public native + int ReleaseDC(final HWND hWnd, final HDC hDC); + + @Override + public native + boolean GetCursorPos(final POINT point); +} diff --git a/src/dorkbox/systemTray/jna/windows/User32_64.java b/src/dorkbox/systemTray/jna/windows/User32_64.java new file mode 100644 index 0000000..d97072e --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/User32_64.java @@ -0,0 +1,173 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HMENU; +import static com.sun.jna.platform.win32.WinDef.HWND; + +import com.sun.jna.Callback; +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; +import com.sun.jna.Pointer; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.win32.W32APIOptions; + +import dorkbox.systemTray.jna.windows.structs.ICONINFO; +import dorkbox.systemTray.jna.windows.structs.MENUITEMINFO; +import dorkbox.systemTray.jna.windows.structs.NONCLIENTMETRICS; + +/** + * On first glance, this appears to be unnecessary to have a DirectMapping class implement an interface - however this is so different + * methods can be overridden by the correct 64bit versions, otherwise multiple copies of this library would have to be loaded (one for + * "normal", and another for the "special case"). + * + * Doing it this way greatly simplifies the API while maintaining Direct Mapping, at the cost of a slightly more complex code hierarchy. + */ +public +class User32_64 implements User32 { + static { + Native.register(NativeLibrary.getInstance("user32", W32APIOptions.DEFAULT_OPTIONS)); + } + + @Override + public + int SetWindowLong(HWND hWnd, int nIndex, Callback procedure) { + return SetWindowLongPtr(hWnd, nIndex, procedure); + } + + // should be used instead of SetWindowLong for 64 versions + public native + int SetWindowLongPtr(HWND hWnd, int nIndex, Callback procedure); + + @Override + public native + HMENU CreatePopupMenu(); + + @Override + public native + boolean AppendMenu(final HMENU hMenu, final int uFlags, final int uIDNewItem, final String lpNewItem); + + @Override + public native + boolean DeleteMenu(final HMENU hMenu, final int uPosition, final int uFlags); + + @Override + public native + boolean DestroyMenu(final HMENU hMenu); + + @Override + public native + boolean TrackPopupMenu(final HMENU hMenu, + final int uFlags, + final int x, + final int y, + final int nReserved, + final HWND hWnd, + final WinDef.RECT prcRect); + + @Override + public native + boolean SetMenuItemInfo(final HMENU hMenu, final int uItem, final boolean fByPosition, final MENUITEMINFO lpmii); + + @Override + public native + boolean GetMenuItemInfo(final HMENU hMenu, final int uItem, final boolean fByPosition, final MENUITEMINFO lpmii); + + @Override + public native + boolean SetForegroundWindow(final HWND hWnd); + + @Override + public native + int GetSystemMetrics(final int nIndex); + + @Override + public native + HWND FindWindowEx(final HWND hwndParent, final HWND hwndChildAfter, final String lpszClass, final String lpszWindow); + + @Override + public native + WinDef.LRESULT SendMessage(final HWND hWnd, final int Msg, final WinDef.WPARAM wParam, final WinDef.LPARAM lParam); + + @Override + public native + WinDef.HICON CreateIconIndirect(final ICONINFO piconinfo); + + @Override + public native + boolean DestroyWindow(final HWND hWnd); + + @Override + public native + boolean SystemParametersInfo(final int uiAction, final int uiParam, final NONCLIENTMETRICS pvParam, final int fWinIni); + + @Override + public native + COLORREF GetSysColor(final int nIndex); + + @Override + public native + void PostMessage(final HWND hWnd, final int msg, final WinDef.WPARAM wParam, final WinDef.LPARAM lParam); + + @Override + public native + HWND CreateWindowEx(final int dwExStyle, + final String lpClassName, + final String lpWindowName, + final int dwStyle, + final int x, + final int y, + final int nWidth, + final int nHeight, + final HWND hWndParent, + final HMENU hMenu, + final WinDef.HINSTANCE hInstance, + final WinNT.HANDLE lpParam); + + @Override + public native + WinDef.LRESULT DefWindowProc(final HWND hWnd, final int Msg, final WinDef.WPARAM wParam, final WinDef.LPARAM lParam); + + @Override + public native + boolean GetMessage(final MSG lpMsg, final Pointer hWnd, final int wMsgFilterMin, final int wMsgFilterMax); + + @Override + public native + boolean TranslateMessage(final MSG lpMsg); + + @Override + public native + boolean DispatchMessage(final MSG lpMsg); + + @Override + public native + int RegisterWindowMessage(final WString lpString); + + @Override + public native + WinDef.HDC GetDC(final HWND hWnd); + + @Override + public native + int ReleaseDC(final HWND hWnd, final WinDef.HDC hDC); + + @Override + public native + boolean GetCursorPos(final WinDef.POINT point); +} diff --git a/src/dorkbox/systemTray/jna/windows/WNDPROC.java b/src/dorkbox/systemTray/jna/windows/WNDPROC.java new file mode 100644 index 0000000..b36cbd1 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/WNDPROC.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HWND; +import static com.sun.jna.platform.win32.WinDef.LPARAM; +import static com.sun.jna.platform.win32.WinDef.LRESULT; +import static com.sun.jna.platform.win32.WinDef.WPARAM; + +import com.sun.jna.win32.StdCallLibrary; + +public +interface WNDPROC extends StdCallLibrary.StdCallCallback { + LRESULT callback(HWND hWnd, int uMsg, WPARAM uParam, LPARAM lParam); +} diff --git a/src/dorkbox/systemTray/jna/windows/WindowsEventDispatch.java b/src/dorkbox/systemTray/jna/windows/WindowsEventDispatch.java new file mode 100644 index 0000000..83d8361 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/WindowsEventDispatch.java @@ -0,0 +1,207 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows; + +import static com.sun.jna.platform.win32.WinDef.HWND; +import static com.sun.jna.platform.win32.WinDef.LPARAM; +import static com.sun.jna.platform.win32.WinDef.LRESULT; +import static com.sun.jna.platform.win32.WinDef.WPARAM; +import static com.sun.jna.platform.win32.WinUser.WM_USER; +import static dorkbox.systemTray.jna.windows.User32.GWL_WNDPROC; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.jna.WString; + +import dorkbox.systemTray.SystemTray; + +@SuppressWarnings({"Convert2Lambda", "UnusedAssignment", "Convert2Diamond", "FieldCanBeLocal"}) +public +class WindowsEventDispatch implements Runnable { + private static final String NAME = "WindowsEventDispatch"; + + + public static final int WM_TASKBARCREATED = User32.IMPL.RegisterWindowMessage(new WString("TaskbarCreated")); + public static final int WM_COMMAND = 0x0111; + public static final int WM_SHELLNOTIFY = WM_USER + 1; + public static final int WM_MEASUREITEM = 44; + public static final int WM_DRAWITEM = 43; + + public static final int MF_POPUP = 0x00000010; + + + private static final WindowsEventDispatch edt = new WindowsEventDispatch(); + private final static Map> messageIDs = new HashMap>(); + + private static final Object lock = new Object(); + private static int referenceCount = 0; + + + private Thread dispatchThread; + + // keep these around to prevent GC + private WNDPROC WndProc; + + // used to dispatch messages + private volatile HWND hWnd; + + + private + WindowsEventDispatch() { + } + + public static + void start() { + synchronized (lock) { + int ref = referenceCount++; + + if (ref == 0) { + edt.start_(); + } + + try { + // wait for the dispatch thread to start + lock.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + public static + void stop() { + synchronized (lock) { + if (--referenceCount == 0) { + edt.stop_(); + } + } + } + + public static + HWND get() { + return edt.hWnd; + } + + // always from inside lock! + private + void start_() { + dispatchThread = new Thread(this, NAME); + dispatchThread.start(); + } + + // always from inside lock! + private void + stop_() { + WPARAM wparam = new WPARAM(0); + LPARAM lparam = new LPARAM(0); + User32.IMPL.SendMessage(hWnd, User32.WM_QUIT, wparam, lparam); + + try { + // wait for the dispatch thread to quit (but only if we are not on the dispatch thread) + if (!Thread.currentThread().equals(dispatchThread)) { + dispatchThread.join(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @SuppressWarnings("Java8MapApi") + public static + void addListener(final int messageId, final Listener listener) { + synchronized (messageIDs) { + List listeners = messageIDs.get(messageId); + if (listeners == null) { + listeners = new ArrayList(); + messageIDs.put(messageId, listeners); + } + + listeners.add(listener); + } + } + public static + void removeListener(final Listener listener) { + synchronized (messageIDs) { + for (Map.Entry> entry : messageIDs.entrySet()) { + List value = entry.getValue(); + if (value.remove(listener)) { + return; + } + } + } + } + + @Override + public + void run() { + WndProc = new WNDPROC() { + @Override + public + LRESULT callback(HWND hWnd, int msg, WPARAM wParam, LPARAM lParam) { + List listeners = null; + synchronized (messageIDs) { + listeners = messageIDs.get(msg); + if (listeners != null) { + // make a copy, in case a listener action modifies the message listener + listeners = new ArrayList(listeners); + } + } + + if (listeners != null) { + for (final Listener listener : listeners) { + if (listener != null) { + try { + listener.run(hWnd, wParam, lParam); + } catch (Exception e) { + SystemTray.logger.error("Error executing listener.", e); + } + } + } + } + + return User32.IMPL.DefWindowProc(hWnd, msg, wParam, lParam); + } + }; + + hWnd = User32.IMPL.CreateWindowEx(0, "STATIC", NAME, 0, 0, 0, 0, 0, null, null, null, + null); + if (hWnd == null) { + throw new GetLastErrorException(); + } + + User32.IMPL.SetWindowLong(hWnd, GWL_WNDPROC, WndProc); + + synchronized (lock) { + lock.notifyAll(); + } + + MSG msg = new MSG(); + while (User32.IMPL.GetMessage(msg, null, 0, 0)) { + User32.IMPL.TranslateMessage(msg); + User32.IMPL.DispatchMessage(msg); + } + + if (hWnd != null) { + if (!User32.IMPL.DestroyWindow(hWnd)) { + throw new GetLastErrorException(); + } + hWnd = null; + } + } +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/BLENDFUNCTION.java b/src/dorkbox/systemTray/jna/windows/structs/BLENDFUNCTION.java new file mode 100644 index 0000000..b15d9c7 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/BLENDFUNCTION.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinUser; + +public class BLENDFUNCTION extends Structure { + public static class ByValue extends BLENDFUNCTION implements Structure.ByValue { + } + + public static class ByReference extends BLENDFUNCTION implements Structure.ByReference { + } + + public byte BlendOp = WinUser.AC_SRC_OVER; // only valid value + public byte BlendFlags = 0; // only valid value + public byte SourceConstantAlpha; + public byte AlphaFormat; + + public + BLENDFUNCTION() { + } + + @Override + protected + List getFieldOrder() { + return Arrays.asList("BlendOp", "BlendFlags", "SourceConstantAlpha", "AlphaFormat"); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/DRAWITEMSTRUCT.java b/src/dorkbox/systemTray/jna/windows/structs/DRAWITEMSTRUCT.java new file mode 100644 index 0000000..d1a95d4 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/DRAWITEMSTRUCT.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import static com.sun.jna.platform.win32.WinDef.HDC; +import static com.sun.jna.platform.win32.WinDef.HWND; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.BaseTSD; +import com.sun.jna.platform.win32.WinDef; + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/bb775802(v=vs.85).aspx + */ +public class DRAWITEMSTRUCT extends Structure { + + public static class ByValue extends DRAWITEMSTRUCT implements Structure.ByValue { + } + + public static class ByReference extends DRAWITEMSTRUCT implements Structure.ByReference { + } + + public static final int ODT_BUTTON = 4; + public static final int ODT_COMBOBOX = 3; + public static final int ODT_LISTBOX = 2; + public static final int ODT_LISTVIEW = 102; + public static final int ODT_MENU = 1; + public static final int ODT_STATIC = 5; + public static final int ODT_TAB = 101; + + public static final int ODS_SELECTED = 1; + + public int CtlType; + public int CtlID; + public int itemID; + public int itemAction; + public int itemState; + public HWND hwndItem; + public HDC hDC; + public WinDef.RECT rcItem; + public BaseTSD.ULONG_PTR itemData; + + public DRAWITEMSTRUCT() { + } + + public DRAWITEMSTRUCT(Pointer p) { + super(p); + + read(); + } + + @Override + protected + List getFieldOrder() { + return Arrays.asList("CtlType", "CtlID", "itemID", "itemAction", "itemState", "hwndItem", "hDC", "rcItem", "itemData"); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/ICONINFO.java b/src/dorkbox/systemTray/jna/windows/structs/ICONINFO.java new file mode 100644 index 0000000..e461dbc --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/ICONINFO.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import static com.sun.jna.platform.win32.WinDef.DWORD; +import static com.sun.jna.platform.win32.WinDef.HBITMAP; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Structure; + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms648052(v=vs.85).aspx + */ +public +class ICONINFO extends Structure { + public boolean IsIcon; + public DWORD xHotspot; + public DWORD yHotspot; + public HBITMAP MaskBitmap; + public HBITMAP ColorBitmap; + + + public + ICONINFO() { + } + + @Override + protected + List getFieldOrder() { + return Arrays.asList("IsIcon", "xHotspot", "yHotspot", "MaskBitmap", "ColorBitmap"); + } + + public static + class ByValue extends ICONINFO implements Structure.ByValue {} +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/LOGFONT.java b/src/dorkbox/systemTray/jna/windows/structs/LOGFONT.java new file mode 100644 index 0000000..87f55c9 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/LOGFONT.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinDef; + +/** + * https://msdn.microsoft.com/en-us/library/windows/desktop/dd145037(v=vs.85).aspx + */ +public class LOGFONT extends Structure { + + public static final int LF_FACESIZE = 32; + + public static class ByValue extends LOGFONT implements Structure.ByValue { + } + + public WinDef.LONG lfHeight; + public WinDef.LONG lfWidth; + public WinDef.LONG lfEscapement; + public WinDef.LONG lfOrientation; + public WinDef.LONG lfWeight; + public byte lfItalic; + public byte lfUnderline; + public byte lfStrikeOut; + public byte lfCharSet; + public byte lfOutPrecision; + public byte lfClipPrecision; + public byte lfQuality; + public byte lfPitchAndFamily; + public char[] lfFaceName = new char[LF_FACESIZE]; + + public LOGFONT() { + } + + public LOGFONT(Pointer p) { + super(p); + + read(); + } + + @Override + protected + List getFieldOrder() { + return Arrays.asList("lfHeight", + "lfWidth", + "lfEscapement", + "lfOrientation", + "lfWeight", + "lfItalic", + "lfUnderline", + "lfStrikeOut", + "lfCharSet", + "lfOutPrecision", + "lfClipPrecision", + "lfQuality", + "lfPitchAndFamily", + "lfFaceName"); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/MEASUREITEMSTRUCT.java b/src/dorkbox/systemTray/jna/windows/structs/MEASUREITEMSTRUCT.java new file mode 100644 index 0000000..fe4377b --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/MEASUREITEMSTRUCT.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.BaseTSD; + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/bb775804(v=vs.85).aspx + */ +public class MEASUREITEMSTRUCT extends Structure { + public static class ByValue extends MEASUREITEMSTRUCT implements Structure.ByValue { + } + + public static class ByReference extends MEASUREITEMSTRUCT implements Structure.ByReference { + } + + public static final int ODT_MENU = 1; + public static final int ODT_LISTBOX = 2; + public static final int ODT_COMBOBOX = 3; + public static final int ODT_BUTTON = 4; + public static final int ODT_STATIC = 5; + + public int CtlType; + public int CtlID; + public int itemID; + public int itemWidth; + public int itemHeight; + public BaseTSD.ULONG_PTR itemData; + + public MEASUREITEMSTRUCT() { + } + + public MEASUREITEMSTRUCT(Pointer p) { + super(p); + + read(); + } + + @Override + protected + List getFieldOrder() { + return Arrays.asList("CtlType", "CtlID", "itemID", "itemWidth", "itemHeight", "itemData"); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/MENUITEMINFO.java b/src/dorkbox/systemTray/jna/windows/structs/MENUITEMINFO.java new file mode 100644 index 0000000..052c936 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/MENUITEMINFO.java @@ -0,0 +1,89 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import static com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; +import static com.sun.jna.platform.win32.WinDef.HBITMAP; +import static com.sun.jna.platform.win32.WinDef.HMENU; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx + */ +public +class MENUITEMINFO extends Structure { + + public static final int MFS_ENABLED = 0x00000000; + public static final int MFS_DISABLED = 0x00000003; + + public static final int MFS_GRAYED = 0x00000003; + public static final int MFS_DEFAULT = 0x00001000; + + public static final int MFS_CHECKED = 0x00000008; + public static final int MFS_UNCHECKED= 0x00000000; + + public static final int MFS_HILITE = 0x00000080; + public static final int MFS_UNHILITE = 0x00000000; + + public static final int MIIM_DATA = 0x00000020; + + + public int cbSize; + public int fMask; + public int fType; + public int fState; + public int wID; + + public HMENU hSubMenu; + public HBITMAP hbmpChecked; + public HBITMAP hbmpUnchecked; + public ULONG_PTR dwItemData; + public String dwTypeData; + public int cch; + public HBITMAP hbmpItem; + + public + MENUITEMINFO() { + cbSize = size(); + } + + public + MENUITEMINFO(Pointer p) { + super(p); + } + + @Override + protected + List getFieldOrder() { + return Arrays.asList("cbSize", + "fMask", + "fType", + "fState", + "wID", + "hSubMenu", + "hbmpChecked", + "hbmpUnchecked", + "dwItemData", + "dwTypeData", + "cch", + "hbmpItem"); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/NONCLIENTMETRICS.java b/src/dorkbox/systemTray/jna/windows/structs/NONCLIENTMETRICS.java new file mode 100644 index 0000000..94aa4d6 --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/NONCLIENTMETRICS.java @@ -0,0 +1,88 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/ff729175(v=vs.85).aspx + */ +public class NONCLIENTMETRICS extends Structure { + public static class ByValue extends NONCLIENTMETRICS implements Structure.ByValue { + } + + public static class ByReference extends NONCLIENTMETRICS implements Structure.ByReference { + } + + public static final int ODT_MENU = 1; + public static final int ODT_LISTBOX = 2; + public static final int ODT_COMBOBOX = 3; + public static final int ODT_BUTTON = 4; + public static final int ODT_STATIC = 5; + + public int cbSize; + public int iBorderWidth; + public int iScrollWidth; + public int iScrollHeight; + public int iCaptionWidth; + public int iCaptionHeight; + public LOGFONT.ByValue lfCaptionFont; + public int iSmCaptionWidth; + public int iSmCaptionHeight; + public LOGFONT.ByValue lfSmCaptionFont; + public int iMenuWidth; + public int iMenuHeight; + public LOGFONT.ByValue lfMenuFont; + public LOGFONT.ByValue lfStatusFont; + public LOGFONT.ByValue lfMessageFont; + //public int iPaddedBorderWidth; + + public NONCLIENTMETRICS() { + cbSize = size(); + } + + public NONCLIENTMETRICS(Pointer p) { + super(p); + + read(); + } + + @Override + protected + List getFieldOrder() { + return Arrays.asList("cbSize", + "iBorderWidth", + "iScrollWidth", + "iScrollHeight", + "iCaptionWidth", + "iCaptionHeight", + "lfCaptionFont", + "iSmCaptionWidth", + "iSmCaptionHeight", + "lfSmCaptionFont", + "iMenuWidth", + "iMenuHeight", + "lfMenuFont", + "lfStatusFont", + "lfMessageFont" + //"iPaddedBorderWidth" + ); + } +} diff --git a/src/dorkbox/systemTray/jna/windows/structs/NOTIFYICONDATA.java b/src/dorkbox/systemTray/jna/windows/structs/NOTIFYICONDATA.java new file mode 100644 index 0000000..ddf651f --- /dev/null +++ b/src/dorkbox/systemTray/jna/windows/structs/NOTIFYICONDATA.java @@ -0,0 +1,116 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.jna.windows.structs; + +import static com.sun.jna.platform.win32.WinDef.HWND; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinDef; + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx + */ +public class NOTIFYICONDATA extends Structure { + static public final int NIF_MESSAGE = 0x1; + static public final int NIF_ICON = 0x2; + static public final int NIF_TIP = 0x4; + static public final int NIF_STATE = 0x8; + + static public final int NIF_INFO = 0x10; + + static public final int NIIF_NONE = 0x0; + static public final int NIIF_INFO = 0x1; + static public final int NIIF_WARNING = 0x2; + static public final int NIIF_ERROR = 0x3; + static public final int NIIF_USER = 0x4; + + public int cbSize; + public HWND hWnd; + public int uID; + public int uFlags; + public int uCallbackMessage; + public WinDef.HICON hIcon; + + public char[] szTip = new char[128]; + + public int dwState; + public int dwStateMask; + + public char[] szInfo = new char[256]; + public int uTimeoutOrVersion; // {UINT uTimeout; UINT uVersion;}; + + public char[] szInfoTitle = new char[64]; + public int dwInfoFlags; + + public + NOTIFYICONDATA() { + cbSize = size(); + } + + public + void setTooltip(String s) { + uFlags |= NIF_TIP; + + System.arraycopy(s.toCharArray(), 0, szTip, 0, Math.min(s.length(), szTip.length)); + szTip[s.length()] = '\0'; + } + + public + void setBalloon(String title, String message, int millis, int niif) { + uFlags |= NIF_INFO; + + System.arraycopy(message.toCharArray(), 0, szInfo, 0, Math.min(message.length(), szInfo.length)); + szInfo[message.length()] = '\0'; + + uTimeoutOrVersion = millis; + + System.arraycopy(title.toCharArray(), 0, szInfoTitle, 0, Math.min(title.length(), szInfoTitle.length)); + szInfoTitle[title.length()] = '\0'; + + dwInfoFlags = niif; + } + + public + void setIcon(WinDef.HICON hIcon) { + uFlags |= NIF_ICON; + this.hIcon = hIcon; + } + + public void setCallback(int callback) { + uFlags |= NIF_MESSAGE; + uCallbackMessage = callback; + } + + @Override + protected List getFieldOrder () { + return Arrays.asList("cbSize", + "hWnd", + "uID", + "uFlags", + "uCallbackMessage", + "hIcon", + "szTip", + "dwState", + "dwStateMask", + "szInfo", + "uTimeoutOrVersion", + "szInfoTitle", + "dwInfoFlags"); + } +} diff --git a/src/dorkbox/systemTray/nativeUI/WindowsBaseMenuItem.java b/src/dorkbox/systemTray/nativeUI/WindowsBaseMenuItem.java new file mode 100644 index 0000000..0e3daad --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/WindowsBaseMenuItem.java @@ -0,0 +1,177 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.nativeUI; + +import static com.sun.jna.platform.win32.WinDef.HMENU; +import static dorkbox.systemTray.jna.windows.User32.MF_BYPOSITION; +import static dorkbox.systemTray.nativeUI.WindowsMenu.MFT_OWNERDRAW; + +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.io.File; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import com.sun.jna.platform.win32.BaseTSD; + +import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.jna.windows.GDI32; +import dorkbox.systemTray.jna.windows.GetLastErrorException; +import dorkbox.systemTray.jna.windows.HBITMAPWrap; +import dorkbox.systemTray.jna.windows.User32; +import dorkbox.systemTray.jna.windows.structs.MENUITEMINFO; +import dorkbox.util.SwingUtil; + +public class WindowsBaseMenuItem { + + private final int position; + + volatile HBITMAPWrap hbitmapWrapImage; + volatile String text = ""; + volatile boolean enabled = true; // default is enabled + + // these have to be volatile, because they can be changed from any thread + private volatile ActionListener callback; + private volatile ActionEvent actionEvent; + + public + WindowsBaseMenuItem(int position) { + this.position = position; + } + + void setText(final String text) { + if (text != null) { + this.text = text; + } else { + this.text = ""; + } + } + + void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + void setImage(final File imageFile) { + if (imageFile != null) { + SwingUtil.invokeAndWaitQuietly(new Runnable() { + @Override + public + void run() { + // has to run on swing EDT. + ImageIcon imageIcon = new ImageIcon(imageFile.getAbsolutePath()); + // fully loads the image and returns when it's done loading the image + imageIcon = new ImageIcon(imageIcon.getImage()); + + hbitmapWrapImage = convertMenuImage(imageIcon); + } + }); + } + else { + hbitmapWrapImage = null; + } + } + + void setCallback(final ActionListener callback, final ActionEvent actionEvent) { + this.callback = callback; + this.actionEvent = actionEvent; + } + + void fireCallback() { + ActionEvent actionEvent = this.actionEvent; + ActionListener callback = this.callback; + + if (callback != null) { + try { + callback.actionPerformed(actionEvent); + } catch (Throwable throwable) { + SystemTray.logger.error("Error calling menu entry {} click event.", this.text, throwable); + } + } + } + + static + BufferedImage createBitmap(Icon icon) { + BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bi.createGraphics(); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + icon.paintIcon(null, g, 0, 0); + g.dispose(); + return bi; + } + + private static HBITMAPWrap convertMenuImage(Icon icon) { +// BufferedImage img = createBitmap(icon); + + int menubarHeight = WindowsMenu.getSystemMenuImageSize(); + + BufferedImage scaledImage = new BufferedImage(menubarHeight, menubarHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = scaledImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + + icon.paintIcon(null, g, 0, 0); +// g.drawImage(img, 0, 0, menubarHeight, menubarHeight, null); + g.dispose(); + + return new HBITMAPWrap(scaledImage); + } + + void remove() { + if (hbitmapWrapImage != null) { + GDI32.DeleteObject(hbitmapWrapImage); + hbitmapWrapImage = null; + } + } + + boolean hasImage() { + return false; + } + + void onCreateMenu(final HMENU parentNative, final boolean hasImages) { +// setSpacerImage(hasImagesInMenu); + + if (!User32.IMPL.AppendMenu(parentNative, MFT_OWNERDRAW, position, null)) { + throw new GetLastErrorException(); + } + + MENUITEMINFO mmi = new MENUITEMINFO(); + if (!User32.IMPL.GetMenuItemInfo(parentNative, position, false, mmi)) { + throw new GetLastErrorException(); + } + + mmi.dwItemData = new BaseTSD.ULONG_PTR(position); + mmi.fMask |= MENUITEMINFO.MIIM_DATA; + + if (!User32.IMPL.SetMenuItemInfo(parentNative, position, false, mmi)) { + throw new GetLastErrorException(); + } + } + + void onDeleteMenu(final HMENU parentNative) { + remove(); + + if (!User32.IMPL.DeleteMenu(parentNative, position, MF_BYPOSITION)) { + throw new GetLastErrorException(); + } + } +} diff --git a/src/dorkbox/systemTray/nativeUI/WindowsMenu.java b/src/dorkbox/systemTray/nativeUI/WindowsMenu.java new file mode 100644 index 0000000..1461cfb --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/WindowsMenu.java @@ -0,0 +1,744 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.nativeUI; + + +import static com.sun.jna.platform.win32.WinDef.HBITMAP; +import static com.sun.jna.platform.win32.WinDef.HDC; +import static com.sun.jna.platform.win32.WinDef.HFONT; +import static com.sun.jna.platform.win32.WinDef.HMENU; +import static com.sun.jna.platform.win32.WinDef.HWND; +import static com.sun.jna.platform.win32.WinDef.LPARAM; +import static com.sun.jna.platform.win32.WinDef.POINT; +import static com.sun.jna.platform.win32.WinDef.RECT; +import static com.sun.jna.platform.win32.WinDef.WPARAM; +import static com.sun.jna.platform.win32.WinNT.HANDLE; +import static com.sun.jna.platform.win32.WinUser.AC_SRC_ALPHA; +import static com.sun.jna.platform.win32.WinUser.AC_SRC_OVER; +import static com.sun.jna.platform.win32.WinUser.SIZE; +import static com.sun.jna.platform.win32.WinUser.SM_CYMENUCHECK; +import static dorkbox.systemTray.jna.windows.WindowsEventDispatch.MF_POPUP; +import static dorkbox.systemTray.jna.windows.WindowsEventDispatch.WM_COMMAND; +import static dorkbox.systemTray.jna.windows.WindowsEventDispatch.WM_DRAWITEM; +import static dorkbox.systemTray.jna.windows.WindowsEventDispatch.WM_MEASUREITEM; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.sun.jna.Pointer; + +import dorkbox.systemTray.Checkbox; +import dorkbox.systemTray.Entry; +import dorkbox.systemTray.Menu; +import dorkbox.systemTray.MenuItem; +import dorkbox.systemTray.Separator; +import dorkbox.systemTray.Status; +import dorkbox.systemTray.jna.windows.GDI32; +import dorkbox.systemTray.jna.windows.GetLastErrorException; +import dorkbox.systemTray.jna.windows.Listener; +import dorkbox.systemTray.jna.windows.MsImg32; +import dorkbox.systemTray.jna.windows.User32; +import dorkbox.systemTray.jna.windows.WindowsEventDispatch; +import dorkbox.systemTray.jna.windows.structs.BLENDFUNCTION; +import dorkbox.systemTray.jna.windows.structs.DRAWITEMSTRUCT; +import dorkbox.systemTray.jna.windows.structs.MEASUREITEMSTRUCT; +import dorkbox.systemTray.jna.windows.structs.NONCLIENTMETRICS; +import dorkbox.systemTray.peer.MenuPeer; + +// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both +@SuppressWarnings("ForLoopReplaceableByForEach") +class WindowsMenu extends WindowsBaseMenuItem implements MenuPeer { + + volatile HMENU _nativeMenu; // must ONLY be created at the end of delete! + private final WindowsMenu parent; + + private final Listener menuItemListener; + private final Listener menuItemMeasureListener; + private final Listener menuItemDrawListener; + + public static final int WM_NULL = 0x0000; + + public static final int VK_ESCAPE = 0x1B; + public static final int WM_KEYDOWN = 0x0100; + + public static final int TPM_RECURSE = 0x0001; + public static final int TPM_RIGHTBUTTON = 0x0002; + + public static final int MFT_OWNERDRAW = 256; + + + private static final int SPACE_ICONS = 2; + + // have to make sure no other methods can call obliterate, delete, or create menu once it's already started + private AtomicBoolean obliterateInProgress = new AtomicBoolean(false); + + // this is a list (that mirrors the actual list) BECAUSE we have to create/delete the entire menu in Windows every time something is changed + private final List menuEntries = new ArrayList(); + + + // called by the system tray constructors + // This is NOT a copy constructor! + @SuppressWarnings("IncompleteCopyConstructor") + WindowsMenu() { + super(0); + this.parent = null; + + // Register drawing menu items, etc + menuItemListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + int position = wParam.intValue() & 0xff; + WindowsBaseMenuItem item = menuEntries.get(position); + item.fireCallback(); + } + }; + + menuItemMeasureListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + MEASUREITEMSTRUCT ms = new MEASUREITEMSTRUCT(new Pointer(lParam.longValue())); + + int position = ms.itemData.intValue(); + WindowsBaseMenuItem item = menuEntries.get(position); + + SIZE size = measureItem(hWnd, item); + ms.itemWidth = size.cx; + ms.itemHeight = size.cy; + ms.write(); + } + }; + + + menuItemDrawListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + DRAWITEMSTRUCT di = new DRAWITEMSTRUCT(new Pointer(lParam.longValue())); + + int position = di.itemData.intValue(); + WindowsBaseMenuItem item = menuEntries.get(position); + + drawItem(item, di.hDC, di.rcItem, di.itemState); + } + }; + + WindowsEventDispatch.addListener(WM_COMMAND, menuItemListener); + WindowsEventDispatch.addListener(WM_MEASUREITEM, menuItemMeasureListener); + WindowsEventDispatch.addListener(WM_DRAWITEM, menuItemDrawListener); + } + + // This is NOT a copy constructor! + @SuppressWarnings("IncompleteCopyConstructor") + private + WindowsMenu(final WindowsMenu parent, final int index) { + super(index); // is what is added to the parent menu (so images work) + this.parent = parent; + + // Register drawing menu items, etc + menuItemListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + int position = wParam.intValue() & 0xff; + WindowsBaseMenuItem item = menuEntries.get(position); + item.fireCallback(); + } + }; + + menuItemMeasureListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + MEASUREITEMSTRUCT ms = new MEASUREITEMSTRUCT(new Pointer(lParam.longValue())); + + int position = ms.itemData.intValue(); + WindowsBaseMenuItem item = menuEntries.get(position); + + SIZE size = measureItem(hWnd, item); + ms.itemWidth = size.cx; + ms.itemHeight = size.cy; + ms.write(); + } + }; + + menuItemDrawListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + DRAWITEMSTRUCT di = new DRAWITEMSTRUCT(new Pointer(lParam.longValue())); + + int position = di.itemData.intValue(); + WindowsBaseMenuItem item = menuEntries.get(position); + + drawItem(item, di.hDC, di.rcItem, di.itemState); + } + }; + + WindowsEventDispatch.addListener(WM_COMMAND, menuItemListener); + WindowsEventDispatch.addListener(WM_MEASUREITEM, menuItemMeasureListener); + WindowsEventDispatch.addListener(WM_DRAWITEM, menuItemDrawListener); + } + + + WindowsMenu getParent() { + return parent; + } + + /** + * Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object. + */ + @SuppressWarnings("ForLoopReplaceableByForEach") + private + void deleteMenu() { + if (obliterateInProgress.get()) { + return; + } + + if (_nativeMenu != null) { + // have to work in reverse so the index is preserved + for (int i = menuEntries.size()-1; i >= 0; i--) { + final WindowsBaseMenuItem menuEntry__ = menuEntries.get(i); + menuEntry__.onDeleteMenu(_nativeMenu); + } + + if (!User32.IMPL.DestroyMenu(_nativeMenu)) { + throw new GetLastErrorException(); + } + } + + if (parent != null) { + parent.deleteMenu(); + } + + // makes a new one + this._nativeMenu = User32.IMPL.CreatePopupMenu(); + + // binds sub-menu to entry (if it exists! it does not for the root menu) + if (parent != null) { + // get around windows crap (transfer handle to decimal) + int handle = (int) Pointer.nativeValue(_nativeMenu.getPointer()); + + if (!User32.IMPL.AppendMenu(getParent()._nativeMenu, MF_POPUP | MFT_OWNERDRAW, handle, null)) { + throw new GetLastErrorException(); + } + } + } + + /** + * some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. + * + * To work around this issue, we destroy then recreate the menu every time something is changed. + * + * ALWAYS CALLED ON THE EDT + */ + @SuppressWarnings("ForLoopReplaceableByForEach") + private + void createMenu() { + if (obliterateInProgress.get()) { + return; + } + + if (parent != null) { + parent.createMenu(); + } + + // now add back other menu entries + boolean hasImages = false; + + for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) { + final WindowsBaseMenuItem menuEntry__ = menuEntries.get(i); + hasImages |= menuEntry__.hasImage(); + } + + for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) { + // the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images + final WindowsBaseMenuItem menuEntry__ = menuEntries.get(i); + menuEntry__.onCreateMenu(_nativeMenu, hasImages); + + if (menuEntry__ instanceof WindowsMenu) { + WindowsMenu subMenu = (WindowsMenu) menuEntry__; + if (subMenu.getParent() != WindowsMenu.this) { + // we don't want to "createMenu" on our sub-menu that is assigned to us directly, as they are already doing it + subMenu.createMenu(); + } + } + } + +// Gtk.gtk_widget_show_all(_nativeMenu); // necessary to guarantee widget is visible (doesn't always show_all for all children) +// onMenuAdded(_nativeMenu); // not needed for windows + } + + /** + * Completely obliterates the menu, no possible way to reconstruct it. + * + * ALWAYS CALLED ON THE EDT + */ + @SuppressWarnings("ForLoopReplaceableByForEach") + private + void obliterateMenu() { + if (_nativeMenu != null && !obliterateInProgress.get()) { + obliterateInProgress.set(true); + + // have to remove all other menu entries + + // a copy is made because sub-menus remove themselves from parents when .remove() is called. If we don't + // do this, errors will be had because indices don't line up anymore. + ArrayList menuEntriesCopy = new ArrayList(menuEntries); + menuEntries.clear(); + + // have to work in reverse so the index is preserved + for (int i = menuEntriesCopy.size()-1; i >= 0; i--) { + final WindowsBaseMenuItem menuEntry__ = menuEntriesCopy.get(i); + menuEntry__.onDeleteMenu(_nativeMenu); + } + menuEntriesCopy.clear(); + + if (!User32.IMPL.DestroyMenu(_nativeMenu)) { + throw new GetLastErrorException(); + } + + _nativeMenu = null; + + obliterateInProgress.set(false); + } + } + + + @Override + public + void add(final Menu parentMenu, final Entry entry, int index) { + deleteMenu(); + + if (entry instanceof Menu) { +// WindowsMenu item = new WindowsMenu(WindowsMenu.this, index); +// menuEntries.add(index, item); +// ((Menu) entry).bind(item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof Separator) { +// WindowsMenuItemSeparator item = new WindowsMenuItemSeparator(WindowsMenu.this); +// menuEntries.add(index, item); +// entry.bind(item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof Checkbox) { +// WindowsMenuItemCheckbox item = new WindowsMenuItemCheckbox(WindowsMenu.this); +// menuEntries.add(index, item); +// ((Checkbox) entry).bind(item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof Status) { +// WindowsMenuItemStatus item = new WindowsMenuItemStatus(WindowsMenu.this); +// menuEntries.add(index, item); +// ((Status) entry).bind(item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof MenuItem) { + WindowsMenuItem item = new WindowsMenuItem(WindowsMenu.this, index); + menuEntries.add(index, item); + ((MenuItem) entry).bind(item, parentMenu, parentMenu.getSystemTray()); + } + + createMenu(); + } + + // is overridden in tray impl + @Override + public + void setImage(final MenuItem menuItem) { + super.setImage(menuItem.getImage()); + } + + // is overridden in tray impl + @Override + public + void setEnabled(final MenuItem menuItem) { + super.setEnabled(menuItem.getEnabled()); + } + + // is overridden in tray impl + @Override + public + void setText(final MenuItem menuItem) { + super.setText(menuItem.getText()); + } + + @Override + public + void setCallback(final MenuItem menuItem) { + // can't have a callback for menus! + } + + // is overridden in tray impl + @Override + public + void setShortcut(final MenuItem menuItem) { +// // yikes... +// final int vKey = SwingUtil.getVirtualKey(menuItem.getShortcut()); +// +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.setShortcut(new MenuShortcut(vKey)); +// } +// }); + } + + /** + * called when a child removes itself from the parent menu. Does not work for sub-menus + * + * ALWAYS CALLED ON THE EDT + */ + public + void remove(final WindowsBaseMenuItem item) { + menuEntries.remove(item); + + // have to rebuild the menu now... + deleteMenu(); // must be on EDT + createMenu(); // must be on EDT + } + + // a child will always remove itself from the parent. + @Override + public + void remove() { + WindowsMenu parent = getParent(); + + if (parent != null) { + // have to remove from the parent.menuEntries first + parent.menuEntries.remove(WindowsMenu.this); + } + + // delete all of the children of this submenu (must happen before the menuEntry is removed) + obliterateMenu(); + + if (parent != null) { + // have to rebuild the menu now... + parent.deleteMenu(); // must be on EDT + parent.createMenu(); // must be on EDT + } + } + + + void showContextMenu(final POINT position) { + HWND mainHwnd = WindowsEventDispatch.get(); + User32.IMPL.SetForegroundWindow(mainHwnd); + + // TrackPopupMenu blocks until popup menu is closed + if (!User32.IMPL.TrackPopupMenu(_nativeMenu, TPM_RIGHTBUTTON, position.x, position.y, 0, mainHwnd, null)) { + // Popup menu already active + if (com.sun.jna.platform.win32.Kernel32.INSTANCE.GetLastError() == 0x000005a6) { + HWND hWnd = null; + while (true) { + // "#32768" - Name of the popup menu class + hWnd = User32.IMPL.FindWindowEx(null, hWnd, "#32768", null); + + if (hWnd == null) { + break; + } + + // close the previous popup menu + User32.IMPL.SendMessage(hWnd, WM_KEYDOWN, new WPARAM(VK_ESCAPE), null); + } + + return; + } else { + throw new GetLastErrorException(); + } + } + + WPARAM wparam = new WPARAM(0); + LPARAM lparam = new LPARAM(0); + User32.IMPL.PostMessage(mainHwnd, WM_NULL, wparam, lparam); + } + + +// // menus have to be cleared before each render. +// void clearMenus() { +// if (_native != null) { +// if (!User32.DestroyMenu(_native)) { +// System.err.println("PROBLEM"); +//// throw new GetLastErrorException(); +// } +// _native = null; +// } +// for (WindowsBaseMenuItem m : menuEntries) { +// m.close(); +// } +// +// hMenusIDs.clear(); +// } + +// void updateMenus() { +// clearMenus(); +// +// HMENU hmenu = User32.CreatePopupMenu(); +// this._native = hmenu; +// +// for (int i = 0; i < menu.getComponentCount(); i++) { +// Component e = menu.getComponent(i); +// +// if (e instanceof JMenu) { +// JMenu sub = (JMenu) e; +// HMENU hsub = createSubmenu(sub); +// +// int nID = menuEntries.size(); +// menuEntries.add(new WindowsBaseMenuItem(sub)); +// +// // you know, the usual windows tricks (transfer handle to +// // decimal) +// int handle = (int) Pointer.nativeValue(hsub.getPointer()); +// +// if (!User32.AppendMenu(hmenu, MF_POPUP | MFT_OWNERDRAW, handle, null)) +// throw new GetLastErrorException(); +// +// MENUITEMINFO mi = new MENUITEMINFO(); +// if (!User32.GetMenuItemInfo(hmenu, handle, false, mi)) +// throw new GetLastErrorException(); +// +// mi.dwItemData = new ULONG_PTR(nID); +// mi.fMask |= MENUITEMINFO.MIIM_DATA; +// if (!User32.SetMenuItemInfo(hmenu, handle, false, mi)) +// throw new GetLastErrorException(); +// +// +// } else if (e instanceof JCheckBoxMenuItem) { +// JCheckBoxMenuItem ch = (JCheckBoxMenuItem) e; +// +// int nID = menuEntries.size(); +// menuEntries.add(new WindowsBaseMenuItem(ch)); +// +// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null)) +// throw new GetLastErrorException(); +// +// MENUITEMINFO mmi = new MENUITEMINFO(); +// if (!User32.GetMenuItemInfo(hmenu, nID, false, mmi)) +// throw new GetLastErrorException(); +// mmi.dwItemData = new ULONG_PTR(nID); +// mmi.fMask |= MENUITEMINFO.MIIM_DATA; +// if (!User32.SetMenuItemInfo(hmenu, nID, false, mmi)) +// throw new GetLastErrorException(); +// } else if (e instanceof JMenuItem) { +// JMenuItem mi = (JMenuItem) e; +// +// int nID = menuEntries.size(); +// menuEntries.add(new WindowsBaseMenuItem(mi)); +// +// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null)) +// throw new GetLastErrorException(); +// +// MENUITEMINFO mmi = new MENUITEMINFO(); +// if (!User32.GetMenuItemInfo(hmenu, nID, false, mmi)) +// throw new GetLastErrorException(); +// mmi.dwItemData = new ULONG_PTR(nID); +// mmi.fMask |= MENUITEMINFO.MIIM_DATA; +// if (!User32.SetMenuItemInfo(hmenu, nID, false, mmi)) +// throw new GetLastErrorException(); +// } +// +// if (e instanceof JPopupMenu.Separator) { +// if (!User32.AppendMenu(hmenu, MF_SEPARATOR, 0, null)) +// throw new GetLastErrorException(); +// } +// } +// } + +// HMENU createSubmenu(JMenu menu) { +// HMENU hmenu = User32.CreatePopupMenu(); +// // seems like you dont have to free this menu, since it already attached +// // to main HMENU handler +// +// for (int i = 0; i < menu.getMenuComponentCount(); i++) { +// Component e = menu.getMenuComponent(i); +// +// if (e instanceof JMenu) { +// JMenu sub = (JMenu) e; +// HMENU hsub = createSubmenu(sub); +// +// // you know, the usual windows tricks (transfer handle to +// // decimal) +// int handle = (int) Pointer.nativeValue(hsub.getPointer()); +// +// int nID = menuEntries.size(); +// menuEntries.add(new WindowsBaseMenuItem(sub)); +// +// if (!User32.AppendMenu(hmenu, MF_POPUP | MFT_OWNERDRAW, handle, null)) +// throw new GetLastErrorException(); +// +// MENUITEMINFO mi = new MENUITEMINFO(); +// if (!User32.GetMenuItemInfo(hmenu, handle, false, mi)) +// throw new GetLastErrorException(); +// mi.dwItemData = new ULONG_PTR(nID); +// mi.fMask |= MENUITEMINFO.MIIM_DATA; +// if (!User32.SetMenuItemInfo(hmenu, handle, false, mi)) +// throw new GetLastErrorException(); +// } else if (e instanceof JCheckBoxMenuItem) { +// JCheckBoxMenuItem ch = (JCheckBoxMenuItem) e; +// +// int nID = menuEntries.size(); +// menuEntries.add(new WindowsBaseMenuItem(ch)); +// +// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null)) +// throw new GetLastErrorException(); +// +// MENUITEMINFO mi = new MENUITEMINFO(); +// if (!User32.GetMenuItemInfo(hmenu, nID, false, mi)) +// throw new GetLastErrorException(); +// mi.dwItemData = new ULONG_PTR(nID); +// mi.fMask |= MENUITEMINFO.MIIM_DATA; +// if (!User32.SetMenuItemInfo(hmenu, nID, false, mi)) +// throw new GetLastErrorException(); +// } else if (e instanceof JMenuItem) { +// JMenuItem mi = (JMenuItem) e; +// +// int nID = menuEntries.size(); +// menuEntries.add(new WindowsBaseMenuItem(mi)); +// +// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null)) +// throw new GetLastErrorException(); +// +// MENUITEMINFO mmi = new MENUITEMINFO(); +// if (!User32.GetMenuItemInfo(hmenu, nID, false, mmi)) +// throw new GetLastErrorException(); +// mmi.dwItemData = new ULONG_PTR(nID); +// mmi.fMask |= MENUITEMINFO.MIIM_DATA; +// if (!User32.SetMenuItemInfo(hmenu, nID, false, mmi)) +// throw new GetLastErrorException(); +// } +// +// if (e instanceof JPopupMenu.Separator) { +// if (!User32.AppendMenu(hmenu, MF_SEPARATOR, 0, null)) +// throw new GetLastErrorException(); +// } +// } +// +// return hmenu; +// } + + + private static + void drawItem(WindowsBaseMenuItem item, HDC hDC, RECT rcItem, int itemState) { + if (!item.enabled) { + GDI32.SetTextColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_GRAYTEXT)); + GDI32.SetBkColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_MENU)); + } + else if ((itemState & DRAWITEMSTRUCT.ODS_SELECTED) == DRAWITEMSTRUCT.ODS_SELECTED) { + GDI32.SetTextColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_HIGHLIGHTTEXT)); + GDI32.SetBkColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_HIGHLIGHT)); + } + else { + GDI32.SetTextColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_MENUTEXT)); + GDI32.SetBkColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_MENU)); + } + + int x = rcItem.left; + int y = rcItem.top; + + x += (getSystemMenuImageSize() + SPACE_ICONS) * 2; + + GDI32.SelectObject(hDC, createSystemMenuFont()); + GDI32.ExtTextOut(hDC, + x, + y, + GDI32.ETO_OPAQUE, + rcItem, + item.text, + item.text.length(), + null); + + x = rcItem.left; + +// if (item.item instanceof JCheckBoxMenuItem) { +// JCheckBoxMenuItem cc = (JCheckBoxMenuItem) item.item; +// if (cc.getState()) { +// // draw checkmark image +//// drawHBITMAP(hbitmapChecked, x, y, hbitmapChecked.getImage().getWidth(), +//// hbitmapChecked.getImage().getHeight(), hDC); +// } +// else { +// // draw blank checkmark image +//// drawHBITMAP(hbitmapUnchecked, x, y, hbitmapUnchecked.getImage().getWidth(), +//// hbitmapUnchecked.getImage().getHeight(), hDC); +// } +// } + + x += getSystemMenuImageSize() + SPACE_ICONS; + + if (item.hbitmapWrapImage != null) { + drawHBITMAP(item.hbitmapWrapImage, + x, + y, + item.hbitmapWrapImage.getImage().getWidth(), + item.hbitmapWrapImage.getImage().getHeight(), + hDC); + } + } + + public static + int getSystemMenuImageSize() { + // get's the height of the default (small) checkmark + return User32.IMPL.GetSystemMetrics(SM_CYMENUCHECK); + } + + static + HFONT createSystemMenuFont() { + NONCLIENTMETRICS nm = new NONCLIENTMETRICS(); + + User32.IMPL.SystemParametersInfo(User32.SPI_GETNONCLIENTMETRICS, 0, nm, 0); + return GDI32.CreateFontIndirect(nm.lfMenuFont); + } + + private static + void drawHBITMAP(HBITMAP hbm, int x, int y, int cx, int cy, HDC hdcDst) { + HDC hdcSrc = GDI32.CreateCompatibleDC(hdcDst); + HANDLE old = GDI32.SelectObject(hdcSrc, hbm); + + BLENDFUNCTION.ByValue bld = new BLENDFUNCTION.ByValue(); + bld.BlendOp = AC_SRC_OVER; + bld.BlendFlags = 0; + bld.SourceConstantAlpha = (byte) 255; + bld.AlphaFormat = AC_SRC_ALPHA; + + if (!MsImg32.AlphaBlend(hdcDst, x, y, cx, cy, hdcSrc, 0, 0, cx, cy, bld)) { + throw new GetLastErrorException(); + } + + GDI32.SelectObject(hdcSrc, old); + + if (!GDI32.DeleteDC(hdcSrc)) { + throw new GetLastErrorException(); + } + } + + private static + SIZE measureItem(HWND hWnd, WindowsBaseMenuItem item) { + HDC hdc = User32.IMPL.GetDC(hWnd); + HANDLE hfntOld = GDI32.SelectObject(hdc, createSystemMenuFont()); + SIZE size = new SIZE(); + if (!GDI32.GetTextExtentPoint32(hdc, + item.text, + item.text.length(), + size)) { + throw new GetLastErrorException(); + } + GDI32.SelectObject(hdc, hfntOld); + User32.IMPL.ReleaseDC(hWnd, hdc); + + size.cx += (getSystemMenuImageSize() + SPACE_ICONS) * 2; + + return size; + } +} diff --git a/src/dorkbox/systemTray/nativeUI/WindowsMenuItem.java b/src/dorkbox/systemTray/nativeUI/WindowsMenuItem.java new file mode 100644 index 0000000..5cb3be1 --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/WindowsMenuItem.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.nativeUI; + +import java.awt.event.ActionEvent; + +import dorkbox.systemTray.peer.MenuItemPeer; + +class WindowsMenuItem extends WindowsBaseMenuItem implements MenuItemPeer { + + private final WindowsMenu parent; + + // this is ALWAYS called on the EDT. + WindowsMenuItem(final WindowsMenu parent, final int index) { + super(index); + this.parent = parent; + } + + @Override + public + void setImage(final dorkbox.systemTray.MenuItem menuItem) { + super.setImage(menuItem.getImage()); + } + + @Override + public + void setEnabled(final dorkbox.systemTray.MenuItem menuItem) { + super.setEnabled(menuItem.getEnabled()); + } + + @Override + public + void setText(final dorkbox.systemTray.MenuItem menuItem) { + super.setText(menuItem.getText()); + } + + @SuppressWarnings("Duplicates") + @Override + public + void setCallback(final dorkbox.systemTray.MenuItem menuItem) { + super.setCallback(menuItem.getCallback(), new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, "")); + } + + @Override + public + void setShortcut(final dorkbox.systemTray.MenuItem menuItem) { +// char shortcut = menuItem.getShortcut(); +// // yikes... +// final int vKey = SwingUtil.getVirtualKey(shortcut); +// +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.setShortcut(new MenuShortcut(vKey)); +// } +// }); + } + + @SuppressWarnings("Duplicates") + @Override + public + void remove() { +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.deleteShortcut(); +// _native.setEnabled(false); +// +// if (swingCallback != null) { +// _native.removeActionListener(swingCallback); +// swingCallback = null; +// } +// parent._native.remove(_native); +// +// _native.removeNotify(); +// } +// }); + } +} diff --git a/src/dorkbox/systemTray/nativeUI/WindowsMenuItemCheckbox.java b/src/dorkbox/systemTray/nativeUI/WindowsMenuItemCheckbox.java new file mode 100644 index 0000000..811d957 --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/WindowsMenuItemCheckbox.java @@ -0,0 +1,153 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.nativeUI; + +import java.awt.event.ActionListener; + +import dorkbox.systemTray.Checkbox; +import dorkbox.systemTray.peer.CheckboxPeer; + +class WindowsMenuItemCheckbox extends WindowsBaseMenuItem implements CheckboxPeer { + + private final WindowsMenu parent; + private final java.awt.CheckboxMenuItem _native = new java.awt.CheckboxMenuItem(); + + // these have to be volatile, because they can be changed from any thread + private volatile ActionListener callback; + private volatile boolean isChecked = false; + + // this is ALWAYS called on the EDT. + WindowsMenuItemCheckbox(final WindowsMenu parent) { + super(0); + this.parent = parent; +// parent._native.add(_native); + } + + @Override + public + void setEnabled(final Checkbox menuItem) { +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.setEnabled(menuItem.getEnabled()); +// } +// }); + } + + @Override + public + void setText(final Checkbox menuItem) { +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.setLabel(menuItem.getText()); +// } +// }); + } + + @SuppressWarnings("Duplicates") + @Override + public + void setCallback(final Checkbox menuItem) { +// if (callback != null) { +// _native.removeActionListener(callback); +// } +// +// callback = menuItem.getCallback(); // can be set to null +// +// if (callback != null) { +// callback = new ActionListener() { +// @Override +// public +// void actionPerformed(ActionEvent e) { +// // this will run on the EDT, since we are calling it from the EDT +// menuItem.setChecked(!isChecked); +// +// // we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms) +// ActionListener cb = menuItem.getCallback(); +// if (cb != null) { +// try { +// cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, "")); +// } catch (Throwable throwable) { +// SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable); +// } +// } +// } +// }; +// +// _native.addActionListener(callback); +// } + } + + @Override + public + void setShortcut(final Checkbox menuItem) { +// char shortcut = menuItem.getShortcut(); +// // yikes... +// final int vKey = SwingUtil.getVirtualKey(shortcut); +// +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.setShortcut(new MenuShortcut(vKey)); +// } +// }); + } + + @Override + public + void setChecked(final Checkbox menuItem) { +// boolean checked = menuItem.getChecked(); +// +// // only dispatch if it's actually different +// if (checked != this.isChecked) { +// this.isChecked = checked; +// +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.setState(isChecked); +// } +// }); +// } + } + + @SuppressWarnings("Duplicates") + @Override + public + void remove() { +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// _native.deleteShortcut(); +// _native.setEnabled(false); +// +// if (callback != null) { +// _native.removeActionListener(callback); +// callback = null; +// } +// parent._native.remove(_native); +// +// _native.removeNotify(); +// } +// }); + } +} diff --git a/src/dorkbox/systemTray/nativeUI/WindowsMenuItemSeparator.java b/src/dorkbox/systemTray/nativeUI/WindowsMenuItemSeparator.java new file mode 100644 index 0000000..375c002 --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/WindowsMenuItemSeparator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.nativeUI; + + +import dorkbox.systemTray.peer.EntryPeer; + +class WindowsMenuItemSeparator extends WindowsBaseMenuItem implements EntryPeer { + + private final WindowsMenu parent; + private final java.awt.MenuItem _native = new java.awt.MenuItem("-"); + + + // this is ALWAYS called on the EDT. + WindowsMenuItemSeparator(final WindowsMenu parent) { + super(0); + this.parent = parent; +// parent._native.add(_native); + } + + @Override + public + void remove() { +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// parent._native.remove(_native); +// } +// }); + } +} diff --git a/src/dorkbox/systemTray/nativeUI/WindowsMenuItemStatus.java b/src/dorkbox/systemTray/nativeUI/WindowsMenuItemStatus.java new file mode 100644 index 0000000..c852605 --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/WindowsMenuItemStatus.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.nativeUI; + +import java.awt.MenuItem; + +import dorkbox.systemTray.Status; +import dorkbox.systemTray.peer.StatusPeer; + +class WindowsMenuItemStatus extends WindowsBaseMenuItem implements StatusPeer { + + private final WindowsMenu parent; + private final MenuItem _native = new MenuItem(); + + WindowsMenuItemStatus(final WindowsMenu parent) { + super(0); + this.parent = parent; + + // status is ALWAYS at 0 index... +// parent._native.insert(_native, 0); + } + + @Override + public + void setText(final Status menuItem) { +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// Font font = _native.getFont(); +// if (font == null) { +// font = new Font(DIALOG, Font.BOLD, 12); // the default font used for dialogs. +// } +// else { +// font = font.deriveFont(Font.BOLD); +// } +// +// _native.setFont(font); +// _native.setLabel(menuItem.getText()); +// +// // this makes sure it can't be selected +// _native.setEnabled(false); +// } +// }); + } + + @Override + public + void remove() { +// SwingUtil.invokeLater(new Runnable() { +// @Override +// public +// void run() { +// parent._native.remove(_native); +// } +// }); + } +} diff --git a/src/dorkbox/systemTray/nativeUI/_WindowsNativeTray.java b/src/dorkbox/systemTray/nativeUI/_WindowsNativeTray.java new file mode 100644 index 0000000..eaa68dc --- /dev/null +++ b/src/dorkbox/systemTray/nativeUI/_WindowsNativeTray.java @@ -0,0 +1,272 @@ +/* + * Copyright 2017 dorkbox, llc + * + * 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 dorkbox.systemTray.nativeUI; + +import static com.sun.jna.platform.win32.WinDef.HWND; +import static com.sun.jna.platform.win32.WinDef.LPARAM; +import static com.sun.jna.platform.win32.WinDef.POINT; +import static com.sun.jna.platform.win32.WinDef.WPARAM; +import static dorkbox.systemTray.jna.windows.Shell32.NIM_ADD; +import static dorkbox.systemTray.jna.windows.Shell32.NIM_DELETE; +import static dorkbox.systemTray.jna.windows.Shell32.NIM_MODIFY; +import static dorkbox.systemTray.jna.windows.Shell32.Shell_NotifyIcon; +import static dorkbox.systemTray.jna.windows.User32.WM_LBUTTONUP; +import static dorkbox.systemTray.jna.windows.User32.WM_QUIT; +import static dorkbox.systemTray.jna.windows.User32.WM_RBUTTONUP; +import static dorkbox.systemTray.jna.windows.WindowsEventDispatch.WM_SHELLNOTIFY; +import static dorkbox.systemTray.jna.windows.WindowsEventDispatch.WM_TASKBARCREATED; + +import java.io.File; + +import javax.swing.ImageIcon; + +import dorkbox.systemTray.MenuItem; +import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.Tray; +import dorkbox.systemTray.jna.windows.HBITMAPWrap; +import dorkbox.systemTray.jna.windows.HICONWrap; +import dorkbox.systemTray.jna.windows.Kernel32; +import dorkbox.systemTray.jna.windows.Listener; +import dorkbox.systemTray.jna.windows.Shell32; +import dorkbox.systemTray.jna.windows.User32; +import dorkbox.systemTray.jna.windows.WindowsEventDispatch; +import dorkbox.systemTray.jna.windows.structs.NOTIFYICONDATA; + + +/** + * Native implementation of a System tray on Windows, derivative of the original implementation by Nathan Sweet (BSD License). + */ +public +class _WindowsNativeTray extends Tray implements NativeUI { + private final Listener quitListener; + private final Listener menuListener; + + // is the system tray visible or not. + private volatile boolean visible = true; + + private volatile File imageFile; + private volatile HICONWrap imageIcon; + + private volatile String tooltipText = ""; + private final Listener showListener; + + public _WindowsNativeTray (final dorkbox.systemTray.SystemTray systemTray) { + super(); + + // we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization. + final WindowsMenu windowsMenu = new WindowsMenu() { + @Override + public + void setEnabled(final MenuItem menuItem) { + boolean enabled = menuItem.getEnabled(); + + if (visible && !enabled) { + // hide + hide(); + } + else if (!visible && enabled) { + // show + show(); + } + } + + @Override + public + void setImage(final MenuItem menuItem) { + imageFile = menuItem.getImage(); + + if (imageIcon != null) { + imageIcon.close(); + } + imageIcon = convertImage(imageFile); + + NOTIFYICONDATA nid = new NOTIFYICONDATA(); + nid.hWnd = WindowsEventDispatch.get(); + nid.setIcon(imageIcon); + + if (!Shell32.Shell_NotifyIcon(NIM_MODIFY, nid)) { + SystemTray.logger.error("Error setting the image for the tray. {}", Kernel32.getLastErrorMessage()); + } + } + + @Override + public + void setText(final MenuItem menuItem) { + // no op. + } + + @Override + public + void setShortcut(final MenuItem menuItem) { + // no op + } + + @Override + public + void remove() { + hide(); + + super.remove(); + + WPARAM wparam = new WPARAM(0); + LPARAM lparam = new LPARAM(0); + User32.IMPL.PostMessage(WindowsEventDispatch.get(), WM_QUIT, wparam, lparam); + } + }; + + // will wait until it's started up. + WindowsEventDispatch.start(); + + HWND hWnd = WindowsEventDispatch.get(); + if (hWnd == null) { + throw new RuntimeException("The Windows System Tray is not supported! Please write an issue and include your OS type and " + + "configuration"); + } + + showListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + show(); + } + }; + quitListener = new Listener() { + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + System.err.println("quit listener"); + WindowsEventDispatch.stop(); + WindowsEventDispatch.removeListener(showListener); + WindowsEventDispatch.removeListener(quitListener); + WindowsEventDispatch.removeListener(menuListener); + } + }; + + + menuListener = new Listener() { + final POINT mousePosition = new POINT(); + + @Override + public + void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) { + int lp = lParam.intValue(); + switch (lp) { + case WM_LBUTTONUP: + if (User32.IMPL.GetCursorPos(mousePosition)) { + windowsMenu.showContextMenu(mousePosition); + } + break; + case WM_RBUTTONUP: + if (User32.IMPL.GetCursorPos(mousePosition)) { + windowsMenu.showContextMenu(mousePosition); + } + break; + } + } + }; + + WindowsEventDispatch.addListener(WM_TASKBARCREATED, showListener); + WindowsEventDispatch.addListener(WM_QUIT, quitListener); + WindowsEventDispatch.addListener(WM_SHELLNOTIFY, menuListener); + + show(); + +// Runtime.getRuntime().addShutdownHook(new Thread() { +// @Override +// public void run () { +// Shell_NotifyIcon(NIM_DELETE, windowNotifyIconData); +// } +// }); + + bind(windowsMenu, null, systemTray); + } + +// public synchronized void balloon (String title, String message, int millis) { +// balloonNotifyIconData.hWnd = this.windowNotifyIconData.hWnd; +// balloonNotifyIconData.uID = this.windowNotifyIconData.uID; +// balloonNotifyIconData.setBalloon(title, message, millis, NIIF_NONE); +// Shell_NotifyIcon(NIM_MODIFY, balloonNotifyIconData); +// } + + + private void hide() { + if (imageIcon != null) { + imageIcon.close(); + imageIcon = null; + } + + NOTIFYICONDATA nid = new NOTIFYICONDATA(); + nid.hWnd = WindowsEventDispatch.get(); + + if (!Shell32.Shell_NotifyIcon(NIM_DELETE, nid)) { + SystemTray.logger.error("Error hiding tray. {}", Kernel32.getLastErrorMessage()); + } + visible = false; + } + + private void show() { + if (imageIcon != null) { + imageIcon.close(); + } + imageIcon = convertImage(imageFile); + + NOTIFYICONDATA nid = new NOTIFYICONDATA(); + nid.hWnd = WindowsEventDispatch.get(); + nid.setTooltip(tooltipText); + nid.setIcon(imageIcon); + nid.setCallback(WM_SHELLNOTIFY); + + if (!Shell_NotifyIcon(NIM_ADD, nid)) { + SystemTray.logger.error("Error showing tray. {}", Kernel32.getLastErrorMessage()); + } + visible = true; + } + + @Override + protected + void setTooltip_(final String tooltipText) { + if (this.tooltipText.equals(tooltipText)){ + return; + } + this.tooltipText = tooltipText; + + NOTIFYICONDATA nid = new NOTIFYICONDATA(); + nid.hWnd = WindowsEventDispatch.get(); + nid.setTooltip(tooltipText); + + Shell_NotifyIcon(NIM_MODIFY, nid); + } + + @Override + public + boolean hasImage() { + return imageFile != null; + } + + private static + HICONWrap convertImage(final File imageFile) { + if (imageFile != null) { + ImageIcon imageIcon = new ImageIcon(imageFile.getAbsolutePath()); + // fully loads the image and returns when it's done loading the image + imageIcon = new ImageIcon(imageIcon.getImage()); + + HBITMAPWrap hbitmapTrayIcon = new HBITMAPWrap(WindowsBaseMenuItem.createBitmap(imageIcon)); + return new HICONWrap(hbitmapTrayIcon); + } + + return null; + } +}