From da8fc4a89e8e95f409ae1c20a7e8994610c6f46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 23 Dec 2024 02:51:22 +0800 Subject: [PATCH] 1222 --- .../BetterGenshinImpact.csproj | 1 + .../Core/Recorder/KeyMouseRecorder.cs | 24 +-- .../Core/Recorder/KeyMouseRecorderJsonLine.cs | 191 +++++++++++++++++ .../Core/Recorder/Model/MacroEvent.cs | 4 - .../Helpers/Device/TouchpadManager.cs | 83 ++++++++ .../Helpers/EnvironmentUtil.cs | 197 ++++++++++++++++++ .../ViewModel/MainWindowViewModel.cs | 2 + 7 files changed, 482 insertions(+), 20 deletions(-) create mode 100644 BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs create mode 100644 BetterGenshinImpact/Helpers/Device/TouchpadManager.cs create mode 100644 BetterGenshinImpact/Helpers/EnvironmentUtil.cs diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index ed76f6a9..8ad5dad2 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -68,6 +68,7 @@ + diff --git a/BetterGenshinImpact/Core/Recorder/KeyMouseRecorder.cs b/BetterGenshinImpact/Core/Recorder/KeyMouseRecorder.cs index 63c06c1d..038deb3d 100644 --- a/BetterGenshinImpact/Core/Recorder/KeyMouseRecorder.cs +++ b/BetterGenshinImpact/Core/Recorder/KeyMouseRecorder.cs @@ -11,6 +11,7 @@ using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; using System.Windows.Forms; +using BetterGenshinImpact.Helpers; using Vanara.PInvoke; namespace BetterGenshinImpact.Core.Recorder; @@ -22,8 +23,6 @@ public class KeyMouseRecorder public List MouseMoveToMacroEvents { get; } = []; public List MouseMoveByMacroEvents { get; } = []; - public DateTime StartTime { get; set; } = DateTime.UtcNow; - public uint StartTick { get; set; } = Kernel32.GetTickCount(); public DateTime LastOrientationDetection { get; set; } = DateTime.UtcNow; @@ -44,6 +43,10 @@ public class KeyMouseRecorder { // MacroEvents 需要以实际时间进行排序 MacroEvents.Sort((a, b) => a.Time.CompareTo(b.Time)); + // 删除为负数的时间 + MacroEvents.RemoveAll(m => m.Time < 0); + + var startTime = EnvironmentUtil.LastBootUpTime()! + TimeSpan.FromMilliseconds(StartTick); var rect = TaskContext.Instance().SystemInfo.CaptureAreaRect; KeyMouseScript keyMouseScript = new() @@ -58,8 +61,8 @@ public class KeyMouseRecorder Width = rect.Width, Height = rect.Height, RecordDpi = TaskContext.Instance().DpiScale, - StartTime = $"{StartTime:yyyy-MM-dd HH:mm:ss:ffff}", - StartTimeUnixTimestamp = (StartTime - new DateTime(1970, 1, 1)).TotalNanoseconds.ToString("F0") + StartTime = $"{startTime:yyyy-MM-dd HH:mm:ss:ffff}", + StartTimeUnixTimestamp = (startTime - new DateTime(1970, 1, 1)).Value.TotalNanoseconds.ToString("F0") } }; return JsonSerializer.Serialize(keyMouseScript, JsonOptions); @@ -143,23 +146,12 @@ public class KeyMouseRecorder public void MouseMoveBy(MouseState state, uint tick, bool save = false) { - uint prevTickCount = 0; - if (MouseMoveByMacroEvents.Count > 0) - { - prevTickCount = MouseMoveByMacroEvents[^1].TickCount - StartTick; - } - else - { - prevTickCount = tick - 5; // 减去间隔时间5ms - } - var mEvent = new MacroEvent { Type = MacroEventType.MouseMoveBy, MouseX = state.X, MouseY = state.Y, - Time = prevTickCount, - TickCount = tick + Time = tick - 5, }; MouseMoveByMacroEvents.Add(mEvent); if (save) diff --git a/BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs b/BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs new file mode 100644 index 00000000..6041d81d --- /dev/null +++ b/BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs @@ -0,0 +1,191 @@ +using BetterGenshinImpact.Core.Recorder.Model; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.GameTask.Common; +using BetterGenshinImpact.GameTask.Common.Map; +using Gma.System.MouseKeyHook; +using SharpDX.DirectInput; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Windows.Forms; +using BetterGenshinImpact.Helpers; +using Vanara.PInvoke; + +namespace BetterGenshinImpact.Core.Recorder; + +public class KeyMouseRecorderJsonLine +{ + public KeyMouseScriptInfo Info { get; set; } + + private readonly Channel _macroEventsChannel = Channel.CreateUnbounded(); + private readonly Channel _mouseMoveToMacroEventsChannel = Channel.CreateUnbounded(); + private readonly Channel _mouseMoveByMacroEventsChannel = Channel.CreateUnbounded(); + private readonly Task _consumerTask; + + public uint StartTick { get; set; } = Kernel32.GetTickCount(); + + public static readonly JsonSerializerOptions JsonOptions = new() + { + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + public KeyMouseRecorderJsonLine(string path) + { + var bootTime = EnvironmentUtil.LastBootUpTime(); + if (bootTime != null) + { + throw new Exception("无法获取系统启动时间"); + } + + var startTime = bootTime! + TimeSpan.FromMilliseconds(StartTick); + var rect = TaskContext.Instance().SystemInfo.CaptureAreaRect; + Info = new KeyMouseScriptInfo + { + X = rect.X, + Y = rect.Y, + Width = rect.Width, + Height = rect.Height, + RecordDpi = TaskContext.Instance().DpiScale, + StartTime = $"{startTime:yyyy-MM-dd HH:mm:ss:ffff}", + StartTimeUnixTimestamp = (startTime - new DateTime(1970, 1, 1)).Value.TotalNanoseconds.ToString("F0") + }; + var infoJson = JsonSerializer.Serialize(Info, JsonOptions); + File.WriteAllText(Path.Combine(path, "info.json"), infoJson); + + _consumerTask = Task.Run(async () => await ConsumeEventsAsync(path)); + } + + private async Task ConsumeEventsAsync(string path) + { + var tasks = new List + { + ConsumeChannelAsync(_macroEventsChannel, Path.Combine(path, "macroEvents.jsonl")), + ConsumeChannelAsync(_mouseMoveToMacroEventsChannel, Path.Combine(path, "mouseMoveToMacroEvents.jsonl")), + ConsumeChannelAsync(_mouseMoveByMacroEventsChannel, Path.Combine(path, "mouseMoveByMacroEvents.jsonl")) + }; + + await Task.WhenAll(tasks); + } + + private async Task ConsumeChannelAsync(Channel channel, string filePath) + { + await using var stream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None); + await using var writer = new StreamWriter(stream); + + await foreach (var macroEvent in channel.Reader.ReadAllAsync()) + { + var json = JsonSerializer.Serialize(macroEvent, JsonOptions); + await writer.WriteLineAsync(json); + await writer.FlushAsync(); + } + } + + private void AddEvent(Channel channel, MacroEvent macroEvent) + { + channel.Writer.TryWrite(macroEvent); + } + + public void KeyDown(KeyEventArgsExt e) + { + var time = e.Timestamp - StartTick; + AddEvent(_macroEventsChannel, new MacroEvent + { + Type = MacroEventType.KeyDown, + KeyCode = e.KeyValue, + Time = time + }); + } + + public void KeyUp(KeyEventArgsExt e) + { + var time = e.Timestamp - StartTick; + AddEvent(_macroEventsChannel, new MacroEvent + { + Type = MacroEventType.KeyUp, + KeyCode = e.KeyValue, + Time = time + }); + } + + public void MouseDown(MouseEventExtArgs e) + { + var time = e.Timestamp - StartTick; + AddEvent(_macroEventsChannel, new MacroEvent + { + Type = MacroEventType.MouseDown, + MouseX = e.X, + MouseY = e.Y, + MouseButton = e.Button.ToString(), + Time = time + }); + } + + public void MouseUp(MouseEventExtArgs e) + { + var time = e.Timestamp - StartTick; + AddEvent(_macroEventsChannel, new MacroEvent + { + Type = MacroEventType.MouseUp, + MouseX = e.X, + MouseY = e.Y, + MouseButton = e.Button.ToString(), + Time = time + }); + } + + public void MouseMoveTo(MouseEventExtArgs e, bool save = false) + { + var time = e.Timestamp - StartTick; + var mEvent = new MacroEvent + { + Type = MacroEventType.MouseMoveTo, + MouseX = e.X, + MouseY = e.Y, + Time = time + }; + AddEvent(_mouseMoveToMacroEventsChannel, mEvent); + if (save) + { + AddEvent(_macroEventsChannel, mEvent); + } + } + + public void MouseWheel(MouseEventExtArgs e) + { + var time = e.Timestamp - StartTick; + AddEvent(_macroEventsChannel, new MacroEvent + { + Type = MacroEventType.MouseWheel, + MouseY = e.Delta, // 120 的倍率 + Time = time + }); + } + + public void MouseMoveBy(MouseState state, uint tick, bool save = false) + { + + var mEvent = new MacroEvent + { + Type = MacroEventType.MouseMoveBy, + MouseX = state.X, + MouseY = state.Y, + Time = tick - 5 + }; + AddEvent(_mouseMoveByMacroEventsChannel, mEvent); + if (save) + { + AddEvent(_macroEventsChannel, mEvent); + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Core/Recorder/Model/MacroEvent.cs b/BetterGenshinImpact/Core/Recorder/Model/MacroEvent.cs index 07b605be..8c3b8c29 100644 --- a/BetterGenshinImpact/Core/Recorder/Model/MacroEvent.cs +++ b/BetterGenshinImpact/Core/Recorder/Model/MacroEvent.cs @@ -12,10 +12,6 @@ public class MacroEvent public int MouseY { get; set; } public string? MouseButton { get; set; } public double Time { get; set; } - - [JsonIgnore] - public uint TickCount { get; set; } - public int? CameraOrientation { get; set; } } diff --git a/BetterGenshinImpact/Helpers/Device/TouchpadManager.cs b/BetterGenshinImpact/Helpers/Device/TouchpadManager.cs new file mode 100644 index 00000000..ed2a9534 --- /dev/null +++ b/BetterGenshinImpact/Helpers/Device/TouchpadManager.cs @@ -0,0 +1,83 @@ +namespace BetterGenshinImpact.Helpers.Device; + +using System; +using System.Runtime.InteropServices; + +public static class TouchpadManager +{ + // 定义 DEVMODE 结构 + [StructLayout(LayoutKind.Sequential)] + private struct DEVMODE + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string dmDeviceName; + public ushort dmSpecVersion; + public ushort dmDriverVersion; + public ushort dmSize; + public ushort dmDriverExtra; + public uint dmFields; + public int dmPositionX; + public int dmPositionY; + public uint dmDisplayOrientation; + public uint dmDisplayFixedOutput; + public short dmColor; + public short dmDuplex; + public short dmYResolution; + public short dmTTOption; + public short dmCollate; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string dmFormName; + public ushort dmLogPixels; + public uint dmBitsPerPel; + public uint dmPelsWidth; + public uint dmPelsHeight; + public uint dmDisplayFlags; + public uint dmDisplayFrequency; + public uint dmICMMethod; + public uint dmICMIntent; + public uint dmMediaType; + public uint dmDitherType; + public uint dmReserved1; + public uint dmReserved2; + public uint dmPanningWidth; + public uint dmPanningHeight; + } + + // 导入 ChangeDisplaySettingsEx 函数 + [DllImport("user32.dll", CharSet = CharSet.Ansi)] + private static extern int ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, uint dwflags, IntPtr lParam); + + private const int DMDO_DEFAULT = 0; + private const int DMDO_90 = 1; + private const int DMDO_180 = 2; + private const int DMDO_270 = 3; + private const int CDS_UPDATEREGISTRY = 0x01; + private const int CDS_TEST = 0x02; + private const int CDS_FULLSCREEN = 0x04; + private const int CDS_GLOBAL = 0x08; + private const int CDS_SET_PRIMARY = 0x10; + private const int CDS_VIDEOPARAMETERS = 0x20; + private const int CDS_ENABLE_UNSAFE_MODES = 0x100; + private const int CDS_DISABLE_UNSAFE_MODES = 0x200; + private const int CDS_RESET = 0x40000000; + private const int CDS_NORESET = 0x10000000; + + public static void DisableTouchpad() + { + DEVMODE devMode = new DEVMODE(); + devMode.dmSize = (ushort)Marshal.SizeOf(typeof(DEVMODE)); + devMode.dmFields = 0x00080000; // DM_DISPLAYORIENTATION + devMode.dmDisplayOrientation = DMDO_180; // 旋转显示方向 + + int result = ChangeDisplaySettingsEx(null, ref devMode, IntPtr.Zero, CDS_UPDATEREGISTRY, IntPtr.Zero); + + if (result == 0) + { + Console.WriteLine("触控板已禁用。"); + } + else + { + Console.WriteLine("无法禁用触控板,错误代码: " + result); + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Helpers/EnvironmentUtil.cs b/BetterGenshinImpact/Helpers/EnvironmentUtil.cs new file mode 100644 index 00000000..6fc4ef4a --- /dev/null +++ b/BetterGenshinImpact/Helpers/EnvironmentUtil.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Runtime.InteropServices; +using NAudio.CoreAudioApi; +using Vanara.PInvoke; + +namespace BetterGenshinImpact.Helpers; + +public class EnvironmentUtil +{ + public static bool IsProcessRunning(string processName) + { + // // 检测 QQ + // bool isQQRunning = IsProcessRunning("QQ"); // QQ的进程名称可能需要确认 + // Console.WriteLine("QQ is " + (isQQRunning ? "running" : "not running")); + // + // // 检测微信 + // bool isWeChatRunning = IsProcessRunning("WeChat"); // WeChat的进程名称可能需要确认 + // Console.WriteLine("WeChat is " + (isWeChatRunning ? "running" : "not running")); + // + // // 检测飞书 + // bool isFeiShuRunning = IsProcessRunning("FeiShu"); // FeiShu的进程名称可能需要确认 + // Console.WriteLine("FeiShu is " + (isFeiShuRunning ? "running" : "not running")); + // 获取所有运行中的进程 + Process[] processes = Process.GetProcessesByName(processName); + return processes.Length > 0; + } + + public static DateTime? LastBootUpTime() + { + // 创建管理对象查询 + ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT LastBootUpTime FROM Win32_OperatingSystem"); + + foreach (ManagementObject queryObj in searcher.Get()) + { + // 获取系统开机时间 + DateTime bootTime = ManagementDateTimeConverter.ToDateTime(queryObj["LastBootUpTime"].ToString()); + Debug.WriteLine("系统开机时间: " + bootTime); + return bootTime; + } + + return null; + } + + public static uint GetMouseSpeed() + { + User32.SystemParametersInfo(User32.SPI.SPI_GETMOUSESPEED, out uint mouseSpeed); + return mouseSpeed; + } + + // 设置鼠标速度 + public static void SetMouseSpeed(uint speed) + { + User32.SystemParametersInfo(User32.SPI.SPI_SETMOUSESPEED, speed); + } + + //SPI_GETWHEELSCROLLLINES + public static uint GetWheelScrollLines() + { + User32.SystemParametersInfo(User32.SPI.SPI_GETWHEELSCROLLLINES, out uint wheelScrollLines); + return wheelScrollLines; + } + + // 设置鼠标滚轮滚动行数 + public static void SetWheelScrollLines(uint lines) + { + User32.SystemParametersInfo(User32.SPI.SPI_SETWHEELSCROLLLINES, lines); + } + + // SPI_GETMOUSETRAILS + public static uint GetMouseTrails() + { + User32.SystemParametersInfo(User32.SPI.SPI_GETMOUSETRAILS, out uint mouseTrails); + return mouseTrails; + } + + // 设置鼠标拖尾 + public static void SetMouseTrails(uint trails) + { + User32.SystemParametersInfo(User32.SPI.SPI_SETMOUSETRAILS, trails); + } + + // SPI_GETMOUSE + /// + /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event#remarks + /// + /// + public static (int, int, int) GetMouse() + { + int[] mouseParams = new int[3]; + IntPtr ptr = Marshal.AllocHGlobal(sizeof(int) * mouseParams.Length); + try + { + User32.SystemParametersInfo(User32.SPI.SPI_GETMOUSE, (uint)mouseParams.Length, ptr, User32.SPIF.None); + Marshal.Copy(ptr, mouseParams, 0, mouseParams.Length); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + return (mouseParams[0], mouseParams[1], mouseParams[2]); + } + + public static void PrintMouseSettings() + { + Debug.WriteLine("鼠标速度: " + GetMouseSpeed()); + Debug.WriteLine("鼠标滚轮滚动行数: " + GetWheelScrollLines()); + Debug.WriteLine("鼠标拖尾: " + GetMouseTrails()); + var (mouseThreshold1, mouseThreshold2, mouseThreshold3) = GetMouse(); + Debug.WriteLine("鼠标阈值1 Mouse Double Click Time (ms): " + mouseThreshold1); + Debug.WriteLine("鼠标阈值2 Mouse Buttons Swapped:: " + mouseThreshold2); + Debug.WriteLine("鼠标阈值3 Mouse Speed: " + mouseThreshold3); + + // Debug.WriteLine("Touchpad Enabled: " + IsTouchpadEnabled()); + + Debug.WriteLine("当前音量: " + GetMasterVolume()); + } + + public static bool IsTouchpadEnabled() + { + try + { + // Create a ManagementObjectSearcher to query for touchpad devices + ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PointingDevice"); + + foreach (ManagementObject obj in searcher.Get()) + { + // Check if the device is a touchpad + if (obj["Description"].ToString().ToLower().Contains("touchpad")) + { + // Get the status of the touchpad + string status = obj["Status"].ToString(); + + Debug.WriteLine($"Touchpad Status: {status}"); + + // Check if the touchpad is enabled + if (status.Equals("OK", StringComparison.OrdinalIgnoreCase)) + { + Debug.WriteLine("The touchpad is enabled."); + return true; + } + else + { + Debug.WriteLine("The touchpad is disabled."); + return false; + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"An error occurred: {ex.Message}"); + } + return true; + } + + public static List<(string, int)> GetCurrentSpeakerVolume() + { + int volume = 0; + var enumerator = new MMDeviceEnumerator(); + + //获取音频输出设备 + IEnumerable speakDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active).ToArray(); + + List<(string, int)> devices = new(); + foreach (var speakDevice in speakDevices) + { + Debug.WriteLine($"Device Friendly Name: {speakDevice.FriendlyName}"); + Debug.WriteLine($"Device Volume: {speakDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100}"); + volume = (int)(speakDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100); + devices.Add((speakDevice.FriendlyName, volume)); + } + return devices; + } + + public static float GetMasterVolume() + { + var enumerator = new MMDeviceEnumerator(); + var device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + return device.AudioEndpointVolume.MasterVolumeLevelScalar; + } + + public static void GetCurrentSpeakerVolume(int volume) + { + var enumerator = new MMDeviceEnumerator(); + IEnumerable speakDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active).ToArray(); + if (speakDevices.Count() > 0) + { + + MMDevice mMDevice = speakDevices.ToList()[0]; + mMDevice.AudioEndpointVolume.MasterVolumeLevelScalar = volume / 100.0f; + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs index 2ae83b01..9c8a4890 100644 --- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs @@ -118,6 +118,8 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel // 更新仓库 // ScriptRepoUpdater.Instance.AutoUpdate(); + + EnvironmentUtil.PrintMouseSettings(); } private async Task GetNewestInfoAsync()