From f57cf2978d92e3493b370d46d9f50ce51c3cdd86 Mon Sep 17 00:00:00 2001 From: Simon G Date: Wed, 7 Apr 2021 13:20:40 +0200 Subject: [PATCH] - add lib.notifyIcon --- Lib.NotifyIcon/INotifyIcon.cs | 52 ++++ Lib.NotifyIcon/Lib.NotifyIcon.csproj | 17 ++ Lib.NotifyIcon/Windows/Native/NativeWindow.cs | 85 +++++++ .../Native/Types/CustomWindowsMessage.cs | 15 ++ .../Windows/Native/Types/MenuFlags.cs | 17 ++ Lib.NotifyIcon/Windows/Native/Types/NIF.cs | 22 ++ Lib.NotifyIcon/Windows/Native/Types/NIIF.cs | 23 ++ Lib.NotifyIcon/Windows/Native/Types/NIM.cs | 16 ++ .../Windows/Native/Types/NotifyIconData.cs | 36 +++ Lib.NotifyIcon/Windows/Native/Types/Point.cs | 12 + Lib.NotifyIcon/Windows/Native/Types/UFlags.cs | 23 ++ .../Windows/Native/Types/WindowsMessage.cs | 240 ++++++++++++++++++ .../Windows/Native/Types/WndClassEx.cs | 26 ++ Lib.NotifyIcon/Windows/Native/WindowApi.cs | 68 +++++ Lib.NotifyIcon/Windows/NotifyIcon.cs | 224 ++++++++++++++++ .../Windows/NotifyIconHelperWindow.cs | 40 +++ Mystify.sln | 6 + 17 files changed, 922 insertions(+) create mode 100644 Lib.NotifyIcon/INotifyIcon.cs create mode 100644 Lib.NotifyIcon/Lib.NotifyIcon.csproj create mode 100644 Lib.NotifyIcon/Windows/Native/NativeWindow.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/CustomWindowsMessage.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/MenuFlags.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/NIF.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/NIIF.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/NIM.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/NotifyIconData.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/Point.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/UFlags.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/WindowsMessage.cs create mode 100644 Lib.NotifyIcon/Windows/Native/Types/WndClassEx.cs create mode 100644 Lib.NotifyIcon/Windows/Native/WindowApi.cs create mode 100644 Lib.NotifyIcon/Windows/NotifyIcon.cs create mode 100644 Lib.NotifyIcon/Windows/NotifyIconHelperWindow.cs diff --git a/Lib.NotifyIcon/INotifyIcon.cs b/Lib.NotifyIcon/INotifyIcon.cs new file mode 100644 index 0000000..37d41d5 --- /dev/null +++ b/Lib.NotifyIcon/INotifyIcon.cs @@ -0,0 +1,52 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using Avalonia.Controls; + +namespace Lib.NotifyIcon +{ + public interface INotifyIcon + { + /// + /// Gets or sets the path for the notify icon + /// + public string IconPath { get; set; } + + /// + /// Gets or sets the tooltip text for the notify icon + /// + public string ToolTipText { get; set; } + + /// + /// Gets or sets the context menu for the notify icon + /// + public ContextMenu ContextMenu { get; set; } + + /// + /// Gets or sets if the notify icon is visible in the taskbar notification area or not + /// + public bool Visible { get; set; } + + /// + /// Removes the notify icon from the taskbar notification area + /// + public void Remove(); + + /// + /// This event is raised when a user clicks on the notification icon + /// + public event EventHandler Click; + + /// + /// This event is raised when a user double clicks on the notification icon + /// + public event EventHandler DoubleClick; + + /// + /// This event is raised when a user right clicks on the notification icon + /// + public event EventHandler RightClick; + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Lib.NotifyIcon.csproj b/Lib.NotifyIcon/Lib.NotifyIcon.csproj new file mode 100644 index 0000000..ff433e1 --- /dev/null +++ b/Lib.NotifyIcon/Lib.NotifyIcon.csproj @@ -0,0 +1,17 @@ + + + + net5.0 + + + + + + + + + + + + + diff --git a/Lib.NotifyIcon/Windows/Native/NativeWindow.cs b/Lib.NotifyIcon/Windows/Native/NativeWindow.cs new file mode 100644 index 0000000..1f6a962 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/NativeWindow.cs @@ -0,0 +1,85 @@ +// Author: Gockner, Simon +// Created: 2021-04-07 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using Lib.NotifyIcon.Windows.Native.Types; + +namespace Lib.NotifyIcon.Windows.Native +{ + internal abstract class NativeWindow + { + private const uint WS_POPUP = 0x80000000; + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly WindowApi.WndProc _wndProc; //We need to store the window proc as a field so that it doesn't get garbage collected away + + private readonly string _className = "NativeHelperWindow" + Guid.NewGuid(); + + /// + /// The window handle of the underlying native window + /// + public IntPtr Handle { get; } + + /// + /// Creates a new native (Win32) helper window for receiving window messages + /// + protected NativeWindow() + { + _wndProc = WndProc; + + WndClassEx wndClassEx = new() + { + cbSize = Marshal.SizeOf(), + lpfnWndProc = _wndProc, + hInstance = WindowApi.GetModuleHandle(null), + lpszClassName = _className + }; + + ushort atom = WindowApi.RegisterClassEx(ref wndClassEx); + + if (atom == 0) + throw new Win32Exception(); + + Handle = WindowApi.CreateWindowEx(0, atom, null, WS_POPUP, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + if (Handle == IntPtr.Zero) + throw new Win32Exception(); + } + + /// + /// Destructs the object and destroys the native window + /// + ~NativeWindow() + { + if (Handle != IntPtr.Zero) + WindowApi.PostMessage(Handle, (uint) WindowsMessage.WmDestroy, IntPtr.Zero, IntPtr.Zero); + } + + /// + /// This function will receive all the system window messages relevant to our window + /// + protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + switch (msg) + { + case (uint) WindowsMessage.WmClose: + { + WindowApi.DestroyWindow(hWnd); + break; + } + case (uint) WindowsMessage.WmDestroy: + { + WindowApi.PostQuitMessage(0); + break; + } + default: + return WindowApi.DefWindowProc(hWnd, msg, wParam, lParam); + } + + return IntPtr.Zero; + } + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/CustomWindowsMessage.cs b/Lib.NotifyIcon/Windows/Native/Types/CustomWindowsMessage.cs new file mode 100644 index 0000000..56230c8 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/CustomWindowsMessage.cs @@ -0,0 +1,15 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + /// + /// Custom Win32 window messages for the NotifyIcon + /// + internal enum CustomWindowsMessage : uint + { + WmTrayIcon = WindowsMessage.WmApp + 1024, + WmTrayMouse = WindowsMessage.WmUser + 1024 + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/MenuFlags.cs b/Lib.NotifyIcon/Windows/Native/Types/MenuFlags.cs new file mode 100644 index 0000000..624cdbd --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/MenuFlags.cs @@ -0,0 +1,17 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + [Flags] + internal enum MenuFlags : uint + { + MfString = 0, + MfByPosition = 0x400, + MfSeparator = 0x800, + MfRemove = 0x1000 + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/NIF.cs b/Lib.NotifyIcon/Windows/Native/Types/NIF.cs new file mode 100644 index 0000000..3188eea --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/NIF.cs @@ -0,0 +1,22 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + // ReSharper disable once InconsistentNaming + [Flags] + internal enum NIF : uint + { + Message = 0x00000001, + Icon = 0x00000002, + Tip = 0x00000004, + State = 0x00000008, + Info = 0x00000010, + Guid = 0x00000020, + Realtime = 0x00000040, + ShowTip = 0x00000080 + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/NIIF.cs b/Lib.NotifyIcon/Windows/Native/Types/NIIF.cs new file mode 100644 index 0000000..ece6eb5 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/NIIF.cs @@ -0,0 +1,23 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + // ReSharper disable once InconsistentNaming + [Flags] + internal enum NIIF : uint + { + None = 0x00000000, + Info = 0x00000001, + Warning = 0x00000002, + Error = 0x00000003, + User = 0x00000004, + IconMask = 0x0000000F, + NoSound = 0x00000010, + LargeIcon = 0x00000020, + RespectQuietTime = 0x00000080 + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/NIM.cs b/Lib.NotifyIcon/Windows/Native/Types/NIM.cs new file mode 100644 index 0000000..9834577 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/NIM.cs @@ -0,0 +1,16 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + // ReSharper disable once InconsistentNaming + internal enum NIM : uint + { + Add = 0x00000000, + Modify = 0x00000001, + Delete = 0x00000002, + SetFocus = 0x00000003, + SetVersion = 0x00000004 + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/NotifyIconData.cs b/Lib.NotifyIcon/Windows/Native/Types/NotifyIconData.cs new file mode 100644 index 0000000..dcb5cf8 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/NotifyIconData.cs @@ -0,0 +1,36 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using System.Runtime.InteropServices; + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal class NotifyIconData + { + public int cbSize = Marshal.SizeOf(); + public IntPtr hWnd; + public int uID; + public NIF uFlags; + public int uCallbackMessage; + public IntPtr hIcon; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string szTip; + + public int dwState = 0; + public int dwStateMask = 0; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string szInfo; + + public int uTimeoutOrVersion; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string szInfoTitle; + + public NIIF dwInfoFlags; + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/Point.cs b/Lib.NotifyIcon/Windows/Native/Types/Point.cs new file mode 100644 index 0000000..3d047e3 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/Point.cs @@ -0,0 +1,12 @@ +// Author: Gockner, Simon +// Created: 2021-04-07 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + internal struct Point + { + public int X; + public int Y; + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/UFlags.cs b/Lib.NotifyIcon/Windows/Native/Types/UFlags.cs new file mode 100644 index 0000000..7a50679 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/UFlags.cs @@ -0,0 +1,23 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + [Flags] + internal enum UFlags : uint + { + TpmLeftAlign = 0x0000, + TpmCenterAlign = 0x0004, + TpmRightAlign = 0x0008, + TpmTopAlign = 0x0000, + TpmVcenterAlign = 0x0010, + TpmBottomAlign = 0x0020, + TpmHorizontal = 0x0000, + TpmVertical = 0x0040, + TpmNoNotify = 0x0080, + TpmReturnCmd = 0x0100 + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/WindowsMessage.cs b/Lib.NotifyIcon/Windows/Native/Types/WindowsMessage.cs new file mode 100644 index 0000000..9088cb8 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/WindowsMessage.cs @@ -0,0 +1,240 @@ +// Author: Gockner, Simon +// Created: 2021-04-07 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + internal enum WindowsMessage : uint + { + WmNull = 0x0000, + WmCreate = 0x0001, + WmDestroy = 0x0002, + WmMove = 0x0003, + WmSize = 0x0005, + WmActivate = 0x0006, + WmSetFocus = 0x0007, + WmKillFocus = 0x0008, + WmEnable = 0x000A, + WmSetRedraw = 0x000B, + WmSetText = 0x000C, + WmGetText = 0x000D, + WmGetTextLength = 0x000E, + WmPaint = 0x000F, + WmClose = 0x0010, + WmQueryEndSession = 0x0011, + WmQueryOpen = 0x0013, + WmEndSession = 0x0016, + WmQuit = 0x0012, + WmEraseBkgnd = 0x0014, + WmSysColorChange = 0x0015, + WmShowWindow = 0x0018, + WmWinIniChange = 0x001A, + WmSettingChange = WmWinIniChange, + WmDevModeChange = 0x001B, + WmActivateApp = 0x001C, + WmFontChange = 0x001D, + WmTimeChange = 0x001E, + WmCancelMode = 0x001F, + WmSetCursor = 0x0020, + WmMouseActivate = 0x0021, + WmChildActivate = 0x0022, + WmQueueSync = 0x0023, + WmGetMinMaxInfo = 0x0024, + WmPaintIcon = 0x0026, + WmIconEraseBkgnd = 0x0027, + WmNextDlgCtl = 0x0028, + WmSpoolerStatus = 0x002A, + WmDrawItem = 0x002B, + WmMeasureItem = 0x002C, + WmDeleteItem = 0x002D, + WmVKeyToItem = 0x002E, + WmCharToItem = 0x002F, + WmSetFont = 0x0030, + WmGetFont = 0x0031, + WmSetHotKey = 0x0032, + WmGetHotKey = 0x0033, + WmQueryDragIcon = 0x0037, + WmCompareItem = 0x0039, + WmGetObject = 0x003D, + WmCompacting = 0x0041, + WmWindowPosChanging = 0x0046, + WmWindowPosChanged = 0x0047, + WmCopyData = 0x004A, + WmCancelJournal = 0x004B, + WmNotify = 0x004E, + WmInputLangChangeRequest = 0x0050, + WmInputLangChange = 0x0051, + WmTCard = 0x0052, + WmHelp = 0x0053, + WmUserChanged = 0x0054, + WmNotifyFormat = 0x0055, + WmContextmenu = 0x007B, + WmStyleChanging = 0x007C, + WmStyleChanged = 0x007D, + WmDisplayChange = 0x007E, + WmGetIcon = 0x007F, + WmSetIcon = 0x0080, + WmNcCreate = 0x0081, + WmNcDestroy = 0x0082, + WmNcCalcSize = 0x0083, + WmNcHitTest = 0x0084, + WmNcPaint = 0x0085, + WmNcActivate = 0x0086, + WmGetDlgCode = 0x0087, + WmSyncPaint = 0x0088, + WmNcMousemove = 0x00A0, + WmNclButtonDown = 0x00A1, + WmNclButtonUp = 0x00A2, + WmNclButtonDblClk = 0x00A3, + WmNcrButtonDown = 0x00A4, + WmNcrButtonUp = 0x00A5, + WmNcrButtonDblClk = 0x00A6, + WmNcmButtonDown = 0x00A7, + WmNcmButtonUp = 0x00A8, + WmNcmButtonDblClk = 0x00A9, + WmNcxButtonDown = 0x00AB, + WmNcxButtonUp = 0x00AC, + WmNcxButtonDblClk = 0x00AD, + WmInputDeviceChange = 0x00FE, + WmInput = 0x00FF, + WmKeyFirst = 0x0100, + WmKeydown = 0x0100, + WmKeyup = 0x0101, + WmChar = 0x0102, + WmDeadChar = 0x0103, + WmSysKeyDown = 0x0104, + WmSysKeyUp = 0x0105, + WmSysChar = 0x0106, + WmSysDeadChar = 0x0107, + WmUniChar = 0x0109, + WmKeyLast = 0x0109, + WmImeStartComposition = 0x010D, + WmImeEndComposition = 0x010E, + WmImeComposition = 0x010F, + WmImeKeyLast = 0x010F, + WmInitDialog = 0x0110, + WmCommand = 0x0111, + WmSysCommand = 0x0112, + WmTimer = 0x0113, + WmHScroll = 0x0114, + WmVScroll = 0x0115, + WmInitMenu = 0x0116, + WmInitMenuPopup = 0x0117, + WmMenuselect = 0x011F, + WmMenuChar = 0x0120, + WmEnterIdle = 0x0121, + WmMenuRButtonUp = 0x0122, + WmMenuDrag = 0x0123, + WmMenuGetObject = 0x0124, + WmUnInitMenuPopup = 0x0125, + WmMenuCommand = 0x0126, + WmChangeUiState = 0x0127, + WmUpdateUiState = 0x0128, + WmQueryUiState = 0x0129, + WmCtlColorMsgBox = 0x0132, + WmCtlColorEdit = 0x0133, + WmCtlColorListBox = 0x0134, + WmCtlColorBtn = 0x0135, + WmCtlColorDlg = 0x0136, + WmCtlColorScrollBar = 0x0137, + WmCtlColorStatic = 0x0138, + WmMouseFirst = 0x0200, + WmMousemove = 0x0200, + WmLButtonDown = 0x0201, + WmLButtonUp = 0x0202, + WmLButtonDblClk = 0x0203, + WmRButtonDown = 0x0204, + WmRButtonUp = 0x0205, + WmRButtonDblClk = 0x0206, + WmMButtonDown = 0x0207, + WmMButtonUp = 0x0208, + WmMButtonDblClk = 0x0209, + WmMousewheel = 0x020A, + WmXButtonDown = 0x020B, + WmXButtonUp = 0x020C, + WmXButtonDblClk = 0x020D, + WmMouseHWheel = 0x020E, + WmMouseLast = 0x020E, + WmParentNotify = 0x0210, + WmEnterMenuLoop = 0x0211, + WmExitMenuLoop = 0x0212, + WmNextMenu = 0x0213, + WmSizing = 0x0214, + WmCaptureChanged = 0x0215, + WmMoving = 0x0216, + WmPowerBroadcast = 0x0218, + WmDevicechange = 0x0219, + WmMdiCreate = 0x0220, + WmMdiDestroy = 0x0221, + WmMdiActivate = 0x0222, + WmMdiRestore = 0x0223, + WmMdiNext = 0x0224, + WmMdiMaximize = 0x0225, + WmMdiTile = 0x0226, + WmMdiCascade = 0x0227, + WmMdiIconArrange = 0x0228, + WmMdiGetActive = 0x0229, + WmMdiSetMenu = 0x0230, + WmEnterSizeMove = 0x0231, + WmExitSizeMove = 0x0232, + WmDropFiles = 0x0233, + WmMdiRefreshMenu = 0x0234, + WmImeSetContext = 0x0281, + WmImeNotify = 0x0282, + WmImeControl = 0x0283, + WmImeCompositionFull = 0x0284, + WmImeSelect = 0x0285, + WmImeChar = 0x0286, + WmImeRequest = 0x0288, + WmImeKeydown = 0x0290, + WmImeKeyup = 0x0291, + WmMouseHover = 0x02A1, + WmMouseleave = 0x02A3, + WmNcMouseHover = 0x02A0, + WmNcMouseLeave = 0x02A2, + WmWtsSessionChange = 0x02B1, + WmTabletFirst = 0x02c0, + WmTabletLast = 0x02df, + WmDpiChanged = 0x02E0, + WmCut = 0x0300, + WmCopy = 0x0301, + WmPaste = 0x0302, + WmClear = 0x0303, + WmUndo = 0x0304, + WmRenderFormat = 0x0305, + WmRenderAllFormats = 0x0306, + WmDestroyClipboard = 0x0307, + WmDrawClipboard = 0x0308, + WmPaintClipboard = 0x0309, + WmVScrollClipboard = 0x030A, + WmSizeClipboard = 0x030B, + WmAskCbForMatName = 0x030C, + WmChangeCbChain = 0x030D, + WmHScrollClipboard = 0x030E, + WmQueryNewPalette = 0x030F, + WmPaletteIsChanging = 0x0310, + WmPaletteChanged = 0x0311, + WmHotkey = 0x0312, + WmPrint = 0x0317, + WmPrintClient = 0x0318, + WmAppCommand = 0x0319, + WmThemeChanged = 0x031A, + WmClipboardUpdate = 0x031D, + WmDwmCompositionChanged = 0x031E, + WmDwmNcRenderingChanged = 0x031F, + WmDwmColorizationColorChanged = 0x0320, + WmDwmWindowMaximizedChange = 0x0321, + WmGetTitleBarInfoEx = 0x033F, + WmHandheldFirst = 0x0358, + WmHandheldLast = 0x035F, + WmAfxFirst = 0x0360, + WmAfxLast = 0x037F, + WmPenWinFirst = 0x0380, + WmPenWinLast = 0x038F, + WmTouch = 0x0240, + WmApp = 0x8000, + WmUser = 0x0400, + + WmDispatchWorkItem = WmUser + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/Types/WndClassEx.cs b/Lib.NotifyIcon/Windows/Native/Types/WndClassEx.cs new file mode 100644 index 0000000..ec1ccce --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/Types/WndClassEx.cs @@ -0,0 +1,26 @@ +// Author: Gockner, Simon +// Created: 2021-04-07 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using System.Runtime.InteropServices; + +namespace Lib.NotifyIcon.Windows.Native.Types +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct WndClassEx + { + public int cbSize; + public int style; + public WindowApi.WndProc lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + public string lpszMenuName; + public string lpszClassName; + public IntPtr hIconSm; + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/Native/WindowApi.cs b/Lib.NotifyIcon/Windows/Native/WindowApi.cs new file mode 100644 index 0000000..b413161 --- /dev/null +++ b/Lib.NotifyIcon/Windows/Native/WindowApi.cs @@ -0,0 +1,68 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using System.Runtime.InteropServices; +using Lib.NotifyIcon.Windows.Native.Types; +using Point = Lib.NotifyIcon.Windows.Native.Types.Point; + +namespace Lib.NotifyIcon.Windows.Native +{ + internal static class WindowApi + { + public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("shell32", CharSet = CharSet.Auto)] + public static extern int Shell_NotifyIcon(NIM dwMessage, NotifyIconData lpData); + + [DllImport("user32.dll")] + public static extern void PostQuitMessage(int nExitCode); + + [DllImport("user32.dll", ExactSpelling = true)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern IntPtr CreatePopupMenu(); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern bool AppendMenu(IntPtr hMenu, MenuFlags uFlags, uint uIdNewItem, string lpNewItem); + + [DllImport("user32.dll")] + public static extern uint TrackPopupMenuEx(IntPtr hMenu, UFlags uFlags, int x, int y, IntPtr hWnd, IntPtr lpTpm); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr CreateWindowEx( + int dwExStyle, + uint lpClassName, + string lpWindowName, + uint dwStyle, + int x, + int y, + int nWidth, + int nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + + [DllImport("user32.dll", EntryPoint = "DefWindowProcW")] + public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern bool GetCursorPos(out Point lpPoint); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")] + public static extern ushort RegisterClassEx(ref WndClassEx lpWcx); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] + public static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("kernel32.dll")] + public static extern IntPtr GetModuleHandle(string lpModuleName); + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/NotifyIcon.cs b/Lib.NotifyIcon/Windows/NotifyIcon.cs new file mode 100644 index 0000000..26bdb05 --- /dev/null +++ b/Lib.NotifyIcon/Windows/NotifyIcon.cs @@ -0,0 +1,224 @@ +// Author: Gockner, Simon +// Created: 2021-04-06 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Drawing; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Platform; +using Lib.NotifyIcon.Windows.Native; +using Lib.NotifyIcon.Windows.Native.Types; +using Point = Lib.NotifyIcon.Windows.Native.Types.Point; + +namespace Lib.NotifyIcon.Windows +{ + public class NotifyIcon : INotifyIcon + { + private static int _nextUId; + + private readonly NotifyIconHelperWindow _helperWindow; + private readonly int _uId; + + private bool _doubleClick; + private bool _iconAdded; + private Icon _icon; + + private string _iconPath = ""; + private string _toolTipText = ""; + private bool _visible; + + /// + /// Creates a new instance and sets up some required resources + /// + public NotifyIcon() + { + _uId = ++_nextUId; + _helperWindow = new NotifyIconHelperWindow(this); + } + + ~NotifyIcon() => UpdateIcon(remove: true); + + /// + /// Gets or sets the path for the notify icon + /// + public string IconPath + { + get => _iconPath; + set + { + _iconPath = value; + + IAssetLoader assetLoader = AvaloniaLocator.Current.GetService(); + _icon = new Icon(assetLoader.Open(new Uri(_iconPath))); + + UpdateIcon(); + } + } + + /// + /// Gets or sets the tooltip text for the notify icon + /// + public string ToolTipText + { + get => _toolTipText; + set + { + _toolTipText = value; + UpdateIcon(); + } + } + + /// + /// Gets or sets the context menu for the notify icon + /// + public ContextMenu ContextMenu { get; set; } + + /// + /// Gets or sets if the notify icon is visible in the taskbar notification area or not + /// + public bool Visible + { + get => _visible; + set + { + _visible = value; + UpdateIcon(); + } + } + + /// + /// Removes the notify icon from the taskbar notification area + /// + public void Remove() => UpdateIcon(true); + + /// + /// Handles the NotifyIcon-specific window messages sent by the notification icon + /// + public void WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + //We only care about tray icon messages + if (msg != (uint) CustomWindowsMessage.WmTrayMouse) + return; + + //Determine the type of message and call the matching event handlers + switch (lParam.ToInt32()) + { + case (int) WindowsMessage.WmLButtonUp: + { + if (!_doubleClick) + Click?.Invoke(this, new EventArgs()); + + _doubleClick = false; + break; + } + case (int) WindowsMessage.WmLButtonDblClk: + { + DoubleClick?.Invoke(this, new EventArgs()); + _doubleClick = true; + break; + } + case (int) WindowsMessage.WmRButtonUp: + { + EventArgs e = new EventArgs(); + RightClick?.Invoke(this, e); + ShowContextMenu(); + break; + } + } + } + + /// + /// Shows, hides or removes the notify icon based on the set properties and parameters + /// + /// If set to true, the notify icon will be removed + private void UpdateIcon(bool remove = false) + { + NotifyIconData iconData = new() + { + hWnd = _helperWindow.Handle, + uID = _uId, + uFlags = NIF.Tip | NIF.Message, + uCallbackMessage = (int) CustomWindowsMessage.WmTrayMouse, + hIcon = IntPtr.Zero, + szTip = ToolTipText + }; + + if (!remove && _icon != null && Visible) + { + iconData.uFlags |= NIF.Icon; + iconData.hIcon = _icon.Handle; + + if (!_iconAdded) + { + WindowApi.Shell_NotifyIcon(NIM.Add, iconData); + _iconAdded = true; + } + else + WindowApi.Shell_NotifyIcon(NIM.Modify, iconData); + } + else + { + WindowApi.Shell_NotifyIcon(NIM.Delete, iconData); + _iconAdded = false; + } + } + + /// + /// If available, displays the notification icon's context menu + /// + private void ShowContextMenu() + { + if (ContextMenu == null) + return; + + // Since we can't use the Avalonia ContextMenu directly due to shortcomings + // regrading its positioning, we'll create a native context menu instead. + // This dictionary will map the menu item IDs which we'll need for the native + // menu to the MenuItems of the provided Avalonia ContextMenu. + Dictionary contextItemLookup = new(); + + // Create a native (Win32) popup menu as the notify icon's context menu. + IntPtr popupMenu = WindowApi.CreatePopupMenu(); + + uint i = 1; + foreach (var item in ContextMenu.Items) + { + MenuItem menuItem = (MenuItem)item; + + // Add items to the native context menu by simply reusing + // the information provided within the Avalonia ContextMenu. + WindowApi.AppendMenu(popupMenu, MenuFlags.MfString, i, (string) menuItem.Header); + + // Add the mapping so that we can find the selected item later + contextItemLookup.Add(i, menuItem); + i++; + } + + // To display a context menu for a notification icon, the current window + // must be the foreground window before the application calls TrackPopupMenu + // or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user + // clicks outside of the menu or the window that created the menu (if it is + // visible). If the current window is a child window, you must set the + // (top-level) parent window as the foreground window. + WindowApi.SetForegroundWindow(_helperWindow.Handle); + + // Get the mouse cursor position + WindowApi.GetCursorPos(out Point pt); + + // Now display the context menu and block until we get a result + uint commandId = WindowApi.TrackPopupMenuEx(popupMenu, + UFlags.TpmBottomAlign | UFlags.TpmRightAlign | UFlags.TpmNoNotify | UFlags.TpmReturnCmd, + pt.X, pt.Y, _helperWindow.Handle, IntPtr.Zero); + + // If we have a result, execute the corresponding command + if (commandId != 0) + contextItemLookup[commandId].Command?.Execute(null); + } + + public event EventHandler Click; + public event EventHandler DoubleClick; + public event EventHandler RightClick; + } +} \ No newline at end of file diff --git a/Lib.NotifyIcon/Windows/NotifyIconHelperWindow.cs b/Lib.NotifyIcon/Windows/NotifyIconHelperWindow.cs new file mode 100644 index 0000000..3887fd7 --- /dev/null +++ b/Lib.NotifyIcon/Windows/NotifyIconHelperWindow.cs @@ -0,0 +1,40 @@ +// Author: Gockner, Simon +// Created: 2021-04-07 +// Copyright(c) 2021 SimonG. All Rights Reserved. + +using System; +using Lib.NotifyIcon.Windows.Native; +using Lib.NotifyIcon.Windows.Native.Types; + +namespace Lib.NotifyIcon.Windows +{ + /// + /// A native Win32 helper window encapsulation for dealing with the window messages sent by the notification icon + /// + internal class NotifyIconHelperWindow : NativeWindow + { + private readonly NotifyIcon _notifyIcon; + + public NotifyIconHelperWindow(NotifyIcon notifyIcon) => _notifyIcon = notifyIcon; + + /// + /// This function will receive all the system window messages relevant to our window. + /// + protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + switch (msg) + { + case (uint) CustomWindowsMessage.WmTrayMouse: + { + // Forward WM_TRAYMOUSE messages to the tray icon's window procedure + _notifyIcon.WndProc(hWnd, msg, wParam, lParam); + break; + } + default: + return base.WndProc(hWnd, msg, wParam, lParam); + } + + return IntPtr.Zero; + } + } +} \ No newline at end of file diff --git a/Mystify.sln b/Mystify.sln index 41c639a..8a9b29e 100644 --- a/Mystify.sln +++ b/Mystify.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mystify", "Mystify\Mystify.csproj", "{87373A7B-B012-4E47-A875-0F189DE731E7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib.NotifyIcon", "Lib.NotifyIcon\Lib.NotifyIcon.csproj", "{47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,9 @@ Global {87373A7B-B012-4E47-A875-0F189DE731E7}.Debug|Any CPU.Build.0 = Debug|Any CPU {87373A7B-B012-4E47-A875-0F189DE731E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {87373A7B-B012-4E47-A875-0F189DE731E7}.Release|Any CPU.Build.0 = Release|Any CPU + {47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47CF9922-FBF5-4AA0-9A4C-0A9405A62E7E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal