diff --git a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs new file mode 100644 index 00000000..a2717693 --- /dev/null +++ b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs @@ -0,0 +1,152 @@ +using System.Windows; +using System.Windows.Media; +using HandyControl.Data; +using HandyControl.Tools; +using HandyControl.Tools.Interop; + +namespace HandyControl.Controls +{ + public class NotifyIcon : FrameworkElement + { + private bool _added; + + private readonly object _syncObj = new object(); + + private WindowClass _windowClass; + + private readonly int _id; + + private static int NextId; + + private ImageSource _icon; + + private IconHandle _defaultLargeIconHandle; + + private IconHandle _defaultSmallIconHandle; + + private IconHandle _currentLargeIconHandle; + + private IconHandle _currentSmallIconHandle; + + private const int WmTrayMouseMessage = NativeMethods.WM_USER + 1024; + + public NotifyIcon() + { + _id = ++NextId; + _windowClass = new WindowClass(); + } + + public static readonly DependencyProperty IconProperty = DependencyProperty.Register( + "Icon", typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource), OnIconChanged)); + + private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctl = (NotifyIcon) d; + ctl._icon = (ImageSource) e.NewValue; + ctl.UpdateIcon(); + } + + public ImageSource Icon + { + get => (ImageSource) GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + private void UpdateIcon() + { + IconHandle largeIconHandle; + IconHandle smallIconHandle; + + if (_icon != null) + { + IconHelper.GetIconHandlesFromImageSource(_icon, out largeIconHandle, out smallIconHandle); + } + else + { + if (_defaultLargeIconHandle == null && _defaultSmallIconHandle == null) + { + IconHelper.GetDefaultIconHandles(out largeIconHandle, out smallIconHandle); + _defaultLargeIconHandle = largeIconHandle; + _defaultSmallIconHandle = smallIconHandle; + } + else + { + largeIconHandle = _defaultLargeIconHandle; + smallIconHandle = _defaultSmallIconHandle; + } + } + + if (_currentLargeIconHandle != null && _currentLargeIconHandle != _defaultLargeIconHandle) + { + _currentLargeIconHandle.Dispose(); + } + + if (_currentSmallIconHandle != null && _currentSmallIconHandle != _defaultSmallIconHandle) + { + _currentSmallIconHandle.Dispose(); + } + + _currentLargeIconHandle = largeIconHandle; + _currentSmallIconHandle = smallIconHandle; + } + + public static readonly DependencyProperty BalloonTipIconProperty = DependencyProperty.Register( + "BalloonTipIcon", typeof(ToolTipIcon), typeof(NotifyIcon), new PropertyMetadata(default(ToolTipIcon))); + + public ToolTipIcon BalloonTipIcon + { + get => (ToolTipIcon) GetValue(BalloonTipIconProperty); + set => SetValue(BalloonTipIconProperty, value); + } + + public static readonly DependencyProperty BalloonTipTitleProperty = DependencyProperty.Register( + "BalloonTipTitle", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string))); + + public string BalloonTipTitle + { + get => (string) GetValue(BalloonTipTitleProperty); + set => SetValue(BalloonTipTitleProperty, value); + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + "Text", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string))); + + public string Text + { + get => (string) GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public void ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIcon tipIcon) + { + if (timeout < 0) return; + if (string.IsNullOrEmpty(tipTitle)) return; + + if (_added) + { + if (DesignerHelper.IsInDesignMode) return; + + IntSecurity.UnrestrictedWindows.Demand(); + + var data = new NOTIFYICONDATA + { + hWnd = _windowClass.MessageWindowHandle, + uID = _id, + uFlags = NativeMethods.NIF_INFO, + uTimeoutOrVersion = timeout, + szInfoTitle = tipTitle, + szInfo = tipText + }; + + switch (tipIcon) + { + case ToolTipIcon.Info: data.dwInfoFlags = NativeMethods.NIIF_INFO; break; + case ToolTipIcon.Warning: data.dwInfoFlags = NativeMethods.NIIF_WARNING; break; + case ToolTipIcon.Error: data.dwInfoFlags = NativeMethods.NIIF_ERROR; break; + case ToolTipIcon.None: data.dwInfoFlags = NativeMethods.NIIF_NONE; break; + } + UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data); + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs b/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs new file mode 100644 index 00000000..1bcf1a11 --- /dev/null +++ b/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace HandyControl.Controls +{ + internal class NotifyIconNativeWindow + { + internal NotifyIcon Reference; + + private GCHandle _rootRef; + + public void LockReference(bool locked) + { + if (locked) + { + if (!_rootRef.IsAllocated) + { + _rootRef = GCHandle.Alloc(Reference, GCHandleType.Normal); + } + } + else + { + if (_rootRef.IsAllocated) + { + _rootRef.Free(); + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Other/NotifyIcon.cs b/HandyControl/Controls/Other/NotifyIcon.cs deleted file mode 100644 index c792ac4f..00000000 --- a/HandyControl/Controls/Other/NotifyIcon.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Windows; -using System.Windows.Controls.Primitives; -using System.Windows.Media; -using HandyControl.Tools; - -namespace HandyControl.Controls -{ - public class NotifyIcon : FrameworkElement - { - private Popup _trayPopup; - - private readonly object _syncObj = new object(); - - public static readonly DependencyProperty IconProperty = DependencyProperty.Register( - "Icon", typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource))); - - public ImageSource Icon - { - get => (ImageSource) GetValue(IconProperty); - set => SetValue(IconProperty, value); - } - - private void UpdateIcon(bool showIconInTray) - { - lock (_syncObj) - { - if (DesignerHelper.IsInDesignMode) return; - } - } - } -} \ No newline at end of file diff --git a/HandyControl/Data/Enum/ToolTipIcon.cs b/HandyControl/Data/Enum/ToolTipIcon.cs new file mode 100644 index 00000000..d14beaed --- /dev/null +++ b/HandyControl/Data/Enum/ToolTipIcon.cs @@ -0,0 +1,10 @@ +namespace HandyControl.Data +{ + public enum ToolTipIcon + { + None, + Info, + Warning, + Error + } +} \ No newline at end of file diff --git a/HandyControl/Data/Icon.cs b/HandyControl/Data/Icon.cs new file mode 100644 index 00000000..d962da56 --- /dev/null +++ b/HandyControl/Data/Icon.cs @@ -0,0 +1,41 @@ +using HandyControl.Tools.Interop; +using System; +using System.Diagnostics.CodeAnalysis; + +namespace HandyControl.Data +{ + public sealed class Icon : IDisposable + { + private IntPtr _handle; + + ~Icon() + { + Dispose(false); + } + + internal Icon(IntPtr handle) + { + if (handle == IntPtr.Zero) + { + throw new ArgumentException("InvalidGDIHandle", typeof(Icon).Name); + } + _handle = handle; + } + + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + private void Dispose(bool disposing) + { + if (_handle != IntPtr.Zero) + { + UnsafeNativeMethods.DestroyIcon(_handle); + _handle = IntPtr.Zero; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index 3cb91255..b4fcfd7b 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -115,8 +115,9 @@ - + + @@ -172,11 +173,13 @@ + + @@ -224,8 +227,13 @@ + + + + + @@ -305,6 +313,20 @@ + + + + + + + + + + + + + + diff --git a/HandyControl/Interactivity/EventToCommand.cs b/HandyControl/Interactivity/EventToCommand.cs index de449aa5..dca2bfae 100644 --- a/HandyControl/Interactivity/EventToCommand.cs +++ b/HandyControl/Interactivity/EventToCommand.cs @@ -1,4 +1,4 @@ -/*refrence from https://github.com/lbugnion/mvvmlight*/ +// reference from https://github.com/lbugnion/mvvmlight //The MIT License(MIT) diff --git a/HandyControl/Tools/Helper/CreateParams.cs b/HandyControl/Tools/Helper/CreateParams.cs new file mode 100644 index 00000000..03f4d550 --- /dev/null +++ b/HandyControl/Tools/Helper/CreateParams.cs @@ -0,0 +1,54 @@ +using System; +using System.Text; + +namespace HandyControl.Tools +{ + internal class CreateParams + { + public string ClassName { get; set; } + + public string Caption { get; set; } + + public int Style { get; set; } + + public int ExStyle{ get; set; } + + public int ClassStyle { get; set; } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public IntPtr Parent { get; set; } + + public object Param { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(64); + sb.Append("CreateParams {'"); + sb.Append(ClassName); + sb.Append("', '"); + sb.Append(Caption); + sb.Append("', 0x"); + sb.Append(Convert.ToString(Style, 16)); + sb.Append(", 0x"); + sb.Append(Convert.ToString(ExStyle, 16)); + sb.Append(", {"); + sb.Append(X); + sb.Append(", "); + sb.Append(Y); + sb.Append(", "); + sb.Append(Width); + sb.Append(", "); + sb.Append(Height); + sb.Append("}"); + sb.Append("}"); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Helper/ExternDllHelper.cs b/HandyControl/Tools/Helper/ExternDllHelper.cs index 77a55d5f..858885dc 100644 --- a/HandyControl/Tools/Helper/ExternDllHelper.cs +++ b/HandyControl/Tools/Helper/ExternDllHelper.cs @@ -17,49 +17,7 @@ namespace HandyControl.Tools private const string User32 = "user32.dll"; - private const string Shell32 = "shell32.dll"; - - public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public class NOTIFYICONDATA - { - public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA)); - public IntPtr hWnd; - public int uID; - public int 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 int dwInfoFlags; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public class WNDCLASS_D - { - public int style; - public WndProc lpfnWndProc; - public int cbClsExtra = 0; - public int cbWndExtra = 0; - public IntPtr hInstance = IntPtr.Zero; - public IntPtr hIcon = IntPtr.Zero; - public IntPtr hCursor = IntPtr.Zero; - public IntPtr hbrBackground = IntPtr.Zero; - public string lpszMenuName = null; - public string lpszClassName = null; - } - - [DllImport(Shell32, CharSet = CharSet.Auto)] - [ResourceExposure(ResourceScope.None)] - public static extern int Shell_NotifyIcon(int message, NOTIFYICONDATA pnid); + public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); public const int E_FAIL = unchecked((int) 0x80004005); @@ -114,7 +72,6 @@ namespace HandyControl.Tools [In] int len); - int Write( [In] IntPtr buf, @@ -128,7 +85,6 @@ namespace HandyControl.Tools [In] int dwOrigin); - void SetSize( [In, MarshalAs(UnmanagedType.I8)] long libNewSize); @@ -142,15 +98,12 @@ namespace HandyControl.Tools [Out, MarshalAs(UnmanagedType.LPArray)] long[] pcbRead); - void Commit( [In] int grfCommitFlags); - void Revert(); - void LockRegion( [In, MarshalAs(UnmanagedType.I8)] long libOffset, @@ -159,7 +112,6 @@ namespace HandyControl.Tools [In] int dwLockType); - void UnlockRegion( [In, MarshalAs(UnmanagedType.I8)] long libOffset, @@ -168,7 +120,6 @@ namespace HandyControl.Tools [In] int dwLockType); - void Stat( [In] IntPtr pStatstg, diff --git a/HandyControl/Tools/Helper/IconHelper.cs b/HandyControl/Tools/Helper/IconHelper.cs new file mode 100644 index 00000000..a45b146c --- /dev/null +++ b/HandyControl/Tools/Helper/IconHelper.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.ObjectModel; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using HandyControl.Tools.Interop; + +namespace HandyControl.Tools +{ + internal static class IconHelper + { + private static Size _smallIconSize; + private static Size _iconSize; + private static int _systemBitDepth; + + [SecurityCritical, SecuritySafeCritical] + public static void GetIconHandlesFromImageSource(ImageSource image, out IconHandle largeIconHandle, out IconHandle smallIconHandle) + { + EnsureSystemMetrics(); + largeIconHandle = CreateIconHandleFromImageSource(image, _iconSize); + smallIconHandle = CreateIconHandleFromImageSource(image, _smallIconSize); + } + + [SecurityCritical] + public static IconHandle CreateIconHandleFromImageSource(ImageSource image, Size size) + { + EnsureSystemMetrics(); + + var asGoodAsItGets = false; + + var bf = image as BitmapFrame; + if (bf?.Decoder?.Frames != null) + { + bf = GetBestMatch(bf.Decoder.Frames, size); + + asGoodAsItGets = bf.Decoder is IconBitmapDecoder || bf.PixelWidth == (int)size.Width && bf.PixelHeight == (int)size.Height; + + image = bf; + } + + if (!asGoodAsItGets) + { + bf = BitmapFrame.Create(GenerateBitmapSource(image, size)); + } + + return CreateIconHandleFromBitmapFrame(bf); + } + + [SecurityCritical] + private static IconHandle CreateIconHandleFromBitmapFrame(BitmapFrame sourceBitmapFrame) + { + BitmapSource bitmapSource = sourceBitmapFrame; + + if (bitmapSource.Format != PixelFormats.Bgra32 && bitmapSource.Format != PixelFormats.Pbgra32) + { + bitmapSource = new FormatConvertedBitmap(bitmapSource, PixelFormats.Bgra32, null, 0.0); + } + + var w = bitmapSource.PixelWidth; + var h = bitmapSource.PixelHeight; + var bpp = bitmapSource.Format.BitsPerPixel; + var stride = (bpp * w + 31) / 32 * 4; + var sizeCopyPixels = stride * h; + var xor = new byte[sizeCopyPixels]; + bitmapSource.CopyPixels(xor, stride, 0); + + return CreateIconCursor(xor, w, h, 0, 0, true); + } + + [SecurityCritical] + internal static IconHandle CreateIconCursor(byte[] colorArray, int width, int height, int xHotspot, int yHotspot, bool isIcon) + { + BitmapHandle colorBitmap = null; + BitmapHandle maskBitmap = null; + + try + { + var bi = new BITMAPINFO(width, -height, 32) + { + bmiHeader_biCompression = NativeMethods.BI_RGB + }; + + var bits = IntPtr.Zero; + colorBitmap = UnsafeNativeMethods.CreateDIBSection(new HandleRef(null, IntPtr.Zero), ref bi, NativeMethods.DIB_RGB_COLORS, ref bits, null, 0); + + if (colorBitmap.IsInvalid || bits == IntPtr.Zero) + { + return IconHandle.GetInvalidIcon(); + } + + Marshal.Copy(colorArray, 0, bits, colorArray.Length); + var maskArray = GenerateMaskArray(width, height, colorArray); + + maskBitmap = UnsafeNativeMethods.CreateBitmap(width, height, 1, 1, maskArray); + if (maskBitmap.IsInvalid) + { + return IconHandle.GetInvalidIcon(); + } + + var iconInfo = new ICONINFO + { + fIcon = isIcon, + xHotspot = xHotspot, + yHotspot = yHotspot, + hbmMask = maskBitmap, + hbmColor = colorBitmap + }; + + return UnsafeNativeMethods.CreateIconIndirect(iconInfo); + } + finally + { + colorBitmap?.Dispose(); + maskBitmap?.Dispose(); + } + } + + private static byte[] GenerateMaskArray(int width, int height, byte[] colorArray) + { + var nCount = width * height; + var bytesPerScanLine = AlignToBytes(width, 2) / 8; + var bitsMask = new byte[bytesPerScanLine * height]; + + for (var i = 0; i < nCount; i++) + { + var hPos = i % width; + var vPos = i / width; + var byteIndex = hPos / 8; + var offsetBit = (byte)(0x80 >> (hPos % 8)); + + if (colorArray[i * 4 + 3] == 0x00) + { + bitsMask[byteIndex + bytesPerScanLine * vPos] |= offsetBit; + } + else + { + bitsMask[byteIndex + bytesPerScanLine * vPos] &= (byte)~offsetBit; + } + + if (hPos == width - 1 && width == 8) + { + bitsMask[1 + bytesPerScanLine * vPos] = 0xff; + } + } + + return bitsMask; + } + + internal static int AlignToBytes(double original, int nBytesCount) + { + var nBitsCount = 8 << (nBytesCount - 1); + return ((int)Math.Ceiling(original) + (nBitsCount - 1)) / nBitsCount * nBitsCount; + } + + private static BitmapSource GenerateBitmapSource(ImageSource img, Size renderSize) + { + var drawingDimensions = new Rect(0, 0, renderSize.Width, renderSize.Height); + + var renderRatio = renderSize.Width / renderSize.Height; + var aspectRatio = img.Width / img.Height; + + if (img.Width <= renderSize.Width && img.Height <= renderSize.Height) + { + drawingDimensions = new Rect((renderSize.Width - img.Width) / 2, (renderSize.Height - img.Height) / 2, img.Width, img.Height); + } + else if (renderRatio > aspectRatio) + { + var scaledRenderWidth = (img.Width / img.Height) * renderSize.Width; + drawingDimensions = new Rect((renderSize.Width - scaledRenderWidth) / 2, 0, scaledRenderWidth, renderSize.Height); + } + else if (renderRatio < aspectRatio) + { + var scaledRenderHeight = img.Height / img.Width * renderSize.Height; + drawingDimensions = new Rect(0, (renderSize.Height - scaledRenderHeight) / 2, renderSize.Width, scaledRenderHeight); + } + + var dv = new DrawingVisual(); + var dc = dv.RenderOpen(); + dc.DrawImage(img, drawingDimensions); + dc.Close(); + + var bmp = new RenderTargetBitmap((int)renderSize.Width, (int)renderSize.Height, 96, 96, PixelFormats.Pbgra32); + bmp.Render(dv); + + return bmp; + } + + private static BitmapFrame GetBestMatch(ReadOnlyCollection frames, Size size) + { + var bestScore = int.MaxValue; + var bestBpp = 0; + var bestIndex = 0; + + var isBitmapIconDecoder = frames[0].Decoder is IconBitmapDecoder; + + for (int i = 0; i < frames.Count && bestScore != 0; ++i) + { + var currentIconBitDepth = isBitmapIconDecoder ? frames[i].Thumbnail.Format.BitsPerPixel : frames[i].Format.BitsPerPixel; + + if (currentIconBitDepth == 0) + { + currentIconBitDepth = 8; + } + + var score = MatchImage(frames[i], size, currentIconBitDepth); + if (score < bestScore) + { + bestIndex = i; + bestBpp = currentIconBitDepth; + bestScore = score; + } + else if (score == bestScore) + { + if (bestBpp < currentIconBitDepth) + { + bestIndex = i; + bestBpp = currentIconBitDepth; + } + } + } + + return frames[bestIndex]; + } + + private static int MatchImage(BitmapFrame frame, Size size, int bpp) + { + var score = 2 * MyAbs(bpp, _systemBitDepth, false) + + MyAbs(frame.PixelWidth, (int)size.Width, true) + + MyAbs(frame.PixelHeight, (int)size.Height, true); + + return score; + } + + private static int MyAbs(int valueHave, int valueWant, bool fPunish) + { + var diff = (valueHave - valueWant); + + if (diff < 0) + { + diff = (fPunish ? -2 : -1) * diff; + } + + return diff; + } + + [SecurityCritical, SecuritySafeCritical] + private static void EnsureSystemMetrics() + { + if (_systemBitDepth == 0) + { + var hdcDesktop = new HandleRef(null, UnsafeNativeMethods.GetDC(new HandleRef())); + try + { + var sysBitDepth = UnsafeNativeMethods.GetDeviceCaps(hdcDesktop, NativeMethods.BITSPIXEL); + sysBitDepth *= UnsafeNativeMethods.GetDeviceCaps(hdcDesktop, NativeMethods.PLANES); + + if (sysBitDepth == 8) + { + sysBitDepth = 4; + } + + var cxSmallIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CXSMICON); + var cySmallIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CYSMICON); + var cxIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CXICON); + var cyIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CYICON); + + _smallIconSize = new Size(cxSmallIcon, cySmallIcon); + _iconSize = new Size(cxIcon, cyIcon); + _systemBitDepth = sysBitDepth; + } + finally + { + UnsafeNativeMethods.ReleaseDC(new HandleRef(), hdcDesktop); + } + } + } + + [SecurityCritical, SecuritySafeCritical] + public static void GetDefaultIconHandles(out IconHandle largeIconHandle, out IconHandle smallIconHandle) + { + largeIconHandle = null; + smallIconHandle = null; + + SecurityHelper.DemandUIWindowPermission(); + + var iconModuleFile = UnsafeNativeMethods.GetModuleFileName(new HandleRef()); + UnsafeNativeMethods.ExtractIconEx(iconModuleFile, 0, out largeIconHandle, out smallIconHandle, 1); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Helper/SecurityHelper.cs b/HandyControl/Tools/Helper/SecurityHelper.cs new file mode 100644 index 00000000..af582881 --- /dev/null +++ b/HandyControl/Tools/Helper/SecurityHelper.cs @@ -0,0 +1,20 @@ +using System.Security; +using System.Security.Permissions; + +namespace HandyControl.Tools +{ + internal class SecurityHelper + { + private static UIPermission _allWindowsUIPermission; + + [SecurityCritical] + internal static void DemandUIWindowPermission() + { + if (_allWindowsUIPermission == null) + { + _allWindowsUIPermission = new UIPermission(UIPermissionWindow.AllWindows); + } + _allWindowsUIPermission.Demand(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/BITMAPINFO.cs b/HandyControl/Tools/Interop/BITMAPINFO.cs new file mode 100644 index 00000000..4a803564 --- /dev/null +++ b/HandyControl/Tools/Interop/BITMAPINFO.cs @@ -0,0 +1,52 @@ +using System.Runtime.InteropServices; +using System.Security; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential, Pack = 2)] + internal struct BITMAPINFO + { + public int bmiHeader_biSize; + + public int bmiHeader_biWidth; + + public int bmiHeader_biHeight; + + public short bmiHeader_biPlanes; + + public short bmiHeader_biBitCount; + + public int bmiHeader_biCompression; + + public int bmiHeader_biSizeImage; + + public int bmiHeader_biXPelsPerMeter; + + public int bmiHeader_biYPelsPerMeter; + + public int bmiHeader_biClrUsed; + + public int bmiHeader_biClrImportant; + + public BITMAPINFO(int width, int height, short bpp) + { + bmiHeader_biSize = SizeOf(); + bmiHeader_biWidth = width; + bmiHeader_biHeight = height; + bmiHeader_biPlanes = 1; + bmiHeader_biBitCount = bpp; + bmiHeader_biCompression = 0; + bmiHeader_biSizeImage = 0; + bmiHeader_biXPelsPerMeter = 0; + bmiHeader_biYPelsPerMeter = 0; + bmiHeader_biClrUsed = 0; + bmiHeader_biClrImportant = 0; + } + + [SecuritySafeCritical] + private static int SizeOf() + { + return Marshal.SizeOf(typeof(BITMAPINFO)); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/BitmapHandle.cs b/HandyControl/Tools/Interop/BitmapHandle.cs new file mode 100644 index 00000000..2970e514 --- /dev/null +++ b/HandyControl/Tools/Interop/BitmapHandle.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; + +namespace HandyControl.Tools.Interop +{ + internal sealed class BitmapHandle : WpfSafeHandle + { + [SecurityCritical] + private BitmapHandle(bool ownsHandle) : base(ownsHandle, CommonHandles.GDI) + { + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + return UnsafeNativeMethods.DeleteObject(handle); + } + + [SecurityCritical] + internal HandleRef MakeHandleRef(object obj) + { + return new HandleRef(obj, handle); + } + + [SecurityCritical] + internal static BitmapHandle CreateFromHandle(IntPtr hbitmap, bool ownsHandle = true) + { + return new BitmapHandle(ownsHandle) + { + handle = hbitmap, + }; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/CommonHandles.cs b/HandyControl/Tools/Interop/CommonHandles.cs new file mode 100644 index 00000000..deb2a7f5 --- /dev/null +++ b/HandyControl/Tools/Interop/CommonHandles.cs @@ -0,0 +1,13 @@ +namespace HandyControl.Tools.Interop +{ + internal static class CommonHandles + { + public static readonly int Icon = HandleCollector.RegisterType(nameof(Icon), 20, 500); + + public static readonly int HDC = HandleCollector.RegisterType(nameof(HDC), 100, 2); + + public static readonly int GDI = HandleCollector.RegisterType(nameof(GDI), 50, 500); + + public static readonly int Kernel = HandleCollector.RegisterType(nameof(Kernel), 0, 1000); + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/ExternDll.cs b/HandyControl/Tools/Interop/ExternDll.cs new file mode 100644 index 00000000..81fa0c71 --- /dev/null +++ b/HandyControl/Tools/Interop/ExternDll.cs @@ -0,0 +1,11 @@ +namespace HandyControl.Tools.Interop +{ + internal static class ExternDll + { + public const string + User32 = "user32.dll", + Gdi32 = "gdi32.dll", + Kernel32 = "kernel32.dll", + Shell32 = "shell32.dll"; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/HandleCollector.cs b/HandyControl/Tools/Interop/HandleCollector.cs new file mode 100644 index 00000000..b53f3c96 --- /dev/null +++ b/HandyControl/Tools/Interop/HandleCollector.cs @@ -0,0 +1,132 @@ +// reference from https://referencesource.microsoft.com/#WindowsBase/Shared/MS/Win32/HandleCollector.cs,d0f99220d8e1b708 + +using System; +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + internal static class HandleCollector + { + private static HandleType[] HandleTypes; + private static int HandleTypeCount; + + private static readonly object HandleMutex = new object(); + + internal static IntPtr Add(IntPtr handle, int type) + { + HandleTypes[type - 1].Add(); + return handle; + } + + [System.Security.SecuritySafeCritical] + internal static SafeHandle Add(SafeHandle handle, int type) + { + HandleTypes[type - 1].Add(); + return handle; + } + + internal static void Add(int type) + { + HandleTypes[type - 1].Add(); + } + + internal static int RegisterType(string typeName, int expense, int initialThreshold) + { + lock (HandleMutex) + { + if (HandleTypeCount == 0 || HandleTypeCount == HandleTypes.Length) + { + HandleType[] newTypes = new HandleType[HandleTypeCount + 10]; + if (HandleTypes != null) + { + Array.Copy(HandleTypes, 0, newTypes, 0, HandleTypeCount); + } + HandleTypes = newTypes; + } + + HandleTypes[HandleTypeCount++] = new HandleType(expense, initialThreshold); + return HandleTypeCount; + } + } + + internal static IntPtr Remove(IntPtr handle, int type) + { + HandleTypes[type - 1].Remove(); + return handle; + } + + [System.Security.SecuritySafeCritical] + internal static SafeHandle Remove(SafeHandle handle, int type) + { + HandleTypes[type - 1].Remove(); + return handle; + } + + internal static void Remove(int type) + { + HandleTypes[type - 1].Remove(); + } + + private class HandleType + { + private readonly int _initialThreshHold; + private int _threshHold; + private int _handleCount; + private readonly int _deltaPercent; + + internal HandleType(int expense, int initialThreshHold) + { + _initialThreshHold = initialThreshHold; + _threshHold = initialThreshHold; + _deltaPercent = 100 - expense; + } + + internal void Add() + { + lock (this) + { + _handleCount++; + var performCollect = NeedCollection(); + + if (!performCollect) + { + return; + } + } + + GC.Collect(); + + var sleep = (100 - _deltaPercent) / 4; + System.Threading.Thread.Sleep(sleep); + } + + private bool NeedCollection() + { + + if (_handleCount > _threshHold) + { + _threshHold = _handleCount + ((_handleCount * _deltaPercent) / 100); + return true; + } + + var oldThreshHold = (100 * _threshHold) / (100 + _deltaPercent); + if (oldThreshHold >= _initialThreshHold && _handleCount < (int)(oldThreshHold * .9F)) + { + _threshHold = oldThreshHold; + } + + return false; + } + + internal void Remove() + { + lock (this) + { + _handleCount--; + + _handleCount = Math.Max(0, _handleCount); + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/ICONINFO.cs b/HandyControl/Tools/Interop/ICONINFO.cs new file mode 100644 index 00000000..564e8df9 --- /dev/null +++ b/HandyControl/Tools/Interop/ICONINFO.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential)] + internal class ICONINFO + { + public bool fIcon = false; + public int xHotspot = 0; + public int yHotspot = 0; + public BitmapHandle hbmMask = null; + public BitmapHandle hbmColor = null; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/IconHandle.cs b/HandyControl/Tools/Interop/IconHandle.cs new file mode 100644 index 00000000..cbaec541 --- /dev/null +++ b/HandyControl/Tools/Interop/IconHandle.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.ConstrainedExecution; +using System.Security; + +namespace HandyControl.Tools.Interop +{ + internal sealed class IconHandle : WpfSafeHandle + { + [SecurityCritical] + private IconHandle() : base(true, CommonHandles.Icon) + { + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + return UnsafeNativeMethods.DestroyIcon(handle); + } + + [SecurityCritical, SecuritySafeCritical] + internal static IconHandle GetInvalidIcon() + { + return new IconHandle(); + } + + [SecurityCritical] + internal IntPtr CriticalGetHandle() + { + return handle; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/IntSecurity.cs b/HandyControl/Tools/Interop/IntSecurity.cs new file mode 100644 index 00000000..679ddaa4 --- /dev/null +++ b/HandyControl/Tools/Interop/IntSecurity.cs @@ -0,0 +1,21 @@ +using System; +using System.Security; +using System.Security.Permissions; + +namespace HandyControl.Tools.Interop +{ + internal class IntSecurity + { + public static CodeAccessPermission UnrestrictedWindows => + new Lazy(() => AllWindows).Value; + + public static CodeAccessPermission AllWindows => + new Lazy(() => new UIPermission(UIPermissionWindow.AllWindows)).Value; + + public static CodeAccessPermission CreateAnyWindow => + new Lazy(() => SafeSubWindows).Value; + + public static CodeAccessPermission SafeSubWindows => + new Lazy(() => new UIPermission(UIPermissionWindow.SafeSubWindows)).Value; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NOTIFYICONDATA.cs b/HandyControl/Tools/Interop/NOTIFYICONDATA.cs new file mode 100644 index 00000000..6794cbb0 --- /dev/null +++ b/HandyControl/Tools/Interop/NOTIFYICONDATA.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class NOTIFYICONDATA + { + public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA)); + public IntPtr hWnd; + public int uID; + public int 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 int dwInfoFlags; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NativeMethods.cs b/HandyControl/Tools/Interop/NativeMethods.cs new file mode 100644 index 00000000..c670c683 --- /dev/null +++ b/HandyControl/Tools/Interop/NativeMethods.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace HandyControl.Tools.Interop +{ + public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); + + [SuppressMessage("ReSharper", "InconsistentNaming")] + [SuppressMessage("ReSharper", "IdentifierTypo")] + internal class NativeMethods + { + public const int + WS_CHILD = 0x40000000, + WM_USER = 0x0400, + BITSPIXEL = 12, + PLANES = 14, + BI_RGB = 0, + DIB_RGB_COLORS = 0, + NIF_MESSAGE = 0x00000001, + NIF_INFO = 0x00000010, + NIF_ICON = 0x00000002, + NIF_TIP = 0x00000004, + NIM_ADD = 0x00000000, + NIM_DELETE = 0x00000002, + NIM_MODIFY = 0x00000001, + NIIF_NONE = 0x00000000, + NIIF_INFO = 0x00000001, + NIIF_WARNING = 0x00000002, + NIIF_ERROR = 0x00000003; + + [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int RegisterWindowMessage(string msg); + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NativeValues.cs b/HandyControl/Tools/Interop/NativeValues.cs new file mode 100644 index 00000000..628123f7 --- /dev/null +++ b/HandyControl/Tools/Interop/NativeValues.cs @@ -0,0 +1,98 @@ +namespace HandyControl.Tools.Interop +{ + internal enum SM + { + CXSCREEN = 0, + CYSCREEN = 1, + CXVSCROLL = 2, + CYHSCROLL = 3, + CYCAPTION = 4, + CXBORDER = 5, + CYBORDER = 6, + CXFIXEDFRAME = 7, + CYFIXEDFRAME = 8, + CYVTHUMB = 9, + CXHTHUMB = 10, + CXICON = 11, + CYICON = 12, + CXCURSOR = 13, + CYCURSOR = 14, + CYMENU = 15, + CXFULLSCREEN = 16, + CYFULLSCREEN = 17, + CYKANJIWINDOW = 18, + MOUSEPRESENT = 19, + CYVSCROLL = 20, + CXHSCROLL = 21, + DEBUG = 22, + SWAPBUTTON = 23, + CXMIN = 28, + CYMIN = 29, + CXSIZE = 30, + CYSIZE = 31, + CXFRAME = 32, + CXSIZEFRAME = CXFRAME, + CYFRAME = 33, + CYSIZEFRAME = CYFRAME, + CXMINTRACK = 34, + CYMINTRACK = 35, + CXDOUBLECLK = 36, + CYDOUBLECLK = 37, + CXICONSPACING = 38, + CYICONSPACING = 39, + MENUDROPALIGNMENT = 40, + PENWINDOWS = 41, + DBCSENABLED = 42, + CMOUSEBUTTONS = 43, + SECURE = 44, + CXEDGE = 45, + CYEDGE = 46, + CXMINSPACING = 47, + CYMINSPACING = 48, + CXSMICON = 49, + CYSMICON = 50, + CYSMCAPTION = 51, + CXSMSIZE = 52, + CYSMSIZE = 53, + CXMENUSIZE = 54, + CYMENUSIZE = 55, + ARRANGE = 56, + CXMINIMIZED = 57, + CYMINIMIZED = 58, + CXMAXTRACK = 59, + CYMAXTRACK = 60, + CXMAXIMIZED = 61, + CYMAXIMIZED = 62, + NETWORK = 63, + CLEANBOOT = 67, + CXDRAG = 68, + CYDRAG = 69, + SHOWSOUNDS = 70, + CXMENUCHECK = 71, + CYMENUCHECK = 72, + SLOWMACHINE = 73, + MIDEASTENABLED = 74, + MOUSEWHEELPRESENT = 75, + XVIRTUALSCREEN = 76, + YVIRTUALSCREEN = 77, + CXVIRTUALSCREEN = 78, + CYVIRTUALSCREEN = 79, + CMONITORS = 80, + SAMEDISPLAYFORMAT = 81, + IMMENABLED = 82, + CXFOCUSBORDER = 83, + CYFOCUSBORDER = 84, + TABLETPC = 86, + MEDIACENTER = 87, + REMOTESESSION = 0x1000, + REMOTECONTROL = 0x2001, + } + + internal static class Win32Constant + { + internal const int MAX_PATH = 260; + internal const int INFOTIPSIZE = 1024; + internal const int TRUE = 1; + internal const int FALSE = 0; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/SafeFileMappingHandle.cs b/HandyControl/Tools/Interop/SafeFileMappingHandle.cs new file mode 100644 index 00000000..ad26db90 --- /dev/null +++ b/HandyControl/Tools/Interop/SafeFileMappingHandle.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using Microsoft.Win32.SafeHandles; + +namespace HandyControl.Tools.Interop +{ + internal sealed class SafeFileMappingHandle : SafeHandleZeroOrMinusOneIsInvalid + { + [SecurityCritical] + internal SafeFileMappingHandle(IntPtr handle) : base(false) + { + SetHandle(handle); + } + + [SecurityCritical, SecuritySafeCritical] + internal SafeFileMappingHandle() : base(true) + { + } + + public override bool IsInvalid + { + [SecurityCritical, SecuritySafeCritical] + get => handle == IntPtr.Zero; + } + + [SecurityCritical, SecuritySafeCritical] + protected override bool ReleaseHandle() + { + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); + try + { + return CloseHandleNoThrow(new HandleRef(null, handle)); + } + finally + { + CodeAccessPermission.RevertAssert(); + } + } + + [SecurityCritical] + public static bool CloseHandleNoThrow(HandleRef handle) + { + HandleCollector.Remove((IntPtr)handle, CommonHandles.Kernel); + var result = UnsafeNativeMethods.IntCloseHandle(handle); + return result; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/UnsafeNativeMethods.cs b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs new file mode 100644 index 00000000..ed6eb2a4 --- /dev/null +++ b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs @@ -0,0 +1,173 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security; +using System.Text; + +namespace HandyControl.Tools.Interop +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal class UnsafeNativeMethods + { + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, SetLastError = true, ExactSpelling = true, EntryPoint = nameof(GetDC), CharSet = CharSet.Auto)] + public static extern IntPtr IntGetDC(HandleRef hWnd); + + [SecurityCritical] + public static IntPtr GetDC(HandleRef hWnd) + { + var hDc = IntGetDC(hWnd); + if (hDc == IntPtr.Zero) + { + throw new Win32Exception(); + } + + return HandleCollector.Add(hDc, CommonHandles.HDC); + } + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, ExactSpelling = true, EntryPoint = nameof(ReleaseDC), CharSet = CharSet.Auto)] + public static extern int IntReleaseDC(HandleRef hWnd, HandleRef hDC); + + [SecurityCritical] + public static int ReleaseDC(HandleRef hWnd, HandleRef hDC) + { + HandleCollector.Remove((IntPtr)hDC, CommonHandles.HDC); + return IntReleaseDC(hWnd, hDC); + } + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] + public static extern int GetDeviceCaps(HandleRef hDC, int nIndex); + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32)] + public static extern int GetSystemMetrics(SM nIndex); + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, EntryPoint = nameof(DestroyIcon), CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool IntDestroyIcon(IntPtr hIcon); + + [SecurityCritical] + public static bool DestroyIcon(IntPtr hIcon) + { + var result = IntDestroyIcon(hIcon); + return result; + } + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, EntryPoint = nameof(DeleteObject), CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool IntDeleteObject(IntPtr hObject); + + [SecurityCritical] + public static bool DeleteObject(IntPtr hObject) + { + var result = IntDeleteObject(hObject); + return result; + } + + [SecurityCritical] + public static BitmapHandle CreateDIBSection(HandleRef hdc, ref BITMAPINFO bitmapInfo, int iUsage, ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset) + { + if (hSection == null) + { + hSection = new SafeFileMappingHandle(IntPtr.Zero); + } + + var hBitmap = PrivateCreateDIBSection(hdc, ref bitmapInfo, iUsage, ref ppvBits, hSection, dwOffset); + return hBitmap; + } + + [DllImport(ExternDll.Kernel32, EntryPoint = "CloseHandle", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool IntCloseHandle(HandleRef handle); + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto, EntryPoint = nameof(CreateDIBSection))] + private static extern BitmapHandle PrivateCreateDIBSection(HandleRef hdc, ref BITMAPINFO bitmapInfo, int iUsage, ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset); + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto, EntryPoint = nameof(CreateIconIndirect))] + private static extern IconHandle PrivateCreateIconIndirect([In, MarshalAs(UnmanagedType.LPStruct)]ICONINFO iconInfo); + + [SecurityCritical] + public static IconHandle CreateIconIndirect([In, MarshalAs(UnmanagedType.LPStruct)]ICONINFO iconInfo) + { + var hIcon = PrivateCreateIconIndirect(iconInfo); + return hIcon; + } + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto, EntryPoint = nameof(CreateBitmap))] + private static extern BitmapHandle PrivateCreateBitmap(int width, int height, int planes, int bitsPerPixel, byte[] lpvBits); + + [SecurityCritical] + internal static BitmapHandle CreateBitmap(int width, int height, int planes, int bitsPerPixel, byte[] lpvBits) + { + var hBitmap = PrivateCreateBitmap(width, height, planes, bitsPerPixel, lpvBits); + return hBitmap; + } + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Kernel32, EntryPoint = "GetModuleFileName", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int IntGetModuleFileName(HandleRef hModule, StringBuilder buffer, int length); + + [SecurityCritical] + internal static string GetModuleFileName(HandleRef hModule) + { + var buffer = new StringBuilder(Win32Constant.MAX_PATH); + while (true) + { + var size = IntGetModuleFileName(hModule, buffer, buffer.Capacity); + if (size == 0) + { + throw new Win32Exception(); + } + + if (size == buffer.Capacity) + { + buffer.EnsureCapacity(buffer.Capacity * 2); + continue; + } + + return buffer.ToString(); + } + } + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Shell32, CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)] + internal static extern int ExtractIconEx(string szExeFileName, int nIconIndex, out IconHandle phiconLarge, out IconHandle phiconSmall, int nIcons); + + [DllImport(ExternDll.Shell32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int Shell_NotifyIcon(int message, NOTIFYICONDATA pnid); + + [SecurityCritical] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [DllImport(ExternDll.User32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW")] + public static extern IntPtr CreateWindowEx( + int dwExStyle, + [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, + int dwStyle, + int x, + int y, + int nWidth, + int nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + + [DllImport(ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.None)] + public static extern short RegisterClass(WNDCLASS wc); + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/WNDCLASS.cs b/HandyControl/Tools/Interop/WNDCLASS.cs new file mode 100644 index 00000000..363d083f --- /dev/null +++ b/HandyControl/Tools/Interop/WNDCLASS.cs @@ -0,0 +1,23 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + [SuppressMessage("ReSharper", "InconsistentNaming")] + [SuppressMessage("ReSharper", "IdentifierTypo")] + internal class WNDCLASS + { + public int style; + public ExternDllHelper.WndProc lpfnWndProc; + public int cbClsExtra = 0; + public int cbWndExtra = 0; + public IntPtr hInstance = IntPtr.Zero; + public IntPtr hIcon = IntPtr.Zero; + public IntPtr hCursor = IntPtr.Zero; + public IntPtr hbrBackground = IntPtr.Zero; + public string lpszMenuName = null; + public string lpszClassName = null; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/WindowClass.cs b/HandyControl/Tools/Interop/WindowClass.cs new file mode 100644 index 00000000..db49dc77 --- /dev/null +++ b/HandyControl/Tools/Interop/WindowClass.cs @@ -0,0 +1,103 @@ +using System; +using System.Security.Permissions; + +namespace HandyControl.Tools.Interop +{ + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + internal class WindowClass + { + internal string WindowClassName; + + private static readonly int WmTaskbarcreated = NativeMethods.RegisterWindowMessage("TaskbarCreated"); + + internal IntPtr MessageWindowHandle { get; private set; } + + private readonly object _syncObj = new object(); + + private bool _added; + + public WindowClass() + { + RegisterClass(); + } + + private void RegisterClass() + { + WindowClassName = $"HandyControl.Controls.NotifyIcon{Guid.NewGuid()}"; + var wndclass = new WNDCLASS + { + style = 0, + lpfnWndProc = Callback, + cbClsExtra = 0, + cbWndExtra = 0, + hInstance = IntPtr.Zero, + hIcon = IntPtr.Zero, + hCursor = IntPtr.Zero, + hbrBackground = IntPtr.Zero, + lpszMenuName = string.Empty, + lpszClassName = WindowClassName + }; + + UnsafeNativeMethods.RegisterClass(wndclass); + MessageWindowHandle = UnsafeNativeMethods.CreateWindowEx(0, WindowClassName, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + } + + public IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) + { + if (msg == WmTaskbarcreated) + { + _added = false; + AddIcon(); + } + return IntPtr.Zero; + } + + private void AddIcon() + { + lock (_syncObj) + { + if (DesignerHelper.IsInDesignMode) return; + + //NativeMethods.NOTIFYICONDATA data = new NativeMethods.NOTIFYICONDATA(); + //data.uCallbackMessage = WM_TRAYMOUSEMESSAGE; + //data.uFlags = NativeMethods.NIF_MESSAGE; + //if (showIconInTray) + //{ + // if (window.Handle == IntPtr.Zero) + // { + // window.CreateHandle(new CreateParams()); + // } + //} + //data.hWnd = window.Handle; + //data.uID = id; + //data.hIcon = IntPtr.Zero; + //data.szTip = null; + //if (icon != null) + //{ + // data.uFlags |= NativeMethods.NIF_ICON; + // data.hIcon = icon.Handle; + //} + //data.uFlags |= NativeMethods.NIF_TIP; + //data.szTip = text; + + //if (showIconInTray && icon != null) + //{ + // if (!added) + // { + // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_ADD, data); + // added = true; + // } + // else + // { + // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data); + // } + //} + //else if (added) + //{ + // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_DELETE, data); + // added = false; + //} + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/WpfSafeHandle.cs b/HandyControl/Tools/Interop/WpfSafeHandle.cs new file mode 100644 index 00000000..8141a2cf --- /dev/null +++ b/HandyControl/Tools/Interop/WpfSafeHandle.cs @@ -0,0 +1,24 @@ +using System.Security; +using Microsoft.Win32.SafeHandles; + +namespace HandyControl.Tools.Interop +{ + internal abstract class WpfSafeHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly int _collectorId; + + [SecurityCritical] + protected WpfSafeHandle(bool ownsHandle, int collectorId) : base(ownsHandle) + { + HandleCollector.Add(collectorId); + _collectorId = collectorId; + } + + [SecurityCritical, SecuritySafeCritical] + protected override void Dispose(bool disposing) + { + HandleCollector.Remove(_collectorId); + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/HandyControlDemo/Properties/Langs/Lang.en.resx b/HandyControlDemo/Properties/Langs/Lang.en.resx index 58dc6dc0..27a87236 100644 --- a/HandyControlDemo/Properties/Langs/Lang.en.resx +++ b/HandyControlDemo/Properties/Langs/Lang.en.resx @@ -500,7 +500,7 @@ NotifyIcon - + Badge diff --git a/HandyControlDemo/Properties/Langs/Lang.resx b/HandyControlDemo/Properties/Langs/Lang.resx index 3687f4be..dc7baedd 100644 --- a/HandyControlDemo/Properties/Langs/Lang.resx +++ b/HandyControlDemo/Properties/Langs/Lang.resx @@ -500,7 +500,7 @@ 托盘图标 - + 标记