Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8a748d8
feat: 悬浮窗重构(#2)
ywydog Jun 19, 2026
45552df
feat: 分析分支与主分支区别
ywydog Jun 19, 2026
b310895
merge: 合并 main 并解决悬浮窗相关冲突
Jun 19, 2026
afeb7e5
fest:重构悬浮窗
ywydog Jun 19, 2026
94748a3
refactor(floating): 移除组件库,改为按钮单独设置;修复置顶/置底方向
Jun 19, 2026
209db5d
refactor(floating): 完全移除组件库,学习 oSq 分支的按钮/行设置设计
Jun 19, 2026
d303698
refactor(settings): remove remove button from floating window editor
Jun 19, 2026
a3bfe98
feat(automation): add revert snapshots for floating window actions
Jun 19, 2026
7697d78
Merge branch 'Programmer-MrWang:main' into main
ywydog Jun 23, 2026
45a8fb6
feat: 分析分支与主分支区别
ywydog Jun 26, 2026
fb0ed47
feat: 分析分支与主分支区别
ywydog Jun 27, 2026
8bd4f9f
feat: 分析分支与主分支区别
ywydog Jul 2, 2026
c0ca696
修补命名空间
ywydog Jul 4, 2026
1ac66eb
feat: 分析分支与主分支区别
ywydog Jul 9, 2026
377df27
feat: 分析分支与主分支区别
ywydog Jul 9, 2026
65c2fe5
feat: 分析分支与主分支区别
ywydog Jul 9, 2026
e6418ec
feat: 分析分支与主分支区别
ywydog Jul 9, 2026
bf9ed84
merge: 合并 origin/main 并解决悬浮窗相关冲突
Jul 9, 2026
07e8c9a
feat: 完善悬浮窗的逻辑
ywydog Jul 9, 2026
bc142f1
feat: 优化悬浮窗配置管理与定时器使用
ywydog Jul 13, 2026
dd7bf52
fix(floating): mode 2/3 改回 DispatcherTimer 事件触发
ywydog Jul 13, 2026
27fea00
merge: 合并 trae/solo-agent-VuGEdC 的悬浮窗优化到 main
ywydog Jul 13, 2026
cd20cd3
fix(build): 恢复 ILessonsService 字段并修正 SettingsPage 引用
ywydog Jul 13, 2026
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
13 changes: 12 additions & 1 deletion Actions/SetVolume.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,18 @@ internal class MMDeviceEnumeratorWrapper
public MMDeviceEnumeratorWrapper()
{
var type = Type.GetTypeFromCLSID(new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E"));
_enumerator = (IMMDeviceEnumerator)Activator.CreateInstance(type);
if (type == null)
{
throw new InvalidOperationException("无法获取 MMDeviceEnumerator 类型。");
}

var instance = Activator.CreateInstance(type);
if (instance == null)
{
throw new InvalidOperationException("无法创建 MMDeviceEnumerator 实例。");
}

_enumerator = (IMMDeviceEnumerator)instance;
}

public IMMDevice GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role)
Expand Down
31 changes: 25 additions & 6 deletions Actions/ShowFloatingWindowAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,26 @@ protected override async Task OnInvoke()
try
{
var shouldShow = Settings.ShowFloatingWindow;
var config = GlobalConstants.MainConfig?.Data;

if (IsRevertable)
// 如果没有可用的悬浮窗组件,则强制隐藏且不允许显示
if (_floatingWindowService.Entries.Count == 0)
{
PreviousStates[ActionSet.Guid] = GlobalConstants.MainConfig!.Data.ShowFloatingWindow;
shouldShow = false;
_logger.LogDebug("没有可用的悬浮窗组件,强制隐藏悬浮窗");
}

if (IsRevertable && config != null)
{
PreviousStates[ActionSet.Guid] = config.ShowFloatingWindow;
}

if (config != null)
{
config.ShowFloatingWindow = shouldShow;
GlobalConstants.MainConfig?.Save();
}

GlobalConstants.MainConfig!.Data.ShowFloatingWindow = shouldShow;
GlobalConstants.MainConfig.Save();
_floatingWindowService.UpdateWindowState();

_logger.LogInformation("悬浮窗状态已更新为: {State}", shouldShow ? "开启" : "关闭");
Expand All @@ -58,8 +70,15 @@ protected override async Task OnRevert()
return;
}

GlobalConstants.MainConfig!.Data.ShowFloatingWindow = previousState;
GlobalConstants.MainConfig.Save();
var config = GlobalConstants.MainConfig;
if (config == null)
{
_logger.LogWarning("主配置为空,无法恢复悬浮窗状态");
return;
}

config.Data.ShowFloatingWindow = previousState;
config.Save();
_floatingWindowService.UpdateWindowState();

_logger.LogInformation("已恢复悬浮窗状态为: {State}", previousState ? "开启" : "关闭");
Expand Down
96 changes: 96 additions & 0 deletions Actions/SwitchFloatingWindowThemeAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using ClassIsland.Shared;
using Microsoft.Extensions.Logging;
using SystemTools.Services;
using SystemTools.Settings;
using SystemTools.Shared;

namespace SystemTools.Actions;

/// <summary>
/// 切换悬浮窗主题行动
/// </summary>
[ActionInfo("SystemTools.SwitchFloatingWindowTheme", "切换悬浮窗主题", "\uE790", false)]
public class SwitchFloatingWindowThemeAction(ILogger<SwitchFloatingWindowThemeAction> logger) : ActionBase<SwitchFloatingWindowThemeSettings>
{
private readonly ILogger<SwitchFloatingWindowThemeAction> _logger = logger;
private static readonly ConcurrentDictionary<Guid, int> PreviousThemes = new();

protected override async Task OnInvoke()
{
_logger.LogDebug("SwitchFloatingWindowThemeAction OnInvoke 开始");

try
{
var service = IAppHost.GetService<FloatingWindowService>();
var config = GlobalConstants.MainConfig?.Data;

if (Settings.TargetTheme >= 0)
{
if (IsRevertable && config != null)
{
PreviousThemes[ActionSet.Guid] = config.FloatingWindowTheme;
}

service.SetWindowTheme(Settings.TargetTheme);
_logger.LogInformation("已设置悬浮窗主题为: {Theme}", GetThemeName(Settings.TargetTheme));
}
else
{
if (IsRevertable && config != null)
{
PreviousThemes[ActionSet.Guid] = config.FloatingWindowTheme;
}

service.ToggleWindowTheme();
_logger.LogInformation("已切换到下一个悬浮窗主题");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "切换悬浮窗主题失败");
throw;
}

await base.OnInvoke();
_logger.LogDebug("SwitchFloatingWindowThemeAction OnInvoke 完成");
}

protected override async Task OnRevert()
{
await base.OnRevert();

if (!PreviousThemes.TryRemove(ActionSet.Guid, out var previousTheme))
{
_logger.LogInformation("未找到主题恢复快照,跳过悬浮窗主题恢复。ActionSet={ActionSetGuid}", ActionSet.Guid);
return;
}

try
{
var service = IAppHost.GetService<FloatingWindowService>();
service.SetWindowTheme(previousTheme);
_logger.LogInformation("已恢复悬浮窗主题为: {Theme}", GetThemeName(previousTheme));
}
catch (Exception ex)
{
_logger.LogError(ex, "恢复悬浮窗主题失败");
throw;
}
}

private static string GetThemeName(int theme)
{
return theme switch
{
0 => "跟随系统",
1 => "浅色",
2 => "深色",
_ => "未知"
};
}
}
86 changes: 86 additions & 0 deletions Actions/ToggleFloatingWindowLayerAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using ClassIsland.Shared;
using Microsoft.Extensions.Logging;
using SystemTools.Services;
using SystemTools.Settings;

namespace SystemTools.Actions;

/// <summary>
/// 切换悬浮窗层级行动
/// </summary>
[ActionInfo("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗层级", "\uE9A8", false)]
public class ToggleFloatingWindowLayerAction(ILogger<ToggleFloatingWindowLayerAction> logger) : ActionBase<ToggleFloatingWindowLayerSettings>
{
private readonly ILogger<ToggleFloatingWindowLayerAction> _logger = logger;
private static readonly ConcurrentDictionary<Guid, int> PreviousLayers = new();

protected override async Task OnInvoke()
{
_logger.LogDebug("ToggleFloatingWindowLayerAction OnInvoke 开始");

try
{
var service = IAppHost.GetService<FloatingWindowService>();
var profile = service.ProfileManager.CurrentProfile;

// 根据设置决定是切换还是设置到指定层级
// TargetLayer: -1=切换, 0=置底, 1=置顶
if (Settings.TargetLayer >= 0)
{
if (IsRevertable)
{
PreviousLayers[ActionSet.Guid] = profile.FloatingWindowLayer;
}

service.SetWindowLayer(Settings.TargetLayer);
_logger.LogInformation("已设置悬浮窗层级为: {Layer}", Settings.TargetLayer == 0 ? "置底" : "置顶");
}
else
{
if (IsRevertable)
{
PreviousLayers[ActionSet.Guid] = profile.FloatingWindowLayer;
}

service.ToggleWindowLayer();
_logger.LogInformation("已切换悬浮窗层级状态");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "切换悬浮窗层级失败");
throw;
}

await base.OnInvoke();
_logger.LogDebug("ToggleFloatingWindowLayerAction OnInvoke 完成");
}

protected override async Task OnRevert()
{
await base.OnRevert();

if (!PreviousLayers.TryRemove(ActionSet.Guid, out var previousLayer))
{
_logger.LogInformation("未找到层级恢复快照,跳过悬浮窗层级恢复。ActionSet={ActionSetGuid}", ActionSet.Guid);
return;
}

try
{
var service = IAppHost.GetService<FloatingWindowService>();
service.SetWindowLayer(previousLayer);
_logger.LogInformation("已恢复悬浮窗层级为: {Layer}", previousLayer == 0 ? "置底" : "置顶");
}
catch (Exception ex)
{
_logger.LogError(ex, "恢复悬浮窗层级失败");
throw;
}
}
}
86 changes: 86 additions & 0 deletions Actions/ToggleFloatingWindowProfileAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using ClassIsland.Shared;
using Microsoft.Extensions.Logging;
using SystemTools.Services;
using SystemTools.Settings;

namespace SystemTools.Actions;

/// <summary>
/// 切换悬浮窗配置方案行动
/// </summary>
[ActionInfo("SystemTools.ToggleFloatingWindowProfile", "切换悬浮窗配置方案", "\uE9A8", false)]
public class ToggleFloatingWindowProfileAction(ILogger<ToggleFloatingWindowProfileAction> logger) : ActionBase<ToggleFloatingWindowProfileSettings>
{
private readonly ILogger<ToggleFloatingWindowProfileAction> _logger = logger;
private static readonly ConcurrentDictionary<Guid, string> PreviousProfiles = new();

protected override async Task OnInvoke()
{
_logger.LogDebug("ToggleFloatingWindowProfileAction OnInvoke 开始");

try
{
var service = IAppHost.GetService<FloatingWindowService>();
var currentProfileName = service.ProfileManager.CurrentProfileName;

// 根据设置决定是切换到下一个还是切换到指定方案
// TargetProfileName: null=切换到下一个, 其他=指定方案名称
if (!string.IsNullOrWhiteSpace(Settings.TargetProfileName))
{
if (IsRevertable)
{
PreviousProfiles[ActionSet.Guid] = currentProfileName;
}

service.SwitchToProfile(Settings.TargetProfileName);
_logger.LogInformation("已切换到悬浮窗配置方案: {Name}", Settings.TargetProfileName);
}
else
{
if (IsRevertable)
{
PreviousProfiles[ActionSet.Guid] = currentProfileName;
}

service.ToggleWindowProfile();
_logger.LogInformation("已切换到下一个悬浮窗配置方案");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "切换悬浮窗配置方案失败");
throw;
}

await base.OnInvoke();
_logger.LogDebug("ToggleFloatingWindowProfileAction OnInvoke 完成");
}

protected override async Task OnRevert()
{
await base.OnRevert();

if (!PreviousProfiles.TryRemove(ActionSet.Guid, out var previousProfile))
{
_logger.LogInformation("未找到配置方案恢复快照,跳过悬浮窗配置方案恢复。ActionSet={ActionSetGuid}", ActionSet.Guid);
return;
}

try
{
var service = IAppHost.GetService<FloatingWindowService>();
service.SwitchToProfile(previousProfile);
_logger.LogInformation("已恢复悬浮窗配置方案为: {Name}", previousProfile);
}
catch (Exception ex)
{
_logger.LogError(ex, "恢复悬浮窗配置方案失败");
throw;
}
}
}
9 changes: 8 additions & 1 deletion Actions/ToggleWorkflowAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace SystemTools.Actions;

[ActionInfo("SystemTools.ToggleWorkflow", "开关自动化", "\uE9A8", false)]
[ActionInfo("SystemTools.ToggleWorkflow", "开关自动化", "\uE8B8", false)]
public class ToggleWorkflowAction(ILogger<ToggleWorkflowAction> logger) : ActionBase<ToggleWorkflowSettings>
{
private readonly ILogger<ToggleWorkflowAction> _logger = logger;
Expand Down Expand Up @@ -98,6 +98,13 @@ protected override async Task OnRevert()
{
await base.OnRevert();

if (Settings == null || !Settings.RevertToOriginal)
{
_logger.LogDebug("RevertToOriginal 为 false,跳过恢复");
PreviousSnapshots.TryRemove(ActionSet.Guid, out _);
return;
}

if (!PreviousSnapshots.TryRemove(ActionSet.Guid, out var snapshot))
{
_logger.LogWarning("未找到触发前状态,跳过恢复。ActionSet={ActionSetGuid}", ActionSet.Guid);
Expand Down
23 changes: 23 additions & 0 deletions ConfigHandlers/ButtonRulesetConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
using ClassIsland.Core.Models.Ruleset;
using CommunityToolkit.Mvvm.ComponentModel;

namespace SystemTools.ConfigHandlers;

/// <summary>
/// 悬浮窗按钮的规则集配置
/// </summary>
public partial class ButtonRulesetConfig : ObservableObject
{
[ObservableProperty]
[JsonPropertyName("isVisible")]
private bool _isVisible = true;

[ObservableProperty]
[JsonPropertyName("hideOnRule")]
private bool _hideOnRule;

[ObservableProperty]
[JsonPropertyName("hidingRules")]
private Ruleset _hidingRules = new();
}
Loading
Loading