diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index c0d5de7e3b..e2abef0344 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -47,7 +47,6 @@ Allmodule ALLOWUNDO ALLVIEW ALPHATYPE -amazonbedrock AModifier amr ANDSCANS @@ -142,7 +141,7 @@ bla BLACKFRAME BLENDFUNCTION Blockquotes -Blt +blt BLURBEHIND BLURREGION bmi @@ -696,7 +695,6 @@ hmonitor homies homljgmgpmcbpjbnjpfijnhipfkiclkd HOOKPROC -huggingface HORZRES HORZSIZE Hostbackdropbrush @@ -1875,6 +1873,7 @@ UPDATENOW UPDATEREGISTRY updown UPGRADINGPRODUCTCODE +upscaling Uptool urld Usb diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 04f9cfaaeb..560b44b5a4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,6 +5,7 @@ ## 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 diff --git a/.github/workflows/msstore-submissions.yml b/.github/workflows/msstore-submissions.yml index a44dafb199..36dfc4d785 100644 --- a/.github/workflows/msstore-submissions.yml +++ b/.github/workflows/msstore-submissions.yml @@ -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@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: '9.0.x' diff --git a/.gitmodules b/.gitmodules index 1601291341..f878c1a9e3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [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 diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index c419d1b588..83289fa102 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -291,6 +291,7 @@ "Mono.Cecil.Rocks.dll", "Newtonsoft.Json.dll", "CommunityToolkit.WinUI.Controls.TitleBar.dll", + "CommunityToolkit.WinUI.Controls.OpacityMaskView.dll", "NLog.dll", "HtmlAgilityPack.dll", diff --git a/Cpp.Build.props b/Cpp.Build.props index 99738fd0dc..f146a4d770 100644 --- a/Cpp.Build.props +++ b/Cpp.Build.props @@ -1,56 +1,6 @@ - - - $(Configuration) - - - - - - - - - - - MultiThreadedDebug - - - - %(IgnoreSpecificDefaultLibraries);libucrtd.lib - %(AdditionalOptions) /defaultlib:ucrtd.lib - - - - - - MultiThreaded - - - - %(IgnoreSpecificDefaultLibraries);libucrt.lib - %(AdditionalOptions) /defaultlib:ucrt.lib - - - - - - - MultiThreadedDebugDLL - - - - - MultiThreadedDLL - - @@ -76,6 +26,7 @@ true $(MsbuildThisFileDirectory)\CppRuleSet.ruleset + $(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(CAExcludePath) @@ -84,7 +35,7 @@ arm64 false true - $(MSBuildThisFileFullPath)\..\deps\;$(MSBuildThisFileFullPath)\..\packages\;$(ExternalIncludePath) + $(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(ExternalIncludePath) Guard @@ -123,6 +74,7 @@ _DEBUG;%(PreprocessorDefinitions) Disabled + MultiThreadedDebug true @@ -132,6 +84,7 @@ NDEBUG;%(PreprocessorDefinitions) MaxSpeed + MultiThreaded true true diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md index 56a2eb9eee..0ad4bda9c9 100644 --- a/DATA_AND_PRIVACY.md +++ b/DATA_AND_PRIVACY.md @@ -147,6 +147,18 @@ _If you want to find diagnostic data events in the source code, these two links Microsoft.PowerToys.AdvancedPasteSemanticKernelFormatEvent Triggered when Advanced Paste leverages the Semantic Kernel. + + Microsoft.PowerToys.AdvancedPasteSemanticKernelErrorEvent + Occurs when the Semantic Kernel workflow encounters an error. + + + Microsoft.PowerToys.AdvancedPasteEndpointUsageEvent + Logs the AI provider, model, and processing duration for each endpoint call. + + + Microsoft.PowerToys.AdvancedPasteCustomActionErrorEvent + Records provider, model, and status details when a custom action fails. + ### Always on Top diff --git a/Directory.Packages.props b/Directory.Packages.props index 9f01103d8f..a88ac0ec38 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,6 +7,7 @@ + @@ -51,10 +52,8 @@ - - diff --git a/NOTICE.md b/NOTICE.md index a0d87d429c..6ca3cbfceb 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1498,6 +1498,7 @@ SOFTWARE. - CoenM.ImageSharp.ImageHash - CommunityToolkit.Common - CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock +- CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView - CommunityToolkit.Mvvm - CommunityToolkit.WinUI.Animations - CommunityToolkit.WinUI.Collections diff --git a/README.md b/README.md index 067d6d6f50..ab53e53a46 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@

Microsoft PowerToys

- +

+ Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks. +

Installation · @@ -18,8 +20,10 @@ Release notes



-Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks. -

+ +## 🔨 Utilities + +PowerToys includes over 25 utilities to help you customize and optimize your Windows experience: | | | | |---|---|---| @@ -37,20 +41,13 @@ Microsoft PowerToys is a collection of utilities that help you customize Windows ## 📋 Installation -For detailed installation instructions, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install). - -Before you begin, make sure your device meets the system requirements: - -> [!NOTE] -> - Windows 11 or Windows 10 version 2004 (20H1 / build 19041) or newer -> - 64-bit processor: x64 or ARM64 -> - Latest stable version of [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) is installed via the bootstrapper during setup - -Choose one of the installation methods below: +For detailed installation instructions and system requirements, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install). +But to get started quickly, choose one of the installation methods below: +

-Download .exe from GitHub - +Download .exe from GitHub +
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer. @@ -67,11 +64,11 @@ Go to the [PowerToys GitHub releases][github-release-link], click Assets to reve | Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] | | Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] | | Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] | -
-Microsoft Store +Microsoft Store +
You can easily install PowerToys from the Microsoft Store:

@@ -82,10 +79,9 @@ You can easily install PowerToys from the Microsoft Store:

-
-WinGet - +WinGet +
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell: *User scope installer [default]* @@ -100,8 +96,8 @@ winget install --scope machine Microsoft.PowerToys -s winget
-Other methods - +Other methods +
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
diff --git a/deps/cziplib b/deps/cziplib deleted file mode 160000 index 81314fff0a..0000000000 --- a/deps/cziplib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 81314fff0a882b72a9ad321e7a3311660125b56e diff --git a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp index 0cfc3b1765..968fcc2530 100644 --- a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../../src/common/logger/logger.h" #include "../../src/common/utils/gpo.h" @@ -856,14 +857,69 @@ 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"); - cred = vault.Retrieve(L"https://platform.openai.com/api-keys", L"PowerToys_AdvancedPaste_OpenAIKey"); - vault.Remove(cred); + 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 (...) + { + } + } } catch (...) { diff --git a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj index 21b0e75837..ae50cdcedb 100644 --- a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj +++ b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj @@ -110,6 +110,7 @@ Disabled _DEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions) EnableFastChecks + MultiThreadedDebug true @@ -122,6 +123,7 @@ MaxSpeed true NDEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded true diff --git a/installer/PowerToysSetupVNext/Directory.Build.props b/installer/PowerToysSetupVNext/Directory.Build.props index 69a63832d1..505e3cf844 100644 --- a/installer/PowerToysSetupVNext/Directory.Build.props +++ b/installer/PowerToysSetupVNext/Directory.Build.props @@ -1,5 +1,4 @@ - @@ -9,4 +8,4 @@ $(BaseIntermediateOutputPath) - \ No newline at end of file +
diff --git a/installer/PowerToysSetupVNext/Product.wxs b/installer/PowerToysSetupVNext/Product.wxs index 556fddc7f4..3e812beb2e 100644 --- a/installer/PowerToysSetupVNext/Product.wxs +++ b/installer/PowerToysSetupVNext/Product.wxs @@ -120,8 +120,8 @@ - - + + diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj index d45e32f87c..3972c1b0f7 100644 --- a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj @@ -1,7 +1,30 @@ + + + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + x64 + + + Release + x64 + + + {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D} + DynamicLibrary + Unicode SilentFilesInUseBAFunction PowerToysSetupCustomActionsVNext bafunctions.def @@ -10,6 +33,7 @@ + DynamicLibrary true @@ -41,10 +65,7 @@ - - Use - precomp.h - + Create precomp.h @@ -71,5 +92,31 @@ + + + + _DEBUG;%(PreprocessorDefinitions) + Disabled + MultiThreadedDebug + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + MaxSpeed + MultiThreaded + true + true + + + true + true + true + + + diff --git a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp index ceccde5f0d..9b9e5d570f 100644 --- a/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp +++ b/installer/PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunctions.cpp @@ -18,6 +18,7 @@ public: // IBootstrapperApplication BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running detect begin BA function. fCached=%d, registrationType=%d, cPackages=%u, fCancel=%d", fCached, registrationType, cPackages, *pfCancel); + LExit: return hr; } @@ -31,6 +32,12 @@ public: // IBAFunctions BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running plan begin BA function. cPackages=%u, fCancel=%d", cPackages, *pfCancel); + //------------------------------------------------------------------------------------------------- + // YOUR CODE GOES HERE + // BalExitOnFailure(hr, "Change this message to represent real error handling."); + //------------------------------------------------------------------------------------------------- + + LExit: return hr; } @@ -56,7 +63,6 @@ public: // IBAFunctions ) { HRESULT hr = S_OK; - UNREFERENCED_PARAMETER(source); BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION CALLED *** Running OnExecuteFilesInUse BA function. packageId=%ls, cFiles=%u, recommendation=%d", wzPackageId, cFiles, nRecommendation); diff --git a/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj b/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj index ff9332cfc0..43f4749892 100644 --- a/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj +++ b/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj @@ -9,6 +9,12 @@ CalculatorEngineCommon false + + + true + false + + true @@ -19,9 +25,11 @@ true - false + true true Windows Store + false + @@ -140,5 +148,43 @@ - + + + + + + + MultiThreadedDebug + stdcpp17 + + + + %(IgnoreSpecificDefaultLibraries);libucrtd.lib + %(AdditionalOptions) /defaultlib:ucrtd.lib + + + + + + MultiThreaded + + + + %(IgnoreSpecificDefaultLibraries);libucrt.lib + %(AdditionalOptions) /defaultlib:ucrt.lib + + + \ No newline at end of file diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index 52c91a0795..2b256cd926 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -216,10 +216,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getAllowedAdvancedPasteGoogleValue()); } - GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteAnthropicValue() - { - return static_cast(powertoys_gpo::getAllowedAdvancedPasteAnthropicValue()); - } GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOllamaValue() { return static_cast(powertoys_gpo::getAllowedAdvancedPasteOllamaValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 846fba0a61..e57cccccd9 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -60,7 +60,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetAllowedAdvancedPasteAzureAIInferenceValue(); static GpoRuleConfigured GetAllowedAdvancedPasteMistralValue(); static GpoRuleConfigured GetAllowedAdvancedPasteGoogleValue(); - static GpoRuleConfigured GetAllowedAdvancedPasteAnthropicValue(); static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue(); static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue(); static GpoRuleConfigured GetConfiguredNewPlusEnabledValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index ab6dbf0c29..06d035aa35 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -64,7 +64,6 @@ namespace PowerToys static GpoRuleConfigured GetAllowedAdvancedPasteAzureAIInferenceValue(); static GpoRuleConfigured GetAllowedAdvancedPasteMistralValue(); static GpoRuleConfigured GetAllowedAdvancedPasteGoogleValue(); - static GpoRuleConfigured GetAllowedAdvancedPasteAnthropicValue(); static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue(); static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue(); static GpoRuleConfigured GetConfiguredNewPlusEnabledValue(); diff --git a/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs b/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs index 84c38c49f2..287588a71e 100644 --- a/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs +++ b/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs @@ -169,40 +169,30 @@ internal sealed class FoundryClient public async Task EnsureModelLoaded(string modelId) { - try + Logger.LogInfo($"[FoundryClient] EnsureModelLoaded called with: {modelId}"); + + // Check if already loaded + if (await IsModelLoaded(modelId).ConfigureAwait(false)) { - Logger.LogInfo($"[FoundryClient] EnsureModelLoaded called with: {modelId}"); - - // Check if already loaded - if (await IsModelLoaded(modelId).ConfigureAwait(false)) - { - Logger.LogInfo($"[FoundryClient] Model already loaded: {modelId}"); - return true; - } - - // Check if model exists in cache - var cachedModels = await ListCachedModels().ConfigureAwait(false); - Logger.LogInfo($"[FoundryClient] Cached models: {string.Join(", ", cachedModels.Select(m => m.Name))}"); - - if (!cachedModels.Any(m => m.Name == modelId)) - { - Logger.LogWarning($"[FoundryClient] Model not found in cache: {modelId}"); - return false; - } - - // Load the model - Logger.LogInfo($"[FoundryClient] Loading model: {modelId}"); - await _foundryManager.LoadModelAsync(modelId).ConfigureAwait(false); - - // Verify it's loaded - var loaded = await IsModelLoaded(modelId).ConfigureAwait(false); - Logger.LogInfo($"[FoundryClient] Model load result: {loaded}"); - return loaded; + Logger.LogInfo($"[FoundryClient] Model already loaded: {modelId}"); + return true; } - catch (Exception ex) + + // Load the model + Logger.LogInfo($"[FoundryClient] Loading model: {modelId}"); + await _foundryManager.LoadModelAsync(modelId).ConfigureAwait(false); + + // Verify it's loaded + var loaded = await IsModelLoaded(modelId).ConfigureAwait(false); + Logger.LogInfo($"[FoundryClient] Model load result: {loaded}"); + return loaded; + } + + public async Task EnsureRunning() + { + if (!_foundryManager.IsServiceRunning) { - Logger.LogError($"[FoundryClient] EnsureModelLoaded exception: {ex.Message}"); - return false; + await _foundryManager.StartServiceAsync(); } } } diff --git a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs index b866ab05d9..2447d818a2 100644 --- a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs +++ b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs @@ -13,7 +13,8 @@ namespace LanguageModelProvider; public sealed class FoundryLocalModelProvider : ILanguageModelProvider { private IEnumerable? _downloadedModels; - private FoundryClient? _foundryManager; + private IEnumerable? _catalogModels; + private FoundryClient? _foundryClient; private string? _serviceUrl; public static FoundryLocalModelProvider Instance { get; } = new(); @@ -22,66 +23,54 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider public string ProviderDescription => "The model will run locally via Foundry Local"; - public string UrlPrefix => "fl://"; - - public IChatClient? GetIChatClient(string url) + public IChatClient? GetIChatClient(string modelId) { - try - { - Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {url}"); - InitializeAsync().GetAwaiter().GetResult(); - } - catch (Exception ex) - { - Logger.LogError($"[FoundryLocal] Failed to initialize: {ex.Message}"); - return null; - } + Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {modelId}"); + InitializeAsync().GetAwaiter().GetResult(); - if (string.IsNullOrWhiteSpace(_serviceUrl) || _foundryManager == null) - { - Logger.LogError("[FoundryLocal] Service URL or manager is null"); - return null; - } - - // Extract model ID from URL (format: fl://modelname) - var modelId = url.Replace(UrlPrefix, string.Empty).Trim('/'); if (string.IsNullOrWhiteSpace(modelId)) { Logger.LogError("[FoundryLocal] Model ID is empty after extraction"); return null; } - Logger.LogInfo($"[FoundryLocal] Extracted model ID: {modelId}"); + // Check if model is in catalog + var isInCatalog = _catalogModels?.Any(m => m.Name == modelId) ?? false; + if (!isInCatalog) + { + var errorMessage = $"{modelId} is not supported in Foundry Local. Please configure supported models in Settings."; + Logger.LogError($"[FoundryLocal] {errorMessage}"); + throw new InvalidOperationException(errorMessage); + } + + // Check if model is cached + var isInCache = _downloadedModels?.Any(m => m.ProviderModelDetails is FoundryCachedModel cached && cached.Name == modelId) ?? false; + if (!isInCache) + { + var errorMessage = $"The requested model '{modelId}' is not cached. Please download it using Foundry Local."; + Logger.LogError($"[FoundryLocal] {errorMessage}"); + throw new InvalidOperationException(errorMessage); + } // Ensure the model is loaded before returning chat client - try + var isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult(); + if (!isLoaded) { - var isLoaded = _foundryManager.EnsureModelLoaded(modelId).GetAwaiter().GetResult(); - if (!isLoaded) - { - Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}"); - return null; - } - - Logger.LogInfo($"[FoundryLocal] Model is loaded: {modelId}"); - } - catch (Exception ex) - { - Logger.LogError($"[FoundryLocal] Exception ensuring model loaded: {ex.Message}"); - return null; + Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}"); + throw new InvalidOperationException($"Failed to load the model '{modelId}'."); } // Use ServiceUri instead of Endpoint since Endpoint already includes /v1 - var baseUri = _foundryManager.GetServiceUri(); + var baseUri = _foundryClient.GetServiceUri(); if (baseUri == null) { - Logger.LogError("[FoundryLocal] Service URI is null"); - return null; + const string message = "Foundry Local service URL is not available. Please make sure Foundry Local is installed and running."; + Logger.LogError($"[FoundryLocal] {message}"); + throw new InvalidOperationException(message); } var endpointUri = new Uri($"{baseUri.ToString().TrimEnd('/')}/v1"); Logger.LogInfo($"[FoundryLocal] Creating OpenAI client with endpoint: {endpointUri}"); - Logger.LogInfo($"[FoundryLocal] Model ID for chat client: {modelId}"); return new OpenAIClient( new ApiKeyCredential("none"), @@ -128,29 +117,36 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider private void Reset() { _downloadedModels = null; + _catalogModels = null; _ = InitializeAsync(); } private async Task InitializeAsync(CancellationToken cancelationToken = default) { - if (_foundryManager != null && _downloadedModels != null && _downloadedModels.Any()) + if (_foundryClient != null && _downloadedModels != null && _downloadedModels.Any() && _catalogModels != null && _catalogModels.Any()) { + await _foundryClient.EnsureRunning().ConfigureAwait(false); return; } Logger.LogInfo("[FoundryLocal] Initializing provider"); - _foundryManager ??= await FoundryClient.CreateAsync(); + _foundryClient ??= await FoundryClient.CreateAsync(); - if (_foundryManager == null) + if (_foundryClient == null) { - Logger.LogError("[FoundryLocal] Failed to create Foundry client"); - return; + const string message = "Foundry Local client could not be created. Please make sure Foundry Local is installed and running."; + Logger.LogError($"[FoundryLocal] {message}"); + throw new InvalidOperationException(message); } - _serviceUrl ??= await _foundryManager.GetServiceUrl(); + _serviceUrl ??= await _foundryClient.GetServiceUrl(); Logger.LogInfo($"[FoundryLocal] Service URL: {_serviceUrl}"); - var cachedModels = await _foundryManager.ListCachedModels(); + var catalogModels = await _foundryClient.ListCatalogModels(); + Logger.LogInfo($"[FoundryLocal] Found {catalogModels.Count} catalog models"); + _catalogModels = catalogModels; + + var cachedModels = await _foundryClient.ListCachedModels(); Logger.LogInfo($"[FoundryLocal] Found {cachedModels.Count} cached models"); List downloadedModels = []; @@ -162,7 +158,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider { Id = $"fl-{model.Name}", Name = model.Name, - Url = $"{UrlPrefix}{model.Name}", + Url = $"fl://{model.Name}", Description = $"{model.Name} running locally with Foundry Local", HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL], SupportedOnQualcomm = true, @@ -178,7 +174,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider { Logger.LogInfo("[FoundryLocal] Checking availability"); await InitializeAsync(); - var available = _foundryManager != null; + var available = _foundryClient != null; Logger.LogInfo($"[FoundryLocal] Available: {available}"); return available; } diff --git a/src/common/LanguageModelProvider/ILanguageModelProvider.cs b/src/common/LanguageModelProvider/ILanguageModelProvider.cs index 60b3d99386..2bef3fb7f1 100644 --- a/src/common/LanguageModelProvider/ILanguageModelProvider.cs +++ b/src/common/LanguageModelProvider/ILanguageModelProvider.cs @@ -10,13 +10,11 @@ public interface ILanguageModelProvider { string Name { get; } - string UrlPrefix { get; } - string ProviderDescription { get; } Task> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default); - IChatClient? GetIChatClient(string url); + IChatClient? GetIChatClient(string modelId); string GetIChatClientString(string url); } diff --git a/src/common/LanguageModelProvider/LanguageModelService.cs b/src/common/LanguageModelProvider/LanguageModelService.cs deleted file mode 100644 index 1cfb3b3c49..0000000000 --- a/src/common/LanguageModelProvider/LanguageModelService.cs +++ /dev/null @@ -1,106 +0,0 @@ -// 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.Collections.Concurrent; -using Microsoft.Extensions.AI; - -namespace LanguageModelProvider; - -public sealed class LanguageModelService -{ - private readonly ConcurrentDictionary _providersByPrefix; - - public LanguageModelService(IEnumerable providers) - { - ArgumentNullException.ThrowIfNull(providers); - - _providersByPrefix = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var provider in providers) - { - if (!string.IsNullOrWhiteSpace(provider.UrlPrefix)) - { - _providersByPrefix[provider.UrlPrefix] = provider; - } - } - } - - public static LanguageModelService CreateDefault() - { - return new LanguageModelService(new[] - { - FoundryLocalModelProvider.Instance, - }); - } - - public IReadOnlyCollection Providers => _providersByPrefix.Values.ToArray(); - - public bool RegisterProvider(ILanguageModelProvider provider) - { - ArgumentNullException.ThrowIfNull(provider); - - if (string.IsNullOrWhiteSpace(provider.UrlPrefix)) - { - throw new ArgumentException("Provider must supply a URL prefix.", nameof(provider)); - } - - _providersByPrefix[provider.UrlPrefix] = provider; - return true; - } - - public ILanguageModelProvider? GetProviderFor(string? modelReference) - { - if (string.IsNullOrWhiteSpace(modelReference)) - { - return null; - } - - foreach (var provider in _providersByPrefix.Values) - { - if (modelReference.StartsWith(provider.UrlPrefix, StringComparison.OrdinalIgnoreCase)) - { - return provider; - } - } - - return null; - } - - public async Task> GetModelsAsync(bool refresh = false, CancellationToken cancellationToken = default) - { - List models = []; - - foreach (var provider in _providersByPrefix.Values) - { - cancellationToken.ThrowIfCancellationRequested(); - var providerModels = await provider.GetModelsAsync(refresh, cancellationToken).ConfigureAwait(false); - models.AddRange(providerModels); - } - - return models; - } - - public IChatClient? GetClient(ModelDetails model) - { - if (model is null) - { - return null; - } - - var reference = !string.IsNullOrWhiteSpace(model.Url) ? model.Url : model.Id; - return GetClient(reference); - } - - public IChatClient? GetClient(string? modelReference) - { - if (string.IsNullOrWhiteSpace(modelReference)) - { - return null; - } - - var provider = GetProviderFor(modelReference); - - return provider?.GetIChatClient(modelReference); - } -} diff --git a/src/common/interop/PowerToys.Interop.vcxproj b/src/common/interop/PowerToys.Interop.vcxproj index 472119925e..ca29e69cce 100644 --- a/src/common/interop/PowerToys.Interop.vcxproj +++ b/src/common/interop/PowerToys.Interop.vcxproj @@ -63,12 +63,14 @@ + MultiThreadedDebug true true + MultiThreaded false true false diff --git a/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj b/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj index b2ebc7cb72..077333a664 100644 --- a/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj +++ b/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj @@ -46,6 +46,16 @@ notifications + + + MultiThreadedDebugDLL + + + + + MultiThreadedDLL + + $(IntDir)pch.pch diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index ecf338d212..ab71d09d0b 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -89,7 +89,6 @@ namespace powertoys_gpo const std::wstring POLICY_ALLOW_ADVANCED_PASTE_AZURE_AI_INFERENCE = L"AllowAdvancedPasteAzureAIInference"; const std::wstring POLICY_ALLOW_ADVANCED_PASTE_MISTRAL = L"AllowAdvancedPasteMistral"; const std::wstring POLICY_ALLOW_ADVANCED_PASTE_GOOGLE = L"AllowAdvancedPasteGoogle"; - const std::wstring POLICY_ALLOW_ADVANCED_PASTE_ANTHROPIC = L"AllowAdvancedPasteAnthropic"; const std::wstring POLICY_ALLOW_ADVANCED_PASTE_OLLAMA = L"AllowAdvancedPasteOllama"; const std::wstring POLICY_ALLOW_ADVANCED_PASTE_FOUNDRY_LOCAL = L"AllowAdvancedPasteFoundryLocal"; const std::wstring POLICY_MWB_CLIPBOARD_SHARING_ENABLED = L"MwbClipboardSharingEnabled"; @@ -615,11 +614,6 @@ namespace powertoys_gpo return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_GOOGLE); } - inline gpo_rule_configured_t getAllowedAdvancedPasteAnthropicValue() - { - return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_ANTHROPIC); - } - inline gpo_rule_configured_t getAllowedAdvancedPasteOllamaValue() { return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_OLLAMA); diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs index 7a548f3ef1..3db41f3fd0 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs @@ -49,6 +49,8 @@ internal sealed class IntegrationTestUserSettings : IUserSettings public bool CloseAfterLosingFocus => false; + public bool EnableClipboardPreview => true; + public IReadOnlyList CustomActions => _customActions; public IReadOnlyList AdditionalActions => _additionalActions; diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj index d5e262c6b1..1b18bc3910 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj @@ -59,10 +59,8 @@ - - diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs index 2737fb44c2..3fa940952e 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs @@ -112,11 +112,7 @@ namespace AdvancedPaste /// Invoked when the application is launched. /// /// Details about the launch request and process. -#if DEBUG - protected async override void OnLaunched(LaunchActivatedEventArgs args) -#else protected override void OnLaunched(LaunchActivatedEventArgs args) -#endif { var cmdArgs = Environment.GetCommandLineArgs(); if (cmdArgs?.Length > 1) @@ -138,10 +134,6 @@ namespace AdvancedPaste { ProcessNamedPipe(cmdArgs[2]); } - -#if DEBUG - await ShowWindow(); // This allows for direct access without using PowerToys Runner, not all functionality might work -#endif } private void ProcessNamedPipe(string pipeName) diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml index 8b7e3e5c6a..6303564d9b 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml @@ -558,7 +558,7 @@ + Foreground="{ThemeResource TextFillColorSecondaryBrush}" /> + Foreground="{ThemeResource TextFillColorSecondaryBrush}" /> + + + + + MultiThreadedDebug + + + + %(IgnoreSpecificDefaultLibraries);libucrtd.lib + %(AdditionalOptions) /defaultlib:ucrtd.lib + + + + + + MultiThreaded + + + + %(IgnoreSpecificDefaultLibraries);libucrt.lib + %(AdditionalOptions) /defaultlib:ucrt.lib + + + + \ No newline at end of file diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs index 62434a632a..2a82f80a02 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs @@ -64,6 +64,8 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext public string Title { get => string.IsNullOrEmpty(field) ? Name : field; protected set; } = string.Empty; + public string Id { get; protected set; } = string.Empty; + // This property maps to `IPage.IsLoading`, but we want to expose our own // `IsLoading` property as a combo of this value and `IsInitialized` public bool ModelIsLoading { get; protected set; } = true; @@ -142,6 +144,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext return; // throw? } + Id = page.Id; Name = page.Name; ModelIsLoading = page.IsLoading; Title = page.Title; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Events/OpenPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Events/OpenPage.cs index 040dd146d0..ac941b7724 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Events/OpenPage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Events/OpenPage.cs @@ -15,9 +15,12 @@ public class OpenPage : EventBase, IEvent { public int PageDepth { get; set; } - public OpenPage(int pageDepth) + public string Id { get; set; } + + public OpenPage(int pageDepth, string id) { PageDepth = pageDepth; + Id = id; EventName = "CmdPal_OpenPage"; } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs index d009c626e0..e51597d268 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs @@ -21,7 +21,6 @@ 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; @@ -160,7 +159,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, new AsyncNavigationRequest(message.Page, message.CancellationToken), message.WithAnimation ? DefaultPageAnimation : _noAnimation); - PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth)); + PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth, message.Page.Id)); if (!ViewModel.IsNested) { @@ -655,15 +654,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; + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw index 8d15c39b39..9c66be3773 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw @@ -429,7 +429,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.More - Last Position + Last position Reopen the window where it was last closed diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj index 260f5560a9..1de58f349f 100644 --- a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj +++ b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj @@ -74,11 +74,35 @@ stdcpp20 + + + MultiThreadedDebug + + + + %(IgnoreSpecificDefaultLibraries);libucrtd.lib + %(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf + stdcpp20 + + + MultiThreaded + + + + %(IgnoreSpecificDefaultLibraries);libucrt.lib + %(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf + diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs index 6a0dde88cc..98e2fae688 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs @@ -15,6 +15,7 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit; [JsonSerializable(typeof(List))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "Dictionary")] +[JsonSerializable(typeof(List>))] [JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)] internal sealed partial class JsonSerializationContext : JsonSerializerContext { diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs index cdb7b72b25..87beb49075 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ToggleSetting.cs @@ -26,15 +26,69 @@ public sealed class ToggleSetting : Setting public override Dictionary ToDictionary() { - return new Dictionary + var items = new List>(); + + if (!string.IsNullOrEmpty(Label)) { - { "type", "Input.Toggle" }, - { "title", Label }, - { "id", Key }, - { "label", Description }, - { "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) }, - { "isRequired", IsRequired }, - { "errorMessage", ErrorMessage }, + 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> + { + new() + { + { "type", "Column" }, + { "width", "20px" }, + { + "items", new List> + { + 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" }, }; } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj index 410cb38697..b3d38a8af1 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj @@ -68,6 +68,34 @@ true false + + + + MultiThreadedDebug + + + + %(IgnoreSpecificDefaultLibraries);libucrtd.lib + %(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf + + + + + + MultiThreaded + + + + %(IgnoreSpecificDefaultLibraries);libucrt.lib + %(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf + + diff --git a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj index 7aab504830..b54ee19e34 100644 --- a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj +++ b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj @@ -26,6 +26,7 @@ _DEBUG;%(PreprocessorDefinitions) Disabled true + MultiThreadedDebug true @@ -36,6 +37,7 @@ NDEBUG;%(PreprocessorDefinitions) MaxSpeed false + MultiThreaded true true diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj index 8a4fa95889..3c124d63e4 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj @@ -30,6 +30,7 @@ _DEBUG;%(PreprocessorDefinitions) Disabled true + MultiThreadedDebug true @@ -40,6 +41,7 @@ NDEBUG;%(PreprocessorDefinitions) MaxSpeed false + MultiThreaded true true diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.dark.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.dark.png index 4228da1e88..7a96d92df1 100644 Binary files a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.dark.png and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.dark.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.light.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.light.png index 6c4dcc5ae5..580cf3f609 100644 Binary files a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.light.png and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Images/oneNote.light.png differ diff --git a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj index 0bfb8d2db1..16272dba69 100644 --- a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj +++ b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj @@ -44,6 +44,7 @@ true NotUsing /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div %(AdditionalOptions) + MultiThreaded stdcpplatest ..\;..\lib\;..\..\..\;%(AdditionalIncludeDirectories) diff --git a/src/settings-ui/Settings.UI.Library/AIProviderConfigurationSnapshot.cs b/src/settings-ui/Settings.UI.Library/AIProviderConfigurationSnapshot.cs deleted file mode 100644 index 456632545f..0000000000 --- a/src/settings-ui/Settings.UI.Library/AIProviderConfigurationSnapshot.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 -{ - /// - /// Stores provider-specific configuration overrides so each AI service can keep distinct settings. - /// - 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; - } -} diff --git a/src/settings-ui/Settings.UI.Library/AIServiceType.cs b/src/settings-ui/Settings.UI.Library/AIServiceType.cs index 5cbd6a855c..27eccff1cf 100644 --- a/src/settings-ui/Settings.UI.Library/AIServiceType.cs +++ b/src/settings-ui/Settings.UI.Library/AIServiceType.cs @@ -17,10 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library FoundryLocal, Mistral, Google, - HuggingFace, AzureAIInference, Ollama, - Anthropic, - AmazonBedrock, } } diff --git a/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs b/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs index 91f035d23a..5b19212eba 100644 --- a/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs +++ b/src/settings-ui/Settings.UI.Library/AIServiceTypeExtensions.cs @@ -29,11 +29,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library "ml" or "windowsml" or "winml" => AIServiceType.ML, "mistral" => AIServiceType.Mistral, "google" or "googleai" or "googlegemini" => AIServiceType.Google, - "huggingface" => AIServiceType.HuggingFace, "azureaiinference" or "azureinference" => AIServiceType.AzureAIInference, "ollama" => AIServiceType.Ollama, - "anthropic" => AIServiceType.Anthropic, - "amazonbedrock" or "bedrock" => AIServiceType.AmazonBedrock, _ => AIServiceType.Unknown, }; } @@ -52,11 +49,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library AIServiceType.ML => "ML", AIServiceType.Mistral => "Mistral", AIServiceType.Google => "Google", - AIServiceType.HuggingFace => "HuggingFace", AIServiceType.AzureAIInference => "AzureAIInference", AIServiceType.Ollama => "Ollama", - AIServiceType.Anthropic => "Anthropic", - AIServiceType.AmazonBedrock => "AmazonBedrock", AIServiceType.Unknown => string.Empty, _ => throw new ArgumentOutOfRangeException(nameof(serviceType), serviceType, "Unsupported AI service type."), }; @@ -76,11 +70,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library AIServiceType.ML => "ml", AIServiceType.Mistral => "mistral", AIServiceType.Google => "google", - AIServiceType.HuggingFace => "huggingface", AIServiceType.AzureAIInference => "azureaiinference", AIServiceType.Ollama => "ollama", - AIServiceType.Anthropic => "anthropic", - AIServiceType.AmazonBedrock => "amazonbedrock", _ => string.Empty, }; } diff --git a/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs index fc9e484808..c967f5d840 100644 --- a/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs +++ b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs @@ -15,31 +15,6 @@ public static class AIServiceTypeRegistry { private static readonly Dictionary MetadataMap = new() { - [AIServiceType.AmazonBedrock] = new AIServiceTypeMetadata - { - ServiceType = AIServiceType.AmazonBedrock, - DisplayName = "Amazon Bedrock", - IsAvailableInUI = false, // Currently disabled in UI - IconPath = "ms-appx:///Assets/Settings/Icons/Models/Bedrock.svg", - IsOnlineService = true, - LegalDescription = "AdvancedPaste_AmazonBedrock_LegalDescription", - TermsLabel = "AdvancedPaste_AmazonBedrock_TermsLabel", - TermsUri = new Uri("https://aws.amazon.com/service-terms/"), - PrivacyLabel = "AdvancedPaste_AmazonBedrock_PrivacyLabel", - PrivacyUri = new Uri("https://aws.amazon.com/privacy/"), - }, - [AIServiceType.Anthropic] = new AIServiceTypeMetadata - { - ServiceType = AIServiceType.Anthropic, - DisplayName = "Anthropic", - IconPath = "ms-appx:///Assets/Settings/Icons/Models/Anthropic.svg", - IsOnlineService = true, - LegalDescription = "AdvancedPaste_Anthropic_LegalDescription", - TermsLabel = "AdvancedPaste_Anthropic_TermsLabel", - TermsUri = new Uri("https://privacy.claude.com/en/collections/10672567-policies-terms-of-service"), - PrivacyLabel = "AdvancedPaste_Anthropic_PrivacyLabel", - PrivacyUri = new Uri("https://privacy.claude.com/en/"), - }, [AIServiceType.AzureAIInference] = new AIServiceTypeMetadata { ServiceType = AIServiceType.AzureAIInference, @@ -85,14 +60,6 @@ public static class AIServiceTypeRegistry PrivacyLabel = "AdvancedPaste_Google_PrivacyLabel", PrivacyUri = new Uri("https://support.google.com/gemini/answer/13594961"), }, - [AIServiceType.HuggingFace] = new AIServiceTypeMetadata - { - ServiceType = AIServiceType.HuggingFace, - DisplayName = "Hugging Face", - IconPath = "ms-appx:///Assets/Settings/Icons/Models/HuggingFace.svg", - IsOnlineService = true, - IsAvailableInUI = false, // Currently disabled in UI - }, [AIServiceType.Mistral] = new AIServiceTypeMetadata { ServiceType = AIServiceType.Mistral, diff --git a/src/settings-ui/Settings.UI.Library/AdvancedPasteMigrationHelper.cs b/src/settings-ui/Settings.UI.Library/AdvancedPasteMigrationHelper.cs new file mode 100644 index 0000000000..b61f83dd3d --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/AdvancedPasteMigrationHelper.cs @@ -0,0 +1,105 @@ +// 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.Collections.ObjectModel; +using System.Linq; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + /// + /// Helper methods for migrating legacy Advanced Paste settings to the updated schema. + /// + public static class AdvancedPasteMigrationHelper + { + /// + /// Ensures an OpenAI provider exists in the configuration, creating one if necessary. + /// + /// The configuration instance. + /// The ensured provider and a flag indicating whether changes were made. + public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration) + { + if (configuration is null) + { + return (null, false); + } + + configuration.Providers ??= new ObservableCollection(); + + const string serviceTypeKey = "OpenAI"; + var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, serviceTypeKey, StringComparison.OrdinalIgnoreCase)); + bool updated = false; + + if (existingProvider is null) + { + existingProvider = CreateProvider(serviceTypeKey); + configuration.Providers.Add(existingProvider); + updated = true; + } + + updated |= EnsureActiveProviderIsValid(configuration, existingProvider); + + return (existingProvider, updated); + } + + /// + /// Creates a provider with default values for the requested service type. + /// + private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey) + { + 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, + IsLocalModel = metadata.IsLocalModel, + }; + + return provider; + } + + private static bool EnsureActiveProviderIsValid(PasteAIConfiguration configuration, PasteAIProviderDefinition preferredProvider = null) + { + if (configuration?.Providers is null || configuration.Providers.Count == 0) + { + if (!string.IsNullOrWhiteSpace(configuration?.ActiveProviderId)) + { + configuration.ActiveProviderId = string.Empty; + return true; + } + + return false; + } + + bool updated = false; + + var activeProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase)); + if (activeProvider is null) + { + activeProvider = preferredProvider ?? configuration.Providers.First(); + configuration.ActiveProviderId = activeProvider.Id; + updated = true; + } + + foreach (var provider in configuration.Providers) + { + bool shouldBeActive = string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase); + if (provider.IsActive != shouldBeActive) + { + provider.IsActive = shouldBeActive; + updated = true; + } + } + + return updated; + } + } +} diff --git a/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs b/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs index 429beca295..b952cdf9f8 100644 --- a/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs +++ b/src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs @@ -29,6 +29,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library IsAIEnabled = false; ShowCustomPreview = true; CloseAfterLosingFocus = false; + EnableClipboardPreview = true; PasteAIConfiguration = new(); CustomModelStoragePath = GetDefaultCustomModelStoragePath(); } @@ -36,29 +37,41 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonConverter(typeof(BoolPropertyJsonConverter))] public bool IsAIEnabled { get; set; } - [JsonExtensionData] - public Dictionary ExtensionData + private bool? _legacyAdvancedAIEnabled; + + [JsonPropertyName("IsAdvancedAIEnabled")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public BoolProperty LegacyAdvancedAIEnabledProperty { - get => _extensionData; + get => null; set { - _extensionData = value; - - if (_extensionData != null && _extensionData.TryGetValue("IsOpenAIEnabled", out var legacyElement) && legacyElement.ValueKind == JsonValueKind.Object && legacyElement.TryGetProperty("value", out var valueElement)) + if (value is not null) { - IsAIEnabled = valueElement.ValueKind switch - { - JsonValueKind.True => true, - JsonValueKind.False => false, - _ => IsAIEnabled, - }; - - _extensionData.Remove("IsOpenAIEnabled"); + LegacyAdvancedAIEnabled = value.Value; } } } - private Dictionary _extensionData; + [JsonIgnore] + public bool? LegacyAdvancedAIEnabled + { + get => _legacyAdvancedAIEnabled; + private set => _legacyAdvancedAIEnabled = value; + } + + public bool TryConsumeLegacyAdvancedAIEnabled(out bool value) + { + if (_legacyAdvancedAIEnabled is bool flag) + { + value = flag; + _legacyAdvancedAIEnabled = null; + return true; + } + + value = default; + return false; + } [JsonConverter(typeof(BoolPropertyJsonConverter))] public bool ShowCustomPreview { get; set; } @@ -66,6 +79,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonConverter(typeof(BoolPropertyJsonConverter))] public bool CloseAfterLosingFocus { get; set; } + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableClipboardPreview { get; set; } + [JsonPropertyName("advanced-paste-ui-hotkey")] public HotkeySettings AdvancedPasteUIShortcut { get; set; } diff --git a/src/settings-ui/Settings.UI.Library/PasteAIConfiguration.cs b/src/settings-ui/Settings.UI.Library/PasteAIConfiguration.cs index 3ed0937280..3308ab535e 100644 --- a/src/settings-ui/Settings.UI.Library/PasteAIConfiguration.cs +++ b/src/settings-ui/Settings.UI.Library/PasteAIConfiguration.cs @@ -20,8 +20,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library { private string _activeProviderId = string.Empty; private ObservableCollection _providers = new(); - private bool _useSharedCredentials = true; - private Dictionary _legacyProviderConfigurations; public event PropertyChangedEventHandler PropertyChanged; @@ -39,21 +37,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library set => SetProperty(ref _providers, value ?? new ObservableCollection()); } - [JsonPropertyName("use-shared-credentials")] - public bool UseSharedCredentials - { - get => _useSharedCredentials; - set => SetProperty(ref _useSharedCredentials, value); - } - - [JsonPropertyName("provider-configurations")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public Dictionary LegacyProviderConfigurations - { - get => _legacyProviderConfigurations; - set => _legacyProviderConfigurations = value; - } - [JsonIgnore] public PasteAIProviderDefinition ActiveProvider { diff --git a/src/settings-ui/Settings.UI.Library/PasteAIProviderDefaults.cs b/src/settings-ui/Settings.UI.Library/PasteAIProviderDefaults.cs new file mode 100644 index 0000000000..1ccfa753fa --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/PasteAIProviderDefaults.cs @@ -0,0 +1,29 @@ +// 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. + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + /// + /// Provides default values for Paste AI provider definitions. + /// + public static class PasteAIProviderDefaults + { + /// + /// Gets the default model name for a given AI service type. + /// + public static string GetDefaultModelName(AIServiceType serviceType) + { + return serviceType switch + { + AIServiceType.OpenAI => "gpt-4o", + AIServiceType.AzureOpenAI => "gpt-4o", + AIServiceType.Mistral => "mistral-large-latest", + AIServiceType.Google => "gemini-1.5-pro", + AIServiceType.AzureAIInference => "gpt-4o-mini", + AIServiceType.Ollama => "llama3", + _ => string.Empty, + }; + } + } +} diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Anthropic.svg b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Anthropic.svg deleted file mode 100644 index f990d4650f..0000000000 --- a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Anthropic.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Bedrock.svg b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Bedrock.svg deleted file mode 100644 index d7a3d800d9..0000000000 --- a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/Bedrock.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/HuggingFace.svg b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/HuggingFace.svg deleted file mode 100644 index 9fe0aff336..0000000000 --- a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/HuggingFace.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Background.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Background.png new file mode 100644 index 0000000000..929a637d34 Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Background.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Hero.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Hero.png new file mode 100644 index 0000000000..a8889dcc05 Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Hero.png differ diff --git a/src/settings-ui/Settings.UI/Converters/StringToDouble.cs b/src/settings-ui/Settings.UI/Converters/StringToDouble.cs new file mode 100644 index 0000000000..fae0618467 --- /dev/null +++ b/src/settings-ui/Settings.UI/Converters/StringToDouble.cs @@ -0,0 +1,33 @@ +// 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.Globalization; +using Microsoft.UI.Xaml.Data; + +namespace Microsoft.PowerToys.Settings.UI.Converters +{ + public partial class StringToDoubleConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is string s && double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out double result)) + { + return result; + } + + return 0.0; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + if (value is double d) + { + return d.ToString(CultureInfo.InvariantCulture); + } + + return "0"; + } + } +} diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj index 057413a408..a6751adb98 100644 --- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj +++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj @@ -28,6 +28,8 @@ + + @@ -71,6 +73,7 @@ + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml index f695301e3a..9574094e77 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml @@ -30,6 +30,24 @@ + + + + + + + + + @@ -56,7 +75,6 @@ - - + - - - + TextWrapping="Wrap" /> + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml index 0424715d8b..33a52c3c49 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml @@ -103,10 +103,7 @@ - + @@ -171,6 +168,9 @@ + + + + PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"> 900 700 @@ -535,46 +534,44 @@ Spacing="16"> - + x:Uid="AdvancedPaste_APIKey" + MinWidth="200" /> - - - - - + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs index 06f901eff5..a9d0c35272 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs @@ -28,7 +28,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views public sealed partial class AdvancedPastePage : NavigablePage, IRefreshablePage, IDisposable { private readonly ObservableCollection _foundryCachedModels = new(); - private readonly ObservableCollection _foundryDownloadableModels = new(); private CancellationTokenSource _foundryModelLoadCts; private bool _suppressFoundrySelectionChanged; private bool _isFoundryLocalAvailable; @@ -37,6 +36,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views private const string AdvancedAISystemPrompt = "You are an agent who is tasked with helping users paste their clipboard data. You have functions available to help you with this task. Call function when necessary to help user finish the transformation task. You never need to ask permission, always try to do as the user asks. The user will only input one message and will not be available for further questions, so try your best. The user will put in a request to format their clipboard data and you will fulfill it. Do not output anything else besides the reformatted clipboard content."; private const string SimpleAISystemPrompt = "You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it. Do not output anything else besides the reformatted clipboard content."; + private static readonly string AdvancedAISystemPromptNormalized = AdvancedAISystemPrompt.Trim(); + private static readonly string SimpleAISystemPromptNormalized = SimpleAISystemPrompt.Trim(); private readonly ObservableCollection _customModelOptions = new(); @@ -62,7 +63,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views if (FoundryLocalPicker is not null) { FoundryLocalPicker.CachedModels = _foundryCachedModels; - FoundryLocalPicker.DownloadableModels = _foundryDownloadableModels; FoundryLocalPicker.SelectionChanged += FoundryLocalPicker_SelectionChanged; FoundryLocalPicker.LoadRequested += FoundryLocalPicker_LoadRequested; } @@ -471,7 +471,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views bool requiresEndpoint = serviceKind is AIServiceType.AzureOpenAI or AIServiceType.AzureAIInference or AIServiceType.Mistral - or AIServiceType.HuggingFace or AIServiceType.Ollama; bool requiresDeployment = serviceKind == AIServiceType.AzureOpenAI; bool requiresApiVersion = serviceKind == AIServiceType.AzureOpenAI; @@ -622,7 +621,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views var cachedModels = cachedModelsEnumerable?.ToList() ?? new List(); - UpdateFoundryCollections(cachedModels, []); + UpdateFoundryCollections(cachedModels); ShowFoundryAvailableState(); RestoreFoundrySelection(cachedModels); } @@ -691,7 +690,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views UpdateFoundrySaveButtonState(); } - private void UpdateFoundryCollections(IReadOnlyCollection cachedModels, IReadOnlyCollection catalogModels) + private void UpdateFoundryCollections(IReadOnlyCollection cachedModels) { _foundryCachedModels.Clear(); @@ -700,20 +699,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views _foundryCachedModels.Add(model); } - var cachedReferences = new HashSet(_foundryCachedModels.Select(m => NormalizeFoundryModelReference(m.Url ?? m.Name)), StringComparer.OrdinalIgnoreCase); - - _foundryDownloadableModels.Clear(); - - foreach (var model in catalogModels.OrderBy(m => m.Name, StringComparer.OrdinalIgnoreCase)) - { - var reference = NormalizeFoundryModelReference(model.Url ?? model.Name); - if (cachedReferences.Contains(reference)) - { - continue; - } - - _foundryDownloadableModels.Add(new FoundryDownloadableModel(model)); - } + var cachedReferences = new HashSet(_foundryCachedModels.Select(m => m.Name), StringComparer.OrdinalIgnoreCase); } private void RestoreFoundrySelection(IReadOnlyCollection cachedModels) @@ -729,9 +715,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views if (!string.IsNullOrWhiteSpace(currentModelReference)) { - var normalizedReference = NormalizeFoundryModelReference(currentModelReference); matchingModel = cachedModels.FirstOrDefault(model => - string.Equals(NormalizeFoundryModelReference(model.Url ?? model.Name), normalizedReference, StringComparison.OrdinalIgnoreCase)); + string.Equals(model.Name, currentModelReference, StringComparison.OrdinalIgnoreCase)); } if (FoundryLocalPicker is null) @@ -761,7 +746,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views { if (ViewModel?.PasteAIProviderDraft is not null) { - ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(matchingModel.Url ?? matchingModel.Name); + ViewModel.PasteAIProviderDraft.ModelName = matchingModel.Name; } if (FoundryLocalPicker is not null) @@ -773,19 +758,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views UpdateFoundrySaveButtonState(); } - private static string NormalizeFoundryModelReference(string modelReference) - { - if (string.IsNullOrWhiteSpace(modelReference)) - { - return string.Empty; - } - - var prefix = FoundryLocalModelProvider.Instance.UrlPrefix; - return modelReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) - ? modelReference - : $"{prefix}{modelReference}"; - } - private void UpdateFoundrySaveButtonState() { if (PasteAIProviderConfigurationDialog is null) @@ -809,7 +781,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views return; } - if (!_isFoundryLocalAvailable || _foundryDownloadableModels.Any(model => model.IsDownloading)) + if (!_isFoundryLocalAvailable) { PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false; return; @@ -830,7 +802,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views { if (ViewModel?.PasteAIProviderDraft is not null) { - ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(selectedModel.Url ?? selectedModel.Name); + ViewModel.PasteAIProviderDraft.ModelName = selectedModel.Name; } if (FoundryLocalPicker is not null) @@ -959,6 +931,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views return; } + NormalizeSystemPrompt(draft); string serviceType = draft.ServiceType ?? "OpenAI"; string apiKey = PasteAIApiKeyPasswordBox.Password; string trimmedApiKey = apiKey?.Trim() ?? string.Empty; @@ -989,22 +962,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views return; } - bool isEmptyOrDefault = string.IsNullOrWhiteSpace(draft.SystemPrompt) || - draft.SystemPrompt.Trim() == AdvancedAISystemPrompt.Trim() || - draft.SystemPrompt.Trim() == SimpleAISystemPrompt.Trim(); - - if (isEmptyOrDefault) - { - if (!draft.EnableAdvancedAI) - { - // Now we'll switch - draft.SystemPrompt = AdvancedAISystemPrompt; - } - else - { - draft.SystemPrompt = SimpleAISystemPrompt; - } - } + NormalizeSystemPrompt(draft); + UpdateSystemPromptPlaceholder(); } private static bool RequiresApiKeyForService(string serviceType) @@ -1028,7 +987,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views AIServiceType.AzureOpenAI => "https://your-resource.openai.azure.com/", AIServiceType.AzureAIInference => "https://{resource-name}.cognitiveservices.azure.com/", AIServiceType.Mistral => "https://api.mistral.ai/v1/", - AIServiceType.HuggingFace => "https://api-inference.huggingface.co/models/", AIServiceType.Ollama => "http://localhost:11434/", _ => "https://your-resource.openai.azure.com/", }; @@ -1106,15 +1064,47 @@ namespace Microsoft.PowerToys.Settings.UI.Views private Visibility GetServicePrivacyVisibility(string serviceType) => HasServicePrivacyLink(serviceType) ? Visibility.Visible : Visibility.Collapsed; - private void UpdateSystemPromptPlaceholder() + private static bool IsPlaceholderSystemPrompt(string prompt) { - var draft = ViewModel?.PasteAIProviderDraft; - if (draft is null || PasteAISystemPromptTextBox is null) + if (string.IsNullOrWhiteSpace(prompt)) + { + return true; + } + + string trimmedPrompt = prompt.Trim(); + return string.Equals(trimmedPrompt, AdvancedAISystemPromptNormalized, StringComparison.Ordinal) + || string.Equals(trimmedPrompt, SimpleAISystemPromptNormalized, StringComparison.Ordinal); + } + + private static void NormalizeSystemPrompt(PasteAIProviderDefinition draft) + { + if (draft is null) { return; } - PasteAISystemPromptTextBox.PlaceholderText = draft.EnableAdvancedAI + if (IsPlaceholderSystemPrompt(draft.SystemPrompt)) + { + draft.SystemPrompt = string.Empty; + } + } + + private void UpdateSystemPromptPlaceholder() + { + var draft = ViewModel?.PasteAIProviderDraft; + if (draft is null) + { + return; + } + + NormalizeSystemPrompt(draft); + if (PasteAISystemPromptTextBox is null) + { + return; + } + + bool useAdvancedPlaceholder = PasteAIEnableAdvancedAICheckBox?.IsOn ?? draft.EnableAdvancedAI; + PasteAISystemPromptTextBox.PlaceholderText = useAdvancedPlaceholder ? AdvancedAISystemPrompt : SimpleAISystemPrompt; } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml index 599f97c5e8..4f60a4a2b8 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml @@ -10,40 +10,200 @@ xmlns:ui="using:CommunityToolkit.WinUI" AutomationProperties.LandmarkType="Main" mc:Ignorable="d"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + - - - - - - + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml.cs index 145fc9b592..f295bdd655 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml.cs @@ -63,12 +63,20 @@ namespace Microsoft.PowerToys.Settings.UI.Views } } - private void CmdPalSettingsDeeplink_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + private void SettingsCard_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { // Launch CmdPal settings window as normal user using explorer string launchPath = "explorer.exe"; string launchArgs = "x-cmdpal://settings"; LaunchApp(launchPath, launchArgs); } + + private void LaunchCard_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + // Launch CmdPal window as normal user using explorer + string launchPath = "explorer.exe"; + string launchArgs = "x-cmdpal"; + LaunchApp(launchPath, launchArgs); + } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml index 7f55e31cd8..ec61a0fcd5 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml @@ -1,24 +1,23 @@  - - + - + + - - - + - - - + - - - - - - - - - - + + Text="{x:Bind ViewModel.LocationPanelLightTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}" + TextAlignment="Left" /> @@ -321,12 +325,12 @@ + Text="{x:Bind ViewModel.LocationPanelDarkTime, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}" + TextAlignment="Left" /> @@ -358,4 +362,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs index 4a8e8905d7..974447a20e 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs @@ -12,88 +12,94 @@ 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; using Microsoft.PowerToys.Settings.UI.ViewModels; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using PowerToys.GPOWrapper; using Settings.UI.Library; -using Settings.UI.Library.Helpers; using Windows.Devices.Geolocation; -using Windows.Services.Maps; namespace Microsoft.PowerToys.Settings.UI.Views { - public sealed partial class LightSwitchPage : Page + public sealed partial class LightSwitchPage : NavigablePage, IRefreshablePage { - private readonly string _appName = "LightSwitch"; - private readonly SettingsUtils _settingsUtils; - private readonly Func _sendConfigMsg = ShellPage.SendDefaultIPCMessage; + private readonly string appName = "LightSwitch"; + private readonly SettingsUtils settingsUtils; + private readonly Func sendConfigMsg = ShellPage.SendDefaultIPCMessage; - private readonly ISettingsRepository _generalSettingsRepository; - private readonly ISettingsRepository _moduleSettingsRepository; + private readonly SettingsRepository generalSettingsRepository; + private readonly SettingsRepository moduleSettingsRepository; - private readonly IFileSystem _fileSystem; - private readonly IFileSystemWatcher _fileSystemWatcher; - private readonly DispatcherQueue _dispatcherQueue; + private readonly IFileSystem fileSystem; + private readonly IFileSystemWatcher fileSystemWatcher; + private readonly DispatcherQueue dispatcherQueue; + private bool suppressViewModelUpdates; private LightSwitchViewModel ViewModel { get; set; } public LightSwitchPage() { - _settingsUtils = new SettingsUtils(); - _sendConfigMsg = ShellPage.SendDefaultIPCMessage; + this.settingsUtils = new SettingsUtils(); + this.sendConfigMsg = ShellPage.SendDefaultIPCMessage; - _generalSettingsRepository = SettingsRepository.GetInstance(_settingsUtils); - _moduleSettingsRepository = SettingsRepository.GetInstance(_settingsUtils); + this.generalSettingsRepository = SettingsRepository.GetInstance(this.settingsUtils); + this.moduleSettingsRepository = SettingsRepository.GetInstance(this.settingsUtils); // Get settings from JSON (or defaults if JSON missing) - var darkSettings = _moduleSettingsRepository.SettingsConfig; + var darkSettings = this.moduleSettingsRepository.SettingsConfig; // Pass them into the ViewModel - ViewModel = new LightSwitchViewModel(darkSettings, ShellPage.SendDefaultIPCMessage); - ViewModel.PropertyChanged += ViewModel_PropertyChanged; + this.ViewModel = new LightSwitchViewModel(darkSettings, this.sendConfigMsg); + this.ViewModel.PropertyChanged += ViewModel_PropertyChanged; - LoadSettings(_generalSettingsRepository, _moduleSettingsRepository); + this.LoadSettings(this.generalSettingsRepository, this.moduleSettingsRepository); - DataContext = ViewModel; + DataContext = this.ViewModel; - var settingsPath = _settingsUtils.GetSettingsFilePath(_appName); + var settingsPath = this.settingsUtils.GetSettingsFilePath(this.appName); - _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - _fileSystem = new FileSystem(); + this.dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + this.fileSystem = new FileSystem(); - _fileSystemWatcher = _fileSystem.FileSystemWatcher.New(); - _fileSystemWatcher.Path = _fileSystem.Path.GetDirectoryName(settingsPath); - _fileSystemWatcher.Filter = _fileSystem.Path.GetFileName(settingsPath); - _fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime; - _fileSystemWatcher.Changed += Settings_Changed; - _fileSystemWatcher.EnableRaisingEvents = true; + this.fileSystemWatcher = this.fileSystem.FileSystemWatcher.New(); + this.fileSystemWatcher.Path = this.fileSystem.Path.GetDirectoryName(settingsPath); + this.fileSystemWatcher.Filter = this.fileSystem.Path.GetFileName(settingsPath); + this.fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime; + this.fileSystemWatcher.Changed += Settings_Changed; + this.fileSystemWatcher.EnableRaisingEvents = true; this.InitializeComponent(); - this.Loaded += LightSwitchPage_Loaded; - this.Loaded += (s, e) => ViewModel.OnPageLoaded(); + Loaded += LightSwitchPage_Loaded; + Loaded += (s, e) => this.ViewModel.OnPageLoaded(); + } + + public void RefreshEnabledState() + { + this.ViewModel.RefreshEnabledState(); } private void LightSwitchPage_Loaded(object sender, RoutedEventArgs e) { - if (ViewModel.SearchLocations.Count == 0) + if (this.ViewModel.SearchLocations.Count == 0) { foreach (var city in SearchLocationLoader.GetAll()) { - ViewModel.SearchLocations.Add(city); + this.ViewModel.SearchLocations.Add(city); } } - ViewModel.InitializeScheduleMode(); + this.ViewModel.InitializeScheduleMode(); } - private async Task GetGeoLocation() + private async void GetGeoLocation_Click(object sender, RoutedEventArgs e) { - SyncButton.IsEnabled = false; - SyncLoader.IsActive = true; - SyncLoader.Visibility = Visibility.Visible; + this.LatitudeBox.IsEnabled = false; + this.LongitudeBox.IsEnabled = false; + this.SyncButton.IsEnabled = false; + this.SyncLoader.IsActive = true; + this.SyncLoader.Visibility = Visibility.Visible; + this.LocationResultPanel.Visibility = Visibility.Collapsed; try { @@ -112,75 +118,110 @@ namespace Microsoft.PowerToys.Settings.UI.Views double latitude = Math.Round(pos.Coordinate.Point.Position.Latitude); double longitude = Math.Round(pos.Coordinate.Point.Position.Longitude); - SunTimes result = SunCalc.CalculateSunriseSunset( - latitude, - longitude, - DateTime.Now.Year, - DateTime.Now.Month, - DateTime.Now.Day); + ViewModel.LocationPanelLatitude = latitude; + ViewModel.LocationPanelLongitude = longitude; - ViewModel.LightTime = (result.SunriseHour * 60) + result.SunriseMinute; - ViewModel.DarkTime = (result.SunsetHour * 60) + result.SunsetMinute; - ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture); - ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture); + var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); + + this.ViewModel.LocationPanelLightTimeMinutes = (result.SunriseHour * 60) + result.SunriseMinute; + this.ViewModel.LocationPanelDarkTimeMinutes = (result.SunsetHour * 60) + result.SunsetMinute; // Since we use this mode, we can remove the selected city data. - ViewModel.SelectedCity = null; - - // CityAutoSuggestBox.Text = string.Empty; - ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}, {ViewModel.Longitude}"; + this.ViewModel.SelectedCity = null; // ViewModel.CityTimesText = $"Sunrise: {result.SunriseHour}:{result.SunriseMinute:D2}\n" + $"Sunset: {result.SunsetHour}:{result.SunsetMinute:D2}"; - SyncButton.IsEnabled = true; - SyncLoader.IsActive = false; - SyncLoader.Visibility = Visibility.Collapsed; - LocationDialog.IsPrimaryButtonEnabled = true; - LocationResultPanel.Visibility = Visibility.Visible; + this.SyncButton.IsEnabled = true; + this.SyncLoader.IsActive = false; + this.SyncLoader.Visibility = Visibility.Collapsed; + this.LocationDialog.IsPrimaryButtonEnabled = true; + this.LatitudeBox.IsEnabled = true; + this.LongitudeBox.IsEnabled = true; + this.LocationResultPanel.Visibility = Visibility.Visible; } catch (Exception ex) { - SyncButton.IsEnabled = true; - SyncLoader.IsActive = false; - System.Diagnostics.Debug.WriteLine("Location error: " + ex.Message); + this.SyncButton.IsEnabled = true; + this.SyncLoader.IsActive = false; + this.SyncLoader.Visibility = Visibility.Collapsed; + this.LocationResultPanel.Visibility = Visibility.Collapsed; + this.LatitudeBox.IsEnabled = true; + this.LongitudeBox.IsEnabled = true; + Logger.LogInfo($"Location error: " + ex.Message); + } + } + + private void LatLonBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) + { + double latitude = this.LatitudeBox.Value; + double longitude = this.LongitudeBox.Value; + + if (double.IsNaN(latitude) || double.IsNaN(longitude) || (latitude == 0 && longitude == 0)) + { + return; + } + + var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); + + this.ViewModel.LocationPanelLightTimeMinutes = (result.SunriseHour * 60) + result.SunriseMinute; + this.ViewModel.LocationPanelDarkTimeMinutes = (result.SunsetHour * 60) + result.SunsetMinute; + + this.LocationResultPanel.Visibility = Visibility.Visible; + if (this.LocationDialog != null) + { + this.LocationDialog.IsPrimaryButtonEnabled = true; } } private void LocationDialog_PrimaryButtonClick(object sender, ContentDialogButtonClickEventArgs args) { - if (ViewModel.ScheduleMode == "SunriseToSunsetUser") + if (double.IsNaN(this.LatitudeBox.Value) || double.IsNaN(this.LongitudeBox.Value)) { - ViewModel.SyncButtonInformation = ViewModel.SelectedCity.City; - } - else if (ViewModel.ScheduleMode == "SunriseToSunsetGeo") - { - ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}, {ViewModel.Longitude}"; + return; } - SunriseModeChartState(); + double latitude = this.LatitudeBox.Value; + double longitude = this.LongitudeBox.Value; + + // need to save the values + this.ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture); + this.ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture); + this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}, {this.ViewModel.Longitude}"; + + var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); + + this.ViewModel.LightTime = (result.SunriseHour * 60) + result.SunriseMinute; + this.ViewModel.DarkTime = (result.SunsetHour * 60) + result.SunsetMinute; + + this.SunriseModeChartState(); } private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { + if (this.suppressViewModelUpdates) + { + return; + } + if (e.PropertyName == "IsEnabled") { - if (ViewModel.IsEnabled != _generalSettingsRepository.SettingsConfig.Enabled.LightSwitch) + if (this.ViewModel.IsEnabled != this.generalSettingsRepository.SettingsConfig.Enabled.LightSwitch) { - _generalSettingsRepository.SettingsConfig.Enabled.LightSwitch = ViewModel.IsEnabled; + this.generalSettingsRepository.SettingsConfig.Enabled.LightSwitch = this.ViewModel.IsEnabled; - var generalSettingsMessage = new OutGoingGeneralSettings(_generalSettingsRepository.SettingsConfig).ToString(); + var generalSettingsMessage = new OutGoingGeneralSettings(this.generalSettingsRepository.SettingsConfig).ToString(); Logger.LogInfo($"Saved general settings from Light Switch page."); - _sendConfigMsg?.Invoke(generalSettingsMessage); + this.sendConfigMsg?.Invoke(generalSettingsMessage); } } else { - if (ViewModel.ModuleSettings != null) + if (this.ViewModel.ModuleSettings != null) { - SndLightSwitchSettings currentSettings = new(_moduleSettingsRepository.SettingsConfig); + SndLightSwitchSettings currentSettings = new(this.moduleSettingsRepository.SettingsConfig); SndModuleSettings csIpcMessage = new(currentSettings); - SndLightSwitchSettings outSettings = new(ViewModel.ModuleSettings); + SndLightSwitchSettings outSettings = new(this.ViewModel.ModuleSettings); SndModuleSettings outIpcMessage = new(outSettings); string csMessage = csIpcMessage.ToJsonString(); @@ -190,13 +231,13 @@ namespace Microsoft.PowerToys.Settings.UI.Views { Logger.LogInfo($"Saved Light Switch settings from Light Switch page."); - _sendConfigMsg?.Invoke(outMessage); + this.sendConfigMsg?.Invoke(outMessage); } } } } - private void LoadSettings(ISettingsRepository generalSettingsRepository, ISettingsRepository moduleSettingsRepository) + private void LoadSettings(SettingsRepository generalSettingsRepository, SettingsRepository moduleSettingsRepository) { if (generalSettingsRepository != null) { @@ -221,8 +262,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views { if (generalSettings != null) { - ViewModel.IsEnabled = generalSettings.Enabled.LightSwitch; - ViewModel.ModuleSettings = (LightSwitchSettings)lightSwitchSettings.Clone(); + this.ViewModel.IsEnabled = generalSettings.Enabled.LightSwitch; + this.ViewModel.ModuleSettings = (LightSwitchSettings)lightSwitchSettings.Clone(); UpdateEnabledState(generalSettings.Enabled.LightSwitch); } @@ -239,10 +280,14 @@ namespace Microsoft.PowerToys.Settings.UI.Views private void Settings_Changed(object sender, FileSystemEventArgs e) { - _dispatcherQueue.TryEnqueue(() => + this.dispatcherQueue.TryEnqueue(() => { - _moduleSettingsRepository.ReloadSettings(); - LoadSettings(_generalSettingsRepository, _moduleSettingsRepository); + this.suppressViewModelUpdates = true; + + this.moduleSettingsRepository.ReloadSettings(); + this.LoadSettings(this.generalSettingsRepository, this.moduleSettingsRepository); + + this.suppressViewModelUpdates = false; }); } @@ -253,20 +298,20 @@ namespace Microsoft.PowerToys.Settings.UI.Views if (enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) { // Get the enabled state from GPO. - ViewModel.IsEnabledGpoConfigured = true; - ViewModel.EnabledGPOConfiguration = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; + this.ViewModel.IsEnabledGpoConfigured = true; + this.ViewModel.EnabledGPOConfiguration = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; } else { - ViewModel.IsEnabled = recommendedState; + this.ViewModel.IsEnabled = recommendedState; } } private async void SyncLocationButton_Click(object sender, RoutedEventArgs e) { - LocationDialog.IsPrimaryButtonEnabled = false; - LocationResultPanel.Visibility = Visibility.Collapsed; - await LocationDialog.ShowAsync(); + this.LocationDialog.IsPrimaryButtonEnabled = false; + this.LocationResultPanel.Visibility = Visibility.Collapsed; + await this.LocationDialog.ShowAsync(); } private void CityAutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) @@ -276,7 +321,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views string query = sender.Text.ToLower(CultureInfo.CurrentCulture); // Filter your cities (assuming ViewModel.Cities is a List) - var filtered = ViewModel.SearchLocations + var filtered = this.ViewModel.SearchLocations .Where(c => (c.City?.Contains(query, StringComparison.CurrentCultureIgnoreCase) ?? false) || (c.Country?.Contains(query, StringComparison.CurrentCultureIgnoreCase) ?? false)) @@ -286,7 +331,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views } } - private void CityAutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + /* private void CityAutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { if (args.SelectedItem is SearchLocation location) { @@ -296,43 +341,38 @@ namespace Microsoft.PowerToys.Settings.UI.Views LocationDialog.IsPrimaryButtonEnabled = true; LocationResultPanel.Visibility = Visibility.Visible; } - } + } */ private void ModeSelector_SelectionChanged(object sender, SelectionChangedEventArgs e) { - switch (ViewModel.ScheduleMode) + switch (this.ViewModel.ScheduleMode) { case "FixedHours": VisualStateManager.GoToState(this, "ManualState", true); - TimelineCard.Visibility = Visibility.Visible; + this.TimelineCard.Visibility = Visibility.Visible; break; case "SunsetToSunrise": VisualStateManager.GoToState(this, "SunsetToSunriseState", true); - SunriseModeChartState(); + this.SunriseModeChartState(); break; default: VisualStateManager.GoToState(this, "OffState", true); - TimelineCard.Visibility = Visibility.Collapsed; + this.TimelineCard.Visibility = Visibility.Collapsed; break; } } - private async void LocationDialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args) - { - await GetGeoLocation(); - } - private void SunriseModeChartState() { - if (ViewModel.Latitude != "0.0" && ViewModel.Longitude != "0.0") + if (this.ViewModel.Latitude != "0.0" && this.ViewModel.Longitude != "0.0") { - TimelineCard.Visibility = Visibility.Visible; - LocationWarningBar.Visibility = Visibility.Collapsed; + this.TimelineCard.Visibility = Visibility.Visible; + this.LocationWarningBar.Visibility = Visibility.Collapsed; } else { - TimelineCard.Visibility = Visibility.Collapsed; - LocationWarningBar.Visibility = Visibility.Visible; + this.TimelineCard.Visibility = Visibility.Collapsed; + this.LocationWarningBar.Visibility = Visibility.Visible; } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml index fc7fb9c39f..60d0f5370d 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml @@ -283,6 +283,7 @@ + - - - - - - + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml index 09a5a13e51..aae80d05e7 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml @@ -285,6 +285,9 @@ AutomationProperties.AutomationId="InputOutputNavItem" Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/InputOutput.png}" SelectsOnInvoked="False"> + + + + Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}"> + + + + - @@ -644,17 +644,14 @@ Please review the placeholder content that represents the final terms and usage Enable OpenAI content moderation - - Enable Advanced AI - - + Use built-in functions to handle complex tasks. Token consumption may increase. Access Clipboard History - Clipboard History shows a list of previously copied items. + View and select previously copied items Actions @@ -724,15 +721,6 @@ Please review the placeholder content that represents the final terms and usage Google Privacy Policy - - Your API key connects directly to Anthropic services. By setting up this provider, you agree to comply with Anthropic's usage policies and data handling practices. - - - Anthropic Terms of Service - - - Anthropic Privacy Policy - Your API key connects directly to Mistral services. By setting up this provider, you agree to comply with Mistral's usage policies and data handling practices. @@ -742,15 +730,6 @@ Please review the placeholder content that represents the final terms and usage Mistral Privacy Policy - - Your API key connects directly to Amazon services. By setting up this provider, you agree to comply with Amazon's usage policies and data handling practices. - - - AWS Service Terms - - - AWS Privacy Notice - Ollama Terms of Service @@ -2725,9 +2704,7 @@ From there, simply click on one of the supported files in the File Explorer and Use a keyboard shortcut to highlight left and right mouse clicks. Mouse as in the hardware peripheral. - - - + Enable CursorWrap @@ -2737,30 +2714,21 @@ From there, simply click on one of the supported files in the File Explorer and Wrap the mouse cursor between monitor edges - - - Activation shortcut - - - Hotkey to toggle cursor wrapping on/off + Activation and behavior Set shortcut - Disable wrapping while dragging - - - + Auto-activate on startup Automatically activate on utility startup - - + Mouse Pointer Crosshairs Mouse as in the hardware peripheral. @@ -4114,11 +4082,8 @@ Activate by holding the key for the character you want to add an accent to, then Preview the output of AI formats and Image to text before pasting - - Advanced AI - - - Supports advanced workflows by chaining transformations and working with files and images. May use additional API credits. + + Enable Advanced AI Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format. @@ -4660,9 +4625,13 @@ Activate by holding the key for the character you want to add an accent to, then If you do not have credits you will see an 'API key quota exceeded' error - Automatically close the Advanced Paste window after it loses focus + Automatically close the window after it loses focus Advanced Paste is a product name, do not loc + + Show clipboard preview + Enables display of clipboard contents preview in the Advanced Paste window + The Command Not Found module is disabled by your organization. "Command Not Found" is a product name @@ -5220,25 +5189,12 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m System Tools - - Command Palette - - - A better quick launcher - - - Open Command Palette - Enable Command Palette - "Command Palette" is the name of the utility. - - - A fully extensible quick launcher with a richer display and additional capabilities without sacrificing performance. + Command Palette is a product name, do not loc - Learn more about Command Palette - Command Palette is a product name, do not loc + Learn more Command Palette @@ -5246,11 +5202,11 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m A fully extensible quick launcher with a richer display and additional capabilities without sacrificing performance. - "Command Palette" is a product name + Command Palette is a product name, do not loc Command Palette - "Command Palette" is a product name + Command Palette is a product name, do not loc and start typing! @@ -5283,14 +5239,8 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Retry - - Activation - - - Activation shortcut - - - Open Command Palette settings to customize the activation shortcut + + Settings chroma (CIE LCh) @@ -5513,16 +5463,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Select a location - Select + Save Cancel - - Get current location - - To calculate the sunrise and sunset, Light Switch needs a location. + Detect your location automatically or enter it manually to calculate sunrise and sunset times. Sunrise @@ -5530,8 +5477,17 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Sunset - - Location + + Latitude + + + Longitude + + + Detect location + + + Detect location Sunrise @@ -5615,7 +5571,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Shortcut conflicts - + No conflicts found @@ -5726,4 +5682,133 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Backup count + + Set Location + + + Open Foundry Local model list + Do not localize "Foundry Local", it's a product name + + + Run Foundry Local to download or add a local model + Do not localize "Foundry Local", it's a product name + + + No models downloaded + + + Loading Foundry Local status.. + Do not localize "Foundry Local", it's a product name + + + Foundry Local model + Do not localize "Foundry Local", it's a product name + + + Use the Foundry Local CLI to download models that run locally on-device. They'll appear here. + Do not localize "Foundry Local", it's a product name + + + Refresh model list + + + Foundry Local is not available on this device yet. + Do not localize "Foundry Local", it's a product name + + + Start the Foundry Local service before returning to PowerToys. + + + Follow the Foundry Local CLI guide + Do not localize "Foundry Local", it's a product name + + + Model providers + + + Add online or local models + + + Edit + + + Remove + + + Model name + + + Endpoint URL + + + API key + + + Enter API key + + + API version + + + Deployment name + + + System prompt + + + Save + + + Cancel + + + Display a preview of the current clipboard content + + + Learn more + + + Foundry Local is still in public preview + Do not loc "Foundry Local" + + + Configure the activation shortcut, extensions, behavior and much more + + + Open Command Palette + Command Palette is a product name, do not loc + + + A better quick launcher + + + Find files, launch apps, and do so much more with the most extensible quick launcher. + + + Command Palette + Command Palette is a product name, do not loc + + + Open Command Palette + Command Palette is a product name, do not loc + + + Powerful extensions help you do more + + + Extensible + + + Find files and launch apps in an instant + + + Fast + + + Beautiful + + + A modern UI built with Fluent Design + Fluent Design is a product name, do not loc + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs index 5483c39ddb..408b5fcae8 100644 --- a/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs @@ -11,10 +11,8 @@ using System.Globalization; using System.IO; using System.IO.Abstractions; using System.Linq; -using System.Reflection; using System.Runtime.Versioning; using System.Text.Json; -using System.Text.Json.Serialization; using global::PowerToys.GPOWrapper; using Microsoft.PowerToys.Settings.UI.Helpers; @@ -58,6 +56,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private Func SendConfigMSG { get; } + private static readonly HashSet CustomActionNonPersistedProperties = new(StringComparer.Ordinal) + { + nameof(AdvancedPasteCustomAction.CanMoveUp), + nameof(AdvancedPasteCustomAction.CanMoveDown), + nameof(AdvancedPasteCustomAction.IsValid), + nameof(AdvancedPasteCustomAction.HasConflict), + nameof(AdvancedPasteCustomAction.Tooltip), + nameof(AdvancedPasteCustomAction.SubActions), + }; + public AdvancedPasteViewModel( ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, @@ -166,19 +174,86 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private void MigrateLegacyAIEnablement() { - if (_advancedPasteSettings.Properties.IsAIEnabled || IsOnlineAIModelsDisallowedByGPO) + var properties = _advancedPasteSettings?.Properties; + if (properties is null) { return; } - if (!LegacyOpenAIKeyExists()) + bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag); + bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag; + + if (IsOnlineAIModelsDisallowedByGPO) { + if (legacyAdvancedAIConsumed) + { + SaveAndNotifySettings(); + } + return; } - _advancedPasteSettings.Properties.IsAIEnabled = true; - SaveAndNotifySettings(); - OnPropertyChanged(nameof(IsAIEnabled)); + PasswordCredential legacyCredential = TryGetLegacyOpenAICredential(); + + if (legacyCredential is null) + { + if (legacyAdvancedAIConsumed) + { + SaveAndNotifySettings(); + } + + return; + } + + var configuration = properties.PasteAIConfiguration; + if (configuration is null) + { + configuration = new PasteAIConfiguration(); + properties.PasteAIConfiguration = configuration; + } + + bool configurationUpdated = false; + + var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration); + PasteAIProviderDefinition openAIProvider = ensureResult.Provider; + configurationUpdated |= ensureResult.Updated; + + if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled) + { + openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled; + configurationUpdated = true; + } + + if (legacyCredential is not null && openAIProvider is not null) + { + SavePasteAIApiKey(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password); + RemoveLegacyOpenAICredential(); + } + + const bool shouldEnableAI = true; + bool enabledChanged = false; + if (properties.IsAIEnabled != shouldEnableAI) + { + properties.IsAIEnabled = shouldEnableAI; + enabledChanged = true; + } + + bool shouldPersist = configurationUpdated || enabledChanged || legacyAdvancedAIConsumed; + + if (shouldPersist) + { + SaveAndNotifySettings(); + + if (configurationUpdated) + { + OnPropertyChanged(nameof(PasteAIConfiguration)); + } + + if (enabledChanged) + { + OnPropertyChanged(nameof(IsAIEnabled)); + } + } } public bool IsEnabled @@ -223,34 +298,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels public bool IsAIEnabled => _advancedPasteSettings.Properties.IsAIEnabled && !IsOnlineAIModelsDisallowedByGPO; - private bool LegacyOpenAIKeyExists() + private PasswordCredential TryGetLegacyOpenAICredential() { try { PasswordVault vault = new(); - - // return vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey") is not null; - var legacyOpenAIKey = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); - if (legacyOpenAIKey != null) - { - string credentialResource = GetAICredentialResource("OpenAI"); - var targetProvider = PasteAIConfiguration?.ActiveProvider ?? PasteAIConfiguration?.Providers?.FirstOrDefault(); - string providerId = targetProvider?.Id ?? string.Empty; - string serviceType = targetProvider?.ServiceType ?? "OpenAI"; - string credentialUserName = GetPasteAICredentialUserName(providerId, serviceType); - PasswordCredential cred = new(credentialResource, credentialUserName, legacyOpenAIKey.Password); - vault.Add(cred); - - // delete old key - TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); - return true; - } - - return false; + var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); + credential?.RetrievePassword(); + return credential; + } + catch (Exception) + { + return null; + } + } + + private void RemoveLegacyOpenAICredential() + { + try + { + PasswordVault vault = new(); + TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); } catch (Exception) { - return false; } } @@ -491,6 +562,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool EnableClipboardPreview + { + get => _advancedPasteSettings.Properties.EnableClipboardPreview; + set + { + if (value != _advancedPasteSettings.Properties.EnableClipboardPreview) + { + _advancedPasteSettings.Properties.EnableClipboardPreview = value; + NotifySettingsChanged(); + } + } + } + public bool IsConflictingCopyShortcut => _customActions.Select(customAction => customAction.Shortcut) .Concat([PasteAsPlainTextShortcut, AdvancedPasteUIShortcut, PasteAsMarkdownShortcut, PasteAsJsonShortcut]) @@ -531,7 +615,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels var provider = new PasteAIProviderDefinition { ServiceType = persistedServiceType, - ModelName = GetDefaultModelName(normalizedServiceType), + ModelName = PasteAIProviderDefaults.GetDefaultModelName(normalizedServiceType), EndpointUrl = string.Empty, ApiVersion = string.Empty, DeploymentName = string.Empty, @@ -571,22 +655,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels return serviceTypeKind; } - private static string GetDefaultModelName(AIServiceType serviceType) - { - return serviceType switch - { - AIServiceType.OpenAI => "gpt-4", - AIServiceType.AzureOpenAI => "gpt-4", - AIServiceType.Mistral => "mistral-large-latest", - AIServiceType.Google => "gemini-1.5-pro", - AIServiceType.AzureAIInference => "gpt-4o-mini", - AIServiceType.Ollama => "llama3", - AIServiceType.Anthropic => "claude-3-5-sonnet", - AIServiceType.AmazonBedrock => "anthropic.claude-3-haiku", - _ => string.Empty, - }; - } - public bool IsServiceTypeAllowedByGPO(AIServiceType serviceType) { var metadata = AIServiceTypeRegistry.GetMetadata(serviceType); @@ -609,7 +677,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels AIServiceType.AzureAIInference => GPOWrapper.GetAllowedAdvancedPasteAzureAIInferenceValue(), AIServiceType.Mistral => GPOWrapper.GetAllowedAdvancedPasteMistralValue(), AIServiceType.Google => GPOWrapper.GetAllowedAdvancedPasteGoogleValue(), - AIServiceType.Anthropic => GPOWrapper.GetAllowedAdvancedPasteAnthropicValue(), _ => GpoRuleConfigured.Unavailable, }; @@ -864,9 +931,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels "azureaiinference" => "https://azure.microsoft.com/products/ai-services/ai-inference", "mistral" => "https://console.mistral.ai/account/api-keys", "google" => "https://ai.google.dev/", - "huggingface" => "https://huggingface.co/settings/tokens", - "anthropic" => "https://console.anthropic.com/account/keys", - "amazonbedrock" => "https://aws.amazon.com/bedrock/", "ollama" => "https://ollama.com/", _ => "https://platform.openai.com/api-keys", }; @@ -1020,7 +1084,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private void OnCustomActionPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (typeof(AdvancedPasteCustomAction).GetProperty(e.PropertyName).GetCustomAttribute() == null) + if (!string.IsNullOrEmpty(e.PropertyName) && !CustomActionNonPersistedProperties.Contains(e.PropertyName)) { SaveCustomActions(); } @@ -1175,6 +1239,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels EnsureDirectoryExists(target.CustomModelStoragePath); OnPropertyChanged(nameof(CustomModelStoragePath)); } + + if (target.EnableClipboardPreview != source.EnableClipboardPreview) + { + target.EnableClipboardPreview = source.EnableClipboardPreview; + OnPropertyChanged(nameof(EnableClipboardPreview)); + } var incomingConfig = source.PasteAIConfiguration ?? new PasteAIConfiguration(); if (ShouldReplacePasteAIConfiguration(target.PasteAIConfiguration, incomingConfig)) @@ -1200,11 +1270,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels return true; } - if (current.UseSharedCredentials != incoming.UseSharedCredentials) - { - return true; - } - var currentProviders = current.Providers ?? new ObservableCollection(); var incomingProviders = incoming.Providers ?? new ObservableCollection(); @@ -1360,8 +1425,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels return; } - if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal) - || string.Equals(e.PropertyName, nameof(PasteAIConfiguration.UseSharedCredentials), StringComparison.Ordinal)) + if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal)) { SaveAndNotifySettings(); } @@ -1377,6 +1441,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } pasteConfig.Providers ??= new ObservableCollection(); + SubscribeToPasteAIProviders(pasteConfig); } diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs index 15a50b5dbd..56437cd9f3 100644 --- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs @@ -145,6 +145,7 @@ 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, DashboardModuleItems = GetModuleItems(moduleType), }; newItem.EnabledChangedCallback = EnabledChangedOnUI; diff --git a/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs index 3f9ea48c18..621fa91d43 100644 --- a/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs @@ -407,6 +407,71 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + private double _locationPanelLatitude; + private double _locationPanelLongitude; + + public double LocationPanelLatitude + { + get => _locationPanelLatitude; + set + { + if (_locationPanelLatitude != value) + { + _locationPanelLatitude = value; + NotifyPropertyChanged(); + NotifyPropertyChanged(nameof(LocationPanelLightTime)); + } + } + } + + public double LocationPanelLongitude + { + get => _locationPanelLongitude; + set + { + if (_locationPanelLongitude != value) + { + _locationPanelLongitude = value; + NotifyPropertyChanged(); + } + } + } + + private int _locationPanelLightTime; + private int _locationPanelDarkTime; + + public int LocationPanelLightTimeMinutes + { + get => _locationPanelLightTime; + set + { + if (_locationPanelLightTime != value) + { + _locationPanelLightTime = value; + NotifyPropertyChanged(); + NotifyPropertyChanged(nameof(LocationPanelLightTime)); + } + } + } + + public int LocationPanelDarkTimeMinutes + { + get => _locationPanelDarkTime; + set + { + if (_locationPanelDarkTime != value) + { + _locationPanelDarkTime = value; + NotifyPropertyChanged(); + NotifyPropertyChanged(nameof(LocationPanelDarkTime)); + } + } + } + + public TimeSpan LocationPanelLightTime => TimeSpan.FromMinutes(_locationPanelLightTime); + + public TimeSpan LocationPanelDarkTime => TimeSpan.FromMinutes(_locationPanelDarkTime); + public HotkeySettings ToggleThemeActivationShortcut { get => ModuleSettings.Properties.ToggleThemeHotkey.Value; @@ -497,7 +562,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels SyncButtonInformation = SelectedCity != null ? SelectedCity.City - : $"{Latitude},{Longitude}"; + : $"{Latitude}°,{Longitude}°"; double lat = double.Parse(ModuleSettings.Properties.Latitude.Value, CultureInfo.InvariantCulture); double lon = double.Parse(ModuleSettings.Properties.Longitude.Value, CultureInfo.InvariantCulture); diff --git a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs index 518b2a6fa4..27695d1037 100644 --- a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs @@ -1000,6 +1000,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels GeneralSettingsConfig.Enabled.CursorWrap = value; OnPropertyChanged(nameof(IsCursorWrapEnabled)); + // Auto-enable the AutoActivate setting when CursorWrap is enabled + // This ensures cursor wrapping is active immediately after enabling + if (value && !_cursorWrapAutoActivate) + { + CursorWrapAutoActivate = true; + } + OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); SendConfigMSG(outgoing.ToString()); diff --git a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs index 2d93deef81..e1704e16fb 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs @@ -24,6 +24,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { public class ZoomItViewModel : Observable { + private const string FormatGif = "GIF"; + private const string FormatMp4 = "MP4"; + private ISettingsUtils SettingsUtils { get; set; } private GeneralSettings GeneralSettingsConfig { get; set; } @@ -656,12 +659,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { get { - if (_zoomItSettings.Properties.RecordFormat.Value == "GIF") + if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif) { return 0; } - if (_zoomItSettings.Properties.RecordFormat.Value == "MP4") + if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4) { return 1; } @@ -672,19 +675,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels set { int format = 0; - if (_zoomItSettings.Properties.RecordFormat.Value == "GIF") + if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif) { format = 0; } - if (_zoomItSettings.Properties.RecordFormat.Value == "MP4") + if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4) { format = 1; } if (format != value) { - _zoomItSettings.Properties.RecordFormat.Value = value == 0 ? "GIF" : "MP4"; + _zoomItSettings.Properties.RecordFormat.Value = value == 0 ? FormatGif : FormatMp4; OnPropertyChanged(nameof(RecordFormatIndex)); NotifySettingsChanged(); diff --git a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj index 734146a663..f56b2646b4 100644 --- a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj +++ b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj @@ -30,6 +30,7 @@ NotUsing ../../../src/ + true Console @@ -37,10 +38,6 @@ - - - 4706;26451;4267;4244;%(DisableSpecificWarnings) - @@ -61,8 +58,6 @@ - - diff --git a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters index 0723bd31ac..f8117733d9 100644 --- a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters +++ b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters @@ -8,7 +8,6 @@ ZipTools - @@ -28,8 +27,6 @@ ZipTools - - diff --git a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp index 9ef9505fa5..edd48839d2 100644 --- a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp +++ b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp @@ -92,7 +92,6 @@ void ReportGPOValues(const std::filesystem::path &tmpDir) report << "getAllowedAdvancedPasteAzureAIInferenceValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteAzureAIInferenceValue()) << std::endl; report << "getAllowedAdvancedPasteMistralValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteMistralValue()) << std::endl; report << "getAllowedAdvancedPasteGoogleValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteGoogleValue()) << std::endl; - report << "getAllowedAdvancedPasteAnthropicValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteAnthropicValue()) << std::endl; report << "getAllowedAdvancedPasteOllamaValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteOllamaValue()) << std::endl; report << "getAllowedAdvancedPasteFoundryLocalValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteFoundryLocalValue()) << std::endl; report << "getConfiguredMwbClipboardSharingEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue()) << std::endl; diff --git a/tools/BugReportTool/BugReportTool/ZipTools/zipfolder.cpp b/tools/BugReportTool/BugReportTool/ZipTools/zipfolder.cpp index 6b5f4f0180..476707bd67 100644 --- a/tools/BugReportTool/BugReportTool/ZipTools/zipfolder.cpp +++ b/tools/BugReportTool/BugReportTool/ZipTools/zipfolder.cpp @@ -1,50 +1,53 @@ #include "ZipFolder.h" -#include "..\..\..\..\deps\cziplib\src\zip.h" #include +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include + void ZipFolder(std::filesystem::path zipPath, std::filesystem::path folderPath) { - std::string reportFilename{ "PowerToysReport_" }; - reportFilename += timeutil::format_as_local("%F-%H-%M-%S", timeutil::now()); - reportFilename += ".zip"; + const auto reportFilename{ + std::format("PowerToysReport_{0}.zip", + timeutil::format_as_local("%F-%H-%M-%S", timeutil::now())) + }; + const auto finalReportFullPath{ zipPath / reportFilename }; - auto tmpZipPath = std::filesystem::temp_directory_path(); - tmpZipPath /= reportFilename; + const auto tempReportFilename{ reportFilename + ".tmp" }; + const auto tempReportFullPath{ zipPath / tempReportFilename }; - struct zip_t* zip = zip_open(tmpZipPath.string().c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w'); - if (!zip) + // tar -c --format=zip -f "ReportFile.zip" * + const auto executable{ wil::ExpandEnvironmentStringsW(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)) { printf("Cannot open zip."); throw -1; } - 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); - } - } + WaitForSingleObject(pi.hProcess, INFINITE); - zip_close(zip); - - std::error_code err; - std::filesystem::copy(tmpZipPath, zipPath, err); + std::error_code err{}; + std::filesystem::rename(tempReportFullPath, finalReportFullPath, err); if (err.value() != 0) { - wprintf_s(L"Failed to copy %s. Error code: %d\n", tmpZipPath.c_str(), err.value()); + wprintf_s(L"Failed to rename %s. Error code: %d\n", tempReportFullPath.native().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()); - } -} \ No newline at end of file +} diff --git a/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj b/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj index 39c656a6cc..028007de67 100644 --- a/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj +++ b/tools/project_template/ModuleTemplate/ModuleTemplate.vcxproj @@ -46,6 +46,7 @@ true _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true + MultiThreadedDebug stdcpplatest @@ -63,6 +64,7 @@ true NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true + MultiThreaded stdcpplatest diff --git a/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj b/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj index a1ef0522aa..297516b0d5 100644 --- a/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj +++ b/tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj @@ -47,6 +47,7 @@ Disabled true _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + MultiThreadedDebug stdcpplatest @@ -63,6 +64,7 @@ true true NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + MultiThreaded stdcpplatest