diff --git a/source/SylphyHorn.Core/Serialization/GeneralSettings.cs b/source/SylphyHorn.Core/Serialization/GeneralSettings.cs index ef80c14..f971786 100644 --- a/source/SylphyHorn.Core/Serialization/GeneralSettings.cs +++ b/source/SylphyHorn.Core/Serialization/GeneralSettings.cs @@ -33,6 +33,8 @@ public GeneralSettings(ISerializationProvider provider) public SerializableProperty Culture => this.Cache(key => new SerializableProperty(key, this._provider)); + public SerializableProperty Position => this.Cache(key => new SerializableProperty(key, this._provider, 4 /* Fill */)); + public SerializableProperty Placement => this.Cache(key => new SerializableProperty(key, this._provider, 5 /* Center */)); public SerializableProperty Display => this.Cache(key => new SerializableProperty(key, this._provider, 0)); diff --git a/source/SylphyHorn/Application.xaml b/source/SylphyHorn/Application.xaml index b3e7c15..2a8cf5b 100644 --- a/source/SylphyHorn/Application.xaml +++ b/source/SylphyHorn/Application.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converters="http://schemes.grabacr.net/winfx/2015/personal/converters" + xmlns:converters2="clr-namespace:SylphyHorn.UI.Converters" xmlns:controls="clr-namespace:SylphyHorn.UI.Controls"> @@ -21,6 +22,7 @@ + diff --git a/source/SylphyHorn/Properties/Resources.Designer.cs b/source/SylphyHorn/Properties/Resources.Designer.cs index 5d5a7d5..c977640 100644 --- a/source/SylphyHorn/Properties/Resources.Designer.cs +++ b/source/SylphyHorn/Properties/Resources.Designer.cs @@ -106,7 +106,7 @@ public static string Settings_Background_Desktop { } /// - /// Supported image formats: JPEG, PNG, BMP に類似しているローカライズされた文字列を検索します。 + /// Supported image formats: に類似しているローカライズされた文字列を検索します。 /// public static string Settings_Background_Note1 { get { @@ -123,6 +123,69 @@ public static string Settings_Background_Note2 { } } + /// + /// Choose a default position に類似しているローカライズされた文字列を検索します。 + /// + public static string Settings_Background_Position { + get { + return ResourceManager.GetString("Settings_Background_Position", resourceCulture); + } + } + + /// + /// Center に類似しているローカライズされた文字列を検索します。 + /// + public static string Settings_Background_Position_Center { + get { + return ResourceManager.GetString("Settings_Background_Position_Center", resourceCulture); + } + } + + /// + /// Fill に類似しているローカライズされた文字列を検索します。 + /// + public static string Settings_Background_Position_Fill { + get { + return ResourceManager.GetString("Settings_Background_Position_Fill", resourceCulture); + } + } + + /// + /// Fit に類似しているローカライズされた文字列を検索します。 + /// + public static string Settings_Background_Position_Fit { + get { + return ResourceManager.GetString("Settings_Background_Position_Fit", resourceCulture); + } + } + + /// + /// Span に類似しているローカライズされた文字列を検索します。 + /// + public static string Settings_Background_Position_Span { + get { + return ResourceManager.GetString("Settings_Background_Position_Span", resourceCulture); + } + } + + /// + /// Stretch に類似しているローカライズされた文字列を検索します。 + /// + public static string Settings_Background_Position_Stretch { + get { + return ResourceManager.GetString("Settings_Background_Position_Stretch", resourceCulture); + } + } + + /// + /// Tile に類似しているローカライズされた文字列を検索します。 + /// + public static string Settings_Background_Position_Tile { + get { + return ResourceManager.GetString("Settings_Background_Position_Tile", resourceCulture); + } + } + /// /// Select Background Images Folder に類似しているローカライズされた文字列を検索します。 /// diff --git a/source/SylphyHorn/Properties/Resources.ja.resx b/source/SylphyHorn/Properties/Resources.ja.resx index 2df6bfd..f86c586 100644 --- a/source/SylphyHorn/Properties/Resources.ja.resx +++ b/source/SylphyHorn/Properties/Resources.ja.resx @@ -133,11 +133,32 @@ デスクトップの背景 - サポートされている画像形式: JPG, PNG, BMP + サポートされている画像形式: メモ: 仮想デスクトップ番号をファイル名とした画像ファイルを用意してください (例: "1.png", "2.png", ...)。 + + 既定の配置方法を選ぶ + + + 中央に表示 + + + 画面のサイズに合わせる + + + ページ幅に合わせる + + + スパン + + + 拡大して表示 + + + 並べて表示 + 背景画像フォルダーを選択 diff --git a/source/SylphyHorn/Properties/Resources.resx b/source/SylphyHorn/Properties/Resources.resx index c1c5467..3e353d4 100644 --- a/source/SylphyHorn/Properties/Resources.resx +++ b/source/SylphyHorn/Properties/Resources.resx @@ -133,11 +133,32 @@ Desktop background - Supported image formats: JPEG, PNG, BMP + Supported image formats: Note: Please prepare image files with the virtual desktop number as filename (e.g. "1.png", "2.png", ...). + + Choose a default position + + + Center + + + Fill + + + Fit + + + Span + + + Stretch + + + Tile + Select Background Images Folder diff --git a/source/SylphyHorn/Services/ImageFormatSupportDetector.cs b/source/SylphyHorn/Services/ImageFormatSupportDetector.cs new file mode 100644 index 0000000..359ea1c --- /dev/null +++ b/source/SylphyHorn/Services/ImageFormatSupportDetector.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using WindowsDesktop.Interop; + +namespace SylphyHorn.Services +{ + public abstract class ImageFormatSupportDetector + { + private bool? _isSupported = null; + + public bool IsSupported + { + get + { + if (!this._isSupported.HasValue) + { + this._isSupported = this.GetValue(); + } + return this._isSupported.Value; + } + } + + public abstract string[] Extensions { get; } + + public abstract string FileType { get; } + + public abstract bool GetValue(); + } + + public abstract class ClsidImageFormatSupportDetector : ImageFormatSupportDetector + { + public abstract Guid CLSID { get; } + + public override bool GetValue() + { + try + { + var decoderType = Type.GetTypeFromCLSID(this.CLSID); + var decoder = Activator.CreateInstance(decoderType); + return true; + } + catch (COMException ex) when (ex.Match(HResult.REGDB_E_CLASSNOTREG)) + { + return false; + } + } + } +} diff --git a/source/SylphyHorn/Services/ImageFormatSupportDetectors.cs b/source/SylphyHorn/Services/ImageFormatSupportDetectors.cs new file mode 100644 index 0000000..923eeb8 --- /dev/null +++ b/source/SylphyHorn/Services/ImageFormatSupportDetectors.cs @@ -0,0 +1,163 @@ +using Microsoft.Win32; +using System; +using System.Collections.ObjectModel; +using System.Linq; + +namespace SylphyHorn.Services +{ + public sealed class JpegXrSupportDetector : ClsidImageFormatSupportDetector + { + public override string[] Extensions => new string[] { ".wdp", ".jxr" }; + + public override string FileType => "JPEG XR"; + + public override Guid CLSID => new Guid(0xa26cec36, 0x234c, 0x4950, 0xae, 0x16, 0xe3, 0x4a, 0xac, 0xe7, 0x1d, 0x0d); + } + + public sealed class WebPSupportDetector : ClsidImageFormatSupportDetector + { + public override string[] Extensions => new string[] { ".webp" }; + + public override string FileType => "WebP"; + + public override Guid CLSID => new Guid(0x7693e886, 0x51c9, 0x4070, 0x84, 0x19, 0x9f, 0x70, 0x73, 0x8e, 0xc8, 0xfa); + } + + public sealed class HEIFSupportDetector : ClsidImageFormatSupportDetector + { + private bool? _isHIFExtensionSupported; + + public bool IsHIFExtensionSupported + { + get + { + if (!this._isHIFExtensionSupported.HasValue) + { + if (Environment.OSVersion.Version.Build >= 21301) + { + this._isHIFExtensionSupported = true; + } + else if (Environment.OSVersion.Version.Build >= 19041) + { + const string targetHootfixId = "KB5003214"; + const string query = "SELECT HotFixID FROM Win32_QuickFixEngineering"; + + bool supported = false; + var searcher = new System.Management.ManagementObjectSearcher(query); + foreach (var hotfix in searcher.Get()) + { + if (hotfix["HotFixID"].ToString() == targetHootfixId) + { + supported = true; + break; + } + } + this._isHIFExtensionSupported = supported; + } + else + { + this._isHIFExtensionSupported = false; + } + } + return this._isHIFExtensionSupported.Value; + } + } + + private bool? _isHEVCSupported; + + public bool IsHEVCSupported + { + get + { + if (!this._isHEVCSupported.HasValue) + { + const string targetKeyName = "Microsoft.HEVCVideoExtension_"; + + this._isHEVCSupported = Registry.ClassesRoot + .OpenSubKey("ActivatableClasses") + .OpenSubKey("Package") + .GetSubKeyNames() + .Any(name => name.StartsWith(targetKeyName)); + } + return this._isHEVCSupported.Value; + } + } + + private bool? _isAV1Supported; + + public bool IsAV1Supported + { + get + { + if (!this._isAV1Supported.HasValue) + { + const string targetKeyName = "Microsoft.AV1VideoExtension_"; + + this._isAV1Supported = Registry.ClassesRoot + .OpenSubKey("ActivatableClasses") + .OpenSubKey("Package") + .GetSubKeyNames() + .Any(name => name.StartsWith(targetKeyName)); + } + return this._isAV1Supported.Value; + } + } + + private bool? _isAVIFSupported; + + public bool IsAVIFSupported + { + get + { + if (!this._isAVIFSupported.HasValue) + { + this._isAVIFSupported = Environment.OSVersion.Version.Build >= 18305 && this.IsAV1Supported; + } + return this._isAVIFSupported.Value; + } + } + + public override string[] Extensions + { + get + { + var extensions = new Collection(); + if (this.IsHIFExtensionSupported) + { + extensions.Add(".hif"); + } + extensions.Add(".heif"); + extensions.Add(".heifs"); + extensions.Add(".avci"); + extensions.Add(".avcs"); + if (this.IsHEVCSupported) + { + extensions.Add(".heic"); + extensions.Add(".heics"); + } + if (this.IsAVIFSupported) + { + extensions.Add(".avif"); + extensions.Add(".avifs"); + } + return extensions.ToArray(); + } + } + + public override string FileType + { + get + { + return this.IsAVIFSupported + ? this.IsHEVCSupported + ? "HEIF (AVCI, HEIC, AVIF)" + : "HEIF (AVCI, AVIF)" + : this.IsHEVCSupported + ? "HEIF (AVCI, HEIC)" + : "HEIF (AVCI)"; + } + } + + public override Guid CLSID => new Guid(0xe9a4a80a, 0x44fe, 0x4de4, 0x89, 0x71, 0x71, 0x50, 0xb1, 0x0a, 0x51, 0x99); + } +} diff --git a/source/SylphyHorn/Services/WallpaperService.cs b/source/SylphyHorn/Services/WallpaperService.cs index 0f0b17c..4d78a36 100644 --- a/source/SylphyHorn/Services/WallpaperService.cs +++ b/source/SylphyHorn/Services/WallpaperService.cs @@ -7,13 +7,32 @@ using System.Windows.Media; using SylphyHorn.Interop; using SylphyHorn.Serialization; +using SylphyHorn.UI.Bindings; using WindowsDesktop; namespace SylphyHorn.Services { public class WallpaperService : IDisposable { - private static readonly string[] _supportedExtensions = { ".png", ".jpg", ".jpeg", ".bmp", }; + private static readonly ImageFormatSupportDetector[] detectors = + { + new JpegXrSupportDetector(), + new WebPSupportDetector(), + new HEIFSupportDetector(), + }; + + private static readonly string[] _defaultSupportedExtensions = { ".bmp", ".dib", ".gif", ".png", ".tif", ".tiff", ".jpe", ".jpg", ".jpeg", ".jfif" }; + private static readonly string[] _supportedExtensions; + + private static readonly string[] _defaultSupportedFileTypes = { "BMP", "GIF", "PNG", "TIFF", "JPEG" }; + + public static string[] SupportedFileTypes { get; } + + static WallpaperService() + { + _supportedExtensions = _defaultSupportedExtensions.Concat(detectors.Where(d => d.IsSupported).SelectMany(d => d.Extensions)).ToArray(); + SupportedFileTypes = _defaultSupportedFileTypes.Concat(detectors.Where(d => d.IsSupported).Select(d => d.FileType)).ToArray(); + } public static WallpaperService Instance { get; } = new WallpaperService(); @@ -31,7 +50,7 @@ private void VirtualDesktopOnCurrentChanged(object sender, VirtualDesktopChanged var desktops = VirtualDesktop.GetDesktops(); var newIndex = Array.IndexOf(desktops, e.NewDesktop) + 1; - var wallpapers = this.GetWallpaperFiles(Settings.General.DesktopBackgroundFolderPath); + var wallpapers = this.GetWallpaperFiles(Settings.General.DesktopBackgroundFolderPath, (WallpaperPosition)Settings.General.Position.Value); var files = wallpapers.Where(x => x.DesktopIndex == newIndex).ToArray(); if (files.Length == 0) { @@ -42,7 +61,7 @@ private void VirtualDesktopOnCurrentChanged(object sender, VirtualDesktopChanged }); } - public WallpaperFile[] GetWallpaperFiles(string directoryPath) + public WallpaperFile[] GetWallpaperFiles(string directoryPath, WallpaperPosition defaultPosition) { try { @@ -52,9 +71,9 @@ public WallpaperFile[] GetWallpaperFiles(string directoryPath) var col = new Collection(); foreach (var file in directoryInfo.GetFiles()) { - if (_supportedExtensions.Any(x => x == file.Extension)) + if (_supportedExtensions.Any(x => string.Equals(x, file.Extension, StringComparison.OrdinalIgnoreCase))) { - var wallpaper = WallpaperFile.CreateFromFile(file); + var wallpaper = WallpaperFile.CreateFromFile(file, defaultPosition); col.Add(wallpaper); } } @@ -104,16 +123,6 @@ public static Tuple GetCurrentColorAndWallpaper() } } - public enum WallpaperPosition : byte - { - Center = 0, - Tile, - Stretch, - Fit, - Fill, - Span, - } - public class WallpaperFile { /// @@ -129,7 +138,9 @@ public class WallpaperFile public uint Number => (uint)(this.DesktopIndex << 16 | this.MonitorIndex); - public string DesktopMonitorText => this.MonitorIndex == 0 ? this.DesktopIndex.ToString() : $"{this.DesktopIndex}-{this.MonitorIndex}"; + public string DesktopMonitorText => (WallpaperPosition)Settings.General.Position.Value != this.Position + ? this.MonitorIndex == 0 ? $"{this.DesktopIndex} ({this.Position})" : $"{this.DesktopIndex}-{this.MonitorIndex} ({this.Position})" + : this.MonitorIndex == 0 ? this.DesktopIndex.ToString() : $"{this.DesktopIndex}-{this.MonitorIndex}"; private WallpaperFile(string path, ushort desktopIndex, ushort monitorIndex, WallpaperPosition position) { @@ -139,13 +150,13 @@ private WallpaperFile(string path, ushort desktopIndex, ushort monitorIndex, Wal this.Position = position; } - public static WallpaperFile CreateFromFile(FileInfo file) + public static WallpaperFile CreateFromFile(FileInfo file, WallpaperPosition defaultPosition) { var identifiers = Path.GetFileNameWithoutExtension(file.Name).Split('-'); ushort desktop = 0; ushort monitor = 0; - var position = WallpaperPosition.Fit; + var position = defaultPosition; if (identifiers.Length > 0 && ushort.TryParse(identifiers[0], out desktop)) { @@ -164,12 +175,12 @@ public static WallpaperFile CreateFromFile(FileInfo file) private static WallpaperPosition Parse(string options) { var options2 = options.ToLower(); + if (options2.StartsWith("fil")) return WallpaperPosition.Fill; + if (options2.StartsWith("sp")) return WallpaperPosition.Span; if (options2[0] == 'c') return WallpaperPosition.Center; if (options2[0] == 't') return WallpaperPosition.Tile; if (options2[0] == 's') return WallpaperPosition.Stretch; if (options2[0] == 'f') return WallpaperPosition.Fit; - if (options2.StartsWith("fil")) return WallpaperPosition.Fill; - if (options2.StartsWith("sp")) return WallpaperPosition.Span; return WallpaperPosition.Fit; } } diff --git a/source/SylphyHorn/SylphyHorn.csproj b/source/SylphyHorn/SylphyHorn.csproj index a2a7cce..32b22d4 100644 --- a/source/SylphyHorn/SylphyHorn.csproj +++ b/source/SylphyHorn/SylphyHorn.csproj @@ -245,8 +245,11 @@ True Resources.resx + + + @@ -254,6 +257,7 @@ + diff --git a/source/SylphyHorn/UI/Bindings/SettingsWindowViewModel.cs b/source/SylphyHorn/UI/Bindings/SettingsWindowViewModel.cs index fdc00c2..347ea17 100644 --- a/source/SylphyHorn/UI/Bindings/SettingsWindowViewModel.cs +++ b/source/SylphyHorn/UI/Bindings/SettingsWindowViewModel.cs @@ -25,6 +25,8 @@ public class SettingsWindowViewModel : WindowViewModel public IReadOnlyCollection> Cultures { get; } + public IReadOnlyCollection> Positions { get; } + public IReadOnlyCollection> Placements { get; } public bool IsDisplayEnabled { get; } @@ -83,6 +85,24 @@ public string Culture #endregion + #region Position notification property + + public WallpaperPosition Position + { + get => (WallpaperPosition)Settings.General.Position.Value; + set + { + if ((WallpaperPosition)Settings.General.Position.Value != value) + { + Settings.General.Position.Value = (byte)value; + + this.RaisePropertyChanged(); + } + } + } + + #endregion + #region Placement notification property public WindowPlacement Placement @@ -206,6 +226,16 @@ public SettingsWindowViewModel(HookService hookService) .OrderBy(x => x.Display)) .ToList(); + this.Positions = new[] + { + new DisplayViewModel { Display = Resources.Settings_Background_Position_Fill, Value = WallpaperPosition.Fill }, + new DisplayViewModel { Display = Resources.Settings_Background_Position_Fit, Value = WallpaperPosition.Fit }, + new DisplayViewModel { Display = Resources.Settings_Background_Position_Stretch, Value = WallpaperPosition.Stretch }, + new DisplayViewModel { Display = Resources.Settings_Background_Position_Tile, Value = WallpaperPosition.Tile }, + new DisplayViewModel { Display = Resources.Settings_Background_Position_Center, Value = WallpaperPosition.Center }, + new DisplayViewModel { Display = Resources.Settings_Background_Position_Span, Value = WallpaperPosition.Span }, + }; + this.Placements = new[] { new DisplayViewModel { Display = Resources.Settings_NotificationWindowPlacement_TopLeft, Value = WindowPlacement.TopLeft, }, @@ -240,7 +270,7 @@ public SettingsWindowViewModel(HookService hookService) this._HasStartupLink = this._startup.IsExists; Settings.General.DesktopBackgroundFolderPath - .Subscribe(path => this.Backgrounds = WallpaperService.Instance.GetWallpaperFiles(path)) + .Subscribe(path => this.Backgrounds = WallpaperService.Instance.GetWallpaperFiles(path, (WallpaperPosition)Settings.General.Position.Value)) .AddTo(this); var colAndWall = WallpaperService.GetCurrentColorAndWallpaper(); diff --git a/source/SylphyHorn/UI/Bindings/WallpaperPosition.cs b/source/SylphyHorn/UI/Bindings/WallpaperPosition.cs new file mode 100644 index 0000000..aefed73 --- /dev/null +++ b/source/SylphyHorn/UI/Bindings/WallpaperPosition.cs @@ -0,0 +1,12 @@ +namespace SylphyHorn.UI.Bindings +{ + public enum WallpaperPosition : byte + { + Center = 0, + Tile, + Stretch, + Fit, + Fill, + Span, + } +} diff --git a/source/SylphyHorn/UI/Controls/ArrayToStringConverter.cs b/source/SylphyHorn/UI/Controls/ArrayToStringConverter.cs new file mode 100644 index 0000000..ace631e --- /dev/null +++ b/source/SylphyHorn/UI/Controls/ArrayToStringConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +namespace SylphyHorn.UI.Converters +{ + public class ArrayToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return string.Join(parameter as string ?? ", ", value as object[]); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/source/SylphyHorn/UI/Controls/UnlockImageConverter.cs b/source/SylphyHorn/UI/Controls/UnlockImageConverter.cs index 8551f01..de3d611 100644 --- a/source/SylphyHorn/UI/Controls/UnlockImageConverter.cs +++ b/source/SylphyHorn/UI/Controls/UnlockImageConverter.cs @@ -19,7 +19,10 @@ public object Convert(object value, Type targetType, object parameter, CultureIn { if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) return null; - using (var stream = new FileStream((string)value, FileMode.Open)) + var filename = (string)value; + if (string.IsNullOrEmpty(filename)) return null; + + using (var stream = new FileStream(filename, FileMode.Open)) { var decoder = BitmapDecoder.Create( stream, diff --git a/source/SylphyHorn/UI/SettingsWindow.xaml b/source/SylphyHorn/UI/SettingsWindow.xaml index 036c252..f0f91f5 100644 --- a/source/SylphyHorn/UI/SettingsWindow.xaml +++ b/source/SylphyHorn/UI/SettingsWindow.xaml @@ -103,6 +103,24 @@ +