Merge pull request #236 from InkCanvasForClass/beta

ICC CE 1.7.12.0
This commit is contained in:
CJK_mkp
2025-10-02 16:03:39 +08:00
committed by GitHub
27 changed files with 1597 additions and 946 deletions
+1 -1
View File
@@ -1 +1 @@
1.7.11.0
1.7.12.0
+198 -592
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.7.11.4")]
[assembly: AssemblyFileVersion("1.7.11.4")]
[assembly: AssemblyVersion("1.7.12.0")]
[assembly: AssemblyFileVersion("1.7.12.0")]
+84 -8
View File
@@ -18,6 +18,11 @@ namespace Ink_Canvas.Helpers
private readonly object _frameLock = new object();
private Dispatcher _dispatcher;
// 新增属性
private int _rotationAngle = 0; // 0=0度,1=90度,2=180度,3=270度
private int _resolutionWidth = 640;
private int _resolutionHeight = 480;
public event EventHandler<Bitmap> FrameReceived;
public event EventHandler<string> ErrorOccurred;
@@ -25,6 +30,25 @@ namespace Ink_Canvas.Helpers
public List<FilterInfo> AvailableCameras { get; private set; }
public FilterInfo CurrentCamera { get; private set; }
// 新增属性
public int RotationAngle
{
get => _rotationAngle;
set => _rotationAngle = Math.Max(0, Math.Min(3, value));
}
public int ResolutionWidth
{
get => _resolutionWidth;
set => _resolutionWidth = Math.Max(320, Math.Min(1920, value));
}
public int ResolutionHeight
{
get => _resolutionHeight;
set => _resolutionHeight = Math.Max(240, Math.Min(1080, value));
}
public CameraService()
{
_dispatcher = Dispatcher.CurrentDispatcher;
@@ -32,6 +56,16 @@ namespace Ink_Canvas.Helpers
RefreshCameraList();
}
public CameraService(int rotationAngle, int resolutionWidth, int resolutionHeight)
{
_dispatcher = Dispatcher.CurrentDispatcher;
AvailableCameras = new List<FilterInfo>();
_rotationAngle = rotationAngle;
_resolutionWidth = resolutionWidth;
_resolutionHeight = resolutionHeight;
RefreshCameraList();
}
/// <summary>
/// 刷新可用摄像头列表
/// </summary>
@@ -242,14 +276,16 @@ namespace Ink_Canvas.Helpers
var width = sourceFrame.Width;
var height = sourceFrame.Height;
if (width > 0 && height > 0)
{
_currentFrame = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (var graphics = Graphics.FromImage(_currentFrame))
{
graphics.DrawImage(sourceFrame, 0, 0);
}
}
if (width > 0 && height > 0)
{
// 应用旋转
Bitmap rotatedFrame = ApplyRotation(sourceFrame);
// 应用分辨率调整
_currentFrame = ResizeImage(rotatedFrame, _resolutionWidth, _resolutionHeight);
rotatedFrame?.Dispose();
}
else
{
_currentFrame = null;
@@ -300,6 +336,46 @@ namespace Ink_Canvas.Helpers
return AvailableCameras.Count > 0;
}
/// <summary>
/// 应用旋转到图像
/// </summary>
private Bitmap ApplyRotation(Bitmap source)
{
if (_rotationAngle == 0)
return new Bitmap(source);
var rotationType = RotateFlipType.RotateNoneFlipNone;
switch (_rotationAngle)
{
case 1: rotationType = RotateFlipType.Rotate90FlipNone; break;
case 2: rotationType = RotateFlipType.Rotate180FlipNone; break;
case 3: rotationType = RotateFlipType.Rotate270FlipNone; break;
}
var rotated = new Bitmap(source);
rotated.RotateFlip(rotationType);
return rotated;
}
/// <summary>
/// 调整图像大小
/// </summary>
private Bitmap ResizeImage(Bitmap source, int width, int height)
{
if (source.Width == width && source.Height == height)
return new Bitmap(source);
var resized = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (var graphics = Graphics.FromImage(resized))
{
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphics.DrawImage(source, 0, 0, width, height);
}
return resized;
}
public void Dispose()
{
StopPreview();
@@ -25,6 +25,7 @@ namespace Ink_Canvas.Helpers
private const string IpcEventName = "InkCanvasFileAssociationEvent";
private const string IpcFilePrefix = "InkCanvasFileAssociation_";
private const string IpcBoardModePrefix = "InkCanvasBoardMode_";
private const string IpcShowModePrefix = "InkCanvasShowMode_";
private const int IpcTimeout = 5000; // 5秒超时
/// <summary>
@@ -310,6 +311,56 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 尝试通过IPC将展开浮动栏命令发送给已运行的实例
/// </summary>
/// <returns>是否成功发送</returns>
public static bool TrySendShowModeCommandToExistingInstance()
{
try
{
LogHelper.WriteLogToFile("尝试通过IPC发送展开浮动栏命令给已运行实例", LogHelper.LogType.Event);
// 创建IPC文件
string tempDir = Path.GetTempPath();
string ipcFileName = IpcShowModePrefix + Guid.NewGuid().ToString("N") + ".tmp";
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
// 写入展开浮动栏命令到IPC文件
File.WriteAllText(ipcFilePath, "SHOW_MODE", Encoding.UTF8);
// 创建事件通知已运行实例
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
{
ipcEvent.Set();
}
// 等待一段时间让已运行实例处理命令
Thread.Sleep(1000);
// 清理IPC文件
try
{
if (File.Exists(ipcFilePath))
{
File.Delete(ipcFilePath);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
}
LogHelper.WriteLogToFile("IPC展开浮动栏命令发送完成", LogHelper.LogType.Event);
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"通过IPC发送展开浮动栏命令失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 启动IPC监听器,等待其他实例发送文件路径
/// </summary>
@@ -470,6 +521,61 @@ namespace Ink_Canvas.Helpers
catch { }
}
}
// 处理展开浮动栏命令IPC文件
string[] showModeFiles = Directory.GetFiles(tempDir, IpcShowModePrefix + "*.tmp");
foreach (string ipcFile in showModeFiles)
{
try
{
// 读取命令内容
string command = File.ReadAllText(ipcFile, Encoding.UTF8);
if (command == "SHOW_MODE")
{
LogHelper.WriteLogToFile("IPC接收到展开浮动栏命令", LogHelper.LogType.Event);
// 在UI线程中处理展开浮动栏
Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
{
try
{
// 获取主窗口并展开浮动栏
if (Application.Current.MainWindow is MainWindow mainWindow)
{
// 如果当前处于收纳模式,则展开浮动栏
if (mainWindow.isFloatingBarFolded)
{
await mainWindow.UnFoldFloatingBar(new object());
}
mainWindow.ShowNotification("已退出收纳模式并恢复浮动栏");
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"IPC处理展开浮动栏失败: {ex.Message}", LogHelper.LogType.Error);
}
}));
}
// 删除IPC文件
File.Delete(ipcFile);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理展开浮动栏IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
// 尝试删除损坏的IPC文件
try
{
if (File.Exists(ipcFile))
{
File.Delete(ipcFile);
}
}
catch { }
}
}
}
catch (Exception ex)
{
+308 -301
View File
@@ -55,6 +55,27 @@ namespace Ink_Canvas.Helpers
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out ForegroundWindowInfo.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 ForegroundWindowInfo.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 bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
@@ -68,9 +89,11 @@ namespace Ink_Canvas.Helpers
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
private const int SW_SHOWNORMAL = 1;
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;
private const int GWL_STYLE = -16;
private const int GWL_EXSTYLE = -20;
private const uint WS_EX_TOOLWINDOW = 0x00000080;
private const uint WS_EX_APPWINDOW = 0x00040000;
@@ -79,10 +102,17 @@ namespace Ink_Canvas.Helpers
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_HIDEWINDOW = 0x0080;
private const uint SWP_SHOWWINDOW = 0x0040;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
private const uint PROCESS_VM_READ = 0x0010;
private const uint WM_CLOSE = 0x0010;
private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
private const int LOGPIXELSX = 88;
private const int LOGPIXELSY = 90;
#endregion
#region
@@ -212,6 +242,19 @@ namespace Ink_Canvas.Helpers
public string Description { get; set; }
public InterceptType? ParentType { get; set; }
public List<InterceptType> ChildTypes { get; set; } = new List<InterceptType>();
// 新增的精确匹配字段
public bool HasWindowStyle { get; set; }
public uint WindowStyle { get; set; }
public bool HasWindowSize { get; set; }
public int WindowWidth { get; set; }
public int WindowHeight { get; set; }
public bool ExactTitleMatch { get; set; } = false;
public bool ExactClassNameMatch { get; set; } = false;
// 运行时状态字段
public bool foundHwnd { get; set; } = false;
public IntPtr outHwnd { get; set; } = IntPtr.Zero;
}
#endregion
@@ -225,13 +268,9 @@ namespace Ink_Canvas.Helpers
private bool _isRunning;
private bool _disposed;
// 性能优化字段
private readonly Dictionary<IntPtr, DateTime> _lastScanTime = new Dictionary<IntPtr, DateTime>();
private readonly HashSet<IntPtr> _knownWindows = new HashSet<IntPtr>();
private readonly Dictionary<string, DateTime> _processLastScanTime = new Dictionary<string, DateTime>();
// 简化的性能统计
private int _consecutiveEmptyScans = 0;
private DateTime _lastSuccessfulScan = DateTime.Now;
private readonly object _scanLock = new object();
#endregion
@@ -275,7 +314,14 @@ namespace Ink_Canvas.Helpers
ClassNamePattern = "HwndWrapper[EasiNote.exe;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板3 桌面悬浮窗"
Description = "希沃白板3 桌面悬浮窗",
HasWindowStyle = true,
WindowStyle = 370081792,
HasWindowSize = true,
WindowWidth = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
WindowHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height,
ExactTitleMatch = true,
ExactClassNameMatch = false
};
// 希沃白板5 桌面悬浮窗
@@ -287,7 +333,14 @@ namespace Ink_Canvas.Helpers
ClassNamePattern = "HwndWrapper[EasiNote;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板5 桌面悬浮窗"
Description = "希沃白板5 桌面悬浮窗",
HasWindowStyle = true,
WindowStyle = 369623040,
HasWindowSize = true,
WindowWidth = 550,
WindowHeight = 200,
ExactTitleMatch = false,
ExactClassNameMatch = false
};
// 希沃白板5C 桌面悬浮窗
@@ -299,7 +352,14 @@ namespace Ink_Canvas.Helpers
ClassNamePattern = "HwndWrapper[EasiNote5C;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板5C 桌面悬浮窗"
Description = "希沃白板5C 桌面悬浮窗",
HasWindowStyle = true,
WindowStyle = 369623040,
HasWindowSize = true,
WindowWidth = 550,
WindowHeight = 200,
ExactTitleMatch = false,
ExactClassNameMatch = false
};
// 希沃品课教师端 桌面悬浮窗(父规则)
@@ -313,7 +373,11 @@ namespace Ink_Canvas.Helpers
RequiresAdmin = false,
Description = "希沃品课教师端 桌面悬浮窗",
ParentType = null,
ChildTypes = new List<InterceptType> { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService }
ChildTypes = new List<InterceptType> { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService },
HasWindowStyle = true,
WindowStyle = 0x16CF0000,
ExactTitleMatch = true,
ExactClassNameMatch = true
};
// 希沃品课教师端 画笔悬浮窗(子规则)
@@ -327,7 +391,11 @@ namespace Ink_Canvas.Helpers
RequiresAdmin = false,
Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)",
ParentType = InterceptType.SeewoPincoSideBarFloating,
ChildTypes = new List<InterceptType>()
ChildTypes = new List<InterceptType>(),
HasWindowStyle = true,
WindowStyle = 335675392,
ExactTitleMatch = true,
ExactClassNameMatch = true
};
// 希沃品课教师端 桌面画板(子规则)
@@ -341,7 +409,14 @@ namespace Ink_Canvas.Helpers
RequiresAdmin = false,
Description = "希沃品课教师端 桌面画板",
ParentType = InterceptType.SeewoPincoSideBarFloating,
ChildTypes = new List<InterceptType>()
ChildTypes = new List<InterceptType>(),
HasWindowStyle = true,
WindowStyle = 369623040,
HasWindowSize = true,
WindowWidth = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
WindowHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height,
ExactTitleMatch = false,
ExactClassNameMatch = false
};
// 希沃PPT小工具
@@ -665,6 +740,12 @@ namespace Ink_Canvas.Helpers
var rule = _interceptRules[type];
rule.IsEnabled = enabled;
// 如果规则被禁用,恢复相关的被拦截窗口
if (!enabled)
{
RestoreWindowsByType(type);
}
// 如果是父规则被禁用,则禁用所有子规则
if (!enabled && rule.ChildTypes.Count > 0)
{
@@ -673,6 +754,7 @@ namespace Ink_Canvas.Helpers
if (_interceptRules.ContainsKey(childType))
{
_interceptRules[childType].IsEnabled = false;
RestoreWindowsByType(childType);
}
}
}
@@ -726,6 +808,14 @@ namespace Ink_Canvas.Helpers
return new Dictionary<InterceptType, InterceptRule>(_interceptRules);
}
/// <summary>
/// 获取当前被拦截的窗口数量
/// </summary>
public int GetInterceptedWindowsCount()
{
return _interceptedWindows.Count;
}
/// <summary>
/// 手动扫描一次
/// </summary>
@@ -740,10 +830,41 @@ namespace Ink_Canvas.Helpers
public void RestoreAllWindows()
{
var windowsToRestore = new List<IntPtr>(_interceptedWindows.Keys);
var restoredCount = 0;
foreach (var hWnd in windowsToRestore)
{
RestoreWindow(hWnd);
if (RestoreWindow(hWnd))
{
restoredCount++;
}
}
}
/// <summary>
/// 恢复指定类型的被拦截窗口
/// </summary>
public void RestoreWindowsByType(InterceptType type)
{
var windowsToRestore = new List<IntPtr>();
foreach (var kvp in _interceptedWindows)
{
if (kvp.Value == type)
{
windowsToRestore.Add(kvp.Key);
}
}
var restoredCount = 0;
foreach (var hWnd in windowsToRestore)
{
if (RestoreWindow(hWnd))
{
restoredCount++;
}
}
}
/// <summary>
@@ -753,15 +874,29 @@ namespace Ink_Canvas.Helpers
{
if (!_interceptedWindows.ContainsKey(hWnd)) return false;
var interceptType = _interceptedWindows[hWnd];
if (IsWindow(hWnd))
{
// 使用多种方法确保窗口恢复显示
ShowWindow(hWnd, SW_RESTORE);
ShowWindow(hWnd, SW_SHOW);
ShowWindow(hWnd, SW_SHOWNORMAL);
// 将窗口置于前台并显示
SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
// 强制将窗口带到前台
BringWindowToTop(hWnd);
SetForegroundWindow(hWnd);
_interceptedWindows.Remove(hWnd);
WindowRestored?.Invoke(this, new WindowRestoredEventArgs
{
WindowHandle = hWnd,
InterceptType = _interceptedWindows[hWnd]
InterceptType = interceptType
});
return true;
@@ -775,187 +910,103 @@ namespace Ink_Canvas.Helpers
#region
/// <summary>
/// 清理无效的窗口句柄
/// </summary>
private void CleanupInvalidWindows()
{
var invalidWindows = new List<IntPtr>();
foreach (var kvp in _interceptedWindows)
{
var hWnd = kvp.Key;
if (!IsWindow(hWnd))
{
invalidWindows.Add(hWnd);
}
}
foreach (var hWnd in invalidWindows)
{
_interceptedWindows.Remove(hWnd);
}
}
private void ScanForWindows(object state)
{
if (!_isRunning) return;
lock (_scanLock)
try
{
try
// 简化的扫描逻辑
var interceptedCount = 0;
CleanupInvalidWindows();
// 重置所有规则的发现状态
foreach (var rule in _interceptRules.Values)
{
var scanStartTime = DateTime.Now;
var windowsFound = 0;
var windowsIntercepted = 0;
// 清理过期的缓存
CleanupExpiredCache();
// 使用优化的扫描策略
if (_consecutiveEmptyScans > 3)
if (rule.IsEnabled)
{
// 如果连续多次扫描没有发现新窗口,使用快速扫描模式
PerformQuickScan(ref windowsFound, ref windowsIntercepted);
rule.foundHwnd = false;
}
else
}
// 枚举所有窗口
EnumWindows(EnumWindowsCallback, IntPtr.Zero);
// 处理找到的窗口
foreach (var rule in _interceptRules.Values)
{
if (rule.IsEnabled && rule.foundHwnd && rule.outHwnd != IntPtr.Zero)
{
// 正常扫描模式
PerformFullScan(ref windowsFound, ref windowsIntercepted);
}
// 更新扫描统计
UpdateScanStatistics(windowsFound, windowsIntercepted, scanStartTime);
// 动态调整扫描间隔
AdjustScanInterval();
}
catch (Exception ex)
{
// 记录错误但不中断扫描
LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
_consecutiveEmptyScans++;
}
}
}
/// <summary>
/// 执行快速扫描 - 只检查已知进程
/// </summary>
private void PerformQuickScan(ref int windowsFound, ref int windowsIntercepted)
{
var targetProcesses = new HashSet<string>();
var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 };
// 收集所有启用的规则对应的进程名
foreach (var rule in _interceptRules.Values)
{
if (rule.IsEnabled && !string.IsNullOrEmpty(rule.ProcessName))
{
targetProcesses.Add(rule.ProcessName.ToLower());
}
}
// 只扫描目标进程的窗口
foreach (var processName in targetProcesses)
{
try
{
var processes = Process.GetProcessesByName(processName);
foreach (var process in processes)
{
if (process.MainWindowHandle != IntPtr.Zero)
bool shouldIntercept = !_interceptedWindows.ContainsKey(rule.outHwnd) ||
(_interceptedWindows.ContainsKey(rule.outHwnd) && IsWindowVisible(rule.outHwnd));
if (shouldIntercept)
{
ProcessWindow(process.MainWindowHandle, scanData);
InterceptWindow(rule.outHwnd, rule);
interceptedCount++;
}
}
}
catch
// 更新统计
if (interceptedCount == 0)
{
// 忽略进程访问错误
_consecutiveEmptyScans++;
}
else
{
_consecutiveEmptyScans = 0;
_lastSuccessfulScan = DateTime.Now;
}
}
windowsFound = scanData.WindowsFound;
windowsIntercepted = scanData.WindowsIntercepted;
}
/// <summary>
/// 执行完整扫描
/// </summary>
private void PerformFullScan(ref int windowsFound, ref int windowsIntercepted)
{
var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 };
EnumWindows((hWnd, lParam) =>
catch (Exception ex)
{
ProcessWindow(hWnd, scanData);
return true;
}, IntPtr.Zero);
windowsFound = scanData.WindowsFound;
windowsIntercepted = scanData.WindowsIntercepted;
LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
_consecutiveEmptyScans++;
}
}
/// <summary>
/// 处理单个窗口
/// </summary>
private bool ProcessWindow(IntPtr hWnd, ScanData scanData)
private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
{
try
{
// 递归枚举子窗口
EnumChildWindows(hWnd, EnumWindowsCallback, lParam);
// 基本检查
if (!IsWindow(hWnd) || !IsWindowVisible(hWnd)) return true;
// 检查是否已经被拦截
if (_interceptedWindows.ContainsKey(hWnd)) return true;
// 检查缓存,避免重复处理
if (_knownWindows.Contains(hWnd))
{
var lastScan = _lastScanTime.ContainsKey(hWnd) ? _lastScanTime[hWnd] : DateTime.MinValue;
if (DateTime.Now - lastScan < TimeSpan.FromSeconds(30)) // 30秒内不重复处理
{
return true;
}
}
scanData.WindowsFound++;
_knownWindows.Add(hWnd);
_lastScanTime[hWnd] = DateTime.Now;
// 获取窗口信息
var windowInfo = GetWindowInfo(hWnd);
if (windowInfo == null) return true;
// 检查进程缓存
if (_processLastScanTime.ContainsKey(windowInfo.ProcessName))
{
var lastProcessScan = _processLastScanTime[windowInfo.ProcessName];
if (DateTime.Now - lastProcessScan < TimeSpan.FromSeconds(10)) // 10秒内不重复扫描同一进程
{
return true;
}
}
_processLastScanTime[windowInfo.ProcessName] = DateTime.Now;
// 检查窗口样式,过滤掉系统窗口和主窗口
var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
var style = GetWindowLong(hWnd, -16); // GWL_STYLE
// 跳过工具窗口
if ((exStyle & WS_EX_TOOLWINDOW) != 0)
{
return true;
}
// 跳过主窗口(有标题栏和系统菜单的窗口)
const uint WS_CAPTION = 0x00C00000;
const uint WS_SYSMENU = 0x00080000;
if ((style & WS_CAPTION) != 0 && (style & WS_SYSMENU) != 0)
{
return true;
}
// 检查窗口大小,跳过过大的窗口
var rect = new ForegroundWindowInfo.RECT();
GetWindowRect(hWnd, out rect);
var width = rect.Right - rect.Left;
var height = rect.Bottom - rect.Top;
if (width > 600 || height > 400)
{
return true;
}
// 检查是否匹配拦截规则
// 检查每个启用的规则
foreach (var rule in _interceptRules.Values)
{
if (!rule.IsEnabled) continue;
if (!rule.IsEnabled || rule.foundHwnd) continue;
if (MatchesRule(windowInfo, rule))
if (MatchesRulePrecise(hWnd, rule))
{
InterceptWindow(hWnd, rule);
scanData.WindowsIntercepted++;
break;
rule.outHwnd = hWnd;
rule.foundHwnd = true;
}
}
@@ -963,107 +1014,11 @@ namespace Ink_Canvas.Helpers
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
LogHelper.WriteLogToFile($"枚举窗口回调错误: {ex.Message}", LogHelper.LogType.Error);
return true;
}
}
/// <summary>
/// 清理过期缓存
/// </summary>
private void CleanupExpiredCache()
{
var now = DateTime.Now;
var expiredWindows = new List<IntPtr>();
var expiredProcesses = new List<string>();
// 清理窗口缓存
foreach (var kvp in _lastScanTime)
{
if (now - kvp.Value > TimeSpan.FromMinutes(5))
{
expiredWindows.Add(kvp.Key);
}
}
foreach (var hWnd in expiredWindows)
{
_lastScanTime.Remove(hWnd);
_knownWindows.Remove(hWnd);
}
// 清理进程缓存
foreach (var kvp in _processLastScanTime)
{
if (now - kvp.Value > TimeSpan.FromMinutes(2))
{
expiredProcesses.Add(kvp.Key);
}
}
foreach (var processName in expiredProcesses)
{
_processLastScanTime.Remove(processName);
}
}
/// <summary>
/// 更新扫描统计
/// </summary>
private void UpdateScanStatistics(int windowsFound, int windowsIntercepted, DateTime scanStartTime)
{
var scanDuration = DateTime.Now - scanStartTime;
if (windowsFound == 0)
{
_consecutiveEmptyScans++;
}
else
{
_consecutiveEmptyScans = 0;
_lastSuccessfulScan = DateTime.Now;
}
}
/// <summary>
/// 动态调整扫描间隔
/// </summary>
private void AdjustScanInterval()
{
if (!_isRunning) return;
int newInterval;
if (_consecutiveEmptyScans > 5)
{
// 连续多次空扫描,增加间隔到30秒
newInterval = 30000;
}
else if (_consecutiveEmptyScans > 3)
{
// 连续多次空扫描,增加间隔到15秒
newInterval = 15000;
}
else if (_consecutiveEmptyScans > 1)
{
// 连续空扫描,增加间隔到10秒
newInterval = 10000;
}
else
{
// 正常扫描,使用5秒间隔
newInterval = 5000;
}
// 更新定时器间隔
_scanTimer.Change(newInterval, newInterval);
}
private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
{
// 这个方法现在由ProcessWindow替代,保留用于兼容性
return true;
}
private WindowInfo GetWindowInfo(IntPtr hWnd)
{
try
@@ -1100,62 +1055,119 @@ namespace Ink_Canvas.Helpers
}
}
private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule)
/// <summary>
/// 精确匹配规则
/// </summary>
private bool MatchesRulePrecise(IntPtr hWnd, InterceptRule rule)
{
try
{
// 检查进程名(如果指定了进程名)
if (!string.IsNullOrEmpty(rule.ProcessName))
{
if (!windowInfo.ProcessName.ToLower().Contains(rule.ProcessName.ToLower()))
{
return false;
}
}
// 检查窗口标题(如果指定了模式)
if (!string.IsNullOrEmpty(rule.WindowTitlePattern))
{
if (!windowInfo.WindowTitle.ToLower().Contains(rule.WindowTitlePattern.ToLower()))
{
return false;
}
}
// 检查类名(如果指定了模式)
// 检查类名
if (!string.IsNullOrEmpty(rule.ClassNamePattern))
{
if (!windowInfo.ClassName.ToLower().Contains(rule.ClassNamePattern.ToLower()))
var className = new StringBuilder(256);
GetClassName(hWnd, className, className.Capacity);
var classNameStr = className.ToString();
if (rule.ExactClassNameMatch)
{
if (!classNameStr.Equals(rule.ClassNamePattern, StringComparison.OrdinalIgnoreCase))
return false;
}
else
{
if (!classNameStr.Contains(rule.ClassNamePattern))
return false;
}
}
// 检查窗口标题
if (!string.IsNullOrEmpty(rule.WindowTitlePattern))
{
var windowTitle = new StringBuilder(256);
GetWindowText(hWnd, windowTitle, windowTitle.Capacity);
var titleStr = windowTitle.ToString();
if (rule.ExactTitleMatch)
{
if (!titleStr.Equals(rule.WindowTitlePattern, StringComparison.OrdinalIgnoreCase))
return false;
}
else
{
if (!titleStr.Contains(rule.WindowTitlePattern))
return false;
}
}
// 检查窗口样式
if (rule.HasWindowStyle)
{
var style = GetWindowLong(hWnd, GWL_STYLE);
if (style != rule.WindowStyle)
return false;
}
// 检查窗口尺寸
if (rule.HasWindowSize)
{
var rect = new ForegroundWindowInfo.RECT();
if (DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out rect, Marshal.SizeOf(rect)) == 0)
{
var width = rect.Right - rect.Left;
var height = rect.Bottom - rect.Top;
// 检查精确匹配
if (rule.WindowWidth == width && rule.WindowHeight == height)
return true;
// 检查缩放匹配
var hdc = GetDC(IntPtr.Zero);
var horizontalDPI = GetDeviceCaps(hdc, LOGPIXELSX);
var verticalDPI = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(IntPtr.Zero, hdc);
var scale = (horizontalDPI + verticalDPI) / 2.0f / 96.0f;
var scaledWidth = (int)(rule.WindowWidth * scale);
var scaledHeight = (int)(rule.WindowHeight * scale);
if (Math.Abs(scaledWidth - width) <= 1 && Math.Abs(scaledHeight - height) <= 1)
return true;
return false;
}
}
// 如果所有检查都通过,就认为是目标窗口
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error);
LogHelper.WriteLogToFile($"精确匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule)
{
return MatchesRulePrecise(windowInfo.Handle, rule);
}
private void InterceptWindow(IntPtr hWnd, InterceptRule rule)
{
try
{
// 使用多种方法隐藏窗口
// 方法1:移动到屏幕外
SetWindowPos(hWnd, IntPtr.Zero, -2000, -2000, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW);
// 方法2:最小化窗口
ShowWindow(hWnd, SW_MINIMIZE);
// 方法3:隐藏窗口
if (!IsWindow(hWnd) || !IsWindowVisible(hWnd))
{
if (_interceptedWindows.ContainsKey(hWnd))
{
_interceptedWindows.Remove(hWnd);
}
return;
}
// 直接隐藏窗口,不发送关闭消息
ShowWindow(hWnd, SW_HIDE);
// 记录拦截的窗口
_interceptedWindows[hWnd] = rule.Type;
@@ -1167,6 +1179,7 @@ namespace Ink_Canvas.Helpers
Rule = rule,
WindowTitle = GetWindowTitle(hWnd)
});
}
catch (Exception ex)
{
@@ -1243,12 +1256,6 @@ namespace Ink_Canvas.Helpers
public Process Process { get; set; }
}
private class ScanData
{
public int WindowsFound { get; set; }
public int WindowsIntercepted { get; set; }
}
#endregion
#region
+5
View File
@@ -575,6 +575,11 @@
<Resource Include="Resources\PresentationExample\sidebar-dark.png" />
<Resource Include="Resources\PresentationExample\sidebar-white.png" />
<Resource Include="Resources\PresentationExample\toolbar.png" />
<Resource Include="Resources\Startup-animation\ICC Spring.png" />
<Resource Include="Resources\Startup-animation\ICC Summer.png" />
<Resource Include="Resources\Startup-animation\ICC Autumn.png" />
<Resource Include="Resources\Startup-animation\ICC Winter.png" />
<Resource Include="Resources\Startup-animation\ICC Horse.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
+22 -3
View File
@@ -1040,6 +1040,28 @@
<ComboBoxItem Content="跟随系统" FontFamily="Microsoft YaHei UI" />
</ComboBox>
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="启用启动动画" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchEnableSplashScreen"
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchEnableSplashScreen_Toggled" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="启动动画样式" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<ComboBox Name="ComboBoxSplashScreenStyle" FontFamily="Microsoft YaHei UI"
SelectedIndex="1" Width="150"
SelectionChanged="ComboBoxSplashScreenStyle_SelectionChanged">
<ComboBoxItem Content="随机" FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="跟随四季" FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="春季" FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="夏季" FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="秋季" FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="冬季" FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="马年限定" FontFamily="Microsoft YaHei UI" />
</ComboBox>
</ui:SimpleStackPanel>
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0"
Stroke="#3f3f46" StrokeThickness="1" Margin="0,4,0,4" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
@@ -3666,9 +3688,6 @@
<TextBlock
Text="© 2025 CJK_mkp 版权所有" FontWeight="Bold"
TextWrapping="Wrap" Foreground="White" />
<TextBlock
Text="© 2022-2025 HarkoTek Studio 提供技术支持"
TextWrapping="Wrap" Foreground="#a1a1aa" />
<TextBlock
Text="We love Open-Source forever!" FontWeight="Bold"
TextWrapping="Wrap" Foreground="#a1a1aa" />
+24 -4
View File
@@ -511,8 +511,8 @@ namespace Ink_Canvas
}
SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged;
// 自动收纳到侧边栏(若通过 --board 进入白板模式则跳过收纳)
if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode)
// 自动收纳到侧边栏(若通过 --board 进入白板模式或 --show 参数则跳过收纳)
if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode && !App.StartWithShowMode)
{
FoldFloatingBar_MouseUp(new object(), null);
}
@@ -523,8 +523,6 @@ namespace Ink_Canvas
else
RadioCrashNoAction.IsChecked = true;
// 如果当前不是黑板模式,则切换到黑板模式
if (currentMode == 0)
{
@@ -584,6 +582,21 @@ namespace Ink_Canvas
SwitchToBoardMode();
}), DispatcherPriority.Loaded);
}
// 检查是否通过--show参数启动,如果是则确保退出收纳模式并恢复浮动栏
if (App.StartWithShowMode)
{
LogHelper.WriteLogToFile("检测到--show参数,退出收纳模式并恢复浮动栏", LogHelper.LogType.Event);
// 延迟执行,确保UI已完全加载
Dispatcher.BeginInvoke(new Action(async () =>
{
// 如果当前处于收纳模式,则展开浮动栏
if (isFloatingBarFolded)
{
await UnFoldFloatingBar(new object());
}
}), DispatcherPriority.Loaded);
}
}
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
@@ -727,6 +740,13 @@ namespace Ink_Canvas
_inkFadeManager = null;
}
// 清理悬浮窗拦截管理器
if (_floatingWindowInterceptorManager != null)
{
_floatingWindowInterceptorManager.Dispose();
_floatingWindowInterceptorManager = null;
}
// 停止置顶维护定时器
StopTopmostMaintenance();
@@ -29,7 +29,6 @@ namespace Ink_Canvas
{
public partial class MainWindow : Window
{
// 添加当前模式的缓存,避免依赖可能过时的inkCanvas.EditingMode状态
private string _currentToolMode = "cursor";
#region "手勢"
@@ -1537,7 +1536,6 @@ namespace Ink_Canvas
// 仅计算Windows任务栏高度,不考虑其他程序对工作区的影响
var toolbarHeight = ForegroundWindowInfo.GetTaskbarHeight(screen, dpiScaleY);
// 计算浮动栏位置,考虑快捷调色盘的显示状态
// 使用更可靠的方法获取浮动栏宽度
double baseWidth = ViewboxFloatingBar.ActualWidth;
@@ -1577,7 +1575,7 @@ namespace Ink_Canvas
else
{
// 双行显示模式,宽度较大
floatingBarWidth = Math.Max(floatingBarWidth, 820 * ViewboxFloatingBarScaleTransform.ScaleX);
floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX);
}
}
@@ -1657,7 +1655,7 @@ namespace Ink_Canvas
public async void PureViewboxFloatingBarMarginAnimationInDesktopMode()
{
// 新增:在白板模式下不执行浮动栏动画
// 在白板模式下不执行浮动栏动画
if (currentMode == 1)
{
return;
@@ -1682,8 +1680,6 @@ namespace Ink_Canvas
// 仅计算Windows任务栏高度,不考虑其他程序对工作区的影响
var toolbarHeight = ForegroundWindowInfo.GetTaskbarHeight(screen, dpiScaleY);
// 计算浮动栏位置,考虑快捷调色盘的显示状态
// 使用更可靠的方法获取浮动栏宽度
double baseWidth = ViewboxFloatingBar.ActualWidth;
// 如果ActualWidth为0,尝试使用DesiredSize
@@ -1722,13 +1718,13 @@ namespace Ink_Canvas
else
{
// 双行显示模式,宽度较大
floatingBarWidth = Math.Max(floatingBarWidth, 850 * ViewboxFloatingBarScaleTransform.ScaleX);
floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX);
}
}
pos.X = (screenWidth - floatingBarWidth) / 2;
// 如果任务栏高度为0(隐藏状态),则使用固定边距
// 如果任务栏高度为0,则使用固定边距
if (toolbarHeight == 0)
{
pos.Y = screenHeight - ViewboxFloatingBar.ActualHeight * ViewboxFloatingBarScaleTransform.ScaleY -
@@ -1828,7 +1824,7 @@ namespace Ink_Canvas
else
{
// 双行显示模式,宽度较大
floatingBarWidth = Math.Max(floatingBarWidth, 820 * ViewboxFloatingBarScaleTransform.ScaleX);
floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX);
}
}
+14
View File
@@ -186,6 +186,20 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchEnableSplashScreen_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.EnableSplashScreen = ToggleSwitchEnableSplashScreen.IsOn;
SaveSettingsToFile();
}
private void ComboBoxSplashScreenStyle_SelectionChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.SplashScreenStyle = ComboBoxSplashScreenStyle.SelectedIndex;
SaveSettingsToFile();
}
private void ViewboxFloatingBarScaleTransformValueSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
@@ -332,6 +332,10 @@ namespace Ink_Canvas
ToggleSwitchEnableQuickPanel.IsOn = Settings.Appearance.IsShowQuickPanel;
ToggleSwitchEnableSplashScreen.IsOn = Settings.Appearance.EnableSplashScreen;
ComboBoxSplashScreenStyle.SelectedIndex = Settings.Appearance.SplashScreenStyle;
ToggleSwitchEnableTrayIcon.IsOn = Settings.Appearance.EnableTrayIcon;
ICCTrayIconExampleImage.Visibility =
Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed;
+17 -3
View File
@@ -275,6 +275,8 @@ namespace Ink_Canvas
{
var processes = Process.GetProcessesByName("EasiNote");
if (processes.Length > 0) arg += " /IM EasiNote.exe";
var seewoStartProcesses = Process.GetProcessesByName("SeewoStart");
if (seewoStartProcesses.Length > 0) arg += " /IM SeewoStart.exe";
}
if (Settings.Automation.IsAutoKillHiteAnnotation)
@@ -471,10 +473,22 @@ namespace Ink_Canvas
Trace.WriteLine(ForegroundWindowInfo.ProcessPath());
Trace.WriteLine(version);
Trace.WriteLine(prodName);
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote && (!(windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500) ||
!Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno))
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
{ // EasiNote5
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
// 检查是否是桌面批注窗口
bool isAnnotationWindow = windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500;
// 如果启用了忽略桌面批注窗口功能,且当前是批注窗口
if (Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno && isAnnotationWindow)
{
// 强制保持收纳状态
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
}
else if (!isAnnotationWindow)
{
// 非批注窗口时正常收纳
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
}
}
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3)
{ // EasiNote3
@@ -696,6 +696,11 @@ namespace Ink_Canvas
touchPoint = e.GetTouchPoint(inkCanvas);
EraserOverlay_PointerDown(sender);
EraserOverlay_PointerMove(sender, touchPoint.Position);
if (Settings.Canvas.IsShowCursor)
{
inkCanvas.ForceCursor = false;
inkCanvas.UseCustomCursor = false;
}
}
}
}
@@ -840,6 +845,11 @@ namespace Ink_Canvas
// 禁用橡皮擦覆盖层
DisableEraserOverlay();
if (Settings.Canvas.IsShowCursor)
{
inkCanvas.ForceCursor = true;
inkCanvas.UseCustomCursor = true;
}
LogHelper.WriteLogToFile("Palm eraser state reset completed");
}
@@ -948,6 +958,12 @@ namespace Ink_Canvas
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
DisableEraserOverlay();
if (Settings.Canvas.IsShowCursor)
{
inkCanvas.ForceCursor = true;
inkCanvas.UseCustomCursor = true;
}
LogHelper.WriteLogToFile("Palm eraser force recovery completed");
}
+2 -2
View File
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.7.11.4")]
[assembly: AssemblyFileVersion("1.7.11.4")]
[assembly: AssemblyVersion("1.7.12.0")]
[assembly: AssemblyFileVersion("1.7.12.0")]
+21
View File
@@ -27,6 +27,8 @@ namespace Ink_Canvas
public RandSettings RandSettings { get; set; } = new RandSettings();
[JsonProperty("modeSettings")]
public ModeSettings ModeSettings { get; set; } = new ModeSettings();
[JsonProperty("camera")]
public CameraSettings Camera { get; set; } = new CameraSettings();
}
public class Canvas
@@ -199,6 +201,10 @@ namespace Ink_Canvas
public int UnFoldButtonImageType { get; set; }
[JsonProperty("isShowLRSwitchButton")]
public bool IsShowLRSwitchButton { get; set; }
[JsonProperty("enableSplashScreen")]
public bool EnableSplashScreen { get; set; } = false;
[JsonProperty("splashScreenStyle")]
public int SplashScreenStyle { get; set; } = 1; // 0-随机, 1-跟随四季, 2-春季, 3-夏季, 4-秋季, 5-冬季, 6-马年限定
[JsonProperty("isShowQuickPanel")]
public bool IsShowQuickPanel { get; set; } = true;
[JsonProperty("chickenSoupSource")]
@@ -678,4 +684,19 @@ namespace Ink_Canvas
[JsonProperty("isPPTOnlyMode")]
public bool IsPPTOnlyMode { get; set; } = false; // 是否为仅PPT模式,默认为false(正常模式)
}
public class CameraSettings
{
[JsonProperty("rotationAngle")]
public int RotationAngle { get; set; } = 0;
[JsonProperty("resolutionWidth")]
public int ResolutionWidth { get; set; } = 1920;
[JsonProperty("resolutionHeight")]
public int ResolutionHeight { get; set; } = 1080;
[JsonProperty("selectedCameraIndex")]
public int SelectedCameraIndex { get; set; } = 0;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 KiB

@@ -181,29 +181,65 @@
VerticalAlignment="Center" />
<!-- 摄像头控制面板 -->
<StackPanel Orientation="Horizontal"
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,8">
<!-- 摄像头选择下拉框 -->
<ComboBox Name="CameraSelectionComboBox"
Width="200"
Margin="4,0"
Background="#2d2d2d"
Foreground="White"
BorderBrush="#404040"
SelectionChanged="CameraSelectionComboBox_SelectionChanged" />
<!-- 第一行:摄像头选择和切换 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<!-- 摄像头选择下拉框 -->
<ComboBox Name="CameraSelectionComboBox"
Width="200"
Margin="4,0"
Background="#2d2d2d"
Foreground="White"
BorderBrush="#404040"
SelectionChanged="CameraSelectionComboBox_SelectionChanged" />
<!-- 切换摄像头按钮 -->
<Button Name="SwitchCameraButton"
Content="切换摄像头"
Margin="4,0"
Padding="8,4"
Background="#3b82f6"
Foreground="White"
BorderThickness="0"
FontWeight="Medium"
Click="SwitchCameraButton_Click" />
</StackPanel>
<!-- 切换摄像头按钮 -->
<Button Name="SwitchCameraButton"
Content="切换摄像头"
Margin="4,0"
Padding="8,4"
Background="#3b82f6"
Foreground="White"
BorderThickness="0"
FontWeight="Medium"
Click="SwitchCameraButton_Click" />
<!-- 第二行:旋转和分辨率控制 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,4,0,0">
<!-- 旋转控制 -->
<StackPanel Orientation="Horizontal" Margin="4,0">
<TextBlock Text="旋转:" Foreground="White" VerticalAlignment="Center" Margin="0,0,4,0" FontSize="12"/>
<Button Name="RotateLeftButton" Content="⟲" Width="30" Height="30" Margin="2,0"
Background="#4b5563" Foreground="White" BorderThickness="0"
Click="RotateLeftButton_Click" FontSize="10" FontWeight="10"/>
<TextBlock Name="RotationAngleText" Text="0°" Foreground="White" VerticalAlignment="Center"
Margin="4,0" FontSize="12" MinWidth="30" TextAlignment="Center"/>
<Button Name="RotateRightButton" Content="⟳" Width="30" Height="30" Margin="2,0"
Background="#4b5563" Foreground="White" BorderThickness="0"
Click="RotateRightButton_Click" FontSize="10" FontWeight="10"/>
</StackPanel>
<!-- 分隔线 -->
<Rectangle Width="1" Height="20" Fill="#404040" Margin="8,0"/>
<!-- 分辨率控制 -->
<StackPanel Orientation="Horizontal" Margin="4,0">
<TextBlock Text="分辨率:" Foreground="White" VerticalAlignment="Center" Margin="0,0,4,0" FontSize="12"/>
<ComboBox Name="ResolutionComboBox" Width="120" Margin="2,0"
Background="#2d2d2d" Foreground="White" BorderBrush="#404040"
SelectionChanged="ResolutionComboBox_SelectionChanged">
<ComboBoxItem Content="640x480" Tag="640,480"/>
<ComboBoxItem Content="800x600" Tag="800,600"/>
<ComboBoxItem Content="1024x768" Tag="1024,768"/>
<ComboBoxItem Content="1280x720" Tag="1280,720"/>
<ComboBoxItem Content="1920x1080" Tag="1920,1080"/>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
<!-- 摄像头状态指示 -->
@@ -104,12 +104,20 @@ namespace Ink_Canvas
{
try
{
_cameraService = new CameraService();
// 从设置中加载摄像头配置
var cameraSettings = MainWindow.Settings.Camera;
_cameraService = new CameraService(
cameraSettings.RotationAngle,
cameraSettings.ResolutionWidth,
cameraSettings.ResolutionHeight);
_cameraService.FrameReceived += CameraService_FrameReceived;
_cameraService.ErrorOccurred += CameraService_ErrorOccurred;
// 初始化摄像头选择下拉框
RefreshCameraComboBox();
// 初始化旋转和分辨率显示
InitializeCameraControls();
}
catch (Exception ex)
{
@@ -117,6 +125,26 @@ namespace Ink_Canvas
}
}
private void InitializeCameraControls()
{
if (_cameraService != null)
{
// 更新旋转角度显示
UpdateRotationDisplay();
// 设置分辨率下拉框
var currentResolution = $"{_cameraService.ResolutionWidth}x{_cameraService.ResolutionHeight}";
foreach (ComboBoxItem item in ResolutionComboBox.Items)
{
if (item.Tag?.ToString() == $"{_cameraService.ResolutionWidth},{_cameraService.ResolutionHeight}")
{
ResolutionComboBox.SelectedItem = item;
break;
}
}
}
}
private void RefreshCameraComboBox()
{
try
@@ -1213,6 +1241,70 @@ namespace Ink_Canvas
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
}
#region
private void RotateLeftButton_Click(object sender, RoutedEventArgs e)
{
if (_cameraService != null)
{
_cameraService.RotationAngle = (_cameraService.RotationAngle - 1 + 4) % 4;
UpdateRotationDisplay();
SaveCameraSettings();
}
}
private void RotateRightButton_Click(object sender, RoutedEventArgs e)
{
if (_cameraService != null)
{
_cameraService.RotationAngle = (_cameraService.RotationAngle + 1) % 4;
UpdateRotationDisplay();
SaveCameraSettings();
}
}
private void ResolutionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_cameraService != null && ResolutionComboBox.SelectedItem is ComboBoxItem selectedItem)
{
var resolution = selectedItem.Tag?.ToString();
if (!string.IsNullOrEmpty(resolution))
{
var parts = resolution.Split(',');
if (parts.Length == 2 &&
int.TryParse(parts[0], out int width) &&
int.TryParse(parts[1], out int height))
{
_cameraService.ResolutionWidth = width;
_cameraService.ResolutionHeight = height;
SaveCameraSettings();
}
}
}
}
private void UpdateRotationDisplay()
{
if (_cameraService != null)
{
var angle = _cameraService.RotationAngle * 90;
RotationAngleText.Text = $"{angle}°";
}
}
private void SaveCameraSettings()
{
if (_cameraService != null)
{
MainWindow.Settings.Camera.RotationAngle = _cameraService.RotationAngle;
MainWindow.Settings.Camera.ResolutionWidth = _cameraService.ResolutionWidth;
MainWindow.Settings.Camera.ResolutionHeight = _cameraService.ResolutionHeight;
MainWindow.SaveSettingsToFile();
}
}
#endregion
protected override void OnClosed(EventArgs e)
{
try
+69
View File
@@ -0,0 +1,69 @@
<Window x:Class="Ink_Canvas.Windows.SplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Ink Canvas"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
WindowState="Maximized"
Topmost="True"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen">
<Grid>
<!-- 背景遮罩 -->
<Rectangle Fill="#80000000" />
<!-- 启动图片容器 -->
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- 启动图片 -->
<Image x:Name="StartupImage"
Width="600"
Height="450"
Stretch="Uniform" />
<!-- 版本号显示 - 右上角 -->
<TextBlock x:Name="VersionTextBlock"
FontSize="12"
Foreground="FloralWhite"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,10,50,0"
Opacity="0.8"
Panel.ZIndex="1000" />
</Grid>
<!-- 加载文本 -->
<TextBlock x:Name="LoadingText"
Text="正在启动 Ink Canvas..."
FontSize="18"
FontWeight="SemiBold"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,200,0,0" />
<!-- 进度条 -->
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,276.5,0,0"
Width="532"
Height="10">
<!-- 进度条填充 -->
<Border x:Name="ProgressBarFill"
HorizontalAlignment="Left"
Width="0">
<Border.Style>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="0,0,7,7"/>
</Style>
</Border.Style>
</Border>
<!-- 进度条背景 -->
<Border x:Name="ProgressBarBackground"
CornerRadius="7"
BorderThickness="1"
ClipToBounds="True"/>
</Grid>
</Grid>
</Window>
+530
View File
@@ -0,0 +1,530 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.IO;
using Newtonsoft.Json;
namespace Ink_Canvas.Windows
{
/// <summary>
/// SplashScreen.xaml 的交互逻辑
/// </summary>
public partial class SplashScreen : Window
{
private DispatcherTimer _timer;
private int _loadingStep = 0;
private int _actualSplashStyle = 1;
private readonly string[] _loadingMessages = {
"正在启动 Ink Canvas...",
"正在初始化组件...",
"正在加载配置...",
"正在准备界面...",
"启动完成!"
};
public SplashScreen()
{
InitializeComponent();
InitializeSplashScreen();
}
private void InitializeSplashScreen()
{
// 设置窗口居中
WindowStartupLocation = WindowStartupLocation.CenterScreen;
// 设置版本号
SetVersionText();
// 加载启动图片并获取实际样式
_actualSplashStyle = LoadSplashImageWithStyle();
// 启动加载动画
StartLoadingAnimation();
}
private void StartLoadingAnimation()
{
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(1200)
};
_timer.Tick += Timer_Tick;
_timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
if (_loadingStep < _loadingMessages.Length)
{
LoadingText.Text = _loadingMessages[_loadingStep];
_loadingStep++;
}
else
{
_timer.Stop();
// 不要自动关闭启动画面,等待外部调用CloseSplashScreen
}
}
public void CloseSplashScreen()
{
// 添加淡出动画
var fadeOutAnimation = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromMilliseconds(300),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};
fadeOutAnimation.Completed += (s, e) =>
{
this.Close();
};
this.BeginAnimation(OpacityProperty, fadeOutAnimation);
}
/// <summary>
/// 设置加载进度(0-100
/// </summary>
/// <param name="progress">进度百分比</param>
public void SetProgress(int progress)
{
Dispatcher.Invoke(() =>
{
// 设置进度条颜色
SetProgressBarColor();
// 获取进度条容器的实际宽度
double containerWidth = ProgressBarBackground.ActualWidth;
if (containerWidth <= 0)
{
// 如果ActualWidth为0,使用设计时宽度
containerWidth = 530;
}
// 计算目标宽度
double targetWidth = containerWidth * (progress / 100.0);
// 创建Storyboard动画
var storyboard = new Storyboard();
// 创建宽度动画
var widthAnimation = new DoubleAnimation
{
From = ProgressBarFill.Width,
To = targetWidth,
Duration = TimeSpan.FromMilliseconds(300),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
// 设置动画目标
Storyboard.SetTarget(widthAnimation, ProgressBarFill);
Storyboard.SetTargetProperty(widthAnimation, new PropertyPath(Border.WidthProperty));
// 添加动画到Storyboard
storyboard.Children.Add(widthAnimation);
// 添加动画完成事件
storyboard.Completed += (s, e) =>
{
// 确保最终值正确设置
ProgressBarFill.Width = targetWidth;
// 根据进度调整圆角
if (progress >= 100)
{
// 进度100%时,底部角都是圆角
ProgressBarFill.CornerRadius = new CornerRadius(0, 0, 7, 7);
}
else
{
// 进度未满时,只有左侧是圆角
ProgressBarFill.CornerRadius = new CornerRadius(0, 0, 0, 7);
}
};
// 开始动画
storyboard.Begin();
});
}
/// <summary>
/// 设置加载消息
/// </summary>
/// <param name="message">加载消息</param>
public void SetLoadingMessage(string message)
{
// 使用实际选择的样式
SetLoadingMessage(message, _actualSplashStyle);
}
public void SetLoadingMessage(string message, int actualSplashStyle)
{
Dispatcher.Invoke(() =>
{
LoadingText.Text = message;
// 根据实际启动动画样式调整加载文本样式
if (actualSplashStyle == 6) // 马年限定
{
// 马年限定样式
LoadingText.FontSize = 12;
LoadingText.FontWeight = FontWeights.SemiBold;
LoadingText.Foreground = Brushes.White;
LoadingText.HorizontalAlignment = HorizontalAlignment.Center;
LoadingText.Margin = new Thickness(0,200,140,4);
}
else
{
// 默认样式
LoadingText.FontSize = 18;
LoadingText.FontWeight = FontWeights.SemiBold;
LoadingText.Foreground = Brushes.White;
LoadingText.HorizontalAlignment = HorizontalAlignment.Center;
LoadingText.Margin = new Thickness(0,200,0,0);
}
});
}
/// <summary>
/// 获取当前启动动画样式
/// </summary>
/// <returns>启动动画样式索引</returns>
private int GetCurrentSplashStyle()
{
try
{
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
if (File.Exists(settingsPath))
{
var json = File.ReadAllText(settingsPath);
dynamic obj = JsonConvert.DeserializeObject(json);
if (obj?["appearance"]?["splashScreenStyle"] != null)
{
return (int)obj["appearance"]["splashScreenStyle"];
}
}
return 1; // 默认跟随四季
}
catch
{
return 1; // 默认跟随四季
}
}
/// <summary>
/// 设置版本号文本
/// </summary>
private void SetVersionText()
{
try
{
var version = Assembly.GetExecutingAssembly().GetName().Version;
if (version != null)
{
VersionTextBlock.Text = $"v{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
else
{
VersionTextBlock.Text = "v5.0.4.0";
}
}
catch
{
VersionTextBlock.Text = "v5.0.4.0";
}
}
/// <summary>
/// 加载启动图片
/// </summary>
private void LoadSplashImage()
{
try
{
string imagePath = GetSplashImagePath();
if (!string.IsNullOrEmpty(imagePath))
{
StartupImage.Source = new System.Windows.Media.Imaging.BitmapImage(new Uri(imagePath));
}
}
catch (Exception ex)
{
// 如果加载失败,使用默认图片
System.Diagnostics.Debug.WriteLine($"加载启动图片失败: {ex.Message}");
}
}
/// <summary>
/// 加载启动图片并返回实际样式
/// </summary>
/// <returns>实际选择的样式</returns>
public int LoadSplashImageWithStyle()
{
try
{
int actualStyle;
string imagePath = GetSplashImagePath(out actualStyle);
if (!string.IsNullOrEmpty(imagePath))
{
StartupImage.Source = new System.Windows.Media.Imaging.BitmapImage(new Uri(imagePath));
}
return actualStyle;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"加载启动图片失败: {ex.Message}");
return GetActualStyle(1);
}
}
/// <summary>
/// 根据设置获取启动图片路径
/// </summary>
private string GetSplashImagePath()
{
try
{
// 读取设置
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
int splashStyle = 1; // 默认跟随四季
if (File.Exists(settingsPath))
{
var json = File.ReadAllText(settingsPath);
dynamic obj = JsonConvert.DeserializeObject(json);
if (obj?["appearance"]?["splashScreenStyle"] != null)
{
splashStyle = (int)obj["appearance"]["splashScreenStyle"];
}
}
// 根据样式选择图片
string imageName = GetImageNameByStyle(splashStyle);
return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
}
catch
{
string imageName = GetImageNameByStyle(1);
return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
}
}
/// <summary>
/// 根据设置获取启动图片路径和实际样式
/// </summary>
/// <param name="actualStyle">返回实际选择的样式</param>
/// <returns>图片路径</returns>
private string GetSplashImagePath(out int actualStyle)
{
try
{
// 读取设置
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
int splashStyle = 1; // 默认跟随四季
if (File.Exists(settingsPath))
{
var json = File.ReadAllText(settingsPath);
dynamic obj = JsonConvert.DeserializeObject(json);
if (obj?["appearance"]?["splashScreenStyle"] != null)
{
splashStyle = (int)obj["appearance"]["splashScreenStyle"];
}
}
// 根据样式选择图片,并获取实际样式
actualStyle = GetActualStyle(splashStyle);
string imageName = GetImageNameByStyle(splashStyle);
return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
}
catch
{
actualStyle = GetActualStyle(1);
string imageName = GetImageNameByStyle(1);
return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
}
}
/// <summary>
/// 获取实际样式
/// </summary>
/// <param name="style">设置中的样式</param>
/// <returns>实际选择的样式</returns>
private int GetActualStyle(int style)
{
switch (style)
{
case 0: // 随机
var random = new Random();
var randomStyles = new[] { 2, 3, 4, 5, 6 }; // 春季、夏季、秋季、冬季、马年限定
return randomStyles[random.Next(randomStyles.Length)];
case 1: // 跟随四季
var month = DateTime.Now.Month;
if (month >= 3 && month <= 5) return 2; // 春季
if (month >= 6 && month <= 8) return 3; // 夏季
if (month >= 9 && month <= 11) return 4; // 秋季
return 5; // 冬季
default:
return style;
}
}
/// <summary>
/// 根据样式获取图片名称
/// </summary>
private string GetImageNameByStyle(int style)
{
switch (style)
{
case 0: // 随机
var random = new Random();
var randomStyles = new[] { 2, 3, 4, 5, 6 }; // 春季、夏季、秋季、冬季、马年限定
return GetImageNameByStyle(randomStyles[random.Next(randomStyles.Length)]);
case 1: // 跟随四季
var month = DateTime.Now.Month;
if (month >= 3 && month <= 5) return GetImageNameByStyle(2); // 春季
if (month >= 6 && month <= 8) return GetImageNameByStyle(3); // 夏季
if (month >= 9 && month <= 11) return GetImageNameByStyle(4); // 秋季
return GetImageNameByStyle(5); // 冬季
case 2: // 春季
return "ICC Spring.png";
case 3: // 夏季
return "ICC Summer.png";
case 4: // 秋季
return "ICC Autumn.png";
case 5: // 冬季
return "ICC Winter.png";
case 6: // 马年限定
return "ICC Horse.png";
default:// 默认返回
return "ICC Horse.png";
}
}
/// <summary>
/// 根据实际样式设置进度条颜色
/// </summary>
private void SetProgressBarColor()
{
Color progressColor;
switch (_actualSplashStyle)
{
case 2: // 春季 - H=136, S=15, L=22
progressColor = HslToRgb(136, 15, 22);
break;
case 3: // 夏季 - H=6, S=15, L=22
progressColor = HslToRgb(6, 15, 22);
break;
case 4: // 秋季 - H=39, S=15, L=22
progressColor = HslToRgb(39, 15, 22);
break;
case 5: // 冬季 - H=204, S=15, L=22
progressColor = HslToRgb(204, 15, 22);
break;
case 6: // 马年限定 - 白色
progressColor = Colors.White;
break;
default: // 默认使用
progressColor = Colors.White;
break;
}
// 创建渐变画刷
var gradientBrush = new LinearGradientBrush
{
StartPoint = new System.Windows.Point(0, 0),
EndPoint = new System.Windows.Point(1, 0)
};
// 根据颜色类型设置渐变
if (_actualSplashStyle == 6) // 马年限定使用白色渐变
{
gradientBrush.GradientStops.Add(new GradientStop(Colors.White, 0));
gradientBrush.GradientStops.Add(new GradientStop(Color.FromArgb(200, 255, 255, 255), 0.5));
gradientBrush.GradientStops.Add(new GradientStop(Color.FromArgb(150, 255, 255, 255), 1));
}
else // 其他样式使用HSL颜色的渐变
{
var lighterColor = Color.FromArgb(255,
(byte)Math.Min(255, progressColor.R + 30),
(byte)Math.Min(255, progressColor.G + 30),
(byte)Math.Min(255, progressColor.B + 30));
var darkerColor = Color.FromArgb(255,
(byte)Math.Max(0, progressColor.R - 30),
(byte)Math.Max(0, progressColor.G - 30),
(byte)Math.Max(0, progressColor.B - 30));
gradientBrush.GradientStops.Add(new GradientStop(lighterColor, 0));
gradientBrush.GradientStops.Add(new GradientStop(progressColor, 0.5));
gradientBrush.GradientStops.Add(new GradientStop(darkerColor, 1));
}
ProgressBarFill.Background = gradientBrush;
}
/// <summary>
/// 将HSL颜色转换为RGB颜色
/// </summary>
/// <param name="h">色相 (0-360)</param>
/// <param name="s">饱和度 (0-100)</param>
/// <param name="l">亮度 (0-100)</param>
/// <returns>RGB颜色</returns>
private Color HslToRgb(double h, double s, double l)
{
// 将HSL值转换为0-1范围
h = h / 360.0;
s = s / 100.0;
l = l / 100.0;
double r, g, b;
if (s == 0)
{
// 无饱和度,为灰度
r = g = b = l;
}
else
{
double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
double p = 2 * l - q;
r = HueToRgb(p, q, h + 1.0 / 3.0);
g = HueToRgb(p, q, h);
b = HueToRgb(p, q, h - 1.0 / 3.0);
}
return Color.FromRgb(
(byte)Math.Round(r * 255),
(byte)Math.Round(g * 255),
(byte)Math.Round(b * 255)
);
}
/// <summary>
/// HSL颜色转换辅助方法
/// </summary>
private double HueToRgb(double p, double q, double t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
if (t < 1.0 / 2.0) return q;
if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
return p;
}
}
}
+3
View File
@@ -110,3 +110,6 @@
## License
GPLv3
## 项目引用
[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker)
+18 -1
View File
@@ -81,4 +81,21 @@ ICC CE 1.7.X.X更新日志
80. 修复切换模式后无法持续生效
81. 优化UI
82. 修复插入图片后使用多指书写导致图片消失
83. 优化PowerPoint联动增强
83. 优化PowerPoint联动增强
84. 修复墨迹克隆
85. 修复白板手势
86. 新增退出白板自动收纳
87. 修复进入PPT自动收纳
88. 更新计时器铃声及按钮样式
89. 更新更新弹窗样式
90. 完善悬浮窗拦截功能
91. 新增主题切换
92. 更新历史版本回滚页面样式
93. 修复悬浮窗拦截
94. 改进计时器
95. 新增配置文件损坏自动恢复
96. 修复手掌擦
97. 改进橡皮系统
98. 新增启动动画
99. 修复仅调色盘状态下浮动栏不居中
100. 修复希沃白板查杀与思锐希沃启动器导致的重复启动