197 lines
6.9 KiB
C#
197 lines
6.9 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace ClipForge
|
|
{
|
|
public class TrayIconService : IDisposable
|
|
{
|
|
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
|
private static extern bool Shell_NotifyIcon(uint dwMessage, ref NOTIFYICONDATA lpdata);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool DestroyMenu(IntPtr hMenu);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr CreatePopupMenu();
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
private static extern bool AppendMenu(IntPtr hMenu, uint uFlags, uint uIDNewItem, string lpNewItem);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern int TrackPopupMenu(IntPtr hMenu, uint uFlags, int x, int y, int nReserved, IntPtr hWnd, IntPtr prcRect);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool GetCursorPos(out POINT lpPoint);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
|
|
// This overload loads from a file path
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr LoadImage(IntPtr hInst, string lpszName,
|
|
uint uType, int cxDesired, int cyDesired, uint fuLoad);
|
|
|
|
// This overload loads a system icon by ID
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr LoadImage(IntPtr hInst, IntPtr lpszName,
|
|
uint uType, int cxDesired, int cyDesired, uint fuLoad);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, WndProcDelegate newProc);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("kernel32.dll")]
|
|
private static extern IntPtr GetModuleHandle(string? lpModuleName);
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
private struct NOTIFYICONDATA
|
|
{
|
|
public uint cbSize;
|
|
public IntPtr hWnd;
|
|
public uint uID;
|
|
public uint uFlags;
|
|
public uint uCallbackMessage;
|
|
public IntPtr hIcon;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
|
public string szTip;
|
|
public uint dwState;
|
|
public uint dwStateMask;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
|
public string szInfo;
|
|
public uint uTimeoutOrVersion;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
|
|
public string szInfoTitle;
|
|
public uint dwInfoFlags;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct POINT { public int x; public int y; }
|
|
|
|
private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
|
|
|
private const uint NIM_ADD = 0x00000000;
|
|
private const uint NIM_MODIFY = 0x00000001;
|
|
private const uint NIM_DELETE = 0x00000002;
|
|
private const uint NIF_MESSAGE = 0x00000001;
|
|
private const uint NIF_ICON = 0x00000002;
|
|
private const uint NIF_TIP = 0x00000004;
|
|
private const uint WM_APP = 0x8000;
|
|
private const uint WM_TRAYICON = WM_APP + 1;
|
|
private const uint WM_RBUTTONUP = 0x0205;
|
|
private const uint WM_LBUTTONDBLCLK = 0x0203;
|
|
private const uint MF_STRING = 0x00000000;
|
|
private const uint MF_SEPARATOR = 0x00000800;
|
|
private const uint TPM_RETURNCMD = 0x0100;
|
|
private const int IDI_APPLICATION = 32512;
|
|
private const uint IMAGE_ICON = 1;
|
|
private const uint LR_SHARED = 0x00008000;
|
|
private const uint LR_LOADFROMFILE = 0x00000010;
|
|
private const int GWLP_WNDPROC = -4;
|
|
|
|
private const uint CMD_OPEN = 1001;
|
|
private const uint CMD_EXIT = 1002;
|
|
|
|
private NOTIFYICONDATA _iconData;
|
|
private IntPtr _hwnd;
|
|
private bool _added;
|
|
private WndProcDelegate? _wndProc;
|
|
private IntPtr _oldWndProc;
|
|
|
|
private Action? _showWindow;
|
|
private Action? _exitApp;
|
|
|
|
public void Initialize(IntPtr hwnd, Action showWindow, Action exitApp)
|
|
{
|
|
_hwnd = hwnd;
|
|
_showWindow = showWindow;
|
|
_exitApp = exitApp;
|
|
|
|
_wndProc = TrayWndProc;
|
|
_oldWndProc = SetWindowLongPtr(_hwnd, GWLP_WNDPROC, _wndProc);
|
|
|
|
// Try to load our custom icon from the app directory
|
|
var iconPath = System.IO.Path.Combine(
|
|
AppContext.BaseDirectory, "clipforge.ico");
|
|
|
|
IntPtr hIcon;
|
|
if (System.IO.File.Exists(iconPath))
|
|
{
|
|
hIcon = LoadImage(IntPtr.Zero, iconPath,
|
|
IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
|
|
}
|
|
else
|
|
{
|
|
// Fallback to generic Windows icon
|
|
hIcon = LoadImage(IntPtr.Zero, new IntPtr(IDI_APPLICATION),
|
|
IMAGE_ICON, 16, 16, LR_SHARED);
|
|
}
|
|
|
|
_iconData = new NOTIFYICONDATA
|
|
{
|
|
cbSize = (uint)Marshal.SizeOf<NOTIFYICONDATA>(),
|
|
hWnd = _hwnd,
|
|
uID = 1,
|
|
uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE,
|
|
uCallbackMessage = WM_TRAYICON,
|
|
hIcon = hIcon,
|
|
szTip = "ClipForge — Recording"
|
|
};
|
|
|
|
Shell_NotifyIcon(NIM_ADD, ref _iconData);
|
|
_added = true;
|
|
}
|
|
|
|
private IntPtr TrayWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
if (msg == WM_TRAYICON)
|
|
{
|
|
uint mouseEvent = (uint)lParam & 0xFFFF;
|
|
|
|
if (mouseEvent == WM_LBUTTONDBLCLK)
|
|
_showWindow?.Invoke();
|
|
|
|
if (mouseEvent == WM_RBUTTONUP)
|
|
ShowContextMenu();
|
|
}
|
|
|
|
return CallWindowProc(_oldWndProc, hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
private void ShowContextMenu()
|
|
{
|
|
var hMenu = CreatePopupMenu();
|
|
AppendMenu(hMenu, MF_STRING, CMD_OPEN, "Open ClipForge");
|
|
AppendMenu(hMenu, MF_SEPARATOR, 0, "");
|
|
AppendMenu(hMenu, MF_STRING, CMD_EXIT, "Exit");
|
|
|
|
GetCursorPos(out POINT pt);
|
|
SetForegroundWindow(_hwnd);
|
|
|
|
int cmd = TrackPopupMenu(hMenu, TPM_RETURNCMD,
|
|
pt.x, pt.y, 0, _hwnd, IntPtr.Zero);
|
|
|
|
DestroyMenu(hMenu);
|
|
|
|
if (cmd == CMD_OPEN) _showWindow?.Invoke();
|
|
if (cmd == CMD_EXIT) _exitApp?.Invoke();
|
|
}
|
|
|
|
public void SetStatus(string status)
|
|
{
|
|
if (!_added) return;
|
|
_iconData.szTip = $"ClipForge — {status}";
|
|
Shell_NotifyIcon(NIM_MODIFY, ref _iconData);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_added)
|
|
{
|
|
Shell_NotifyIcon(NIM_DELETE, ref _iconData);
|
|
_added = false;
|
|
}
|
|
}
|
|
}
|
|
} |