Files
PowerToys/src/settings-ui/Settings.UI/ViewModels/PowerRenameViewModel.cs
moooyo 72fc8288eb Add HEIF/AVIF EXIF metadata extraction and UI support (#44466)
- Support EXIF extraction from HEIF/HEIC and AVIF images by detecting
container format and using correct WIC metadata paths.
- Extend supported file extensions to include .heic, .heif, and .avif.
- Add unit tests and test data for HEIF/AVIF extraction, with graceful
handling if required Windows Store extensions are missing.
- Update PowerRename settings UI to show HEIF/AVIF extension status and
provide install buttons.
- Extend ViewModel to detect/install required extensions and expose
status for binding.

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #43758
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-01-08 11:18:47 +08:00

315 lines
10 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Input;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public partial class PowerRenameViewModel : Observable
{
private GeneralSettings GeneralSettingsConfig { get; set; }
private readonly SettingsUtils _settingsUtils;
private const string ModuleName = PowerRenameSettings.ModuleName;
private string _settingsConfigFileFolder = string.Empty;
private PowerRenameSettings Settings { get; set; }
private Func<string, int> SendConfigMSG { get; }
public PowerRenameViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, string configFileSubfolder = "")
{
// Update Settings file folder:
_settingsConfigFileFolder = configFileSubfolder;
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
ArgumentNullException.ThrowIfNull(settingsRepository);
GeneralSettingsConfig = settingsRepository.SettingsConfig;
try
{
PowerRenameLocalProperties localSettings = _settingsUtils.GetSettingsOrDefault<PowerRenameLocalProperties>(GetSettingsSubPath(), "power-rename-settings.json");
Settings = new PowerRenameSettings(localSettings);
}
catch (Exception e)
{
Logger.LogError($"Exception encountered while reading {ModuleName} settings.", e);
#if DEBUG
if (e is ArgumentException || e is ArgumentNullException || e is PathTooLongException)
{
throw;
}
#endif
PowerRenameLocalProperties localSettings = new PowerRenameLocalProperties();
Settings = new PowerRenameSettings(localSettings);
_settingsUtils.SaveSettings(localSettings.ToJsonString(), GetSettingsSubPath(), "power-rename-settings.json");
}
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
_powerRenameEnabledOnContextMenu = Settings.Properties.ShowIcon.Value;
_powerRenameEnabledOnContextExtendedMenu = Settings.Properties.ExtendedContextMenuOnly.Value;
_powerRenameRestoreFlagsOnLaunch = Settings.Properties.PersistState.Value;
_powerRenameMaxDispListNumValue = Settings.Properties.MaxMRUSize.Value;
_autoComplete = Settings.Properties.MRUEnabled.Value;
_powerRenameUseBoostLib = Settings.Properties.UseBoostLib.Value;
// Initialize extension helpers
HeifExtension = new StoreExtensionHelper(
"Microsoft.HEIFImageExtension_8wekyb3d8bbwe",
"ms-windows-store://pdp/?ProductId=9PMMSR1CGPWG",
"HEIF");
AvifExtension = new StoreExtensionHelper(
"Microsoft.AV1VideoExtension_8wekyb3d8bbwe",
"ms-windows-store://pdp/?ProductId=9MVZQVXJBQ9V",
"AV1");
InitializeEnabledValue();
}
private void InitializeEnabledValue()
{
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredPowerRenameEnabledValue();
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
{
// Get the enabled state from GPO.
_enabledStateIsGPOConfigured = true;
_powerRenameEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
}
else
{
_powerRenameEnabled = GeneralSettingsConfig.Enabled.PowerRename;
}
}
private GpoRuleConfigured _enabledGpoRuleConfiguration;
private bool _enabledStateIsGPOConfigured;
private bool _powerRenameEnabled;
private bool _powerRenameEnabledOnContextMenu;
private bool _powerRenameEnabledOnContextExtendedMenu;
private bool _powerRenameRestoreFlagsOnLaunch;
private int _powerRenameMaxDispListNumValue;
private bool _autoComplete;
private bool _powerRenameUseBoostLib;
public bool IsEnabled
{
get
{
return _powerRenameEnabled;
}
set
{
if (_enabledStateIsGPOConfigured)
{
// If it's GPO configured, shouldn't be able to change this state.
return;
}
if (value != _powerRenameEnabled)
{
GeneralSettingsConfig.Enabled.PowerRename = value;
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(snd.ToString());
_powerRenameEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
RaisePropertyChanged(nameof(GlobalAndMruEnabled));
}
}
}
public bool IsEnabledGpoConfigured
{
get => _enabledStateIsGPOConfigured;
}
public bool MRUEnabled
{
get
{
return _autoComplete;
}
set
{
if (value != _autoComplete)
{
_autoComplete = value;
Settings.Properties.MRUEnabled.Value = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(GlobalAndMruEnabled));
}
}
}
public bool GlobalAndMruEnabled
{
get
{
return _autoComplete && _powerRenameEnabled;
}
}
public bool EnabledOnContextMenu
{
get
{
return _powerRenameEnabledOnContextMenu;
}
set
{
if (value != _powerRenameEnabledOnContextMenu)
{
_powerRenameEnabledOnContextMenu = value;
Settings.Properties.ShowIcon.Value = value;
RaisePropertyChanged();
}
}
}
public bool EnabledOnContextExtendedMenu
{
get
{
return _powerRenameEnabledOnContextExtendedMenu;
}
set
{
if (value != _powerRenameEnabledOnContextExtendedMenu)
{
_powerRenameEnabledOnContextExtendedMenu = value;
Settings.Properties.ExtendedContextMenuOnly.Value = value;
RaisePropertyChanged();
}
}
}
public bool RestoreFlagsOnLaunch
{
get
{
return _powerRenameRestoreFlagsOnLaunch;
}
set
{
if (value != _powerRenameRestoreFlagsOnLaunch)
{
_powerRenameRestoreFlagsOnLaunch = value;
Settings.Properties.PersistState.Value = value;
RaisePropertyChanged();
}
}
}
public int MaxDispListNum
{
get
{
return _powerRenameMaxDispListNumValue;
}
set
{
if (value != _powerRenameMaxDispListNumValue)
{
_powerRenameMaxDispListNumValue = value;
Settings.Properties.MaxMRUSize.Value = value;
RaisePropertyChanged();
}
}
}
public bool UseBoostLib
{
get
{
return _powerRenameUseBoostLib;
}
set
{
if (value != _powerRenameUseBoostLib)
{
_powerRenameUseBoostLib = value;
Settings.Properties.UseBoostLib.Value = value;
RaisePropertyChanged();
}
}
}
public string GetSettingsSubPath()
{
return _settingsConfigFileFolder + "\\" + ModuleName;
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
// Notify UI of property change
OnPropertyChanged(propertyName);
if (SendConfigMSG != null)
{
SndPowerRenameSettings snd = new SndPowerRenameSettings(Settings);
SndModuleSettings<SndPowerRenameSettings> ipcMessage = new SndModuleSettings<SndPowerRenameSettings>(snd);
SendConfigMSG(ipcMessage.ToJsonString());
}
}
public void RefreshEnabledState()
{
InitializeEnabledValue();
OnPropertyChanged(nameof(IsEnabled));
OnPropertyChanged(nameof(GlobalAndMruEnabled));
}
// Store extension helpers
public StoreExtensionHelper HeifExtension { get; private set; }
public StoreExtensionHelper AvifExtension { get; private set; }
// Convenience properties for XAML binding
public bool IsHeifExtensionInstalled => HeifExtension.IsInstalled;
public bool IsAvifExtensionInstalled => AvifExtension.IsInstalled;
public ICommand InstallHeifExtensionCommand => HeifExtension.InstallCommand;
public ICommand InstallAvifExtensionCommand => AvifExtension.InstallCommand;
public void RefreshHeifExtensionStatus()
{
HeifExtension.RefreshStatus();
OnPropertyChanged(nameof(IsHeifExtensionInstalled));
}
public void RefreshAvifExtensionStatus()
{
AvifExtension.RefreshStatus();
OnPropertyChanged(nameof(IsAvifExtensionInstalled));
}
}
}