openhardwaremonitor/GUI/NotifyIconAdv.cs

807 lines
25 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) 2012 Michael Möller <mmoeller@openhardwaremonitor.org>
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Windows.Forms;
namespace OpenHardwareMonitor.GUI {
public class NotifyIconAdv : IDisposable {
private NotifyIcon genericNotifyIcon;
private NotifyIconWindowsImplementation windowsNotifyIcon;
public NotifyIconAdv() {
if (Hardware.OperatingSystem.IsUnix) { // Unix
genericNotifyIcon = new NotifyIcon();
} else { // Windows
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;
else
return windowsNotifyIcon.BalloonTipText;
}
set {
if (genericNotifyIcon != null)
genericNotifyIcon.BalloonTipText = value;
else
windowsNotifyIcon.BalloonTipText = value;
}
}
public ToolTipIcon BalloonTipIcon {
get {
if (genericNotifyIcon != null)
return genericNotifyIcon.BalloonTipIcon;
else
return windowsNotifyIcon.BalloonTipIcon;
}
set {
if (genericNotifyIcon != null)
genericNotifyIcon.BalloonTipIcon = value;
else
windowsNotifyIcon.BalloonTipIcon = value;
}
}
public string BalloonTipTitle {
get {
if (genericNotifyIcon != null)
return genericNotifyIcon.BalloonTipTitle;
else
return windowsNotifyIcon.BalloonTipTitle;
}
set {
if (genericNotifyIcon != null)
genericNotifyIcon.BalloonTipTitle = value;
else
windowsNotifyIcon.BalloonTipTitle = value;
}
}
public ContextMenu ContextMenu {
get {
if (genericNotifyIcon != null)
return genericNotifyIcon.ContextMenu;
else
return windowsNotifyIcon.ContextMenu;
}
set {
if (genericNotifyIcon != null)
genericNotifyIcon.ContextMenu = value;
else
windowsNotifyIcon.ContextMenu = value;
}
}
public ContextMenuStrip ContextMenuStrip {
get {
if (genericNotifyIcon != null)
return genericNotifyIcon.ContextMenuStrip;
else
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;
else
return windowsNotifyIcon.Icon;
}
set {
if (genericNotifyIcon != null)
genericNotifyIcon.Icon = value;
else
windowsNotifyIcon.Icon = value;
}
}
public string Text {
get {
if (genericNotifyIcon != null)
return genericNotifyIcon.Text;
else
return windowsNotifyIcon.Text;
}
set {
if (genericNotifyIcon != null)
genericNotifyIcon.Text = value;
else
windowsNotifyIcon.Text = value;
}
}
public bool Visible {
get {
if (genericNotifyIcon != null)
return genericNotifyIcon.Visible;
else
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 = 0;
private object syncObj = new object();
private Icon icon;
private string text = "";
private int id;
private bool created;
private NotifyIconNativeWindow window;
private bool doubleClickDown;
private bool visible;
private 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 ContextMenu ContextMenu { get; set; }
public ContextMenuStrip ContextMenuStrip { get; set; }
public object Tag { 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 Type[] { typeof(int) }, null);
id = ++NotifyIconWindowsImplementation.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;
ContextMenu = 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("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 (ContextMenu == null && ContextMenuStrip == null)
return;
NativeMethods.Point p = new NativeMethods.Point();
NativeMethods.GetCursorPos(ref p);
NativeMethods.SetForegroundWindow(
new HandleRef(window, window.Handle));
if (ContextMenu != null) {
ContextMenu.GetType().InvokeMember("OnPopup",
BindingFlags.NonPublic | BindingFlags.InvokeMethod |
BindingFlags.Instance, null, ContextMenu,
new Object[] { System.EventArgs.Empty });
NativeMethods.TrackPopupMenuEx(
new HandleRef(ContextMenu, ContextMenu.Handle), 72,
p.x, p.y, new HandleRef(window, window.Handle),
IntPtr.Zero);
NativeMethods.PostMessage(
new HandleRef(window, window.Handle), WM_NULL, 0, 0);
return;
}
if (ContextMenuStrip != null)
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();
data.CallbackMessage = WM_TRAYMOUSEMESSAGE;
data.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) {
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 = false;
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(ref Message message, MouseButtons button,
bool doubleClick) {
if (doubleClick) {
if (DoubleClick != null)
DoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
if (MouseDoubleClick != null)
MouseDoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
doubleClickDown = true;
}
if (MouseDown != null)
MouseDown(this,
new MouseEventArgs(button, doubleClick ? 2 : 1, 0, 0, 0));
}
private void ProcessMouseUp(ref Message message, MouseButtons button) {
if (MouseUp != null)
MouseUp(this, new MouseEventArgs(button, 0, 0, 0, 0));
if (!doubleClickDown) {
if (Click != null)
Click(this, new MouseEventArgs(button, 0, 0, 0, 0));
if (MouseClick != null)
MouseClick(this, new MouseEventArgs(button, 0, 0, 0, 0));
}
doubleClickDown = false;
}
private void ProcessInitMenuPopup(ref Message message) {
if (ContextMenu != null &&
(bool)ContextMenu.GetType().InvokeMember("ProcessInitMenuPopup",
BindingFlags.NonPublic | BindingFlags.InvokeMethod |
BindingFlags.Instance, null, ContextMenu,
new Object[] { message.WParam })) {
return;
}
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:
if (MouseMove != null)
MouseMove(this,
new MouseEventArgs(Control.MouseButtons, 0, 0, 0, 0));
return;
case WM_LBUTTONDOWN:
ProcessMouseDown(ref message, MouseButtons.Left, false);
return;
case WM_LBUTTONUP:
ProcessMouseUp(ref message, MouseButtons.Left);
return;
case WM_LBUTTONDBLCLK:
ProcessMouseDown(ref message, MouseButtons.Left, true);
return;
case WM_RBUTTONDOWN:
ProcessMouseDown(ref message, MouseButtons.Right, false);
return;
case WM_RBUTTONUP:
if (ContextMenu != null || ContextMenuStrip != null)
ShowContextMenu();
ProcessMouseUp(ref message, MouseButtons.Right);
return;
case WM_RBUTTONDBLCLK:
ProcessMouseDown(ref message, MouseButtons.Right, true);
return;
case WM_MBUTTONDOWN:
ProcessMouseDown(ref message, MouseButtons.Middle, false);
return;
case WM_MBUTTONUP:
ProcessMouseUp(ref message, MouseButtons.Middle);
return;
case WM_MBUTTONDBLCLK:
ProcessMouseDown(ref message, MouseButtons.Middle, true);
return;
case NIN_BALLOONSHOW:
if (BalloonTipShown != null)
BalloonTipShown(this, EventArgs.Empty);
return;
case NIN_BALLOONHIDE:
case NIN_BALLOONTIMEOUT:
if (BalloonTipClosed != null)
BalloonTipClosed(this, EventArgs.Empty);
return;
case NIN_BALLOONUSERCLICK:
if (BalloonTipClicked != null)
BalloonTipClicked(this, EventArgs.Empty);
return;
default:
return;
}
}
if (message.Msg == NotifyIconWindowsImplementation.WM_TASKBARCREATED) {
lock (syncObj) {
created = false;
}
UpdateNotifyIcon(visible);
}
window.DefWndProc(ref message);
}
private class NotifyIconNativeWindow : NativeWindow {
private NotifyIconWindowsImplementation reference;
private GCHandle referenceHandle;
internal NotifyIconNativeWindow(NotifyIconWindowsImplementation component) {
this.reference = component;
}
~NotifyIconNativeWindow() {
if (base.Handle != IntPtr.Zero)
NativeMethods.PostMessage(
new HandleRef(this, base.Handle), WM_CLOSE, 0, 0);
}
public void LockReference(bool locked) {
if (locked) {
if (!referenceHandle.IsAllocated) {
referenceHandle = GCHandle.Alloc(reference, GCHandleType.Normal);
return;
}
} 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 int WM_TASKBARCREATED =
NativeMethods.RegisterWindowMessage("TaskbarCreated");
private static class NativeMethods {
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr PostMessage(HandleRef hwnd, int msg,
int wparam, int lparam);
[DllImport("user32.dll", 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("shell32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Shell_NotifyIcon(NotifyIconMessage message,
NotifyIconData pnid);
[DllImport("user32.dll", 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 int x;
public int y;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool GetCursorPos(ref Point point);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool SetForegroundWindow(HandleRef hWnd);
}
}
}
}