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
190 changes: 130 additions & 60 deletions Actions/ChangeWallpaperAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.Win32;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
Expand All @@ -22,81 +21,53 @@ protected override async Task OnInvoke()
{
_logger.LogDebug("ChangeWallpaperAction OnInvoke 开始");

if (Settings == null || string.IsNullOrWhiteSpace(Settings.ImagePath))
if (Settings == null)
{
_logger.LogWarning("壁纸设置为空");
return;
}

if (Settings.Mode == ChangeWallpaperMode.Image && string.IsNullOrWhiteSpace(Settings.ImagePath))
{
_logger.LogWarning("图片路径为空");
return;
}

if (!File.Exists(Settings.ImagePath))
if (Settings.Mode == ChangeWallpaperMode.Image && !File.Exists(Settings.ImagePath))
{
_logger.LogError("图片文件不存在: {Path}", Settings.ImagePath);
throw new FileNotFoundException("指定的图片文件不存在", Settings.ImagePath);
}

try
{
var imagePath = Settings.ImagePath;
var fit = Settings.FitStyle;
_logger.LogInformation("正在切换壁纸到: {Path}, FitStyle: {Fit}", imagePath, fit);

// 根据 fitStyle 计算注册表值(TileWallpaper, WallpaperStyle)
var (tileValue, styleValue) = fit switch
if (Settings.Mode == ChangeWallpaperMode.SolidColor)
{
0 => ("1", "1"), // 平铺:TileWallpaper=1, WallpaperStyle=1
1 => ("0", "0"), // 居中:TileWallpaper=0, WallpaperStyle=0
2 => ("0", "2"), // 拉伸:TileWallpaper=0, WallpaperStyle=2
3 => ("0", "6"), // 填充:TileWallpaper=0, WallpaperStyle=6
4 => ("0", "10"), // 适应:TileWallpaper=0, WallpaperStyle=10
5 => ("0", "22"), // 跨区:TileWallpaper=0, WallpaperStyle=22
_ => ("0", "2") // 默认:拉伸
};

// 在后台线程执行可能阻塞的注册表与系统 API 操作,避免阻塞宿主 UI
await Task.Run(() =>
var color = ParseColor(Settings.SolidColor);
_logger.LogInformation("正在切换为纯色壁纸: {Color}", Settings.SolidColor);
await Task.Run(() => SetSolidColorWallpaper(color));
_logger.LogInformation("切换纯色壁纸成功: {Color}", Settings.SolidColor);
}
else
{
// 1. 修改注册表以设置契合度
using (var desktopRegKey = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true))
{
if (desktopRegKey == null)
{
throw new Win32Exception("无法访问系统桌面注册表配置项,操作失败。");
}

desktopRegKey.SetValue("TileWallpaper", tileValue, RegistryValueKind.String);
desktopRegKey.SetValue("WallpaperStyle", styleValue, RegistryValueKind.String);
}

// 2. 调用 SystemParametersInfo 设置壁纸并通知系统
IntPtr uniPtr = IntPtr.Zero;
try
{
uniPtr = Marshal.StringToHGlobalUni(imagePath);
bool result;
unsafe
{
void* uniVoidPtr = (void*)uniPtr;
result = PInvoke.SystemParametersInfo(
Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_ACTION.SPI_SETDESKWALLPAPER,
0,
uniVoidPtr,
Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS.SPIF_UPDATEINIFILE |
Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS.SPIF_SENDCHANGE);
}

if (!result)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "SystemParametersInfo失败");
}
}
finally
var imagePath = Settings.ImagePath;
var fit = Settings.FitStyle;
_logger.LogInformation("正在切换壁纸到: {Path}, FitStyle: {Fit}", imagePath, fit);

var (tileValue, styleValue) = fit switch
{
if (uniPtr != IntPtr.Zero)
Marshal.FreeHGlobal(uniPtr);
}
});
0 => ("1", "1"), // 平铺
1 => ("0", "0"), // 居中
2 => ("0", "2"), // 拉伸
3 => ("0", "10"), // 填充
4 => ("0", "6"), // 适应
5 => ("0", "22"), // 跨区
_ => ("0", "10") // 默认:填充
};

_logger.LogInformation("切换壁纸成功: {Path}", imagePath);
await Task.Run(() => SetImageWallpaper(imagePath, tileValue, styleValue));
_logger.LogInformation("切换壁纸成功: {Path}", imagePath);
}
}
catch (Exception ex)
{
Expand All @@ -108,4 +79,103 @@ await Task.Run(() =>
await base.OnInvoke();
_logger.LogDebug("ChangeWallpaperAction OnInvoke 完成");
}

private static (byte R, byte G, byte B) ParseColor(string value)
{
var color = value.Trim();
if (color.StartsWith("#"))
{
color = color[1..];
}

if (color.Length == 6 && int.TryParse(color, System.Globalization.NumberStyles.HexNumber, null, out var rgb))
{
return ((byte)((rgb >> 16) & 0xFF), (byte)((rgb >> 8) & 0xFF), (byte)(rgb & 0xFF));
}

var parts = value.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3 &&
byte.TryParse(parts[0], out var r) &&
byte.TryParse(parts[1], out var g) &&
byte.TryParse(parts[2], out var b))
{
return (r, g, b);
}

throw new FormatException("纯色壁纸颜色格式无效,请使用 #RRGGBB 或 R,G,B。");
}

private static void SetImageWallpaper(string imagePath, string tileValue, string styleValue)
{
using (var desktopRegKey = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true))
{
if (desktopRegKey == null)
{
throw new Win32Exception("无法访问系统桌面注册表配置项,操作失败。");
}

desktopRegKey.SetValue("TileWallpaper", tileValue, RegistryValueKind.String);
desktopRegKey.SetValue("WallpaperStyle", styleValue, RegistryValueKind.String);
}

ApplyWallpaper(imagePath);
}

private static void SetSolidColorWallpaper((byte R, byte G, byte B) color)
{
using (var colorsRegKey = Registry.CurrentUser.OpenSubKey("Control Panel\\Colors", true))
{
if (colorsRegKey == null)
{
throw new Win32Exception("无法访问系统颜色注册表配置项,操作失败。");
}

colorsRegKey.SetValue("Background", $"{color.R} {color.G} {color.B}", RegistryValueKind.String);
}

using (var desktopRegKey = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true))
{
if (desktopRegKey == null)
{
throw new Win32Exception("无法访问系统桌面注册表配置项,操作失败。");
}

desktopRegKey.SetValue("Wallpaper", string.Empty, RegistryValueKind.String);
desktopRegKey.SetValue("TileWallpaper", "0", RegistryValueKind.String);
desktopRegKey.SetValue("WallpaperStyle", "0", RegistryValueKind.String);
}

ApplyWallpaper(string.Empty);
}

private static void ApplyWallpaper(string wallpaperPath)
{
IntPtr uniPtr = IntPtr.Zero;
try
{
uniPtr = Marshal.StringToHGlobalUni(wallpaperPath);
bool result;
unsafe
{
result = PInvoke.SystemParametersInfo(
Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_ACTION.SPI_SETDESKWALLPAPER,
0,
(void*)uniPtr,
Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS.SPIF_UPDATEINIFILE |
Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS.SPIF_SENDCHANGE);
}

if (!result)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "SystemParametersInfo失败");
}
}
finally
{
if (uniPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(uniPtr);
}
}
}
}
70 changes: 56 additions & 14 deletions Actions/SimulateKeyboardAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Threading.Tasks;
using SystemTools.Settings;
using Windows.Win32;
Expand All @@ -20,29 +20,43 @@ protected override async Task OnInvoke()
{
_logger.LogDebug("SimulateKeyboardAction OnInvoke 开始");

if (Settings == null || Settings.Keys == null || Settings.Keys.Count == 0)
if (Settings == null)
{
_logger.LogWarning("没有录制的按键");
return;
}

var actions = Settings.Actions.Count > 0 ? Settings.Actions : ConvertLegacyKeys(Settings.Keys);
if (actions.Count == 0)
{
_logger.LogWarning("没有录制的按键");
return;
}

try
{
_logger.LogInformation("正在模拟 {Count} 个按键", Settings.Keys.Count);
_logger.LogInformation("正在模拟 {Count} 个按键操作", actions.Count);

for (int i = 0; i < Settings.Keys.Count; i++)
for (int i = 0; i < actions.Count; i++)
{
if (byte.TryParse(Settings.Keys[i].Split(':')[0], out byte keyCode))
var action = actions[i];
await Task.Delay((int)action.Interval);

switch (action.Type)
{
PInvoke.keybd_event(keyCode, 0, 0, UIntPtr.Zero);
await Task.Delay(KEY_PRESS_DELAY);
PInvoke.keybd_event(keyCode, 0,
Windows.Win32.UI.Input.KeyboardAndMouse.KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP, UIntPtr.Zero);

if (i < Settings.Keys.Count - 1)
{
await Task.Delay(KEY_INTERVAL_DELAY);
}
case KeyboardAction.ActionType.KeyDown:
PInvoke.keybd_event(action.KeyCode, 0, 0, UIntPtr.Zero);
break;
case KeyboardAction.ActionType.KeyUp:
PInvoke.keybd_event(action.KeyCode, 0,
Windows.Win32.UI.Input.KeyboardAndMouse.KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP, UIntPtr.Zero);
break;
default:
PInvoke.keybd_event(action.KeyCode, 0, 0, UIntPtr.Zero);
await Task.Delay(KEY_PRESS_DELAY);
PInvoke.keybd_event(action.KeyCode, 0,
Windows.Win32.UI.Input.KeyboardAndMouse.KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP, UIntPtr.Zero);
break;
}
}

Expand All @@ -58,6 +72,34 @@ protected override async Task OnInvoke()
_logger.LogDebug("SimulateKeyboardAction OnInvoke 完成");
}

private static List<KeyboardAction> ConvertLegacyKeys(List<string>? keys)
{
var actions = new List<KeyboardAction>();
if (keys == null)
{
return actions;
}

foreach (var key in keys)
{
var parts = key.Split(':', 2);
if (!byte.TryParse(parts[0], out var keyCode))
{
continue;
}

actions.Add(new KeyboardAction
{
Type = KeyboardAction.ActionType.Press,
KeyCode = keyCode,
KeyName = parts.Length > 1 ? parts[1] : keyCode.ToString(),
Interval = actions.Count == 0 ? 0 : KEY_INTERVAL_DELAY
});
}

return actions;
}

//[DllImport("user32.dll", SetLastError = true)]
//private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);

Expand Down
16 changes: 15 additions & 1 deletion Controls/Components/BetterCarouselContainerComponent.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
Expand Down Expand Up @@ -36,6 +36,7 @@ public partial class BetterCarouselContainerComponent : ComponentBase<BetterCaro
private int _selectedIndex;
private int _playDirection = 1;
private DateTime _displayStartedAt = DateTime.UtcNow;
private DateTime _lastProgressUpdatedAt = DateTime.MinValue;
private bool _isLoaded;
private bool _isAnimating;

Expand Down Expand Up @@ -362,6 +363,12 @@ private void BetterCarouselContainerComponent_OnDetachedFromVisualTree(object? s

private void OnLessonsServicePreMainTimerTicked(object? sender, EventArgs e)
{
if (Settings.ReduceProgressBarPrecision &&
DateTime.UtcNow - _lastProgressUpdatedAt < TimeSpan.FromSeconds(1))
{
return;
}

UpdateProgressState();
}

Expand All @@ -378,6 +385,12 @@ private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs
OnPropertyChanged(nameof(ShowBottomProgressBar));
}

if (e.PropertyName is nameof(Settings.ReduceProgressBarPrecision))
{
_lastProgressUpdatedAt = DateTime.MinValue;
UpdateProgressState();
}

if (e.PropertyName is nameof(Settings.RotationMode) or nameof(Settings.IsAnimationEnabled) or nameof(Settings.ShowProgressBar))
{
UpdateProgressState(resetWhenIdle: true);
Expand Down Expand Up @@ -586,6 +599,7 @@ private void RestartProgress()

private void SetCurrentProgressPercent(double value)
{
_lastProgressUpdatedAt = DateTime.UtcNow;
if (Math.Abs(CurrentProgressPercent - value) < 0.01)
{
return;
Expand Down
11 changes: 10 additions & 1 deletion Controls/Components/BetterCarouselContainerSettingsControl.axaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<ci:ComponentBase
<ci:ComponentBase
x:Class="SystemTools.Controls.Components.BetterCarouselContainerSettingsControl"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Expand Down Expand Up @@ -82,6 +82,15 @@
</controls:SettingsExpander.Footer>
</controls:SettingsExpander>

<controls:SettingsExpander Header="降低进度条精度"
Description="开启后进度条每秒刷新一次,降低刷新频率"
IconSource="{ci:FluentIconSource &#xEDC1;}"
IsVisible="{Binding Settings.ShowProgressBar}">
<controls:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding Settings.ReduceProgressBarPrecision, Mode=TwoWay}" />
</controls:SettingsExpander.Footer>
</controls:SettingsExpander>

<controls:SettingsExpander Header="添加分隔符样式"
Description="在容器左右两侧固定显示“&lt;”和“&gt;”"
IconSource="{ci:FluentIconSource &#xEEDF;}">
Expand Down
Loading
Loading