dorisoy_HardwareMonitor/LibreHardwareMonitor/UI/NotifyIconAdv.cs
// 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);
            }
        }
    }
}
