mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-29 14:14:26 +08:00
0.6.2
删除了视频壁纸功能,为纯色背景添加了自定义颜色选项。
This commit is contained in:
@@ -52,15 +52,14 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
|
||||
<PackageReference Include="LibVLCSharp.Avalonia" Version="3.9.5" />
|
||||
|
||||
<PackageReference Include="PortAudioSharp2" Version="1.0.6" />
|
||||
<PackageReference Include="MaterialColorUtilities" Version="0.3.0" />
|
||||
<PackageReference Include="PostHog" Version="2.4.0" />
|
||||
<PackageReference Include="Sentry" Version="4.0.0" />
|
||||
<PackageReference Include="System.Runtime.WindowsRuntime" Version="4.7.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.23" Condition="('$(RuntimeIdentifier)' == '' and $([MSBuild]::IsOSPlatform('Windows')))
 or '$(RuntimeIdentifier)' == 'win-x64'
 or '$(RuntimeIdentifier)' == 'win-x86'" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Mac" Version="3.1.3.1" Condition="('$(RuntimeIdentifier)' == '' and $([MSBuild]::IsOSPlatform('OSX')))
 or '$(RuntimeIdentifier)' == 'osx-x64'" />
|
||||
|
||||
<PackageReference Include="WebView.Avalonia" Version="11.0.0.1" />
|
||||
<PackageReference Include="WebView.Avalonia.Desktop" Version="11.0.0.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="16.3.0" />
|
||||
|
||||
@@ -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": "返回",
|
||||
|
||||
@@ -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<Color> 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<Color> 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<Color> 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<Color> 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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -147,11 +147,6 @@ internal sealed class WallpaperMediaService : IWallpaperMediaService
|
||||
".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SelectionOption> 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
<Image x:Name="DesktopVideoWallpaperImage"
|
||||
IsVisible="False"
|
||||
IsHitTestVisible="False"
|
||||
Stretch="UniformToFill"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch" />
|
||||
|
||||
<vlc:VideoView x:Name="DesktopVideoWallpaperView"
|
||||
IsVisible="False"
|
||||
IsHitTestVisible="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch" />
|
||||
|
||||
<Grid x:Name="DesktopGrid"
|
||||
HorizontalAlignment="Center"
|
||||
|
||||
@@ -25,7 +25,7 @@ using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using LanMountainDesktop.Theme;
|
||||
using LanMountainDesktop.Views.Components;
|
||||
using LibVLCSharp.Shared;
|
||||
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
@@ -35,7 +35,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
{
|
||||
None,
|
||||
Image,
|
||||
Video,
|
||||
SolidColor
|
||||
}
|
||||
|
||||
@@ -65,10 +64,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
{
|
||||
".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp"
|
||||
};
|
||||
private static readonly HashSet<string> 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<Color> _recommendedColors = Array.Empty<Color>();
|
||||
@@ -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();
|
||||
|
||||
@@ -21,27 +21,11 @@
|
||||
CornerRadius="48"
|
||||
BoxShadow="0 12 32 #50000000">
|
||||
<Panel Background="{DynamicResource AdaptiveSurfaceBaseBrush}" Margin="2">
|
||||
<!-- 图片/视频预览 -->
|
||||
<!-- 图片预览 -->
|
||||
<Border Background="#FFF6F7F9"
|
||||
IsVisible="{Binding IsImage}">
|
||||
<Border Background="{Binding PreviewBrush}" />
|
||||
</Border>
|
||||
<Border Background="#FFF6F7F9"
|
||||
IsVisible="{Binding IsVideo}">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<fi:FluentIcon Icon="Video"
|
||||
Width="72"
|
||||
Height="72"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
<TextBlock Text="{Binding VideoModeHintText}"
|
||||
Width="300"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- 纯色预览 -->
|
||||
<Border Background="{Binding SelectedColor}"
|
||||
IsVisible="{Binding IsSolidColor}" />
|
||||
@@ -52,31 +36,104 @@
|
||||
|
||||
<!-- 右侧:颜色选择网格 -->
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="12" IsVisible="{Binding IsSolidColor}">
|
||||
<TextBlock Text="{Binding WallpaperColorLabel}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
<TextBlock Text="{Binding WallpaperColorLabel}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Opacity="0.8" />
|
||||
<ItemsControl ItemsSource="{Binding PresetColors}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="4" Rows="3" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="{Binding}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding $parent[UserControl].((vm:WallpaperSettingsPageViewModel)DataContext).SelectColorCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
ToolTip.Tip="{Binding}">
|
||||
<!-- 简单的悬停与选中效果由 Button 默认样式提供,
|
||||
由于使用了低饱和度颜色,背景色本身就很柔和 -->
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<UniformGrid Columns="4" Rows="3">
|
||||
<!-- 预设颜色 1-11 -->
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#D8A7B1"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#D8A7B1"
|
||||
ToolTip.Tip="#D8A7B1" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#B6C9BB"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#B6C9BB"
|
||||
ToolTip.Tip="#B6C9BB" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#A2B5BB"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#A2B5BB"
|
||||
ToolTip.Tip="#A2B5BB" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#E6E2D3"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#E6E2D3"
|
||||
ToolTip.Tip="#E6E2D3" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#B5A397"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#B5A397"
|
||||
ToolTip.Tip="#B5A397" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#C5C1C0"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#C5C1C0"
|
||||
ToolTip.Tip="#C5C1C0" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#D4BE8D"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#D4BE8D"
|
||||
ToolTip.Tip="#D4BE8D" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#C08261"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#C08261"
|
||||
ToolTip.Tip="#C08261" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#8E9775"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#8E9775"
|
||||
ToolTip.Tip="#8E9775" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#9FBAD3"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#9FBAD3"
|
||||
ToolTip.Tip="#9FBAD3" />
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="#E5BAA2"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
Command="{Binding SelectColorCommand}"
|
||||
CommandParameter="#E5BAA2"
|
||||
ToolTip.Tip="#E5BAA2" />
|
||||
<!-- 第12个位置:自定义颜色选择器 -->
|
||||
<Button Width="48" Height="48" Margin="4" Padding="0"
|
||||
Background="{Binding CustomColorBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="6"
|
||||
ToolTip.Tip="自定义颜色">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedLeft">
|
||||
<StackPanel Width="260" Spacing="12">
|
||||
<ColorPicker Color="{Binding CustomColor}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</UniformGrid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -105,7 +162,7 @@
|
||||
</ui:SettingsExpander.Footer>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<!-- 图片/视频文件选择 -->
|
||||
<!-- 图片文件选择 -->
|
||||
<ui:SettingsExpander Header="{Binding WallpaperPathLabel}"
|
||||
IsVisible="{Binding IsImage}"
|
||||
Margin="0,4,0,0">
|
||||
@@ -129,7 +186,7 @@
|
||||
<!-- 填充方式 -->
|
||||
<ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}"
|
||||
Description="{Binding WallpaperPlacementDescription}"
|
||||
IsVisible="{Binding IsImageOrVideo}"
|
||||
IsVisible="{Binding IsImage}"
|
||||
Margin="0,4,0,0">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<fi:SymbolIconSource Symbol="Maximize" />
|
||||
@@ -147,12 +204,6 @@
|
||||
</ui:SettingsExpander.Footer>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<TextBlock Margin="0,8,0,0"
|
||||
IsVisible="{Binding IsVideo}"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="{Binding VideoModeHintText}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user