diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index 6c755e03..996ca54d 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -30,6 +30,9 @@ using Wpf.Ui; using Wpf.Ui.DependencyInjection; using Wpf.Ui.Violeta.Controls; +// Wine 平台适配 +using BetterGenshinImpact.Platform.Wine; + namespace BetterGenshinImpact; public partial class App : Application @@ -176,6 +179,8 @@ public partial class App : Application /// protected override async void OnStartup(StartupEventArgs e) { + // Wine 平台适配 + WinePlatformAddon.ApplyApplicationConfig(); base.OnStartup(e); try diff --git a/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs b/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs index 79b0c4ea..e6a5867a 100644 --- a/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs +++ b/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs @@ -11,10 +11,13 @@ using System.Windows.Forms; using Vanara.PInvoke; using Timer = System.Timers.Timer; +// Wine 平台适配 +using BetterGenshinImpact.Platform.Wine; namespace BetterGenshinImpact.Core.Monitor; -public class MouseKeyMonitor +public partial class MouseKeyMonitor { + /// /// 长按F变F连发 /// @@ -68,12 +71,15 @@ public class MouseKeyMonitor _hWnd = gameHandle; // Note: for the application hook, use the Hook.AppEvents() instead - GlobalHook.KeyDown += GlobalHookKeyDown; - GlobalHook.KeyUp += GlobalHookKeyUp; - GlobalHook.MouseDownExt += GlobalHookMouseDownExt; - GlobalHook.MouseUpExt += GlobalHookMouseUpExt; - GlobalHook.MouseMoveExt += GlobalHookMouseMoveExt; - GlobalHook.MouseWheelExt += GlobalHookMouseWheelExt; + if (!WinePlatformAddon.IsRunningOnWine) { + GlobalHook.KeyDown += GlobalHookKeyDown; + GlobalHook.KeyUp += GlobalHookKeyUp; + GlobalHook.MouseDownExt += GlobalHookMouseDownExt; + GlobalHook.MouseUpExt += GlobalHookMouseUpExt; + GlobalHook.MouseMoveExt += GlobalHookMouseMoveExt; + GlobalHook.MouseWheelExt += GlobalHookMouseWheelExt; + } + TrySubscribeWinePolling(); //_globalHook.KeyPress += GlobalHookKeyPress; _pickUpKey = TaskContext.Instance().Config.KeyBindingsConfig.PickUpOrInteract.ToWinFormKeys(); @@ -208,7 +214,7 @@ public class MouseKeyMonitor public void Unsubscribe() { - if (_globalHook != null) + if (_globalHook != null && !WinePlatformAddon.IsRunningOnWine) { _globalHook.KeyDown -= GlobalHookKeyDown; _globalHook.KeyUp -= GlobalHookKeyUp; @@ -220,5 +226,8 @@ public class MouseKeyMonitor _globalHook.Dispose(); _globalHook = null; } + if (WinePlatformAddon.IsRunningOnWine){ + DisposeWineAddon(); + } } } diff --git a/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs b/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs index fafa51db..604fdbfe 100644 --- a/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs +++ b/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Windows; using System.Windows.Interop; using System.Windows.Media; +using BetterGenshinImpact.Platform.Wine; using Vanara.PInvoke; namespace BetterGenshinImpact.Helpers.DpiAwareness; @@ -20,7 +21,28 @@ internal class DpiAwarenessController static DpiAwarenessController() { - SHCore.SetProcessDpiAwareness(SHCore.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE).ThrowIfFailed(); + if (WinePlatformAddon.IsRunningOnWine) + { + try + { + SHCore + .SetProcessDpiAwareness( + SHCore.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE + ) + .ThrowIfFailed(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine( + $"Failed to set DPI awareness in Wine: {ex.Message}" + ); + } + } + else{ + SHCore + .SetProcessDpiAwareness(SHCore.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE) + .ThrowIfFailed(); + } } /// diff --git a/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs b/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs index 567ceb83..bfa8a957 100644 --- a/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs +++ b/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs @@ -4,6 +4,7 @@ using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask; using Wpf.Ui.Controls; +using BetterGenshinImpact.Platform.Wine; namespace BetterGenshinImpact.Helpers.Ui; public class WindowHelper @@ -11,7 +12,23 @@ public class WindowHelper public static void TryApplySystemBackdrop(System.Windows.Window window) { var themeType = TaskContext.Instance().Config.CommonConfig.CurrentThemeType; - ApplyThemeToWindow(window, themeType); + + // Wine 平台适配 + if (WinePlatformAddon.IsRunningOnWine) + { + try + { + ApplyThemeToWindow(window, themeType); + } + catch + { + System.Diagnostics.Debug.WriteLine($"Failed to apply theme in Wine"); + } + } + else + { + ApplyThemeToWindow(window, themeType); + }; } /// diff --git a/BetterGenshinImpact/View/MaskWindow.xaml b/BetterGenshinImpact/View/MaskWindow.xaml index fc7a884d..173cb795 100644 --- a/BetterGenshinImpact/View/MaskWindow.xaml +++ b/BetterGenshinImpact/View/MaskWindow.xaml @@ -6,6 +6,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:viewModel="clr-namespace:BetterGenshinImpact.ViewModel" + xmlns:platform="clr-namespace:BetterGenshinImpact.Platform.Wine" Title="MaskWindow" Width="500" Height="800" @@ -27,7 +28,7 @@ - + + + diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs index 2a34fa24..4bbfdff0 100644 --- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs @@ -34,6 +34,8 @@ using BetterGenshinImpact.ViewModel.Windows; using Wpf.Ui; using Wpf.Ui.Controls; +using BetterGenshinImpact.Platform.Wine; + namespace BetterGenshinImpact.ViewModel; public partial class MainWindowViewModel : ObservableObject, IViewModel @@ -164,6 +166,13 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel _logger.LogInformation($"主题类型已从 {originalThemeType} 修正为 {themeType},因为当前系统不支持该主题效果"); } + if (WinePlatformAddon.IsRunningOnWine) + { + // Wine 平台下不应用主题 + _logger.LogInformation("检测到运行在 Wine 平台,跳过主题应用"); + return; + } + switch (themeType) { case ThemeType.DarkNone: @@ -429,8 +438,8 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel deviceId = "default"; // 如果获取设备ID失败,使用默认值 } - // 每个设备只运行一次 - if (!Config.CommonConfig.OnceHadRunDeviceIdList.Contains(deviceId)) + // 每个设备只运行一次 | 在Wine上会崩溃 + if (!Config.CommonConfig.OnceHadRunDeviceIdList.Contains(deviceId) && !WinePlatformAddon.IsRunningOnWine) { WelcomeDialog prompt = new WelcomeDialog { diff --git a/BetterGenshinImpact/Wine/MouseKeyMonitor.Wine.cs b/BetterGenshinImpact/Wine/MouseKeyMonitor.Wine.cs new file mode 100644 index 00000000..1d9fbaa6 --- /dev/null +++ b/BetterGenshinImpact/Wine/MouseKeyMonitor.Wine.cs @@ -0,0 +1,103 @@ +using System; +using System.Windows.Forms; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.Platform.Wine; + +namespace BetterGenshinImpact.Core.Monitor +{ + public partial class MouseKeyMonitor + { + private WinePlatformAddon _wineAddon; + + /// + /// [Wine专用] 尝试初始化轮询机制 + /// + private void TrySubscribeWinePolling() + { + // 如果不是 Wine,直接返回,确保安全 + if (!WinePlatformAddon.IsRunningOnWine) + return; + + // 初始化 Addon 工具 + _wineAddon = new WinePlatformAddon(null); + + // 启动轮询,指向下方的 PollingLoop + _wineAddon.StartPolling(PollingLoop, 15); + } + + /// + /// [Wine专用] 轮询循环逻辑 + /// + private void PollingLoop() + { + // 1. 处理 F 键 (交互键) + bool isFDown = WinePlatformAddon.IsKeyDown(_pickUpKey); + if (isFDown) + { + if (_firstFKeyDownTime == DateTime.MaxValue) + { + _firstFKeyDownTime = DateTime.Now; // 刚按下 + } + else + { + // 按住中:检查是否达到连发阈值 + var timeSpan = DateTime.Now - _firstFKeyDownTime; + if ( + timeSpan.TotalMilliseconds > 200 + && TaskContext.Instance().Config.MacroConfig.FPressHoldToContinuationEnabled + && !_fTimer.Enabled + ) + { + _fTimer.Start(); + } + } + } + else + { + // 松开 + if (_firstFKeyDownTime != DateTime.MaxValue) + { + _firstFKeyDownTime = DateTime.MaxValue; + _fTimer.Stop(); + } + } + + // 2. 处理 Space 键 (跳跃键) + bool isSpaceDown = WinePlatformAddon.IsKeyDown(_releaseControlKey); + if (isSpaceDown) + { + if (_firstSpaceKeyDownTime == DateTime.MaxValue) + { + _firstSpaceKeyDownTime = DateTime.Now; + } + else + { + var timeSpan = DateTime.Now - _firstSpaceKeyDownTime; + if ( + timeSpan.TotalMilliseconds > 300 + && TaskContext + .Instance() + .Config.MacroConfig.SpacePressHoldToContinuationEnabled + && !_spaceTimer.Enabled + ) + { + _spaceTimer.Start(); + } + } + } + else + { + if (_firstSpaceKeyDownTime != DateTime.MaxValue) + { + _firstSpaceKeyDownTime = DateTime.MaxValue; + _spaceTimer.Stop(); + } + } + } + + private void DisposeWineAddon() + { + _wineAddon?.Dispose(); + } + } +} diff --git a/BetterGenshinImpact/Wine/WinePlatformAddon.cs b/BetterGenshinImpact/Wine/WinePlatformAddon.cs new file mode 100644 index 00000000..79ff5954 --- /dev/null +++ b/BetterGenshinImpact/Wine/WinePlatformAddon.cs @@ -0,0 +1,160 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using System.Windows.Interop; +using System.Windows.Media; +using Microsoft.Extensions.Logging; + +namespace BetterGenshinImpact.Platform.Wine +{ + /// + /// Wine/Linux 兼容性增强插件 + /// 包含:全局环境检测、启动配置、定时轮询工具 + /// + public class WinePlatformAddon : IDisposable + { + // ============================================================= + // Part 1: 静态成员 (全局检测、配置、工具方法) + // ============================================================= + + private static bool? _isWineCached; + + /// + /// [全局静态] 检查是否运行在 Wine 环境中 + /// + public static bool IsRunningOnWine + { + get + { + _isWineCached ??= CheckIsRunningOnWine(); + return _isWineCached.Value; + } + } + + /// + /// [全局静态] 应用程序启动初始化配置 (App.xaml.cs 调用) + /// + public static void ApplyApplicationConfig() + { + if (IsRunningOnWine) + { + // 强制使用软件渲染,解决 WPF 硬件加速在 Wine 下的黑屏/崩溃问题 + RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly; + System.Diagnostics.Debug.WriteLine("【兼容层】Wine环境检测:已启用软件渲染模式。"); + } + } + + /// + /// [全局静态] 检查物理按键状态 (封装 Win32 API) + /// + public static bool IsKeyDown(Keys key) + { + // 最高位为 1 表示按下 + return (GetAsyncKeyState((int)key) & 0x8000) != 0; + } + + // ============================================================= + // Part 2: 实例成员 (定时器生命周期管理) + // ============================================================= + + private readonly System.Timers.Timer _pollingTimer; + private Action? _pollingAction; + private readonly ILogger? _logger; + + /// + /// 构造函数 + /// + /// 可选日志记录器 + public WinePlatformAddon(ILogger? logger = null) + { + _logger = logger; + _pollingTimer = new System.Timers.Timer(); + _pollingTimer.Elapsed += OnTimerElapsed; + // 默认为 AutoReset = true (循环触发) + } + + /// + /// 启动定时任务 (例如:位置同步、按键轮询) + /// + /// 每帧执行的回调 + /// 间隔毫秒数 + public void StartPolling(Action action, int intervalMs) + { + if (!IsRunningOnWine) + return; // Windows 环境下直接忽略,不启动定时器 + + _pollingAction = action; + _pollingTimer.Interval = intervalMs; + + if (!_pollingTimer.Enabled) + { + _pollingTimer.Start(); + _logger?.LogInformation($"【兼容层】已启动轮询定时器 (Interval: {intervalMs}ms)"); + } + } + + /// + /// 停止定时任务 + /// + public void StopPolling() + { + if (_pollingTimer.Enabled) + { + _pollingTimer.Stop(); + } + } + + private void OnTimerElapsed(object? sender, System.Timers.ElapsedEventArgs e) + { + try + { + _pollingAction?.Invoke(); + } + catch (Exception ex) + { + // 避免轮询线程崩溃影响主程序 + _logger?.LogDebug(ex, "WinePlatformAddon Polling Error"); + } + } + + public void Dispose() + { + StopPolling(); + _pollingTimer.Dispose(); + } + + // ============================================================= + // Part 3: 内部 P/Invoke (对外隐藏) + // ============================================================= + + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + private static extern IntPtr GetModuleHandle(string lpModuleName); + + [DllImport( + "kernel32.dll", + CharSet = CharSet.Ansi, + ExactSpelling = true, + SetLastError = true + )] + private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [DllImport("user32.dll")] + private static extern short GetAsyncKeyState(int vKey); + + private static bool CheckIsRunningOnWine() + { + try + { + IntPtr hModule = GetModuleHandle("ntdll.dll"); + if (hModule == IntPtr.Zero) + return false; + IntPtr fPtr = GetProcAddress(hModule, "wine_get_version"); + return fPtr != IntPtr.Zero; + } + catch + { + return false; + } + } + } +}