Files
2025-04-07 07:44:27 -07:00

861 lines
27 KiB
C#

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Windows.Forms;
namespace LibreHardwareMonitor.UI;
public class NotifyIconAdv : IDisposable
{
private readonly NotifyIcon _genericNotifyIcon;
private readonly NotifyIconWindowsImplementation _windowsNotifyIcon;
public NotifyIconAdv()
{
if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix)
_genericNotifyIcon = new NotifyIcon();
else
_windowsNotifyIcon = new NotifyIconWindowsImplementation();
}
public event EventHandler BalloonTipClicked
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipClicked += value;
else
_windowsNotifyIcon.BalloonTipClicked += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipClicked -= value;
else
_windowsNotifyIcon.BalloonTipClicked -= value;
}
}
public event EventHandler BalloonTipClosed
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipClosed += value;
else
_windowsNotifyIcon.BalloonTipClosed += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipClosed -= value;
else
_windowsNotifyIcon.BalloonTipClosed -= value;
}
}
public event EventHandler BalloonTipShown
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipShown += value;
else
_windowsNotifyIcon.BalloonTipShown += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipShown -= value;
else
_windowsNotifyIcon.BalloonTipShown -= value;
}
}
public event EventHandler Click
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.Click += value;
else
_windowsNotifyIcon.Click += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.Click -= value;
else
_windowsNotifyIcon.Click -= value;
}
}
public event EventHandler DoubleClick
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.DoubleClick += value;
else
_windowsNotifyIcon.DoubleClick += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.DoubleClick -= value;
else
_windowsNotifyIcon.DoubleClick -= value;
}
}
public event MouseEventHandler MouseClick
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseClick += value;
else
_windowsNotifyIcon.MouseClick += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseClick -= value;
else
_windowsNotifyIcon.MouseClick -= value;
}
}
public event MouseEventHandler MouseDoubleClick
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseDoubleClick += value;
else
_windowsNotifyIcon.MouseDoubleClick += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseDoubleClick -= value;
else
_windowsNotifyIcon.MouseDoubleClick -= value;
}
}
public event MouseEventHandler MouseDown
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseDown += value;
else
_windowsNotifyIcon.MouseDown += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseDown -= value;
else
_windowsNotifyIcon.MouseDown -= value;
}
}
public event MouseEventHandler MouseMove
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseMove += value;
else
_windowsNotifyIcon.MouseMove += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseMove -= value;
else
_windowsNotifyIcon.MouseMove -= value;
}
}
public event MouseEventHandler MouseUp
{
add
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseUp += value;
else
_windowsNotifyIcon.MouseUp += value;
}
remove
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.MouseUp -= value;
else
_windowsNotifyIcon.MouseUp -= value;
}
}
public string BalloonTipText
{
get
{
if (_genericNotifyIcon != null)
return _genericNotifyIcon.BalloonTipText;
return _windowsNotifyIcon.BalloonTipText;
}
set
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipText = value;
else
_windowsNotifyIcon.BalloonTipText = value;
}
}
public ToolTipIcon BalloonTipIcon
{
get
{
if (_genericNotifyIcon != null)
return _genericNotifyIcon.BalloonTipIcon;
return _windowsNotifyIcon.BalloonTipIcon;
}
set
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipIcon = value;
else
_windowsNotifyIcon.BalloonTipIcon = value;
}
}
public string BalloonTipTitle
{
get
{
if (_genericNotifyIcon != null)
return _genericNotifyIcon.BalloonTipTitle;
return _windowsNotifyIcon.BalloonTipTitle;
}
set
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.BalloonTipTitle = value;
else
_windowsNotifyIcon.BalloonTipTitle = value;
}
}
public ContextMenuStrip ContextMenuStrip
{
get
{
if (_genericNotifyIcon != null)
return _genericNotifyIcon.ContextMenuStrip;
return _windowsNotifyIcon.ContextMenuStrip;
}
set
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.ContextMenuStrip = value;
else
_windowsNotifyIcon.ContextMenuStrip = value;
}
}
public object Tag { get; set; }
public Icon Icon
{
get
{
if (_genericNotifyIcon != null)
return _genericNotifyIcon.Icon;
return _windowsNotifyIcon.Icon;
}
set
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.Icon = value;
else
_windowsNotifyIcon.Icon = value;
}
}
public string Text
{
get
{
if (_genericNotifyIcon != null)
return _genericNotifyIcon.Text;
return _windowsNotifyIcon.Text;
}
set
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.Text = value;
else
_windowsNotifyIcon.Text = value;
}
}
public bool Visible
{
get
{
if (_genericNotifyIcon != null)
return _genericNotifyIcon.Visible;
return _windowsNotifyIcon.Visible;
}
set
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.Visible = value;
else
_windowsNotifyIcon.Visible = value;
}
}
public void Dispose()
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.Dispose();
else
_windowsNotifyIcon.Dispose();
}
public void ShowBalloonTip(int timeout)
{
ShowBalloonTip(timeout, BalloonTipTitle, BalloonTipText, BalloonTipIcon);
}
public void ShowBalloonTip(int timeout, string tipTitle, string tipText,
ToolTipIcon tipIcon)
{
if (_genericNotifyIcon != null)
_genericNotifyIcon.ShowBalloonTip(timeout, tipTitle, tipText, tipIcon);
else
_windowsNotifyIcon.ShowBalloonTip(timeout, tipTitle, tipText, tipIcon);
}
private class NotifyIconWindowsImplementation : Component
{
private static int _nextId;
private readonly object _syncObj = new object();
private Icon _icon;
private string _text = "";
private readonly int _id;
private bool _created;
private NotifyIconNativeWindow _window;
private bool _doubleClickDown;
private bool _visible;
private readonly MethodInfo _commandDispatch;
public event EventHandler BalloonTipClicked;
public event EventHandler BalloonTipClosed;
public event EventHandler BalloonTipShown;
public event EventHandler Click;
public event EventHandler DoubleClick;
public event MouseEventHandler MouseClick;
public event MouseEventHandler MouseDoubleClick;
public event MouseEventHandler MouseDown;
public event MouseEventHandler MouseMove;
public event MouseEventHandler MouseUp;
public string BalloonTipText { get; set; }
public ToolTipIcon BalloonTipIcon { get; set; }
public string BalloonTipTitle { get; set; }
public ContextMenuStrip ContextMenuStrip { get; set; }
public Icon Icon
{
get
{
return _icon;
}
set
{
if (_icon != value)
{
_icon = value;
UpdateNotifyIcon(_visible);
}
}
}
public string Text
{
get
{
return _text;
}
set
{
if (value == null)
value = "";
if (value.Length > 63)
throw new ArgumentOutOfRangeException();
if (!value.Equals(_text))
{
_text = value;
if (_visible)
UpdateNotifyIcon(_visible);
}
}
}
public bool Visible
{
get
{
return _visible;
}
set
{
if (_visible != value)
{
_visible = value;
UpdateNotifyIcon(_visible);
}
}
}
public NotifyIconWindowsImplementation()
{
BalloonTipText = "";
BalloonTipTitle = "";
_commandDispatch = typeof(Form).Assembly.
GetType("System.Windows.Forms.Command").GetMethod("DispatchID",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
null, new[] { typeof(int) }, null);
_id = ++_nextId;
_window = new NotifyIconNativeWindow(this);
UpdateNotifyIcon(_visible);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_window != null)
{
_icon = null;
_text = "";
UpdateNotifyIcon(false);
_window.DestroyHandle();
_window = null;
ContextMenuStrip = null;
}
}
else
{
if (_window != null && _window.Handle != IntPtr.Zero)
{
NativeMethods.PostMessage(new HandleRef(_window, _window.Handle), WM_CLOSE, 0, 0);
_window.ReleaseHandle();
}
}
base.Dispose(disposing);
}
public void ShowBalloonTip(int timeout)
{
ShowBalloonTip(timeout, BalloonTipTitle, BalloonTipText, BalloonTipIcon);
}
public void ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIcon tipIcon)
{
if (timeout < 0)
throw new ArgumentOutOfRangeException(nameof(timeout));
if (string.IsNullOrEmpty(tipText))
throw new ArgumentException("tipText");
if (DesignMode)
return;
if (_created)
{
NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData();
if (_window.Handle == IntPtr.Zero)
_window.CreateHandle(new CreateParams());
data.Window = _window.Handle;
data.ID = _id;
data.Flags = NativeMethods.NotifyIconDataFlags.Info;
data.TimeoutOrVersion = timeout;
data.InfoTitle = tipTitle;
data.Info = tipText;
data.InfoFlags = (int)tipIcon;
NativeMethods.Shell_NotifyIcon(NativeMethods.NotifyIconMessage.Modify, data);
}
}
private void ShowContextMenu()
{
if (ContextMenuStrip == null)
return;
NativeMethods.Point p = new NativeMethods.Point();
NativeMethods.GetCursorPos(ref p);
NativeMethods.SetForegroundWindow(new HandleRef(_window, _window.Handle));
ContextMenuStrip?.GetType().InvokeMember("ShowInTaskbar",
BindingFlags.NonPublic | BindingFlags.InvokeMethod |
BindingFlags.Instance, null, ContextMenuStrip, new object[] { p.X, p.Y });
}
private void UpdateNotifyIcon(bool showNotifyIcon)
{
if (DesignMode)
return;
lock (_syncObj)
{
_window.LockReference(showNotifyIcon);
NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData { CallbackMessage = WM_TRAYMOUSEMESSAGE, Flags = NativeMethods.NotifyIconDataFlags.Message };
if (showNotifyIcon && _window.Handle == IntPtr.Zero)
_window.CreateHandle(new CreateParams());
data.Window = _window.Handle;
data.ID = _id;
if (_icon != null)
{
data.Flags |= NativeMethods.NotifyIconDataFlags.Icon;
data.Icon = _icon.Handle;
}
data.Flags |= NativeMethods.NotifyIconDataFlags.Tip;
data.Tip = _text;
if (showNotifyIcon && _icon != null)
{
if (!_created)
{
if (NativeMethods.Shell_NotifyIcon(NativeMethods.NotifyIconMessage.Modify, data))
{
_created = true;
}
else
{
int i = 0;
do
{
_created = NativeMethods.Shell_NotifyIcon(NativeMethods.NotifyIconMessage.Add, data);
if (!_created)
{
System.Threading.Thread.Sleep(200);
i++;
}
} while (!_created && i < 40);
}
}
else
{
NativeMethods.Shell_NotifyIcon(NativeMethods.NotifyIconMessage.Modify, data);
}
}
else
{
if (_created)
{
int i = 0;
bool deleted;
do
{
deleted = NativeMethods.Shell_NotifyIcon(NativeMethods.NotifyIconMessage.Delete, data);
if (!deleted)
{
System.Threading.Thread.Sleep(200);
i++;
}
} while (!deleted && i < 40);
_created = false;
}
}
}
}
private void ProcessMouseDown(MouseButtons button, bool doubleClick)
{
if (doubleClick)
{
DoubleClick?.Invoke(this, new MouseEventArgs(button, 2, 0, 0, 0));
MouseDoubleClick?.Invoke(this, new MouseEventArgs(button, 2, 0, 0, 0));
_doubleClickDown = true;
}
MouseDown?.Invoke(this, new MouseEventArgs(button, doubleClick ? 2 : 1, 0, 0, 0));
}
private void ProcessMouseUp(MouseButtons button)
{
MouseUp?.Invoke(this, new MouseEventArgs(button, 0, 0, 0, 0));
if (!_doubleClickDown)
{
Click?.Invoke(this, new MouseEventArgs(button, 0, 0, 0, 0));
MouseClick?.Invoke(this, new MouseEventArgs(button, 0, 0, 0, 0));
}
_doubleClickDown = false;
}
private void ProcessInitMenuPopup(ref Message message)
{
_window.DefWndProc(ref message);
}
private void WndProc(ref Message message)
{
switch (message.Msg)
{
case WM_DESTROY:
UpdateNotifyIcon(false);
return;
case WM_COMMAND:
if (message.LParam != IntPtr.Zero)
{
_window.DefWndProc(ref message);
return;
}
_commandDispatch.Invoke(null, new object[] { message.WParam.ToInt32() & 0xFFFF });
return;
case WM_INITMENUPOPUP:
ProcessInitMenuPopup(ref message);
return;
case WM_TRAYMOUSEMESSAGE:
switch ((int)message.LParam)
{
case WM_MOUSEMOVE:
MouseMove?.Invoke(this,
new MouseEventArgs(Control.MouseButtons, 0, 0, 0, 0));
return;
case WM_LBUTTONDOWN:
ProcessMouseDown(MouseButtons.Left, false);
return;
case WM_LBUTTONUP:
ProcessMouseUp(MouseButtons.Left);
return;
case WM_LBUTTONDBLCLK:
ProcessMouseDown(MouseButtons.Left, true);
return;
case WM_RBUTTONDOWN:
ProcessMouseDown(MouseButtons.Right, false);
return;
case WM_RBUTTONUP:
if (ContextMenuStrip != null)
ShowContextMenu();
ProcessMouseUp(MouseButtons.Right);
return;
case WM_RBUTTONDBLCLK:
ProcessMouseDown(MouseButtons.Right, true);
return;
case WM_MBUTTONDOWN:
ProcessMouseDown(MouseButtons.Middle, false);
return;
case WM_MBUTTONUP:
ProcessMouseUp(MouseButtons.Middle);
return;
case WM_MBUTTONDBLCLK:
ProcessMouseDown(MouseButtons.Middle, true);
return;
case NIN_BALLOONSHOW:
BalloonTipShown?.Invoke(this, EventArgs.Empty);
return;
case NIN_BALLOONHIDE:
case NIN_BALLOONTIMEOUT:
BalloonTipClosed?.Invoke(this, EventArgs.Empty);
return;
case NIN_BALLOONUSERCLICK:
BalloonTipClicked?.Invoke(this, EventArgs.Empty);
return;
default:
return;
}
}
if (message.Msg == _wmTaskBarCreated)
{
lock (_syncObj)
{
_created = false;
}
UpdateNotifyIcon(_visible);
}
_window.DefWndProc(ref message);
}
private class NotifyIconNativeWindow : NativeWindow
{
private readonly NotifyIconWindowsImplementation _reference;
private GCHandle _referenceHandle;
internal NotifyIconNativeWindow(NotifyIconWindowsImplementation component)
{
_reference = component;
}
~NotifyIconNativeWindow()
{
if (Handle != IntPtr.Zero)
NativeMethods.PostMessage(new HandleRef(this, Handle), WM_CLOSE, 0, 0);
}
public void LockReference(bool locked)
{
if (locked)
{
if (!_referenceHandle.IsAllocated)
{
_referenceHandle = GCHandle.Alloc(_reference, GCHandleType.Normal);
}
}
else
{
if (_referenceHandle.IsAllocated)
_referenceHandle.Free();
}
}
protected override void OnThreadException(Exception e)
{
Application.OnThreadException(e);
}
protected override void WndProc(ref Message m)
{
_reference.WndProc(ref m);
}
}
private const int WM_NULL = 0x00;
private const int WM_DESTROY = 0x02;
private const int WM_CLOSE = 0x10;
private const int WM_COMMAND = 0x111;
private const int WM_INITMENUPOPUP = 0x117;
private const int WM_MOUSEMOVE = 0x200;
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_LBUTTONUP = 0x202;
private const int WM_LBUTTONDBLCLK = 0x203;
private const int WM_RBUTTONDOWN = 0x204;
private const int WM_RBUTTONUP = 0x205;
private const int WM_RBUTTONDBLCLK = 0x206;
private const int WM_MBUTTONDOWN = 0x207;
private const int WM_MBUTTONUP = 0x208;
private const int WM_MBUTTONDBLCLK = 0x209;
private const int WM_TRAYMOUSEMESSAGE = 0x800;
private const int NIN_BALLOONSHOW = 0x402;
private const int NIN_BALLOONHIDE = 0x403;
private const int NIN_BALLOONTIMEOUT = 0x404;
private const int NIN_BALLOONUSERCLICK = 0x405;
private static readonly int _wmTaskBarCreated = NativeMethods.RegisterWindowMessage("TaskbarCreated");
private static class NativeMethods
{
private const string DllNameUser32 = "user32.dll";
private const string DllNameShell32 = "shell32.dll";
[DllImport(DllNameUser32, CharSet = CharSet.Auto)]
public static extern IntPtr PostMessage(HandleRef hwnd, int msg, int wparam, int lparam);
[DllImport(DllNameUser32, CharSet = CharSet.Auto)]
public static extern int RegisterWindowMessage(string msg);
[Flags]
public enum NotifyIconDataFlags : int
{
Message = 0x1,
Icon = 0x2,
Tip = 0x4,
State = 0x8,
Info = 0x10
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class NotifyIconData
{
private int _size = Marshal.SizeOf(typeof(NotifyIconData));
public IntPtr Window;
public int ID;
public NotifyIconDataFlags Flags;
public int CallbackMessage;
public IntPtr Icon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string Tip;
public int State;
public int StateMask;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string Info;
public int TimeoutOrVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string InfoTitle;
public int InfoFlags;
}
public enum NotifyIconMessage : int
{
Add = 0x0,
Modify = 0x1,
Delete = 0x2
}
[DllImport(DllNameShell32, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Shell_NotifyIcon(NotifyIconMessage message, NotifyIconData pnid);
[DllImport(DllNameUser32, CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool TrackPopupMenuEx(HandleRef hmenu, int fuFlags, int x, int y, HandleRef hwnd, IntPtr tpm);
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public readonly int X;
public readonly int Y;
}
[DllImport(DllNameUser32, CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool GetCursorPos(ref Point point);
[DllImport(DllNameUser32, CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool SetForegroundWindow(HandleRef hWnd);
}
}
}