Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions ConfigHandlers/MainConfigData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
38 changes: 6 additions & 32 deletions Controls/Components/NetworkStatusComponent.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,13 @@ namespace SystemTools.Controls.Components;
)]
public partial class NetworkStatusComponent : ComponentBase<NetworkStatusSettings>, 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
{
Expand Down Expand Up @@ -95,8 +92,6 @@ private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs
{
if (e.PropertyName == nameof(Settings.DetectMode))
{
_autoModeUseHttp = false;
_httpDetectCountSinceIcmp = 0;
_ = CheckNetworkStatusAsync();
return;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -218,7 +192,7 @@ private async Task<IcmpProbeResult> TryIcmpPingAsync(string url)
{
if (reply.RoundtripTime <= 0)
{
return IcmpProbeResult.Fail("错误");
return IcmpProbeResult.Fail("0ms");
}

return IcmpProbeResult.Ok(reply.RoundtripTime);
Expand Down
145 changes: 132 additions & 13 deletions Services/FloatingWindowService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FloatingWindowTrigger, FloatingWindowEntry> _entries = new();
Expand All @@ -43,27 +49,40 @@ public class FloatingWindowService
private readonly Dictionary<string, double> _buttonWidthCache = new();
private bool _allowWindowClose;
private bool _restoringFromMinimized;
private bool _isTouchInputMode;
private bool _isTouchDeviceDetected;
private bool _touchDragAllowed;
private PixelPoint _touchDragStartScreenPoint;
private PixelPoint _touchDragStartWindowPosition;
private Border? _touchDragHandle;
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);

[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)
Expand All @@ -79,6 +98,7 @@ public void Start()
{
EnsureWindow();
EnsureLayerRecheckHooks();
EnsureGlobalInputHooks();
SubscribeThemeChanged();
ApplyVisibility();
RefreshLayerRecheckMode();
Expand All @@ -101,6 +121,7 @@ public void Stop()
LayerRecheck50MsTimer.Stop();
LayerRecheck1MsTimer.Stop();
RemoveLayerRecheckHooks();
RemoveGlobalInputHooks();
UnsubscribeThemeChanged();
});
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand All @@ -339,7 +380,7 @@ private void RefreshWindowButtons()

_stackPanel.Children.Clear();

if (_isTouchInputMode)
if (_isTouchDeviceDetected)
{
_touchDragHandle = CreateTouchDragHandle(scale, contentForeground);
_stackPanel.Children.Add(_touchDragHandle);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -684,20 +730,93 @@ 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;
_touchDragAllowed = false;
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<MSLLHOOKSTRUCT>(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)
Expand Down
Loading
Loading