176 lines
7.6 KiB
C#
176 lines
7.6 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace KronaStaffTerminal.Services;
|
|
|
|
public sealed class TrayService : IDisposable
|
|
{
|
|
private const uint CallbackMessage = 0x8001;
|
|
private const uint NimAdd = 0;
|
|
private const uint NimModify = 1;
|
|
private const uint NimDelete = 2;
|
|
private const uint NifMessage = 1;
|
|
private const uint NifIcon = 2;
|
|
private const uint NifTip = 4;
|
|
private const uint NifInfo = 16;
|
|
private const uint WmLButtonDblClk = 0x0203;
|
|
private const uint WmRButtonUp = 0x0205;
|
|
private const uint MfString = 0;
|
|
private const uint MfSeparator = 0x0800;
|
|
private const uint TpmReturnCmd = 0x0100;
|
|
private const uint ImageIcon = 1;
|
|
private const uint LrLoadFromFile = 0x0010;
|
|
private const uint LrDefaultSize = 0x0040;
|
|
private const uint OpenCommand = 1001;
|
|
private const uint ExitCommand = 1002;
|
|
|
|
private readonly Action _showWindow;
|
|
private readonly Action _requestExit;
|
|
private readonly WindowProcedure _windowProcedure;
|
|
private readonly IntPtr _windowHandle;
|
|
private readonly IntPtr _iconHandle;
|
|
private NotifyIconData _iconData;
|
|
private bool _disposed;
|
|
|
|
public TrayService(Action showWindow, Action requestExit)
|
|
{
|
|
_showWindow = showWindow;
|
|
_requestExit = requestExit;
|
|
_windowProcedure = HandleWindowMessage;
|
|
var className = $"KronaStaffTerminal.Tray.{Environment.ProcessId}";
|
|
var windowClass = new WindowClass
|
|
{
|
|
Size = (uint)Marshal.SizeOf<WindowClass>(),
|
|
WindowProcedure = Marshal.GetFunctionPointerForDelegate(_windowProcedure),
|
|
Instance = GetModuleHandle(null),
|
|
ClassName = className
|
|
};
|
|
RegisterClassEx(ref windowClass);
|
|
_windowHandle = CreateWindowEx(0, className, className, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, windowClass.Instance, IntPtr.Zero);
|
|
_iconHandle = LoadImage(IntPtr.Zero, Path.Combine(AppContext.BaseDirectory, "Assets", "AppIcon.ico"), ImageIcon, 0, 0, LrLoadFromFile | LrDefaultSize);
|
|
_iconData = CreateIconData(NifMessage | NifIcon | NifTip);
|
|
ShellNotifyIcon(NimAdd, ref _iconData);
|
|
}
|
|
|
|
public void ShowRunningBalloon()
|
|
{
|
|
_iconData.Flags = NifInfo;
|
|
_iconData.InfoTitle = "Krona Staff";
|
|
_iconData.Info = "Терминал продолжает работать в трее.";
|
|
_iconData.InfoFlags = 1;
|
|
ShellNotifyIcon(NimModify, ref _iconData);
|
|
}
|
|
|
|
private NotifyIconData CreateIconData(uint flags) => new()
|
|
{
|
|
Size = (uint)Marshal.SizeOf<NotifyIconData>(),
|
|
WindowHandle = _windowHandle,
|
|
Id = 1,
|
|
Flags = flags,
|
|
CallbackMessage = CallbackMessage,
|
|
IconHandle = _iconHandle,
|
|
Tip = "Krona Staff — терминал"
|
|
};
|
|
|
|
private IntPtr HandleWindowMessage(IntPtr window, uint message, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
if (message == CallbackMessage)
|
|
{
|
|
var mouseMessage = unchecked((uint)lParam.ToInt64());
|
|
if (mouseMessage == WmLButtonDblClk)
|
|
{
|
|
Dispatch(_showWindow);
|
|
return IntPtr.Zero;
|
|
}
|
|
if (mouseMessage == WmRButtonUp)
|
|
{
|
|
ShowContextMenu();
|
|
return IntPtr.Zero;
|
|
}
|
|
}
|
|
return DefWindowProc(window, message, wParam, lParam);
|
|
}
|
|
|
|
private void ShowContextMenu()
|
|
{
|
|
var menu = CreatePopupMenu();
|
|
AppendMenu(menu, MfString, OpenCommand, "Открыть Krona Staff");
|
|
AppendMenu(menu, MfSeparator, 0, null);
|
|
AppendMenu(menu, MfString, ExitCommand, "Выход администратора…");
|
|
GetCursorPos(out var point);
|
|
SetForegroundWindow(_windowHandle);
|
|
var command = TrackPopupMenu(menu, TpmReturnCmd, point.X, point.Y, 0, _windowHandle, IntPtr.Zero);
|
|
DestroyMenu(menu);
|
|
if (command == OpenCommand) Dispatch(_showWindow);
|
|
if (command == ExitCommand) Dispatch(_requestExit);
|
|
}
|
|
|
|
private static void Dispatch(Action action) => App.DispatcherQueue.TryEnqueue(() => action());
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
ShellNotifyIcon(NimDelete, ref _iconData);
|
|
if (_iconHandle != IntPtr.Zero) DestroyIcon(_iconHandle);
|
|
if (_windowHandle != IntPtr.Zero) DestroyWindow(_windowHandle);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
private struct WindowClass
|
|
{
|
|
public uint Size;
|
|
public uint Style;
|
|
public IntPtr WindowProcedure;
|
|
public int ClassExtra;
|
|
public int WindowExtra;
|
|
public IntPtr Instance;
|
|
public IntPtr Icon;
|
|
public IntPtr Cursor;
|
|
public IntPtr Background;
|
|
public string? MenuName;
|
|
public string ClassName;
|
|
public IntPtr SmallIcon;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
private struct NotifyIconData
|
|
{
|
|
public uint Size;
|
|
public IntPtr WindowHandle;
|
|
public uint Id;
|
|
public uint Flags;
|
|
public uint CallbackMessage;
|
|
public IntPtr IconHandle;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string Tip;
|
|
public uint State;
|
|
public uint StateMask;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string Info;
|
|
public uint TimeoutOrVersion;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string InfoTitle;
|
|
public uint InfoFlags;
|
|
public Guid GuidItem;
|
|
public IntPtr BalloonIcon;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct Point { public int X; public int Y; }
|
|
|
|
private delegate IntPtr WindowProcedure(IntPtr window, uint message, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern ushort RegisterClassEx(ref WindowClass windowClass);
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr CreateWindowEx(uint extendedStyle, string className, string windowName, uint style, int x, int y, int width, int height, IntPtr parent, IntPtr menu, IntPtr instance, IntPtr parameter);
|
|
[DllImport("user32.dll")] private static extern IntPtr DefWindowProc(IntPtr window, uint message, IntPtr wParam, IntPtr lParam);
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr LoadImage(IntPtr instance, string name, uint type, int width, int height, uint load);
|
|
[DllImport("user32.dll")] private static extern bool DestroyIcon(IntPtr icon);
|
|
[DllImport("user32.dll")] private static extern bool DestroyWindow(IntPtr window);
|
|
[DllImport("user32.dll")] private static extern IntPtr CreatePopupMenu();
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool AppendMenu(IntPtr menu, uint flags, uint id, string? text);
|
|
[DllImport("user32.dll")] private static extern uint TrackPopupMenu(IntPtr menu, uint flags, int x, int y, int reserved, IntPtr window, IntPtr rectangle);
|
|
[DllImport("user32.dll")] private static extern bool DestroyMenu(IntPtr menu);
|
|
[DllImport("user32.dll")] private static extern bool GetCursorPos(out Point point);
|
|
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr window);
|
|
[DllImport("shell32.dll", CharSet = CharSet.Unicode)] private static extern bool Shell_NotifyIcon(uint message, ref NotifyIconData data);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string? moduleName);
|
|
|
|
private static bool ShellNotifyIcon(uint message, ref NotifyIconData data) => Shell_NotifyIcon(message, ref data);
|
|
}
|