diff --git a/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/design.md b/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/design.md new file mode 100644 index 0000000..67a8a5c --- /dev/null +++ b/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/design.md @@ -0,0 +1,578 @@ +# 移除视频壁纸功能 - 技术设计文档 + +## 1. 概述 + +### 1.1 设计目标 + +本设计文档描述如何从 LanMountainDesktop 项目中完全移除视频壁纸功能,包括: +- 移除 LibVLC 相关依赖 +- 清理主窗口中的视频壁纸代码 +- 简化壁纸设置页面 +- 清理本地化资源 + +### 1.2 技术约束 + +- 保持现有图片壁纸和纯色壁纸功能完整 +- 确保应用构建和运行正常 +- 不引入新的外部依赖 + +--- + +## 2. 架构变更 + +### 2.1 变更概览图 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 变更前架构 │ +├─────────────────────────────────────────────────────────────────┤ +│ MainWindow │ +│ ├── DesktopWallpaperLayer (背景层) │ +│ │ ├── DesktopWallpaperImageLayer (图片层) │ +│ │ ├── DesktopVideoWallpaperImage (视频海报层) │ +│ │ └── DesktopVideoWallpaperView (VLC视频播放层) │ +│ ├── _libVlc, _videoWallpaperPlayer, _videoWallpaperMedia │ +│ └── StartVideoWallpaper(), StopVideoWallpaper() │ +│ │ +│ WallpaperSettingsPage │ +│ ├── 类型选择: Image | Video | SolidColor │ +│ └── 视频预览区域 │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ 变更后架构 │ +├─────────────────────────────────────────────────────────────────┤ +│ MainWindow │ +│ ├── DesktopWallpaperLayer (背景层) │ +│ │ └── DesktopWallpaperImageLayer (图片层) │ +│ └── (移除所有视频相关字段和方法) │ +│ │ +│ WallpaperSettingsPage │ +│ ├── 类型选择: Image | SolidColor │ +│ └── (移除视频预览区域) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 组件变更清单 + +| 组件 | 变更类型 | 说明 | +|------|----------|------| +| LanMountainDesktop.csproj | 修改 | 移除 LibVLC 包引用 | +| MainWindow.axaml | 修改 | 移除视频控件和命名空间 | +| MainWindow.axaml.cs | 修改 | 移除视频相关字段和清理代码 | +| MainWindow.SettingsHardCut.Stubs.cs | 修改 | 移除视频壁纸方法 | +| AppearanceThemeService.cs | 修改 | 移除视频种子提取器 | +| WallpaperSettingsPage.axaml | 修改 | 移除视频类型UI | +| WallpaperSettingsPageViewModel.cs | 修改 | 移除视频相关属性 | +| SettingsContracts.cs | 修改 | 移除 Video 枚举值 | +| SettingsDomainServices.cs | 修改 | 移除视频扩展名检测 | +| zh-CN.json | 修改 | 移除视频相关本地化文本 | + +--- + +## 3. 详细设计 + +### 3.1 项目依赖变更 (LanMountainDesktop.csproj) + +#### 3.1.1 移除的包引用 + +```xml + + + + +``` + +#### 3.1.2 变更影响 + +- 减少约 100MB+ 的依赖包大小 +- 简化构建和发布流程 +- 移除平台特定的原生库依赖 + +--- + +### 3.2 主窗口 XAML 变更 (MainWindow.axaml) + +#### 3.2.1 移除命名空间声明 + +```xml + +xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia" +``` + +#### 3.2.2 移除视频壁纸控件 + +移除以下控件(约第126-137行): + +```xml + + + + + +``` + +--- + +### 3.3 主窗口代码变更 (MainWindow.axaml.cs) + +#### 3.3.1 移除 using 声明 + +```csharp +// 移除以下 using(如果存在) +using LibVLCSharp.Shared; +using LibVLCSharp.Avalonia; +``` + +#### 3.3.2 移除静态字段 + +```csharp +// 移除以下字段(约第68-71行) +private static readonly HashSet SupportedVideoExtensions = new(StringComparer.OrdinalIgnoreCase) +{ + ".mp4", ".mkv", ".webm", ".avi", ".mov", ".m4v" +}; +``` + +#### 3.3.3 移除实例字段 + +```csharp +// 移除以下字段(约第123-146行) +private Bitmap? _videoWallpaperPosterBitmap; +private string? _videoWallpaperPosterPath; +private string? _wallpaperVideoPath; +private LibVLC? _libVlc; +private MediaPlayer? _videoWallpaperPlayer; +private Media? _videoWallpaperMedia; +private readonly object _desktopVideoFrameSync = new(); +private MediaPlayer.LibVLCVideoLockCb? _desktopVideoLockCallback; +private MediaPlayer.LibVLCVideoUnlockCb? _desktopVideoUnlockCallback; +private MediaPlayer.LibVLCVideoDisplayCb? _desktopVideoDisplayCallback; +private DispatcherTimer? _desktopVideoFrameRefreshTimer; +private IntPtr _desktopVideoFrameBufferPtr; +private byte[]? _desktopVideoStagingBuffer; +private WriteableBitmap? _desktopVideoBitmap; +private int _desktopVideoFrameWidth; +private int _desktopVideoFrameHeight; +private int _desktopVideoFramePitch; +private int _desktopVideoFrameBufferSize; +private int _desktopVideoFrameDirtyFlag; +``` + +#### 3.3.4 修改 OnClosed 方法 + +移除视频相关清理代码(约第336-350行): + +```csharp +// 移除以下代码行 +StopVideoWallpaper(); +_videoWallpaperMedia?.Dispose(); +_videoWallpaperMedia = null; +_videoWallpaperPlayer?.Dispose(); +_videoWallpaperPlayer = null; +_desktopVideoFrameRefreshTimer?.Stop(); +_desktopVideoFrameRefreshTimer = null; +_videoWallpaperPosterBitmap?.Dispose(); +_videoWallpaperPosterBitmap = null; +_videoWallpaperPosterPath = null; +_libVlc?.Dispose(); +_libVlc = null; +``` + +--- + +### 3.4 主窗口 Stub 方法变更 (MainWindow.SettingsHardCut.Stubs.cs) + +#### 3.4.1 移除 using 声明 + +```csharp +// 移除以下 using(第19-20行) +using LibVLCSharp.Shared; +using LibVLCSharp.Avalonia; +``` + +#### 3.4.2 移除方法 + +移除以下完整方法: + +| 方法名 | 行号范围 | 说明 | +|--------|----------|------| +| `StartVideoWallpaper` | 337-383 | 启动视频壁纸播放 | +| `StopVideoWallpaper` | 385-395 | 停止视频壁纸播放 | +| `TryCaptureVideoWallpaperPosterFrame` | 666-751 | 捕获视频海报帧 | +| `ApplyVideoWallpaperPosterVisibility` | 647-664 | 控制视频海报可见性 | + +#### 3.4.3 修改 UpdateWallpaperDisplay 方法 + +简化为仅处理图片壁纸: + +```csharp +private void UpdateWallpaperDisplay() +{ + // 移除视频分支,仅保留图片处理 + StopVideoWallpaper(); // 移除此调用 + ApplyWallpaperBrush(); +} +``` + +修改后: + +```csharp +private void UpdateWallpaperDisplay() +{ + ApplyWallpaperBrush(); +} +``` + +#### 3.4.4 修改 ApplyWallpaperBrush 方法 + +移除所有 `ApplyVideoWallpaperPosterVisibility` 调用: + +```csharp +// 移除以下调用 +ApplyVideoWallpaperPosterVisibility(showPoster: false); +ApplyVideoWallpaperPosterVisibility(showPoster: _videoWallpaperPosterBitmap is not null); +``` + +#### 3.4.5 修改 SetWallpaperState 方法 + +移除视频类型处理分支(约第238-247行): + +```csharp +// 移除以下代码块 +var requestedTypeIsVideo = string.Equals(_wallpaperType, "Video", StringComparison.OrdinalIgnoreCase); +if (SupportedVideoExtensions.Contains(extension) || requestedTypeIsVideo) +{ + _wallpaperMediaType = WallpaperMediaType.Video; + _wallpaperVideoPath = _wallpaperPath; + _wallpaperDisplayState = File.Exists(_wallpaperPath) + ? WallpaperDisplayState.CurrentValidWallpaper + : WallpaperDisplayState.TemporarilyUnavailable; + return; +} +``` + +--- + +### 3.5 外观主题服务变更 (AppearanceThemeService.cs) + +#### 3.5.1 移除接口和类 + +移除以下代码(约第92-184行): + +```csharp +// 移除接口 +internal interface IVideoWallpaperSeedExtractor +{ + IReadOnlyList ExtractSeedCandidates(string videoPath, MonetColorService monetColorService); +} + +// 移除实现类 +internal sealed class LibVlcVideoWallpaperSeedExtractor : IVideoWallpaperSeedExtractor +{ + // ... 整个类实现 +} +``` + +--- + +### 3.6 壁纸设置页面 XAML 变更 (WallpaperSettingsPage.axaml) + +#### 3.6.1 移除视频预览区域 + +移除以下代码(约第29-44行): + +```xml + + + + + + +``` + +#### 3.6.2 移除视频模式提示文本 + +移除以下代码(约第150-154行): + +```xml + +``` + +#### 3.6.3 修改填充方式设置可见性绑定 + +```xml + +IsVisible="{Binding IsImageOrVideo}" + + +IsVisible="{Binding IsImage}" +``` + +--- + +### 3.7 壁纸设置 ViewModel 变更 (WallpaperSettingsPageViewModel.cs) + +#### 3.7.1 移除属性 + +```csharp +// 移除以下属性 +[ObservableProperty] +private bool _isImageOrVideo; + +[ObservableProperty] +private bool _isVideo; + +[ObservableProperty] +private string _videoModeHintText = string.Empty; +``` + +#### 3.7.2 修改 CreateWallpaperTypes 方法 + +```csharp +// 修改前 +private IReadOnlyList CreateWallpaperTypes() +{ + return + [ + new SelectionOption("Image", L("settings.wallpaper.type.image", "Image")), + new SelectionOption("Video", L("settings.wallpaper.type.video", "Video")), + new SelectionOption("SolidColor", L("settings.wallpaper.type.solid_color", "Solid Color")) + ]; +} + +// 修改后 +private IReadOnlyList CreateWallpaperTypes() +{ + return + [ + new SelectionOption("Image", L("settings.wallpaper.type.image", "Image")), + new SelectionOption("SolidColor", L("settings.wallpaper.type.solid_color", "Solid Color")) + ]; +} +``` + +#### 3.7.3 修改 UpdateVisibility 方法 + +移除 IsVideo 和 IsImageOrVideo 的赋值: + +```csharp +// 移除以下行 +IsVideo = SelectedWallpaperType?.Value == "Video"; +IsImageOrVideo = SelectedWallpaperType?.Value is "Image" or "Video"; +``` + +#### 3.7.4 修改 RefreshLocalizedText 方法 + +```csharp +// 移除以下行 +VideoModeHintText = L("settings.wallpaper.video_mode", "Video wallpaper uses automatic fill mode."); +``` + +--- + +### 3.8 设置契约变更 (SettingsContracts.cs) + +#### 3.8.1 修改 WallpaperMediaType 枚举 + +```csharp +// 修改前 +public enum WallpaperMediaType +{ + None, + Image, + Video +} + +// 修改后 +public enum WallpaperMediaType +{ + None, + Image +} +``` + +--- + +### 3.9 设置域服务变更 (SettingsDomainServices.cs) + +#### 3.9.1 移除视频扩展名集合 + +```csharp +// 移除以下字段(约第150-153行) +private static readonly HashSet VideoExtensions = new(StringComparer.OrdinalIgnoreCase) +{ + ".mp4", ".mkv", ".webm", ".avi", ".mov", ".m4v" +}; +``` + +#### 3.9.2 修改 DetectMediaType 方法 + +```csharp +// 修改前 +public WallpaperMediaType DetectMediaType(string? path) +{ + if (string.IsNullOrWhiteSpace(path)) + { + return WallpaperMediaType.None; + } + + var extension = Path.GetExtension(path.Trim()); + if (string.IsNullOrWhiteSpace(extension)) + { + return WallpaperMediaType.None; + } + + if (ImageExtensions.Contains(extension)) + { + return WallpaperMediaType.Image; + } + + if (VideoExtensions.Contains(extension)) + { + return WallpaperMediaType.Video; + } + + return WallpaperMediaType.None; +} + +// 修改后 +public WallpaperMediaType DetectMediaType(string? path) +{ + if (string.IsNullOrWhiteSpace(path)) + { + return WallpaperMediaType.None; + } + + var extension = Path.GetExtension(path.Trim()); + if (string.IsNullOrWhiteSpace(extension)) + { + return WallpaperMediaType.None; + } + + if (ImageExtensions.Contains(extension)) + { + return WallpaperMediaType.Image; + } + + return WallpaperMediaType.None; +} +``` + +--- + +### 3.10 本地化文件变更 (zh-CN.json) + +#### 3.10.1 移除的本地化键 + +```json +// 移除以下键值对 +"settings.wallpaper.type.video": "视频", +"settings.wallpaper.video_applied": "视频壁纸已应用。", +"settings.wallpaper.video_mode": "视频壁纸使用自动填充模式。", +"settings.wallpaper.video_restored": "已恢复保存的视频壁纸。", +"settings.wallpaper.video_not_found": "未找到视频壁纸文件。", +"settings.wallpaper.video_player_unavailable": "视频播放器不可用。", +"settings.wallpaper.video_play_failed_format": "播放视频壁纸失败:{0}" +``` + +#### 3.10.2 修改描述文本 + +```json +// 修改前 +"settings.wallpaper.description": "选择图片或视频后可立即设为应用窗口壁纸。", + +// 修改后 +"settings.wallpaper.description": "选择图片后可立即设为应用窗口壁纸。", +``` + +--- + +## 4. 数据模型变更 + +### 4.1 WallpaperMediaType 枚举简化 + +``` +变更前: None | Image | Video +变更后: None | Image +``` + +### 4.2 设置存储兼容性 + +现有用户设置中如果包含 `Type: "Video"` 的壁纸配置: +- 应用将无法识别该类型 +- 将回退到纯色背景 +- 用户需要重新选择图片壁纸 + +--- + +## 5. 风险评估 + +### 5.1 潜在风险 + +| 风险 | 影响 | 缓解措施 | +|------|------|----------| +| 现有视频壁纸用户设置失效 | 中 | 应用会自动回退到纯色背景 | +| 遗漏的视频相关代码引用 | 低 | 编译器会报告未定义类型错误 | +| 本地化键遗漏 | 低 | 运行时会显示键名而非翻译文本 | + +### 5.2 回滚策略 + +如需回滚,可通过 Git 恢复以下文件: +- LanMountainDesktop.csproj +- MainWindow.axaml / .axaml.cs +- MainWindow.SettingsHardCut.Stubs.cs +- AppearanceThemeService.cs +- WallpaperSettingsPage.axaml +- WallpaperSettingsPageViewModel.cs +- SettingsContracts.cs +- SettingsDomainServices.cs +- zh-CN.json + +--- + +## 6. 验证清单 + +### 6.1 编译验证 + +- [ ] 项目编译无错误 +- [ ] 无 LibVLC 相关类型引用警告 +- [ ] 无未使用变量警告 + +### 6.2 功能验证 + +- [ ] 应用正常启动 +- [ ] 图片壁纸正常显示 +- [ ] 纯色壁纸正常显示 +- [ ] 壁纸设置页面正常打开 +- [ ] 类型选择器仅显示"图片"和"纯色" +- [ ] 壁纸导入功能正常工作 + +### 6.3 清理验证 + +- [ ] 无 LibVLC 相关 DLL 在输出目录 +- [ ] 无视频相关本地化文本残留 +- [ ] 无视频相关 UI 控件残留 diff --git a/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/spec.md b/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/spec.md new file mode 100644 index 0000000..d5cb620 --- /dev/null +++ b/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/spec.md @@ -0,0 +1,206 @@ +# 移除视频壁纸功能规格说明书 + +## Why + +当前 LanMountainDesktop 项目包含视频壁纸功能,该功能引入了以下复杂性和依赖: +1. 引入了 LibVLCSharp.Avalonia、VideoLAN.LibVLC.Windows、VideoLAN.LibVLC.Mac 等重型依赖 +2. 在主窗口中残留大量视频壁纸相关代码和字段 +3. 在设置页面中保留了视频类型选择器和相关 UI 元素 +4. 在本地化文件中保留了视频壁纸相关文本 +5. 增加了应用复杂度和维护成本 + +用户决定移除该功能以简化代码库。 + +## What Changes + +- 移除 LibVLCSharp.Avalonia 及 VideoLAN.LibVLC.* NuGet 依赖 +- 移除 AppearanceThemeService.cs 中的 LibVlcVideoWallpaperSeedExtractor 类和 IVideoWallpaperSeedExtractor 接口 +- 移除 MainWindow.axaml.cs 中的视频壁纸相关字段和清理代码 +- 移除 MainWindow.SettingsHardCut.Stubs.cs 中的视频壁纸相关方法 +- 移除 MainWindow.axaml 中的 DesktopVideoWallpaperImage 和 DesktopVideoWallpaperView 控件 +- 移除 WallpaperSettingsPage.axaml 中的视频类型选择器和视频模式提示 +- 移除 WallpaperSettingsPageViewModel.cs 中的 IsVideo、VideoModeHintText 等属性 +- 移除 SettingsContracts.cs 中 WallpaperMediaType 枚举的 Video 值 +- 移除 SettingsDomainServices.cs 中 WallpaperMediaService 类的视频扩展名检测逻辑 +- 移除本地化文件中的视频壁纸相关文本 + +## Impact + +### Affected specs +- 壁纸设置功能规格 +- 主窗口桌面层规格 + +### Affected code +- `LanMountainDesktop.csproj` - NuGet 依赖配置 +- `Services/AppearanceThemeService.cs` - 视频壁纸种子提取器 +- `Views/MainWindow.axaml.cs` - 主窗口字段和清理逻辑 +- `Views/MainWindow.SettingsHardCut.Stubs.cs` - 视频壁纸控制方法 +- `Views/MainWindow.axaml` - 视频壁纸 UI 控件 +- `Views/SettingsPages/WallpaperSettingsPage.axaml` - 壁纸设置页面 UI +- `ViewModels/WallpaperSettingsPageViewModel.cs` - 壁纸设置 ViewModel +- `Services/Settings/SettingsContracts.cs` - 壁纸媒体类型枚举 +- `Services/Settings/SettingsDomainServices.cs` - 壁纸媒体服务 +- `Localization/zh-CN.json` - 本地化文本 + +--- + +## REMOVED Requirements + +### Requirement: 视频壁纸播放功能 + +**Reason**: 用户决定移除视频壁纸功能以简化代码库,减少重型依赖 + +**Migration**: +- 用户如需动态壁纸,可使用静态图片壁纸替代 +- 现有视频壁纸设置将被重置为纯色背景 + +#### Scenario: 视频壁纸播放 +- **GIVEN** 用户选择了视频文件作为壁纸 +- **WHEN** 系统检测到视频格式 +- **THEN** 系统不再支持视频壁纸播放 +- **AND THEN** 系统提示用户该文件类型不受支持 + +### Requirement: LibVLC 依赖 + +**Reason**: 移除视频壁纸功能后不再需要 LibVLC 库 + +**Migration**: 从项目依赖中移除以下包: +- LibVLCSharp.Avalonia +- VideoLAN.LibVLC.Windows +- VideoLAN.LibVLC.Mac + +### Requirement: 视频壁纸种子提取 + +**Reason**: 移除视频壁纸功能后不再需要从视频中提取颜色种子 + +**Migration**: 移除 `LibVlcVideoWallpaperSeedExtractor` 类和 `IVideoWallpaperSeedExtractor` 接口 + +### Requirement: 视频壁纸 UI 控件 + +**Reason**: 移除视频壁纸功能后不再需要视频显示控件 + +**Migration**: 移除 `DesktopVideoWallpaperImage` 和 `DesktopVideoWallpaperView` 控件 + +### Requirement: 视频类型选择器 + +**Reason**: 移除视频壁纸功能后不再需要视频类型选项 + +**Migration**: 从壁纸类型选择器中移除"视频"选项 + +--- + +## MODIFIED Requirements + +### Requirement: 壁纸媒体类型检测 + +**当前**: 支持检测 None、Image、Video 三种类型 + +**修改后**: 仅支持检测 None、Image 两种类型 + +#### Scenario: 检测媒体类型 +- **WHEN** 用户选择壁纸文件 +- **THEN** 系统仅检测图片格式(.png, .jpg, .jpeg, .bmp, .gif, .webp) +- **AND THEN** 视频格式文件将被识别为不受支持的类型 + +### Requirement: 壁纸类型选项 + +**当前**: 提供图片、视频、纯色三种类型选项 + +**修改后**: 仅提供图片、纯色两种类型选项 + +#### Scenario: 壁纸类型选择 +- **WHEN** 用户打开壁纸设置页面 +- **THEN** 类型选择器仅显示"图片"和"纯色"选项 +- **AND THEN** "视频"选项不再显示 + +### Requirement: 壁纸设置页面预览 + +**当前**: 根据类型显示图片预览、视频预览或纯色预览 + +**修改后**: 根据类型显示图片预览或纯色预览 + +#### Scenario: 预览显示 +- **WHEN** 用户选择壁纸类型 +- **THEN** 系统仅显示图片预览或纯色预览 +- **AND THEN** 视频预览区域不再显示 + +### Requirement: 主窗口壁纸显示 + +**当前**: 支持显示静态图片壁纸和视频壁纸 + +**修改后**: 仅支持显示静态图片壁纸 + +#### Scenario: 壁纸显示更新 +- **WHEN** 用户应用新壁纸 +- **THEN** 系统仅处理静态图片壁纸显示 +- **AND THEN** 视频壁纸播放逻辑不再执行 + +--- + +## ADDED Requirements + +### Requirement: 清理残留代码 + +系统 SHALL 完全移除视频壁纸功能相关的所有代码和资源。 + +#### Scenario: 主窗口字段清理 +- **WHEN** 执行代码清理 +- **THEN** 移除以下字段: + - `_videoWallpaperPosterBitmap` + - `_videoWallpaperPosterPath` + - `_libVlc` + - `_videoWallpaperPlayer` + - `_videoWallpaperMedia` + - `_wallpaperVideoPath` + +#### Scenario: 主窗口方法清理 +- **WHEN** 执行代码清理 +- **THEN** 移除以下方法: + - `StartVideoWallpaper` + - `StopVideoWallpaper` + - `TryCaptureVideoWallpaperPosterFrame` + - `ApplyVideoWallpaperPosterVisibility` + - `UpdateWallpaperDisplay` 中的视频处理分支 + +#### Scenario: ViewModel 属性清理 +- **WHEN** 执行代码清理 +- **THEN** 移除以下属性: + - `IsVideo` + - `VideoModeHintText` + - `IsImageOrVideo`(改为 `IsImage`) + +#### Scenario: 本地化文本清理 +- **WHEN** 执行代码清理 +- **THEN** 移除以下本地化键: + - `settings.wallpaper.type.video` + - `settings.wallpaper.video_applied` + - `settings.wallpaper.video_mode` + - `settings.wallpaper.video_restored` + - `settings.wallpaper.video_not_found` + - `settings.wallpaper.video_player_unavailable` + - `settings.wallpaper.video_play_failed_format` + +### Requirement: 依赖项清理 + +系统 SHALL 从项目文件中移除 LibVLC 相关 NuGet 包引用。 + +#### Scenario: NuGet 包移除 +- **WHEN** 执行依赖清理 +- **THEN** 移除以下包引用: + - `LibVLCSharp.Avalonia` + - `VideoLAN.LibVLC.Windows` + - `VideoLAN.LibVLC.Mac` + +### Requirement: 构建验证 + +系统 SHALL 在移除视频壁纸功能后保持正常构建和运行。 + +#### Scenario: 构建成功 +- **WHEN** 执行项目构建 +- **THEN** 构建成功无错误 +- **AND THEN** 所有现有测试通过 + +#### Scenario: 应用启动 +- **WHEN** 启动应用程序 +- **THEN** 应用正常启动 +- **AND THEN** 壁纸设置功能正常工作(仅支持图片和纯色) diff --git a/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/tasks.md b/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/tasks.md new file mode 100644 index 0000000..3eb0e79 --- /dev/null +++ b/LanMontainDesktop/.trae/specs/remove-video-wallpaper-feature/tasks.md @@ -0,0 +1,600 @@ +# 移除视频壁纸功能 - 编码任务清单 + +## 任务概览 + +本文档将技术设计分解为可执行的编码任务,按依赖关系排序执行。 + +--- + +## 任务 1: 移除项目依赖 + +**优先级**: P0 (最高) +**依赖**: 无 +**预估工作量**: 5 分钟 + +### 描述 + +从项目文件中移除 LibVLC 相关的 NuGet 包引用。 + +### 输入 + +- `LanMountainDesktop/LanMountainDesktop.csproj` + +### 输出 + +- 修改后的 `LanMountainDesktop.csproj`,移除以下包引用: + - `LibVLCSharp.Avalonia` + - `VideoLAN.LibVLC.Windows` + - `VideoLAN.LibVLC.Mac` + +### 验收标准 + +- [ ] 项目文件中不再包含 LibVLC 相关包引用 +- [ ] 执行 `dotnet restore` 成功 + +### 执行提示 + +``` +编辑 LanMountainDesktop.csproj,移除以下 PackageReference 节点: +1. +2. +3. +``` + +--- + +## 任务 2: 移除主窗口 XAML 视频控件 + +**优先级**: P0 +**依赖**: 任务 1 +**预估工作量**: 10 分钟 + +### 描述 + +从 MainWindow.axaml 中移除视频壁纸相关的 XAML 控件和命名空间声明。 + +### 输入 + +- `LanMountainDesktop/Views/MainWindow.axaml` + +### 输出 + +- 移除 LibVLC 命名空间声明 +- 移除 `DesktopVideoWallpaperImage` 控件 +- 移除 `DesktopVideoWallpaperView` 控件 + +### 验收标准 + +- [ ] XAML 中无 `xmlns:vlc` 命名空间 +- [ ] XAML 中无 `DesktopVideoWallpaperImage` 元素 +- [ ] XAML 中无 `DesktopVideoWallpaperView` 元素 + +### 执行提示 + +``` +编辑 MainWindow.axaml: +1. 移除第 9 行: xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia" +2. 移除第 126-131 行: +3. 移除第 133-137 行: +``` + +--- + +## 任务 3: 移除主窗口代码视频字段 + +**优先级**: P0 +**依赖**: 任务 1 +**预估工作量**: 15 分钟 + +### 描述 + +从 MainWindow.axaml.cs 中移除视频壁纸相关的字段声明。 + +### 输入 + +- `LanMountainDesktop/Views/MainWindow.axaml.cs` + +### 输出 + +- 移除 `SupportedVideoExtensions` 静态字段 +- 移除所有视频相关实例字段 + +### 验收标准 + +- [ ] 无 `SupportedVideoExtensions` 字段 +- [ ] 无 `_videoWallpaperPosterBitmap` 字段 +- [ ] 无 `_videoWallpaperPosterPath` 字段 +- [ ] 无 `_wallpaperVideoPath` 字段 +- [ ] 无 `_libVlc` 字段 +- [ ] 无 `_videoWallpaperPlayer` 字段 +- [ ] 无 `_videoWallpaperMedia` 字段 +- [ ] 无 `_desktopVideoFrameSync` 及相关视频帧处理字段 + +### 执行提示 + +``` +编辑 MainWindow.axaml.cs: +1. 移除第 68-71 行的 SupportedVideoExtensions 定义 +2. 移除第 123-146 行的所有视频相关字段 +``` + +--- + +## 任务 4: 移除主窗口 OnClosed 清理代码 + +**优先级**: P0 +**依赖**: 任务 3 +**预估工作量**: 5 分钟 + +### 描述 + +从 MainWindow.axaml.cs 的 OnClosed 方法中移除视频相关清理代码。 + +### 输入 + +- `LanMountainDesktop/Views/MainWindow.axaml.cs` (OnClosed 方法) + +### 输出 + +- 简化的 OnClosed 方法,无视频清理逻辑 + +### 验收标准 + +- [ ] OnClosed 方法中无 `StopVideoWallpaper()` 调用 +- [ ] OnClosed 方法中无 `_videoWallpaperMedia` 相关清理 +- [ ] OnClosed 方法中无 `_videoWallpaperPlayer` 相关清理 +- [ ] OnClosed 方法中无 `_libVlc` 相关清理 + +### 执行提示 + +``` +编辑 MainWindow.axaml.cs 的 OnClosed 方法,移除以下代码行: +- StopVideoWallpaper(); +- _videoWallpaperMedia?.Dispose(); _videoWallpaperMedia = null; +- _videoWallpaperPlayer?.Dispose(); _videoWallpaperPlayer = null; +- _desktopVideoFrameRefreshTimer?.Stop(); _desktopVideoFrameRefreshTimer = null; +- _videoWallpaperPosterBitmap?.Dispose(); _videoWallpaperPosterBitmap = null; +- _videoWallpaperPosterPath = null; +- _libVlc?.Dispose(); _libVlc = null; +``` + +--- + +## 任务 5: 移除主窗口 Stub 方法 + +**优先级**: P0 +**依赖**: 任务 1 +**预估工作量**: 20 分钟 + +### 描述 + +从 MainWindow.SettingsHardCut.Stubs.cs 中移除视频壁纸相关方法和 using 声明。 + +### 输入 + +- `LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs` + +### 输出 + +- 移除 LibVLC using 声明 +- 移除 `StartVideoWallpaper` 方法 +- 移除 `StopVideoWallpaper` 方法 +- 移除 `TryCaptureVideoWallpaperPosterFrame` 方法 +- 移除 `ApplyVideoWallpaperPosterVisibility` 方法 + +### 验收标准 + +- [ ] 无 `using LibVLCSharp.Shared;` +- [ ] 无 `using LibVLCSharp.Avalonia;` +- [ ] 无 `StartVideoWallpaper` 方法定义 +- [ ] 无 `StopVideoWallpaper` 方法定义 +- [ ] 无 `TryCaptureVideoWallpaperPosterFrame` 方法定义 +- [ ] 无 `ApplyVideoWallpaperPosterVisibility` 方法定义 + +### 执行提示 + +``` +编辑 MainWindow.SettingsHardCut.Stubs.cs: +1. 移除第 19-20 行的 using 声明 +2. 移除 StartVideoWallpaper 方法(第 337-383 行) +3. 移除 StopVideoWallpaper 方法(第 385-395 行) +4. 移除 ApplyVideoWallpaperPosterVisibility 方法(第 647-664 行) +5. 移除 TryCaptureVideoWallpaperPosterFrame 方法(第 666-751 行) +``` + +--- + +## 任务 6: 简化壁纸状态处理逻辑 + +**优先级**: P0 +**依赖**: 任务 5 +**预估工作量**: 15 分钟 + +### 描述 + +修改 MainWindow.SettingsHardCut.Stubs.cs 中的壁纸状态处理方法,移除视频类型分支。 + +### 输入 + +- `LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs` + +### 输出 + +- 简化的 `SetWallpaperState` 方法 +- 简化的 `UpdateWallpaperDisplay` 方法 +- 简化的 `ApplyWallpaperBrush` 方法 + +### 验收标准 + +- [ ] `SetWallpaperState` 中无视频类型检测分支 +- [ ] `SetWallpaperState` 中无 `_wallpaperVideoPath` 赋值 +- [ ] `UpdateWallpaperDisplay` 中无 `StopVideoWallpaper()` 调用 +- [ ] `ApplyWallpaperBrush` 中无 `ApplyVideoWallpaperPosterVisibility` 调用 + +### 执行提示 + +``` +编辑 MainWindow.SettingsHardCut.Stubs.cs: + +1. SetWallpaperState 方法: + - 移除 requestedTypeIsVideo 变量定义 + - 移除视频类型检测 if 块(SupportedVideoExtensions.Contains 检查) + +2. UpdateWallpaperDisplay 方法: + - 移除视频类型分支,仅保留 ApplyWallpaperBrush() 调用 + +3. ApplyWallpaperBrush 方法: + - 移除所有 ApplyVideoWallpaperPosterVisibility 调用 +``` + +--- + +## 任务 7: 移除外观主题服务视频提取器 + +**优先级**: P1 +**依赖**: 任务 1 +**预估工作量**: 10 分钟 + +### 描述 + +从 AppearanceThemeService.cs 中移除视频壁纸种子提取器接口和实现类。 + +### 输入 + +- `LanMountainDesktop/Services/AppearanceThemeService.cs` + +### 输出 + +- 移除 `IVideoWallpaperSeedExtractor` 接口 +- 移除 `LibVlcVideoWallpaperSeedExtractor` 类 + +### 验收标准 + +- [ ] 无 `IVideoWallpaperSeedExtractor` 接口定义 +- [ ] 无 `LibVlcVideoWallpaperSeedExtractor` 类定义 + +### 执行提示 + +``` +编辑 AppearanceThemeService.cs: +移除第 92-184 行的接口和类定义: +- IVideoWallpaperSeedExtractor 接口 +- LibVlcVideoWallpaperSeedExtractor 类 +``` + +--- + +## 任务 8: 简化壁纸设置页面 XAML + +**优先级**: P1 +**依赖**: 无 +**预估工作量**: 10 分钟 + +### 描述 + +从 WallpaperSettingsPage.axaml 中移除视频预览区域和相关 UI 元素。 + +### 输入 + +- `LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml` + +### 输出 + +- 移除视频预览 Border 区域 +- 移除视频模式提示 TextBlock +- 修改填充方式可见性绑定 + +### 验收标准 + +- [ ] 无视频预览 Border(IsVisible="{Binding IsVideo}") +- [ ] 无 VideoModeHintText 绑定的 TextBlock +- [ ] 填充方式设置绑定改为 `IsVisible="{Binding IsImage}"` + +### 执行提示 + +``` +编辑 WallpaperSettingsPage.axaml: +1. 移除第 29-44 行的视频预览 Border +2. 移除第 150-154 行的视频模式提示 TextBlock +3. 修改第 132 行: IsVisible="{Binding IsImageOrVideo}" 改为 IsVisible="{Binding IsImage}" +``` + +--- + +## 任务 9: 简化壁纸设置 ViewModel + +**优先级**: P1 +**依赖**: 任务 8 +**预估工作量**: 15 分钟 + +### 描述 + +从 WallpaperSettingsPageViewModel.cs 中移除视频相关属性和方法逻辑。 + +### 输入 + +- `LanMountainDesktop/ViewModels/WallpaperSettingsPageViewModel.cs` + +### 输出 + +- 移除 `_isImageOrVideo`、`_isVideo`、`_videoModeHintText` 属性 +- 修改 `CreateWallpaperTypes` 方法 +- 修改 `UpdateVisibility` 方法 +- 修改 `RefreshLocalizedText` 方法 + +### 验收标准 + +- [ ] 无 `IsImageOrVideo` 属性 +- [ ] 无 `IsVideo` 属性 +- [ ] 无 `VideoModeHintText` 属性 +- [ ] `CreateWallpaperTypes` 仅返回 Image 和 SolidColor 选项 +- [ ] `UpdateVisibility` 中无 IsVideo、IsImageOrVideo 赋值 +- [ ] `RefreshLocalizedText` 中无 VideoModeHintText 赋值 + +### 执行提示 + +``` +编辑 WallpaperSettingsPageViewModel.cs: +1. 移除第 76-77 行的 _isImageOrVideo 字段和属性 +2. 移除第 85-86 行的 _isVideo 字段和属性 +3. 移除第 94-95 行的 _videoModeHintText 字段和属性 +4. 修改 CreateWallpaperTypes 方法,移除 Video 选项 +5. 修改 UpdateVisibility 方法,移除 IsVideo 和 IsImageOrVideo 赋值 +6. 修改 RefreshLocalizedText 方法,移除 VideoModeHintText 赋值 +``` + +--- + +## 任务 10: 简化壁纸媒体类型枚举 + +**优先级**: P1 +**依赖**: 无 +**预估工作量**: 5 分钟 + +### 描述 + +从 SettingsContracts.cs 中移除 WallpaperMediaType 枚举的 Video 值。 + +### 输入 + +- `LanMountainDesktop/Services/Settings/SettingsContracts.cs` + +### 输出 + +- 简化的 `WallpaperMediaType` 枚举 + +### 验收标准 + +- [ ] `WallpaperMediaType` 枚举仅包含 `None` 和 `Image` + +### 执行提示 + +``` +编辑 SettingsContracts.cs: +修改第 11-16 行的枚举定义: +public enum WallpaperMediaType +{ + None, + Image +} +``` + +--- + +## 任务 11: 简化壁纸媒体服务 + +**优先级**: P1 +**依赖**: 任务 10 +**预估工作量**: 10 分钟 + +### 描述 + +从 SettingsDomainServices.cs 中移除视频扩展名检测逻辑。 + +### 输入 + +- `LanMountainDesktop/Services/Settings/SettingsDomainServices.cs` + +### 输出 + +- 移除 `VideoExtensions` 字段 +- 简化 `DetectMediaType` 方法 + +### 验收标准 + +- [ ] 无 `VideoExtensions` 字段定义 +- [ ] `DetectMediaType` 方法中无视频扩展名检测逻辑 + +### 执行提示 + +``` +编辑 SettingsDomainServices.cs: +1. 移除第 150-153 行的 VideoExtensions 字段定义 +2. 修改 DetectMediaType 方法,移除视频检测分支 +``` + +--- + +## 任务 12: 清理本地化文件 + +**优先级**: P2 +**依赖**: 无 +**预估工作量**: 5 分钟 + +### 描述 + +从 zh-CN.json 中移除视频壁纸相关的本地化文本。 + +### 输入 + +- `LanMountainDesktop/Localization/zh-CN.json` + +### 输出 + +- 移除视频相关本地化键 +- 修改壁纸描述文本 + +### 验收标准 + +- [ ] 无 `settings.wallpaper.type.video` 键 +- [ ] 无 `settings.wallpaper.video_applied` 键 +- [ ] 无 `settings.wallpaper.video_mode` 键 +- [ ] 无 `settings.wallpaper.video_restored` 键 +- [ ] 无 `settings.wallpaper.video_not_found` 键 +- [ ] 无 `settings.wallpaper.video_player_unavailable` 键 +- [ ] 无 `settings.wallpaper.video_play_failed_format` 键 +- [ ] `settings.wallpaper.description` 文本已更新 + +### 执行提示 + +``` +编辑 zh-CN.json: +1. 移除以下键值对: + - "settings.wallpaper.type.video" + - "settings.wallpaper.video_applied" + - "settings.wallpaper.video_mode" + - "settings.wallpaper.video_restored" + - "settings.wallpaper.video_not_found" + - "settings.wallpaper.video_player_unavailable" + - "settings.wallpaper.video_play_failed_format" + +2. 修改描述文本: + "settings.wallpaper.description": "选择图片后可立即设为应用窗口壁纸。" +``` + +--- + +## 任务 13: 构建验证 + +**优先级**: P0 +**依赖**: 任务 1-12 全部完成 +**预估工作量**: 10 分钟 + +### 描述 + +验证项目在移除视频壁纸功能后能够正常构建。 + +### 输入 + +- 整个项目 + +### 输出 + +- 构建成功确认 + +### 验收标准 + +- [ ] `dotnet build` 执行成功,无编译错误 +- [ ] 无 LibVLC 相关类型未定义错误 +- [ ] 无未使用变量警告(或已处理) + +### 执行提示 + +``` +在项目根目录执行: +dotnet build LanMountainDesktop/LanMountainDesktop.csproj + +检查输出: +- 确认无编译错误 +- 确认无 LibVLC 相关类型引用错误 +``` + +--- + +## 任务 14: 功能验证 + +**优先级**: P0 +**依赖**: 任务 13 +**预估工作量**: 15 分钟 + +### 描述 + +验证应用在移除视频壁纸功能后核心功能正常工作。 + +### 输入 + +- 构建后的应用 + +### 输出 + +- 功能验证报告 + +### 验收标准 + +- [ ] 应用正常启动 +- [ ] 图片壁纸正常显示 +- [ ] 纯色壁纸正常显示 +- [ ] 壁纸设置页面正常打开 +- [ ] 类型选择器仅显示"图片"和"纯色"选项 +- [ ] 壁纸导入功能正常工作 + +### 执行提示 + +``` +运行应用: +dotnet run --project LanMountainDesktop/LanMountainDesktop.csproj + +手动验证: +1. 应用启动无崩溃 +2. 打开设置 -> 壁纸页面 +3. 确认类型选择器仅有"图片"和"纯色" +4. 测试选择图片壁纸 +5. 测试选择纯色壁纸 +``` + +--- + +## 任务依赖关系图 + +``` +任务 1 (移除依赖) + ├── 任务 2 (XAML控件) + ├── 任务 3 (代码字段) + │ └── 任务 4 (OnClosed清理) + ├── 任务 5 (Stub方法) + │ └── 任务 6 (状态处理逻辑) + └── 任务 7 (主题服务) + +任务 8 (设置页面XAML) + └── 任务 9 (设置ViewModel) + +任务 10 (枚举简化) + └── 任务 11 (媒体服务) + +任务 12 (本地化) - 独立 + +任务 13 (构建验证) - 依赖所有任务 + └── 任务 14 (功能验证) +``` + +--- + +## 执行顺序建议 + +按以下顺序执行可确保依赖关系正确: + +1. **第一批** (可并行): 任务 1, 任务 8, 任务 10, 任务 12 +2. **第二批** (可并行): 任务 2, 任务 3, 任务 5, 任务 7, 任务 9, 任务 11 +3. **第三批** (可并行): 任务 4, 任务 6 +4. **第四批**: 任务 13 +5. **第五批**: 任务 14 diff --git a/LanMountainDesktop/LanMountainDesktop.csproj b/LanMountainDesktop/LanMountainDesktop.csproj index 2a5f164..006549f 100644 --- a/LanMountainDesktop/LanMountainDesktop.csproj +++ b/LanMountainDesktop/LanMountainDesktop.csproj @@ -52,15 +52,14 @@ - + - - + diff --git a/LanMountainDesktop/Localization/zh-CN.json b/LanMountainDesktop/Localization/zh-CN.json index e6451c9..f866438 100644 --- a/LanMountainDesktop/Localization/zh-CN.json +++ b/LanMountainDesktop/Localization/zh-CN.json @@ -31,13 +31,14 @@ "settings.nav.plugins": "插件", "settings.nav.about": "关于", "settings.wallpaper.title": "壁纸", - "settings.wallpaper.description": "选择图片或视频后可立即设为应用窗口壁纸。", + "settings.wallpaper.description": "选择图片后可立即设为应用窗口壁纸。", "settings.wallpaper.current_label": "当前壁纸", "settings.wallpaper.type_label": "壁纸类型", "settings.wallpaper.type.image": "图片", - "settings.wallpaper.type.video": "视频", "settings.wallpaper.type.solid_color": "纯色", "settings.wallpaper.color_label": "壁纸颜色", + "settings.wallpaper.custom_color_tooltip": "自定义颜色", + "settings.wallpaper.custom_color_apply": "应用", "settings.wallpaper.placement_label": "显示方式", "settings.wallpaper.placement_desc": "调整图像在桌面上的填充方式。", "settings.wallpaper.pick_button": "选择文件", @@ -46,20 +47,14 @@ "settings.wallpaper.storage_unavailable": "存储提供器不可用。", "settings.wallpaper.import_failed": "导入壁纸文件失败。", "settings.wallpaper.image_applied": "图片壁纸已应用。", - "settings.wallpaper.video_applied": "视频壁纸已应用。", "settings.wallpaper.unsupported_file": "所选文件类型不受支持。", "settings.wallpaper.apply_failed_format": "应用壁纸失败:{0}", "settings.wallpaper.mode_format": "壁纸模式:{0}。", - "settings.wallpaper.video_mode": "视频壁纸使用自动填充模式。", "settings.wallpaper.cleared": "背景已恢复为纯色。", "settings.wallpaper.default_status": "当前使用纯色背景。", "settings.wallpaper.saved_not_found": "未找到已保存的壁纸文件,已使用纯色背景。", "settings.wallpaper.restored": "已恢复保存的壁纸。", - "settings.wallpaper.video_restored": "已恢复保存的视频壁纸。", "settings.wallpaper.restore_failed": "恢复已保存壁纸失败,已使用纯色背景。", - "settings.wallpaper.video_not_found": "未找到视频壁纸文件。", - "settings.wallpaper.video_player_unavailable": "视频播放器不可用。", - "settings.wallpaper.video_play_failed_format": "播放视频壁纸失败:{0}", "settings.grid.title": "网格布局", "settings.grid.description": "每个组件至少占用一个格子(最小 1x1)。", "settings.grid.short_side_label": "短边格数", @@ -85,7 +80,6 @@ "settings.color.theme_ready_format": "主题色已就绪:{0}。", "settings.color.theme_applied_format": "{0}主题色已应用:{1}。", "settings.color.theme_updated_wallpaper": "壁纸已更新,莫奈色已刷新。", - "settings.color.theme_updated_video": "视频壁纸已更新,主题色已刷新。", "settings.color.theme_cleared_wallpaper": "壁纸已清除,莫奈色已刷新。", "settings.status_bar.title": "状态栏", "settings.status_bar.description": "选择顶部状态栏显示的组件。", @@ -393,7 +387,6 @@ "settings.footer": "LanMountainDesktop 设置", "filepicker.title": "选择壁纸", "filepicker.image_files": "图片文件", - "filepicker.video_files": "视频文件", "common.day": "日间", "common.night": "夜间", "common.back": "返回", diff --git a/LanMountainDesktop/Services/AppearanceThemeService.cs b/LanMountainDesktop/Services/AppearanceThemeService.cs index f3a4691..7c6b9e2 100644 --- a/LanMountainDesktop/Services/AppearanceThemeService.cs +++ b/LanMountainDesktop/Services/AppearanceThemeService.cs @@ -15,7 +15,6 @@ using LanMountainDesktop.Models; using LanMountainDesktop.PluginSdk; using LanMountainDesktop.Services.Settings; using LanMountainDesktop.Theme; -using LibVLCSharp.Shared; using Microsoft.Win32; namespace LanMountainDesktop.Services; @@ -89,11 +88,6 @@ internal interface IMaterialSurfaceService AppearanceMaterialSurface GetSurface(ThemeColorContext context, MaterialSurfaceRole role); } -internal interface IVideoWallpaperSeedExtractor -{ - IReadOnlyList ExtractSeedCandidates(string videoPath, MonetColorService monetColorService); -} - internal readonly record struct WallpaperSeedSourceDescriptor( string SourceKind, string SourceKey, @@ -114,75 +108,6 @@ internal readonly record struct WallpaperPaletteResolution( Color EffectiveSeedColor, string? ResolvedWallpaperPath); -internal sealed class LibVlcVideoWallpaperSeedExtractor : IVideoWallpaperSeedExtractor -{ - public IReadOnlyList ExtractSeedCandidates(string videoPath, MonetColorService monetColorService) - { - if (string.IsNullOrWhiteSpace(videoPath) || !File.Exists(videoPath)) - { - return []; - } - - var snapshotPath = Path.Combine( - Path.GetTempPath(), - $"lanmountaindesktop-video-seed-{Guid.NewGuid():N}.png"); - - try - { - using var libVlc = new LibVLC("--no-audio", "--intf=dummy", "--no-video-title-show"); - using var media = new Media(libVlc, new Uri(videoPath)); - using var mediaPlayer = new MediaPlayer(libVlc) - { - Media = media - }; - - mediaPlayer.Play(); - - var stopwatch = Stopwatch.StartNew(); - while (stopwatch.Elapsed < TimeSpan.FromSeconds(5)) - { - Thread.Sleep(180); - if (!mediaPlayer.TakeSnapshot(0, snapshotPath, 320, 180)) - { - continue; - } - - var fileInfo = new FileInfo(snapshotPath); - if (!fileInfo.Exists || fileInfo.Length <= 0) - { - continue; - } - - using var bitmap = new Bitmap(snapshotPath); - return monetColorService.ExtractSeedCandidates(bitmap); - } - } - catch (Exception ex) - { - AppLogger.Warn( - "Appearance.VideoWallpaperPalette", - $"Failed to extract wallpaper seed candidates from video '{videoPath}'.", - ex); - } - finally - { - try - { - if (File.Exists(snapshotPath)) - { - File.Delete(snapshotPath); - } - } - catch - { - // Best effort cleanup only. - } - } - - return []; - } -} - internal sealed class SystemWallpaperService : ISystemWallpaperService { public bool IsSupported => OperatingSystem.IsWindows(); @@ -477,7 +402,6 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa private readonly ISystemWallpaperService _systemWallpaperService; private readonly IWindowMaterialService _windowMaterialService; private readonly IMaterialSurfaceService _materialSurfaceService; - private readonly IVideoWallpaperSeedExtractor _videoWallpaperSeedExtractor; private readonly MonetColorService _monetColorService = new(); private readonly string _liveThemeColorMode; private readonly string _liveSystemMaterialMode; @@ -490,14 +414,12 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa ISettingsFacadeService settingsFacade, ISystemWallpaperService systemWallpaperService, IWindowMaterialService windowMaterialService, - IMaterialSurfaceService materialSurfaceService, - IVideoWallpaperSeedExtractor? videoWallpaperSeedExtractor = null) + IMaterialSurfaceService materialSurfaceService) { _settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade)); _systemWallpaperService = systemWallpaperService ?? throw new ArgumentNullException(nameof(systemWallpaperService)); _windowMaterialService = windowMaterialService ?? throw new ArgumentNullException(nameof(windowMaterialService)); _materialSurfaceService = materialSurfaceService ?? throw new ArgumentNullException(nameof(materialSurfaceService)); - _videoWallpaperSeedExtractor = videoWallpaperSeedExtractor ?? new LibVlcVideoWallpaperSeedExtractor(); var initialThemeState = _settingsFacade.Theme.Get(); _liveThemeColorMode = ThemeAppearanceValues.NormalizeThemeColorMode( initialThemeState.ThemeColorMode, @@ -886,7 +808,6 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa IReadOnlyList seedCandidates = source.SourceKind switch { "app_wallpaper" or "system_wallpaper" => ExtractImageSeedCandidates(source.FilePath), - "app_video" => ExtractVideoSeedCandidates(source.FilePath), "app_solid" when source.SolidColor is { } solidColor => new[] { solidColor }, _ => [] }; @@ -920,16 +841,6 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa } } - private IReadOnlyList ExtractVideoSeedCandidates(string? wallpaperPath) - { - if (string.IsNullOrWhiteSpace(wallpaperPath) || !File.Exists(wallpaperPath)) - { - return []; - } - - return _videoWallpaperSeedExtractor.ExtractSeedCandidates(wallpaperPath, _monetColorService); - } - private WallpaperSeedSourceDescriptor ResolveWallpaperSeedSource(WallpaperSettingsState wallpaperState) { if (string.Equals(wallpaperState.Type, "SolidColor", StringComparison.OrdinalIgnoreCase) && @@ -960,16 +871,6 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa wallpaperPath, null); } - - if (appWallpaperMediaType == WallpaperMediaType.Video) - { - return new WallpaperSeedSourceDescriptor( - "app_video", - CreateWallpaperSourceKey("app_video", wallpaperPath), - wallpaperPath, - wallpaperPath, - null); - } } var systemWallpaper = _systemWallpaperService.GetWallpaperPath(); diff --git a/LanMountainDesktop/Services/Settings/SettingsContracts.cs b/LanMountainDesktop/Services/Settings/SettingsContracts.cs index 3a33d36..cab1afd 100644 --- a/LanMountainDesktop/Services/Settings/SettingsContracts.cs +++ b/LanMountainDesktop/Services/Settings/SettingsContracts.cs @@ -11,12 +11,11 @@ namespace LanMountainDesktop.Services.Settings; public enum WallpaperMediaType { None, - Image, - Video + Image } public sealed record GridSettingsState(int ShortSideCells, string SpacingPreset, int EdgeInsetPercent); -public sealed record WallpaperSettingsState(string? WallpaperPath, string Type, string? Color, string Placement); +public sealed record WallpaperSettingsState(string? WallpaperPath, string Type, string? Color, string Placement, string? CustomColor = null); public sealed record ThemeAppearanceSettingsState( bool IsNightMode, string? ThemeColor, diff --git a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs index 464d25a..d23f42f 100644 --- a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs +++ b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs @@ -147,11 +147,6 @@ internal sealed class WallpaperMediaService : IWallpaperMediaService ".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp" }; - private static readonly HashSet VideoExtensions = new(StringComparer.OrdinalIgnoreCase) - { - ".mp4", ".mkv", ".webm", ".avi", ".mov", ".m4v" - }; - private readonly string _wallpapersDirectory; public WallpaperMediaService() @@ -180,11 +175,6 @@ internal sealed class WallpaperMediaService : IWallpaperMediaService return WallpaperMediaType.Image; } - if (VideoExtensions.Contains(extension)) - { - return WallpaperMediaType.Video; - } - return WallpaperMediaType.None; } diff --git a/LanMountainDesktop/ViewModels/WallpaperSettingsPageViewModel.cs b/LanMountainDesktop/ViewModels/WallpaperSettingsPageViewModel.cs index 5b351f5..eeaffe1 100644 --- a/LanMountainDesktop/ViewModels/WallpaperSettingsPageViewModel.cs +++ b/LanMountainDesktop/ViewModels/WallpaperSettingsPageViewModel.cs @@ -82,23 +82,24 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase [ObservableProperty] private bool _isImage; - [ObservableProperty] - private bool _isVideo; - [ObservableProperty] private Bitmap? _previewImage; [ObservableProperty] private IBrush? _previewBrush; + // 自定义颜色持久化 [ObservableProperty] - private string _videoModeHintText = string.Empty; + private Color _customColor = Colors.White; + + [ObservableProperty] + private IBrush _customColorBrush = new SolidColorBrush(Colors.White); public void Load() { var wallpaper = _settingsFacade.Wallpaper.Get(); WallpaperPath = wallpaper.WallpaperPath ?? string.Empty; - + SelectedWallpaperType = WallpaperTypes.FirstOrDefault(t => t.Value == wallpaper.Type) ?? WallpaperTypes[0]; SelectedColor = wallpaper.Color ?? PresetColors[0]; @@ -108,7 +109,14 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase SelectedWallpaperPlacement = WallpaperPlacements.FirstOrDefault(option => string.Equals(option.Value, wallpaperPlacement, StringComparison.OrdinalIgnoreCase)) ?? WallpaperPlacements[0]; - + + // 加载自定义颜色 + if (!string.IsNullOrWhiteSpace(wallpaper.CustomColor) && Color.TryParse(wallpaper.CustomColor, out var customColor)) + { + CustomColor = customColor; + CustomColorBrush = new SolidColorBrush(customColor); + } + UpdateVisibility(); UpdatePreviewFromCurrentSelection(); } @@ -124,8 +132,7 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase private void UpdateVisibility() { IsImage = SelectedWallpaperType?.Value == "Image"; - IsVideo = SelectedWallpaperType?.Value == "Video"; - IsImageOrVideo = SelectedWallpaperType?.Value is "Image" or "Video"; + IsImageOrVideo = IsImage; IsSolidColor = SelectedWallpaperType?.Value == "SolidColor"; } @@ -135,6 +142,16 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase SaveWallpaper(); } + partial void OnCustomColorChanged(Color value) + { + CustomColorBrush = new SolidColorBrush(value); + // 将自定义颜色应用到壁纸 + var colorHex = $"#{value.A:X2}{value.R:X2}{value.G:X2}{value.B:X2}"; + SelectedColor = colorHex; + if (_isInitializing) return; + SaveWallpaper(); + } + public async Task ImportWallpaperAsync(string sourcePath) { var importedPath = await _settingsFacade.WallpaperMedia.ImportAssetAsync(sourcePath); @@ -222,11 +239,13 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase var normalizedPath = SelectedWallpaperType?.Value == "SolidColor" || string.IsNullOrWhiteSpace(WallpaperPath) ? null : WallpaperPath; + var customColorHex = $"#{CustomColor.A:X2}{CustomColor.R:X2}{CustomColor.G:X2}{CustomColor.B:X2}"; _settingsFacade.Wallpaper.Save(new WallpaperSettingsState( normalizedPath, selectedType, SelectedColor, - selectedPlacement)); + selectedPlacement, + customColorHex)); } private IReadOnlyList CreateWallpaperPlacements() @@ -246,7 +265,6 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase return [ new SelectionOption("Image", L("settings.wallpaper.type.image", "Image")), - new SelectionOption("Video", L("settings.wallpaper.type.video", "Video")), new SelectionOption("SolidColor", L("settings.wallpaper.type.solid_color", "Solid Color")) ]; } @@ -257,7 +275,7 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase [ "#D8A7B1", "#B6C9BB", "#A2B5BB", "#E6E2D3", "#B5A397", "#C5C1C0", "#D4BE8D", "#C08261", - "#8E9775", "#9FBAD3", "#E5BAA2", "#4E596F" + "#8E9775", "#9FBAD3", "#E5BAA2" ]; } @@ -271,7 +289,6 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase WallpaperPlacementDescription = L("settings.wallpaper.placement_desc", "Adjust how the image fills the desktop."); ImportWallpaperButtonText = L("settings.wallpaper.pick_button", "Import Wallpaper"); FilePickerTitle = L("filepicker.title", "Select wallpaper"); - VideoModeHintText = L("settings.wallpaper.video_mode", "Video wallpaper uses automatic fill mode."); } private string L(string key, string fallback) diff --git a/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs b/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs index f12ec94..1cdca75 100644 --- a/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs +++ b/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs @@ -16,8 +16,6 @@ using LanMountainDesktop.PluginSdk; using LanMountainDesktop.Services; using LanMountainDesktop.Theme; using LanMountainDesktop.Views.Components; -using LibVLCSharp.Shared; -using LibVLCSharp.Avalonia; namespace LanMountainDesktop.Views; @@ -213,7 +211,6 @@ public partial class MainWindow _wallpaperType = string.IsNullOrWhiteSpace(type) ? "Image" : type.Trim(); _wallpaperPlacement = WallpaperImageBrushFactory.NormalizePlacement(placement); _wallpaperSolidColor = TryParseColor(color, out var parsedColor) ? parsedColor : null; - _wallpaperVideoPath = null; _wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured; _wallpaperBitmap?.Dispose(); @@ -235,17 +232,6 @@ public partial class MainWindow } var extension = Path.GetExtension(_wallpaperPath); - var requestedTypeIsVideo = string.Equals(_wallpaperType, "Video", StringComparison.OrdinalIgnoreCase); - if (SupportedVideoExtensions.Contains(extension) || requestedTypeIsVideo) - { - _wallpaperMediaType = WallpaperMediaType.Video; - _wallpaperVideoPath = _wallpaperPath; - _wallpaperDisplayState = File.Exists(_wallpaperPath) - ? WallpaperDisplayState.CurrentValidWallpaper - : WallpaperDisplayState.TemporarilyUnavailable; - return; - } - if (!SupportedImageExtensions.Contains(extension)) { _wallpaperMediaType = WallpaperMediaType.Image; @@ -285,7 +271,6 @@ public partial class MainWindow if (_wallpaperMediaType == WallpaperMediaType.SolidColor && _wallpaperSolidColor.HasValue) { DesktopWallpaperLayer.Background = new SolidColorBrush(_wallpaperSolidColor.Value); - ApplyVideoWallpaperPosterVisibility(showPoster: false); return; } @@ -296,7 +281,6 @@ public partial class MainWindow DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? CreateNeutralWallpaperFallbackBrush(); DesktopWallpaperImageLayer.Background = WallpaperImageBrushFactory.Create(_wallpaperBitmap, _wallpaperPlacement); DesktopWallpaperImageLayer.IsVisible = true; - ApplyVideoWallpaperPosterVisibility(showPoster: false); return; } @@ -308,92 +292,17 @@ public partial class MainWindow DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? CreateNeutralWallpaperFallbackBrush(); DesktopWallpaperImageLayer.Background = WallpaperImageBrushFactory.Create(_lastValidWallpaperBitmap, _wallpaperPlacement); DesktopWallpaperImageLayer.IsVisible = true; - ApplyVideoWallpaperPosterVisibility(showPoster: false); return; } DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? CreateNeutralWallpaperFallbackBrush(); - ApplyVideoWallpaperPosterVisibility( - showPoster: _wallpaperMediaType == WallpaperMediaType.Video && _videoWallpaperPosterBitmap is not null); } private void UpdateWallpaperDisplay() { - if (_wallpaperMediaType == WallpaperMediaType.Video) - { - if (!string.IsNullOrWhiteSpace(_wallpaperVideoPath)) - { - StartVideoWallpaper(_wallpaperVideoPath); - } - } - else - { - StopVideoWallpaper(); - } - ApplyWallpaperBrush(); } - private void StartVideoWallpaper(string videoPath) - { - if (string.IsNullOrWhiteSpace(videoPath) || !File.Exists(videoPath)) - { - ApplyVideoWallpaperPosterVisibility(showPoster: _videoWallpaperPosterBitmap is not null); - return; - } - - try - { - _libVlc ??= new LibVLC(); - _videoWallpaperPlayer ??= new MediaPlayer(_libVlc); - - if (_videoWallpaperMedia?.Mrl != videoPath) - { - _videoWallpaperMedia?.Dispose(); - _videoWallpaperMedia = new Media(_libVlc, new Uri(videoPath)); - _videoWallpaperPlayer.Media = _videoWallpaperMedia; - } - - if (DesktopVideoWallpaperView is { } videoView) - { - videoView.MediaPlayer = _videoWallpaperPlayer; - videoView.IsVisible = true; - } - - if (!string.Equals(_videoWallpaperPosterPath, videoPath, StringComparison.OrdinalIgnoreCase)) - { - ApplyVideoWallpaperPosterVisibility(showPoster: false); - } - else - { - ApplyVideoWallpaperPosterVisibility(showPoster: _videoWallpaperPosterBitmap is not null); - } - - if (!_videoWallpaperPlayer.IsPlaying) - { - _videoWallpaperPlayer.Play(); - } - - TryCaptureVideoWallpaperPosterFrame(videoPath); - } - catch - { - ApplyVideoWallpaperPosterVisibility(showPoster: _videoWallpaperPosterBitmap is not null); - } - } - - private void StopVideoWallpaper() - { - if (DesktopVideoWallpaperView is { } videoView) - { - videoView.IsVisible = false; - } - - _videoWallpaperPlayer?.Stop(); - _wallpaperVideoPath = null; - ApplyVideoWallpaperPosterVisibility(showPoster: false); - } - private double CalculateCurrentBackgroundLuminance() { var brush = DesktopWallpaperLayer.Background; @@ -644,112 +553,6 @@ public partial class MainWindow } } - private void ApplyVideoWallpaperPosterVisibility(bool showPoster) - { - if (DesktopVideoWallpaperImage is not { } posterImage) - { - return; - } - - if (!showPoster || - _videoWallpaperPosterBitmap is null || - !string.Equals(_videoWallpaperPosterPath, _wallpaperVideoPath, StringComparison.OrdinalIgnoreCase)) - { - posterImage.IsVisible = false; - return; - } - - posterImage.Source = _videoWallpaperPosterBitmap; - posterImage.IsVisible = true; - } - - private void TryCaptureVideoWallpaperPosterFrame(string videoPath) - { - if (_videoWallpaperPlayer is null || string.IsNullOrWhiteSpace(videoPath)) - { - return; - } - - _ = Task.Run(async () => - { - var snapshotPath = Path.Combine( - Path.GetTempPath(), - $"lanmountaindesktop-wallpaper-poster-{Guid.NewGuid():N}.png"); - - try - { - for (var attempt = 0; attempt < 12; attempt++) - { - await Task.Delay(250).ConfigureAwait(false); - - if (_wallpaperMediaType != WallpaperMediaType.Video || - !string.Equals(_wallpaperVideoPath, videoPath, StringComparison.OrdinalIgnoreCase) || - _videoWallpaperPlayer is null) - { - return; - } - - if (!_videoWallpaperPlayer.TakeSnapshot(0, snapshotPath, 640, 360)) - { - continue; - } - - if (!File.Exists(snapshotPath)) - { - continue; - } - - var fileInfo = new FileInfo(snapshotPath); - if (fileInfo.Length <= 0) - { - continue; - } - - Bitmap posterBitmap; - await using (var stream = File.OpenRead(snapshotPath)) - { - posterBitmap = new Bitmap(stream); - } - - await Dispatcher.UIThread.InvokeAsync(() => - { - if (_wallpaperMediaType != WallpaperMediaType.Video || - !string.Equals(_wallpaperVideoPath, videoPath, StringComparison.OrdinalIgnoreCase)) - { - posterBitmap.Dispose(); - return; - } - - _videoWallpaperPosterBitmap?.Dispose(); - _videoWallpaperPosterBitmap = posterBitmap; - _videoWallpaperPosterPath = videoPath; - ApplyVideoWallpaperPosterVisibility(showPoster: true); - }); - - return; - } - } - catch - { - // Best effort poster capture only. - } - finally - { - try - { - if (File.Exists(snapshotPath)) - { - File.Delete(snapshotPath); - } - } - catch - { - // Best effort cleanup only. - } - } - }); - } - private DesktopLayoutSettingsSnapshot BuildDesktopLayoutSettingsSnapshot() { return new DesktopLayoutSettingsSnapshot diff --git a/LanMountainDesktop/Views/MainWindow.axaml b/LanMountainDesktop/Views/MainWindow.axaml index 0362f53..55f2704 100644 --- a/LanMountainDesktop/Views/MainWindow.axaml +++ b/LanMountainDesktop/Views/MainWindow.axaml @@ -6,7 +6,7 @@ xmlns:ic="using:FluentIcons.Avalonia.Fluent" xmlns:mi="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:comp="using:LanMountainDesktop.Views.Components" - xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" @@ -123,18 +123,7 @@ VerticalAlignment="Stretch" Background="Transparent" /> - - SupportedVideoExtensions = new(StringComparer.OrdinalIgnoreCase) - { - ".mp4", ".mkv", ".webm", ".avi", ".mov", ".m4v" - }; private static readonly TaskbarActionId[] DefaultPinnedTaskbarActions = [ TaskbarActionId.MinimizeToWindows @@ -120,30 +115,11 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider private Bitmap? _wallpaperBitmap; private Bitmap? _lastValidWallpaperBitmap; private string? _lastValidWallpaperPath; - private Bitmap? _videoWallpaperPosterBitmap; - private string? _videoWallpaperPosterPath; private WallpaperMediaType _wallpaperMediaType; private WallpaperDisplayState _wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured; private string _wallpaperPlacement = WallpaperImageBrushFactory.Fill; - private string? _wallpaperVideoPath; private string _wallpaperType = "Image"; private Color? _wallpaperSolidColor; - private LibVLC? _libVlc; - private MediaPlayer? _videoWallpaperPlayer; - private Media? _videoWallpaperMedia; - private readonly object _desktopVideoFrameSync = new(); - private MediaPlayer.LibVLCVideoLockCb? _desktopVideoLockCallback; - private MediaPlayer.LibVLCVideoUnlockCb? _desktopVideoUnlockCallback; - private MediaPlayer.LibVLCVideoDisplayCb? _desktopVideoDisplayCallback; - private DispatcherTimer? _desktopVideoFrameRefreshTimer; - private IntPtr _desktopVideoFrameBufferPtr; - private byte[]? _desktopVideoStagingBuffer; - private WriteableBitmap? _desktopVideoBitmap; - private int _desktopVideoFrameWidth; - private int _desktopVideoFrameHeight; - private int _desktopVideoFramePitch; - private int _desktopVideoFrameBufferSize; - private int _desktopVideoFrameDirtyFlag; private string? _wallpaperPath; private string _wallpaperStatus = "Current background uses solid color."; private IReadOnlyList _recommendedColors = Array.Empty(); @@ -333,21 +309,9 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider _detachedComponentLibraryWindow.Close(); } _detachedComponentLibraryWindow = null; - StopVideoWallpaper(); DisposeLauncherResources(); - _videoWallpaperMedia?.Dispose(); - _videoWallpaperMedia = null; - _videoWallpaperPlayer?.Dispose(); - _videoWallpaperPlayer = null; - _desktopVideoFrameRefreshTimer?.Stop(); - _desktopVideoFrameRefreshTimer = null; - _videoWallpaperPosterBitmap?.Dispose(); - _videoWallpaperPosterBitmap = null; - _videoWallpaperPosterPath = null; _lastValidWallpaperBitmap?.Dispose(); _lastValidWallpaperBitmap = null; - _libVlc?.Dispose(); - _libVlc = null; if (_recommendationInfoService is IDisposable recommendationServiceDisposable) { recommendationServiceDisposable.Dispose(); diff --git a/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml index 9a10cf9..22ba8c0 100644 --- a/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml +++ b/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml @@ -21,27 +21,11 @@ CornerRadius="48" BoxShadow="0 12 32 #50000000"> - + - - - - - - @@ -52,31 +36,104 @@ - - - - - - - - - - - - - + + + + @@ -105,7 +162,7 @@ - + @@ -129,7 +186,7 @@ @@ -147,12 +204,6 @@ - -