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;
+ }
+ }
+ }
+}