mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-02 10:26:22 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9081bc7438 | ||
|
|
7307242396 | ||
|
|
630ed29700 | ||
|
|
41b0b850e1 | ||
|
|
96dc200324 | ||
|
|
9597f87dc4 | ||
|
|
7572586b4c |
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@@ -1283,7 +1283,7 @@ rectp
|
||||
RECTSOURCE
|
||||
recyclebin
|
||||
Redist
|
||||
Reencode
|
||||
reencode
|
||||
reencoded
|
||||
REFCLSID
|
||||
REFGUID
|
||||
|
||||
@@ -128,7 +128,6 @@
|
||||
"PowerToys.KeyboardManager.dll",
|
||||
"KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe",
|
||||
"KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe",
|
||||
"PowerToys.KeyboardManagerEditorLibraryWrapper.dll",
|
||||
|
||||
"PowerToys.Launcher.dll",
|
||||
"PowerToys.PowerLauncher.dll",
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
trigger: none
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: "0 0 * * *" # every day at midnight
|
||||
displayName: "Daily midnight Build"
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
always: false # only run if there's code changes!
|
||||
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
|
||||
@@ -15,7 +8,6 @@ parameters:
|
||||
type: object
|
||||
default:
|
||||
- x64
|
||||
- arm64
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
displayName: "Enable MSBuild Caching"
|
||||
@@ -28,15 +20,6 @@ parameters:
|
||||
type: boolean
|
||||
displayName: "Build Using Visual Studio Preview"
|
||||
default: false
|
||||
- name: useLatestWinAppSDK
|
||||
type: boolean
|
||||
default: true
|
||||
- name: winAppSDKVersionNumber
|
||||
type: string
|
||||
default: 1.6
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
extends:
|
||||
template: templates/pipeline-ci-build.yml
|
||||
@@ -45,6 +28,3 @@ extends:
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
runTests: ${{ parameters.runTests }}
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
|
||||
@@ -43,43 +43,11 @@ stages:
|
||||
- template: job-ci-precheck.yml
|
||||
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
- stage: Build_${{ platform }}
|
||||
displayName: Build ${{ platform }}
|
||||
${{ if ne(variables['Build.Reason'], 'Manual') }}:
|
||||
dependsOn: [Precheck]
|
||||
condition: and(succeeded(), ne(dependencies.Precheck.outputs['Precheck.verifyBuildRequest.skipBuild'], 'Yes'))
|
||||
${{ else }}:
|
||||
dependsOn: []
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
jobs:
|
||||
- template: job-build-project.yml
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
pool:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ platform }}
|
||||
buildConfigurations: [Release]
|
||||
enablePackageCaching: true
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
runTests: ${{ parameters.runTests }}
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
|
||||
- ${{ if and(eq(parameters.runTests, true), not(and(eq(platform, 'arm64'), eq(variables['System.PullRequest.IsFork'], true)))) }}:
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
dependsOn:
|
||||
- Build_${{platform}}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
|
||||
@@ -24,10 +24,18 @@ steps:
|
||||
- pwsh: |
|
||||
$azureCliPath = "$(Build.ArtifactStagingDirectory)\AzureCLI\bin"
|
||||
$env:Path = "$azureCliPath;" + $env:Path
|
||||
Write-Host "Add azure-devops..."
|
||||
az extension add -n azure-devops
|
||||
Write-Host "Configuring Azure DevOps defaults..."
|
||||
az devops configure --defaults organization='$(System.TeamFoundationCollectionUri)' project='$(System.TeamProject)' --use-git-aliases true
|
||||
Write-Host "check permission"
|
||||
az pipelines list --org "$(System.TeamFoundationCollectionUri)" --project "$(System.TeamProject)" --output table
|
||||
Write-Host "Downloading artifacts..."
|
||||
az pipelines runs artifact download --artifact-name ${{parameters.artifactName}} --path "$(Pipeline.Workspace)/${{parameters.artifactName}}" --run-id $(Build.BuildId) --debug
|
||||
if ($env:AZURE_DEVOPS_EXT_PAT -eq $null -or $env:AZURE_DEVOPS_EXT_PAT -eq "") {
|
||||
Write-Host "Error: AZURE_DEVOPS_EXT_PAT is not set."
|
||||
exit 1
|
||||
}
|
||||
az pipelines runs artifact download --artifact-name ${{parameters.artifactName}} --path "$(Pipeline.Workspace)/${{parameters.artifactName}}" --run-id 116384714 --debug
|
||||
displayName: 'Download artifacts with Azure CLI'
|
||||
env:
|
||||
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
|
||||
|
||||
@@ -15,8 +15,8 @@ Param(
|
||||
$referencedFileVersionsPerDll = @{}
|
||||
$totalFailures = 0
|
||||
|
||||
Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude *UITest*,MouseJump.Common.UnitTests*,*.FuzzTests* | ForEach-Object {
|
||||
# Temporarily exclude All UI-Test, Fuzzer-Test projects because of Appium.WebDriver dependencies
|
||||
Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude UITests-FancyZones*,MouseJump.Common.UnitTests*,*.FuzzTests* | ForEach-Object {
|
||||
# Temporarily exclude FancyZones UI tests because of Appium.WebDriver dependencies
|
||||
$depsJsonFullFileName = $_.FullName
|
||||
$depsJsonFileName = $_.Name
|
||||
$depsJson = Get-Content $depsJsonFullFileName | ConvertFrom-Json
|
||||
|
||||
@@ -644,8 +644,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibrar
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.FuzzTests", "src\modules\Hosts\Hosts.FuzzTests\Hosts.FuzzTests.csproj", "{EBED240C-8702-452D-B764-6DB9DA9179AF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.UITests", "src\modules\Hosts\Hosts.UITests\Hosts.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -2254,14 +2252,20 @@ Global
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.ActiveCfg = Release|x64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.Build.0 = Release|x64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.ActiveCfg = Release|x64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.Build.0 = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.Build.0 = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x86.Build.0 = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.ActiveCfg = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.Build.0 = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x86.ActiveCfg = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x86.Build.0 = Release|x64
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2286,14 +2290,6 @@ Global
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|x64.ActiveCfg = Release|x64
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|x64.Build.0 = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.ActiveCfg = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -2533,7 +2529,6 @@ Global
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
|
||||
{4382A954-179A-4078-92AF-715187DFFF50} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<UseWPF>true</UseWPF>
|
||||
|
||||
@@ -91,20 +91,20 @@ namespace Common.UI
|
||||
{
|
||||
try
|
||||
{
|
||||
var directoryPath = System.AppContext.BaseDirectory;
|
||||
var assemblyPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
var fullPath = new DirectoryInfo(assemblyPath).FullName;
|
||||
if (mainExecutableIsOnTheParentFolder)
|
||||
{
|
||||
// Need to go into parent folder for PowerToys.exe. Likely a WinUI3 App SDK application.
|
||||
directoryPath = Path.Combine(directoryPath, "..");
|
||||
directoryPath = Path.Combine(directoryPath, "PowerToys.exe");
|
||||
fullPath = fullPath + "\\..\\PowerToys.exe";
|
||||
}
|
||||
else
|
||||
{
|
||||
// PowerToys.exe is in the same path as the application.
|
||||
directoryPath = Path.Combine(directoryPath, "PowerToys.exe");
|
||||
fullPath = fullPath + "\\PowerToys.exe";
|
||||
}
|
||||
|
||||
Process.Start(new ProcessStartInfo(directoryPath) { Arguments = "--open-settings=" + SettingsWindowNameToString(window) });
|
||||
Process.Start(new ProcessStartInfo(fullPath) { Arguments = "--open-settings=" + SettingsWindowNameToString(window) });
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using Microsoft.Win32;
|
||||
|
||||
namespace Common.UI
|
||||
{
|
||||
public partial class ThemeManager : IDisposable
|
||||
public class ThemeManager : IDisposable
|
||||
{
|
||||
private readonly Application _app;
|
||||
private const string LightTheme = "Light.Accent1";
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ManagedCommon.Serialization;
|
||||
|
||||
namespace ManagedCommon
|
||||
{
|
||||
@@ -36,7 +35,7 @@ namespace ManagedCommon
|
||||
inputStream.Close();
|
||||
reader.Dispose();
|
||||
|
||||
return JsonSerializer.Deserialize<OutGoingLanguageSettings>(data, SourceGenerationContext.Default.OutGoingLanguageSettings).LanguageTag;
|
||||
return JsonSerializer.Deserialize<OutGoingLanguageSettings>(data).LanguageTag;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -15,23 +15,15 @@ namespace ManagedCommon
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
|
||||
private static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location).ProductVersion;
|
||||
|
||||
private static readonly string Error = "Error";
|
||||
private static readonly string Warning = "Warning";
|
||||
private static readonly string Info = "Info";
|
||||
private static readonly string Debug = "Debug";
|
||||
private static readonly string TraceFlag = "Trace";
|
||||
|
||||
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
/*
|
||||
* Please pay more attention!
|
||||
* If you want to publish it with Native AOT enabled (or publish as a single file).
|
||||
* You need to find another way to remove Assembly.Location usage.
|
||||
*/
|
||||
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file
|
||||
private static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location).ProductVersion;
|
||||
#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the logger and sets the path for logging.
|
||||
/// </summary>
|
||||
@@ -61,16 +53,18 @@ namespace ManagedCommon
|
||||
Trace.AutoFlush = true;
|
||||
}
|
||||
|
||||
public static void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void LogError(string message)
|
||||
{
|
||||
Log(message, Error, memberName, sourceFilePath, sourceLineNumber);
|
||||
Log(message, Error);
|
||||
}
|
||||
|
||||
public static void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void LogError(string message, Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
{
|
||||
Log(message, Error, memberName, sourceFilePath, sourceLineNumber);
|
||||
Log(message, Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -89,33 +83,38 @@ namespace ManagedCommon
|
||||
"Stack trace: " + Environment.NewLine +
|
||||
ex.StackTrace;
|
||||
|
||||
Log(exMessage, Error, memberName, sourceFilePath, sourceLineNumber);
|
||||
Log(exMessage, Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void LogWarning(string message)
|
||||
{
|
||||
Log(message, Warning, memberName, sourceFilePath, sourceLineNumber);
|
||||
Log(message, Warning);
|
||||
}
|
||||
|
||||
public static void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void LogInfo(string message)
|
||||
{
|
||||
Log(message, Info, memberName, sourceFilePath, sourceLineNumber);
|
||||
Log(message, Info);
|
||||
}
|
||||
|
||||
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void LogDebug(string message)
|
||||
{
|
||||
Log(message, Debug, memberName, sourceFilePath, sourceLineNumber);
|
||||
Log(message, Debug);
|
||||
}
|
||||
|
||||
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void LogTrace()
|
||||
{
|
||||
Log(string.Empty, TraceFlag, memberName, sourceFilePath, sourceLineNumber);
|
||||
Log(string.Empty, TraceFlag);
|
||||
}
|
||||
|
||||
private static void Log(string message, string type, string memberName, string sourceFilePath, int sourceLineNumber)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void Log(string message, string type)
|
||||
{
|
||||
Trace.WriteLine("[" + DateTime.Now.TimeOfDay + "] [" + type + "] " + GetCallerInfo(memberName, sourceFilePath, sourceLineNumber));
|
||||
Trace.WriteLine("[" + DateTime.Now.TimeOfDay + "] [" + type + "] " + GetCallerInfo());
|
||||
Trace.Indent();
|
||||
if (message != string.Empty)
|
||||
{
|
||||
@@ -125,27 +124,49 @@ namespace ManagedCommon
|
||||
Trace.Unindent();
|
||||
}
|
||||
|
||||
private static string GetCallerInfo(string memberName, string sourceFilePath, int sourceLineNumber)
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static string GetCallerInfo()
|
||||
{
|
||||
string callerFileName = "Unknown";
|
||||
StackTrace stackTrace = new();
|
||||
|
||||
var callerMethod = GetCallerMethod(stackTrace);
|
||||
|
||||
return $"{callerMethod?.DeclaringType?.Name}::{callerMethod.Name}";
|
||||
}
|
||||
|
||||
private static MethodBase GetCallerMethod(StackTrace stackTrace)
|
||||
{
|
||||
const int topFrame = 3;
|
||||
|
||||
var topMethod = stackTrace.GetFrame(topFrame)?.GetMethod();
|
||||
|
||||
try
|
||||
{
|
||||
string fileName = Path.GetFileName(sourceFilePath);
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
if (topMethod?.Name == nameof(IAsyncStateMachine.MoveNext) && typeof(IAsyncStateMachine).IsAssignableFrom(topMethod?.DeclaringType))
|
||||
{
|
||||
callerFileName = fileName;
|
||||
// Async method; return actual method as determined by heuristic:
|
||||
// "Nearest method on stack to async state-machine's MoveNext() in same namespace but in a different type".
|
||||
// There are tighter ways of determining the actual method, but this is good enough and probably faster.
|
||||
for (int deepFrame = topFrame + 1; deepFrame < stackTrace.FrameCount; deepFrame++)
|
||||
{
|
||||
var deepMethod = stackTrace.GetFrame(deepFrame)?.GetMethod();
|
||||
|
||||
if (deepMethod?.DeclaringType != topMethod?.DeclaringType && deepMethod?.DeclaringType?.Namespace == topMethod?.DeclaringType?.Namespace)
|
||||
{
|
||||
return deepMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
callerFileName = "Unknown";
|
||||
// Ignore exceptions in Release. The code above won't throw, but if it does, we don't want to crash the app.
|
||||
#if DEBUG
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
|
||||
return $"{callerFileName}::{memberName}::{sourceLineNumber}";
|
||||
return topMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>PowerToys ManagedCommon</Description>
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace ManagedCommon
|
||||
|
||||
internal static int Size
|
||||
{
|
||||
get { return Marshal.SizeOf<INPUT>(); }
|
||||
get { return Marshal.SizeOf(typeof(INPUT)); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,12 @@ namespace ManagedCommon
|
||||
{
|
||||
public static class RunnerHelper
|
||||
{
|
||||
public static void WaitForPowerToysRunner(int powerToysPID, Action act, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
|
||||
public static void WaitForPowerToysRunner(int powerToysPID, Action act)
|
||||
{
|
||||
var stackTrace = new StackTrace();
|
||||
var assembly = Assembly.GetCallingAssembly().GetName();
|
||||
PowerToysTelemetry.Log.WriteEvent(new DebugEvent() { Message = $"[{assembly}][{memberName}]WaitForPowerToysRunner waiting for Event powerToysPID={powerToysPID}" });
|
||||
var callingMethod = stackTrace.GetFrame(1).GetMethod().Name;
|
||||
PowerToysTelemetry.Log.WriteEvent(new DebugEvent() { Message = $"[{assembly}][{callingMethod}]WaitForPowerToysRunner waiting for Event powerToysPID={powerToysPID}" });
|
||||
Task.Run(() =>
|
||||
{
|
||||
const uint INFINITE = 0xFFFFFFFF;
|
||||
@@ -28,7 +29,7 @@ namespace ManagedCommon
|
||||
IntPtr powerToysProcHandle = NativeMethods.OpenProcess(SYNCHRONIZE, false, powerToysPID);
|
||||
if (NativeMethods.WaitForSingleObject(powerToysProcHandle, INFINITE) == WAIT_OBJECT_0)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new DebugEvent() { Message = $"[{assembly}][{memberName}]WaitForPowerToysRunner Event Notified powerToysPID={powerToysPID}" });
|
||||
PowerToysTelemetry.Log.WriteEvent(new DebugEvent() { Message = $"[{assembly}][{callingMethod}]WaitForPowerToysRunner Event Notified powerToysPID={powerToysPID}" });
|
||||
act.Invoke();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,13 +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;
|
||||
using static ManagedCommon.LanguageHelper;
|
||||
|
||||
namespace ManagedCommon.Serialization;
|
||||
|
||||
[JsonSerializable(typeof(OutGoingLanguageSettings))]
|
||||
internal sealed partial class SourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace ManagedCommon
|
||||
/// <param name="sender">Sender ThemeListener</param>
|
||||
public delegate void ThemeChangedEvent(ThemeListener sender);
|
||||
|
||||
public partial class ThemeListener : IDisposable
|
||||
public class ThemeListener : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the App Theme.
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Remote;
|
||||
using OpenQA.Selenium.Support.Events;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -18,12 +18,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// override ToString to return detailed debugging content provided by OpenQA.Selenium.By
|
||||
return this.by.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a By object using the name attribute.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Remote;
|
||||
using OpenQA.Selenium.Support.Events;
|
||||
using static Microsoft.PowerToys.UITest.UITestBase;
|
||||
|
||||
[assembly: InternalsVisibleTo("Session")]
|
||||
|
||||
@@ -19,7 +24,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
public class Element
|
||||
{
|
||||
private WindowsElement? windowsElement;
|
||||
|
||||
private WindowsDriver<WindowsElement>? driver;
|
||||
|
||||
internal void SetWindowsElement(WindowsElement windowsElement) => this.windowsElement = windowsElement;
|
||||
@@ -39,20 +43,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get { return this.windowsElement?.Text ?? string.Empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the UI element is Enabled or not.
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
get { return this.windowsElement?.Enabled ?? false; }
|
||||
}
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get { return this.windowsElement?.Selected ?? false; }
|
||||
get { return GetAttribute("Value"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,19 +78,26 @@ namespace Microsoft.PowerToys.UITest
|
||||
get { return GetAttribute("ControlType"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the UI element is enabled.
|
||||
/// </summary>
|
||||
/// <returns>True if the element is enabled; otherwise, false.</returns>
|
||||
public bool IsEnabled() => GetAttribute("IsEnabled") == "True";
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the UI element is selected.
|
||||
/// </summary>
|
||||
/// <returns>True if the element is selected; otherwise, false.</returns>
|
||||
public bool IsSelected() => GetAttribute("IsSelected") == "True";
|
||||
|
||||
/// <summary>
|
||||
/// Click the UI element.
|
||||
/// </summary>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click.</param>
|
||||
public void Click(bool rightClick = false)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
PerformAction(actions =>
|
||||
{
|
||||
actions.MoveToElement(windowElement);
|
||||
|
||||
// Move 2by2 offset to make click more stable instead of click on the border of the element
|
||||
actions.MoveByOffset(2, 2);
|
||||
|
||||
if (rightClick)
|
||||
{
|
||||
actions.ContextClick();
|
||||
@@ -108,25 +106,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
actions.Click();
|
||||
}
|
||||
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Double Click the UI element.
|
||||
/// </summary>
|
||||
public void DoubleClick()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement);
|
||||
|
||||
// Move 2by2 offset to make click more stable instead of click on the border of the element
|
||||
actions.MoveByOffset(2, 2);
|
||||
|
||||
actions.DoubleClick();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,7 +133,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElement = FindHelper.Find<T, AppiumWebElement>(
|
||||
var foundElement = FindElementHelper.Find<T, AppiumWebElement>(
|
||||
() =>
|
||||
{
|
||||
var element = this.windowsElement.FindElement(by.ToSeleniumBy());
|
||||
@@ -178,7 +157,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElements = FindHelper.FindAll<T, AppiumWebElement>(
|
||||
var foundElements = FindElementHelper.FindAll<T, AppiumWebElement>(
|
||||
() =>
|
||||
{
|
||||
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
|
||||
@@ -194,24 +173,13 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform on the element.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 100 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 100 ms</param>
|
||||
protected void PerformAction(Action<Actions, WindowsElement> action, int msPreAction = 100, int msPostAction = 100)
|
||||
private void PerformAction(Action<Actions> action)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
Task.Delay(msPreAction).Wait();
|
||||
}
|
||||
|
||||
var windowElement = this.windowsElement!;
|
||||
var element = this.windowsElement;
|
||||
Actions actions = new Actions(this.driver);
|
||||
action(actions, windowElement);
|
||||
|
||||
if (msPostAction > 0)
|
||||
{
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
actions.MoveToElement(element);
|
||||
action(actions);
|
||||
actions.Build().Perform();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
// 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.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
|
||||
[assembly: InternalsVisibleTo("Element")]
|
||||
@@ -15,7 +22,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Helper class for finding elements.
|
||||
/// </summary>
|
||||
internal static class FindHelper
|
||||
internal static class FindElementHelper
|
||||
{
|
||||
public static T Find<T, TW>(Func<TW> findElementFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
@@ -44,12 +51,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
Assert.IsNotNull(element, $"New Element {typeof(T).Name} error: element is null.");
|
||||
|
||||
T newElement = new T();
|
||||
if (timeoutMS > 0)
|
||||
{
|
||||
// Only set timeout if it is positive value
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(timeoutMS);
|
||||
}
|
||||
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(timeoutMS);
|
||||
newElement.SetSession(driver);
|
||||
newElement.SetWindowsElement(element);
|
||||
return newElement;
|
||||
@@ -1,40 +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 OpenQA.Selenium;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a textbox in the UI test environment.
|
||||
/// </summary>
|
||||
public class TextBox : Element
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the text of the textbox.
|
||||
/// </summary>
|
||||
/// <param name="value">The text to set.</param>
|
||||
/// <param name="clearText">A value indicating whether to clear the text before setting it. Default value is true</param>
|
||||
/// <returns>The current TextBox instance.</returns>
|
||||
public TextBox SetText(string value, bool clearText = true)
|
||||
{
|
||||
if (clearText)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
// select all text and delete it
|
||||
windowElement.SendKeys(Keys.Control + "a");
|
||||
windowElement.SendKeys(Keys.Delete);
|
||||
});
|
||||
}
|
||||
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
windowElement.SendKeys(value);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +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 Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ToggleSwitch in the UI test environment.
|
||||
/// </summary>
|
||||
public class ToggleSwitch : Button
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the ToggleSwitch is on.
|
||||
/// </summary>
|
||||
public bool IsOn
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Selected;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ToggleSwitch to the specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">A value indicating whether the ToggleSwitch should be active. Default is true</param>
|
||||
/// <returns>The current ToggleSwitch instance.</returns>
|
||||
public ToggleSwitch Toggle(bool value = true)
|
||||
{
|
||||
if (this.IsOn != value)
|
||||
{
|
||||
// Toggle the switch
|
||||
this.Click();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,13 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Remote;
|
||||
using OpenQA.Selenium.Support.Events;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
// 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.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -39,7 +45,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElement = FindHelper.Find<T, WindowsElement>(
|
||||
var foundElement = FindElementHelper.Find<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
var element = this.WindowsDriver.FindElement(by.ToSeleniumBy());
|
||||
@@ -59,20 +65,21 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T>? FindAll<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElements = FindHelper.FindAll<T, WindowsElement>(
|
||||
var foundElements = FindElementHelper.FindAll<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
|
||||
Assert.IsTrue(elements.Count > 0, $"Elements not found using selector: {by}");
|
||||
return elements;
|
||||
},
|
||||
this.WindowsDriver,
|
||||
timeoutMS);
|
||||
|
||||
return foundElements ?? new ReadOnlyCollection<T>(new List<T>());
|
||||
return foundElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,7 +110,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
SetForegroundWindow(windowHandle);
|
||||
var hexWindowHandle = windowHandle.ToString("x");
|
||||
var appCapabilities = new AppiumOptions();
|
||||
|
||||
appCapabilities.AddAdditionalCapability("appTopLevelWindow", hexWindowHandle);
|
||||
appCapabilities.AddAdditionalCapability("deviceName", "WindowsPC");
|
||||
this.WindowsDriver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), appCapabilities);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
@@ -16,4 +15,7 @@
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -33,34 +37,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.testInit.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by selector.
|
||||
/// Shortcut for this.Session.Find<T>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nested class for test initialization.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Models.KernelQueryCache;
|
||||
|
||||
@@ -1,14 +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;
|
||||
|
||||
namespace AdvancedPaste.UnitTests.Mocks;
|
||||
|
||||
internal sealed class NoOpProgress : IProgress<double>
|
||||
{
|
||||
public void Report(double value)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -132,18 +131,17 @@ public sealed class AIServiceBatchIntegrationTests
|
||||
{
|
||||
VaultCredentialsProvider credentialsProvider = new();
|
||||
PromptModerationService promptModerationService = new(credentialsProvider);
|
||||
NoOpProgress progress = new();
|
||||
CustomTextTransformService customTextTransformService = new(credentialsProvider, promptModerationService);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case PasteFormats.CustomTextTransformation:
|
||||
return DataPackageHelpers.CreateFromText(await customTextTransformService.TransformTextAsync(batchTestInput.Prompt, batchTestInput.Clipboard, CancellationToken.None, progress));
|
||||
return DataPackageHelpers.CreateFromText(await customTextTransformService.TransformTextAsync(batchTestInput.Prompt, batchTestInput.Clipboard));
|
||||
|
||||
case PasteFormats.KernelQuery:
|
||||
var clipboardData = DataPackageHelpers.CreateFromText(batchTestInput.Clipboard).GetView();
|
||||
KernelService kernelService = new(new NoOpKernelQueryCacheService(), credentialsProvider, promptModerationService, customTextTransformService);
|
||||
return await kernelService.TransformClipboardAsync(batchTestInput.Prompt, clipboardData, isSavedQuery: false, CancellationToken.None, progress);
|
||||
return await kernelService.TransformClipboardAsync(batchTestInput.Prompt, clipboardData, isSavedQuery: false);
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Unexpected format {format}");
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -131,7 +130,7 @@ public sealed class KernelServiceIntegrationTests : IDisposable
|
||||
|
||||
private async Task<DataPackageView> GetKernelOutputAsync(string prompt, DataPackage input)
|
||||
{
|
||||
var output = await _kernelService.TransformClipboardAsync(prompt, input.GetView(), isSavedQuery: false, CancellationToken.None, new NoOpProgress());
|
||||
var output = await _kernelService.TransformClipboardAsync(prompt, input.GetView(), isSavedQuery: false);
|
||||
|
||||
Assert.AreEqual(1, _eventListener.SemanticKernelEvents.Count);
|
||||
Assert.IsTrue(_eventListener.SemanticKernelTokens > 0);
|
||||
|
||||
@@ -1,109 +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;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
using AdvancedPaste.UnitTests.Mocks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
namespace AdvancedPaste.UnitTests.ServicesTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class TranscodeHelperIntegrationTests
|
||||
{
|
||||
private sealed record class MediaProperties(BasicProperties Basic, MusicProperties Music, VideoProperties Video);
|
||||
|
||||
private const string InputRootFolder = @"%USERPROFILE%\AdvancedPasteTranscodeMediaTestData";
|
||||
|
||||
/// <summary> Tests transforming a folder of media files.
|
||||
/// - Verifies that the output file has the same basic properties (e.g. duration) as the input file.
|
||||
/// - Copies the output file to a subfolder of the input folder for manual inspection.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[DataRow(@"audio", PasteFormats.TranscodeToMp3)]
|
||||
[DataRow(@"video", PasteFormats.TranscodeToMp4)]
|
||||
public async Task TestTransformFolder(string inputSubfolder, PasteFormats format)
|
||||
{
|
||||
var inputFolder = Environment.ExpandEnvironmentVariables(Path.Combine(InputRootFolder, inputSubfolder));
|
||||
|
||||
if (!Directory.Exists(inputFolder))
|
||||
{
|
||||
Assert.Inconclusive($"Skipping tests for {inputFolder} as it does not exist");
|
||||
}
|
||||
|
||||
var outputPath = Path.Combine(inputFolder, $"test_output_{format}");
|
||||
|
||||
foreach (var inputPath in Directory.EnumerateFiles(inputFolder))
|
||||
{
|
||||
await RunTestTransformFileAsync(inputPath, outputPath, format);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunTestTransformFileAsync(string inputPath, string finalOutputPath, PasteFormats format)
|
||||
{
|
||||
Logger.LogDebug($"Running {nameof(RunTestTransformFileAsync)} for {inputPath}/{format}");
|
||||
|
||||
Directory.CreateDirectory(finalOutputPath);
|
||||
|
||||
var inputPackage = await DataPackageHelpers.CreateFromFileAsync(inputPath);
|
||||
var inputProperties = await GetPropertiesAsync(await StorageFile.GetFileFromPathAsync(inputPath));
|
||||
|
||||
var outputPackage = await TransformHelpers.TransformAsync(format, inputPackage.GetView(), CancellationToken.None, new NoOpProgress());
|
||||
|
||||
var outputItems = await outputPackage.GetView().GetStorageItemsAsync();
|
||||
Assert.AreEqual(1, outputItems.Count);
|
||||
var outputFile = outputItems.Single() as StorageFile;
|
||||
Assert.IsNotNull(outputFile);
|
||||
var outputProperties = await GetPropertiesAsync(outputFile);
|
||||
AssertPropertiesMatch(format, inputProperties, outputProperties);
|
||||
|
||||
await outputFile.CopyAsync(await StorageFolder.GetFolderFromPathAsync(finalOutputPath), outputFile.Name, NameCollisionOption.ReplaceExisting);
|
||||
await outputPackage.GetView().TryCleanupAfterDelayAsync(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
private static void AssertPropertiesMatch(PasteFormats format, MediaProperties inputProperties, MediaProperties outputProperties)
|
||||
{
|
||||
Assert.IsTrue(outputProperties.Basic.Size > 0);
|
||||
|
||||
Assert.AreEqual(inputProperties.Music.Title, outputProperties.Music.Title);
|
||||
Assert.AreEqual(inputProperties.Music.Album, outputProperties.Music.Album);
|
||||
Assert.AreEqual(inputProperties.Music.Artist, outputProperties.Music.Artist);
|
||||
AssertDurationsApproxEqual(inputProperties.Music.Duration, outputProperties.Music.Duration);
|
||||
|
||||
if (format == PasteFormats.TranscodeToMp4)
|
||||
{
|
||||
Assert.AreEqual(inputProperties.Video.Title, outputProperties.Video.Title);
|
||||
AssertDurationsApproxEqual(inputProperties.Video.Duration, outputProperties.Video.Duration);
|
||||
|
||||
var inputVideoDimensions = GetNormalizedDimensions(inputProperties.Video);
|
||||
if (inputVideoDimensions != null)
|
||||
{
|
||||
Assert.AreEqual(inputVideoDimensions, GetNormalizedDimensions(outputProperties.Video));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<MediaProperties> GetPropertiesAsync(StorageFile file) =>
|
||||
new(await file.GetBasicPropertiesAsync(), await file.Properties.GetMusicPropertiesAsync(), await file.Properties.GetVideoPropertiesAsync());
|
||||
|
||||
private static void AssertDurationsApproxEqual(TimeSpan expected, TimeSpan actual) =>
|
||||
Assert.AreEqual(expected.Ticks, actual.Ticks, delta: TimeSpan.FromSeconds(1).Ticks);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dimensions of a video, if available. Accounts for the fact that the dimensions may sometimes be swapped.
|
||||
/// </summary>
|
||||
private static (uint Width, uint Height)? GetNormalizedDimensions(VideoProperties properties) =>
|
||||
properties.Width == 0 || properties.Height == 0
|
||||
? null
|
||||
: (Math.Max(properties.Width, properties.Height), Math.Min(properties.Width, properties.Height));
|
||||
}
|
||||
@@ -28,7 +28,6 @@
|
||||
Background="Transparent"
|
||||
BorderThickness="4"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
IsHitTestVisible="False"
|
||||
Visibility="Collapsed">
|
||||
<!-- CornerRadius needs to be > 0 -->
|
||||
<Grid.BorderBrush>
|
||||
|
||||
@@ -178,36 +178,17 @@
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<ProgressRing
|
||||
Width="30"
|
||||
Height="30"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsActive="{Binding DataContext.IsBusy, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}"
|
||||
IsIndeterminate="{Binding DataContext.HasIndeterminateTransformProgress, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Visibility="{Binding DataContext.IsBusy, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Value="{Binding DataContext.TransformProgress, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
|
||||
|
||||
<StackPanel
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Visibility="{Binding DataContext.IsBusy, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
<Image
|
||||
x:Name="AIGlyphImage"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Source="/Assets/AdvancedPaste/SemanticKernel.svg"
|
||||
Visibility="{Binding DataContext.IsAdvancedAIEnabled, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<PathIcon
|
||||
x:Name="AIGlyph"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Data="M128 766q0-42 24-77t65-48l178-57q32-11 61-30t52-42q50-50 71-114l58-179q13-40 48-65t78-26q42 0 77 24t50 65l58 177q21 66 72 117 49 50 117 72l176 58q43 14 69 48t26 80q0 41-25 76t-64 49l-178 58q-66 21-117 72-32 32-51 73t-33 84-26 83-30 73-45 51-71 20q-42 0-77-24t-49-65l-58-178q-8-25-19-47t-28-43q-34-43-77-68t-89-41-89-27-78-29-55-45-21-75zm1149 7q-76-29-145-53t-129-60-104-88-73-138l-57-176-67 176q-18 48-42 89t-60 78q-34 34-76 61t-89 43l-177 57q75 29 144 53t127 60 103 89 73 137l57 176 67-176q37-97 103-168t168-103l177-57zm-125 759q0-31 20-57t49-36l99-32q34-11 53-34t30-51 20-59 20-54 33-41 58-16q32 0 59 19t38 50q6 20 11 40t13 40 17 38 25 34q16 17 39 26t48 18 49 16 44 20 31 32 12 50q0 33-18 60t-51 38q-19 6-39 11t-41 13-39 17-34 25q-24 25-35 62t-24 73-35 61-68 25q-32 0-59-19t-38-50q-6-18-11-39t-13-41-17-40-24-33q-18-17-41-27t-47-17-49-15-43-20-30-33-12-54zm583 4q-43-13-74-30t-55-41-40-55-32-74q-12 41-29 72t-42 55-55 42-71 31q81 23 128 71t71 129q15-43 31-74t40-54 53-40 75-32z"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Visibility="{Binding DataContext.IsAdvancedAIEnabled, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
<Image
|
||||
x:Name="AIGlyphImage"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Source="/Assets/AdvancedPaste/SemanticKernel.svg"
|
||||
Visibility="{Binding DataContext.IsAdvancedAIEnabled, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<PathIcon
|
||||
x:Name="AIGlyph"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Data="M128 766q0-42 24-77t65-48l178-57q32-11 61-30t52-42q50-50 71-114l58-179q13-40 48-65t78-26q42 0 77 24t50 65l58 177q21 66 72 117 49 50 117 72l176 58q43 14 69 48t26 80q0 41-25 76t-64 49l-178 58q-66 21-117 72-32 32-51 73t-33 84-26 83-30 73-45 51-71 20q-42 0-77-24t-49-65l-58-178q-8-25-19-47t-28-43q-34-43-77-68t-89-41-89-27-78-29-55-45-21-75zm1149 7q-76-29-145-53t-129-60-104-88-73-138l-57-176-67 176q-18 48-42 89t-60 78q-34 34-76 61t-89 43l-177 57q75 29 144 53t127 60 103 89 73 137l57 176 67-176q37-97 103-168t168-103l177-57zm-125 759q0-31 20-57t49-36l99-32q34-11 53-34t30-51 20-59 20-54 33-41 58-16q32 0 59 19t38 50q6 20 11 40t13 40 17 38 25 34q16 17 39 26t48 18 49 16 44 20 31 32 12 50q0 33-18 60t-51 38q-19 6-39 11t-41 13-39 17-34 25q-24 25-35 62t-24 73-35 61-68 25q-32 0-59-19t-38-50q-6-18-11-39t-13-41-17-40-24-33q-18-17-41-27t-47-17-49-15-43-20-30-33-12-54zm583 4q-43-13-74-30t-55-41-40-55-32-74q-12 41-29 72t-42 55-55 42-71 31q81 23 128 71t71 129q15-43 31-74t40-54 53-40 75-32z"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Visibility="{Binding DataContext.IsAdvancedAIEnabled, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
</Viewbox>
|
||||
<ScrollViewer
|
||||
@@ -591,24 +572,6 @@
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="CancelBtn"
|
||||
x:Uid="CancelBtnAutomation"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Command="{x:Bind CancelPasteActionCommand}"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
IsEnabled="False"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="Collapsed">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="CancelBtnToolTip" TextWrapping="WrapWholeWords" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- Transparent overlay to show tooltip -->
|
||||
<Grid
|
||||
x:Name="SendBtnOverlay"
|
||||
@@ -716,10 +679,6 @@
|
||||
<Setter Target="Loader.IsLoading" Value="True" />
|
||||
<Setter Target="InputTxtBox.IsEnabled" Value="False" />
|
||||
<Setter Target="SendBtn.IsEnabled" Value="False" />
|
||||
<Setter Target="SendBtn.Visibility" Value="Collapsed" />
|
||||
<Setter Target="SendBtnOverlay.Visibility" Value="Collapsed" />
|
||||
<Setter Target="CancelBtn.IsEnabled" Value="True" />
|
||||
<Setter Target="CancelBtn.Visibility" Value="Visible" />
|
||||
<Setter Target="DisclaimerPresenter.Visibility" Value="Collapsed" />
|
||||
<Setter Target="LoadingText.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace AdvancedPaste.Controls
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is nameof(ViewModel.IsBusy) or nameof(ViewModel.PasteActionError))
|
||||
if (e.PropertyName == nameof(ViewModel.Busy) || e.PropertyName == nameof(ViewModel.PasteActionError))
|
||||
{
|
||||
var state = ViewModel.IsBusy ? "LoadingState" : ViewModel.PasteActionError.HasText ? "ErrorState" : "DefaultState";
|
||||
var state = ViewModel.Busy ? "LoadingState" : ViewModel.PasteActionError.HasText ? "ErrorState" : "DefaultState";
|
||||
VisualStateManager.GoToState(this, state, true);
|
||||
}
|
||||
}
|
||||
@@ -78,9 +78,6 @@ namespace AdvancedPaste.Controls
|
||||
[RelayCommand]
|
||||
private async Task GenerateCustomAIAsync() => await ViewModel.ExecuteCustomAIFormatFromCurrentQueryAsync(PasteActionSource.PromptBox);
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CancelPasteActionAsync() => await ViewModel.CancelPasteActionAsync();
|
||||
|
||||
private async void InputTxtBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0 && ViewModel.IsCustomAIAvailable)
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace AdvancedPaste
|
||||
{
|
||||
private readonly WindowMessageMonitor _msgMonitor;
|
||||
private readonly IUserSettings _userSettings;
|
||||
private readonly OptionsViewModel _optionsViewModel;
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
@@ -33,7 +32,8 @@ namespace AdvancedPaste
|
||||
InitializeComponent();
|
||||
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
_optionsViewModel = App.GetService<OptionsViewModel>();
|
||||
|
||||
var optionsViewModel = App.GetService<OptionsViewModel>();
|
||||
|
||||
var baseHeight = MinHeight;
|
||||
var coreActionCount = PasteFormat.MetadataDict.Values.Count(metadata => metadata.IsCoreAction);
|
||||
@@ -43,7 +43,7 @@ namespace AdvancedPaste
|
||||
double GetHeight(int maxCustomActionCount) =>
|
||||
baseHeight +
|
||||
new PasteFormatsToHeightConverter().GetHeight(coreActionCount + _userSettings.AdditionalActions.Count) +
|
||||
new PasteFormatsToHeightConverter() { MaxItems = maxCustomActionCount }.GetHeight(_optionsViewModel.IsCustomAIServiceEnabled ? _userSettings.CustomActions.Count : 0);
|
||||
new PasteFormatsToHeightConverter() { MaxItems = maxCustomActionCount }.GetHeight(optionsViewModel.IsCustomAIServiceEnabled ? _userSettings.CustomActions.Count : 0);
|
||||
|
||||
MinHeight = GetHeight(1);
|
||||
Height = GetHeight(5);
|
||||
@@ -52,9 +52,9 @@ namespace AdvancedPaste
|
||||
UpdateHeight();
|
||||
|
||||
_userSettings.Changed += (_, _) => UpdateHeight();
|
||||
_optionsViewModel.PropertyChanged += (_, e) =>
|
||||
optionsViewModel.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(_optionsViewModel.IsCustomAIServiceEnabled))
|
||||
if (e.PropertyName == nameof(optionsViewModel.IsCustomAIServiceEnabled))
|
||||
{
|
||||
UpdateHeight();
|
||||
}
|
||||
@@ -111,9 +111,8 @@ namespace AdvancedPaste
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private async void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
|
||||
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
|
||||
{
|
||||
await _optionsViewModel.CancelPasteActionAsync();
|
||||
Hide();
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Models;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Win32;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Data.Html;
|
||||
using Windows.Graphics.Imaging;
|
||||
@@ -22,6 +18,8 @@ namespace AdvancedPaste.Helpers;
|
||||
|
||||
internal static class DataPackageHelpers
|
||||
{
|
||||
private static readonly Lazy<HashSet<string>> ImageFileTypes = new(GetImageFileTypes());
|
||||
|
||||
private static readonly (string DataFormat, ClipboardFormat ClipboardFormat)[] DataFormats =
|
||||
[
|
||||
(StandardDataFormats.Text, ClipboardFormat.Text),
|
||||
@@ -29,14 +27,6 @@ internal static class DataPackageHelpers
|
||||
(StandardDataFormats.Bitmap, ClipboardFormat.Image),
|
||||
];
|
||||
|
||||
private static readonly Lazy<(ClipboardFormat Format, HashSet<string> FileTypes)[]> SupportedFileTypes =
|
||||
new(() =>
|
||||
[
|
||||
(ClipboardFormat.Image, GetImageFileTypes()),
|
||||
(ClipboardFormat.Audio, GetMediaFileTypes("audio")),
|
||||
(ClipboardFormat.Video, GetMediaFileTypes("video")),
|
||||
]);
|
||||
|
||||
internal static DataPackage CreateFromText(string text)
|
||||
{
|
||||
DataPackage dataPackage = new();
|
||||
@@ -67,12 +57,9 @@ internal static class DataPackageHelpers
|
||||
{
|
||||
availableFormats |= ClipboardFormat.File;
|
||||
|
||||
foreach (var (format, fileTypes) in SupportedFileTypes.Value)
|
||||
if (ImageFileTypes.Value.Contains(file.FileType))
|
||||
{
|
||||
if (fileTypes.Contains(file.FileType))
|
||||
{
|
||||
availableFormats |= format;
|
||||
}
|
||||
availableFormats |= ClipboardFormat.Image;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,60 +93,6 @@ internal static class DataPackageHelpers
|
||||
return availableFormats == ClipboardFormat.Text ? !string.IsNullOrEmpty(await dataPackageView.GetTextAsync()) : availableFormats != ClipboardFormat.None;
|
||||
}
|
||||
|
||||
internal static async Task TryCleanupAfterDelayAsync(this DataPackageView dataPackageView, TimeSpan delay)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tempFile = await GetSingleTempFileOrNullAsync(dataPackageView);
|
||||
|
||||
if (tempFile != null)
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
|
||||
Logger.LogDebug($"Cleaning up temporary file with extension [{tempFile.Extension}] from data package after delay");
|
||||
|
||||
tempFile.Delete();
|
||||
if (NormalizeDirectoryPath(tempFile.Directory?.Parent?.FullName) == NormalizeDirectoryPath(Path.GetTempPath()))
|
||||
{
|
||||
tempFile.Directory?.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to clean up temporary files", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<FileInfo> GetSingleTempFileOrNullAsync(this DataPackageView dataPackageView)
|
||||
{
|
||||
if (!dataPackageView.Contains(StandardDataFormats.StorageItems))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var storageItems = await dataPackageView.GetStorageItemsAsync();
|
||||
|
||||
if (storageItems.Count != 1 || storageItems.Single() is not StorageFile file)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
FileInfo fileInfo = new(file.Path);
|
||||
var tempPathDirectory = NormalizeDirectoryPath(Path.GetTempPath());
|
||||
|
||||
var directoryPaths = new[] { fileInfo.Directory, fileInfo.Directory?.Parent }
|
||||
.Where(directory => directory != null)
|
||||
.Select(directory => NormalizeDirectoryPath(directory.FullName));
|
||||
|
||||
return directoryPaths.Contains(NormalizeDirectoryPath(Path.GetTempPath())) ? fileInfo : null;
|
||||
}
|
||||
|
||||
private static string NormalizeDirectoryPath(string path) =>
|
||||
Path.GetFullPath(new Uri(path).LocalPath)
|
||||
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
|
||||
.ToUpperInvariant();
|
||||
|
||||
internal static async Task<string> GetTextOrEmptyAsync(this DataPackageView dataPackageView) =>
|
||||
dataPackageView.Contains(StandardDataFormats.Text) ? await dataPackageView.GetTextAsync() : string.Empty;
|
||||
|
||||
@@ -220,27 +153,4 @@ internal static class DataPackageHelpers
|
||||
BitmapDecoder.GetDecoderInformationEnumerator()
|
||||
.SelectMany(di => di.FileExtensions)
|
||||
.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
private static HashSet<string> GetMediaFileTypes(string mediaKind)
|
||||
{
|
||||
static string AssocQueryString(NativeMethods.AssocStr assocStr, string extension)
|
||||
{
|
||||
uint pcchOut = 0;
|
||||
|
||||
NativeMethods.AssocQueryString(NativeMethods.AssocF.None, assocStr, extension, null, null, ref pcchOut);
|
||||
|
||||
StringBuilder pszOut = new((int)pcchOut);
|
||||
var hResult = NativeMethods.AssocQueryString(NativeMethods.AssocF.None, assocStr, extension, null, pszOut, ref pcchOut);
|
||||
return hResult == NativeMethods.HResult.Ok ? pszOut.ToString() : string.Empty;
|
||||
}
|
||||
|
||||
var comparison = StringComparison.OrdinalIgnoreCase;
|
||||
var extensions = from extension in Registry.ClassesRoot.GetSubKeyNames()
|
||||
where extension.StartsWith('.')
|
||||
where AssocQueryString(NativeMethods.AssocStr.PerceivedType, extension).Equals(mediaKind, comparison) ||
|
||||
AssocQueryString(NativeMethods.AssocStr.ContentType, extension).StartsWith($"{mediaKind}/", comparison)
|
||||
select extension;
|
||||
|
||||
return extensions.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Models;
|
||||
@@ -18,8 +17,6 @@ internal static class KernelExtensions
|
||||
private const string DataPackageKey = "DataPackage";
|
||||
private const string LastErrorKey = "LastError";
|
||||
private const string ActionChainKey = "ActionChain";
|
||||
private const string CancellationTokenKey = "CancellationToken";
|
||||
private const string ProgressKey = "Progress";
|
||||
|
||||
internal static DataPackageView GetDataPackageView(this Kernel kernel)
|
||||
{
|
||||
@@ -43,14 +40,6 @@ internal static class KernelExtensions
|
||||
|
||||
internal static void SetDataPackageView(this Kernel kernel, DataPackageView dataPackageView) => kernel.Data[DataPackageKey] = dataPackageView;
|
||||
|
||||
internal static CancellationToken GetCancellationToken(this Kernel kernel) => kernel.Data.TryGetValue(CancellationTokenKey, out object value) ? (CancellationToken)value : CancellationToken.None;
|
||||
|
||||
internal static void SetCancellationToken(this Kernel kernel, CancellationToken cancellationToken) => kernel.Data[CancellationTokenKey] = cancellationToken;
|
||||
|
||||
internal static IProgress<double> GetProgress(this Kernel kernel) => kernel.Data.TryGetValue(ProgressKey, out object obj) ? obj as IProgress<double> : null;
|
||||
|
||||
internal static void SetProgress(this Kernel kernel, IProgress<double> progress) => kernel.Data[ProgressKey] = progress;
|
||||
|
||||
internal static Exception GetLastError(this Kernel kernel) => kernel.Data.TryGetValue(LastErrorKey, out object obj) ? obj as Exception : null;
|
||||
|
||||
internal static void SetLastError(this Kernel kernel, Exception error) => kernel.Data[LastErrorKey] = error;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace AdvancedPaste.Helpers
|
||||
{
|
||||
@@ -84,68 +83,6 @@ namespace AdvancedPaste.Helpers
|
||||
Scancode = 0x0008,
|
||||
}
|
||||
|
||||
public enum HResult
|
||||
{
|
||||
Ok = 0x0000,
|
||||
False = 0x0001,
|
||||
InvalidArguments = unchecked((int)0x80070057),
|
||||
OutOfMemory = unchecked((int)0x8007000E),
|
||||
NoInterface = unchecked((int)0x80004002),
|
||||
Fail = unchecked((int)0x80004005),
|
||||
ExtractionFailed = unchecked((int)0x8004B200),
|
||||
ElementNotFound = unchecked((int)0x80070490),
|
||||
TypeElementNotFound = unchecked((int)0x8002802B),
|
||||
NoObject = unchecked((int)0x800401E5),
|
||||
Win32ErrorCanceled = 1223,
|
||||
Canceled = unchecked((int)0x800704C7),
|
||||
ResourceInUse = unchecked((int)0x800700AA),
|
||||
AccessDenied = unchecked((int)0x80030005),
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AssocF
|
||||
{
|
||||
None = 0,
|
||||
Init_NoRemapCLSID = 0x1,
|
||||
Init_ByExeName = 0x2,
|
||||
Open_ByExeName = 0x3,
|
||||
Init_DefaultToStar = 0x4,
|
||||
Init_DefaultToFolder = 0x8,
|
||||
NoUserSettings = 0x10,
|
||||
NoTruncate = 0x20,
|
||||
Verify = 0x40,
|
||||
RemapRunDll = 0x80,
|
||||
NoFixUps = 0x100,
|
||||
IgnoreBaseClass = 0x200,
|
||||
}
|
||||
|
||||
public enum AssocStr
|
||||
{
|
||||
Command = 1,
|
||||
Executable,
|
||||
FriendlyDocName,
|
||||
FriendlyAppName,
|
||||
NoOpen,
|
||||
ShellNewValue,
|
||||
DDECommand,
|
||||
DDEIfExec,
|
||||
DDEApplication,
|
||||
DDETopic,
|
||||
InfoTip,
|
||||
QuickTip,
|
||||
TileInfo,
|
||||
ContentType,
|
||||
DefaultIcon,
|
||||
ShellExtension,
|
||||
PerceivedType,
|
||||
DelegateExecute,
|
||||
SupportedUriProtocols,
|
||||
ProgId,
|
||||
AppId,
|
||||
AppPublisher,
|
||||
AppIconReference,
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
|
||||
@@ -163,8 +100,5 @@ namespace AdvancedPaste.Helpers
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool GetCursorPos(out PointInter lpPoint);
|
||||
|
||||
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
internal static extern HResult AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, [In][Out] ref uint pcchOut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Windows.Globalization;
|
||||
@@ -16,14 +15,11 @@ namespace AdvancedPaste.Helpers;
|
||||
|
||||
public static class OcrHelpers
|
||||
{
|
||||
public static async Task<string> ExtractTextAsync(SoftwareBitmap bitmap, CancellationToken cancellationToken)
|
||||
public static async Task<string> ExtractTextAsync(SoftwareBitmap bitmap)
|
||||
{
|
||||
var ocrLanguage = GetOCRLanguage() ?? throw new InvalidOperationException("Unable to determine OCR language");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var ocrEngine = OcrEngine.TryCreateFromLanguage(ocrLanguage) ?? throw new InvalidOperationException("Unable to create OCR engine");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var ocrResult = await ocrEngine.RecognizeAsync(bitmap);
|
||||
|
||||
return string.IsNullOrWhiteSpace(ocrResult.Text)
|
||||
|
||||
@@ -1,167 +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;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Models;
|
||||
using ManagedCommon;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Media.MediaProperties;
|
||||
using Windows.Media.Transcoding;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace AdvancedPaste.Helpers;
|
||||
|
||||
internal static class TranscodeHelpers
|
||||
{
|
||||
public static async Task<DataPackage> TranscodeToMp3Async(DataPackageView clipboardData, CancellationToken cancellationToken, IProgress<double> progress) =>
|
||||
await TranscodeMediaAsync(clipboardData, MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High), ".mp3", cancellationToken, progress);
|
||||
|
||||
public static async Task<DataPackage> TranscodeToMp4Async(DataPackageView clipboardData, CancellationToken cancellationToken, IProgress<double> progress) =>
|
||||
await TranscodeMediaAsync(clipboardData, MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p), ".mp4", cancellationToken, progress);
|
||||
|
||||
private static async Task<DataPackage> TranscodeMediaAsync(DataPackageView clipboardData, MediaEncodingProfile baseOutputProfile, string extension, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var inputFiles = await clipboardData.GetStorageItemsAsync();
|
||||
|
||||
if (inputFiles.Count != 1)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(TranscodeMediaAsync)} does not support multiple files");
|
||||
}
|
||||
|
||||
var inputFile = inputFiles.Single() as StorageFile ?? throw new InvalidOperationException($"{nameof(TranscodeMediaAsync)} only supports files");
|
||||
var inputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(inputFile.Path);
|
||||
|
||||
var inputProfile = await MediaEncodingProfile.CreateFromFileAsync(inputFile);
|
||||
var outputProfile = CreateOutputProfile(inputProfile, baseOutputProfile);
|
||||
|
||||
#if DEBUG
|
||||
static string ProfileToString(MediaEncodingProfile profile) => System.Text.Json.JsonSerializer.Serialize(profile, options: new() { WriteIndented = true });
|
||||
Logger.LogDebug($"{nameof(inputProfile)}: {ProfileToString(inputProfile)}");
|
||||
Logger.LogDebug($"{nameof(outputProfile)}: {ProfileToString(outputProfile)}");
|
||||
#endif
|
||||
|
||||
var outputFolder = await Task.Run(() => Directory.CreateTempSubdirectory("PowerToys_AdvancedPaste_"), cancellationToken);
|
||||
var outputFileName = StringComparer.OrdinalIgnoreCase.Equals(Path.GetExtension(inputFile.Path), extension) ? inputFileNameWithoutExtension + "_1" : inputFileNameWithoutExtension;
|
||||
var outputFilePath = Path.Combine(outputFolder.FullName, Path.ChangeExtension(outputFileName, extension));
|
||||
await File.WriteAllBytesAsync(outputFilePath, [], cancellationToken); // TranscodeAsync seems to require the output file to exist
|
||||
|
||||
await TranscodeMediaAsync(inputFile, await StorageFile.GetFileFromPathAsync(outputFilePath), outputProfile, cancellationToken, progress);
|
||||
|
||||
return await DataPackageHelpers.CreateFromFileAsync(outputFilePath);
|
||||
}
|
||||
|
||||
private static MediaEncodingProfile CreateOutputProfile(MediaEncodingProfile inputProfile, MediaEncodingProfile baseOutputProfile)
|
||||
{
|
||||
MediaEncodingProfile outputProfile = new()
|
||||
{
|
||||
Video = null,
|
||||
Audio = null,
|
||||
};
|
||||
|
||||
outputProfile.Container = baseOutputProfile.Container.Copy();
|
||||
|
||||
if (inputProfile.Video != null && baseOutputProfile.Video != null)
|
||||
{
|
||||
outputProfile.Video = baseOutputProfile.Video.Copy();
|
||||
|
||||
if (inputProfile.Video.Bitrate != 0)
|
||||
{
|
||||
outputProfile.Video.Bitrate = inputProfile.Video.Bitrate;
|
||||
}
|
||||
|
||||
if (inputProfile.Video.FrameRate.Numerator != 0)
|
||||
{
|
||||
outputProfile.Video.FrameRate.Numerator = inputProfile.Video.FrameRate.Numerator;
|
||||
}
|
||||
|
||||
if (inputProfile.Video.FrameRate.Denominator != 0)
|
||||
{
|
||||
outputProfile.Video.FrameRate.Denominator = inputProfile.Video.FrameRate.Denominator;
|
||||
}
|
||||
|
||||
if (inputProfile.Video.PixelAspectRatio.Numerator != 0)
|
||||
{
|
||||
outputProfile.Video.PixelAspectRatio.Numerator = inputProfile.Video.PixelAspectRatio.Numerator;
|
||||
}
|
||||
|
||||
if (inputProfile.Video.PixelAspectRatio.Denominator != 0)
|
||||
{
|
||||
outputProfile.Video.PixelAspectRatio.Denominator = inputProfile.Video.PixelAspectRatio.Denominator;
|
||||
}
|
||||
|
||||
outputProfile.Video.Width = inputProfile.Video.Width;
|
||||
outputProfile.Video.Height = inputProfile.Video.Height;
|
||||
}
|
||||
|
||||
if (inputProfile.Audio != null && baseOutputProfile.Audio != null)
|
||||
{
|
||||
outputProfile.Audio = baseOutputProfile.Audio.Copy();
|
||||
|
||||
if (inputProfile.Audio.Bitrate != 0)
|
||||
{
|
||||
outputProfile.Audio.Bitrate = inputProfile.Audio.Bitrate;
|
||||
}
|
||||
|
||||
if (inputProfile.Audio.BitsPerSample != 0)
|
||||
{
|
||||
outputProfile.Audio.BitsPerSample = inputProfile.Audio.BitsPerSample;
|
||||
}
|
||||
|
||||
if (inputProfile.Audio.ChannelCount != 0)
|
||||
{
|
||||
outputProfile.Audio.ChannelCount = inputProfile.Audio.ChannelCount;
|
||||
}
|
||||
|
||||
if (inputProfile.Audio.SampleRate != 0)
|
||||
{
|
||||
outputProfile.Audio.SampleRate = inputProfile.Audio.SampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
return outputProfile;
|
||||
}
|
||||
|
||||
private static async Task TranscodeMediaAsync(StorageFile inputFile, StorageFile outputFile, MediaEncodingProfile outputProfile, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
if (outputProfile.Video == null && outputProfile.Audio == null)
|
||||
{
|
||||
throw new InvalidOperationException("Target profile does not contain media");
|
||||
}
|
||||
|
||||
async Task<PrepareTranscodeResult> GetPrepareResult(bool hardwareAccelerationEnabled)
|
||||
{
|
||||
MediaTranscoder transcoder = new()
|
||||
{
|
||||
AlwaysReencode = false,
|
||||
HardwareAccelerationEnabled = hardwareAccelerationEnabled,
|
||||
};
|
||||
|
||||
return await transcoder.PrepareFileTranscodeAsync(inputFile, outputFile, outputProfile);
|
||||
}
|
||||
|
||||
var prepareResult = await GetPrepareResult(hardwareAccelerationEnabled: true);
|
||||
|
||||
if (!prepareResult.CanTranscode)
|
||||
{
|
||||
Logger.LogWarning($"Unable to transcode with hardware acceleration enabled, falling back to software; {nameof(prepareResult.FailureReason)}={prepareResult.FailureReason}");
|
||||
|
||||
prepareResult = await GetPrepareResult(hardwareAccelerationEnabled: false);
|
||||
}
|
||||
|
||||
if (!prepareResult.CanTranscode)
|
||||
{
|
||||
var message = ResourceLoaderInstance.ResourceLoader.GetString(prepareResult.FailureReason == TranscodeFailureReason.CodecNotFound ? "TranscodeErrorUnsupportedCodec" : "TranscodeErrorGeneral");
|
||||
throw new PasteActionException(message, new InvalidOperationException($"Error transcoding; {nameof(prepareResult.FailureReason)}={prepareResult.FailureReason}"));
|
||||
}
|
||||
|
||||
await prepareResult.TranscodeAsync().AsTask(cancellationToken, progress);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Models;
|
||||
@@ -18,19 +17,17 @@ namespace AdvancedPaste.Helpers;
|
||||
|
||||
public static class TransformHelpers
|
||||
{
|
||||
public static async Task<DataPackage> TransformAsync(PasteFormats format, DataPackageView clipboardData, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public static async Task<DataPackage> TransformAsync(PasteFormats format, DataPackageView clipboardData)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
PasteFormats.PlainText => await ToPlainTextAsync(clipboardData),
|
||||
PasteFormats.Markdown => await ToMarkdownAsync(clipboardData),
|
||||
PasteFormats.Json => await ToJsonAsync(clipboardData),
|
||||
PasteFormats.ImageToText => await ImageToTextAsync(clipboardData, cancellationToken),
|
||||
PasteFormats.PasteAsTxtFile => await ToTxtFileAsync(clipboardData, cancellationToken),
|
||||
PasteFormats.PasteAsPngFile => await ToPngFileAsync(clipboardData, cancellationToken),
|
||||
PasteFormats.PasteAsHtmlFile => await ToHtmlFileAsync(clipboardData, cancellationToken),
|
||||
PasteFormats.TranscodeToMp3 => await TranscodeHelpers.TranscodeToMp3Async(clipboardData, cancellationToken, progress),
|
||||
PasteFormats.TranscodeToMp4 => await TranscodeHelpers.TranscodeToMp4Async(clipboardData, cancellationToken, progress),
|
||||
PasteFormats.ImageToText => await ImageToTextAsync(clipboardData),
|
||||
PasteFormats.PasteAsTxtFile => await ToTxtFileAsync(clipboardData),
|
||||
PasteFormats.PasteAsPngFile => await ToPngFileAsync(clipboardData),
|
||||
PasteFormats.PasteAsHtmlFile => await ToHtmlFileAsync(clipboardData),
|
||||
PasteFormats.KernelQuery => throw new ArgumentException($"Unsupported format {format}", nameof(format)),
|
||||
PasteFormats.CustomTextTransformation => throw new ArgumentException($"Unsupported format {format}", nameof(format)),
|
||||
_ => throw new ArgumentException($"Unknown value {format}", nameof(format)),
|
||||
@@ -55,16 +52,16 @@ public static class TransformHelpers
|
||||
return CreateDataPackageFromText(await JsonHelper.ToJsonFromXmlOrCsvAsync(clipboardData));
|
||||
}
|
||||
|
||||
private static async Task<DataPackage> ImageToTextAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
|
||||
private static async Task<DataPackage> ImageToTextAsync(DataPackageView clipboardData)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var bitmap = await clipboardData.GetImageContentAsync() ?? throw new ArgumentException("No image content found in clipboard", nameof(clipboardData));
|
||||
var text = await OcrHelpers.ExtractTextAsync(bitmap, cancellationToken);
|
||||
var text = await OcrHelpers.ExtractTextAsync(bitmap);
|
||||
return CreateDataPackageFromText(text);
|
||||
}
|
||||
|
||||
private static async Task<DataPackage> ToPngFileAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
|
||||
private static async Task<DataPackage> ToPngFileAsync(DataPackageView clipboardData)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
@@ -75,25 +72,25 @@ public static class TransformHelpers
|
||||
encoder.SetSoftwareBitmap(clipboardBitmap);
|
||||
await encoder.FlushAsync();
|
||||
|
||||
return await CreateDataPackageFromFileContentAsync(pngStream.AsStreamForRead(), "png", cancellationToken);
|
||||
return await CreateDataPackageFromFileContentAsync(pngStream.AsStreamForRead(), "png");
|
||||
}
|
||||
|
||||
private static async Task<DataPackage> ToTxtFileAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
|
||||
private static async Task<DataPackage> ToTxtFileAsync(DataPackageView clipboardData)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var text = await clipboardData.GetTextOrHtmlTextAsync();
|
||||
return await CreateDataPackageFromFileContentAsync(text, "txt", cancellationToken);
|
||||
return await CreateDataPackageFromFileContentAsync(text, "txt");
|
||||
}
|
||||
|
||||
private static async Task<DataPackage> ToHtmlFileAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
|
||||
private static async Task<DataPackage> ToHtmlFileAsync(DataPackageView clipboardData)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var cfHtml = await clipboardData.GetHtmlContentAsync();
|
||||
var html = RemoveHtmlMetadata(cfHtml);
|
||||
|
||||
return await CreateDataPackageFromFileContentAsync(html, "html", cancellationToken);
|
||||
return await CreateDataPackageFromFileContentAsync(html, "html");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,7 +114,7 @@ public static class TransformHelpers
|
||||
return (startFragmentIndex == null || endFragmentIndex == null) ? cfHtml : cfHtml[startFragmentIndex.Value..endFragmentIndex.Value];
|
||||
}
|
||||
|
||||
private static async Task<DataPackage> CreateDataPackageFromFileContentAsync(string data, string fileExtension, CancellationToken cancellationToken)
|
||||
private static async Task<DataPackage> CreateDataPackageFromFileContentAsync(string data, string fileExtension)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
@@ -126,16 +123,16 @@ public static class TransformHelpers
|
||||
|
||||
var path = GetPasteAsFileTempFilePath(fileExtension);
|
||||
|
||||
await File.WriteAllTextAsync(path, data, cancellationToken);
|
||||
await File.WriteAllTextAsync(path, data);
|
||||
return await DataPackageHelpers.CreateFromFileAsync(path);
|
||||
}
|
||||
|
||||
private static async Task<DataPackage> CreateDataPackageFromFileContentAsync(Stream stream, string fileExtension, CancellationToken cancellationToken)
|
||||
private static async Task<DataPackage> CreateDataPackageFromFileContentAsync(Stream stream, string fileExtension)
|
||||
{
|
||||
var path = GetPasteAsFileTempFilePath(fileExtension);
|
||||
|
||||
using var fileStream = File.Create(path);
|
||||
await stream.CopyToAsync(fileStream, cancellationToken);
|
||||
await stream.CopyToAsync(fileStream);
|
||||
|
||||
return await DataPackageHelpers.CreateFromFileAsync(path);
|
||||
}
|
||||
|
||||
@@ -108,9 +108,7 @@ namespace AdvancedPaste.Settings
|
||||
(PasteFormats.ImageToText, [sourceAdditionalActions.ImageToText]),
|
||||
(PasteFormats.PasteAsTxtFile, [sourceAdditionalActions.PasteAsFile, sourceAdditionalActions.PasteAsFile.PasteAsTxtFile]),
|
||||
(PasteFormats.PasteAsPngFile, [sourceAdditionalActions.PasteAsFile, sourceAdditionalActions.PasteAsFile.PasteAsPngFile]),
|
||||
(PasteFormats.PasteAsHtmlFile, [sourceAdditionalActions.PasteAsFile, sourceAdditionalActions.PasteAsFile.PasteAsHtmlFile]),
|
||||
(PasteFormats.TranscodeToMp3, [sourceAdditionalActions.Transcode, sourceAdditionalActions.Transcode.TranscodeToMp3]),
|
||||
(PasteFormats.TranscodeToMp4, [sourceAdditionalActions.Transcode, sourceAdditionalActions.Transcode.TranscodeToMp4]),
|
||||
(PasteFormats.PasteAsHtmlFile, [sourceAdditionalActions.PasteAsFile, sourceAdditionalActions.PasteAsFile.PasteAsHtmlFile])
|
||||
];
|
||||
|
||||
_additionalActions.Clear();
|
||||
|
||||
@@ -13,7 +13,6 @@ public enum ClipboardFormat
|
||||
Text = 1 << 0,
|
||||
Html = 1 << 1,
|
||||
Audio = 1 << 2,
|
||||
Video = 1 << 3,
|
||||
Image = 1 << 4,
|
||||
File = 1 << 5, // output only for now
|
||||
Image = 1 << 3,
|
||||
File = 1 << 4, // output only for now
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class PasteActionError
|
||||
public static PasteActionError FromException(Exception ex) =>
|
||||
new()
|
||||
{
|
||||
Text = ex is PasteActionException ? ex.Message : ResourceLoaderInstance.ResourceLoader.GetString(ex is OperationCanceledException ? "PasteActionCanceled" : "PasteError"),
|
||||
Text = ex is PasteActionException ? ex.Message : ResourceLoaderInstance.ResourceLoader.GetString("PasteError"),
|
||||
Details = (ex as PasteActionException)?.AIServiceMessage ?? string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,34 +82,12 @@ public enum PasteFormats
|
||||
KernelFunctionDescription = "Takes HTML data in the clipboard and transforms it to an HTML file.")]
|
||||
PasteAsHtmlFile,
|
||||
|
||||
[PasteFormatMetadata(
|
||||
IsCoreAction = false,
|
||||
ResourceId = "TranscodeToMp3",
|
||||
IconGlyph = "\uE8D6",
|
||||
RequiresAIService = false,
|
||||
CanPreview = false,
|
||||
SupportedClipboardFormats = ClipboardFormat.Audio | ClipboardFormat.Video,
|
||||
IPCKey = AdvancedPasteTranscodeAction.PropertyNames.TranscodeToMp3,
|
||||
KernelFunctionDescription = "Takes an audio or video file in the clipboard and transcodes it to MP3.")]
|
||||
TranscodeToMp3,
|
||||
|
||||
[PasteFormatMetadata(
|
||||
IsCoreAction = false,
|
||||
ResourceId = "TranscodeToMp4",
|
||||
IconGlyph = "\uE714",
|
||||
RequiresAIService = false,
|
||||
CanPreview = false,
|
||||
SupportedClipboardFormats = ClipboardFormat.Video,
|
||||
IPCKey = AdvancedPasteTranscodeAction.PropertyNames.TranscodeToMp4,
|
||||
KernelFunctionDescription = "Takes a video file in the clipboard and transcodes it to MP4 (H.264/AAC).")]
|
||||
TranscodeToMp4,
|
||||
|
||||
[PasteFormatMetadata(
|
||||
IsCoreAction = false,
|
||||
IconGlyph = "\uE945",
|
||||
RequiresAIService = true,
|
||||
CanPreview = true,
|
||||
SupportedClipboardFormats = ClipboardFormat.Text | ClipboardFormat.Html | ClipboardFormat.Audio | ClipboardFormat.Video | ClipboardFormat.Image,
|
||||
SupportedClipboardFormats = ClipboardFormat.Text | ClipboardFormat.Html | ClipboardFormat.Audio | ClipboardFormat.Image,
|
||||
RequiresPrompt = true)]
|
||||
KernelQuery,
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AdvancedPaste.Services;
|
||||
|
||||
public interface ICustomTextTransformService
|
||||
{
|
||||
Task<string> TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress<double> progress);
|
||||
Task<string> TransformTextAsync(string prompt, string inputText);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
@@ -12,5 +10,5 @@ namespace AdvancedPaste.Services;
|
||||
|
||||
public interface IKernelService
|
||||
{
|
||||
Task<DataPackage> TransformClipboardAsync(string prompt, DataPackageView clipboardData, bool isSavedQuery, CancellationToken cancellationToken, IProgress<double> progress);
|
||||
Task<DataPackage> TransformClipboardAsync(string prompt, DataPackageView clipboardData, bool isSavedQuery);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Models;
|
||||
@@ -13,5 +11,5 @@ namespace AdvancedPaste.Services;
|
||||
|
||||
public interface IPasteFormatExecutor
|
||||
{
|
||||
Task<DataPackage> ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source, CancellationToken cancellationToken, IProgress<double> progress);
|
||||
Task<DataPackage> ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AdvancedPaste.Services;
|
||||
|
||||
public interface IPromptModerationService
|
||||
{
|
||||
Task ValidateAsync(string fullPrompt, CancellationToken cancellationToken);
|
||||
Task ValidateAsync(string fullPrompt);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -37,14 +36,12 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
|
||||
protected abstract AIServiceUsage GetAIServiceUsage(ChatMessageContent chatMessage);
|
||||
|
||||
public async Task<DataPackage> TransformClipboardAsync(string prompt, DataPackageView clipboardData, bool isSavedQuery, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public async Task<DataPackage> TransformClipboardAsync(string prompt, DataPackageView clipboardData, bool isSavedQuery)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var kernel = CreateKernel();
|
||||
kernel.SetDataPackageView(clipboardData);
|
||||
kernel.SetCancellationToken(cancellationToken);
|
||||
kernel.SetProgress(progress);
|
||||
|
||||
CacheKey cacheKey = new() { Prompt = prompt, AvailableFormats = await clipboardData.GetAvailableFormatsAsync() };
|
||||
var maybeCacheValue = _queryCacheService.ReadOrNull(cacheKey);
|
||||
@@ -54,7 +51,7 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
|
||||
try
|
||||
{
|
||||
(chatHistory, var usage) = cacheUsed ? await ExecuteCachedActionChain(kernel, maybeCacheValue.ActionChain) : await ExecuteAICompletion(kernel, prompt, cancellationToken);
|
||||
(chatHistory, var usage) = cacheUsed ? await ExecuteCachedActionChain(kernel, maybeCacheValue.ActionChain) : await ExecuteAICompletion(kernel, prompt);
|
||||
|
||||
LogResult(cacheUsed, isSavedQuery, kernel.GetOrAddActionChain(), usage);
|
||||
|
||||
@@ -87,7 +84,7 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
AdvancedPasteSemanticKernelErrorEvent errorEvent = new(ex is PasteActionModeratedException ? PasteActionModeratedException.ErrorDescription : ex.Message);
|
||||
PowerToysTelemetry.Log.WriteEvent(errorEvent);
|
||||
|
||||
if (ex is PasteActionException or OperationCanceledException)
|
||||
if (ex is PasteActionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
@@ -130,7 +127,7 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
return $"{combinedSystemMessage}{newLine}{newLine}User instructions:{newLine}{userPromptMessage.Content}";
|
||||
}
|
||||
|
||||
private async Task<(ChatHistory ChatHistory, AIServiceUsage Usage)> ExecuteAICompletion(Kernel kernel, string prompt, CancellationToken cancellationToken)
|
||||
private async Task<(ChatHistory ChatHistory, AIServiceUsage Usage)> ExecuteAICompletion(Kernel kernel, string prompt)
|
||||
{
|
||||
ChatHistory chatHistory = [];
|
||||
|
||||
@@ -144,10 +141,10 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
|
||||
chatHistory.AddUserMessage(prompt);
|
||||
|
||||
await _promptModerationService.ValidateAsync(GetFullPrompt(chatHistory), cancellationToken);
|
||||
await _promptModerationService.ValidateAsync(GetFullPrompt(chatHistory));
|
||||
|
||||
var chatResult = await kernel.GetRequiredService<IChatCompletionService>()
|
||||
.GetChatMessageContentAsync(chatHistory, PromptExecutionSettings, kernel, cancellationToken);
|
||||
.GetChatMessageContentAsync(chatHistory, PromptExecutionSettings, kernel);
|
||||
chatHistory.Add(chatResult);
|
||||
|
||||
var totalUsage = chatHistory.Select(GetAIServiceUsage)
|
||||
@@ -160,8 +157,6 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
{
|
||||
foreach (var item in actionChain)
|
||||
{
|
||||
kernel.GetCancellationToken().ThrowIfCancellationRequested();
|
||||
|
||||
if (item.Arguments.Count > 0)
|
||||
{
|
||||
await ExecutePromptTransformAsync(kernel, item.Format, item.Arguments[PromptParameterName]);
|
||||
@@ -213,14 +208,14 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
async dataPackageView =>
|
||||
{
|
||||
var input = await dataPackageView.GetTextAsync();
|
||||
string output = await GetPromptBasedOutput(format, prompt, input, kernel.GetCancellationToken(), kernel.GetProgress());
|
||||
string output = await GetPromptBasedOutput(format, prompt, input);
|
||||
return DataPackageHelpers.CreateFromText(output);
|
||||
});
|
||||
|
||||
private async Task<string> GetPromptBasedOutput(PasteFormats format, string prompt, string input, CancellationToken cancellationToken, IProgress<double> progress) =>
|
||||
private async Task<string> GetPromptBasedOutput(PasteFormats format, string prompt, string input) =>
|
||||
format switch
|
||||
{
|
||||
PasteFormats.CustomTextTransformation => await _customTextTransformService.TransformTextAsync(prompt, input, cancellationToken, progress),
|
||||
PasteFormats.CustomTextTransformation => await _customTextTransformService.TransformTextAsync(prompt, input),
|
||||
_ => throw new ArgumentException($"Unsupported format {format} for prompt transform", nameof(format)),
|
||||
};
|
||||
|
||||
@@ -228,7 +223,7 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
|
||||
ExecuteTransformAsync(
|
||||
kernel,
|
||||
new ActionChainItem(format, Arguments: []),
|
||||
async dataPackageView => await TransformHelpers.TransformAsync(format, dataPackageView, kernel.GetCancellationToken(), kernel.GetProgress()));
|
||||
async dataPackageView => await TransformHelpers.TransformAsync(format, dataPackageView));
|
||||
|
||||
private static async Task<string> ExecuteTransformAsync(Kernel kernel, ActionChainItem actionChainItem, Func<DataPackageView, Task<DataPackage>> transformFunc)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -24,11 +23,11 @@ public sealed class CustomTextTransformService(IAICredentialsProvider aiCredenti
|
||||
private readonly IAICredentialsProvider _aiCredentialsProvider = aiCredentialsProvider;
|
||||
private readonly IPromptModerationService _promptModerationService = promptModerationService;
|
||||
|
||||
private async Task<Completions> GetAICompletionAsync(string systemInstructions, string userMessage, CancellationToken cancellationToken)
|
||||
private async Task<Completions> GetAICompletionAsync(string systemInstructions, string userMessage)
|
||||
{
|
||||
var fullPrompt = systemInstructions + "\n\n" + userMessage;
|
||||
|
||||
await _promptModerationService.ValidateAsync(fullPrompt, cancellationToken);
|
||||
await _promptModerationService.ValidateAsync(fullPrompt);
|
||||
|
||||
OpenAIClient azureAIClient = new(_aiCredentialsProvider.Key);
|
||||
|
||||
@@ -42,8 +41,7 @@ public sealed class CustomTextTransformService(IAICredentialsProvider aiCredenti
|
||||
},
|
||||
Temperature = 0.01F,
|
||||
MaxTokens = 2000,
|
||||
},
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
if (response.Value.Choices[0].FinishReason == "length")
|
||||
{
|
||||
@@ -53,7 +51,7 @@ public sealed class CustomTextTransformService(IAICredentialsProvider aiCredenti
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<string> TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public async Task<string> TransformTextAsync(string prompt, string inputText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
@@ -82,7 +80,7 @@ Output:
|
||||
|
||||
try
|
||||
{
|
||||
var response = await GetAICompletionAsync(systemInstructions, userMessage, cancellationToken);
|
||||
var response = await GetAICompletionAsync(systemInstructions, userMessage);
|
||||
|
||||
var usage = response.Usage;
|
||||
AdvancedPasteGenerateCustomFormatEvent telemetryEvent = new(usage.PromptTokens, usage.CompletionTokens, ModelName);
|
||||
@@ -100,7 +98,7 @@ Output:
|
||||
AdvancedPasteGenerateCustomErrorEvent errorEvent = new(ex is PasteActionModeratedException ? PasteActionModeratedException.ErrorDescription : ex.Message);
|
||||
PowerToysTelemetry.Log.WriteEvent(errorEvent);
|
||||
|
||||
if (ex is PasteActionException or OperationCanceledException)
|
||||
if (ex is PasteActionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -19,12 +18,12 @@ public sealed class PromptModerationService(IAICredentialsProvider aiCredentials
|
||||
|
||||
private readonly IAICredentialsProvider _aiCredentialsProvider = aiCredentialsProvider;
|
||||
|
||||
public async Task ValidateAsync(string fullPrompt, CancellationToken cancellationToken)
|
||||
public async Task ValidateAsync(string fullPrompt)
|
||||
{
|
||||
try
|
||||
{
|
||||
ModerationClient moderationClient = new(ModelName, _aiCredentialsProvider.Key);
|
||||
var moderationClientResult = await moderationClient.ClassifyTextAsync(fullPrompt, cancellationToken);
|
||||
var moderationClientResult = await moderationClient.ClassifyTextAsync(fullPrompt);
|
||||
var moderationResult = moderationClientResult.Value;
|
||||
|
||||
Logger.LogDebug($"{nameof(PromptModerationService)}.{nameof(ValidateAsync)} complete; {nameof(moderationResult.Flagged)}={moderationResult.Flagged}");
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -18,7 +17,7 @@ public sealed class PasteFormatExecutor(IKernelService kernelService, ICustomTex
|
||||
private readonly IKernelService _kernelService = kernelService;
|
||||
private readonly ICustomTextTransformService _customTextTransformService = customTextTransformService;
|
||||
|
||||
public async Task<DataPackage> ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public async Task<DataPackage> ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source)
|
||||
{
|
||||
if (!pasteFormat.IsEnabled)
|
||||
{
|
||||
@@ -35,9 +34,9 @@ public sealed class PasteFormatExecutor(IKernelService kernelService, ICustomTex
|
||||
return await Task.Run(async () =>
|
||||
pasteFormat.Format switch
|
||||
{
|
||||
PasteFormats.KernelQuery => await _kernelService.TransformClipboardAsync(pasteFormat.Prompt, clipboardData, pasteFormat.IsSavedQuery, cancellationToken, progress),
|
||||
PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText(await _customTextTransformService.TransformTextAsync(pasteFormat.Prompt, await clipboardData.GetTextAsync(), cancellationToken, progress)),
|
||||
_ => await TransformHelpers.TransformAsync(format, clipboardData, cancellationToken, progress),
|
||||
PasteFormats.KernelQuery => await _kernelService.TransformClipboardAsync(pasteFormat.Prompt, clipboardData, pasteFormat.IsSavedQuery),
|
||||
PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText(await _customTextTransformService.TransformTextAsync(pasteFormat.Prompt, await clipboardData.GetTextAsync())),
|
||||
_ => await TransformHelpers.TransformAsync(format, clipboardData),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -135,9 +135,6 @@
|
||||
<data name="OpenAIApiKeyError" xml:space="preserve">
|
||||
<value>OpenAI request failed with status code: </value>
|
||||
</data>
|
||||
<data name="PasteActionCanceled" xml:space="preserve">
|
||||
<value>The paste operation was canceled</value>
|
||||
</data>
|
||||
<data name="PasteError" xml:space="preserve">
|
||||
<value>An error occurred during the paste operation</value>
|
||||
</data>
|
||||
@@ -191,19 +188,7 @@
|
||||
</data>
|
||||
<data name="PasteAsHtmlFile" xml:space="preserve">
|
||||
<value>Paste as .html file</value>
|
||||
</data>
|
||||
<data name="TranscodeToMp3" xml:space="preserve">
|
||||
<value>Transcode to .mp3</value>
|
||||
</data>
|
||||
<data name="TranscodeToMp4" xml:space="preserve">
|
||||
<value>Transcode to .mp4 (H.264/AAC)</value>
|
||||
</data>
|
||||
<data name="TranscodeErrorGeneral" xml:space="preserve">
|
||||
<value>An error occurred while transcoding media file</value>
|
||||
</data>
|
||||
<data name="TranscodeErrorUnsupportedCodec" xml:space="preserve">
|
||||
<value>The media file contains an unsupported codec</value>
|
||||
</data>
|
||||
<data name="PasteButtonAutomation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Paste</value>
|
||||
</data>
|
||||
@@ -222,9 +207,6 @@
|
||||
<data name="SendBtnToolTip.Text" xml:space="preserve">
|
||||
<value>Generate and paste data</value>
|
||||
</data>
|
||||
<data name="CancelBtnToolTip.Text" xml:space="preserve">
|
||||
<value>Cancel paste operation</value>
|
||||
</data>
|
||||
<data name="RegenerateBtnToolTip.Text" xml:space="preserve">
|
||||
<value>Regenerate</value>
|
||||
</data>
|
||||
@@ -234,9 +216,6 @@
|
||||
<data name="SendButtonAutomation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Generate and paste data</value>
|
||||
</data>
|
||||
<data name="CancelBtnAutomation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Cancel paste operation</value>
|
||||
</data>
|
||||
<data name="SettingsBtn.Content" xml:space="preserve">
|
||||
<value>Open settings</value>
|
||||
</data>
|
||||
|
||||
@@ -8,8 +8,6 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -31,7 +29,7 @@ using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
public sealed partial class OptionsViewModel : ObservableObject, IProgress<double>, IDisposable
|
||||
public sealed partial class OptionsViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly DispatcherTimer _clipboardTimer;
|
||||
@@ -39,8 +37,6 @@ namespace AdvancedPaste.ViewModels
|
||||
private readonly IPasteFormatExecutor _pasteFormatExecutor;
|
||||
private readonly IAICredentialsProvider _aiCredentialsProvider;
|
||||
|
||||
private CancellationTokenSource _pasteActionCancellationTokenSource;
|
||||
|
||||
public DataPackageView ClipboardData { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -69,11 +65,7 @@ namespace AdvancedPaste.ViewModels
|
||||
private bool _pasteFormatsDirty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isBusy;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(HasIndeterminateTransformProgress))]
|
||||
private double _transformProgress = double.NaN;
|
||||
private bool _busy;
|
||||
|
||||
public ObservableCollection<PasteFormat> StandardPasteFormats { get; } = [];
|
||||
|
||||
@@ -89,24 +81,9 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
|
||||
|
||||
public bool HasIndeterminateTransformProgress => double.IsNaN(TransformProgress);
|
||||
|
||||
private PasteFormats CustomAIFormat => _userSettings.IsAdvancedAIEnabled ? PasteFormats.KernelQuery : PasteFormats.CustomTextTransformation;
|
||||
|
||||
private bool Visible
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetMainWindow()?.Visible is true;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
return false; // window is closed
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool Visible => GetMainWindow()?.Visible is true;
|
||||
|
||||
public event EventHandler PreviewRequested;
|
||||
|
||||
@@ -212,12 +189,7 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
void UpdateFormats(ObservableCollection<PasteFormat> collection, IEnumerable<PasteFormat> pasteFormats)
|
||||
{
|
||||
// Hack: Clear collection via repeated RemoveAt to avoid this crash, which seems to occasionally occur when using Clear:
|
||||
// https://github.com/microsoft/microsoft-ui-xaml/issues/8684
|
||||
while (collection.Count > 0)
|
||||
{
|
||||
collection.RemoveAt(collection.Count - 1);
|
||||
}
|
||||
collection.Clear();
|
||||
|
||||
foreach (var format in FilterAndSort(pasteFormats))
|
||||
{
|
||||
@@ -242,13 +214,12 @@ namespace AdvancedPaste.ViewModels
|
||||
public void Dispose()
|
||||
{
|
||||
_clipboardTimer.Stop();
|
||||
_pasteActionCancellationTokenSource?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task ReadClipboardAsync()
|
||||
{
|
||||
if (IsBusy)
|
||||
if (Busy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -353,10 +324,6 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
await ClipboardHelper.TryCopyPasteAsync(package, HideWindow);
|
||||
Query = string.Empty;
|
||||
|
||||
// Delete any temp files created. A delay is needed to ensure the file is not in use by the target application -
|
||||
// for example, when pasting onto File Explorer, the paste operation will trigger a file copy.
|
||||
_ = Task.Run(() => package.GetView().TryCleanupAfterDelayAsync(TimeSpan.FromSeconds(30)));
|
||||
}
|
||||
|
||||
// Command to select the previous custom format
|
||||
@@ -395,7 +362,7 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
internal async Task ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source)
|
||||
{
|
||||
if (IsBusy)
|
||||
if (Busy)
|
||||
{
|
||||
Logger.LogWarning($"Execution of {pasteFormat.Format} from {source} suppressed as busy");
|
||||
return;
|
||||
@@ -410,18 +377,16 @@ namespace AdvancedPaste.ViewModels
|
||||
var elapsedWatch = Stopwatch.StartNew();
|
||||
Logger.LogDebug($"Started executing {pasteFormat.Format} from source {source}");
|
||||
|
||||
IsBusy = true;
|
||||
_pasteActionCancellationTokenSource = new();
|
||||
TransformProgress = double.NaN;
|
||||
Busy = true;
|
||||
PasteActionError = PasteActionError.None;
|
||||
Query = pasteFormat.Query;
|
||||
|
||||
try
|
||||
{
|
||||
// Minimum time to show busy spinner for AI actions when triggered by global keyboard shortcut.
|
||||
var aiActionMinTaskTime = TimeSpan.FromSeconds(1.5);
|
||||
var aiActionMinTaskTime = TimeSpan.FromSeconds(2);
|
||||
var delayTask = (Visible && source == PasteActionSource.GlobalKeyboardShortcut) ? Task.Delay(aiActionMinTaskTime) : Task.CompletedTask;
|
||||
var dataPackage = await _pasteFormatExecutor.ExecutePasteFormatAsync(pasteFormat, source, _pasteActionCancellationTokenSource.Token, this);
|
||||
var dataPackage = await _pasteFormatExecutor.ExecutePasteFormatAsync(pasteFormat, source);
|
||||
|
||||
await delayTask;
|
||||
|
||||
@@ -445,9 +410,7 @@ namespace AdvancedPaste.ViewModels
|
||||
PasteActionError = PasteActionError.FromException(ex);
|
||||
}
|
||||
|
||||
IsBusy = false;
|
||||
_pasteActionCancellationTokenSource?.Dispose();
|
||||
_pasteActionCancellationTokenSource = null;
|
||||
Busy = false;
|
||||
elapsedWatch.Stop();
|
||||
Logger.LogDebug($"Finished executing {pasteFormat.Format} from source {source}; timeTakenMs={elapsedWatch.ElapsedMilliseconds}");
|
||||
}
|
||||
@@ -521,26 +484,5 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
return IsAllowedByGPO && _aiCredentialsProvider.Refresh();
|
||||
}
|
||||
|
||||
public async Task CancelPasteActionAsync()
|
||||
{
|
||||
if (_pasteActionCancellationTokenSource != null)
|
||||
{
|
||||
await _pasteActionCancellationTokenSource.CancelAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void IProgress<double>.Report(double value)
|
||||
{
|
||||
ReportProgress(value);
|
||||
}
|
||||
|
||||
private void ReportProgress(double value)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
TransformProgress = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,6 @@ void Trace::AdvancedPaste_SettingsTelemetry(const PowertoyModuleIface::Hotkey& p
|
||||
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"ImageToText"), "ImageToTextHotkey"),
|
||||
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsTxtFile"), "PasteAsTxtFileHotkey"),
|
||||
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsPngFile"), "PasteAsPngFileHotkey"),
|
||||
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsHtmlFile"), "PasteAsHtmlFileHotkey"),
|
||||
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"TranscodeToMp3"), "TranscodeToMp3Hotkey"),
|
||||
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"TranscodeToMp4"), "TranscodeToMp4Hotkey")
|
||||
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsHtmlFile"), "PasteAsHtmlFileHotkey")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,108 +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;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Hosts.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class HostModuleTests : UITestBase
|
||||
{
|
||||
public HostModuleTests()
|
||||
: base(PowerToysModule.Hosts)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if Empty-view is shown when no entries are present.
|
||||
/// And 'Add an entry' button from Empty-view is functional.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestEmptyView()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// 'Add an entry' button (only show-up when list is empty) should be visible
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 1, "'Add an entry' button should be visible in the empty view");
|
||||
|
||||
// Click 'Add an entry' from empty-view for adding Host override rule
|
||||
this.Find<Button>(By.Name("Add an entry")).Click();
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", false, false);
|
||||
|
||||
// Should have one row now and not more empty view
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if 'New entry' button is functional
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestAddingEntry()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0, "Should have no row after removing all");
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
}
|
||||
|
||||
private void AddEntry(string ip, string host, bool active = true, bool clickAddEntryButton = true)
|
||||
{
|
||||
if (clickAddEntryButton)
|
||||
{
|
||||
// Click 'Add an entry' for adding Host override rule
|
||||
this.Find<Button>(By.Name("New entry")).Click();
|
||||
}
|
||||
|
||||
// Adding a new host override localhost -> 192.168.0.1
|
||||
Assert.IsFalse(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Disabled by default");
|
||||
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Address")).SetText(ip, false).Text == ip);
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Hosts")).SetText(host, false).Text == host);
|
||||
|
||||
this.Find<ToggleSwitch>(By.Name("Active")).Toggle(active);
|
||||
|
||||
Assert.IsTrue(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Enabled after providing valid inputs");
|
||||
|
||||
// Add the entry
|
||||
this.Find<Button>(By.Name("Add")).Click();
|
||||
|
||||
// 0.5 second delay after adding an entry
|
||||
Task.Delay(500).Wait();
|
||||
}
|
||||
|
||||
private void CloseWarningDialog()
|
||||
{
|
||||
// Find 'Accept' button which come in 'Warning' dialog
|
||||
if (this.FindAll<Window>(By.Name("Warning")).Count > 0 &&
|
||||
this.FindAll<Button>(By.Name("Accept")).Count > 0)
|
||||
{
|
||||
// Hide Warning dialog if any
|
||||
this.Find<Button>(By.Name("Accept")).Click();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAllEntries()
|
||||
{
|
||||
// Delete all existing host-override rules
|
||||
foreach (var deleteBtn in this.FindAll<Button>(By.Name("Delete")))
|
||||
{
|
||||
deleteBtn.Click();
|
||||
this.Find<Button>(By.Name("Yes")).Click();
|
||||
}
|
||||
|
||||
// Should have no row left, and no more delete button
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}</ProjectGuid>
|
||||
<RootNamespace>PowerToys.Hosts.UITests</RootNamespace>
|
||||
<AssemblyName>PowerToys.Hosts.UITests</AssemblyName>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -192,7 +192,7 @@ namespace WorkspacesCsharpLibrary.Models
|
||||
else
|
||||
{
|
||||
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_(:?x64|arm64)__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
Match match = packagedAppPathRegex.Match(appPath);
|
||||
_isPackagedApp = match.Success;
|
||||
if (match.Success)
|
||||
|
||||
@@ -62,10 +62,10 @@ namespace PlacementHelper
|
||||
else
|
||||
{
|
||||
placement.showCmd = SW_RESTORE;
|
||||
ScreenToWorkAreaCoords(window, monitor, rect);
|
||||
placement.rcNormalPosition = rect;
|
||||
}
|
||||
|
||||
ScreenToWorkAreaCoords(window, monitor, rect);
|
||||
placement.rcNormalPosition = rect;
|
||||
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
|
||||
|
||||
auto result = ::SetWindowPlacement(window, &placement);
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
<v:VisibilityBoolConverter x:Key="VisibilityBoolConverter" />
|
||||
<v:EnumToIntConverter x:Key="EnumToIntConverter" />
|
||||
<v:AccessTextToTextConverter x:Key="AccessTextToTextConverter" />
|
||||
<v:NumberBoxValueConverter x:Key="NumberBoxValueConverter" />
|
||||
<v:ZeroToEmptyStringNumberFormatter x:Key="ZeroToEmptyStringNumberFormatter" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -10,55 +10,29 @@ using System.Windows.Data;
|
||||
|
||||
using ImageResizer.Properties;
|
||||
|
||||
namespace ImageResizer.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Converts between double and string for text-based controls bound to Width or Height fields.
|
||||
/// Optionally returns localized "Auto" text when the underlying value is 0, letting the UI show,
|
||||
/// for example "(auto) x 1024 pixels".
|
||||
/// </summary>
|
||||
[ValueConversion(typeof(double), typeof(string))]
|
||||
internal class AutoDoubleConverter : IValueConverter
|
||||
namespace ImageResizer.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a double to a string, optionally showing "Auto" for 0 values. NaN values are
|
||||
/// converted to empty strings.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert from <see cref="double"/> to
|
||||
/// <see cref="string"/>.</param>
|
||||
/// <param name="targetType">The conversion target type. <see cref="string"/> here.</param>
|
||||
/// <param name="parameter">Set to "Auto" to return the localized "Auto" string if the
|
||||
/// value is 0.</param>
|
||||
/// <param name="culture">The <see cref="CultureInfo"/> to use for the number formatting.
|
||||
/// </param>
|
||||
/// <returns>The string representation of the passed-in value.</returns>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value switch
|
||||
[ValueConversion(typeof(double), typeof(string))]
|
||||
internal class AutoDoubleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
double d => d switch
|
||||
{
|
||||
double.NaN => "0",
|
||||
0 => (string)parameter == "Auto" ? Resources.Input_Auto : "0",
|
||||
_ => d.ToString(culture),
|
||||
},
|
||||
var d = (double)value;
|
||||
|
||||
_ => "0",
|
||||
};
|
||||
return d != 0
|
||||
? d.ToString(culture)
|
||||
: (string)parameter == "Auto"
|
||||
? Resources.Input_Auto
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the string representation back to a double, returning 0 if the string is empty,
|
||||
/// null or not a valid number in the specified culture.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to convert.</param>
|
||||
/// <param name="targetType">The conversion target type. <see cref="double"/> here.</param>
|
||||
/// <param name="parameter">Converter parameter. Unused.</param>
|
||||
/// <param name="culture">The <see cref="CultureInfo"/> to use for the text parsing.</param>
|
||||
/// <returns>The corresponding double value.</returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value switch
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
null or "" => 0,
|
||||
string text when double.TryParse(text, NumberStyles.Any, culture, out double result) => result,
|
||||
_ => 0,
|
||||
};
|
||||
var text = (string)value;
|
||||
|
||||
return !string.IsNullOrEmpty(text)
|
||||
? double.Parse(text, culture)
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:m="clr-namespace:ImageResizer.Models"
|
||||
xmlns:p="clr-namespace:ImageResizer.Properties"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:v="clr-namespace:ImageResizer.Views">
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -115,12 +114,8 @@
|
||||
KeyDown="Button_KeyDown"
|
||||
Minimum="0"
|
||||
SpinButtonPlacementMode="Inline">
|
||||
<ui:NumberBox.NumberFormatter>
|
||||
<v:ZeroToEmptyStringNumberFormatter />
|
||||
</ui:NumberBox.NumberFormatter>
|
||||
<ui:NumberBox.Value>
|
||||
<Binding
|
||||
Converter="{StaticResource NumberBoxValueConverter}"
|
||||
ElementName="SizeComboBox"
|
||||
Mode="TwoWay"
|
||||
Path="SelectedValue.Width"
|
||||
@@ -148,12 +143,8 @@
|
||||
Minimum="0"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Visibility="{Binding ElementName=SizeComboBox, Path=SelectedValue.ShowHeight, Converter={StaticResource BoolValueConverter}}">
|
||||
<ui:NumberBox.NumberFormatter>
|
||||
<v:ZeroToEmptyStringNumberFormatter />
|
||||
</ui:NumberBox.NumberFormatter>
|
||||
<ui:NumberBox.Value>
|
||||
<Binding
|
||||
Converter="{StaticResource NumberBoxValueConverter}"
|
||||
ElementName="SizeComboBox"
|
||||
Mode="TwoWay"
|
||||
Path="SelectedValue.Height"
|
||||
|
||||
@@ -1,32 +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;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace ImageResizer.Views;
|
||||
|
||||
public class NumberBoxValueConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the underlying double value to a display-friendly format. Ensures that NaN values
|
||||
/// are not propagated to the UI.
|
||||
/// </summary>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value is double d && double.IsNaN(d) ? 0 : value;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the user input back to the underlying double value. If the input is not a valid
|
||||
/// number, 0 is returned.
|
||||
/// </summary>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value switch
|
||||
{
|
||||
null => 0,
|
||||
double d when double.IsNaN(d) => 0,
|
||||
string str when !double.TryParse(str, out _) => 0,
|
||||
_ => value,
|
||||
};
|
||||
}
|
||||
@@ -1,37 +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;
|
||||
using System.Globalization;
|
||||
using Wpf.Ui.Controls;
|
||||
|
||||
namespace ImageResizer.Views;
|
||||
|
||||
public class ZeroToEmptyStringNumberFormatter : INumberFormatter, INumberParser
|
||||
{
|
||||
public string FormatDouble(double? value) => value switch
|
||||
{
|
||||
null => string.Empty,
|
||||
0 => string.Empty,
|
||||
_ => value.Value.ToString(CultureInfo.CurrentCulture),
|
||||
};
|
||||
|
||||
public double? ParseDouble(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return double.TryParse(value, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) ? result : 0;
|
||||
}
|
||||
|
||||
public string FormatInt(int? value) => throw new NotImplementedException();
|
||||
|
||||
public string FormatUInt(uint? value) => throw new NotImplementedException();
|
||||
|
||||
public int? ParseInt(string value) => throw new NotImplementedException();
|
||||
|
||||
public uint? ParseUInt(string value) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
#include "winres.h"
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -34,9 +34,6 @@
|
||||
<RootNamespace>KeyboardManagerEditorLibraryWrapper</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetName>PowerToys.KeyboardManagerEditorLibraryWrapper</TargetName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
@@ -221,7 +218,6 @@
|
||||
<ClInclude Include="framework.h" />
|
||||
<ClInclude Include="KeyboardManagerEditorLibraryWrapper.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
@@ -235,9 +231,6 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="KeyboardManagerEditorLibraryWrapper.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
<ClInclude Include="KeyboardManagerEditorLibraryWrapper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
@@ -42,9 +39,4 @@
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="KeyboardManagerEditorLibraryWrapper.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by AlwaysOnTopModuleInterface.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys Keyboard Manager Editor Library Wrapper"
|
||||
#define INTERNAL_NAME "PowerToys.KeyboardManagerEditorLibraryWrapper"
|
||||
#define ORIGINAL_FILENAME "PowerToys.KeyboardManagerEditorLibraryWrapper.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
@@ -7,8 +7,6 @@
|
||||
<AssemblyTitle>PowerToys.Run</AssemblyTitle>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
<!-- For some users, it crashes in ExtractAssociatedIcon https://github.com/microsoft/PowerToys/issues/37254. Workaround suggested here https://github.com/dotnet/wpf/issues/10483 was to disable CETCompat -->
|
||||
<CETCompat>false</CETCompat>
|
||||
<UseWindowsForms>False</UseWindowsForms>
|
||||
<StartupObject>PowerLauncher.App</StartupObject>
|
||||
<ApplicationIcon>Assets\PowerLauncher\RunResource.ico</ApplicationIcon>
|
||||
|
||||
@@ -136,25 +136,13 @@ namespace Wox.Infrastructure
|
||||
var link = new ShellLink();
|
||||
const int STGM_READ = 0;
|
||||
|
||||
// Make sure not to open exclusive handles.
|
||||
// See: https://github.com/microsoft/WSL/issues/11276
|
||||
const int STGM_SHARE_DENY_NONE = 0x00000040;
|
||||
const int STGM_TRANSACTED = 0x00010000;
|
||||
|
||||
try
|
||||
{
|
||||
((IPersistFile)link).Load(path, STGM_READ | STGM_SHARE_DENY_NONE | STGM_TRANSACTED);
|
||||
((IPersistFile)link).Load(path, STGM_READ);
|
||||
}
|
||||
catch (System.IO.FileNotFoundException ex)
|
||||
{
|
||||
Log.Exception("Path could not be retrieved " + path, ex, GetType(), path);
|
||||
Marshal.ReleaseComObject(link);
|
||||
return string.Empty;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Exception("Exception loading path " + path, ex, GetType(), path);
|
||||
Marshal.ReleaseComObject(link);
|
||||
Log.Exception("Path could not be retrieved", ex, GetType(), path);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -677,7 +677,7 @@ namespace PowerAccent.Core
|
||||
LetterKey.VK_O => new string[] { "ο", "ό", "ω", "ώ" },
|
||||
LetterKey.VK_P => new string[] { "π", "φ", "ψ" },
|
||||
LetterKey.VK_R => new string[] { "ρ" },
|
||||
LetterKey.VK_S => new string[] { "σ", "ς" },
|
||||
LetterKey.VK_S => new string[] { "σ" },
|
||||
LetterKey.VK_T => new string[] { "τ", "θ", "ϑ" },
|
||||
LetterKey.VK_U => new string[] { "υ", "ύ" },
|
||||
LetterKey.VK_X => new string[] { "ξ" },
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
@@ -37,7 +36,4 @@ public sealed partial class AdvancedPasteAdditionalAction : Observable, IAdvance
|
||||
get => _isShown;
|
||||
set => Set(ref _isShown, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<IAdvancedPasteAction> SubActions => [];
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ public sealed class AdvancedPasteAdditionalActions
|
||||
{
|
||||
public const string ImageToText = "image-to-text";
|
||||
public const string PasteAsFile = "paste-as-file";
|
||||
public const string Transcode = "transcode";
|
||||
}
|
||||
|
||||
[JsonPropertyName(PropertyNames.ImageToText)]
|
||||
@@ -23,22 +22,6 @@ public sealed class AdvancedPasteAdditionalActions
|
||||
[JsonPropertyName(PropertyNames.PasteAsFile)]
|
||||
public AdvancedPastePasteAsFileAction PasteAsFile { get; init; } = new();
|
||||
|
||||
[JsonPropertyName(PropertyNames.Transcode)]
|
||||
public AdvancedPasteTranscodeAction Transcode { get; init; } = new();
|
||||
|
||||
public IEnumerable<IAdvancedPasteAction> GetAllActions()
|
||||
{
|
||||
Queue<IAdvancedPasteAction> queue = new([ImageToText, PasteAsFile, Transcode]);
|
||||
|
||||
while (queue.Count != 0)
|
||||
{
|
||||
var action = queue.Dequeue();
|
||||
yield return action;
|
||||
|
||||
foreach (var subAction in action.SubActions)
|
||||
{
|
||||
queue.Enqueue(subAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
[JsonIgnore]
|
||||
public IEnumerable<IAdvancedPasteAction> AllActions => new IAdvancedPasteAction[] { ImageToText, PasteAsFile }.Concat(PasteAsFile.SubActions);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
@@ -99,9 +98,6 @@ public sealed class AdvancedPasteCustomAction : Observable, IAdvancedPasteAction
|
||||
private set => Set(ref _isValid, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<IAdvancedPasteAction> SubActions => [];
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
AdvancedPasteCustomAction clone = new();
|
||||
|
||||
@@ -52,5 +52,5 @@ public sealed class AdvancedPastePasteAsFileAction : Observable, IAdvancedPasteA
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<IAdvancedPasteAction> SubActions => [PasteAsTxtFile, PasteAsPngFile, PasteAsHtmlFile];
|
||||
public IEnumerable<AdvancedPasteAdditionalAction> SubActions => [PasteAsTxtFile, PasteAsPngFile, PasteAsHtmlFile];
|
||||
}
|
||||
|
||||
@@ -1,47 +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.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
public sealed class AdvancedPasteTranscodeAction : Observable, IAdvancedPasteAction
|
||||
{
|
||||
public static class PropertyNames
|
||||
{
|
||||
public const string TranscodeToMp3 = "transcode-to-mp3";
|
||||
public const string TranscodeToMp4 = "transcode-to-mp4";
|
||||
}
|
||||
|
||||
private AdvancedPasteAdditionalAction _transcodeToMp3 = new();
|
||||
private AdvancedPasteAdditionalAction _transcodeToMp4 = new();
|
||||
private bool _isShown = true;
|
||||
|
||||
[JsonPropertyName("isShown")]
|
||||
public bool IsShown
|
||||
{
|
||||
get => _isShown;
|
||||
set => Set(ref _isShown, value);
|
||||
}
|
||||
|
||||
[JsonPropertyName(PropertyNames.TranscodeToMp3)]
|
||||
public AdvancedPasteAdditionalAction TranscodeToMp3
|
||||
{
|
||||
get => _transcodeToMp3;
|
||||
init => Set(ref _transcodeToMp3, value);
|
||||
}
|
||||
|
||||
[JsonPropertyName(PropertyNames.TranscodeToMp4)]
|
||||
public AdvancedPasteAdditionalAction TranscodeToMp4
|
||||
{
|
||||
get => _transcodeToMp4;
|
||||
init => Set(ref _transcodeToMp4, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<IAdvancedPasteAction> SubActions => [TranscodeToMp3, TranscodeToMp4];
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library;
|
||||
@@ -10,6 +9,4 @@ namespace Microsoft.PowerToys.Settings.UI.Library;
|
||||
public interface IAdvancedPasteAction : INotifyPropertyChanged
|
||||
{
|
||||
public bool IsShown { get; }
|
||||
|
||||
public IEnumerable<IAdvancedPasteAction> SubActions { get; }
|
||||
}
|
||||
|
||||
@@ -1,61 +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;
|
||||
using System.Globalization;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts between double and string for text-based controls bound to Width or Height fields.
|
||||
/// Optionally returns localized "Auto" text when the underlying value is 0, letting the UI show,
|
||||
/// for example "(auto) x 1024 pixels".
|
||||
/// </summary>
|
||||
public sealed partial class ImageResizerDoubleToAutoConverter : IValueConverter
|
||||
{
|
||||
private static readonly string AutoText =
|
||||
Helpers.ResourceLoaderInstance.ResourceLoader.GetString("ImageResizer_AutoText");
|
||||
|
||||
/// <summary>
|
||||
/// Converts a double to a string, optionally showing "Auto" for 0 values. NaN values are
|
||||
/// converted to empty strings.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert from <see cref="double"/> to
|
||||
/// <see cref="string"/>.</param>
|
||||
/// <param name="targetType">The conversion target type. <see cref="string"/> here.</param>
|
||||
/// <param name="parameter">Set to "Auto" to return the localized "Auto" string if the
|
||||
/// value is 0.</param>
|
||||
/// <param name="language">Ignored.</param>
|
||||
/// <returns>The string representation of the passed-in value.</returns>
|
||||
public object Convert(object value, Type targetType, object parameter, string language) =>
|
||||
value switch
|
||||
{
|
||||
double d => d switch
|
||||
{
|
||||
double.NaN => "0",
|
||||
0 => (string)parameter == "Auto" ? AutoText : "0",
|
||||
_ => d.ToString(CultureInfo.CurrentCulture),
|
||||
},
|
||||
|
||||
_ => "0",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts the string representation back to a double, returning 0 if the string is empty,
|
||||
/// null or not a valid number in the specified culture.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to convert.</param>
|
||||
/// <param name="targetType">The conversion target type. <see cref="double"/> here.</param>
|
||||
/// <param name="parameter">Converter parameter. Unused.</param>
|
||||
/// <param name="language">Ignored.</param>
|
||||
/// <returns>The corresponding double value.</returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
value switch
|
||||
{
|
||||
null or "" => 0.0,
|
||||
string text when double.TryParse(text, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) => result,
|
||||
_ => 0.0,
|
||||
};
|
||||
}
|
||||
@@ -1,31 +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;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Converters;
|
||||
|
||||
public partial class ImageResizerNumberBoxValueConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the underlying double value to a display-friendly format. Ensures that NaN values
|
||||
/// are not propagated to the UI.
|
||||
/// </summary>
|
||||
public object Convert(object value, Type targetType, object parameter, string language) =>
|
||||
value is double d && double.IsNaN(d) ? 0.0 : value;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the user input back to the underlying double value. If the input is not a valid
|
||||
/// number, a double with value 0 is returned.
|
||||
/// </summary>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
value switch
|
||||
{
|
||||
null => 0.0,
|
||||
double d when double.IsNaN(d) => 0.0,
|
||||
string str when !double.TryParse(str, out _) => 0.0,
|
||||
_ => value,
|
||||
};
|
||||
}
|
||||
@@ -1,39 +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;
|
||||
using System.Globalization;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Converters;
|
||||
|
||||
public partial class ImageResizerZeroToEmptyStringNumberFormatter
|
||||
{
|
||||
public string Format(long value) => throw new NotImplementedException();
|
||||
|
||||
public string Format(ulong value) => throw new NotImplementedException();
|
||||
|
||||
public string Format(double value) => throw new NotImplementedException();
|
||||
|
||||
public string FormatDouble(double? value) => value switch
|
||||
{
|
||||
null => string.Empty,
|
||||
0 => string.Empty,
|
||||
_ => value.Value.ToString(CultureInfo.CurrentCulture),
|
||||
};
|
||||
|
||||
public double? ParseDouble(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return double.TryParse(text, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) ? result : 0.0;
|
||||
}
|
||||
|
||||
public long? ParseInt(string text) => throw new NotImplementedException();
|
||||
|
||||
public ulong? ParseUInt(string text) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -1,48 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
{
|
||||
public sealed class ActionMessage
|
||||
{
|
||||
[JsonPropertyName("action")]
|
||||
public SettingsAction Action { get; set; }
|
||||
|
||||
public static ActionMessage Create(string actionName)
|
||||
{
|
||||
return new ActionMessage
|
||||
{
|
||||
Action = new SettingsAction
|
||||
{
|
||||
PublishedDate = new SettingsGeneral
|
||||
{
|
||||
ActionName = actionName,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Those are just a define for one simple struct")]
|
||||
public sealed class SettingsAction
|
||||
{
|
||||
[JsonPropertyName("general")]
|
||||
public SettingsGeneral PublishedDate { get; set; }
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Those are just a define for one simple struct")]
|
||||
public sealed class SettingsGeneral
|
||||
{
|
||||
[JsonPropertyName("action_name")]
|
||||
public string ActionName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
{
|
||||
internal sealed partial class AsyncCommand : ICommand
|
||||
internal sealed class AsyncCommand : ICommand
|
||||
{
|
||||
private readonly Func<Task> _execute;
|
||||
private readonly Func<bool> _canExecute;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
}
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
public partial class IndexedObservableCollection<T> : ObservableCollection<IndexedItem<T>>
|
||||
public class IndexedObservableCollection<T> : ObservableCollection<IndexedItem<T>>
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
{
|
||||
public IndexedObservableCollection(IEnumerable<T> items)
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
|
||||
internal static int Size
|
||||
{
|
||||
get { return Marshal.SizeOf<INPUT>(); }
|
||||
get { return Marshal.SizeOf(typeof(INPUT)); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
{
|
||||
// Contains information for a release. Used to deserialize release JSON info from GitHub.
|
||||
public sealed class PowerToysReleaseInfo
|
||||
{
|
||||
[JsonPropertyName("published_at")]
|
||||
public DateTimeOffset PublishedDate { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; }
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
public string ReleaseNotes { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using System.Windows.Input;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
{
|
||||
public partial class RelayCommand : ICommand
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private readonly Func<bool> _canExecute;
|
||||
@@ -33,7 +33,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "abstract T and abstract")]
|
||||
public partial class RelayCommand<T> : ICommand
|
||||
public class RelayCommand<T> : ICommand
|
||||
{
|
||||
private readonly Action<T> execute;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
@@ -20,9 +20,9 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_placementPath);
|
||||
var placement = JsonSerializer.Deserialize<WINDOWPLACEMENT>(json, SourceGenerationContextContext.Default.WINDOWPLACEMENT);
|
||||
var placement = JsonSerializer.Deserialize<WINDOWPLACEMENT>(json);
|
||||
|
||||
placement.Length = Marshal.SizeOf<WINDOWPLACEMENT>();
|
||||
placement.Length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
|
||||
placement.Flags = 0;
|
||||
placement.ShowCmd = (placement.ShowCmd == NativeMethods.SW_SHOWMAXIMIZED) ? NativeMethods.SW_SHOWMAXIMIZED : NativeMethods.SW_SHOWNORMAL;
|
||||
return placement;
|
||||
@@ -40,7 +40,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
_ = NativeMethods.GetWindowPlacement(handle, out var placement);
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(placement, SourceGenerationContextContext.Default.WINDOWPLACEMENT);
|
||||
var json = JsonSerializer.Serialize(placement);
|
||||
File.WriteAllText(_placementPath, json);
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
|
||||
[JsonSerializable(typeof(WINDOWPLACEMENT))]
|
||||
[JsonSerializable(typeof(AdvancedPasteSettings))]
|
||||
[JsonSerializable(typeof(Dictionary<string, List<string>>))]
|
||||
[JsonSerializable(typeof(AlwaysOnTopSettings))]
|
||||
[JsonSerializable(typeof(ColorPickerSettings))]
|
||||
[JsonSerializable(typeof(CropAndLockSettings))]
|
||||
[JsonSerializable(typeof(FileLocksmithSettings))]
|
||||
[JsonSerializable(typeof(MeasureToolSettings))]
|
||||
[JsonSerializable(typeof(MouseWithoutBordersSettings))]
|
||||
[JsonSerializable(typeof(NewPlusSettings))]
|
||||
[JsonSerializable(typeof(PeekSettings))]
|
||||
[JsonSerializable(typeof(PowerLauncherSettings))]
|
||||
[JsonSerializable(typeof(PowerOcrSettings))]
|
||||
[JsonSerializable(typeof(RegistryPreviewSettings))]
|
||||
[JsonSerializable(typeof(WorkspacesSettings))]
|
||||
[JsonSerializable(typeof(IList<PowerToysReleaseInfo>))]
|
||||
[JsonSerializable(typeof(ActionMessage))]
|
||||
public sealed partial class SourceGenerationContextContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -14,7 +14,6 @@ using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
@@ -168,7 +167,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
|
||||
try
|
||||
{
|
||||
var requestedSettings = JsonSerializer.Deserialize<Dictionary<string, List<string>>>(File.ReadAllText(ipcFileName), SourceGenerationContextContext.Default.DictionaryStringListString);
|
||||
var requestedSettings = JsonSerializer.Deserialize<Dictionary<string, List<string>>>(File.ReadAllText(ipcFileName));
|
||||
File.WriteAllText(ipcFileName, GetSettingCommandLineCommand.Execute(requestedSettings));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,38 +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 Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Controls;
|
||||
|
||||
public partial class ImageResizerDimensionsNumberBox : NumberBox
|
||||
{
|
||||
public ImageResizerDimensionsNumberBox()
|
||||
{
|
||||
this.Loaded += (_, _) => UpdateDisplayText();
|
||||
|
||||
this.ValueChanged += (_, _) => UpdateDisplayText();
|
||||
|
||||
this.GotFocus += (s, e) =>
|
||||
{
|
||||
// Show "0" in the UI when focused on the empty value. This ensures that the spinbutton
|
||||
// controls are usable.
|
||||
if (Value is double.NaN)
|
||||
{
|
||||
Value = 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
this.LostFocus += (_, _) => UpdateDisplayText();
|
||||
}
|
||||
|
||||
private void UpdateDisplayText()
|
||||
{
|
||||
if (FocusState == FocusState.Unfocused && Value == 0)
|
||||
{
|
||||
Text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -30,6 +29,22 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
{
|
||||
public sealed partial class OobeWhatsNew : Page
|
||||
{
|
||||
// Contains information for a release. Used to deserialize release JSON info from GitHub.
|
||||
private sealed class PowerToysReleaseInfo
|
||||
{
|
||||
[JsonPropertyName("published_at")]
|
||||
public DateTimeOffset PublishedDate { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; }
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
public string ReleaseNotes { get; set; }
|
||||
}
|
||||
|
||||
public OobePowerToysModule ViewModel { get; set; }
|
||||
|
||||
public bool ShowDataDiagnosticsInfoBar => GetShowDataDiagnosticsInfoBar();
|
||||
@@ -96,7 +111,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
// https://docs.github.com/rest/overview/resources-in-the-rest-api#user-agent-required
|
||||
getReleaseInfoClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "PowerToys");
|
||||
releaseNotesJSON = await getReleaseInfoClient.GetStringAsync("https://api.github.com/repos/microsoft/PowerToys/releases");
|
||||
IList<PowerToysReleaseInfo> releases = JsonSerializer.Deserialize<IList<PowerToysReleaseInfo>>(releaseNotesJSON, SourceGenerationContextContext.Default.IListPowerToysReleaseInfo);
|
||||
IList<PowerToysReleaseInfo> releases = JsonSerializer.Deserialize<IList<PowerToysReleaseInfo>>(releaseNotesJSON);
|
||||
|
||||
// Get the latest releases
|
||||
var latestReleases = releases.OrderByDescending(release => release.PublishedDate).Take(5);
|
||||
|
||||
@@ -276,37 +276,6 @@
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="Transcode"
|
||||
DataContext="{x:Bind ViewModel.AdditionalActions.Transcode, Mode=OneWay}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="{Binding IsShown, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander.Content>
|
||||
<ToggleSwitch
|
||||
IsOn="{Binding IsShown, Mode=TwoWay}"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</tkcontrols:SettingsExpander.Content>
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<!-- HACK: For some weird reason, a ShortcutControl does not work correctly if it's the first or last item in the expander, so we add an invisible card. -->
|
||||
<tkcontrols:SettingsCard Visibility="Collapsed" />
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="TranscodeToMp3"
|
||||
DataContext="{Binding TranscodeToMp3, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.AdditionalActions.Transcode.IsShown, Mode=OneWay}">
|
||||
<ContentControl ContentTemplate="{StaticResource AdditionalActionTemplate}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="TranscodeToMp4"
|
||||
DataContext="{Binding TranscodeToMp4, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.AdditionalActions.Transcode.IsShown, Mode=OneWay}">
|
||||
<ContentControl ContentTemplate="{StaticResource AdditionalActionTemplate}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<!-- HACK: For some weird reason, a ShortcutControl does not work correctly if it's the first or last item in the expander, so we add an invisible card. -->
|
||||
<tkcontrols:SettingsCard Visibility="Collapsed" />
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="AdvancedPaste_ShortcutWarning"
|
||||
IsClosable="False"
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
ViewModel = new ColorPickerViewModel(
|
||||
settingsUtils,
|
||||
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
|
||||
SettingsRepository<ColorPickerSettings>.GetInstance(settingsUtils),
|
||||
null,
|
||||
ShellPage.SendDefaultIPCMessage);
|
||||
DataContext = ViewModel;
|
||||
InitializeComponent();
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
<converters:ImageResizerUnitToStringConverter x:Key="ImageResizerUnitToStringConverter" />
|
||||
<converters:ImageResizerUnitToIntConverter x:Key="ImageResizerUnitToIntConverter" />
|
||||
<converters:ImageResizerSizeToAccessibleTextConverter x:Key="ImageResizerSizeToAccessibleTextConverter" />
|
||||
<converters:ImageResizerDoubleToAutoConverter x:Key="ImageResizerDoubleToAutoConverter" />
|
||||
<converters:ImageResizerNumberBoxValueConverter x:Key="ImageResizerNumberBoxValueConverter" />
|
||||
<converters:ImageResizerZeroToEmptyStringNumberFormatter x:Key="ImageResizerZeroToEmptyStringNumberFormatter" />
|
||||
<toolkitconverters:BoolToObjectConverter
|
||||
x:Key="BoolToComboBoxIndexConverter"
|
||||
FalseValue="1"
|
||||
@@ -81,7 +78,7 @@
|
||||
Margin="0,0,4,0"
|
||||
FontWeight="SemiBold"
|
||||
Style="{ThemeResource SecondaryTextStyle}"
|
||||
Text="{x:Bind Width, Mode=OneWay, Converter={StaticResource ImageResizerDoubleToAutoConverter}, ConverterParameter=Auto}" />
|
||||
Text="{x:Bind Width, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,5,4,0"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
@@ -94,7 +91,7 @@
|
||||
Margin="0,0,4,0"
|
||||
FontWeight="SemiBold"
|
||||
Style="{ThemeResource SecondaryTextStyle}"
|
||||
Text="{x:Bind Height, Mode=OneWay, Converter={StaticResource ImageResizerDoubleToAutoConverter}, ConverterParameter=Auto}"
|
||||
Text="{x:Bind Height, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsHeightUsed, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock
|
||||
Margin="0,0,4,0"
|
||||
@@ -139,20 +136,20 @@
|
||||
</ComboBox>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<controls:ImageResizerDimensionsNumberBox
|
||||
<NumberBox
|
||||
x:Uid="ImageResizer_Width"
|
||||
Width="116"
|
||||
Minimum="0"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{x:Bind Width, Mode=TwoWay, Converter={StaticResource ImageResizerNumberBoxValueConverter}}" />
|
||||
Value="{x:Bind Width, Mode=TwoWay}" />
|
||||
|
||||
<controls:ImageResizerDimensionsNumberBox
|
||||
<NumberBox
|
||||
x:Uid="ImageResizer_Height"
|
||||
Width="116"
|
||||
Minimum="0"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Visibility="{x:Bind IsHeightUsed, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Value="{x:Bind Height, Mode=TwoWay, Converter={StaticResource ImageResizerNumberBoxValueConverter}}" />
|
||||
Value="{x:Bind Height, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<ComboBox
|
||||
|
||||
@@ -1193,10 +1193,6 @@
|
||||
<value>TIFF compression</value>
|
||||
<comment>{Locked="TIFF"}</comment>
|
||||
</data>
|
||||
<data name="ImageResizer_AutoText" xml:space="preserve">
|
||||
<value>(auto)</value>
|
||||
<comment>Displayed on the preset card when the Width or Height property is zero. The same as "Input_Auto" in the ImageResizerUI project's resources.</comment>
|
||||
</data>
|
||||
<data name="File.Header" xml:space="preserve">
|
||||
<value>File</value>
|
||||
<comment>as in a computer file</comment>
|
||||
@@ -1934,15 +1930,6 @@ Made with 💗 by Microsoft and the PowerToys community.</value>
|
||||
<data name="PasteAsHtmlFile.Header" xml:space="preserve">
|
||||
<value>Paste as .html file</value>
|
||||
</data>
|
||||
<data name="Transcode.Header" xml:space="preserve">
|
||||
<value>Transcode audio / video</value>
|
||||
</data>
|
||||
<data name="TranscodeToMp3.Header" xml:space="preserve">
|
||||
<value>Transcode to .mp3</value>
|
||||
</data>
|
||||
<data name="TranscodeToMp4.Header" xml:space="preserve">
|
||||
<value>Transcode to .mp4 (H.264/AAC)</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAIDialogOpenAIApiKey.Text" xml:space="preserve">
|
||||
<value>OpenAI API key:</value>
|
||||
</data>
|
||||
|
||||
@@ -18,13 +18,12 @@ using global::PowerToys.GPOWrapper;
|
||||
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.SerializationContext;
|
||||
using Microsoft.Win32;
|
||||
using Windows.Security.Credentials;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public partial class AdvancedPasteViewModel : Observable, IDisposable
|
||||
public class AdvancedPasteViewModel : Observable, IDisposable
|
||||
{
|
||||
private static readonly HashSet<string> WarnHotkeys = ["Ctrl + V", "Ctrl + Shift + V"];
|
||||
|
||||
@@ -84,7 +83,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_delayedTimer.Elapsed += DelayedTimer_Tick;
|
||||
_delayedTimer.AutoReset = false;
|
||||
|
||||
foreach (var action in _additionalActions.GetAllActions())
|
||||
foreach (var action in _additionalActions.AllActions)
|
||||
{
|
||||
action.PropertyChanged += OnAdditionalActionPropertyChanged;
|
||||
}
|
||||
@@ -366,7 +365,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
.Any(hotkey => WarnHotkeys.Contains(hotkey.ToString()));
|
||||
|
||||
public bool IsAdditionalActionConflictingCopyShortcut =>
|
||||
_additionalActions.GetAllActions()
|
||||
_additionalActions.AllActions
|
||||
.OfType<AdvancedPasteAdditionalAction>()
|
||||
.Select(additionalAction => additionalAction.Shortcut)
|
||||
.Any(hotkey => WarnHotkeys.Contains(hotkey.ToString()));
|
||||
@@ -388,7 +387,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
AdvancedPasteSettings.ModuleName,
|
||||
JsonSerializer.Serialize(_advancedPasteSettings, SourceGenerationContextContext.Default.AdvancedPasteSettings)));
|
||||
JsonSerializer.Serialize(_advancedPasteSettings)));
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
|
||||
@@ -12,11 +12,10 @@ 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.Library.Utilities;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public partial class AlwaysOnTopViewModel : Observable
|
||||
public class AlwaysOnTopViewModel : Observable
|
||||
{
|
||||
private ISettingsUtils SettingsUtils { get; set; }
|
||||
|
||||
@@ -132,7 +131,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
AlwaysOnTopSettings.ModuleName,
|
||||
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.AlwaysOnTopSettings)));
|
||||
JsonSerializer.Serialize(Settings)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public partial class AwakeViewModel : Observable
|
||||
public class AwakeViewModel : Observable
|
||||
{
|
||||
public AwakeViewModel()
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user