mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-30 17:13:22 -05:00
399 lines
10 KiB
C#
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
|
|
}
|
|
}
|