XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

LumaTunnel

【WinUI】LumaTunnel 是一个面向个人多设备的 Windows 代理系统。客户端在本机提供 HTTP/HTTPS CONNECT 与 SOCKS5 TCP 代理,并通过一个受信任 TLS 证书保护的 WSS 会话,将多个 TCP 流复用到自建 Windows Server 节点。

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.InteropServices;
using LumaTunnel.Client.Interface;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using XFEExtension.NetCore.WinUIHelper.Implements.Services;

namespace LumaTunnel.Client.Implements;

public sealed class TrayService : GlobalServiceBase, ITrayService
{
    private const uint CallbackMessage = 0x8001;
    private const uint LeftDoubleClick = 0x0203;
    private readonly ManualResetEventSlim _ready = new();
    private Thread? _thread;
    private IntPtr _window;
    private IntPtr _icon;
    private WndProc? _wndProc;
    private DispatcherQueue? _dispatcher;
    private Action? _showWindow;

    public void Initialize(Window window, Action showWindow)
    {
        if (_thread is not null)
            return;
        _dispatcher = window.DispatcherQueue;
        _showWindow = showWindow;
        _thread = new Thread(MessageLoop) { IsBackground = true, Name = "LumaTunnel.Tray" };
        _thread.Start();
        _ready.Wait(TimeSpan.FromSeconds(3));
    }

    public void Dispose()
    {
        if (_window != IntPtr.Zero)
            PostMessage(_window, 0x0010, IntPtr.Zero, IntPtr.Zero);
        _thread?.Join(TimeSpan.FromSeconds(2));
        _ready.Dispose();
        GC.SuppressFinalize(this);
    }

    private void MessageLoop()
    {
        _wndProc = WindowProcedure;
        var className = "LumaTunnelTrayWindow." + Environment.ProcessId;
        var windowClass = new WindowClass
        {
            ClassName = className,
            Instance = GetModuleHandle(null),
            WindowProcedure = Marshal.GetFunctionPointerForDelegate(_wndProc)
        };
        RegisterClass(ref windowClass);
        _window = CreateWindowEx(0, className, className, 0, 0, 0, 0, 0, new IntPtr(-3), IntPtr.Zero, windowClass.Instance, IntPtr.Zero);
        _icon = LoadImage(IntPtr.Zero, Path.Combine(AppContext.BaseDirectory, "Assets", "LumaTunnel.ico"), 1, 0, 0, 0x0010 | 0x0040);
        var data = CreateNotifyData(0);
        ShellNotifyIcon(0, ref data);
        _ready.Set();
        while (GetMessage(out var message, IntPtr.Zero, 0, 0) > 0)
        {
            TranslateMessage(ref message);
            DispatchMessage(ref message);
        }
        data = CreateNotifyData(0);
        ShellNotifyIcon(2, ref data);
        if (_icon != IntPtr.Zero) DestroyIcon(_icon);
        _window = IntPtr.Zero;
    }

    private IntPtr WindowProcedure(IntPtr window, uint message, IntPtr wParam, IntPtr lParam)
    {
        if (message == CallbackMessage && unchecked((uint)lParam.ToInt64()) == LeftDoubleClick)
            _dispatcher?.TryEnqueue(() => _showWindow?.Invoke());
        if (message == 0x0002)
            PostQuitMessage(0);
        return DefWindowProc(window, message, wParam, lParam);
    }

    private NotifyIconData CreateNotifyData(uint flags) => new()
    {
        Size = Marshal.SizeOf<NotifyIconData>(),
        Window = _window,
        Id = 1,
        Flags = flags == 0 ? 0x1u | 0x2u | 0x4u : flags,
        CallbackMessage = CallbackMessage,
        Icon = _icon,
        Tip = "LumaTunnel 光隧",
        Info = string.Empty,
        InfoTitle = string.Empty
    };

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct WindowClass
    {
        public uint Style;
        public IntPtr WindowProcedure;
        public int ClassExtra;
        public int WindowExtra;
        public IntPtr Instance;
        public IntPtr Icon;
        public IntPtr Cursor;
        public IntPtr Background;
        [MarshalAs(UnmanagedType.LPWStr)] public string? MenuName;
        [MarshalAs(UnmanagedType.LPWStr)] public string ClassName;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct NotifyIconData
    {
        public int Size;
        public IntPtr Window;
        public uint Id;
        public uint Flags;
        public uint CallbackMessage;
        public IntPtr Icon;
        [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 Item;
        public IntPtr BalloonIcon;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct Message { public IntPtr Window; public uint Value; public UIntPtr WParam; public IntPtr LParam; public uint Time; public int X; public int Y; }
    private delegate IntPtr WndProc(IntPtr window, uint message, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern ushort RegisterClass(ref WindowClass windowClass);
    [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr CreateWindowEx(uint exStyle, 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")] private static extern int GetMessage(out Message message, IntPtr window, uint min, uint max);
    [DllImport("user32.dll")] private static extern bool TranslateMessage(ref Message message);
    [DllImport("user32.dll")] private static extern IntPtr DispatchMessage(ref Message message);
    [DllImport("user32.dll")] private static extern void PostQuitMessage(int exitCode);
    [DllImport("user32.dll")] private static extern bool PostMessage(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("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string? moduleName);
    [DllImport("shell32.dll", CharSet = CharSet.Unicode)] private static extern bool ShellNotifyIcon(uint message, ref NotifyIconData data);
}