Compare commits

..

11 Commits

Author SHA1 Message Date
vanzue
b95d4c5035 do not load dll at all 2025-11-14 15:47:35 +08:00
vanzue
4e6496ce2e Hide cursor wrap 2025-11-14 14:42:13 +08:00
Niels Laute
8f87058508 [CursorWRap] Revert the shortcut removal (#43537)
See title
2025-11-13 22:40:08 +08:00
Niels Laute
755c138723 [Advanced Paste] Localization (#43536)
More strings to loc, and re-ordering a few settings.
2025-11-13 22:39:56 +08:00
Kai Tao
8b066cea2e Advanced paste: Tweak Foundry Local Displayed Model and start server if server is turned on when using AP (#43529)
<!-- 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
1. Foundry local model name should not prefixed by fl://
2. If foundry service is shutdown, we should not just fail it, we should
start it then call FL to make availability better.

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

- [ ] Closes: #xxx
<!-- - [ ] 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
Verified locally:
1. Manually disable foundry local service, then run AP with foundry
local, it can return result instead of direct failure.
2. 
<img width="659" height="294" alt="image"
src="https://github.com/user-attachments/assets/113da451-7131-4ce7-ae82-0ccf772ad8aa"
/>
<img width="988" height="192" alt="image"
src="https://github.com/user-attachments/assets/aa3650ba-668a-40c4-ad8a-303e09000dd4"
/>
![Uploading image.png…]()
2025-11-13 20:06:48 +08:00
Shawn Yuan
2d92ccdf3b Fix advanced paste migration issue (#43524)
<!-- 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
This pull request refactors how legacy AI enablement settings are
migrated and simplifies the handling of legacy properties in the
settings UI. The main improvements are in the migration logic and the
removal of legacy extension data handling.


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

- [ ] Closes: #xxx
- [ ] **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

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
2025-11-13 20:06:36 +08:00
leileizhang
83ea0c2f28 Refine AdvancedPaste Terms UI and add default system prompt for Advanced AI (#43526)
<!-- 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
1. when no terms and Privacy links don't show the infobar
<img width="349" height="127" alt="image"
src="https://github.com/user-attachments/assets/b7eeeabf-365f-45f6-adb4-56335c14e8ad"
/>
<img width="410" height="217" alt="image"
src="https://github.com/user-attachments/assets/15e053c4-738d-4bb4-9544-24bdf8a5a584"
/>


2. add add default system prompt for Advanced AI

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

- [ ] Closes: #xxx
- [ ] **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
2025-11-13 20:06:25 +08:00
Jiří Polášek
cb81a99c5f CmdPal: Change captitalization of "Last position" item (#43518)
## Summary of the Pull Request

This PR updates the capitalization of the drop-down item from "Last
Position" to "Last position" to align with the guidelines.

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

- [ ] Closes: #xxx
- [ ] **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
2025-11-13 20:06:05 +08:00
vanzue
48a3f4fa87 Merge remote-tracking branch 'origin/main' into stable 2025-11-13 12:52:38 +08:00
Kai Tao
6c05e44680 Merge remote-tracking branch 'origin/main' into stable 2025-11-13 10:39:32 +08:00
Kai Tao
6505cd7a63 Revert "Hybrid CRT for powertys (#42073)"
This reverts commit c71fdca277.
2025-11-12 10:09:52 +08:00
35 changed files with 446 additions and 361 deletions

View File

@@ -5,7 +5,6 @@
## PR Checklist
- [ ] Closes: #xxx
<!-- - [ ] 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

View File

@@ -40,7 +40,7 @@ jobs:
echo powerToysInstallerArm64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url') >> $GITHUB_OUTPUT
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v5
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "deps/expected-lite"]
path = deps/expected-lite
url = https://github.com/martinmoene/expected-lite.git
[submodule "deps/cziplib"]
path = deps/cziplib
url = https://github.com/kuba--/zip.git

View File

@@ -26,7 +26,6 @@
<PropertyGroup Condition="'$(SkipCppCodeAnalysis)' == ''">
<RunCodeAnalysis>true</RunCodeAnalysis>
<CodeAnalysisRuleSet>$(MsbuildThisFileDirectory)\CppRuleSet.ruleset</CodeAnalysisRuleSet>
<CAExcludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(CAExcludePath)</CAExcludePath>
</PropertyGroup>
<!-- C++ source compile-specific things for all configurations -->
@@ -35,7 +34,7 @@
<PreferredToolArchitecture Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' or '$(PROCESSOR_ARCHITEW6432)' == 'ARM64'">arm64</PreferredToolArchitecture>
<VcpkgEnabled>false</VcpkgEnabled>
<ReplaceWildcardsInProjectItems>true</ReplaceWildcardsInProjectItems>
<ExternalIncludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(MSBuildThisFileFullPath)\..\deps\;$(MSBuildThisFileFullPath)\..\packages\;$(ExternalIncludePath)</ExternalIncludePath>
<!-- Enable control flow guard for C++ projects that don't consume any C++ files -->
<!-- This covers the case where a .dll exports a .lib, but doesn't have any ClCompile entries. -->
<LinkControlFlowGuard>Guard</LinkControlFlowGuard>

1
deps/cziplib vendored Submodule

Submodule deps/cziplib added at 81314fff0a

View File

@@ -4,7 +4,6 @@
#include <ProjectTelemetry.h>
#include <spdlog/sinks/base_sink.h>
#include <filesystem>
#include <string_view>
#include "../../src/common/logger/logger.h"
#include "../../src/common/utils/gpo.h"
@@ -857,69 +856,14 @@ UINT __stdcall UnsetAdvancedPasteAPIKeyCA(MSIHANDLE hInstall)
try
{
winrt::Windows::Security::Credentials::PasswordVault vault;
winrt::Windows::Security::Credentials::PasswordCredential cred;
hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey");
ExitOnFailure(hr, "Failed to initialize");
winrt::Windows::Security::Credentials::PasswordVault vault;
auto hasPrefix = [](std::wstring_view value, wchar_t const* prefix) {
std::wstring_view prefixView{ prefix };
return value.compare(0, prefixView.size(), prefixView) == 0;
};
const wchar_t* resourcePrefixes[] = {
L"https://platform.openai.com/api-keys",
L"https://azure.microsoft.com/products/ai-services/openai-service",
L"https://azure.microsoft.com/products/ai-services/ai-inference",
L"https://console.mistral.ai/account/api-keys",
L"https://ai.google.dev/",
};
const wchar_t* usernamePrefixes[] = {
L"PowerToys_AdvancedPaste_",
};
auto credentials = vault.RetrieveAll();
for (auto const& credential : credentials)
{
bool shouldRemove = false;
std::wstring resource{ credential.Resource() };
for (auto const prefix : resourcePrefixes)
{
if (hasPrefix(resource, prefix))
{
shouldRemove = true;
break;
}
}
if (!shouldRemove)
{
std::wstring username{ credential.UserName() };
for (auto const prefix : usernamePrefixes)
{
if (hasPrefix(username, prefix))
{
shouldRemove = true;
break;
}
}
}
if (!shouldRemove)
{
continue;
}
try
{
vault.Remove(credential);
}
catch (...)
{
}
}
cred = vault.Retrieve(L"https://platform.openai.com/api-keys", L"PowerToys_AdvancedPaste_OpenAIKey");
vault.Remove(cred);
}
catch (...)
{

View File

@@ -168,15 +168,6 @@ namespace AdvancedPaste.Settings
}
var properties = settings.Properties;
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
if (legacyCredential is null)
{
return legacyAdvancedAIConsumed;
}
var configuration = properties.PasteAIConfiguration;
if (configuration is null)
@@ -185,11 +176,30 @@ namespace AdvancedPaste.Settings
properties.PasteAIConfiguration = configuration;
}
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
{
return false;
}
bool configurationUpdated = false;
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
configurationUpdated |= ensureResult.Updated;
if (hasLegacyProviders)
{
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
}
PasteAIProviderDefinition openAIProvider = null;
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
{
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
openAIProvider = ensureResult.Provider;
configurationUpdated |= ensureResult.Updated;
}
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
{
@@ -197,13 +207,13 @@ namespace AdvancedPaste.Settings
configurationUpdated = true;
}
if (openAIProvider is not null)
if (legacyCredential is not null && openAIProvider is not null)
{
StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
RemoveLegacyOpenAICredential();
}
const bool shouldEnableAI = true;
bool shouldEnableAI = legacyCredential is not null;
bool enabledUpdated = false;
if (properties.IsAIEnabled != shouldEnableAI)
{

View File

@@ -16,6 +16,7 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.CmdPal.UI.ViewModels.MainPage;
@@ -37,10 +38,8 @@ public partial class MainListPage : DynamicListPage,
"com.microsoft.cmdpal.builtin.datetime",
];
private readonly IServiceProvider _serviceProvider;
private readonly TopLevelCommandManager _tlcManager;
private readonly SettingsModel _settings;
private readonly AliasManager _aliasManager;
private readonly AppStateModel _appState;
private List<Scored<IListItem>>? _filteredItems;
private List<Scored<IListItem>>? _filteredApps;
private List<Scored<IListItem>>? _fallbackItems;
@@ -54,18 +53,17 @@ public partial class MainListPage : DynamicListPage,
private CancellationTokenSource? _cancellationTokenSource;
public MainListPage(TopLevelCommandManager topLevelCommandManager, SettingsModel settingsModel, AliasManager aliasManager, AppStateModel appStateModel)
public MainListPage(IServiceProvider serviceProvider)
{
Title = Resources.builtin_home_name;
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
_serviceProvider = serviceProvider;
_tlcManager = topLevelCommandManager;
_tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
_tlcManager.PropertyChanged += TlcManager_PropertyChanged;
_tlcManager.TopLevelCommands.CollectionChanged += Commands_CollectionChanged;
_appState = appStateModel;
// The all apps page will kick off a BG thread to start loading apps.
// We just want to know when it is done.
var allApps = AllAppsCommandProvider.Page;
@@ -80,13 +78,12 @@ public partial class MainListPage : DynamicListPage,
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateFallbackItemsMessage>(this);
_settings = settingsModel;
_settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(_settings);
var settings = _serviceProvider.GetService<SettingsModel>()!;
settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(settings);
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
IsLoading = true;
_aliasManager = aliasManager;
}
private void TlcManager_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -222,12 +219,14 @@ public partial class MainListPage : DynamicListPage,
// Handle changes to the filter text here
if (!string.IsNullOrEmpty(SearchText))
{
var aliases = _serviceProvider.GetService<AliasManager>()!;
if (token.IsCancellationRequested)
{
return;
}
if (_aliasManager.CheckAlias(newSearch))
if (aliases.CheckAlias(newSearch))
{
if (_filteredItemsIncludesApps != _includeApps)
{
@@ -389,7 +388,7 @@ public partial class MainListPage : DynamicListPage,
}
}
var history = _appState.RecentCommands!;
var history = _serviceProvider.GetService<AppStateModel>()!.RecentCommands!;
Func<string, IListItem, int> scoreItem = (a, b) => { return ScoreTopLevelItem(a, b, history); };
// Produce a list of everything that matches the current filter.
@@ -484,8 +483,9 @@ public partial class MainListPage : DynamicListPage,
private bool ActuallyLoading()
{
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
var allApps = AllAppsCommandProvider.Page;
return allApps.IsLoading || _tlcManager.IsLoading;
return allApps.IsLoading || tlcManager.IsLoading;
}
// Almost verbatim ListHelpers.ScoreListItem, but also accounting for the
@@ -580,9 +580,10 @@ public partial class MainListPage : DynamicListPage,
public void UpdateHistory(IListItem topLevelOrAppItem)
{
var id = IdForTopLevelOrAppItem(topLevelOrAppItem);
var history = _appState.RecentCommands;
var state = _serviceProvider.GetService<AppStateModel>()!;
var history = state.RecentCommands;
history.AddHistoryItem(id);
AppStateModel.SaveState(_appState);
AppStateModel.SaveState(state);
}
private static string IdForTopLevelOrAppItem(IListItem topLevelOrAppItem)
@@ -614,9 +615,10 @@ public partial class MainListPage : DynamicListPage,
_tlcManager.PropertyChanged -= TlcManager_PropertyChanged;
_tlcManager.TopLevelCommands.CollectionChanged -= Commands_CollectionChanged;
if (_settings is not null)
var settings = _serviceProvider.GetService<SettingsModel>();
if (settings is not null)
{
_settings.SettingsChanged -= SettingsChangedHandler;
settings.SettingsChanged -= SettingsChangedHandler;
}
WeakReferenceMessenger.Default.UnregisterAll(this);

View File

@@ -9,14 +9,16 @@ using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ProviderSettingsViewModel(
CommandProviderWrapper _provider,
ProviderSettings _providerSettings,
SettingsModel _settings) : ObservableObject
IServiceProvider _serviceProvider) : ObservableObject
{
private readonly SettingsModel _settings = _serviceProvider.GetService<SettingsModel>()!;
private readonly Lock _initializeSettingsLock = new();
private Task? _initializeSettingsTask;

View File

@@ -5,13 +5,14 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class SettingsViewModel : INotifyPropertyChanged
{
private readonly SettingsModel _settings;
private readonly TopLevelCommandManager _tlcManager;
private readonly IServiceProvider _serviceProvider;
public event PropertyChangedEventHandler? PropertyChanged;
@@ -141,10 +142,10 @@ public partial class SettingsViewModel : INotifyPropertyChanged
public SettingsExtensionsViewModel Extensions { get; }
public SettingsViewModel(SettingsModel settings, TaskScheduler scheduler, TopLevelCommandManager topLevelCommandManager)
public SettingsViewModel(SettingsModel settings, IServiceProvider serviceProvider, TaskScheduler scheduler)
{
_settings = settings;
_tlcManager = topLevelCommandManager;
_serviceProvider = serviceProvider;
var activeProviders = GetCommandProviders();
var allProviderSettings = _settings.ProviderSettings;
@@ -153,7 +154,7 @@ public partial class SettingsViewModel : INotifyPropertyChanged
{
var providerSettings = settings.GetProviderSettings(item);
var settingsModel = new ProviderSettingsViewModel(item, providerSettings, _settings);
var settingsModel = new ProviderSettingsViewModel(item, providerSettings, _serviceProvider);
CommandProviders.Add(settingsModel);
}
@@ -162,7 +163,8 @@ public partial class SettingsViewModel : INotifyPropertyChanged
private IEnumerable<CommandProviderWrapper> GetCommandProviders()
{
var allProviders = _tlcManager.CommandProviders;
var manager = _serviceProvider.GetService<TopLevelCommandManager>()!;
var allProviders = manager.CommandProviders;
return allProviders;
}

View File

@@ -34,10 +34,10 @@ public partial class TopLevelCommandManager : ObservableObject,
TaskScheduler IPageContext.Scheduler => _taskScheduler;
public TopLevelCommandManager(IServiceProvider serviceProvider, TaskScheduler taskScheduler)
public TopLevelCommandManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_taskScheduler = taskScheduler;
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
}

View File

@@ -23,8 +23,6 @@ using Microsoft.CmdPal.Ext.WindowsTerminal;
using Microsoft.CmdPal.Ext.WindowWalker;
using Microsoft.CmdPal.Ext.WinGet;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Pages;
using Microsoft.CmdPal.UI.Settings;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
using Microsoft.CmdPal.UI.ViewModels.Models;
@@ -53,7 +51,10 @@ public partial class App : Application
public ETWTrace EtwTrace { get; private set; } = new ETWTrace();
private readonly ServiceProvider _services;
/// <summary>
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
/// </summary>
public IServiceProvider Services { get; }
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
@@ -66,7 +67,7 @@ public partial class App : Application
_globalErrorHandler.Register(this);
#endif
_services = ConfigureServices();
Services = ConfigureServices();
this.InitializeComponent();
@@ -93,7 +94,7 @@ public partial class App : Application
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
AppWindow = _services.GetRequiredService<MainWindow>();
AppWindow = new MainWindow();
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
((MainWindow)AppWindow).HandleLaunchNonUI(activatedEventArgs);
@@ -109,11 +110,6 @@ public partial class App : Application
// Root services
services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext());
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
// TODO: Register ILogger
// Built-in Commands. Order matters - this is the order they'll be presented by default.
var allApps = new AllAppsCommandProvider();
@@ -156,35 +152,25 @@ public partial class App : Application
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
// Settings & state
// Models
services.AddSingleton<TopLevelCommandManager>();
services.AddSingleton<AliasManager>();
services.AddSingleton<HotkeyManager>();
var sm = SettingsModel.LoadSettings();
services.AddSingleton(sm);
var state = AppStateModel.LoadState();
services.AddSingleton(state);
// Services
services.AddSingleton<TopLevelCommandManager>();
services.AddSingleton<AliasManager>();
services.AddSingleton<HotkeyManager>();
services.AddSingleton<IExtensionService, ExtensionService>();
services.AddSingleton<TrayIconService>();
services.AddSingleton<IRunHistoryService, RunHistoryService>();
// ViewModels
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
services.AddSingleton<SettingsViewModel>();
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
// ViewModels
services.AddSingleton<ShellViewModel>();
// Views
services.AddSingleton<MainWindow>();
services.AddSingleton<ShellPage>();
services.AddTransient<ListPage>();
// Settings Pages
services.AddTransient<GeneralPage>();
services.AddTransient<ExtensionPage>();
services.AddTransient<ExtensionsPage>();
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
return services.BuildServiceProvider();
}

View File

@@ -11,6 +11,7 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
@@ -30,7 +31,6 @@ public sealed partial class ListPage : Page,
IRecipient<ActivateSelectedListItemMessage>,
IRecipient<ActivateSecondaryCommandMessage>
{
private readonly SettingsModel _settings;
private InputSource _lastInputSource;
internal ListViewModel? ViewModel
@@ -51,11 +51,9 @@ public sealed partial class ListPage : Page,
}
}
public ListPage(SettingsModel settings)
public ListPage()
{
this.InitializeComponent();
_settings = settings;
this.NavigationCacheMode = NavigationCacheMode.Disabled;
this.ItemView.Loaded += Items_Loaded;
this.ItemView.PreviewKeyDown += Items_PreviewKeyDown;
@@ -135,7 +133,8 @@ public sealed partial class ListPage : Page,
return;
}
if (_settings.SingleClickActivates)
var settings = App.Current.Services.GetService<SettingsModel>()!;
if (settings.SingleClickActivates)
{
ViewModel?.InvokeItemCommand.Execute(item);
}
@@ -151,7 +150,8 @@ public sealed partial class ListPage : Page,
{
if (ItemView.SelectedItem is ListItemViewModel vm)
{
if (!_settings.SingleClickActivates)
var settings = App.Current.Services.GetService<SettingsModel>()!;
if (!settings.SingleClickActivates)
{
ViewModel?.InvokeItemCommand.Execute(vm);
}

View File

@@ -20,7 +20,7 @@ namespace Microsoft.CmdPal.UI.Helpers;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
public sealed partial class TrayIconService
internal sealed partial class TrayIconService
{
private const uint MY_NOTIFY_ID = 1000;
private const uint WM_TRAY_ICON = PInvoke.WM_USER + 1;

View File

@@ -13,6 +13,6 @@
MinHeight="240"
Activated="MainWindow_Activated"
Closed="MainWindow_Closed"
x:Name="Window"
mc:Ignorable="d">
<pages:ShellPage x:Name="RootShellPage" />
</winuiex:WindowEx>

View File

@@ -14,9 +14,9 @@ using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Pages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
@@ -58,10 +58,6 @@ public sealed partial class MainWindow : WindowEx,
private readonly KeyboardListener _keyboardListener;
private readonly LocalKeyboardListener _localKeyboardListener;
private readonly HiddenOwnerWindowBehavior _hiddenOwnerBehavior = new();
private readonly SettingsModel _settings;
private readonly TrayIconService _trayIconService;
private readonly IExtensionService _extensionService;
private readonly ShellPage _rootShellPage;
private bool _ignoreHotKeyWhenFullScreen = true;
private DesktopAcrylicController? _acrylicController;
@@ -69,17 +65,10 @@ public sealed partial class MainWindow : WindowEx,
private WindowPosition _currentWindowPosition = new();
public MainWindow(SettingsModel settingsModel, TrayIconService trayIconService, IExtensionService extensionService, ShellPage shellPage)
public MainWindow()
{
InitializeComponent();
_settings = settingsModel;
_trayIconService = trayIconService;
_extensionService = extensionService;
_rootShellPage = shellPage;
Window.WindowContent = _rootShellPage;
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
unsafe
@@ -113,7 +102,7 @@ public sealed partial class MainWindow : WindowEx,
ExtendsContentIntoTitleBar = true;
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
SizeChanged += WindowSizeChanged;
_rootShellPage.Loaded += RootShellPage_Loaded;
RootShellPage.Loaded += RootShellPage_Loaded;
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated");
@@ -127,10 +116,10 @@ public sealed partial class MainWindow : WindowEx,
// Load our settings, and then also wire up a settings changed handler
HotReloadSettings();
_settings.SettingsChanged += SettingsChangedHandler;
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler;
// Make sure that we update the acrylic theme when the OS theme changes
_rootShellPage.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
RootShellPage.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
// Hardcoding event name to avoid bringing in the PowerToys.interop dependency. Event name must match CMDPAL_SHOW_EVENT from shared_constants.h
NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () =>
@@ -171,7 +160,8 @@ public sealed partial class MainWindow : WindowEx,
private void RestoreWindowPosition()
{
if (_settings.LastWindowPosition is not WindowPosition savedPosition)
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.LastWindowPosition is not WindowPosition savedPosition)
{
PositionCentered();
return;
@@ -228,10 +218,12 @@ public sealed partial class MainWindow : WindowEx,
private void HotReloadSettings()
{
SetupHotkey(_settings);
_trayIconService.SetupTrayIcon(_settings.ShowSystemTrayIcon);
var settings = App.Current.Services.GetService<SettingsModel>()!;
_ignoreHotKeyWhenFullScreen = _settings.IgnoreShortcutWhenFullscreen;
SetupHotkey(settings);
App.Current.Services.GetService<TrayIconService>()!.SetupTrayIcon(settings.ShowSystemTrayIcon);
_ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen;
}
// We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
@@ -386,7 +378,9 @@ public sealed partial class MainWindow : WindowEx,
public void Receive(ShowWindowMessage message)
{
ShowHwnd(message.Hwnd, _settings.SummonOn);
var settings = App.Current.Services.GetService<SettingsModel>()!;
ShowHwnd(message.Hwnd, settings.SummonOn);
}
public void Receive(HideWindowMessage message)
@@ -473,11 +467,13 @@ public sealed partial class MainWindow : WindowEx,
internal void MainWindow_Closed(object sender, WindowEventArgs args)
{
var serviceProvider = App.Current.Services;
UpdateWindowPositionInMemory();
if (_settings is not null)
var settings = serviceProvider.GetService<SettingsModel>();
if (settings is not null)
{
_settings.LastWindowPosition = new WindowPosition
settings.LastWindowPosition = new WindowPosition
{
X = _currentWindowPosition.X,
Y = _currentWindowPosition.Y,
@@ -485,12 +481,13 @@ public sealed partial class MainWindow : WindowEx,
Height = _currentWindowPosition.Height,
};
SettingsModel.SaveSettings(_settings);
SettingsModel.SaveSettings(settings);
}
_extensionService.SignalStopExtensionsAsync();
var extensionService = serviceProvider.GetService<IExtensionService>()!;
extensionService.SignalStopExtensionsAsync();
_trayIconService.Destroy();
App.Current.Services.GetService<TrayIconService>()!.Destroy();
// WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
// Workaround by turning it off before shutdown.
@@ -516,28 +513,28 @@ public sealed partial class MainWindow : WindowEx,
private void UpdateRegionsForCustomTitleBar()
{
// Specify the interactive regions of the title bar.
var scaleAdjustment = _rootShellPage.XamlRoot.RasterizationScale;
var scaleAdjustment = RootShellPage.XamlRoot.RasterizationScale;
// Get the rectangle around our XAML content. We're going to mark this
// rectangle as "Passthrough", so that the normal window operations
// (resizing, dragging) don't apply in this space.
var transform = _rootShellPage.TransformToVisual(null);
var transform = RootShellPage.TransformToVisual(null);
// Reserve 16px of space at the top for dragging.
var topHeight = 16;
var bounds = transform.TransformBounds(new Rect(
0,
topHeight,
_rootShellPage.ActualWidth,
_rootShellPage.ActualHeight));
RootShellPage.ActualWidth,
RootShellPage.ActualHeight));
var contentRect = GetRect(bounds, scaleAdjustment);
var rectArray = new RectInt32[] { contentRect };
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
// Add a drag-able region on top
var w = _rootShellPage.ActualWidth;
_ = _rootShellPage.ActualHeight;
var w = RootShellPage.ActualWidth;
_ = RootShellPage.ActualHeight;
var dragSides = new RectInt32[]
{
GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall
@@ -627,7 +624,8 @@ public sealed partial class MainWindow : WindowEx,
}
else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase))
{
if (_settings.AllowExternalReload == true)
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.AllowExternalReload == true)
{
Logger.LogInfo("External Reload triggered");
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());

View File

@@ -16,10 +16,12 @@ using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.Settings;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation;
@@ -60,9 +62,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private readonly ToastWindow _toast = new();
private readonly SettingsModel _settings;
private readonly TopLevelCommandManager tlcManager;
private readonly CompositeFormat _pageNavigatedAnnouncement;
private SettingsWindow? _settingsWindow;
@@ -70,21 +69,14 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private CancellationTokenSource? _focusAfterLoadedCts;
private WeakReference<Page>? _lastNavigatedPageRef;
public ShellViewModel ViewModel { get; private set; }
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
public event PropertyChangedEventHandler? PropertyChanged;
public ShellPage(
ShellViewModel shellViewModel,
SettingsModel settingsModel,
TopLevelCommandManager topLevelCommandManager)
public ShellPage()
{
this.InitializeComponent();
ViewModel = shellViewModel;
_settings = settingsModel;
tlcManager = topLevelCommandManager;
// how we are doing navigation around
WeakReferenceMessenger.Default.Register<NavigateBackMessage>(this);
WeakReferenceMessenger.Default.Register<OpenSettingsMessage>(this);
@@ -120,16 +112,19 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
{
get
{
return _settings.DisableAnimations ? _noAnimation : _slideRightTransition;
var settings = App.Current.Services.GetService<SettingsModel>()!;
return settings.DisableAnimations ? _noAnimation : _slideRightTransition;
}
}
public void Receive(NavigateBackMessage message)
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
if (RootFrame.CanGoBack)
{
if (!message.FromBackspace ||
_settings.BackspaceGoesBack)
settings.BackspaceGoesBack)
{
GoBack();
}
@@ -340,6 +335,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private void SummonOnUiThread(HotkeySummonMessage message)
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
var commandId = message.CommandId;
var isRoot = string.IsNullOrEmpty(commandId);
if (isRoot)
@@ -350,11 +346,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// Depending on the settings, either
// * Go home, or
// * Select the search text (if we should remain open on this page)
if (_settings.HotkeyGoesHome)
if (settings.HotkeyGoesHome)
{
GoHome(false);
}
else if (_settings.HighlightSearchOnActivate)
else if (settings.HighlightSearchOnActivate)
{
SearchBox.SelectSearch();
}
@@ -364,7 +360,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
try
{
// For a hotkey bound to a command, first lookup the
// command from our list of top level commands.
// command from our list of toplevel commands.
var tlcManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
var topLevelCommand = tlcManager.LookupCommand(commandId);
if (topLevelCommand is not null)
{
@@ -658,15 +655,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
e.Handled = true;
break;
default:
{
// The CommandBar is responsible for handling all the item keybindings,
// since the bound context item may need to then show another
// context menu
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
WeakReferenceMessenger.Default.Send(msg);
e.Handled = msg.Handled;
break;
}
{
// The CommandBar is responsible for handling all the item keybindings,
// since the bound context item may need to then show another
// context menu
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
WeakReferenceMessenger.Default.Send(msg);
e.Handled = msg.Handled;
break;
}
}
}

View File

@@ -10,6 +10,7 @@ using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.MainPage;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using WinRT;
// To learn more about WinUI, the WinUI project structure,
@@ -18,23 +19,24 @@ namespace Microsoft.CmdPal.UI;
internal sealed class PowerToysRootPageService : IRootPageService
{
private readonly TopLevelCommandManager _topLevelCommandManager;
private readonly IServiceProvider _serviceProvider;
private IExtensionWrapper? _activeExtension;
private Lazy<MainListPage> _mainListPage;
public PowerToysRootPageService(TopLevelCommandManager topLevelCommandManager, SettingsModel settingsModel, AliasManager aliasManager, AppStateModel appStateModel)
public PowerToysRootPageService(IServiceProvider serviceProvider)
{
_topLevelCommandManager = topLevelCommandManager;
_serviceProvider = serviceProvider;
_mainListPage = new Lazy<MainListPage>(() =>
{
return new MainListPage(_topLevelCommandManager, settingsModel, aliasManager, appStateModel);
return new MainListPage(_serviceProvider);
});
}
public async Task PreLoadAsync()
{
await _topLevelCommandManager.LoadBuiltinsAsync();
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
await tlcManager.LoadBuiltinsAsync();
}
public Microsoft.CommandPalette.Extensions.IPage GetRootPage()
@@ -44,11 +46,13 @@ internal sealed class PowerToysRootPageService : IRootPageService
public async Task PostLoadRootPageAsync()
{
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
_topLevelCommandManager.LoadExtensionsCommand.Execute(null);
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
await _topLevelCommandManager.LoadExtensionsCommand.ExecutionTask!;
if (_topLevelCommandManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
tlcManager.LoadExtensionsCommand.Execute(null);
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
{
// TODO: Handle failure case
}

View File

@@ -10,6 +10,8 @@ namespace Microsoft.CmdPal.UI.Settings;
public sealed partial class ExtensionPage : Page
{
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
public ProviderSettingsViewModel? ViewModel { get; private set; }
public ExtensionPage()

View File

@@ -5,6 +5,7 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Controls;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
@@ -13,13 +14,16 @@ namespace Microsoft.CmdPal.UI.Settings;
public sealed partial class ExtensionsPage : Page
{
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
private readonly SettingsViewModel? viewModel;
public ExtensionsPage(SettingsViewModel settingsViewModel)
public ExtensionsPage()
{
this.InitializeComponent();
viewModel = settingsViewModel;
var settings = App.Current.Services.GetService<SettingsModel>()!;
viewModel = new SettingsViewModel(settings, App.Current.Services, _mainTaskScheduler);
}
private void SettingsCard_Click(object sender, RoutedEventArgs e)

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel;
@@ -10,13 +11,16 @@ namespace Microsoft.CmdPal.UI.Settings;
public sealed partial class GeneralPage : Page
{
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
private readonly SettingsViewModel? viewModel;
public GeneralPage(SettingsViewModel settings, TopLevelCommandManager topLevelCommandManager)
public GeneralPage()
{
this.InitializeComponent();
viewModel = settings;
var settings = App.Current.Services.GetService<SettingsModel>()!;
viewModel = new SettingsViewModel(settings, App.Current.Services, _mainTaskScheduler);
}
public string ApplicationVersion

View File

@@ -15,7 +15,6 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit;
[JsonSerializable(typeof(List<Choice>))]
[JsonSerializable(typeof(List<ChoiceSetSetting>))]
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
[JsonSerializable(typeof(List<Dictionary<string, object>>))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)]
internal sealed partial class JsonSerializationContext : JsonSerializerContext
{

View File

@@ -26,69 +26,15 @@ public sealed class ToggleSetting : Setting<bool>
public override Dictionary<string, object> ToDictionary()
{
var items = new List<Dictionary<string, object>>();
if (!string.IsNullOrEmpty(Label))
return new Dictionary<string, object>
{
items.Add(
new()
{
{ "type", "TextBlock" },
{ "text", Label },
{ "wrap", true },
});
}
if (!(string.IsNullOrEmpty(Description) || string.Equals(Description, Label, StringComparison.OrdinalIgnoreCase)))
{
items.Add(
new()
{
{ "type", "TextBlock" },
{ "text", Description },
{ "isSubtle", true },
{ "size", "Small" },
{ "spacing", "Small" },
{ "wrap", true },
});
}
return new()
{
{ "type", "ColumnSet" },
{
"columns", new List<Dictionary<string, object>>
{
new()
{
{ "type", "Column" },
{ "width", "20px" },
{
"items", new List<Dictionary<string, object>>
{
new()
{
{ "type", "Input.Toggle" },
{ "title", " " },
{ "id", Key },
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
},
}
},
{ "verticalContentAlignment", "Center" },
},
new()
{
{ "type", "Column" },
{ "width", "stretch" },
{ "items", items },
{ "verticalContentAlignment", "Center" },
},
}
},
{ "spacing", "Medium" },
{ "type", "Input.Toggle" },
{ "title", Label },
{ "id", Key },
{ "label", Description },
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
};
}

View File

@@ -161,7 +161,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"PowerToys.MouseJump.dll",
L"PowerToys.AlwaysOnTopModuleInterface.dll",
L"PowerToys.MousePointerCrosshairs.dll",
L"PowerToys.CursorWrap.dll",
// L"PowerToys.CursorWrap.dll",
L"PowerToys.PowerAccentModuleInterface.dll",
L"PowerToys.PowerOCRModuleInterface.dll",
L"PowerToys.AdvancedPasteModuleInterface.dll",

View File

@@ -0,0 +1,35 @@
// 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.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
/// <summary>
/// Stores provider-specific configuration overrides so each AI service can keep distinct settings.
/// </summary>
public class AIProviderConfigurationSnapshot
{
[JsonPropertyName("model-name")]
public string ModelName { get; set; } = string.Empty;
[JsonPropertyName("endpoint-url")]
public string EndpointUrl { get; set; } = string.Empty;
[JsonPropertyName("api-version")]
public string ApiVersion { get; set; } = string.Empty;
[JsonPropertyName("deployment-name")]
public string DeploymentName { get; set; } = string.Empty;
[JsonPropertyName("model-path")]
public string ModelPath { get; set; } = string.Empty;
[JsonPropertyName("system-prompt")]
public string SystemPrompt { get; set; } = string.Empty;
[JsonPropertyName("moderation-enabled")]
public bool ModerationEnabled { get; set; } = true;
}
}

View File

@@ -13,12 +13,56 @@ namespace Microsoft.PowerToys.Settings.UI.Library
/// </summary>
public static class AdvancedPasteMigrationHelper
{
/// <summary>
/// Moves legacy provider configuration snapshots into the strongly-typed providers collection.
/// </summary>
/// <param name="configuration">The configuration instance to migrate.</param>
/// <returns>True if the configuration was modified.</returns>
public static bool MigrateLegacyProviderConfigurations(PasteAIConfiguration configuration)
{
if (configuration is null)
{
return false;
}
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
bool configurationUpdated = false;
if (configuration.LegacyProviderConfigurations is { Count: > 0 })
{
foreach (var entry in configuration.LegacyProviderConfigurations)
{
var result = EnsureProvider(configuration, entry.Key, entry.Value);
configurationUpdated |= result.Updated;
}
configuration.LegacyProviderConfigurations = null;
}
configurationUpdated |= EnsureActiveProviderIsValid(configuration);
return configurationUpdated;
}
/// <summary>
/// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
/// </summary>
/// <param name="configuration">The configuration instance.</param>
/// <returns>The ensured provider and a flag indicating whether changes were made.</returns>
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration)
{
return EnsureProvider(configuration, AIServiceType.OpenAI.ToConfigurationString(), null);
}
/// <summary>
/// Ensures a provider for the supplied service type exists, optionally applying a legacy snapshot.
/// </summary>
/// <param name="configuration">The configuration instance.</param>
/// <param name="serviceTypeKey">The persisted service type key.</param>
/// <param name="snapshot">An optional snapshot containing legacy values.</param>
/// <returns>The ensured provider and whether the configuration was updated.</returns>
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureProvider(PasteAIConfiguration configuration, string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
{
if (configuration is null)
{
@@ -27,45 +71,101 @@ namespace Microsoft.PowerToys.Settings.UI.Library
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
const string serviceTypeKey = "OpenAI";
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, serviceTypeKey, StringComparison.OrdinalIgnoreCase));
bool updated = false;
var normalizedServiceType = NormalizeServiceType(serviceTypeKey);
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, normalizedServiceType, StringComparison.OrdinalIgnoreCase));
bool configurationUpdated = false;
if (existingProvider is null)
{
existingProvider = CreateProvider(serviceTypeKey);
existingProvider = CreateProvider(normalizedServiceType, snapshot);
configuration.Providers.Add(existingProvider);
updated = true;
configurationUpdated = true;
}
else if (snapshot is not null)
{
configurationUpdated |= ApplySnapshot(existingProvider, snapshot);
}
updated |= EnsureActiveProviderIsValid(configuration, existingProvider);
configurationUpdated |= EnsureActiveProviderIsValid(configuration, existingProvider);
return (existingProvider, updated);
return (existingProvider, configurationUpdated);
}
/// <summary>
/// Creates a provider with default values for the requested service type.
/// </summary>
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey)
private static string NormalizeServiceType(string serviceTypeKey)
{
var serviceType = serviceTypeKey.ToAIServiceType();
return serviceType.ToConfigurationString();
}
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
{
var serviceType = serviceTypeKey.ToAIServiceType();
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
var provider = new PasteAIProviderDefinition
{
ServiceType = serviceTypeKey,
ModelName = PasteAIProviderDefaults.GetDefaultModelName(serviceType),
EndpointUrl = string.Empty,
ApiVersion = string.Empty,
DeploymentName = string.Empty,
ModelPath = string.Empty,
SystemPrompt = string.Empty,
ModerationEnabled = serviceType == AIServiceType.OpenAI,
ModelName = !string.IsNullOrWhiteSpace(snapshot?.ModelName) ? snapshot.ModelName : PasteAIProviderDefaults.GetDefaultModelName(serviceType),
EndpointUrl = snapshot?.EndpointUrl ?? string.Empty,
ApiVersion = snapshot?.ApiVersion ?? string.Empty,
DeploymentName = snapshot?.DeploymentName ?? string.Empty,
ModelPath = snapshot?.ModelPath ?? string.Empty,
SystemPrompt = snapshot?.SystemPrompt ?? string.Empty,
ModerationEnabled = snapshot?.ModerationEnabled ?? true,
IsLocalModel = metadata.IsLocalModel,
};
return provider;
}
private static bool ApplySnapshot(PasteAIProviderDefinition provider, AIProviderConfigurationSnapshot snapshot)
{
bool updated = false;
if (!string.IsNullOrWhiteSpace(snapshot.ModelName) && !string.Equals(provider.ModelName, snapshot.ModelName, StringComparison.Ordinal))
{
provider.ModelName = snapshot.ModelName;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.EndpointUrl) && !string.Equals(provider.EndpointUrl, snapshot.EndpointUrl, StringComparison.Ordinal))
{
provider.EndpointUrl = snapshot.EndpointUrl;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.ApiVersion) && !string.Equals(provider.ApiVersion, snapshot.ApiVersion, StringComparison.Ordinal))
{
provider.ApiVersion = snapshot.ApiVersion;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.DeploymentName) && !string.Equals(provider.DeploymentName, snapshot.DeploymentName, StringComparison.Ordinal))
{
provider.DeploymentName = snapshot.DeploymentName;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.ModelPath) && !string.Equals(provider.ModelPath, snapshot.ModelPath, StringComparison.Ordinal))
{
provider.ModelPath = snapshot.ModelPath;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.SystemPrompt) && !string.Equals(provider.SystemPrompt, snapshot.SystemPrompt, StringComparison.Ordinal))
{
provider.SystemPrompt = snapshot.SystemPrompt;
updated = true;
}
if (provider.ModerationEnabled != snapshot.ModerationEnabled)
{
provider.ModerationEnabled = snapshot.ModerationEnabled;
updated = true;
}
return updated;
}
private static bool EnsureActiveProviderIsValid(PasteAIConfiguration configuration, PasteAIProviderDefinition preferredProvider = null)
{
if (configuration?.Providers is null || configuration.Providers.Count == 0)

View File

@@ -20,6 +20,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
private string _activeProviderId = string.Empty;
private ObservableCollection<PasteAIProviderDefinition> _providers = new();
private bool _useSharedCredentials = true;
private Dictionary<string, AIProviderConfigurationSnapshot> _legacyProviderConfigurations;
public event PropertyChangedEventHandler PropertyChanged;
@@ -37,6 +39,21 @@ namespace Microsoft.PowerToys.Settings.UI.Library
set => SetProperty(ref _providers, value ?? new ObservableCollection<PasteAIProviderDefinition>());
}
[JsonPropertyName("use-shared-credentials")]
public bool UseSharedCredentials
{
get => _useSharedCredentials;
set => SetProperty(ref _useSharedCredentials, value);
}
[JsonPropertyName("provider-configurations")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, AIProviderConfigurationSnapshot> LegacyProviderConfigurations
{
get => _legacyProviderConfigurations;
set => _legacyProviderConfigurations = value;
}
[JsonIgnore]
public PasteAIProviderDefinition ActiveProvider
{

View File

@@ -273,6 +273,7 @@
<panels:MouseJumpPanel x:Name="MouseUtils_MouseJump_Panel" x:Uid="MouseUtils_MouseJump_Panel" />
<!--
<controls:SettingsGroup x:Uid="MouseUtils_CursorWrap" AutomationProperties.AutomationId="MouseUtils_CursorWrapTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard
@@ -300,7 +301,7 @@
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
</controls:SettingsGroup>-->
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs" AutomationProperties.AutomationId="MouseUtils_MousePointerCrosshairsTestId">
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}">
<tkcontrols:SettingsCard

View File

@@ -285,9 +285,6 @@
AutomationProperties.AutomationId="InputOutputNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/InputOutput.png}"
SelectsOnInvoked="False">
<NavigationViewItem.InfoBadge>
<InfoBadge Style="{StaticResource NewInfoBadge}" />
</NavigationViewItem.InfoBadge>
<NavigationViewItem.MenuItems>
<NavigationViewItem
x:Name="KeyboardManagerNavigationItem"
@@ -302,11 +299,7 @@
x:Uid="Shell_MouseUtilities"
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
AutomationProperties.AutomationId="MouseUtilitiesNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}">
<NavigationViewItem.InfoBadge>
<InfoBadge Style="{StaticResource NewInfoBadge}" />
</NavigationViewItem.InfoBadge>
</NavigationViewItem>
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}" />
<NavigationViewItem
x:Name="MouseWithoutBordersNavigationItem"
x:Uid="Shell_MouseWithoutBorders"

View File

@@ -191,18 +191,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return;
}
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
if (legacyCredential is null)
{
if (legacyAdvancedAIConsumed)
{
SaveAndNotifySettings();
}
return;
}
var configuration = properties.PasteAIConfiguration;
if (configuration is null)
{
@@ -210,11 +198,28 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
properties.PasteAIConfiguration = configuration;
}
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
{
return;
}
bool configurationUpdated = false;
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
configurationUpdated |= ensureResult.Updated;
if (hasLegacyProviders)
{
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
}
PasteAIProviderDefinition openAIProvider = null;
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
{
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
openAIProvider = ensureResult.Provider;
configurationUpdated |= ensureResult.Updated;
}
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
{
@@ -228,7 +233,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
RemoveLegacyOpenAICredential();
}
const bool shouldEnableAI = true;
bool shouldEnableAI = legacyCredential is not null;
bool enabledChanged = false;
if (properties.IsAIEnabled != shouldEnableAI)
{
@@ -1243,6 +1248,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return true;
}
if (current.UseSharedCredentials != incoming.UseSharedCredentials)
{
return true;
}
var currentProviders = current.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
var incomingProviders = incoming.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
@@ -1398,7 +1408,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return;
}
if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal))
if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal)
|| string.Equals(e.PropertyName, nameof(PasteAIConfiguration.UseSharedCredentials), StringComparison.Ordinal))
{
SaveAndNotifySettings();
}
@@ -1415,7 +1426,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
bool configurationUpdated = AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(pasteConfig);
SubscribeToPasteAIProviders(pasteConfig);
if (configurationUpdated)
{
SaveAndNotifySettings();
OnPropertyChanged(nameof(PasteAIConfiguration));
}
}
private static string RetrieveCredentialValue(string credentialResource, string credentialUserName)

View File

@@ -137,6 +137,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
{
// Hide CursorWrap from Dashboard
if (moduleType == ModuleType.CursorWrap)
{
continue;
}
GpoRuleConfigured gpo = ModuleHelper.GetModuleGpoConfiguration(moduleType);
var newItem = new DashboardListItem()
{
@@ -145,7 +151,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
IsEnabled = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType)),
IsLocked = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled,
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
IsNew = moduleType == ModuleType.CursorWrap,
// IsNew = moduleType == ModuleType.CursorWrap,
DashboardModuleItems = GetModuleItems(moduleType),
};
newItem.EnabledChangedCallback = EnabledChangedOnUI;

View File

@@ -36,6 +36,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
{
// Hide CursorWrap from All Apps flyout
if (moduleType == ModuleType.CursorWrap)
{
continue;
}
AddFlyoutMenuItem(moduleType);
}

View File

@@ -30,7 +30,6 @@
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalIncludeDirectories>../../../src/</AdditionalIncludeDirectories>
<TreatAngleIncludeAsExternal>true</TreatAngleIncludeAsExternal>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -38,6 +37,10 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\..\deps\cziplib\src\zip.c">
<!-- Disabling warnings for external code -->
<DisableSpecificWarnings>4706;26451;4267;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<ClCompile Include="EventViewer.cpp" />
<ClCompile Include="InstallationFolder.cpp" />
<ClCompile Include="Package.cpp" />
@@ -58,6 +61,8 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\deps\cziplib\src\miniz.h" />
<ClInclude Include="..\..\..\deps\cziplib\src\zip.h" />
<ClInclude Include="EventViewer.h" />
<ClInclude Include="InstallationFolder.h" />
<ClInclude Include="Package.h" />

View File

@@ -8,6 +8,7 @@
<ClCompile Include="ZipTools\ZipFolder.cpp">
<Filter>ZipTools</Filter>
</ClCompile>
<ClCompile Include="..\..\..\deps\cziplib\src\zip.c" />
<ClCompile Include="ReportMonitorInfo.cpp" />
<ClCompile Include="RegistryUtils.cpp" />
<ClCompile Include="EventViewer.cpp" />
@@ -27,6 +28,8 @@
<Filter>ZipTools</Filter>
</ClInclude>
<ClInclude Include="..\..\..\common\utils\json.h" />
<ClInclude Include="..\..\..\deps\cziplib\src\miniz.h" />
<ClInclude Include="..\..\..\deps\cziplib\src\zip.h" />
<ClInclude Include="ReportMonitorInfo.h" />
<ClInclude Include="RegistryUtils.h" />
<ClInclude Include="EventViewer.h" />

View File

@@ -1,53 +1,50 @@
#include "ZipFolder.h"
#include "..\..\..\..\deps\cziplib\src\zip.h"
#include <common/utils/timeutil.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <format>
#include <wil/stl.h>
#include <wil/win32_helpers.h>
void ZipFolder(std::filesystem::path zipPath, std::filesystem::path folderPath)
{
const auto reportFilename{
std::format("PowerToysReport_{0}.zip",
timeutil::format_as_local("%F-%H-%M-%S", timeutil::now()))
};
const auto finalReportFullPath{ zipPath / reportFilename };
std::string reportFilename{ "PowerToysReport_" };
reportFilename += timeutil::format_as_local("%F-%H-%M-%S", timeutil::now());
reportFilename += ".zip";
const auto tempReportFilename{ reportFilename + ".tmp" };
const auto tempReportFullPath{ zipPath / tempReportFilename };
auto tmpZipPath = std::filesystem::temp_directory_path();
tmpZipPath /= reportFilename;
// tar -c --format=zip -f "ReportFile.zip" *
const auto executable{ wil::ExpandEnvironmentStringsW<std::wstring>(LR"(%WINDIR%\System32\tar.exe)") };
auto commandline{ std::format(LR"("{0}" -c --format=zip -f "{1}" *)", executable, tempReportFullPath.wstring()) };
const auto folderPathAsString{ folderPath.lexically_normal().wstring() };
wil::unique_process_information pi;
STARTUPINFOW si{ .cb = sizeof(STARTUPINFOW) };
if (!CreateProcessW(executable.c_str(),
commandline.data() /* must be mutable */,
nullptr,
nullptr,
FALSE,
DETACHED_PROCESS,
nullptr,
folderPathAsString.c_str(),
&si,
&pi))
struct zip_t* zip = zip_open(tmpZipPath.string().c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
if (!zip)
{
printf("Cannot open zip.");
throw -1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
using recursive_directory_iterator = std::filesystem::recursive_directory_iterator;
const size_t rootSize = folderPath.wstring().size();
for (const auto& dirEntry : recursive_directory_iterator(folderPath))
{
if (dirEntry.is_regular_file())
{
auto path = dirEntry.path().string();
auto relativePath = path.substr(rootSize, path.size());
zip_entry_open(zip, relativePath.c_str());
zip_entry_fwrite(zip, path.c_str());
zip_entry_close(zip);
}
}
std::error_code err{};
std::filesystem::rename(tempReportFullPath, finalReportFullPath, err);
zip_close(zip);
std::error_code err;
std::filesystem::copy(tmpZipPath, zipPath, err);
if (err.value() != 0)
{
wprintf_s(L"Failed to rename %s. Error code: %d\n", tempReportFullPath.native().c_str(), err.value());
wprintf_s(L"Failed to copy %s. Error code: %d\n", tmpZipPath.c_str(), err.value());
}
}
err = {};
std::filesystem::remove_all(tmpZipPath, err);
if (err.value() != 0)
{
wprintf_s(L"Failed to delete %s. Error code: %d\n", tmpZipPath.c_str(), err.value());
}
}