A:添加底部ICON通知

This commit is contained in:
jimmy committed 2025-07-25 15:53:30 +08:00
1 parent efc298e148
commit 8b8b695f0d
13 files changed
+798 -71

No files matched your search

@@ -151,7 +151,7 @@ namespace Common.Utils
totalSpeed.UploadSpeed += speed.UploadSpeed;
}
Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
// Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
return totalSpeed;
}
@@ -113,7 +113,7 @@ namespace Common.Utils
// 跳过环回接口
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
{
Log.Info($"跳过环回接口: {ni.Name}");
//Log.Info($"跳过环回接口: {ni.Name}");
continue;
}
@@ -291,7 +291,7 @@ namespace Common.Utils
}
}
Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
// Log.Info($"总网速 - 下载: {totalSpeed.DownloadSpeedText}, 上传: {totalSpeed.UploadSpeedText}");
return totalSpeed;
}
@@ -308,4 +308,4 @@ namespace Common.Utils
#endregion
}
}
}
@@ -57,6 +57,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATraceEvent_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F4d_003F09175cdc_003FTraceEvent_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebSocketServer_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7d96aff8e344c1d9aa8042f229d493cae00_003Fc6_003F2f09550a_003FWebSocketServer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebView2_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8aca2f2c90e1454c825fc8b69c53a97e14268_003F06_003F22dc0e77_003FWebView2_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWindow_002Ecs_002Fl_003AC_0021_003FUsers_003Fyllyo_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd079e839ea2040f09d8282baf64f01edf61928_003Fab_003F96a4af57_003FWindow_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=f4a8c497_002D43fb_002D4de1_002D9ffa_002D84076d7db2c5/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;NUnit3x::B3F481C9-B351-449C-AE2B-CDEC29BD5DCC::net8.0::Common.Test.Tests.Test1&lt;/TestId&gt;&#xD;
+19 -7
View File
@@ -20,12 +20,16 @@ using Infrastructure.Models;
using Microsoft.Extensions.Logging;
using Serilog.Events;
using Shell;
using Shell.UserControls;
using Shell.Utils;
using Shell.Views;
using Utils.ETW.Etw;
using Utils.Firewall;
namespace NetVizor;
using System.Windows.Forms; // 引用 Windows Forms 命名空间
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
@@ -42,16 +46,20 @@ public partial class App : System.Windows.Application
// 启动服务(如 WebSocket、HTTP、端口监听等)
// StartMyServer();
// NetView
// var netView = new NetView();
// netView.Show();
Mouth();
// NetView 网络监看
var netView = new NetView();
netView.Show();
// Mouth();
// 网速监视
// var window = new NetView();
// window.Show();
// 设置应用程序在关闭最后一个窗口时不自动退出
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
}
private async void Mouth()
@@ -59,7 +67,7 @@ public partial class App : System.Windows.Application
var firewallService = new FirewallService();
// 防火墙状态
// var statusAsync = await firewallService.GetStatusAsync();
var rulesAsync =await firewallService.GetRulesAsync();
var rulesAsync = await firewallService.GetRulesAsync();
Console.WriteLine(JsonHelper.ToJson(rulesAsync));
}
@@ -405,6 +413,10 @@ public partial class App : System.Windows.Application
#endregion
#region 获取防火墙信息
#endregion
#region 取消订阅
server.Post("/api/unsubscribe", async (context) =>
@@ -516,4 +528,4 @@ public class SubscriptionAppInfo : SubscriptionInfo
// 再有,“协议和端口”中的协议类型也有很多,我们也要展示。比如它甚至能指定用户,可以选择用户和组。
// 所以基于上,我们几乎是重写了防火墙的页面。
// 我现在需要你基于这个能获取到的信息(可能有些我没提到),使用html完成一个防火墙的看板,要点击某个规则可以编辑,可以新增。
// 新增的窗口和交互都需要完成,数据的展示也需要完成。你用假数据替代数据填充即可,要求界面要现代化,美观,优雅
// 新增的窗口和交互都需要完成,数据的展示也需要完成。你用假数据替代数据填充即可,要求界面要现代化,美观,优雅
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+12
View File
@@ -6,14 +6,22 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms> <!-- 加上这句,才能正确启用 Forms 支持 -->
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Folder Include="Assets\Icons\"/>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3240.44" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.6.25358.103"/>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms"/>
</ItemGroup>
<ItemGroup>
@@ -30,6 +38,10 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="WinDivert.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Remove="Assets\Icons\app.ico"/>
<Content Include="Assets\Icons\app.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -8,7 +8,7 @@ namespace Shell.UserControls
/// <summary>
/// Interaction logic for NetworkMonitorView.xaml
/// </summary>
public partial class NetworkMonitorView : UserControl
public partial class NetworkMonitorView : System.Windows.Controls.UserControl
{
private NetworkMonitorViewModel _viewModel;
@@ -38,4 +38,4 @@ namespace Shell.UserControls
_viewModel?.Dispose();
}
}
}
}
@@ -0,0 +1,116 @@
<Window x:Class="Shell.UserControls.TrayMenuWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shell.UserControls"
mc:Ignorable="d"
Title="TrayMenu"
Height="200" Width="180"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
Topmost="True"
ShowInTaskbar="False"
ResizeMode="NoResize"
Deactivated="Window_Deactivated">
<Window.Resources>
<!-- 菜单项样式 -->
<Style x:Key="MenuItemStyle" TargetType="Border">
<Setter Property="Padding" Value="15,8" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#E3F2FD" />
</Trigger>
</Style.Triggers>
</Style>
<!-- 图标样式 -->
<Style x:Key="IconStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe MDL2 Assets" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Foreground" Value="#2196F3" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,10,0" />
</Style>
<!-- 文本样式 -->
<Style x:Key="TextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="#333333" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!-- 分隔线样式 -->
<Style x:Key="SeparatorStyle" TargetType="Rectangle">
<Setter Property="Height" Value="1" />
<Setter Property="Fill" Value="#E0E0E0" />
<Setter Property="Margin" Value="10,5" />
</Style>
</Window.Resources>
<Border Background="White"
CornerRadius="8"
BorderBrush="#E0E0E0"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect ShadowDepth="3"
Direction="315"
Color="#40000000"
Opacity="0.3"
BlurRadius="10" />
</Border.Effect>
<StackPanel Margin="5">
<!-- 打开主窗口 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ShowMainWindow_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE8A7;" Style="{StaticResource IconStyle}" />
<TextBlock Text="打开主窗口" Style="{StaticResource TextStyle}" />
</StackPanel>
</Border>
<!-- 分隔线 -->
<Rectangle Style="{StaticResource SeparatorStyle}" />
<!-- 最小化到托盘 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="HideToTray_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE921;" Style="{StaticResource IconStyle}" />
<TextBlock Text="最小化到托盘" Style="{StaticResource TextStyle}" />
</StackPanel>
</Border>
<!-- 关于 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="About_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE946;" Style="{StaticResource IconStyle}" />
<TextBlock Text="关于" Style="{StaticResource TextStyle}" />
</StackPanel>
</Border>
<!-- 分隔线 -->
<Rectangle Style="{StaticResource SeparatorStyle}" />
<!-- 退出程序 -->
<Border Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ExitApplication_Click">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE8BB;"
Style="{StaticResource IconStyle}"
Foreground="#F44336" />
<TextBlock Text="退出程序"
Style="{StaticResource TextStyle}"
Foreground="#F44336" />
</StackPanel>
</Border>
</StackPanel>
</Border>
</Window>
@@ -0,0 +1,256 @@
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Interop;
using System.Windows.Media;
namespace Shell.UserControls;
public partial class TrayMenuWindow : Window
{
public event Action OnShowMainWindow;
public event Action OnExitApplication;
public event Action OnHideToTray;
// 菜单的固定尺寸(与XAML中设置的一致)
private const double MENU_WIDTH = 180;
private const double MENU_HEIGHT = 200;
public TrayMenuWindow()
{
InitializeComponent();
this.Loaded += TrayMenuWindow_Loaded;
// 确保窗口使用Per-Monitor DPI Awareness
this.SourceInitialized += (s, e) =>
{
var hwnd = new WindowInteropHelper(this).Handle;
var source = HwndSource.FromHwnd(hwnd);
if (source != null)
{
source.AddHook(WndProc);
}
};
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// 处理DPI变化消息
const int WM_DPICHANGED = 0x02E0;
if (msg == WM_DPICHANGED)
{
// DPI发生变化时可以在这里处理
Console.WriteLine("DPI changed detected");
}
return IntPtr.Zero;
}
private void TrayMenuWindow_Loaded(object sender, RoutedEventArgs e)
{
// 添加淡入动画
// var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200));
// this.BeginAnimation(OpacityProperty, fadeIn);
// 添加缩放动画
// var scaleTransform = new System.Windows.Media.ScaleTransform(0.8, 0.8);
// this.RenderTransform = scaleTransform;
// this.RenderTransformOrigin = new System.Windows.Point(0.5, 0.5);
// var scaleXAnimation = new DoubleAnimation(0.8, 1, TimeSpan.FromMilliseconds(200));
// var scaleYAnimation = new DoubleAnimation(0.8, 1, TimeSpan.FromMilliseconds(200));
// scaleXAnimation.EasingFunction = new QuadraticEase();
// scaleYAnimation.EasingFunction = new QuadraticEase();
// scaleTransform.BeginAnimation(System.Windows.Media.ScaleTransform.ScaleXProperty, scaleXAnimation);
// scaleTransform.BeginAnimation(System.Windows.Media.ScaleTransform.ScaleYProperty, scaleYAnimation);
}
public void ShowAt(int x, int y)
{
// 使用主显示器工作区域的旧方法(保留向后兼容)
var workArea = SystemParameters.WorkArea;
var customWorkArea = new Shell.Utils.TrayIconManager.RECT
{
Left = (int)workArea.Left,
Top = (int)workArea.Top,
Right = (int)workArea.Right,
Bottom = (int)workArea.Bottom
};
ShowAtWithWorkArea(x, y, customWorkArea);
}
public void ShowAtWithWorkArea(int x, int y, Shell.Utils.TrayIconManager.RECT workArea)
{
Console.WriteLine("=== TrayMenu ShowAtWithWorkArea Debug ===");
Console.WriteLine($"输入鼠标位置: X={x}, Y={y}");
Console.WriteLine(
$"使用工作区域: Left={workArea.Left}, Top={workArea.Top}, Right={workArea.Right}, Bottom={workArea.Bottom}");
Console.WriteLine($"工作区域尺寸: Width={workArea.Right - workArea.Left}, Height={workArea.Bottom - workArea.Top}");
Console.WriteLine($"菜单尺寸: Width={MENU_WIDTH}, Height={MENU_HEIGHT}");
// 获取当前窗口的DPI缩放信息
var dpiScale = GetDpiScale();
Console.WriteLine($"窗口DPI缩放: X={dpiScale.DpiScaleX:F2}, Y={dpiScale.DpiScaleY:F2}");
// 计算菜单显示位置 - 优先显示在鼠标左上方
double left = x - MENU_WIDTH;
double top = y - MENU_HEIGHT;
Console.WriteLine($"初始计算位置 (左上方): Left={left}, Top={top}");
// 智能位置调整算法
var adjustedPosition = CalculateOptimalPosition(x, y, workArea, MENU_WIDTH, MENU_HEIGHT);
left = adjustedPosition.X;
top = adjustedPosition.Y;
Console.WriteLine($"智能调整后位置: Left={left}, Top={top}");
// 最终边界检查(确保完全在工作区域内)
left = Math.Max(workArea.Left, Math.Min(left, workArea.Right - MENU_WIDTH));
top = Math.Max(workArea.Top, Math.Min(top, workArea.Bottom - MENU_HEIGHT));
Console.WriteLine($"最终边界检查后位置: Left={left}, Top={top}");
// 设置窗口位置
this.Left = left;
this.Top = top;
// 显示窗口
this.Show();
this.Activate();
this.Focus();
// 验证实际设置的位置
Console.WriteLine($"窗口实际位置: Left={this.Left}, Top={this.Top}");
Console.WriteLine($"窗口实际尺寸: Width={this.ActualWidth}, Height={this.ActualHeight}");
Console.WriteLine("=== Debug End ===\n");
}
private (double X, double Y) CalculateOptimalPosition(int mouseX, int mouseY,
Shell.Utils.TrayIconManager.RECT workArea, double menuWidth, double menuHeight)
{
double left, top;
// 定义四个可能的位置:左上、右上、左下、右下
var positions = new[]
{
new
{
X = (double)(mouseX - menuWidth), Y = (double)(mouseY - menuHeight), Priority = 1, Name = "左上"
}, // 左上 (优先)
new { X = (double)mouseX, Y = (double)(mouseY - menuHeight), Priority = 2, Name = "右上" }, // 右上
new { X = (double)(mouseX - menuWidth), Y = (double)mouseY, Priority = 3, Name = "左下" }, // 左下
new { X = (double)mouseX, Y = (double)mouseY, Priority = 4, Name = "右下" } // 右下
};
// 检查每个位置是否完全在工作区域内
foreach (var pos in positions.OrderBy(p => p.Priority))
{
bool fitsHorizontally = pos.X >= workArea.Left && pos.X + menuWidth <= workArea.Right;
bool fitsVertically = pos.Y >= workArea.Top && pos.Y + menuHeight <= workArea.Bottom;
Console.WriteLine(
$"检查位置 {pos.Name}: X={pos.X:F1}, Y={pos.Y:F1}, 水平适合={fitsHorizontally}, 垂直适合={fitsVertically}");
if (fitsHorizontally && fitsVertically)
{
Console.WriteLine($"选择位置: {pos.Name}");
return (pos.X, pos.Y);
}
}
// 如果没有位置完全适合,使用智能调整
Console.WriteLine("没有完全适合的位置,使用智能调整");
// 计算最佳的水平位置
if (mouseX - menuWidth >= workArea.Left)
{
left = mouseX - menuWidth; // 左侧有足够空间
}
else if (mouseX + menuWidth <= workArea.Right)
{
left = mouseX; // 右侧有足够空间
}
else
{
// 两侧都不够,居中显示或贴边
left = Math.Max(workArea.Left, workArea.Right - menuWidth);
}
// 计算最佳的垂直位置
if (mouseY - menuHeight >= workArea.Top)
{
top = mouseY - menuHeight; // 上方有足够空间
}
else if (mouseY + menuHeight <= workArea.Bottom)
{
top = mouseY; // 下方有足够空间
}
else
{
// 上下都不够,贴顶部或底部
top = Math.Max(workArea.Top, workArea.Bottom - menuHeight);
}
Console.WriteLine($"智能调整结果: Left={left:F1}, Top={top:F1}");
return (left, top);
}
private System.Windows.DpiScale GetDpiScale()
{
try
{
var source = PresentationSource.FromVisual(this);
if (source?.CompositionTarget != null)
{
return VisualTreeHelper.GetDpi(this);
}
}
catch (Exception ex)
{
Console.WriteLine($"获取DPI缩放失败: {ex.Message}");
}
// 返回默认缩放
return new System.Windows.DpiScale(1.0, 1.0);
}
private void ShowMainWindow_Click(object sender, MouseButtonEventArgs e)
{
OnShowMainWindow?.Invoke();
this.Hide();
}
private void HideToTray_Click(object sender, MouseButtonEventArgs e)
{
OnHideToTray?.Invoke();
this.Hide();
}
private void About_Click(object sender, MouseButtonEventArgs e)
{
System.Windows.MessageBox.Show("这是一个带有系统托盘功能的WPF应用程序。", "关于",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
this.Hide();
}
private void ExitApplication_Click(object sender, MouseButtonEventArgs e)
{
var result = System.Windows.MessageBox.Show("确定要退出程序吗?", "确认退出",
System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question);
if (result == System.Windows.MessageBoxResult.Yes)
{
OnExitApplication?.Invoke();
}
this.Hide();
}
private void Window_Deactivated(object sender, EventArgs e)
{
// 当窗口失去焦点时自动隐藏
this.Hide();
}
}
@@ -9,7 +9,7 @@ using Infrastructure.utils;
namespace Shell.UserControls;
public partial class WebPanel : UserControl
public partial class WebPanel : System.Windows.Controls.UserControl
{
public WebPanel()
{
@@ -87,7 +87,7 @@ public partial class WebPanel : UserControl
switch (msg?.Channel)
{
case "showMessage":
MessageBox.Show(msg.Payload?.Content ?? "无内容", msg.Payload?.Title ?? "提示");
System.Windows.MessageBox.Show(msg.Payload?.Content ?? "无内容", msg.Payload?.Title ?? "提示");
InitWebView1();
break;
case "GetNetInfo":
@@ -175,4 +175,4 @@ public class JsData
{
return $"Title:{Title}, Content:{Content}";
}
}
}
@@ -0,0 +1,299 @@
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using Shell.UserControls;
namespace Shell.Utils;
public class TrayIconManager : IDisposable
{
private NotifyIcon _notifyIcon;
private Window _mainWindow;
private TrayMenuWindow _trayMenu;
private DispatcherTimer _clickTimer;
private bool _isDoubleClick = false;
// Win32 API 用于获取鼠标位置和显示器信息
[DllImport("user32.dll")]
private static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll")]
private static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags);
[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
// 添加DPI相关API
[DllImport("user32.dll")]
private static extern uint GetDpiForWindow(IntPtr hwnd);
[DllImport("user32.dll")]
private static extern uint GetDpiForSystem();
[DllImport("shcore.dll")]
private static extern int GetDpiForMonitor(IntPtr hmonitor, DpiType dpiType, out uint dpiX, out uint dpiY);
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public uint cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
private const uint MONITOR_DEFAULTTONEAREST = 2;
public TrayIconManager(Window mainWindow)
{
_mainWindow = mainWindow;
InitializeTrayIcon();
InitializeClickTimer();
}
private void InitializeTrayIcon()
{
_notifyIcon = new NotifyIcon
{
Icon = LoadIcon(),
Visible = true,
Text = "你的应用程序"
};
_notifyIcon.MouseClick += OnTrayIconClick;
_notifyIcon.MouseDoubleClick += OnTrayIconDoubleClick;
}
private void InitializeClickTimer()
{
_clickTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(300)
};
_clickTimer.Tick += OnClickTimerTick;
}
private Icon LoadIcon()
{
try
{
return new Icon("Assets/Icons/app.ico");
}
catch
{
return SystemIcons.Application;
}
}
private void OnTrayIconClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
try
{
ShowContextMenu();
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
}
else if (e.Button == MouseButtons.Left)
{
_isDoubleClick = false;
_clickTimer.Start();
}
}
private void OnTrayIconDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_isDoubleClick = true;
_clickTimer.Stop();
ShowMainWindow();
}
}
private void OnClickTimerTick(object sender, EventArgs e)
{
_clickTimer.Stop();
if (!_isDoubleClick)
{
// 单击逻辑(可选)
}
}
private void ShowMainWindow()
{
if (_mainWindow != null)
{
_mainWindow.Show();
_mainWindow.WindowState = WindowState.Normal;
_mainWindow.Activate();
_mainWindow.Focus();
}
}
private void ShowContextMenu()
{
Console.WriteLine("=== TrayIconManager ShowContextMenu ===");
if (_trayMenu == null)
{
_trayMenu = new TrayMenuWindow();
_trayMenu.OnShowMainWindow += () => ShowMainWindow();
_trayMenu.OnExitApplication += () => System.Windows.Application.Current.Shutdown();
_trayMenu.OnHideToTray += () => _mainWindow?.Hide();
Console.WriteLine("创建新的TrayMenuWindow");
}
// 使用Win32 API获取鼠标位置
if (GetCursorPos(out POINT mousePos))
{
Console.WriteLine($"Win32 API获取鼠标物理位置: X={mousePos.X}, Y={mousePos.Y}");
// 获取鼠标所在显示器的工作区域和DPI信息
var (workArea, dpiScaleX, dpiScaleY) = GetMouseMonitorInfoWithDpi(mousePos);
Console.WriteLine(
$"显示器工作区域: Left={workArea.Left}, Top={workArea.Top}, Right={workArea.Right}, Bottom={workArea.Bottom}");
Console.WriteLine($"DPI缩放比例: ScaleX={dpiScaleX:F2}, ScaleY={dpiScaleY:F2}");
// 将物理坐标转换为WPF逻辑坐标
var logicalMouseX = mousePos.X / dpiScaleX;
var logicalMouseY = mousePos.Y / dpiScaleY;
// 将工作区域也转换为逻辑坐标
var logicalWorkArea = new RECT
{
Left = (int)(workArea.Left / dpiScaleX),
Top = (int)(workArea.Top / dpiScaleY),
Right = (int)(workArea.Right / dpiScaleX),
Bottom = (int)(workArea.Bottom / dpiScaleY)
};
Console.WriteLine($"逻辑鼠标位置: X={logicalMouseX:F1}, Y={logicalMouseY:F1}");
Console.WriteLine(
$"逻辑工作区域: Left={logicalWorkArea.Left}, Top={logicalWorkArea.Top}, Right={logicalWorkArea.Right}, Bottom={logicalWorkArea.Bottom}");
_trayMenu.ShowAtWithWorkArea((int)logicalMouseX, (int)logicalMouseY, logicalWorkArea);
}
else
{
Console.WriteLine("获取鼠标位置失败,使用默认位置");
// 如果获取鼠标位置失败,使用托盘图标的估计位置
var workArea = SystemParameters.WorkArea;
var defaultX = (int)workArea.Right - 50;
var defaultY = (int)workArea.Bottom - 50;
Console.WriteLine($"使用默认位置: X={defaultX}, Y={defaultY}");
_trayMenu.ShowAt(defaultX, defaultY);
}
Console.WriteLine("=== ShowContextMenu End ===\n");
}
private (RECT workArea, double dpiScaleX, double dpiScaleY) GetMouseMonitorInfoWithDpi(POINT mousePos)
{
// 获取鼠标所在的显示器
IntPtr hMonitor = MonitorFromPoint(mousePos, MONITOR_DEFAULTTONEAREST);
// 获取显示器信息
MONITORINFO monitorInfo = new MONITORINFO();
monitorInfo.cbSize = (uint)Marshal.SizeOf(monitorInfo);
RECT workArea;
double dpiScaleX = 1.0;
double dpiScaleY = 1.0;
if (GetMonitorInfo(hMonitor, ref monitorInfo))
{
workArea = monitorInfo.rcWork;
// 获取该显示器的DPI
try
{
if (GetDpiForMonitor(hMonitor, DpiType.Effective, out uint dpiX, out uint dpiY) == 0)
{
dpiScaleX = dpiX / 96.0; // 96 DPI是标准DPI
dpiScaleY = dpiY / 96.0;
Console.WriteLine($"显示器DPI: X={dpiX}, Y={dpiY}");
}
else
{
// 如果GetDpiForMonitor失败,尝试使用系统DPI
var systemDpi = GetDpiForSystem();
dpiScaleX = dpiScaleY = systemDpi / 96.0;
Console.WriteLine($"使用系统DPI: {systemDpi}");
}
}
catch (Exception ex)
{
Console.WriteLine($"获取DPI失败: {ex.Message},使用默认缩放");
// 作为后备方案,使用WPF的DPI信息
var source = PresentationSource.FromVisual(System.Windows.Application.Current.MainWindow);
if (source?.CompositionTarget != null)
{
var matrix = source.CompositionTarget.TransformFromDevice;
dpiScaleX = 1.0 / matrix.M11;
dpiScaleY = 1.0 / matrix.M22;
}
}
}
else
{
// 如果获取失败,返回主显示器的工作区域
var wpfWorkArea = SystemParameters.WorkArea;
workArea = new RECT
{
Left = (int)wpfWorkArea.Left,
Top = (int)wpfWorkArea.Top,
Right = (int)wpfWorkArea.Right,
Bottom = (int)wpfWorkArea.Bottom
};
// 使用WPF的DPI信息作为后备
var source = PresentationSource.FromVisual(System.Windows.Application.Current.MainWindow);
if (source?.CompositionTarget != null)
{
var matrix = source.CompositionTarget.TransformFromDevice;
dpiScaleX = 1.0 / matrix.M11;
dpiScaleY = 1.0 / matrix.M22;
}
}
return (workArea, dpiScaleX, dpiScaleY);
}
public void Dispose()
{
_notifyIcon?.Dispose();
_trayMenu?.Close();
_clickTimer?.Stop();
}
}
@@ -1,11 +1,41 @@
using System.ComponentModel;
using System.Windows;
using Shell.Utils;
namespace Shell.Views;
public partial class NetView : Window
{
private TrayIconManager _trayIconManager;
public NetView()
{
InitializeComponent();
InitializeTrayIcon();
}
}
private void InitializeTrayIcon()
{
_trayIconManager = new TrayIconManager(this);
}
protected override void OnClosing(CancelEventArgs e)
{
// 阻止窗口关闭,而是隐藏到托盘
e.Cancel = true;
this.Hide();
}
protected override void OnClosed(EventArgs e)
{
_trayIconManager?.Dispose();
base.OnClosed(e);
}
// 如果你想要真正关闭程序的方法
public void ForceClose()
{
_trayIconManager?.Dispose();
System.Windows.Application.Current.Shutdown();
}
}
@@ -15,7 +15,7 @@ public class WindowsFirewallApi : IFirewallApi
/// Windows防火墙策略对象,用于访问防火墙的全局设置和状态
/// </summary>
private readonly INetFwPolicy2? _fwPolicy;
/// <summary>
/// Windows防火墙规则集合对象,用于管理防火墙规则
/// </summary>
@@ -73,9 +73,9 @@ public class WindowsFirewallApi : IFirewallApi
// 获取规则统计信息
var allRules = GetAllRules();
status.TotalRules = allRules.Count; // 总规则数
status.EnabledRules = allRules.Count(r => r.Enabled); // 已启用规则数
status.InboundRules = allRules.Count(r => r.Direction == RuleDirection.Inbound); // 入站规则数
status.TotalRules = allRules.Count; // 总规则数
status.EnabledRules = allRules.Count(r => r.Enabled); // 已启用规则数
status.InboundRules = allRules.Count(r => r.Direction == RuleDirection.Inbound); // 入站规则数
status.OutboundRules = allRules.Count(r => r.Direction == RuleDirection.Outbound); // 出站规则数
return status;
@@ -164,12 +164,12 @@ public class WindowsFirewallApi : IFirewallApi
return new ProfileStatus
{
Profile = profile,
IsEnabled = _fwPolicy.FirewallEnabled[profileType], // 是否启用
BlockAllInboundTraffic = _fwPolicy.BlockAllInboundTraffic[profileType], // 是否阻止所有入站流量
NotifyOnListen = _fwPolicy.NotificationsDisabled[profileType] == false, // 是否显示通知
IsEnabled = _fwPolicy.FirewallEnabled[profileType], // 是否启用
BlockAllInboundTraffic = _fwPolicy.BlockAllInboundTraffic[profileType], // 是否阻止所有入站流量
NotifyOnListen = _fwPolicy.NotificationsDisabled[profileType] == false, // 是否显示通知
UnicastResponsesDisabled = _fwPolicy.UnicastResponsesToMulticastBroadcastDisabled[profileType], // 单播响应设置
DefaultInboundAction = (RuleAction)_fwPolicy.DefaultInboundAction[profileType], // 默认入站动作
DefaultOutboundAction = (RuleAction)_fwPolicy.DefaultOutboundAction[profileType] // 默认出站动作
DefaultInboundAction = (RuleAction)_fwPolicy.DefaultInboundAction[profileType], // 默认入站动作
DefaultOutboundAction = (RuleAction)_fwPolicy.DefaultOutboundAction[profileType] // 默认出站动作
};
}
@@ -186,7 +186,7 @@ public class WindowsFirewallApi : IFirewallApi
if (_fwPolicy == null) return false;
var profileType = ConvertToNetFwProfile(profile);
// 设置各项配置
_fwPolicy.FirewallEnabled[profileType] = status.IsEnabled;
_fwPolicy.BlockAllInboundTraffic[profileType] = status.BlockAllInboundTraffic;
@@ -194,7 +194,7 @@ public class WindowsFirewallApi : IFirewallApi
_fwPolicy.UnicastResponsesToMulticastBroadcastDisabled[profileType] = status.UnicastResponsesDisabled;
_fwPolicy.DefaultInboundAction[profileType] = (NET_FW_ACTION_)status.DefaultInboundAction;
_fwPolicy.DefaultOutboundAction[profileType] = (NET_FW_ACTION_)status.DefaultOutboundAction;
return true;
}
catch
@@ -219,7 +219,8 @@ public class WindowsFirewallApi : IFirewallApi
// 遍历Windows防火墙中的所有规则并转换为自定义格式
foreach (INetFwRule rule in _fwRules)
{
rules.Add(ConvertToFirewallRule(rule));
var convertToFirewallRule = ConvertToFirewallRule(rule);
rules.Add(convertToFirewallRule);
}
return rules;
@@ -358,22 +359,22 @@ public class WindowsFirewallApi : IFirewallApi
if (newRule == null) return false;
// 设置规则的各项属性
newRule.Name = request.Name; // 规则名称
newRule.Description = request.Description; // 规则描述
newRule.ApplicationName = request.ApplicationName; // 应用程序路径
newRule.Name = request.Name; // 规则名称
newRule.Description = request.Description; // 规则描述
newRule.ApplicationName = request.ApplicationName; // 应用程序路径
// 注意:INetFwRule 接口可能没有 ServiceName 属性,这里移除了对它的直接赋值
newRule.Protocol = (int)request.Protocol; // 协议类型
newRule.LocalPorts = request.LocalPorts; // 本地端口
newRule.RemotePorts = request.RemotePorts; // 远程端口
newRule.LocalAddresses = request.LocalAddresses; // 本地地址
newRule.RemoteAddresses = request.RemoteAddresses; // 远程地址
newRule.IcmpTypesAndCodes = request.IcmpTypesAndCodes; // ICMP类型和代码
newRule.Direction = (NET_FW_RULE_DIRECTION_)request.Direction; // 流量方向
newRule.Enabled = request.Enabled; // 是否启用
newRule.Profiles = ConvertToNetFwProfiles(request.Profiles); // 适用的配置文件
newRule.Action = (NET_FW_ACTION_)request.Action; // 规则动作
newRule.Grouping = request.Grouping; // 规则组
newRule.InterfaceTypes = request.InterfaceTypes; // 接口类型
newRule.Protocol = (int)request.Protocol; // 协议类型
newRule.LocalPorts = request.LocalPorts; // 本地端口
newRule.RemotePorts = request.RemotePorts; // 远程端口
newRule.LocalAddresses = request.LocalAddresses; // 本地地址
newRule.RemoteAddresses = request.RemoteAddresses; // 远程地址
newRule.IcmpTypesAndCodes = request.IcmpTypesAndCodes; // ICMP类型和代码
newRule.Direction = (NET_FW_RULE_DIRECTION_)request.Direction; // 流量方向
newRule.Enabled = request.Enabled; // 是否启用
newRule.Profiles = ConvertToNetFwProfiles(request.Profiles); // 适用的配置文件
newRule.Action = (NET_FW_ACTION_)request.Action; // 规则动作
newRule.Grouping = request.Grouping; // 规则组
newRule.InterfaceTypes = request.InterfaceTypes; // 接口类型
// 设置边缘遍历(用于某些特殊网络场景)
if (request.EdgeTraversal)
@@ -469,7 +470,7 @@ public class WindowsFirewallApi : IFirewallApi
{
// 先获取组中的所有规则
var rules = GetRulesByGroup(groupName);
// 逐一删除组中的每个规则
foreach (var rule in rules)
{
@@ -620,14 +621,14 @@ public class WindowsFirewallApi : IFirewallApi
try
{
var rules = GetAllRules();
// 配置JSON序列化选项,包括缩进和枚举转换
var json = JsonSerializer.Serialize(rules, new JsonSerializerOptions
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter() }
});
System.IO.File.WriteAllText(filePath, json);
return true;
}
@@ -647,7 +648,7 @@ public class WindowsFirewallApi : IFirewallApi
try
{
var json = System.IO.File.ReadAllText(filePath);
// 反序列化JSON文件中的规则
var rules = JsonSerializer.Deserialize<List<FirewallRule>>(json, new JsonSerializerOptions
{
@@ -700,9 +701,9 @@ public class WindowsFirewallApi : IFirewallApi
var rules = GetAllRules();
var stats = new FirewallStatistics
{
TotalRules = rules.Count, // 总规则数
EnabledRules = rules.Count(r => r.Enabled), // 已启用规则数
DisabledRules = rules.Count(r => !r.Enabled) // 已禁用规则数
TotalRules = rules.Count, // 总规则数
EnabledRules = rules.Count(r => r.Enabled), // 已启用规则数
DisabledRules = rules.Count(r => !r.Enabled) // 已禁用规则数
};
// 按流量方向统计规则数量
@@ -795,7 +796,7 @@ public class WindowsFirewallApi : IFirewallApi
public bool BlockApplication(string applicationPath)
{
var fileName = System.IO.Path.GetFileName(applicationPath);
// 创建入站阻止规则
var blockInbound = CreateRule(new CreateRuleRequest
{
@@ -1043,25 +1044,25 @@ public class WindowsFirewallApi : IFirewallApi
{
return new FirewallRule
{
Name = rule.Name ?? string.Empty, // 规则名称
Description = rule.Description ?? string.Empty, // 规则描述
ApplicationName = rule.ApplicationName ?? string.Empty, // 应用程序路径
Name = rule.Name ?? string.Empty, // 规则名称
Description = rule.Description ?? string.Empty, // 规则描述
ApplicationName = rule.ApplicationName ?? string.Empty, // 应用程序路径
// ServiceName 在某些版本的 INetFwRule 中可能不存在,使用默认值
ServiceName = string.Empty, // 服务名称
Protocol = (ProtocolType)rule.Protocol, // 协议类型
LocalPorts = rule.LocalPorts ?? string.Empty, // 本地端口
RemotePorts = rule.RemotePorts ?? string.Empty, // 远程端口
LocalAddresses = rule.LocalAddresses ?? string.Empty, // 本地地址
RemoteAddresses = rule.RemoteAddresses ?? string.Empty, // 远程地址
IcmpTypesAndCodes = rule.IcmpTypesAndCodes ?? string.Empty, // ICMP类型和代码
Direction = (RuleDirection)rule.Direction, // 流量方向
Enabled = rule.Enabled, // 是否启用
Profiles = ConvertFromNetFwProfiles(rule.Profiles), // 适用的配置文件
EdgeTraversal = rule.EdgeTraversal, // 边缘遍历设置
Action = (RuleAction)rule.Action, // 规则动作
Grouping = rule.Grouping ?? string.Empty, // 规则组
InterfaceTypes = rule.InterfaceTypes ?? string.Empty, // 接口类型
Interfaces = ParseInterfaces(rule.Interfaces) // 接口列表
ServiceName = string.Empty, // 服务名称
Protocol = (ProtocolType)rule.Protocol, // 协议类型
LocalPorts = rule.LocalPorts ?? string.Empty, // 本地端口
RemotePorts = rule.RemotePorts ?? string.Empty, // 远程端口
LocalAddresses = rule.LocalAddresses ?? string.Empty, // 本地地址
RemoteAddresses = rule.RemoteAddresses ?? string.Empty, // 远程地址
IcmpTypesAndCodes = rule.IcmpTypesAndCodes ?? string.Empty, // ICMP类型和代码
Direction = (RuleDirection)rule.Direction, // 流量方向
Enabled = rule.Enabled, // 是否启用
Profiles = ConvertFromNetFwProfiles(rule.Profiles), // 适用的配置文件
EdgeTraversal = rule.EdgeTraversal, // 边缘遍历设置
Action = (RuleAction)rule.Action, // 规则动作
Grouping = rule.Grouping ?? string.Empty, // 规则组
InterfaceTypes = rule.InterfaceTypes ?? string.Empty, // 接口类型
Interfaces = ParseInterfaces(rule.Interfaces) // 接口列表
};
}
@@ -1137,4 +1138,4 @@ public class WindowsFirewallApi : IFirewallApi
}
#endregion
}
}