using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Threading;
namespace Ink_Canvas.Helpers
{
///
/// 悬浮窗拦截器 - 检测和隐藏指定的悬浮窗
///
public class FloatingWindowInterceptor : IDisposable
{
#region Windows API Declarations
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll")]
private static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("dwmapi.dll")]
private static extern int DwmGetWindowAttribute(IntPtr hWnd, int dwAttribute, out RECT pvAttribute, int cbAttribute);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern bool QueryFullProcessImageNameW(IntPtr hProcess, uint dwFlags, StringBuilder lpExeName, ref uint lpdwSize);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left, Top, Right, Bottom;
}
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
private const int SW_HIDE = 0;
private const int SW_SHOWNOACTIVATE = 4;
private const int GWL_STYLE = -16;
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_NOACTIVATE = 0x0010;
private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
private const uint WM_CLOSE = 0x0010;
private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
private const int LOGPIXELSX = 88;
private const int LOGPIXELSY = 90;
private const int SM_CXSCREEN = 0;
private const int SM_CYSCREEN = 1;
#endregion
#region 拦截规则定义
public enum InterceptType
{
// 希沃系列
SeewoWhiteboard3Floating,
SeewoWhiteboard5Floating,
SeewoWhiteboard5CFloating,
SeewoPincoSideBarFloating,
SeewoPincoDrawingFloating,
SeewoPincoBoardService,
SeewoPPTFloating,
SeewoIwbAssistantFloating,
SeewoDesktopSideBarFloating,
SeewoDesktopDrawingFloating,
// 欧帝
YiouBoardFloating,
// AiClass
AiClassFloating,
// ClassIn X
ClassInXFloating,
// 鸿合
HiteAnnotationFloating,
// 畅言4.0
ChangYanFloating,
ChangYanBrushSettings,
ChangYanSwipeClear,
ChangYanInteraction,
ChangYanSubjectApp,
ChangYanControl,
ChangYanCommonTools,
ChangYanSceneToolbar,
ChangYanDrawWindow,
ChangYanPptFloating,
ChangYanPptPageControl,
ChangYanPptGoBack,
ChangYanPptPreview,
// 畅言5.0
ChangYan5Floating,
// 天喻
IntelligentClassFloating,
IntelligentClassPptFloating,
// C30智能教学
Iclass30SidebarFloating,
Iclass30Floating,
// 保留(已被SeewoDesktopDrawingFloating替代,但保持向后兼容)
SeewoDesktopAnnotationFloating,
}
public enum StyleMatchType { Exact, Subset }
public enum SizeMatchType { None, Exact, DPIScale, Scale, FullScreen, FullHeight, FullWidth }
public class InterceptRule
{
public InterceptType Type { get; set; }
public string ProcessName { get; set; }
public string WindowTitlePattern { get; set; }
public bool TitleIsRegex { get; set; } = false;
public string ClassNamePattern { get; set; }
public bool ClassNameIsRegex { get; set; } = true;
public bool IsEnabled { get; set; }
public bool RequiresAdmin { get; set; }
public string Description { get; set; }
public InterceptType? ParentType { get; set; }
public List ChildTypes { get; set; } = new List();
public bool HasWindowStyle { get; set; }
public uint WindowStyle { get; set; }
public StyleMatchType StyleMatchType { get; set; } = StyleMatchType.Exact;
public SizeMatchType SizeMatchType { get; set; } = SizeMatchType.None;
public int WindowWidth { get; set; }
public int WindowHeight { get; set; }
// Action type (Close vs Hide vs Move)
public InterceptActionType ActionType { get; set; } = InterceptActionType.Hide;
// 运行时扫描状态(每次扫描重置)
public bool foundHwnd { get; set; } = false;
public IntPtr outHwnd { get; set; } = IntPtr.Zero;
}
public enum InterceptActionType { Hide, Close, Move }
#endregion
#region 私有字段
private readonly Dictionary _interceptRules;
private readonly Dictionary _interceptedWindows;
private readonly Dictionary _movedWindowPositions;
private readonly Timer _scanTimer;
private readonly Dispatcher _dispatcher;
private bool _isRunning;
private bool _disposed;
private int _consecutiveEmptyScans = 0;
private DateTime _lastSuccessfulScan = DateTime.Now;
#endregion
#region 公共属性
public bool IsRunning => _isRunning;
#endregion
#region 事件
public event EventHandler WindowIntercepted;
public event EventHandler WindowRestored;
#endregion
#region 构造函数
public FloatingWindowInterceptor()
{
_interceptRules = new Dictionary();
_interceptedWindows = new Dictionary();
_movedWindowPositions = new Dictionary();
_dispatcher = Dispatcher.CurrentDispatcher;
InitializeRules();
_scanTimer = new Timer(ScanForWindows, null, Timeout.Infinite, Timeout.Infinite);
}
#endregion
#region 初始化规则
private void InitializeRules()
{
// ─── 希沃白板3 ───────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoWhiteboard3Floating,
ProcessName = "EasiNote.exe",
WindowTitlePattern = "^Note$",
TitleIsRegex = true,
ClassNamePattern = @"HwndWrapper\[EasiNote\.exe;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16CF0000, // WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_SYSMENU|WS_THICKFRAME|WS_GROUP|WS_TABSTOP|WS_MINIMIZEBOX|WS_MAXIMIZEBOX
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "希沃白板3 桌面悬浮窗"
});
// ─── 希沃白板5 ───────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoWhiteboard5Floating,
ProcessName = "EasiNote.exe",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[EasiNote;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000, // WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_SYSMENU
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "希沃白板5 桌面悬浮窗"
});
// ─── 希沃白板5C ──────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoWhiteboard5CFloating,
ProcessName = "EasiNote5C.exe",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[EasiNote5C;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "希沃白板5C 桌面悬浮窗"
});
// ─── 希沃品课 侧栏(父规则)────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoPincoSideBarFloating,
ProcessName = "seewoPincoTeacher.exe",
WindowTitlePattern = "^希沃品课——appBar$",
TitleIsRegex = true,
ClassNamePattern = "^Chrome_WidgetWin_1$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x04440000, // WS_CLIPSIBLINGS|WS_GROUP|WS_MINIMIZEBOX
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.None,
ActionType = InterceptActionType.Close,
IsEnabled = true,
Description = "希沃品课教师端 侧栏悬浮窗",
ChildTypes = new List { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService }
});
// ─── 希沃品课 画笔(子规则)────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoPincoDrawingFloating,
ProcessName = "seewoPincoTeacher.exe",
WindowTitlePattern = "^希沃品课——integration$",
TitleIsRegex = true,
ClassNamePattern = "^Chrome_WidgetWin_1$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x04440000,
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.FullScreen,
ActionType = InterceptActionType.Close,
IsEnabled = true,
Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)",
ParentType = InterceptType.SeewoPincoSideBarFloating
});
// ─── 希沃品课 桌面画板(子规则)─────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoPincoBoardService,
ProcessName = "BoardService.exe",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[BoardService;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000,
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.FullScreen,
ActionType = InterceptActionType.Close,
IsEnabled = true,
Description = "希沃品课教师端 桌面画板",
ParentType = InterceptType.SeewoPincoSideBarFloating
});
// ─── 希沃PPT小工具 ───────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoPPTFloating,
ProcessName = "PPTService.exe",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[PPTService\.exe;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "希沃PPT小工具"
});
// ─── 希沃课堂助手 ────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoIwbAssistantFloating,
ProcessName = "SeewoIwbAssistant.exe",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[SeewoIwbAssistant;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "希沃课堂助手"
});
// ─── 希沃桌面 侧栏 ───────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.SeewoDesktopSideBarFloating,
ProcessName = "ResidentSideBar.exe",
WindowTitlePattern = "^ResidentSideBar$",
TitleIsRegex = true,
ClassNamePattern = @"HwndWrapper\[ResidentSideBar\.exe;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x17080000, // WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_MAXIMIZE|WS_SYSMENU
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
RequiresAdmin = true,
Description = "希沃桌面 侧栏悬浮窗"
});
// ─── 希沃桌面 画笔 ───────────────────────────────────────────────────────
// (旧名 SeewoDesktopAnnotationFloating 已合并到此条,保留向后兼容枚举)
AddRule(new InterceptRule
{
Type = InterceptType.SeewoDesktopDrawingFloating,
ProcessName = "DesktopAnnotation.exe",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[DesktopAnnotation(\.exe)?;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000,
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "希沃桌面 画笔悬浮窗"
});
// 向后兼容:SeewoDesktopAnnotationFloating 指向相同规则逻辑,默认禁用(使用新条目)
AddRule(new InterceptRule
{
Type = InterceptType.SeewoDesktopAnnotationFloating,
ProcessName = "DesktopAnnotation.exe",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[DesktopAnnotation(\.exe)?;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000,
StyleMatchType = StyleMatchType.Exact,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = false,
Description = "希沃桌面 画笔悬浮窗(旧)"
});
// ─── 欧帝白板 ─────────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.YiouBoardFloating,
ProcessName = "WriteBoard.exe",
WindowTitlePattern = "^WinYODesktop$",
TitleIsRegex = true,
ClassNamePattern = @"HwndWrapper\[WriteBoard\.exe;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x16080000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.DPIScale,
WindowWidth = 780,
WindowHeight = 60,
IsEnabled = true,
RequiresAdmin = true,
Description = "欧帝白板 悬浮窗"
});
// ─── AiClass ──────────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.AiClassFloating,
ProcessName = "wisdom_class.exe",
WindowTitlePattern = "^TransparentWindow$",
TitleIsRegex = true,
ClassNamePattern = "^UIWndTransparent$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x82000000, // WS_POPUP|WS_CLIPSIBLINGS
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "AiClass 桌面悬浮窗"
});
// ─── ClassIn X ────────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.ClassInXFloating,
ProcessName = "ClassIn X.exe",
WindowTitlePattern = "^ClassIn X$",
TitleIsRegex = true,
ClassNamePattern = "^Qt5151QWindowIcon$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x86000000, // WS_POPUP|WS_CLIPSIBLINGS|WS_CLIPCHILDREN
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "ClassIn X 桌面悬浮窗"
});
// ─── 鸿合屏幕书写 ────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.HiteAnnotationFloating,
ProcessName = "HiteVision.exe",
WindowTitlePattern = "^HiteAnnotation$",
TitleIsRegex = true,
ClassNamePattern = "^Qt5QWindowToolSaveBits$",
ClassNameIsRegex = true,
IsEnabled = true,
Description = "鸿合屏幕书写"
});
// ─── 畅言4.0 父规则 ──────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.ChangYanFloating,
ProcessName = "ifly.qzk.Toolbar.exe",
WindowTitlePattern = "^DrawWindow$",
TitleIsRegex = true,
ClassNamePattern = "^Qt5QWindowToolSaveBits$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x86000000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂4.0 绘制窗口",
ChildTypes = new List
{
InterceptType.ChangYanBrushSettings,
InterceptType.ChangYanSwipeClear,
InterceptType.ChangYanInteraction,
InterceptType.ChangYanSubjectApp,
InterceptType.ChangYanControl,
InterceptType.ChangYanCommonTools,
InterceptType.ChangYanSceneToolbar,
InterceptType.ChangYanDrawWindow,
}
});
// 畅言4.0 子规则
AddChangYan4ChildRule(InterceptType.ChangYanBrushSettings, "^画笔设置$", "^Qt5QWindowOwnDCIcon$", "畅言4.0 画笔设置");
AddChangYan4ChildRule(InterceptType.ChangYanSwipeClear, "^滑动清除$", "^Qt5QWindowOwnDCIcon$", "畅言4.0 滑动清除");
AddChangYan4ChildRule(InterceptType.ChangYanInteraction, "^互动$", "^Qt5QWindowOwnDCIcon$", "畅言4.0 互动");
AddChangYan4ChildRule(InterceptType.ChangYanSubjectApp, "^学科应用$", "^Qt5QWindowOwnDCIcon$", "畅言4.0 学科应用");
AddChangYan4ChildRule(InterceptType.ChangYanControl, "^管控$", "^Qt5QWindowOwnDCIcon$", "畅言4.0 管控");
AddChangYan4ChildRule(InterceptType.ChangYanCommonTools, "^通用工具$", "^Qt5QWindowOwnDCIcon$", "畅言4.0 通用工具");
AddChangYan4ChildRule(InterceptType.ChangYanSceneToolbar, "^SceneToolbar$", "^Qt5QWindowOwnDCIcon$", "畅言4.0 场景工具栏");
AddChangYan4ChildRule(InterceptType.ChangYanDrawWindow, "^DrawWindow$", "^Qt5QWindowToolSaveBits$", "畅言4.0 绘制窗口(子)");
// ─── 畅言4.0 PPT 悬浮窗 ─────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.ChangYanPptFloating,
ProcessName = "ifly.qzk.Toolbar.exe",
WindowTitlePattern = "Exch",
ClassNamePattern = "^Qt5QWindowToolSaveBitsOwnDC$",
ClassNameIsRegex = true,
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言4.0 PPT悬浮窗",
ChildTypes = new List { InterceptType.ChangYanPptPageControl, InterceptType.ChangYanPptGoBack, InterceptType.ChangYanPptPreview }
});
AddChangYan4PptChildRule(InterceptType.ChangYanPptPageControl, "PageCtl", "畅言4.0 PPT页面控制");
AddChangYan4PptChildRule(InterceptType.ChangYanPptGoBack, "Goback", "畅言4.0 PPT返回");
AddChangYan4PptChildRule(InterceptType.ChangYanPptPreview, "Preview", "畅言4.0 PPT预览");
// ─── 畅言5.0 ─────────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.ChangYan5Floating,
ProcessName = "ifly.qzk.Toolbar.exe",
WindowTitlePattern = "^DrawWindow$",
TitleIsRegex = true,
ClassNamePattern = "^Qt5152QWindowIcon$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x86000000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂5.0 绘制窗口"
});
// ─── 天喻互动课堂 父规则 ──────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.IntelligentClassFloating,
ProcessName = "IntelligentClassApp.exe",
WindowTitlePattern = "^桌面小工具 - 互动课堂$",
TitleIsRegex = true,
ClassNamePattern = @"HwndWrapper\[IntelligentClassApp\.exe;;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x17080000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "天喻互动课堂 桌面悬浮窗",
ChildTypes = new List { InterceptType.IntelligentClassPptFloating }
});
// ─── 天喻互动课堂 PPT 子规则 ──────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.IntelligentClassPptFloating,
ProcessName = "IntelligentClass.Office.PowerPoint.vsto|vstolocal",
WindowTitlePattern = "",
ClassNamePattern = @"HwndWrapper\[IntelligentClass\.Office\.PowerPoint\.vsto\|vstolocal;VSTA_Main;[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\]",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x86000000,
StyleMatchType = StyleMatchType.Subset,
SizeMatchType = SizeMatchType.FullScreen,
IsEnabled = true,
Description = "天喻互动课堂 PPT悬浮窗",
ParentType = InterceptType.IntelligentClassFloating
});
// ─── C30 侧栏(默认关闭)────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.Iclass30SidebarFloating,
ProcessName = "teach.exe",
WindowTitlePattern = "^控制工具条$",
TitleIsRegex = true,
ClassNamePattern = "^ctrltool$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x86880000, // WS_POPUP|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_SYSMENU
StyleMatchType = StyleMatchType.Subset,
ActionType = InterceptActionType.Move,
IsEnabled = false,
Description = "C30智能教学 侧栏悬浮窗"
});
// ─── C30 画笔工具 ─────────────────────────────────────────────────────────
AddRule(new InterceptRule
{
Type = InterceptType.Iclass30Floating,
ProcessName = "teach.exe",
WindowTitlePattern = "^工具栏$",
TitleIsRegex = true,
ClassNamePattern = "^toolview$",
ClassNameIsRegex = true,
HasWindowStyle = true,
WindowStyle = 0x86880000,
StyleMatchType = StyleMatchType.Subset,
ActionType = InterceptActionType.Move,
IsEnabled = true,
Description = "C30智能教学 画笔工具栏"
});
}
private void AddRule(InterceptRule rule)
{
_interceptRules[rule.Type] = rule;
}
private void AddChangYan4ChildRule(InterceptType type, string titlePattern, string classPattern, string desc)
{
AddRule(new InterceptRule
{
Type = type,
ProcessName = "ifly.qzk.Toolbar.exe",
WindowTitlePattern = titlePattern,
TitleIsRegex = true,
ClassNamePattern = classPattern,
ClassNameIsRegex = true,
IsEnabled = true,
RequiresAdmin = true,
Description = desc,
ParentType = InterceptType.ChangYanFloating
});
}
private void AddChangYan4PptChildRule(InterceptType type, string titlePattern, string desc)
{
AddRule(new InterceptRule
{
Type = type,
ProcessName = "ifly.qzk.Toolbar.exe",
WindowTitlePattern = titlePattern,
ClassNamePattern = "^Qt5QWindowToolSaveBitsOwnDC$",
ClassNameIsRegex = true,
IsEnabled = true,
RequiresAdmin = true,
Description = desc,
ParentType = InterceptType.ChangYanPptFloating
});
}
#endregion
#region 公共方法
public void Start(int scanIntervalMs = 5000)
{
if (_isRunning) return;
_isRunning = true;
_scanTimer.Change(0, Math.Max(scanIntervalMs, 2000));
}
public void Stop()
{
if (!_isRunning) return;
_isRunning = false;
_scanTimer.Change(Timeout.Infinite, Timeout.Infinite);
RestoreAllWindows();
}
public void SetInterceptRule(InterceptType type, bool enabled)
{
if (!_interceptRules.ContainsKey(type)) return;
var rule = _interceptRules[type];
rule.IsEnabled = enabled;
if (!enabled) RestoreWindowsByType(type);
if (!enabled && rule.ChildTypes.Count > 0)
{
foreach (var childType in rule.ChildTypes)
if (_interceptRules.ContainsKey(childType))
{
_interceptRules[childType].IsEnabled = false;
RestoreWindowsByType(childType);
}
}
else if (enabled && rule.ChildTypes.Count > 0)
{
foreach (var childType in rule.ChildTypes)
if (_interceptRules.ContainsKey(childType))
_interceptRules[childType].IsEnabled = true;
}
else if (!enabled && rule.ParentType.HasValue)
{
var parentRule = _interceptRules[rule.ParentType.Value];
bool hasEnabledChildren = parentRule.ChildTypes.Any(ct =>
_interceptRules.ContainsKey(ct) && _interceptRules[ct].IsEnabled);
if (!hasEnabledChildren)
parentRule.IsEnabled = false;
}
else if (enabled && rule.ParentType.HasValue)
{
_interceptRules[rule.ParentType.Value].IsEnabled = true;
}
}
public InterceptRule GetInterceptRule(InterceptType type)
{
return _interceptRules.ContainsKey(type) ? _interceptRules[type] : null;
}
public Dictionary GetAllRules()
{
return new Dictionary(_interceptRules);
}
public int GetInterceptedWindowsCount() => _interceptedWindows.Count;
public void ScanOnce() => ScanForWindows(null);
public void RestoreAllWindows()
{
foreach (var hWnd in new List(_interceptedWindows.Keys))
RestoreWindow(hWnd);
}
public void RestoreWindowsByType(InterceptType type)
{
foreach (var hWnd in _interceptedWindows.Where(kvp => kvp.Value == type).Select(kvp => kvp.Key).ToList())
RestoreWindow(hWnd);
}
public bool RestoreWindow(IntPtr hWnd)
{
if (!_interceptedWindows.ContainsKey(hWnd)) return false;
var interceptType = _interceptedWindows[hWnd];
if (IsWindow(hWnd))
{
if (_movedWindowPositions.TryGetValue(hWnd, out var pos))
{
SetWindowPos(hWnd, IntPtr.Zero, pos.x, pos.y, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
_movedWindowPositions.Remove(hWnd);
}
else
{
ShowWindow(hWnd, SW_SHOWNOACTIVATE);
}
_interceptedWindows.Remove(hWnd);
WindowRestored?.Invoke(this, new WindowRestoredEventArgs
{
WindowHandle = hWnd,
InterceptType = interceptType
});
return true;
}
_interceptedWindows.Remove(hWnd);
_movedWindowPositions.Remove(hWnd);
return false;
}
#endregion
#region 私有方法
private void CleanupInvalidWindows()
{
foreach (var hWnd in _interceptedWindows.Keys.Where(h => !IsWindow(h)).ToList())
{
_interceptedWindows.Remove(hWnd);
_movedWindowPositions.Remove(hWnd);
}
}
private void ScanForWindows(object state)
{
if (!_isRunning) return;
try
{
CleanupInvalidWindows();
var foundWindows = new List<(IntPtr hwnd, InterceptRule rule)>();
EnumWindows((hWnd, lParam) =>
{
EnumChildWindows(hWnd, (childHwnd, _) =>
{
CheckWindow(childHwnd, foundWindows);
return true;
}, IntPtr.Zero);
CheckWindow(hWnd, foundWindows);
return true;
}, IntPtr.Zero);
var interceptedCount = 0;
foreach (var (hWnd, rule) in foundWindows)
{
// 已被隐藏且仍不可见:已拦截,无需重复操作
if (_interceptedWindows.ContainsKey(hWnd) && !IsWindowVisible(hWnd))
continue;
ApplyIntercept(hWnd, rule);
interceptedCount++;
}
if (interceptedCount == 0) _consecutiveEmptyScans++;
else
{
_consecutiveEmptyScans = 0;
_lastSuccessfulScan = DateTime.Now;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
_consecutiveEmptyScans++;
}
}
private void CheckWindow(IntPtr hWnd, List<(IntPtr, InterceptRule)> found)
{
// 必须是合法窗口句柄,但不要求可见(被我们隐藏的窗口也需要被发现以维持拦截状态)
if (!IsWindow(hWnd)) return;
foreach (var rule in _interceptRules.Values)
{
if (!rule.IsEnabled) continue;
if (MatchesRule(hWnd, rule))
{
found.Add((hWnd, rule));
break; // 一个窗口只匹配第一条命中的规则
}
}
}
private bool MatchesRule(IntPtr hWnd, InterceptRule rule)
{
try
{
// 检查类名
if (!string.IsNullOrEmpty(rule.ClassNamePattern))
{
var sb = new StringBuilder(512);
GetClassName(hWnd, sb, sb.Capacity);
var className = sb.ToString();
if (rule.ClassNameIsRegex)
{
if (!Regex.IsMatch(className, rule.ClassNamePattern)) return false;
}
else
{
if (!className.Contains(rule.ClassNamePattern)) return false;
}
}
// 检查窗口标题
if (!string.IsNullOrEmpty(rule.WindowTitlePattern))
{
var sb = new StringBuilder(512);
GetWindowText(hWnd, sb, sb.Capacity);
var title = sb.ToString();
if (rule.TitleIsRegex)
{
if (!Regex.IsMatch(title, rule.WindowTitlePattern)) return false;
}
else
{
if (!title.Contains(rule.WindowTitlePattern)) return false;
}
}
// 检查进程名
if (!string.IsNullOrEmpty(rule.ProcessName))
{
GetWindowThreadProcessId(hWnd, out uint processId);
if (processId == 0) return false;
var processName = GetProcessFileName(processId);
if (processName == null) return false;
// ProcessName 可能是不含路径的文件名(如 "EasiNote.exe")
if (!processName.Equals(rule.ProcessName, StringComparison.OrdinalIgnoreCase))
return false;
}
// 检查窗口样式
if (rule.HasWindowStyle)
{
var style = GetWindowLong(hWnd, GWL_STYLE);
if (rule.StyleMatchType == StyleMatchType.Exact)
{
if (style != rule.WindowStyle) return false;
}
else // Subset
{
if ((style & rule.WindowStyle) != rule.WindowStyle) return false;
}
}
// 检查窗口尺寸
if (rule.SizeMatchType != SizeMatchType.None)
{
RECT rect;
if (DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out rect, Marshal.SizeOf(typeof(RECT))) != 0)
GetWindowRect(hWnd, out rect);
int w = rect.Right - rect.Left;
int h = rect.Bottom - rect.Top;
if (w == 0 && h == 0) return false;
// DWM 返回物理像素,GetSystemMetrics 返回逻辑像素,需换算
var hdc = GetDC(IntPtr.Zero);
float scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f;
float scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0f;
ReleaseDC(IntPtr.Zero, hdc);
int screenW = (int)(GetSystemMetrics(SM_CXSCREEN) * scaleX);
int screenH = (int)(GetSystemMetrics(SM_CYSCREEN) * scaleY);
const int tolerance = 2;
switch (rule.SizeMatchType)
{
case SizeMatchType.FullScreen:
if (Math.Abs(w - screenW) > tolerance || Math.Abs(h - screenH) > tolerance)
return false;
break;
case SizeMatchType.FullHeight:
if (Math.Abs(h - screenH) > tolerance) return false;
break;
case SizeMatchType.FullWidth:
if (Math.Abs(w - screenW) > tolerance) return false;
break;
case SizeMatchType.Exact:
if (w != rule.WindowWidth || h != rule.WindowHeight) return false;
break;
case SizeMatchType.DPIScale:
if (Math.Abs(rule.WindowWidth * scaleX - w) > tolerance ||
Math.Abs(rule.WindowHeight * scaleY - h) > tolerance)
return false;
break;
case SizeMatchType.Scale:
{
if (rule.WindowWidth == 0 || rule.WindowHeight == 0) return false;
float wr = (float)w / rule.WindowWidth;
float hr = (float)h / rule.WindowHeight;
if (Math.Abs(wr - hr) > 0.03f) return false;
break;
}
}
}
return true;
}
catch
{
return false;
}
}
private string GetProcessFileName(uint processId)
{
try
{
var hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId);
if (hProcess == IntPtr.Zero) return null;
try
{
var sb = new StringBuilder(1024);
uint size = (uint)sb.Capacity;
if (QueryFullProcessImageNameW(hProcess, 0, sb, ref size))
{
var fullPath = sb.ToString(0, (int)size);
return System.IO.Path.GetFileName(fullPath);
}
return null;
}
finally
{
CloseHandle(hProcess);
}
}
catch
{
return null;
}
}
private void ApplyIntercept(IntPtr hWnd, InterceptRule rule)
{
try
{
if (!IsWindow(hWnd) || !IsWindowVisible(hWnd))
{
_interceptedWindows.Remove(hWnd);
return;
}
switch (rule.ActionType)
{
case InterceptActionType.Hide:
ShowWindow(hWnd, SW_HIDE);
_interceptedWindows[hWnd] = rule.Type;
break;
case InterceptActionType.Close:
PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
break;
case InterceptActionType.Move:
GetWindowRect(hWnd, out var rect);
int x = rect.Left, y = rect.Top;
if (x != -32000 || y != -32000)
{
_movedWindowPositions[hWnd] = (x, y);
SetWindowPos(hWnd, IntPtr.Zero, -32000, -32000, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
_interceptedWindows[hWnd] = rule.Type;
}
break;
}
WindowIntercepted?.Invoke(this, new WindowInterceptedEventArgs
{
WindowHandle = hWnd,
InterceptType = rule.Type,
Rule = rule,
WindowTitle = GetWindowTitle(hWnd)
});
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"拦截窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
}
}
private string GetWindowTitle(IntPtr hWnd)
{
try
{
var sb = new StringBuilder(256);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
catch { return "Unknown"; }
}
#endregion
#region 事件参数类
public class WindowInterceptedEventArgs : EventArgs
{
public IntPtr WindowHandle { get; set; }
public InterceptType InterceptType { get; set; }
public InterceptRule Rule { get; set; }
public string WindowTitle { get; set; }
}
public class WindowRestoredEventArgs : EventArgs
{
public IntPtr WindowHandle { get; set; }
public InterceptType InterceptType { get; set; }
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
Stop();
_scanTimer?.Dispose();
RestoreAllWindows();
_disposed = true;
}
#endregion
}
}