owntone-server/win32/FireflyConfig/NotifyIconEx.cs

399 lines
10 KiB
C#

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Reflection;
namespace JCMLib
{
public class NotifyIconEx : System.ComponentModel.Component
{
#region Notify Icon Target Window
private class NotifyIconTarget : System.Windows.Forms.Form
{
public NotifyIconTarget()
{
this.Text = "Hidden NotifyIconTarget Window";
}
protected override void DefWndProc(ref Message msg)
{
if(msg.Msg == 0x400) // WM_USER
{
uint msgId = (uint)msg.LParam;
uint id = (uint)msg.WParam;
switch(msgId)
{
case 0x201: // WM_LBUTTONDOWN
break;
case 0x202: // WM_LBUTTONUP
if(ClickNotify != null)
ClickNotify(this, id);
break;
case 0x203: // WM_LBUTTONDBLCLK
if(DoubleClickNotify != null)
DoubleClickNotify(this, id);
break;
case 0x205: // WM_RBUTTONUP
if(RightClickNotify != null)
RightClickNotify(this, id);
break;
case 0x200: // WM_MOUSEMOVE
break;
case 0x402: // NIN_BALLOONSHOW
break;
// this should happen when the balloon is closed using the x
// - we never seem to get this message!
case 0x403: // NIN_BALLOONHIDE
break;
// we seem to get this next message whether the balloon times
// out or whether it is closed using the x
case 0x404: // NIN_BALLOONTIMEOUT
break;
case 0x405: // NIN_BALLOONUSERCLICK
if(ClickBalloonNotify != null)
ClickBalloonNotify(this, id);
break;
}
}
else if(msg.Msg == 0xC086) // WM_TASKBAR_CREATED
{
if(TaskbarCreated != null)
TaskbarCreated(this, System.EventArgs.Empty);
}
else
{
base.DefWndProc(ref msg);
}
}
public delegate void NotifyIconHandler(object sender, uint id);
public event NotifyIconHandler ClickNotify;
public event NotifyIconHandler DoubleClickNotify;
public event NotifyIconHandler RightClickNotify;
public event NotifyIconHandler ClickBalloonNotify;
public event EventHandler TaskbarCreated;
}
#endregion
#region Platform Invoke
[StructLayout(LayoutKind.Sequential)] private struct NotifyIconData
{
public System.UInt32 cbSize; // DWORD
public System.IntPtr hWnd; // HWND
public System.UInt32 uID; // UINT
public NotifyFlags uFlags; // UINT
public System.UInt32 uCallbackMessage; // UINT
public System.IntPtr hIcon; // HICON
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public System.String szTip; // char[128]
public NotifyState dwState; // DWORD
public NotifyState dwStateMask; // DWORD
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
public System.String szInfo; // char[256]
public System.UInt32 uTimeoutOrVersion; // UINT
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]
public System.String szInfoTitle; // char[64]
public NotifyInfoFlags dwInfoFlags; // DWORD
}
[DllImport("shell32.Dll")]
private static extern System.Int32 Shell_NotifyIcon(NotifyCommand cmd, ref NotifyIconData data);
[DllImport("User32.Dll")]
private static extern System.Int32 TrackPopupMenuEx(System.IntPtr hMenu,
System.UInt32 uFlags,
System.Int32 x,
System.Int32 y,
System.IntPtr hWnd,
System.IntPtr ignore);
[StructLayout(LayoutKind.Sequential)] private struct POINT
{
public System.Int32 x;
public System.Int32 y;
}
[DllImport("User32.Dll")]
private static extern System.Int32 GetCursorPos(ref POINT point);
[DllImport("User32.Dll")]
private static extern System.Int32 SetForegroundWindow(System.IntPtr hWnd);
#endregion
public enum NotifyInfoFlags {Error=0x03, Info=0x01, None=0x00, Warning=0x02}
private enum NotifyCommand {Add=0x00, Delete=0x02, Modify=0x01}
private enum NotifyFlags {Message=0x01, Icon=0x02, Tip=0x04, Info=0x10, State=0x08}
private enum NotifyState {Hidden=0x01}
private uint m_id = 0; // each icon in the notification area has an id
private IntPtr m_handle; // save the handle so that we can remove icon
private static NotifyIconTarget m_messageSink = new NotifyIconTarget();
private static uint m_nextId = 1;
private string m_text = "";
private Icon m_icon = null;
private ContextMenu m_contextMenu = null;
private bool m_visible = false;
private bool m_doubleClick = false; // fix for extra mouse up message we want to discard
public event EventHandler Click;
public event EventHandler DoubleClick;
public event EventHandler BalloonClick;
#region Properties
public string Text
{
set
{
if(m_text != value)
{
m_text = value;
CreateOrUpdate();
}
}
get
{
return m_text;
}
}
public Icon Icon
{
set
{
m_icon = value;
CreateOrUpdate();
}
get
{
return m_icon;
}
}
public ContextMenu ContextMenu
{
set
{
m_contextMenu = value;
}
get
{
return m_contextMenu;
}
}
public bool Visible
{
set
{
if(m_visible != value)
{
m_visible = value;
CreateOrUpdate();
}
}
get
{
return m_visible;
}
}
#endregion
public NotifyIconEx()
{
}
// this method adds the notification icon if it has not been added and if we
// have enough data to do so
private void CreateOrUpdate()
{
if(this.DesignMode)
return;
if(m_id == 0)
{
if(m_icon != null)
{
// create icon using available properties
Create(m_nextId++);
}
}
else
{
// update notify icon
Update();
}
}
private void Create(uint id)
{
NotifyIconData data = new NotifyIconData();
data.cbSize = (uint)Marshal.SizeOf(data);
m_handle = m_messageSink.Handle;
data.hWnd = m_handle;
m_id = id;
data.uID = m_id;
data.uCallbackMessage = 0x400;
data.uFlags |= NotifyFlags.Message;
data.hIcon = m_icon.Handle; // this should always be valid
data.uFlags |= NotifyFlags.Icon;
data.szTip = m_text;
data.uFlags |= NotifyFlags.Tip;
if(!m_visible)
data.dwState = NotifyState.Hidden;
data.dwStateMask |= NotifyState.Hidden;
Shell_NotifyIcon(NotifyCommand.Add, ref data);
// add handlers
m_messageSink.ClickNotify += new NotifyIconTarget.NotifyIconHandler(OnClick);
m_messageSink.DoubleClickNotify += new NotifyIconTarget.NotifyIconHandler(OnDoubleClick);
m_messageSink.RightClickNotify += new NotifyIconTarget.NotifyIconHandler(OnRightClick);
m_messageSink.ClickBalloonNotify += new NotifyIconTarget.NotifyIconHandler(OnClickBalloon);
m_messageSink.TaskbarCreated += new EventHandler(OnTaskbarCreated);
}
// update an existing icon
private void Update()
{
NotifyIconData data = new NotifyIconData();
data.cbSize = (uint)Marshal.SizeOf(data);
data.hWnd = m_messageSink.Handle;
data.uID = m_id;
data.hIcon = m_icon.Handle; // this should always be valid
data.uFlags |= NotifyFlags.Icon;
data.szTip = m_text;
data.uFlags |= NotifyFlags.Tip;
data.uFlags |= NotifyFlags.State;
if(!m_visible)
data.dwState = NotifyState.Hidden;
data.dwStateMask |= NotifyState.Hidden;
Shell_NotifyIcon(NotifyCommand.Modify, ref data);
}
protected override void Dispose(bool disposing)
{
Remove();
base.Dispose(disposing);
}
public void Remove()
{
if(m_id != 0)
{
// remove the notify icon
NotifyIconData data = new NotifyIconData();
data.cbSize = (uint)Marshal.SizeOf(data);
data.hWnd = m_handle;
data.uID = m_id;
Shell_NotifyIcon(NotifyCommand.Delete, ref data);
m_id = 0;
}
}
public void ShowBalloon(string title, string text, NotifyInfoFlags type, int timeoutInMilliSeconds)
{
if(timeoutInMilliSeconds < 0)
throw new ArgumentException("The parameter must be positive", "timeoutInMilliseconds");
NotifyIconData data = new NotifyIconData();
data.cbSize = (uint)Marshal.SizeOf(data);
data.hWnd = m_messageSink.Handle;
data.uID = m_id;
data.uFlags = NotifyFlags.Info;
data.uTimeoutOrVersion = (uint)timeoutInMilliSeconds; // this value does not seem to work - any ideas?
data.szInfoTitle = title;
data.szInfo = text;
data.dwInfoFlags = type;
Shell_NotifyIcon(NotifyCommand.Modify, ref data);
}
#region Message Handlers
private void OnClick(object sender, uint id)
{
if(id == m_id)
{
if(!m_doubleClick && Click != null)
Click(this, EventArgs.Empty);
m_doubleClick = false;
}
}
private void OnRightClick(object sender, uint id)
{
if(id == m_id)
{
// show context menu
if(m_contextMenu != null)
{
POINT point = new POINT();
GetCursorPos(ref point);
SetForegroundWindow(m_messageSink.Handle); // this ensures that if we show the menu and then click on another window the menu will close
// call non public member of ContextMenu
m_contextMenu.GetType().InvokeMember("OnPopup",
BindingFlags.NonPublic|BindingFlags.InvokeMethod|BindingFlags.Instance,
null, m_contextMenu, new Object[] {System.EventArgs.Empty});
TrackPopupMenuEx(m_contextMenu.Handle, 64, point.x, point.y, m_messageSink.Handle, IntPtr.Zero);
// PostMessage(m_messageSink.Handle, 0, IntPtr.Zero, IntPtr.Zero);
}
}
}
private void OnDoubleClick(object sender, uint id)
{
if(id == m_id)
{
m_doubleClick = true;
if(DoubleClick != null)
DoubleClick(this, EventArgs.Empty);
}
}
private void OnClickBalloon(object sender, uint id)
{
if(id == m_id)
if(BalloonClick != null)
BalloonClick(this, EventArgs.Empty);
}
private void OnTaskbarCreated(object sender, EventArgs e)
{
if(m_id != 0)
Create(m_id); // keep the id the same
}
#endregion
}
}