diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index c66598b..80a3f23 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -199,6 +199,36 @@ public int FloatingWindowOpacity } } + + bool _floatingWindowShadowEnabled = true; + + [JsonPropertyName("floatingWindowShadowEnabled")] + public bool FloatingWindowShadowEnabled + { + get => _floatingWindowShadowEnabled; + set + { + if (value == _floatingWindowShadowEnabled) return; + _floatingWindowShadowEnabled = value; + OnPropertyChanged(); + } + } + + int _floatingWindowTheme = 0; + + [JsonPropertyName("floatingWindowTheme")] + public int FloatingWindowTheme + { + get => _floatingWindowTheme; + set + { + var normalized = value is 1 or 2 ? value : 0; + if (normalized == _floatingWindowTheme) return; + _floatingWindowTheme = normalized; + OnPropertyChanged(); + } + } + int _floatingWindowPositionX = 100; [JsonPropertyName("floatingWindowPositionX")] diff --git a/Controls/Components/NetworkStatusComponent.axaml.cs b/Controls/Components/NetworkStatusComponent.axaml.cs index f6dfa19..430b6f0 100644 --- a/Controls/Components/NetworkStatusComponent.axaml.cs +++ b/Controls/Components/NetworkStatusComponent.axaml.cs @@ -22,16 +22,13 @@ namespace SystemTools.Controls.Components; )] public partial class NetworkStatusComponent : ComponentBase, INotifyPropertyChanged { - private const int AutoModeIcmpRetryInterval = 60; - private readonly DispatcherTimer _timer; private readonly HttpClient _httpClient; private readonly SemaphoreSlim _checkSemaphore = new(1, 1); private string _statusText = "--"; private IBrush _statusBrush = new SolidColorBrush(Colors.Gray); - private bool _autoModeUseHttp; - private int _httpDetectCountSinceIcmp; + private bool _autoModeForceHttpUntilRestart; public string StatusText { @@ -95,8 +92,6 @@ private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs { if (e.PropertyName == nameof(Settings.DetectMode)) { - _autoModeUseHttp = false; - _httpDetectCountSinceIcmp = 0; _ = CheckNetworkStatusAsync(); return; } @@ -145,40 +140,19 @@ private async Task CheckNetworkStatusAsync() break; case NetworkDetectMode.Auto: default: - if (!_autoModeUseHttp) + if (!_autoModeForceHttpUntilRestart) { var autoIcmpResult = await TryIcmpPingAsync(url); if (autoIcmpResult.Success) { - _httpDetectCountSinceIcmp = 0; delay = autoIcmpResult.Delay; + break; } - else - { - _autoModeUseHttp = true; - _httpDetectCountSinceIcmp = 0; - delay = await TryHttpPingAsync(url); - } - } - else - { - _httpDetectCountSinceIcmp++; - if (_httpDetectCountSinceIcmp >= AutoModeIcmpRetryInterval) - { - _httpDetectCountSinceIcmp = 0; - var retryIcmpResult = await TryIcmpPingAsync(url); - if (retryIcmpResult.Success) - { - _autoModeUseHttp = false; - delay = retryIcmpResult.Delay; - break; - } - } - - delay = await TryHttpPingAsync(url); + _autoModeForceHttpUntilRestart = true; } + delay = await TryHttpPingAsync(url); break; } @@ -218,7 +192,7 @@ private async Task TryIcmpPingAsync(string url) { if (reply.RoundtripTime <= 0) { - return IcmpProbeResult.Fail("错误"); + return IcmpProbeResult.Fail("0ms"); } return IcmpProbeResult.Ok(reply.RoundtripTime); diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index 67df151..d6b4050 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -28,6 +28,12 @@ public class FloatingWindowService private const uint WinEventSkipOwnProcess = 2; private static readonly HWND HwndBottom = new(1); private static readonly HWND HwndTopmost = new(-1); + private const int WhMouseLl = 14; + private const int WmMouseMove = 0x0200; + private const int WmLButtonDown = 0x0201; + private const int WmRButtonDown = 0x0204; + private const ulong MiWpSignatureMask = 0xFFFFFF00UL; + private const ulong MiWpSignature = 0xFF515700UL; private readonly MainConfigHandler _configHandler; private readonly Dictionary _entries = new(); @@ -43,7 +49,7 @@ public class FloatingWindowService private readonly Dictionary _buttonWidthCache = new(); private bool _allowWindowClose; private bool _restoringFromMinimized; - private bool _isTouchInputMode; + private bool _isTouchDeviceDetected; private bool _touchDragAllowed; private PixelPoint _touchDragStartScreenPoint; private PixelPoint _touchDragStartWindowPosition; @@ -51,12 +57,16 @@ public class FloatingWindowService private IntPtr _foregroundHook; private IntPtr _reorderHook; private WinEventProc? _winEventProc; + private IntPtr _mouseHook; + private LowLevelMouseProc? _lowLevelMouseProc; private DispatcherTimer LayerRecheck50MsTimer { get; } = new() { Interval = TimeSpan.FromMilliseconds(50) }; private DispatcherTimer LayerRecheck1MsTimer { get; } = new() { Interval = TimeSpan.FromMilliseconds(1) }; private delegate void WinEventProc(IntPtr hWinEventHook, uint @event, IntPtr hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime); + private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam); + [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); @@ -64,6 +74,15 @@ private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPt [DllImport("user32.dll", SetLastError = true)] private static extern bool UnhookWinEvent(IntPtr hWinEventHook); + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool UnhookWindowsHookEx(IntPtr hhk); + + [DllImport("user32.dll")] + private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); + public event EventHandler? EntriesChanged; public FloatingWindowService(MainConfigHandler configHandler) @@ -79,6 +98,7 @@ public void Start() { EnsureWindow(); EnsureLayerRecheckHooks(); + EnsureGlobalInputHooks(); SubscribeThemeChanged(); ApplyVisibility(); RefreshLayerRecheckMode(); @@ -101,6 +121,7 @@ public void Stop() LayerRecheck50MsTimer.Stop(); LayerRecheck1MsTimer.Stop(); RemoveLayerRecheckHooks(); + RemoveGlobalInputHooks(); UnsubscribeThemeChanged(); }); } @@ -176,16 +197,26 @@ private void UnsubscribeThemeChanged() private void OnApplicationPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { - if (string.Equals(e.Property?.Name, "ActualThemeVariant", StringComparison.Ordinal)) + if (string.Equals(e.Property?.Name, "ActualThemeVariant", StringComparison.Ordinal) + && _configHandler.Data.FloatingWindowTheme == 0) { Dispatcher.UIThread.Post(RefreshWindowButtons); } } + private ThemeVariant ResolveWindowThemeVariant() + { + return _configHandler.Data.FloatingWindowTheme switch + { + 1 => ThemeVariant.Light, + 2 => ThemeVariant.Dark, + _ => _window?.ActualThemeVariant ?? Application.Current?.ActualThemeVariant ?? ThemeVariant.Dark + }; + } + private bool IsLightTheme() { - var theme = _window?.ActualThemeVariant ?? Application.Current?.ActualThemeVariant; - return theme == ThemeVariant.Light; + return ResolveWindowThemeVariant() == ThemeVariant.Light; } private void EnsureWindow() @@ -330,6 +361,16 @@ private void RefreshWindowButtons() if (_windowContainer != null) { _windowContainer.Background = windowBackground; + _windowContainer.BoxShadow = _configHandler.Data.FloatingWindowShadowEnabled + ? new BoxShadows(new BoxShadow + { + OffsetX = 0, + OffsetY = 6 * scale, + Blur = 18 * scale, + Spread = 0, + Color = isLightTheme ? Color.Parse("#28000000") : Color.Parse("#60000000") + }) + : default; } _stackPanel.Orientation = Orientation.Vertical; @@ -339,7 +380,7 @@ private void RefreshWindowButtons() _stackPanel.Children.Clear(); - if (_isTouchInputMode) + if (_isTouchDeviceDetected) { _touchDragHandle = CreateTouchDragHandle(scale, contentForeground); _stackPanel.Children.Add(_touchDragHandle); @@ -536,9 +577,9 @@ private void OnPointerPressed(object? sender, PointerPressedEventArgs e) UpdateInputMode(e.Pointer.Type); - if (_isTouchInputMode) + if (_isTouchDeviceDetected) { - if (!IsEventFromTouchDragHandle(e.Source)) + if (e.Pointer.Type != PointerType.Touch || !IsEventFromTouchDragHandle(e.Source)) { _touchDragAllowed = false; return; @@ -571,9 +612,9 @@ private void OnPointerMoved(object? sender, PointerEventArgs e) UpdateInputMode(e.Pointer.Type); - if (_isTouchInputMode) + if (_isTouchDeviceDetected) { - if (!_touchDragAllowed) + if (e.Pointer.Type != PointerType.Touch || !_touchDragAllowed) { return; } @@ -618,8 +659,13 @@ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) UpdateInputMode(e.Pointer.Type); - if (_isTouchInputMode) + if (_isTouchDeviceDetected) { + if (e.Pointer.Type != PointerType.Touch) + { + return; + } + var wasTouchDragging = _touchDragAllowed; _touchDragAllowed = false; if (!wasTouchDragging) @@ -684,13 +730,26 @@ private bool IsEventFromTouchDragHandle(object? source) private void UpdateInputMode(PointerType pointerType) { - var isTouchMode = pointerType == PointerType.Touch; - if (isTouchMode == _isTouchInputMode) + if (pointerType == PointerType.Touch) + { + SetTouchInputMode(true); + return; + } + + if (pointerType == PointerType.Mouse || pointerType == PointerType.Pen) + { + SetTouchInputMode(false); + } + } + + private void SetTouchInputMode(bool isTouch) + { + if (_isTouchDeviceDetected == isTouch) { return; } - _isTouchInputMode = isTouchMode; + _isTouchDeviceDetected = isTouch; _pointerPressed = false; _dragInitiated = false; _lastPressedArgs = null; @@ -698,6 +757,66 @@ private void UpdateInputMode(PointerType pointerType) Dispatcher.UIThread.Post(RefreshWindowButtons); } + private void EnsureGlobalInputHooks() + { + if (_mouseHook != IntPtr.Zero) + { + return; + } + + _lowLevelMouseProc ??= OnLowLevelMouse; + _mouseHook = SetWindowsHookEx(WhMouseLl, _lowLevelMouseProc, IntPtr.Zero, 0); + } + + private void RemoveGlobalInputHooks() + { + if (_mouseHook == IntPtr.Zero) + { + return; + } + + UnhookWindowsHookEx(_mouseHook); + _mouseHook = IntPtr.Zero; + } + + private IntPtr OnLowLevelMouse(int nCode, IntPtr wParam, IntPtr lParam) + { + if (nCode < 0 || lParam == IntPtr.Zero) + { + return CallNextHookEx(_mouseHook, nCode, wParam, lParam); + } + + var message = unchecked((uint)wParam.ToInt64()); + if (message != WmMouseMove && message != WmLButtonDown && message != WmRButtonDown) + { + return CallNextHookEx(_mouseHook, nCode, wParam, lParam); + } + + var info = Marshal.PtrToStructure(lParam); + var extra = unchecked((ulong)info.dwExtraInfo.ToInt64()); + var isTouchGenerated = (extra & MiWpSignatureMask) == MiWpSignature; + + SetTouchInputMode(isTouchGenerated); + return CallNextHookEx(_mouseHook, nCode, wParam, lParam); + } + + [StructLayout(LayoutKind.Sequential)] + private struct POINT + { + public int X; + public int Y; + } + + [StructLayout(LayoutKind.Sequential)] + private struct MSLLHOOKSTRUCT + { + public POINT pt; + public uint mouseData; + public uint flags; + public uint time; + public IntPtr dwExtraInfo; + } + private PixelRect GetWindowRect(PixelPoint position) { if (_window == null) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index a3b340b..87ce998 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -113,6 +113,32 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 576d815..92130f6 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -64,6 +64,8 @@ or nameof(MainConfigData.FloatingWindowScale) or nameof(MainConfigData.FloatingWindowIconSize) or nameof(MainConfigData.FloatingWindowTextSize) or nameof(MainConfigData.FloatingWindowOpacity) + or nameof(MainConfigData.FloatingWindowTheme) + or nameof(MainConfigData.FloatingWindowShadowEnabled) or nameof(MainConfigData.FloatingWindowLayer) or nameof(MainConfigData.FloatingWindowLayerRecheckMode)) {