Merge pull request #105 from microsoft/main

sync to main
This commit is contained in:
Clint Rutkas
2024-10-21 20:30:56 -07:00
committed by GitHub
437 changed files with 4603 additions and 2534 deletions

View File

@@ -1,5 +1,6 @@
name: "🕷️ Bug report"
description: Report errors or unexpected behavior
type: Bug
labels:
- Issue-Bug
- Needs-Triage

View File

@@ -1,5 +1,6 @@
name: "⭐ Feature or enhancement request"
description: Propose something new.
type: Feature
labels:
- Needs-Triage
body:

View File

@@ -1,5 +1,6 @@
name: "🌐 Localization/Translation issue"
description: Report incorrect translations.
type: Bug
labels:
- Issue-Bug
- Area-Localization

View File

@@ -28,6 +28,8 @@ videoconference
# USERS
# 8LWXpg is user name but user folder causes a flag
LWXpg
Adoumie
Advaith
alekhyareddy

View File

@@ -2,8 +2,6 @@
## "PackagemanagerWrapper.cs" should be "PackageManagerWrapper.cs"
## NOTICE.MD > MOZILLA PUBLIC LICENSE v1.1
# user name but user folder causes a flag
8LWXpg
aaaa
abcdefghjkmnpqrstuvxyz
abgr
@@ -32,7 +30,6 @@ AFFINETRANSFORM
AFX
AGGREGATABLE
AHybrid
AKV
akv
ALarger
ALLAPPS
@@ -1912,6 +1909,7 @@ XFile
XIncrement
XLoc
XNamespace
Xoshiro
XPels
XPixel
xplorer

View File

@@ -166,6 +166,7 @@
"PowerToys.FindMyMouse.dll",
"PowerToys.MouseHighlighter.dll",
"PowerToys.MouseJump.dll",
"PowerToys.MouseJump.Common.dll",
"PowerToys.MousePointerCrosshairs.dll",
"PowerToys.MouseJumpUI.dll",
"PowerToys.MouseJumpUI.exe",

View File

@@ -117,7 +117,10 @@ else
{
Write-Error 'XAML Styling is incorrect, please run `.\.pipelines\applyXamlStyling.ps1 -Main` locally.'
}
if ($lastExitCode -lt 0)
{
Write-Error "Error running dotnet tool run, with the exit code $lastExitCode. Please verify logs and running environment."
}
# Return XAML Styler Status
exit $lastExitCode
}

View File

@@ -4,22 +4,22 @@ trigger:
include:
- main
- stable
paths:
exclude:
- doc/*
- temp/*
- tools/*
- '**.md'
# paths:
# exclude:
# - doc/*
# - temp/*
# - tools/*
# - '**.md'
pr:
branches:
include:
- main
- stable
paths:
exclude:
- '**.md'
- doc
# paths:
# exclude:
# - '**.md'
# - doc
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)

View File

@@ -127,12 +127,10 @@ jobs:
Write-Host "##vso[task.setvariable variable=MSBuildCacheParameters]$MSBuildCacheParameters"
displayName: Prepare MSBuildCache variables
- ${{ if eq(parameters.codeSign, true) }}:
# Only required if we're using ESRP
- template: steps-ensure-dotnet-version.yml
parameters:
sdk: true
version: '6.0'
- template: steps-ensure-dotnet-version.yml
parameters:
sdk: true
version: '6.0'
- template: steps-ensure-dotnet-version.yml
parameters:

View File

@@ -15,7 +15,7 @@ Param(
$referencedFileVersionsPerDll = @{}
$totalFailures = 0
Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude UITests-FancyZones* | ForEach-Object {
Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude UITests-FancyZones*,MouseJump.Common.UnitTests* | ForEach-Object {
# Temporarily exclude FancyZones UI tests because of Appium.WebDriver dependencies
$depsJsonFullFileName = $_.FullName
$depsJsonFileName = $_.Name

View File

@@ -8,6 +8,12 @@ Write-Host "Verifying Nuget packages for $solution"
dotnet tool restore
dotnet consolidate -s $solution
if ($lastExitCode -ne 0)
{
$result = $lastExitCode
Write-Error "Error running dotnet consolidate, with the exit code $lastExitCode. Please verify logs and running environment."
exit $result
}
if (-not $?)
{

View File

@@ -38,16 +38,16 @@
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2739.15" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="8.0.0" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="8.0.7" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="8.0.10" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
TODO: in Common.Dotnet.CsWinRT.props, on upgrade, verify RemoveCsWinRTPackageAnalyzer is no longer needed.
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.1.1" />
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.1.5" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.240829007" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.240923002" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
@@ -66,30 +66,27 @@
<PackageVersion Include="System.CodeDom" Version="8.0.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="8.0.0" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
<PackageVersion Include="System.Data.OleDb" Version="8.0.0" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
<PackageVersion Include="System.Data.OleDb" Version="8.0.1" />
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
<PackageVersion Include="System.Data.SqlClient" Version="4.8.6" />
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="8.0.0" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="8.0.0" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.6" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="8.0.1" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.7" />
<PackageVersion Include="System.IO.Abstractions" Version="17.2.3" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="8.0.0" />
<!-- Package System.Security.Cryptography.ProtectedData added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
<PackageVersion Include="System.Runtime.Caching" Version="8.0.1" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="WinUIEx" Version="2.2.0" />
<PackageVersion Include="WPF-UI" Version="3.0.0" />
<PackageVersion Include="WPF-UI" Version="3.0.5" />
<!-- TODO! Mike see which of these we actually need, and -->
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />

View File

@@ -1327,11 +1327,11 @@ EXHIBIT A -Mozilla Public License.
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2739.15
- Microsoft.Win32.SystemEvents 8.0.0
- Microsoft.Windows.Compatibility 8.0.7
- Microsoft.Windows.Compatibility 8.0.10
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWinRT 2.1.1
- Microsoft.Windows.CsWinRT 2.1.5
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
- Microsoft.WindowsAppSDK 1.6.240829007
- Microsoft.WindowsAppSDK 1.6.240923002
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
- Microsoft.Xaml.Behaviors.Wpf 1.1.39
- ModernWpfUI 0.9.4
@@ -1347,23 +1347,21 @@ EXHIBIT A -Mozilla Public License.
- System.CodeDom 8.0.0
- System.CommandLine 2.0.0-beta4.22272.1
- System.ComponentModel.Composition 8.0.0
- System.Configuration.ConfigurationManager 8.0.0
- System.Data.OleDb 8.0.0
- System.Configuration.ConfigurationManager 8.0.1
- System.Data.OleDb 8.0.1
- System.Data.SqlClient 4.8.6
- System.Diagnostics.EventLog 8.0.0
- System.Diagnostics.PerformanceCounter 8.0.0
- System.Drawing.Common 8.0.6
- System.Diagnostics.EventLog 8.0.1
- System.Drawing.Common 8.0.7
- System.IO.Abstractions 17.2.3
- System.IO.Abstractions.TestingHelpers 17.2.3
- System.Management 8.0.0
- System.Reactive 6.0.1
- System.Runtime.Caching 8.0.0
- System.Security.Cryptography.ProtectedData 8.0.0
- System.ServiceProcess.ServiceController 8.0.0
- System.Runtime.Caching 8.0.1
- System.ServiceProcess.ServiceController 8.0.1
- System.Text.Encoding.CodePages 8.0.0
- System.Text.Json 8.0.4
- System.Text.Json 8.0.5
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0
- UTF.Unknown 2.5.1
- WinUIEx 2.2.0
- WPF-UI 3.0.0
- WPF-UI 3.0.5

View File

@@ -504,9 +504,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBordersHelper",
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MouseJump", "src\modules\MouseUtils\MouseJump\MouseJump.vcxproj", "{8A08D663-4995-40E3-B42C-3F910625F284}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJumpUI", "src\modules\MouseUtils\MouseJumpUI\MouseJumpUI.csproj", "{D962A009-834F-4EEC-AABB-430DF8F98E39}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJump.Common", "src\modules\MouseUtils\MouseJump.Common\MouseJump.Common.csproj", "{923DF87C-CA99-4D1C-B1D2-959174E95BFA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJumpUI.UnitTests", "src\modules\MouseUtils\MouseJumpUI.UnitTests\MouseJumpUI.UnitTests.csproj", "{D9C5DE64-6849-4278-91AD-9660AECF2876}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJump.Common.UnitTests", "src\modules\MouseUtils\MouseJump.Common.UnitTests\MouseJump.Common.UnitTests.csproj", "{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJumpUI", "src\modules\MouseUtils\MouseJumpUI\MouseJumpUI.csproj", "{D962A009-834F-4EEC-AABB-430DF8F98E39}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdvancedPaste", "AdvancedPaste", "{9873BA05-4C41-4819-9283-CF45D795431B}"
EndProject
@@ -622,6 +624,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLauncher", "src\m
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesWindowArranger", "src\modules\Workspaces\WorkspacesWindowArranger\WorkspacesWindowArranger.vcxproj", "{37D07516-4185-43A4-924F-3C7A5D95ECF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTests", "src\modules\MouseWithoutBorders\MouseWithoutBorders.UnitTests\MouseWithoutBorders.UnitTests.csproj", "{66614C26-314C-4B91-9071-76133422CFEF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2250,18 +2254,6 @@ Global
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|x64.Build.0 = Release|x64
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|x86.ActiveCfg = Release|x64
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|x86.Build.0 = Release|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Debug|ARM64.Build.0 = Debug|ARM64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Debug|x64.ActiveCfg = Debug|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Debug|x64.Build.0 = Debug|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Debug|x86.ActiveCfg = Debug|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Debug|x86.Build.0 = Debug|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Release|ARM64.ActiveCfg = Release|ARM64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Release|ARM64.Build.0 = Release|ARM64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Release|x64.ActiveCfg = Release|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Release|x64.Build.0 = Release|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Release|x86.ActiveCfg = Release|x64
{D9C5DE64-6849-4278-91AD-9660AECF2876}.Release|x86.Build.0 = Release|x64
{FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|ARM64.Build.0 = Debug|ARM64
{FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|x64.ActiveCfg = Debug|x64
@@ -2650,6 +2642,30 @@ Global
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.Build.0 = Debug|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.ActiveCfg = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.Build.0 = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.ActiveCfg = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.Build.0 = Debug|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.ActiveCfg = Release|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.Build.0 = Release|ARM64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.ActiveCfg = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.Build.0 = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.ActiveCfg = Release|x64
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.Build.0 = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.Build.0 = Debug|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.ActiveCfg = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.Build.0 = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.ActiveCfg = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.Build.0 = Debug|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.ActiveCfg = Release|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.Build.0 = Release|ARM64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.ActiveCfg = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.Build.0 = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.ActiveCfg = Release|x64
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.Build.0 = Release|x64
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
@@ -2734,6 +2750,18 @@ Global
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x64.Build.0 = Release|x64
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x86.ActiveCfg = Release|x64
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x86.Build.0 = Release|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Debug|ARM64.ActiveCfg = Debug|ARM64
{66614C26-314C-4B91-9071-76133422CFEF}.Debug|ARM64.Build.0 = Debug|ARM64
{66614C26-314C-4B91-9071-76133422CFEF}.Debug|x64.ActiveCfg = Debug|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Debug|x64.Build.0 = Debug|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Debug|x86.ActiveCfg = Debug|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Debug|x86.Build.0 = Debug|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|ARM64.ActiveCfg = Release|ARM64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|ARM64.Build.0 = Release|ARM64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x64.ActiveCfg = Release|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x64.Build.0 = Release|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x86.ActiveCfg = Release|x64
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2913,7 +2941,6 @@ Global
{A663E672-B26D-4EC0-BEAB-FE2E424AC46F} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
{8A08D663-4995-40E3-B42C-3F910625F284} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{D962A009-834F-4EEC-AABB-430DF8F98E39} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{D9C5DE64-6849-4278-91AD-9660AECF2876} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B}
{9CE59ED5-7087-4353-88EB-788038A73CEC} = {1AFB6476-670D-4E80-A464-657E01DFF482}
@@ -2952,6 +2979,8 @@ Global
{8ACB33D9-C95B-47D4-8363-9731EE0930A0} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
{CA716AE6-FE5C-40AC-BB8F-2C87912687AC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
{923DF87C-CA99-4D1C-B1D2-959174E95BFA} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
@@ -2962,6 +2991,7 @@ Global
{367D7543-7DBA-4381-99F1-BF6142A996C4} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{37D07516-4185-43A4-924F-3C7A5D95ECF6} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{66614C26-314C-4B91-9071-76133422CFEF} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -56,3 +56,5 @@ Below are community created plugins that target a website or software. They are
| [Scoop](https://github.com/Quriz/PowerToysRunScoop) | [Quriz](https://github.com/Quriz) | Search and install packages from Scoop |
| [Spotify](https://github.com/waaverecords/PowerToys-Run-Spotify) | [waaverecords](https://github.com/waaverecords) | Search Spotify and control its player |
| [PowerSearch for 1Password](https://github.com/KairuDeibisu/PowerToysRunPlugin1Password) | [KairuDeibisu](https://github.com/KairuDeibisu) | An unofficial plugin for searching 1Password for usernames and passwords |
| [HackMD](https://github.com/8LWXpg/PowerToysRun-HackMD) | [8LWXpg](https://github.com/8LWXpg) | Open HackMD notes |
| [SSH](https://github.com/8LWXpg/PowerToysRun-SSH) | [8LWXpg](https://github.com/8LWXpg) | Connect to ssh clients |

View File

@@ -2,7 +2,7 @@
<!-- Some items may be set in Directory.Build.props in root -->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WindowsSdkPackageVersion>10.0.22621.38</WindowsSdkPackageVersion>
<WindowsSdkPackageVersion>10.0.22621.48</WindowsSdkPackageVersion>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>

View File

@@ -60,14 +60,29 @@ namespace ManagedCommon
public static void LogError(string message, Exception ex)
{
Log(
message + Environment.NewLine +
ex?.Message + Environment.NewLine +
"Inner exception: " + Environment.NewLine +
ex?.InnerException?.Message + Environment.NewLine +
"Stack trace: " + Environment.NewLine +
ex?.StackTrace,
Error);
if (ex == null)
{
LogError(message);
}
else
{
var exMessage =
message + Environment.NewLine +
ex.GetType() + ": " + ex.Message + Environment.NewLine;
if (ex.InnerException != null)
{
exMessage +=
"Inner exception: " + Environment.NewLine +
ex.InnerException.GetType() + ": " + ex.InnerException.Message + Environment.NewLine;
}
exMessage +=
"Stack trace: " + Environment.NewLine +
ex.StackTrace;
Log(exMessage, Error);
}
}
public static void LogWarning(string message)

View File

@@ -63,6 +63,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE;
}
hstring Constants::AdvancedPasteAdditionalActionMessage()
{
return CommonSharedConstants::ADVANCED_PASTE_ADDITIONAL_ACTION_MESSAGE;
}
hstring Constants::AdvancedPasteCustomActionMessage()
{
return CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE;

View File

@@ -19,6 +19,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring AdvancedPasteShowUIMessage();
static hstring AdvancedPasteMarkdownMessage();
static hstring AdvancedPasteJsonMessage();
static hstring AdvancedPasteAdditionalActionMessage();
static hstring AdvancedPasteCustomActionMessage();
static hstring ShowPowerOCRSharedEvent();
static hstring MouseJumpShowPreviewEvent();

View File

@@ -16,6 +16,7 @@ namespace PowerToys
static String AdvancedPasteShowUIMessage();
static String AdvancedPasteMarkdownMessage();
static String AdvancedPasteJsonMessage();
static String AdvancedPasteAdditionalActionMessage();
static String AdvancedPasteCustomActionMessage();
static String ShowPowerOCRSharedEvent();
static String MouseJumpShowPreviewEvent();

View File

@@ -32,6 +32,8 @@ namespace CommonSharedConstants
const wchar_t ADVANCED_PASTE_JSON_MESSAGE[] = L"PasteJson";
const wchar_t ADVANCED_PASTE_ADDITIONAL_ACTION_MESSAGE[] = L"AdditionalAction";
const wchar_t ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE[] = L"CustomAction";
// Path to the event used to show Color Picker

View File

@@ -3,12 +3,16 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
using AdvancedPaste.Services;
using AdvancedPaste.Settings;
using AdvancedPaste.ViewModels;
using ManagedCommon;
@@ -34,6 +38,13 @@ namespace AdvancedPaste
{
public IHost Host { get; private set; }
private static readonly Dictionary<string, PasteFormats> AdditionalActionIPCKeys =
typeof(PasteFormats).GetFields()
.Where(field => field.IsLiteral)
.Select(field => (Format: (PasteFormats)field.GetRawConstantValue(), field.GetCustomAttribute<PasteFormatMetadataAttribute>().IPCKey))
.Where(field => field.IPCKey != null)
.ToDictionary(field => field.IPCKey, field => field.Format);
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private readonly OptionsViewModel viewModel;
@@ -60,8 +71,10 @@ namespace AdvancedPaste
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) =>
{
services.AddSingleton<OptionsViewModel>();
services.AddSingleton<IUserSettings, UserSettings>();
services.AddSingleton<AICompletionsHelper>();
services.AddSingleton<OptionsViewModel>();
services.AddSingleton<IPasteFormatExecutor, PasteFormatExecutor>();
}).Build();
viewModel = GetService<OptionsViewModel>();
@@ -111,35 +124,35 @@ namespace AdvancedPaste
private void ProcessNamedPipe(string pipeName)
{
void OnMessage(string message) => _dispatcherQueue.TryEnqueue(() => OnNamedPipeMessage(message));
void OnMessage(string message) => _dispatcherQueue.TryEnqueue(async () => await OnNamedPipeMessage(message));
Task.Run(async () =>
{
var connectTimeout = TimeSpan.FromSeconds(10);
await NamedPipeProcessor.ProcessNamedPipeAsync(pipeName, connectTimeout, OnMessage, CancellationToken.None);
});
Task.Run(async () => await NamedPipeProcessor.ProcessNamedPipeAsync(pipeName, connectTimeout: TimeSpan.FromSeconds(10), OnMessage, CancellationToken.None));
}
private void OnNamedPipeMessage(string message)
private async Task OnNamedPipeMessage(string message)
{
var messageParts = message.Split();
var messageType = messageParts.First();
if (messageType == PowerToys.Interop.Constants.AdvancedPasteShowUIMessage())
{
OnAdvancedPasteHotkey();
await ShowWindow();
}
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteMarkdownMessage())
{
OnAdvancedPasteMarkdownHotkey();
await viewModel.ExecutePasteFormatAsync(PasteFormats.Markdown, PasteActionSource.GlobalKeyboardShortcut);
}
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteJsonMessage())
{
OnAdvancedPasteJsonHotkey();
await viewModel.ExecutePasteFormatAsync(PasteFormats.Json, PasteActionSource.GlobalKeyboardShortcut);
}
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteAdditionalActionMessage())
{
await OnAdvancedPasteAdditionalActionHotkey(messageParts);
}
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteCustomActionMessage())
{
OnAdvancedPasteCustomActionHotkey(messageParts);
await OnAdvancedPasteCustomActionHotkey(messageParts);
}
}
@@ -148,24 +161,27 @@ namespace AdvancedPaste
Logger.LogError("Unhandled exception", e.Exception);
}
private void OnAdvancedPasteJsonHotkey()
private async Task OnAdvancedPasteAdditionalActionHotkey(string[] messageParts)
{
viewModel.ReadClipboard();
viewModel.ToJsonFunction(true);
if (messageParts.Length != 2)
{
Logger.LogWarning("Unexpected additional action message");
}
else
{
if (!AdditionalActionIPCKeys.TryGetValue(messageParts[1], out PasteFormats pasteFormat))
{
Logger.LogWarning($"Unexpected additional action type {messageParts[1]}");
}
else
{
await ShowWindow();
await viewModel.ExecutePasteFormatAsync(pasteFormat, PasteActionSource.GlobalKeyboardShortcut);
}
}
}
private void OnAdvancedPasteMarkdownHotkey()
{
viewModel.ReadClipboard();
viewModel.ToMarkdownFunction(true);
}
private void OnAdvancedPasteHotkey()
{
ShowWindow();
}
private void OnAdvancedPasteCustomActionHotkey(string[] messageParts)
private async Task OnAdvancedPasteCustomActionHotkey(string[] messageParts)
{
if (messageParts.Length != 2)
{
@@ -179,16 +195,15 @@ namespace AdvancedPaste
}
else
{
ShowWindow();
viewModel.ReadClipboard();
viewModel.ExecuteCustomActionWithPaste(customActionId);
await ShowWindow();
await viewModel.ExecuteCustomActionAsync(customActionId, PasteActionSource.GlobalKeyboardShortcut);
}
}
}
private void ShowWindow()
private async Task ShowWindow()
{
viewModel.OnShow();
await viewModel.OnShowAsync();
if (window is null)
{

View File

@@ -346,7 +346,7 @@
x:Name="InputTxtBox"
HorizontalAlignment="Stretch"
x:FieldModifier="public"
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, Mode=OneWay}"
IsEnabled="{x:Bind ViewModel.ClipboardHasData, Mode=OneWay}"
KeyDown="InputTxtBox_KeyDown"
PlaceholderText="{x:Bind ViewModel.InputTxtBoxPlaceholderText, Mode=OneWay}"
Style="{StaticResource CustomTextBoxStyle}"
@@ -589,7 +589,7 @@
Background="Transparent"
Visibility="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind ViewModel.GeneralErrorText}" />
<ToolTip Content="{x:Bind ViewModel.AIDisabledErrorText}" />
</ToolTipService.ToolTip>
</Grid>
</Grid>
@@ -638,7 +638,7 @@
FontWeight="SemiBold"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.ApiErrorText, Mode=OneWay}" />
Text="{x:Bind ViewModel.PasteOperationErrorText, Mode=OneWay}" />
<HyperlinkButton
x:Uid="SettingsBtn"
Grid.Column="1"

View File

@@ -2,17 +2,15 @@
// 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.Net;
using System.ComponentModel;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Settings;
using AdvancedPaste.Models;
using AdvancedPaste.ViewModels;
using CommunityToolkit.Mvvm.Input;
using ManagedCommon;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -20,12 +18,6 @@ namespace AdvancedPaste.Controls
{
public sealed partial class PromptBox : Microsoft.UI.Xaml.Controls.UserControl
{
// Minimum time to show spinner when generating custom format using forcePasteCustom
private static readonly TimeSpan MinTaskTime = TimeSpan.FromSeconds(2);
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private readonly IUserSettings _userSettings;
public OptionsViewModel ViewModel { get; private set; }
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
@@ -54,12 +46,31 @@ namespace AdvancedPaste.Controls
public PromptBox()
{
this.InitializeComponent();
_userSettings = App.GetService<IUserSettings>();
InitializeComponent();
ViewModel = App.GetService<OptionsViewModel>();
ViewModel.CustomActionActivated += (_, e) => GenerateCustom(e.ForcePasteCustom);
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
ViewModel.CustomActionActivated += ViewModel_CustomActionActivated;
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ViewModel.Busy) || e.PropertyName == nameof(ViewModel.PasteOperationErrorText))
{
var state = ViewModel.Busy ? "LoadingState" : string.IsNullOrEmpty(ViewModel.PasteOperationErrorText) ? "DefaultState" : "ErrorState";
VisualStateManager.GoToState(this, state, true);
}
}
private void ViewModel_CustomActionActivated(object sender, CustomActionActivatedEventArgs e)
{
Logger.LogTrace();
if (!e.PasteResult)
{
PreviewGrid.Width = InputTxtBox.ActualWidth;
PreviewFlyout.ShowAt(InputTxtBox);
}
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
@@ -68,48 +79,7 @@ namespace AdvancedPaste.Controls
}
[RelayCommand]
private void GenerateCustom() => GenerateCustom(false);
private void GenerateCustom(bool forcePasteCustom)
{
Logger.LogTrace();
VisualStateManager.GoToState(this, "LoadingState", true);
string inputInstructions = ViewModel.Query;
ViewModel.SaveQuery(inputInstructions);
var customFormatTask = ViewModel.GenerateCustomFunction(inputInstructions);
var delayTask = forcePasteCustom ? Task.Delay(MinTaskTime) : Task.CompletedTask;
Task.WhenAll(customFormatTask, delayTask)
.ContinueWith(
_ =>
{
_dispatcherQueue.TryEnqueue(() =>
{
ViewModel.CustomFormatResult = customFormatTask.Result;
if (ViewModel.ApiRequestStatus == (int)HttpStatusCode.OK)
{
VisualStateManager.GoToState(this, "DefaultState", true);
if (_userSettings.ShowCustomPreview && !forcePasteCustom)
{
PreviewGrid.Width = InputTxtBox.ActualWidth;
PreviewFlyout.ShowAt(InputTxtBox);
}
else
{
ViewModel.PasteCustom();
InputTxtBox.Text = string.Empty;
}
}
else
{
VisualStateManager.GoToState(this, "ErrorState", true);
}
});
},
TaskScheduler.Default);
}
private async Task GenerateCustomAsync() => await ViewModel.GenerateCustomFunctionAsync(PasteActionSource.PromptBox);
[RelayCommand]
private void Recall()
@@ -127,29 +97,24 @@ namespace AdvancedPaste.Controls
ClipboardHelper.SetClipboardTextContent(lastQuery.ClipboardData);
}
private void InputTxtBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
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.IsCustomAIEnabled)
{
GenerateCustom();
await GenerateCustomAsync();
}
}
private void PreviewPasteBtn_Click(object sender, RoutedEventArgs e)
{
ViewModel.PasteCustom();
InputTxtBox.Text = string.Empty;
}
private void ThumbUpDown_Click(object sender, RoutedEventArgs e)
{
if (sender is Button btn)
if (sender is Button btn && bool.TryParse(btn.CommandParameter as string, out bool result))
{
bool result;
if (bool.TryParse(btn.CommandParameter as string, out result))
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteCustomFormatOutputThumbUpDownEvent(result));
}
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteCustomFormatOutputThumbUpDownEvent(result));
}
}

View File

@@ -0,0 +1,24 @@
// 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;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace AdvancedPaste.Converters;
public sealed partial class PasteFormatsToHeightConverter : IValueConverter
{
private const int ItemHeight = 40;
public int MaxItems { get; set; } = 5;
public object Convert(object value, Type targetType, object parameter, string language) =>
new GridLength(GetHeight((value is ICollection collection) ? collection.Count : (value is int intValue) ? intValue : 0));
public int GetHeight(int itemCount) => Math.Min(MaxItems, itemCount) * ItemHeight;
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}

View File

@@ -8,9 +8,9 @@
xmlns:pages="using:AdvancedPaste.Pages"
xmlns:winuiex="using:WinUIEx"
Width="420"
Height="308"
Height="188"
MinWidth="420"
MinHeight="308"
MinHeight="188"
Closed="WindowEx_Closed"
IsAlwaysOnTop="True"
IsMaximizable="False"

View File

@@ -4,9 +4,13 @@
using System;
using System.Linq;
using AdvancedPaste.Converters;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
using AdvancedPaste.Settings;
using AdvancedPaste.ViewModels;
using ManagedCommon;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
@@ -24,25 +28,32 @@ namespace AdvancedPaste
public MainWindow()
{
this.InitializeComponent();
InitializeComponent();
_userSettings = App.GetService<IUserSettings>();
var optionsViewModel = App.GetService<OptionsViewModel>();
var baseHeight = MinHeight;
var coreActionCount = PasteFormat.MetadataDict.Values.Count(metadata => metadata.IsCoreAction);
void UpdateHeight()
{
var trimmedCustomActionCount = optionsViewModel.IsPasteWithAIEnabled ? Math.Min(_userSettings.CustomActions.Count, 5) : 0;
Height = MinHeight = baseHeight + (trimmedCustomActionCount * 40);
double GetHeight(int maxCustomActionCount) =>
baseHeight +
new PasteFormatsToHeightConverter().GetHeight(coreActionCount + _userSettings.AdditionalActions.Count) +
new PasteFormatsToHeightConverter() { MaxItems = maxCustomActionCount }.GetHeight(optionsViewModel.IsAIServiceEnabled ? _userSettings.CustomActions.Count : 0);
MinHeight = GetHeight(1);
Height = GetHeight(5);
}
UpdateHeight();
_userSettings.CustomActions.CollectionChanged += (_, _) => UpdateHeight();
_userSettings.Changed += (_, _) => UpdateHeight();
optionsViewModel.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(optionsViewModel.IsPasteWithAIEnabled))
if (e.PropertyName == nameof(optionsViewModel.IsAIServiceEnabled))
{
UpdateHeight();
}

View File

@@ -16,8 +16,9 @@
<Page.Resources>
<tkconverters:BoolToVisibilityConverter x:Name="BoolToVisibilityConverter" />
<converters:CountToVisibilityConverter x:Name="countToVisibilityConverter" />
<converters:PasteFormatsToHeightConverter x:Name="standardPasteFormatsToHeightConverter" />
<converters:CountToDoubleConverter
x:Name="customActionsCountToMinHeightConverter"
x:Name="customActionsToMinHeightConverter"
ValueIfNonZero="40"
ValueIfZero="0" />
<Style
@@ -28,37 +29,56 @@
<Setter Property="Padding" Value="0" />
</Style.Setters>
</Style>
<Style x:Key="PasteFormatListViewItemStyle" TargetType="ListViewItem">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
</Style>
<DataTemplate x:Key="PasteFormatTemplate" x:DataType="local:PasteFormat">
<Grid>
<ToolTipService.ToolTip>
<TextBlock Text="{x:Bind ToolTip}" />
</ToolTipService.ToolTip>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="26" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon
Margin="0,0,0,0"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
FontSize="16"
Glyph="{x:Bind IconGlyph}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
x:Phase="1"
Text="{x:Bind Name}" />
<TextBlock
Grid.Column="2"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ShortcutText, Mode=OneWay}"
Visibility="{x:Bind ShortcutText.Length, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
</Grid>
<Button
Margin="0"
Padding="5,0,5,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
AllowFocusOnInteraction="False"
BorderThickness="0"
Click="ListView_Button_Click"
IsEnabled="{x:Bind IsEnabled, Mode=OneWay}">
<Grid Opacity="{x:Bind Opacity, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="26" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ToolTipService.ToolTip>
<TextBlock Text="{x:Bind ToolTip, Mode=OneWay}" />
</ToolTipService.ToolTip>
<FontIcon
Margin="0,0,0,0"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
FontSize="16"
Glyph="{x:Bind IconGlyph, Mode=OneWay}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
x:Phase="1"
Text="{x:Bind Name, Mode=OneWay}" />
<TextBlock
Grid.Column="2"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ShortcutText, Mode=OneWay}"
Visibility="{x:Bind ShortcutText.Length, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
</Grid>
</Button>
</DataTemplate>
</Page.Resources>
<Page.KeyboardAccelerators>
@@ -166,9 +186,9 @@
BorderThickness="0,1,0,0"
RowSpacing="4">
<Grid.RowDefinitions>
<RowDefinition Height="{x:Bind ViewModel.StandardPasteFormats.Count, Mode=OneWay, Converter={StaticResource standardPasteFormatsToHeightConverter}}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsCountToMinHeightConverter}}" />
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsToMinHeightConverter}}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
@@ -176,12 +196,13 @@
x:Name="PasteOptionsListView"
Grid.Row="0"
VerticalAlignment="Bottom"
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, Mode=OneWay}"
IsItemClickEnabled="True"
ItemClick="ListView_Click"
IsItemClickEnabled="False"
ItemContainerStyle="{StaticResource PasteFormatListViewItemStyle}"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource PasteFormatTemplate}"
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Auto"
SelectionMode="None"
TabIndex="1" />
@@ -196,9 +217,8 @@
x:Name="CustomActionsListView"
Grid.Row="2"
VerticalAlignment="Top"
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
IsItemClickEnabled="True"
ItemClick="ListView_Click"
IsItemClickEnabled="False"
ItemContainerStyle="{StaticResource PasteFormatListViewItemStyle}"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource PasteFormatTemplate}"
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"

View File

@@ -130,15 +130,15 @@ namespace AdvancedPaste.Pages
}
}
private void ListView_Click(object sender, ItemClickEventArgs e)
private async void ListView_Button_Click(object sender, RoutedEventArgs e)
{
if (e.ClickedItem is PasteFormat format)
if (sender is Button { DataContext: PasteFormat format })
{
ViewModel.ExecutePasteFormat(format);
await ViewModel.ExecutePasteFormatAsync(format, PasteActionSource.ContextMenu);
}
}
private void KeyboardAccelerator_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args)
private async void KeyboardAccelerator_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args)
{
if (GetMainWindow()?.Visible is false)
{
@@ -171,7 +171,7 @@ namespace AdvancedPaste.Pages
case VirtualKey.Number7:
case VirtualKey.Number8:
case VirtualKey.Number9:
ViewModel.ExecutePasteFormat(sender.Key);
await ViewModel.ExecutePasteFormatAsync(sender.Key);
break;
default:
@@ -199,8 +199,7 @@ namespace AdvancedPaste.Pages
}
else if (item.Image is not null)
{
RandomAccessStreamReference image = null;
image = await item.Item.Content.GetBitmapAsync();
RandomAccessStreamReference image = await item.Item.Content.GetBitmapAsync();
ClipboardHelper.SetClipboardImageContent(image);
}
}

View File

@@ -3,12 +3,16 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AdvancedPaste.Models;
using ManagedCommon;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.ApplicationModel.DataTransfer;
using Windows.Data.Html;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.System;
@@ -16,6 +20,34 @@ namespace AdvancedPaste.Helpers
{
internal static class ClipboardHelper
{
private static readonly HashSet<string> ImageFileTypes = new(StringComparer.InvariantCultureIgnoreCase) { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico", ".svg" };
private static readonly (string DataFormat, ClipboardFormat ClipboardFormat)[] DataFormats =
[
(StandardDataFormats.Text, ClipboardFormat.Text),
(StandardDataFormats.Html, ClipboardFormat.Html),
(StandardDataFormats.Bitmap, ClipboardFormat.Image),
];
internal static async Task<ClipboardFormat> GetAvailableClipboardFormatsAsync(DataPackageView clipboardData)
{
var availableClipboardFormats = DataFormats.Aggregate(
ClipboardFormat.None,
(result, formatPair) => clipboardData.Contains(formatPair.DataFormat) ? (result | formatPair.ClipboardFormat) : result);
if (clipboardData.Contains(StandardDataFormats.StorageItems))
{
var storageItems = await clipboardData.GetStorageItemsAsync();
if (storageItems.Count == 1 && storageItems.Single() is StorageFile file && ImageFileTypes.Contains(file.FileType))
{
availableClipboardFormats |= ClipboardFormat.ImageFile;
}
}
return availableClipboardFormats;
}
internal static void SetClipboardTextContent(string text)
{
Logger.LogTrace();
@@ -26,31 +58,45 @@ namespace AdvancedPaste.Helpers
output.SetText(text);
Clipboard.SetContentWithOptions(output, null);
// TODO(stefan): For some reason Flush() fails from time to time when directly activated via hotkey.
// Calling inside a loop makes it work.
bool flushed = false;
for (int i = 0; i < 5; i++)
Flush();
}
}
private static bool Flush()
{
// TODO(stefan): For some reason Flush() fails from time to time when directly activated via hotkey.
// Calling inside a loop makes it work.
const int maxAttempts = 5;
for (int i = 1; i <= maxAttempts; i++)
{
try
{
if (flushed)
Task.Run(Clipboard.Flush).Wait();
return true;
}
catch (Exception ex)
{
if (i == maxAttempts)
{
break;
}
try
{
Task.Run(() =>
{
Clipboard.Flush();
}).Wait();
flushed = true;
}
catch (Exception ex)
{
Logger.LogError("Clipboard.Flush() failed", ex);
Logger.LogError($"{nameof(Clipboard)}.{nameof(Flush)}() failed", ex);
}
}
}
return false;
}
private static async Task<bool> FlushAsync() => await Task.Run(Flush);
internal static async Task SetClipboardFileContentAsync(string fileName)
{
var storageFile = await StorageFile.GetFileFromPathAsync(fileName);
DataPackage output = new();
output.SetStorageItems([storageFile]);
Clipboard.SetContent(output);
await FlushAsync();
}
internal static void SetClipboardImageContent(RandomAccessStreamReference image)
@@ -63,30 +109,7 @@ namespace AdvancedPaste.Helpers
output.SetBitmap(image);
Clipboard.SetContentWithOptions(output, null);
// TODO(stefan): For some reason Flush() fails from time to time when directly activated via hotkey.
// Calling inside a loop makes it work.
bool flushed = false;
for (int i = 0; i < 5; i++)
{
if (flushed)
{
break;
}
try
{
Task.Run(() =>
{
Clipboard.Flush();
}).Wait();
flushed = true;
}
catch (Exception ex)
{
Logger.LogError("Clipboard.Flush() failed", ex);
}
}
Flush();
}
}
@@ -136,5 +159,58 @@ namespace AdvancedPaste.Helpers
Logger.LogInfo("Paste sent");
}
internal static async Task<string> GetClipboardTextOrHtmlTextAsync(DataPackageView clipboardData)
{
if (clipboardData.Contains(StandardDataFormats.Text))
{
return await clipboardData.GetTextAsync();
}
else if (clipboardData.Contains(StandardDataFormats.Html))
{
var html = await clipboardData.GetHtmlFormatAsync();
return HtmlUtilities.ConvertToText(html);
}
else
{
return string.Empty;
}
}
internal static async Task<string> GetClipboardHtmlContentAsync(DataPackageView clipboardData) =>
clipboardData.Contains(StandardDataFormats.Html) ? await clipboardData.GetHtmlFormatAsync() : string.Empty;
internal static async Task<SoftwareBitmap> GetClipboardImageContentAsync(DataPackageView clipboardData)
{
using var stream = await GetClipboardImageStreamAsync(clipboardData);
if (stream != null)
{
var decoder = await BitmapDecoder.CreateAsync(stream);
return await decoder.GetSoftwareBitmapAsync();
}
return null;
}
private static async Task<IRandomAccessStream> GetClipboardImageStreamAsync(DataPackageView clipboardData)
{
if (clipboardData.Contains(StandardDataFormats.StorageItems))
{
var storageItems = await clipboardData.GetStorageItemsAsync();
var file = storageItems.Count == 1 ? storageItems[0] as StorageFile : null;
if (file != null)
{
return await file.OpenReadAsync();
}
}
if (clipboardData.Contains(StandardDataFormats.Bitmap))
{
var bitmap = await clipboardData.GetBitmapAsync();
return await bitmap.OpenReadAsync();
}
return null;
}
}
}

View File

@@ -2,8 +2,10 @@
// 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.ObjectModel;
using System;
using System.Collections.Generic;
using AdvancedPaste.Models;
using Microsoft.PowerToys.Settings.UI.Library;
namespace AdvancedPaste.Settings
@@ -16,6 +18,10 @@ namespace AdvancedPaste.Settings
public bool CloseAfterLosingFocus { get; }
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; }
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions { get; }
public IReadOnlyList<PasteFormats> AdditionalActions { get; }
public event EventHandler Changed;
}
}

View File

@@ -0,0 +1,39 @@
// 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.Linq;
using System.Threading.Tasks;
using Windows.Globalization;
using Windows.Graphics.Imaging;
using Windows.Media.Ocr;
using Windows.System.UserProfile;
namespace AdvancedPaste.Helpers;
public static class OcrHelpers
{
public static async Task<string> ExtractTextAsync(SoftwareBitmap bitmap)
{
var ocrLanguage = GetOCRLanguage() ?? throw new InvalidOperationException("Unable to determine OCR language");
var ocrEngine = OcrEngine.TryCreateFromLanguage(ocrLanguage) ?? throw new InvalidOperationException("Unable to create OCR engine");
var ocrResult = await ocrEngine.RecognizeAsync(bitmap);
return ocrResult.Text;
}
private static Language GetOCRLanguage()
{
var userLanguageTags = GlobalizationPreferences.Languages.ToList();
var languages = from language in OcrEngine.AvailableRecognizerLanguages
let tag = language.LanguageTag
where userLanguageTags.Contains(tag)
orderby userLanguageTags.IndexOf(tag)
select language;
return languages.FirstOrDefault();
}
}

View File

@@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.IO.Abstractions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AdvancedPaste.Models;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
@@ -20,6 +22,8 @@ namespace AdvancedPaste.Settings
private readonly TaskScheduler _taskScheduler;
private readonly IFileSystemWatcher _watcher;
private readonly object _loadingSettingsLock = new();
private readonly List<PasteFormats> _additionalActions;
private readonly List<AdvancedPasteCustomAction> _customActions;
private const string AdvancedPasteModuleName = "AdvancedPaste";
private const int MaxNumberOfRetry = 5;
@@ -27,13 +31,17 @@ namespace AdvancedPaste.Settings
private bool _disposedValue;
private CancellationTokenSource _cancellationTokenSource;
public event EventHandler Changed;
public bool ShowCustomPreview { get; private set; }
public bool SendPasteKeyCombination { get; private set; }
public bool CloseAfterLosingFocus { get; private set; }
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; private set; }
public IReadOnlyList<PasteFormats> AdditionalActions => _additionalActions;
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions => _customActions;
public UserSettings()
{
@@ -42,8 +50,8 @@ namespace AdvancedPaste.Settings
ShowCustomPreview = true;
SendPasteKeyCombination = true;
CloseAfterLosingFocus = false;
CustomActions = [];
_additionalActions = [];
_customActions = [];
_taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
LoadSettingsFromJson();
@@ -88,18 +96,29 @@ namespace AdvancedPaste.Settings
{
void UpdateSettings()
{
ShowCustomPreview = settings.Properties.ShowCustomPreview;
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
var properties = settings.Properties;
CustomActions.Clear();
foreach (var customAction in settings.Properties.CustomActions.Value)
{
if (customAction.IsShown && customAction.IsValid)
{
CustomActions.Add(customAction);
}
}
ShowCustomPreview = properties.ShowCustomPreview;
SendPasteKeyCombination = properties.SendPasteKeyCombination;
CloseAfterLosingFocus = properties.CloseAfterLosingFocus;
var sourceAdditionalActions = properties.AdditionalActions;
(PasteFormats Format, IAdvancedPasteAction[] Actions)[] additionalActionFormats =
[
(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])
];
_additionalActions.Clear();
_additionalActions.AddRange(additionalActionFormats.Where(tuple => tuple.Actions.All(action => action.IsShown))
.Select(tuple => tuple.Format));
_customActions.Clear();
_customActions.AddRange(properties.CustomActions.Value.Where(customAction => customAction.IsShown && customAction.IsValid));
Changed?.Invoke(this, EventArgs.Empty);
}
Task.Factory

View File

@@ -0,0 +1,18 @@
// 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.Models;
[Flags]
public enum ClipboardFormat
{
None,
Text = 1 << 0,
Html = 1 << 1,
Audio = 1 << 2,
Image = 1 << 3,
ImageFile = 1 << 4,
}

View File

@@ -5,14 +5,13 @@
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.ApplicationModel.DataTransfer;
namespace AdvancedPaste.Models
namespace AdvancedPaste.Models;
public class ClipboardItem
{
public class ClipboardItem
{
public string Content { get; set; }
public string Content { get; set; }
public ClipboardHistoryItem Item { get; set; }
public ClipboardHistoryItem Item { get; set; }
public BitmapImage Image { get; set; }
}
public BitmapImage Image { get; set; }
}

View File

@@ -6,9 +6,9 @@ using System;
namespace AdvancedPaste.Models;
public sealed class CustomActionActivatedEventArgs(string text, bool forcePasteCustom) : EventArgs
public sealed class CustomActionActivatedEventArgs(string text, bool pasteResult) : EventArgs
{
public string Text { get; private set; } = text;
public string Text { get; private init; } = text;
public bool ForcePasteCustom { get; private set; } = forcePasteCustom;
public bool PasteResult { get; private init; } = pasteResult;
}

View File

@@ -0,0 +1,11 @@
// 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.Models;
public sealed class PasteActionException(string message) : Exception(message)
{
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace AdvancedPaste.Models;
public enum PasteActionSource
{
ContextMenu,
InAppKeyboardShortcut,
GlobalKeyboardShortcut,
PromptBox,
}

View File

@@ -2,38 +2,62 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Microsoft.PowerToys.Settings.UI.Library;
namespace AdvancedPaste.Models;
public partial class PasteFormat : ObservableObject
[DebuggerDisplay("{Name} IsEnabled={IsEnabled} ShortcutText={ShortcutText}")]
public sealed class PasteFormat
{
[ObservableProperty]
private string _shortcutText = string.Empty;
public static readonly IReadOnlyDictionary<PasteFormats, PasteFormatMetadataAttribute> MetadataDict =
typeof(PasteFormats).GetFields()
.Where(field => field.IsLiteral)
.ToDictionary(field => (PasteFormats)field.GetRawConstantValue(), field => field.GetCustomAttribute<PasteFormatMetadataAttribute>());
[ObservableProperty]
private string _toolTip = string.Empty;
public PasteFormat()
private PasteFormat(PasteFormats format, ClipboardFormat clipboardFormats, bool isAIServiceEnabled)
{
Format = format;
IsEnabled = SupportsClipboardFormats(clipboardFormats) && (isAIServiceEnabled || !Metadata.RequiresAIService);
}
public PasteFormat(AdvancedPasteCustomAction customAction, string shortcutText)
public PasteFormat(PasteFormats format, ClipboardFormat clipboardFormats, bool isAIServiceEnabled, Func<string, string> resourceLoader)
: this(format, clipboardFormats, isAIServiceEnabled)
{
Name = Metadata.ResourceId == null ? string.Empty : resourceLoader(Metadata.ResourceId);
Prompt = string.Empty;
}
public PasteFormat(AdvancedPasteCustomAction customAction, ClipboardFormat clipboardFormats, bool isAIServiceEnabled)
: this(PasteFormats.Custom, clipboardFormats, isAIServiceEnabled)
{
IconGlyph = "\uE945";
Name = customAction.Name;
Prompt = customAction.Prompt;
Format = PasteFormats.Custom;
ShortcutText = shortcutText;
ToolTip = customAction.Prompt;
}
public string IconGlyph { get; init; }
public PasteFormatMetadataAttribute Metadata => MetadataDict[Format];
public string Name { get; init; }
public string IconGlyph => Metadata.IconGlyph;
public PasteFormats Format { get; init; }
public string Name { get; private init; }
public string Prompt { get; init; } = string.Empty;
public PasteFormats Format { get; private init; }
public string Prompt { get; private init; }
public bool IsEnabled { get; private init; }
public double Opacity => IsEnabled ? 1 : 0.5;
public string ToolTip => string.IsNullOrEmpty(Prompt) ? $"{Name} ({ShortcutText})" : Prompt;
public string Query => string.IsNullOrEmpty(Prompt) ? Name : Prompt;
public string ShortcutText { get; set; } = string.Empty;
public bool SupportsClipboardFormats(ClipboardFormat clipboardFormats) => (clipboardFormats & Metadata.SupportedClipboardFormats) != ClipboardFormat.None;
}

View File

@@ -0,0 +1,23 @@
// 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.Models;
[AttributeUsage(AttributeTargets.Field)]
public sealed class PasteFormatMetadataAttribute : Attribute
{
public bool IsCoreAction { get; init; }
public string ResourceId { get; init; }
public string IconGlyph { get; init; }
public bool RequiresAIService { get; init; }
public ClipboardFormat SupportedClipboardFormats { get; init; }
public string IPCKey { get; init; }
}

View File

@@ -2,13 +2,33 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace AdvancedPaste.Models
using Microsoft.PowerToys.Settings.UI.Library;
namespace AdvancedPaste.Models;
public enum PasteFormats
{
public enum PasteFormats
{
PlainText,
Markdown,
Json,
Custom,
}
[PasteFormatMetadata(IsCoreAction = true, ResourceId = "PasteAsPlainText", IconGlyph = "\uE8E9", RequiresAIService = false, SupportedClipboardFormats = ClipboardFormat.Text)]
PlainText,
[PasteFormatMetadata(IsCoreAction = true, ResourceId = "PasteAsMarkdown", IconGlyph = "\ue8a5", RequiresAIService = false, SupportedClipboardFormats = ClipboardFormat.Text)]
Markdown,
[PasteFormatMetadata(IsCoreAction = true, ResourceId = "PasteAsJson", IconGlyph = "\uE943", RequiresAIService = false, SupportedClipboardFormats = ClipboardFormat.Text)]
Json,
[PasteFormatMetadata(IsCoreAction = false, ResourceId = "ImageToText", IconGlyph = "\uE91B", RequiresAIService = false, SupportedClipboardFormats = ClipboardFormat.Image | ClipboardFormat.ImageFile, IPCKey = AdvancedPasteAdditionalActions.PropertyNames.ImageToText)]
ImageToText,
[PasteFormatMetadata(IsCoreAction = false, ResourceId = "PasteAsTxtFile", IconGlyph = "\uE8D2", RequiresAIService = false, SupportedClipboardFormats = ClipboardFormat.Text | ClipboardFormat.Html, IPCKey = AdvancedPastePasteAsFileAction.PropertyNames.PasteAsTxtFile)]
PasteAsTxtFile,
[PasteFormatMetadata(IsCoreAction = false, ResourceId = "PasteAsPngFile", IconGlyph = "\uE8B9", RequiresAIService = false, SupportedClipboardFormats = ClipboardFormat.Image | ClipboardFormat.ImageFile, IPCKey = AdvancedPastePasteAsFileAction.PropertyNames.PasteAsPngFile)]
PasteAsPngFile,
[PasteFormatMetadata(IsCoreAction = false, ResourceId = "PasteAsHtmlFile", IconGlyph = "\uF6FA", RequiresAIService = false, SupportedClipboardFormats = ClipboardFormat.Html, IPCKey = AdvancedPastePasteAsFileAction.PropertyNames.PasteAsHtmlFile)]
PasteAsHtmlFile,
[PasteFormatMetadata(IsCoreAction = false, IconGlyph = "\uE945", RequiresAIService = true, SupportedClipboardFormats = ClipboardFormat.Text)]
Custom,
}

View File

@@ -0,0 +1,13 @@
// 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.Threading.Tasks;
using AdvancedPaste.Models;
namespace AdvancedPaste.Services;
public interface IPasteFormatExecutor
{
Task<string> ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source);
}

View File

@@ -0,0 +1,253 @@
// 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.IO;
using System.Net;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
using ManagedCommon;
using Microsoft.PowerToys.Telemetry;
using Windows.ApplicationModel.DataTransfer;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
namespace AdvancedPaste.Services;
public sealed class PasteFormatExecutor(AICompletionsHelper aiHelper) : IPasteFormatExecutor
{
private readonly AICompletionsHelper _aiHelper = aiHelper;
public async Task<string> ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source)
{
if (!pasteFormat.IsEnabled)
{
return null;
}
WriteTelemetry(pasteFormat.Format, source);
return await ExecutePasteFormatCoreAsync(pasteFormat, Clipboard.GetContent());
}
private async Task<string> ExecutePasteFormatCoreAsync(PasteFormat pasteFormat, DataPackageView clipboardData)
{
switch (pasteFormat.Format)
{
case PasteFormats.PlainText:
ToPlainText(clipboardData);
return null;
case PasteFormats.Markdown:
ToMarkdown(clipboardData);
return null;
case PasteFormats.Json:
ToJson(clipboardData);
return null;
case PasteFormats.ImageToText:
await ImageToTextAsync(clipboardData);
return null;
case PasteFormats.PasteAsTxtFile:
await ToTxtFileAsync(clipboardData);
return null;
case PasteFormats.PasteAsPngFile:
await ToPngFileAsync(clipboardData);
return null;
case PasteFormats.PasteAsHtmlFile:
await ToHtmlFileAsync(clipboardData);
return null;
case PasteFormats.Custom:
return await ToCustomAsync(pasteFormat.Prompt, clipboardData);
default:
throw new ArgumentException($"Unknown paste format {pasteFormat.Format}", nameof(pasteFormat));
}
}
private static void WriteTelemetry(PasteFormats format, PasteActionSource source)
{
switch (source)
{
case PasteActionSource.ContextMenu:
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(format));
break;
case PasteActionSource.InAppKeyboardShortcut:
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(format));
break;
case PasteActionSource.GlobalKeyboardShortcut:
case PasteActionSource.PromptBox:
break; // no telemetry yet for these sources
default:
throw new ArgumentOutOfRangeException(nameof(format));
}
}
private void ToPlainText(DataPackageView clipboardData)
{
Logger.LogTrace();
SetClipboardTextContent(MarkdownHelper.PasteAsPlainTextFromClipboard(clipboardData));
}
private void ToMarkdown(DataPackageView clipboardData)
{
Logger.LogTrace();
SetClipboardTextContent(MarkdownHelper.ToMarkdown(clipboardData));
}
private void ToJson(DataPackageView clipboardData)
{
Logger.LogTrace();
SetClipboardTextContent(JsonHelper.ToJsonFromXmlOrCsv(clipboardData));
}
private async Task ImageToTextAsync(DataPackageView clipboardData)
{
Logger.LogTrace();
var bitmap = await ClipboardHelper.GetClipboardImageContentAsync(clipboardData);
var text = await OcrHelpers.ExtractTextAsync(bitmap);
SetClipboardTextContent(text);
}
private async Task ToPngFileAsync(DataPackageView clipboardData)
{
Logger.LogTrace();
var clipboardBitmap = await ClipboardHelper.GetClipboardImageContentAsync(clipboardData);
using var pngStream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, pngStream);
encoder.SetSoftwareBitmap(clipboardBitmap);
await encoder.FlushAsync();
await SetClipboardFileContentAsync(pngStream.AsStreamForRead(), "png");
}
private async Task ToTxtFileAsync(DataPackageView clipboardData)
{
Logger.LogTrace();
var text = await ClipboardHelper.GetClipboardTextOrHtmlTextAsync(clipboardData);
await SetClipboardFileContentAsync(text, "txt");
}
private async Task ToHtmlFileAsync(DataPackageView clipboardData)
{
Logger.LogTrace();
var cfHtml = await ClipboardHelper.GetClipboardHtmlContentAsync(clipboardData);
var html = RemoveHtmlMetadata(cfHtml);
await SetClipboardFileContentAsync(html, "html");
}
/// <summary>
/// Removes leading CF_HTML metadata from HTML clipboard data.
/// See: https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
/// </summary>
private static string RemoveHtmlMetadata(string cfHtml)
{
int? GetIntTagValue(string tagName)
{
var tagNameWithColon = tagName + ":";
int tagStartPos = cfHtml.IndexOf(tagNameWithColon, StringComparison.InvariantCulture);
const int tagValueLength = 10;
return tagStartPos != -1 && int.TryParse(cfHtml.AsSpan(tagStartPos + tagNameWithColon.Length, tagValueLength), CultureInfo.InvariantCulture, out int result) ? result : null;
}
var startFragmentIndex = GetIntTagValue("StartFragment");
var endFragmentIndex = GetIntTagValue("EndFragment");
return (startFragmentIndex == null || endFragmentIndex == null) ? cfHtml : cfHtml[startFragmentIndex.Value..endFragmentIndex.Value];
}
private static async Task SetClipboardFileContentAsync(string data, string fileExtension)
{
if (string.IsNullOrEmpty(data))
{
throw new ArgumentException($"Empty value in {nameof(SetClipboardFileContentAsync)}", nameof(data));
}
var path = GetPasteAsFileTempFilePath(fileExtension);
await File.WriteAllTextAsync(path, data);
await ClipboardHelper.SetClipboardFileContentAsync(path);
}
private static async Task SetClipboardFileContentAsync(Stream stream, string fileExtension)
{
var path = GetPasteAsFileTempFilePath(fileExtension);
using var fileStream = File.Create(path);
await stream.CopyToAsync(fileStream);
await ClipboardHelper.SetClipboardFileContentAsync(path);
}
private static string GetPasteAsFileTempFilePath(string fileExtension)
{
var prefix = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsFile_FilePrefix");
var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture);
return Path.Combine(Path.GetTempPath(), $"{prefix}{timestamp}.{fileExtension}");
}
private async Task<string> ToCustomAsync(string prompt, DataPackageView clipboardData)
{
Logger.LogTrace();
if (string.IsNullOrWhiteSpace(prompt))
{
return string.Empty;
}
if (!clipboardData.Contains(StandardDataFormats.Text))
{
Logger.LogWarning("Clipboard does not contain text data");
return string.Empty;
}
var currentClipboardText = await clipboardData.GetTextAsync();
if (string.IsNullOrWhiteSpace(currentClipboardText))
{
Logger.LogWarning("Clipboard has no usable text data");
return string.Empty;
}
var aiResponse = await Task.Run(() => _aiHelper.AIFormatString(prompt, currentClipboardText));
return aiResponse.ApiRequestStatus == (int)HttpStatusCode.OK
? aiResponse.Response
: throw new PasteActionException(TranslateErrorText(aiResponse.ApiRequestStatus));
}
private void SetClipboardTextContent(string content)
{
if (!string.IsNullOrEmpty(content))
{
ClipboardHelper.SetClipboardTextContent(content);
}
}
private static string TranslateErrorText(int apiRequestStatus) => (HttpStatusCode)apiRequestStatus switch
{
HttpStatusCode.TooManyRequests => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests"),
HttpStatusCode.Unauthorized => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized"),
HttpStatusCode.OK => string.Empty,
_ => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + apiRequestStatus.ToString(CultureInfo.InvariantCulture),
};
}

View File

@@ -120,9 +120,12 @@
<data name="AIMistakeNote.Text" xml:space="preserve">
<value>AI can make mistakes.</value>
</data>
<data name="ClipboardDataTypeMismatchWarning" xml:space="preserve">
<value>Clipboard data is not text</value>
<data name="ClipboardEmptyWarning" xml:space="preserve">
<value>Clipboard does not contain any usable formats</value>
</data>
<data name="ClipboardDataNotTextWarning" xml:space="preserve">
<value>Clipboard data is not text</value>
</data>
<data name="OpenAINotConfigured" xml:space="preserve">
<value>To custom with AI is not enabled</value>
</data>
@@ -135,6 +138,9 @@
<data name="OpenAIApiKeyError" xml:space="preserve">
<value>OpenAI request failed with status code: </value>
</data>
<data name="PasteError" xml:space="preserve">
<value>An error occurred during the paste operation</value>
</data>
<data name="ClipboardHistoryButton.Text" xml:space="preserve">
<value>Clipboard history</value>
</data>
@@ -151,7 +157,7 @@
<value>Privacy</value>
</data>
<data name="LoadingText.Text" xml:space="preserve">
<value>Connecting to AI services and generating output..</value>
<value>Generating output...</value>
</data>
<data name="PasteAsJson" xml:space="preserve">
<value>Paste as JSON</value>
@@ -162,6 +168,18 @@
<data name="PasteAsPlainText" xml:space="preserve">
<value>Paste as plain text</value>
</data>
<data name="ImageToText" xml:space="preserve">
<value>Image to text</value>
</data>
<data name="PasteAsTxtFile" xml:space="preserve">
<value>Paste as .txt file</value>
</data>
<data name="PasteAsPngFile" xml:space="preserve">
<value>Paste as .png file</value>
</data>
<data name="PasteAsHtmlFile" xml:space="preserve">
<value>Paste as .html file</value>
</data>
<data name="PasteButtonAutomation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Paste</value>
</data>
@@ -228,4 +246,7 @@
<data name="CtrlKey" xml:space="preserve">
<value>Ctrl</value>
</data>
<data name="PasteAsFile_FilePrefix" xml:space="preserve">
<value>PowerToys_Paste_</value>
</data>
</root>

View File

@@ -3,21 +3,20 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
using AdvancedPaste.Services;
using AdvancedPaste.Settings;
using Common.UI;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
using Microsoft.Win32;
using Windows.ApplicationModel.DataTransfer;
@@ -28,67 +27,66 @@ using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
namespace AdvancedPaste.ViewModels
{
public partial class OptionsViewModel : ObservableObject, IDisposable
public sealed partial class OptionsViewModel : ObservableObject, IDisposable
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private readonly DispatcherTimer _clipboardTimer;
private readonly IUserSettings _userSettings;
private readonly AICompletionsHelper aiHelper;
private readonly App app = App.Current as App;
private readonly PasteFormat[] _allStandardPasteFormats;
private readonly IPasteFormatExecutor _pasteFormatExecutor;
private readonly AICompletionsHelper _aiHelper;
public DataPackageView ClipboardData { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
private bool _isClipboardDataText;
[NotifyPropertyChangedFor(nameof(ClipboardHasData))]
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
[NotifyPropertyChangedFor(nameof(AIDisabledErrorText))]
private ClipboardFormat _availableClipboardFormats;
[ObservableProperty]
private bool _clipboardHistoryEnabled;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
[NotifyPropertyChangedFor(nameof(IsPasteWithAIEnabled))]
[NotifyPropertyChangedFor(nameof(AIDisabledErrorText))]
[NotifyPropertyChangedFor(nameof(IsAIServiceEnabled))]
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
private bool _isAllowedByGPO;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ApiErrorText))]
private int _apiRequestStatus;
private string _pasteOperationErrorText;
[ObservableProperty]
private string _query = string.Empty;
private bool _pasteFormatsDirty;
[ObservableProperty]
private bool _busy;
public ObservableCollection<PasteFormat> StandardPasteFormats { get; } = [];
public ObservableCollection<PasteFormat> CustomActionPasteFormats { get; } = [];
public bool IsPasteWithAIEnabled => IsAllowedByGPO && aiHelper.IsAIEnabled;
public bool IsAIServiceEnabled => IsAllowedByGPO && _aiHelper.IsAIEnabled;
public bool IsCustomAIEnabled => IsPasteWithAIEnabled && IsClipboardDataText;
public bool IsCustomAIEnabled => IsAIServiceEnabled && ClipboardHasText;
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
private bool ClipboardHasText => AvailableClipboardFormats.HasFlag(ClipboardFormat.Text);
private bool Visible => GetMainWindow()?.Visible is true;
public event EventHandler<CustomActionActivatedEventArgs> CustomActionActivated;
public OptionsViewModel(IUserSettings userSettings)
public OptionsViewModel(AICompletionsHelper aiHelper, IUserSettings userSettings, IPasteFormatExecutor pasteFormatExecutor)
{
aiHelper = new AICompletionsHelper();
_aiHelper = aiHelper;
_userSettings = userSettings;
_pasteFormatExecutor = pasteFormatExecutor;
ApiRequestStatus = (int)HttpStatusCode.OK;
_allStandardPasteFormats =
[
new PasteFormat { IconGlyph = "\uE8E9", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText },
new PasteFormat { IconGlyph = "\ue8a5", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown },
new PasteFormat { IconGlyph = "\uE943", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json },
];
GeneratedResponses = new ObservableCollection<string>();
GeneratedResponses = [];
GeneratedResponses.CollectionChanged += (s, e) =>
{
OnPropertyChanged(nameof(HasMultipleResponses));
@@ -96,28 +94,31 @@ namespace AdvancedPaste.ViewModels
};
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
ReadClipboard();
UpdateOpenAIKey();
_clipboardTimer = new() { Interval = TimeSpan.FromSeconds(1) };
_clipboardTimer.Tick += ClipboardTimer_Tick;
_clipboardTimer.Start();
RefreshPasteFormats();
_userSettings.CustomActions.CollectionChanged += (_, _) => EnqueueRefreshPasteFormats();
_userSettings.Changed += (_, _) => EnqueueRefreshPasteFormats();
PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(Query) || e.PropertyName == nameof(IsPasteWithAIEnabled))
string[] dirtyingProperties = [nameof(Query), nameof(IsAIServiceEnabled), nameof(IsCustomAIEnabled), nameof(AvailableClipboardFormats)];
if (dirtyingProperties.Contains(e.PropertyName))
{
EnqueueRefreshPasteFormats();
}
};
}
private void ClipboardTimer_Tick(object sender, object e)
private static MainWindow GetMainWindow() => (App.Current as App)?.GetMainWindow();
private async void ClipboardTimer_Tick(object sender, object e)
{
if (app.GetMainWindow()?.Visible is true)
if (Visible)
{
ReadClipboard();
await ReadClipboardAsync();
UpdateAllowedByGPO();
}
}
@@ -137,10 +138,12 @@ namespace AdvancedPaste.ViewModels
});
}
private PasteFormat CreatePasteFormat(PasteFormats format) => new(format, AvailableClipboardFormats, IsAIServiceEnabled, ResourceLoaderInstance.ResourceLoader.GetString);
private PasteFormat CreatePasteFormat(AdvancedPasteCustomAction customAction) => new(customAction, AvailableClipboardFormats, IsAIServiceEnabled);
private void RefreshPasteFormats()
{
bool Filter(string text) => text.Contains(Query, StringComparison.CurrentCultureIgnoreCase);
var ctrlString = ResourceLoaderInstance.ResourceLoader.GetString("CtrlKey");
int shortcutNum = 0;
@@ -150,28 +153,33 @@ namespace AdvancedPaste.ViewModels
return shortcutNum <= 9 ? $"{ctrlString}+{shortcutNum}" : string.Empty;
}
StandardPasteFormats.Clear();
foreach (var format in _allStandardPasteFormats)
IEnumerable<PasteFormat> FilterAndSort(IEnumerable<PasteFormat> pasteFormats) =>
from pasteFormat in pasteFormats
let comparison = StringComparison.CurrentCultureIgnoreCase
where pasteFormat.Name.Contains(Query, comparison) || pasteFormat.Prompt.Contains(Query, comparison)
orderby pasteFormat.IsEnabled descending
select pasteFormat;
void UpdateFormats(ObservableCollection<PasteFormat> collection, IEnumerable<PasteFormat> pasteFormats)
{
if (Filter(format.Name))
collection.Clear();
foreach (var format in FilterAndSort(pasteFormats))
{
format.ShortcutText = GetNextShortcutText();
format.ToolTip = $"{format.Name} ({format.ShortcutText})";
StandardPasteFormats.Add(format);
if (format.IsEnabled)
{
format.ShortcutText = GetNextShortcutText();
}
collection.Add(format);
}
}
CustomActionPasteFormats.Clear();
if (IsPasteWithAIEnabled)
{
foreach (var customAction in _userSettings.CustomActions)
{
if (Filter(customAction.Name) || Filter(customAction.Prompt))
{
CustomActionPasteFormats.Add(new PasteFormat(customAction, GetNextShortcutText()));
}
}
}
UpdateFormats(StandardPasteFormats, Enum.GetValues<PasteFormats>()
.Where(format => PasteFormat.MetadataDict[format].IsCoreAction || _userSettings.AdditionalActions.Contains(format))
.Select(CreatePasteFormat));
UpdateFormats(CustomActionPasteFormats, IsAIServiceEnabled ? _userSettings.CustomActions.Select(CreatePasteFormat) : []);
}
public void Dispose()
@@ -180,26 +188,34 @@ namespace AdvancedPaste.ViewModels
GC.SuppressFinalize(this);
}
public void ReadClipboard()
public async Task ReadClipboardAsync()
{
if (Busy)
{
return;
}
ClipboardData = Clipboard.GetContent();
IsClipboardDataText = ClipboardData.Contains(StandardDataFormats.Text);
AvailableClipboardFormats = await ClipboardHelper.GetAvailableClipboardFormatsAsync(ClipboardData);
}
public void OnShow()
public async Task OnShowAsync()
{
ReadClipboard();
PasteOperationErrorText = string.Empty;
Query = string.Empty;
await ReadClipboardAsync();
if (UpdateOpenAIKey())
{
app.GetMainWindow()?.StartLoading();
GetMainWindow()?.StartLoading();
_dispatcherQueue.TryEnqueue(() =>
{
app.GetMainWindow()?.FinishLoading(aiHelper.IsAIEnabled);
GetMainWindow()?.FinishLoading(_aiHelper.IsAIEnabled);
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
OnPropertyChanged(nameof(GeneralErrorText));
OnPropertyChanged(nameof(IsPasteWithAIEnabled));
OnPropertyChanged(nameof(AIDisabledErrorText));
OnPropertyChanged(nameof(IsAIServiceEnabled));
OnPropertyChanged(nameof(IsCustomAIEnabled));
});
}
@@ -209,7 +225,7 @@ namespace AdvancedPaste.ViewModels
}
// List to store generated responses
public ObservableCollection<string> GeneratedResponses { get; set; } = new ObservableCollection<string>();
public ObservableCollection<string> GeneratedResponses { get; set; } = [];
// Index to keep track of the current response
private int _currentResponseIndex;
@@ -228,30 +244,20 @@ namespace AdvancedPaste.ViewModels
}
}
public bool HasMultipleResponses
{
get => GeneratedResponses.Count > 1;
}
public bool HasMultipleResponses => GeneratedResponses.Count > 1;
public string CurrentIndexDisplay => $"{CurrentResponseIndex + 1}/{GeneratedResponses.Count}";
public string InputTxtBoxPlaceholderText
=> ResourceLoaderInstance.ResourceLoader.GetString(ClipboardHasData ? "CustomFormatTextBox/PlaceholderText" : "ClipboardEmptyWarning");
public string AIDisabledErrorText
{
get
{
app.GetMainWindow().ClearInputText();
return IsClipboardDataText ? ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText") : GeneralErrorText;
}
}
public string GeneralErrorText
{
get
{
if (!IsClipboardDataText)
if (!ClipboardHasText)
{
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataTypeMismatchWarning");
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataNotTextWarning");
}
if (!IsAllowedByGPO)
@@ -259,7 +265,7 @@ namespace AdvancedPaste.ViewModels
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
}
if (!aiHelper.IsAIEnabled)
if (!_aiHelper.IsAIEnabled)
{
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
}
@@ -270,17 +276,6 @@ namespace AdvancedPaste.ViewModels
}
}
public string ApiErrorText
{
get => (HttpStatusCode)ApiRequestStatus switch
{
HttpStatusCode.TooManyRequests => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests"),
HttpStatusCode.Unauthorized => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized"),
HttpStatusCode.OK => string.Empty,
_ => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + ApiRequestStatus.ToString(CultureInfo.InvariantCulture),
};
}
[ObservableProperty]
private string _customFormatResult;
@@ -289,9 +284,17 @@ namespace AdvancedPaste.ViewModels
{
var text = GeneratedResponses.ElementAtOrDefault(CurrentResponseIndex);
if (text != null)
if (!string.IsNullOrEmpty(text))
{
PasteCustomFunction(text);
ClipboardHelper.SetClipboardTextContent(text);
HideWindow();
if (_userSettings.SendPasteKeyCombination)
{
ClipboardHelper.SendPasteKeyCombination();
}
Query = string.Empty;
}
}
@@ -320,190 +323,120 @@ namespace AdvancedPaste.ViewModels
public void OpenSettings()
{
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.AdvancedPaste, true);
(App.Current as App).GetMainWindow().Close();
GetMainWindow()?.Close();
}
private void SetClipboardContentAndHideWindow(string content)
internal async Task ExecutePasteFormatAsync(PasteFormats format, PasteActionSource source)
{
if (!string.IsNullOrEmpty(content))
{
ClipboardHelper.SetClipboardTextContent(content);
}
if (app.GetMainWindow() != null)
{
Windows.Win32.Foundation.HWND hwnd = (Windows.Win32.Foundation.HWND)app.GetMainWindow().GetWindowHandle();
Windows.Win32.PInvoke.ShowWindow(hwnd, Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_HIDE);
}
await ReadClipboardAsync();
await ExecutePasteFormatAsync(CreatePasteFormat(format), source);
}
internal void ToPlainTextFunction()
internal async Task ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source)
{
try
{
Logger.LogTrace();
string outputString = MarkdownHelper.PasteAsPlainTextFromClipboard(ClipboardData);
SetClipboardContentAndHideWindow(outputString);
if (_userSettings.SendPasteKeyCombination)
{
ClipboardHelper.SendPasteKeyCombination();
}
}
catch
{
}
}
internal void ToMarkdownFunction(bool pasteAlways = false)
{
try
{
Logger.LogTrace();
string outputString = MarkdownHelper.ToMarkdown(ClipboardData);
SetClipboardContentAndHideWindow(outputString);
if (pasteAlways || _userSettings.SendPasteKeyCombination)
{
ClipboardHelper.SendPasteKeyCombination();
}
}
catch
{
}
}
internal void ToJsonFunction(bool pasteAlways = false)
{
try
{
Logger.LogTrace();
string jsonText = JsonHelper.ToJsonFromXmlOrCsv(ClipboardData);
SetClipboardContentAndHideWindow(jsonText);
if (pasteAlways || _userSettings.SendPasteKeyCombination)
{
ClipboardHelper.SendPasteKeyCombination();
}
}
catch
{
}
}
internal void ExecutePasteFormat(VirtualKey key)
{
var index = key - VirtualKey.Number1;
var pasteFormat = StandardPasteFormats.ElementAtOrDefault(index) ?? CustomActionPasteFormats.ElementAtOrDefault(index - StandardPasteFormats.Count);
if (pasteFormat != null)
{
ExecutePasteFormat(pasteFormat);
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(pasteFormat.Format));
}
}
internal void ExecutePasteFormat(PasteFormat pasteFormat)
{
if (!IsClipboardDataText || (pasteFormat.Format == PasteFormats.Custom && !IsCustomAIEnabled))
if (Busy)
{
Logger.LogWarning($"Execution of {pasteFormat.Format} from {source} suppressed as busy");
return;
}
switch (pasteFormat.Format)
if (!pasteFormat.IsEnabled)
{
case PasteFormats.PlainText:
ToPlainTextFunction();
break;
var resourceId = pasteFormat.SupportsClipboardFormats(AvailableClipboardFormats) ? "PasteError" : "ClipboardEmptyWarning";
PasteOperationErrorText = ResourceLoaderInstance.ResourceLoader.GetString(resourceId);
return;
}
case PasteFormats.Markdown:
ToMarkdownFunction();
break;
Busy = true;
PasteOperationErrorText = string.Empty;
Query = pasteFormat.Query;
case PasteFormats.Json:
ToJsonFunction();
break;
if (pasteFormat.Format == PasteFormats.Custom)
{
SaveQuery(Query);
}
case PasteFormats.Custom:
Query = pasteFormat.Prompt;
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(pasteFormat.Prompt, false));
break;
try
{
// Minimum time to show busy spinner for AI actions when triggered by global keyboard shortcut.
var aiActionMinTaskTime = TimeSpan.FromSeconds(2);
var delayTask = (Visible && source == PasteActionSource.GlobalKeyboardShortcut) ? Task.Delay(aiActionMinTaskTime) : Task.CompletedTask;
var aiOutput = await _pasteFormatExecutor.ExecutePasteFormatAsync(pasteFormat, source);
await delayTask;
if (pasteFormat.Format != PasteFormats.Custom)
{
HideWindow();
if (source == PasteActionSource.GlobalKeyboardShortcut || _userSettings.SendPasteKeyCombination)
{
ClipboardHelper.SendPasteKeyCombination();
}
}
else
{
var pasteResult = source == PasteActionSource.GlobalKeyboardShortcut || !_userSettings.ShowCustomPreview;
GeneratedResponses.Add(aiOutput);
CurrentResponseIndex = GeneratedResponses.Count - 1;
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(pasteFormat.Prompt, pasteResult));
if (pasteResult)
{
PasteCustom();
}
}
}
catch (Exception ex)
{
Logger.LogError("Error executing paste format", ex);
PasteOperationErrorText = ex is PasteActionException ? ex.Message : ResourceLoaderInstance.ResourceLoader.GetString("PasteError");
}
Busy = false;
}
internal async Task ExecutePasteFormatAsync(VirtualKey key)
{
var pasteFormat = StandardPasteFormats.Concat(CustomActionPasteFormats)
.Where(pasteFormat => pasteFormat.IsEnabled)
.ElementAtOrDefault(key - VirtualKey.Number1);
if (pasteFormat != null)
{
await ExecutePasteFormatAsync(pasteFormat, PasteActionSource.InAppKeyboardShortcut);
}
}
internal void ExecuteCustomActionWithPaste(int customActionId)
internal async Task ExecuteCustomActionAsync(int customActionId, PasteActionSource source)
{
Logger.LogTrace();
await ReadClipboardAsync();
var customAction = _userSettings.CustomActions.FirstOrDefault(customAction => customAction.Id == customActionId);
if (customAction != null)
{
Query = customAction.Prompt;
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(customAction.Prompt, true));
await ExecutePasteFormatAsync(CreatePasteFormat(customAction), source);
}
}
internal async Task<string> GenerateCustomFunction(string inputInstructions)
internal async Task GenerateCustomFunctionAsync(PasteActionSource triggerSource)
{
Logger.LogTrace();
if (string.IsNullOrWhiteSpace(inputInstructions) || !IsCustomAIEnabled)
{
return string.Empty;
}
if (!IsClipboardDataText)
{
Logger.LogWarning("Clipboard does not contain text data");
return string.Empty;
}
string currentClipboardText = await Task.Run(async () =>
{
try
{
string text = await ClipboardData.GetTextAsync() as string;
return text;
}
catch (Exception)
{
// Couldn't get text from the clipboard. Resume with empty text.
return string.Empty;
}
});
if (string.IsNullOrWhiteSpace(currentClipboardText))
{
Logger.LogWarning("Clipboard has no usable text data");
return string.Empty;
}
var aiResponse = await Task.Run(() => aiHelper.AIFormatString(inputInstructions, currentClipboardText));
string aiOutput = aiResponse.Response;
ApiRequestStatus = aiResponse.ApiRequestStatus;
GeneratedResponses.Add(aiOutput);
CurrentResponseIndex = GeneratedResponses.Count - 1;
return aiOutput;
AdvancedPasteCustomAction customAction = new() { Name = "Default", Prompt = Query };
await ExecutePasteFormatAsync(CreatePasteFormat(customAction), triggerSource);
}
internal void PasteCustomFunction(string text)
private void HideWindow()
{
Logger.LogTrace();
var mainWindow = GetMainWindow();
SetClipboardContentAndHideWindow(text);
if (_userSettings.SendPasteKeyCombination)
if (mainWindow != null)
{
ClipboardHelper.SendPasteKeyCombination();
Windows.Win32.Foundation.HWND hwnd = (Windows.Win32.Foundation.HWND)mainWindow.GetWindowHandle();
Windows.Win32.PInvoke.ShowWindow(hwnd, Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_HIDE);
}
}
@@ -524,11 +457,7 @@ namespace AdvancedPaste.ViewModels
return;
}
string currentClipboardText = Task.Run(async () =>
{
string text = await clipboardData.GetTextAsync() as string;
return text;
}).Result;
var currentClipboardText = Task.Run(async () => await clipboardData.GetTextAsync()).Result;
var queryData = new CustomQuery
{
@@ -536,13 +465,13 @@ namespace AdvancedPaste.ViewModels
ClipboardData = currentClipboardText,
};
SettingsUtils utils = new SettingsUtils();
SettingsUtils utils = new();
utils.SaveSettings(queryData.ToString(), Constants.AdvancedPasteModuleName, Constants.LastQueryJsonFileName);
}
internal CustomQuery LoadPreviousQuery()
{
SettingsUtils utils = new SettingsUtils();
SettingsUtils utils = new();
var query = utils.GetSettings<CustomQuery>(Constants.AdvancedPasteModuleName, Constants.LastQueryJsonFileName);
return query;
}
@@ -572,9 +501,9 @@ namespace AdvancedPaste.ViewModels
if (IsAllowedByGPO)
{
var oldKey = aiHelper.GetKey();
var oldKey = _aiHelper.GetKey();
var newKey = AICompletionsHelper.LoadOpenAIKey();
aiHelper.SetOpenAIKey(newKey);
_aiHelper.SetOpenAIKey(newKey);
return newKey != oldKey;
}

View File

@@ -42,6 +42,7 @@ namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_CUSTOM_ACTIONS[] = L"custom-actions";
const wchar_t JSON_KEY_ADDITIONAL_ACTIONS[] = L"additional-actions";
const wchar_t JSON_KEY_SHORTCUT[] = L"shortcut";
const wchar_t JSON_KEY_IS_SHOWN[] = L"isShown";
const wchar_t JSON_KEY_ID[] = L"id";
@@ -73,7 +74,6 @@ private:
HANDLE m_hProcess;
std::thread create_pipe_thread;
std::unique_ptr<CAtlFile> m_write_pipe;
// Time to wait for process to close after sending WM_CLOSE signal
@@ -86,8 +86,18 @@ private:
Hotkey m_paste_as_markdown_hotkey{};
Hotkey m_paste_as_json_hotkey{};
std::vector<Hotkey> m_custom_action_hotkeys;
std::vector<int> m_custom_action_ids;
template<class Id>
struct ActionData
{
Id id;
Hotkey hotkey;
};
using AdditionalAction = ActionData<std::wstring>;
std::vector<AdditionalAction> m_additional_actions;
using CustomAction = ActionData<int>;
std::vector<CustomAction> m_custom_actions;
bool m_preview_custom_format_output = true;
@@ -166,6 +176,34 @@ private:
open_ai_key_exists();
}
static std::wstring kebab_to_pascal_case(const std::wstring& kebab_str)
{
std::wstring result;
bool capitalize_next = true;
for (const auto ch : kebab_str)
{
if (ch == L'-')
{
capitalize_next = true;
}
else
{
if (capitalize_next)
{
result += std::towupper(ch);
capitalize_next = false;
}
else
{
result += ch;
}
}
}
return result;
}
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
{
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
@@ -197,6 +235,39 @@ private:
return false;
}
void process_additional_action(const winrt::hstring& actionName, const winrt::Windows::Data::Json::IJsonValue& actionValue)
{
if (actionValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
return;
}
const auto action = actionValue.GetObjectW();
if (!action.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
return;
}
if (action.HasKey(JSON_KEY_SHORTCUT))
{
const AdditionalAction additionalAction
{
actionName.c_str(),
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_additional_actions.push_back(additionalAction);
}
else
{
for (const auto& [subActionName, subAction] : action)
{
process_additional_action(subActionName, subAction);
}
}
}
void parse_hotkeys(PowerToysSettings::PowerToyValues& settings)
{
auto settingsObject = settings.get_raw_json();
@@ -239,13 +310,23 @@ private:
*hotkey = parse_single_hotkey(keyName, settingsObject);
}
m_custom_action_hotkeys.clear();
m_custom_action_ids.clear();
m_additional_actions.clear();
m_custom_actions.clear();
if (settingsObject.HasKey(JSON_KEY_PROPERTIES))
{
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
if (propertiesObject.HasKey(JSON_KEY_ADDITIONAL_ACTIONS))
{
const auto additionalActions = propertiesObject.GetNamedObject(JSON_KEY_ADDITIONAL_ACTIONS);
for (const auto& [actionName, additionalAction] : additionalActions)
{
process_additional_action(actionName, additionalAction);
}
}
if (propertiesObject.HasKey(JSON_KEY_CUSTOM_ACTIONS))
{
const auto customActions = propertiesObject.GetNamedObject(JSON_KEY_CUSTOM_ACTIONS).GetNamedArray(JSON_KEY_VALUE);
@@ -257,8 +338,13 @@ private:
if (object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
m_custom_action_hotkeys.push_back(parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT)));
m_custom_action_ids.push_back(static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)));
const CustomAction customActionData
{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_custom_actions.push_back(customActionData);
}
}
}
@@ -331,7 +417,7 @@ private:
return;
}
create_pipe_thread = std::thread([&] { start_named_pipe_server(pipe_name.value()); });
std::thread create_pipe_thread ([&]{ start_named_pipe_server(pipe_name.value()); });
launch_process(pipe_name.value());
create_pipe_thread.join();
}
@@ -730,12 +816,19 @@ public:
m_preview_custom_format_output = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
}
std::unordered_map<std::wstring, Hotkey> additionalActionMap;
for (const auto& action : m_additional_actions)
{
additionalActionMap[kebab_to_pascal_case(action.id)] = action.hotkey;
}
// order of args matter
Trace::AdvancedPaste_SettingsTelemetry(m_paste_as_plain_hotkey,
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey,
m_preview_custom_format_output);
m_preview_custom_format_output,
additionalActionMap);
// If you don't need to do any custom processing of the settings, proceed
// to persists the values calling:
@@ -825,11 +918,24 @@ public:
return true;
}
const auto custom_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (custom_action_index < m_custom_action_ids.size())
const auto additional_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (additional_action_index < m_additional_actions.size())
{
const auto id = m_custom_action_ids.at(custom_action_index);
const auto& id = m_additional_actions.at(additional_action_index).id;
Logger::trace(L"Starting additional action id={}", id);
Trace::AdvancedPaste_Invoked(std::format(L"{}Direct", kebab_to_pascal_case(id)));
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_ADDITIONAL_ACTION_MESSAGE, id);
return true;
}
const auto custom_action_index = additional_action_index - m_additional_actions.size();
if (custom_action_index < m_custom_actions.size())
{
const auto id = m_custom_actions.at(custom_action_index).id;
Logger::trace(L"Starting custom action id={}", id);
@@ -844,7 +950,7 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_custom_action_hotkeys.size();
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_additional_actions.size() + m_custom_actions.size();
if (hotkeys && buffer_size >= num_hotkeys)
{
@@ -852,9 +958,11 @@ public:
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey };
std::copy(default_hotkeys.begin(), default_hotkeys.end(), hotkeys);
std::copy(m_custom_action_hotkeys.begin(), m_custom_action_hotkeys.end(), hotkeys + NUM_DEFAULT_HOTKEYS);
const auto get_action_hotkey = [](const auto& action) { return action.hotkey; };
std::transform(m_additional_actions.begin(), m_additional_actions.end(), hotkeys + NUM_DEFAULT_HOTKEYS, get_action_hotkey);
std::transform(m_custom_actions.begin(), m_custom_actions.end(), hotkeys + NUM_DEFAULT_HOTKEYS + m_additional_actions.size(), get_action_hotkey);
}
return num_hotkeys;

View File

@@ -58,45 +58,44 @@ void Trace::AdvancedPaste_SettingsTelemetry(const PowertoyModuleIface::Hotkey& p
const PowertoyModuleIface::Hotkey& advancedPasteUIHotkey,
const PowertoyModuleIface::Hotkey& pasteMarkdownHotkey,
const PowertoyModuleIface::Hotkey& pasteJsonHotkey,
const bool preview_custom_format_output) noexcept
const bool preview_custom_format_output,
const std::unordered_map<std::wstring, PowertoyModuleIface::Hotkey>& additionalActionsHotkeys) noexcept
{
std::wstring pastePlainHotkeyStr =
std::wstring(pastePlainHotkey.win ? L"Win + " : L"") +
std::wstring(pastePlainHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(pastePlainHotkey.shift ? L"Shift + " : L"") +
std::wstring(pastePlainHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(pastePlainHotkey.key);
const auto getHotKeyStr = [](const PowertoyModuleIface::Hotkey& hotKey)
{
return std::wstring(hotKey.win ? L"Win + " : L"") +
std::wstring(hotKey.ctrl ? L"Ctrl + " : L"") +
std::wstring(hotKey.shift ? L"Shift + " : L"") +
std::wstring(hotKey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(hotKey.key);
};
std::wstring advancedPasteUIHotkeyStr =
std::wstring(advancedPasteUIHotkey.win ? L"Win + " : L"") +
std::wstring(advancedPasteUIHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(advancedPasteUIHotkey.shift ? L"Shift + " : L"") +
std::wstring(advancedPasteUIHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(advancedPasteUIHotkey.key);
std::vector<std::wstring> hotkeyStrs;
const auto getHotkeyCStr = [&](const PowertoyModuleIface::Hotkey& hotkey)
{
hotkeyStrs.push_back(getHotKeyStr(hotkey)); // Probably unnecessary, but offers protection against the macro TraceLoggingWideString expanding to something that would invalidate the pointer
return hotkeyStrs.back().c_str();
};
std::wstring pasteMarkdownHotkeyStr =
std::wstring(pasteMarkdownHotkey.win ? L"Win + " : L"") +
std::wstring(pasteMarkdownHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(pasteMarkdownHotkey.shift ? L"Shift + " : L"") +
std::wstring(pasteMarkdownHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(pasteMarkdownHotkey.key);
std::wstring pasteJsonHotkeyStr =
std::wstring(pasteJsonHotkey.win ? L"Win + " : L"") +
std::wstring(pasteJsonHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(pasteJsonHotkey.shift ? L"Shift + " : L"") +
std::wstring(pasteJsonHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(pasteJsonHotkey.key);
const auto getAdditionalActionHotkeyCStr = [&](const std::wstring& name)
{
const auto it = additionalActionsHotkeys.find(name);
return it != additionalActionsHotkeys.end() ? getHotkeyCStr(it->second) : L"";
};
TraceLoggingWrite(
g_hProvider,
"AdvancedPaste_Settings",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(pastePlainHotkeyStr.c_str(), "PastePlainHotkey"),
TraceLoggingWideString(advancedPasteUIHotkeyStr.c_str(), "AdvancedPasteUIHotkey"),
TraceLoggingWideString(pasteMarkdownHotkeyStr.c_str(), "PasteMarkdownHotkey"),
TraceLoggingWideString(pasteJsonHotkeyStr.c_str(), "PasteJsonHotkey"),
TraceLoggingBoolean(preview_custom_format_output, "ShowCustomPreview")
TraceLoggingWideString(getHotkeyCStr(pastePlainHotkey), "PastePlainHotkey"),
TraceLoggingWideString(getHotkeyCStr(advancedPasteUIHotkey), "AdvancedPasteUIHotkey"),
TraceLoggingWideString(getHotkeyCStr(pasteMarkdownHotkey), "PasteMarkdownHotkey"),
TraceLoggingWideString(getHotkeyCStr(pasteJsonHotkey), "PasteJsonHotkey"),
TraceLoggingBoolean(preview_custom_format_output, "ShowCustomPreview"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"ImageToText"), "ImageToTextHotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsTxtFile"), "PasteAsTxtFileHotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsPngFile"), "PasteAsPngFileHotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsHtmlFile"), "PasteAsHtmlFileHotkey")
);
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <interface/powertoy_module_interface.h>
#include <unordered_map>
class Trace
{
@@ -21,5 +22,6 @@ public:
const PowertoyModuleIface::Hotkey& advancedPasteUIHotkey,
const PowertoyModuleIface::Hotkey& pasteMarkdownHotkey,
const PowertoyModuleIface::Hotkey& pasteJsonHotkey,
const bool preview_custom_format_output) noexcept;
const bool preview_custom_format_output,
const std::unordered_map<std::wstring, PowertoyModuleIface::Hotkey>& additionalActionsHotkeys) noexcept;
};

View File

@@ -20,10 +20,6 @@
<!-- This package is a dependency of Microsoft.Extensions.Logging.EventLog, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
</PackageReference>
<PackageReference Include="System.CodeDom">
<!-- This package is a dependency of System.Management, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
@@ -137,8 +137,8 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -149,8 +149,8 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240829007\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2739.15\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.6.240923002\build\native\Microsoft.WindowsAppSDK.targets'))" />
</Target>
</Project>

View File

@@ -4,5 +4,5 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.240829007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.240923002" targetFramework="native" />
</packages>

View File

@@ -2,20 +2,15 @@
// 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.Drawing;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Common.Helpers;
using MouseJumpUI.Common.Imaging;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Styles;
using MouseJumpUI.Helpers;
using MouseJump.Common.Helpers;
using MouseJump.Common.Imaging;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.Models.Styles;
namespace MouseJumpUI.UnitTests.Common.Helpers;
namespace MouseJump.Common.UnitTests.Helpers;
[TestClass]
public static class DrawingHelperTests
@@ -60,8 +55,8 @@ public static class DrawingHelperTests
new(0, 500, 500, 500),
},
activatedLocation: new(x: 50, y: 50),
desktopImageFilename: "Common/Helpers/_test-4grid-desktop.png",
expectedImageFilename: "Common/Helpers/_test-4grid-expected.png"),
desktopImageFilename: "_test-4grid-desktop.png",
expectedImageFilename: "_test-4grid-expected.png"),
};
/* win 11 */
yield return new object[]
@@ -74,8 +69,8 @@ public static class DrawingHelperTests
new(0, 0, 5120, 1440),
},
activatedLocation: new(x: 50, y: 50),
desktopImageFilename: "Common/Helpers/_test-win11-desktop.png",
expectedImageFilename: "Common/Helpers/_test-win11-expected.png"),
desktopImageFilename: "_test-win11-desktop.png",
expectedImageFilename: "_test-win11-expected.png"),
};
}
@@ -104,13 +99,19 @@ public static class DrawingHelperTests
private static Bitmap LoadImageResource(string filename)
{
// assume embedded resources are in the same source folder as this
// class, and the namespace hierarchy matches the folder structure.
// that way we can build resource names from the current namespace
var resourcePrefix = typeof(DrawingHelperTests).Namespace;
var resourceName = $"{resourcePrefix}.{filename}";
var assembly = Assembly.GetExecutingAssembly();
var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
var resourceName = $"Microsoft.{assemblyName.Name}.{filename.Replace("/", ".")}";
var resourceNames = assembly.GetManifestResourceNames();
if (!resourceNames.Contains(resourceName))
{
throw new InvalidOperationException($"Embedded resource '{resourceName}' does not exist.");
var message = $"Embedded resource '{resourceName}' does not exist. " +
"Valid resource names are: \r\n" + string.Join("\r\n", resourceNames);
throw new InvalidOperationException(message);
}
var stream = assembly.GetManifestResourceStream(resourceName)

View File

@@ -2,17 +2,15 @@
// 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.Drawing;
using System.Text.Json;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Common.Helpers;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Layout;
using MouseJumpUI.Common.Models.Styles;
using MouseJump.Common.Helpers;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.Models.Layout;
using MouseJump.Common.Models.Styles;
namespace MouseJumpUI.UnitTests.Common.Helpers;
namespace MouseJump.Common.UnitTests.Helpers;
[TestClass]
public static class LayoutHelperTests

View File

@@ -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.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Common.Helpers;
using MouseJumpUI.Common.Models.Drawing;
using MouseJump.Common.Helpers;
using MouseJump.Common.Models.Drawing;
namespace MouseJumpUI.UnitTests.Common.Helpers;
namespace MouseJump.Common.UnitTests.Helpers;
[TestClass]
public static class MouseHelperTests

View File

@@ -2,12 +2,10 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Common.Models.Drawing;
using MouseJump.Common.Models.Drawing;
namespace MouseJumpUI.UnitTests.Common.Models.Drawing;
namespace MouseJump.Common.UnitTests.Models.Drawing;
[TestClass]
public static class RectangleInfoTests

View File

@@ -2,12 +2,10 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Common.Models.Drawing;
using MouseJump.Common.Models.Drawing;
namespace MouseJumpUI.UnitTests.Common.Models.Drawing;
namespace MouseJump.Common.UnitTests.Models.Drawing;
[TestClass]
public static class SizeInfoTests

View File

@@ -0,0 +1,44 @@
<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>{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}</ProjectGuid>
<AssemblyName>PowerToys.MouseJump.Common.UnitTests</AssemblyName>
<AssemblyTitle>PowerToys.MouseJump.Common.UnitTests</AssemblyTitle>
<AssemblyDescription>PowerToys MouseJump.Common.UnitTests</AssemblyDescription>
<OutputType>Library</OutputType>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\MouseJump.Common.UnitTests\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Remove="Helpers\_test-4grid-desktop.png" />
<None Remove="Helpers\_test-4grid-expected.png" />
<None Remove="Helpers\_test-win11-desktop.png" />
<None Remove="Helpers\_test-win11-expected.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Helpers\_test-4grid-desktop.png" />
<EmbeddedResource Include="Helpers\_test-4grid-expected.png" />
<EmbeddedResource Include="Helpers\_test-win11-desktop.png" />
<EmbeddedResource Include="Helpers\_test-win11-expected.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MouseJump.Common\MouseJump.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -2,22 +2,18 @@
// 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;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using MouseJumpUI.Common.Imaging;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Layout;
using MouseJumpUI.Common.Models.Styles;
using MouseJump.Common.Imaging;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.Models.Layout;
using MouseJump.Common.Models.Styles;
namespace MouseJumpUI.Common.Helpers;
namespace MouseJump.Common.Helpers;
internal static class DrawingHelper
public static class DrawingHelper
{
public static Bitmap RenderPreview(
PreviewLayout previewLayout,

View File

@@ -2,17 +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 System;
using System.Collections.Generic;
using System.Linq;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.Models.Layout;
using MouseJump.Common.Models.Styles;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Layout;
using MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Helpers;
namespace MouseJumpUI.Common.Helpers;
internal static class LayoutHelper
public static class LayoutHelper
{
public static PreviewLayout GetPreviewLayout(
PreviewStyle previewStyle, List<RectangleInfo> screens, PointInfo activatedLocation)
@@ -111,7 +107,7 @@ internal static class LayoutHelper
/// <returns>A <see cref="BoxBounds"/> object that represents the bounds of the different areas of the box.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="contentBounds"/> or <paramref name="boxStyle"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when any of the styles in <paramref name="boxStyle"/> is null.</exception>
internal static BoxBounds GetBoxBoundsFromContentBounds(
public static BoxBounds GetBoxBoundsFromContentBounds(
RectangleInfo contentBounds,
BoxStyle boxStyle)
{
@@ -139,7 +135,7 @@ internal static class LayoutHelper
/// <returns>A <see cref="BoxBounds"/> object that represents the bounds of the different areas of the box.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="outerBounds"/> or <paramref name="boxStyle"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when any of the styles in <paramref name="boxStyle"/> is null.</exception>
internal static BoxBounds GetBoxBoundsFromOuterBounds(
public static BoxBounds GetBoxBoundsFromOuterBounds(
RectangleInfo outerBounds,
BoxStyle boxStyle)
{

View File

@@ -5,15 +5,14 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.NativeMethods;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.NativeMethods;
using static MouseJump.Common.NativeMethods.Core;
using static MouseJump.Common.NativeMethods.User32;
using static MouseJumpUI.Common.NativeMethods.Core;
using static MouseJumpUI.Common.NativeMethods.User32;
namespace MouseJump.Common.Helpers;
namespace MouseJumpUI.Common.Helpers;
internal static class MouseHelper
public static class MouseHelper
{
/// <summary>
/// Calculates where to move the cursor to by projecting a point from
@@ -24,7 +23,7 @@ internal static class MouseHelper
/// or even negative if the primary monitor is not the at the top-left of the
/// entire desktop rectangle, so results may contain negative coordinates.
/// </remarks>
internal static PointInfo GetJumpLocation(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds)
public static PointInfo GetJumpLocation(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds)
{
return previewLocation
.Scale(previewSize.ScaleToFitRatio(desktopBounds.Size))
@@ -34,7 +33,7 @@ internal static class MouseHelper
/// <summary>
/// Get the current position of the cursor.
/// </summary>
internal static PointInfo GetCursorPosition()
public static PointInfo GetCursorPosition()
{
var lpPoint = new LPPOINT(new POINT(0, 0));
var result = User32.GetCursorPos(lpPoint);
@@ -57,7 +56,7 @@ internal static class MouseHelper
/// <remarks>
/// See https://github.com/mikeclayton/FancyMouse/pull/3
/// </remarks>
internal static void SetCursorPosition(PointInfo location)
public static void SetCursorPosition(PointInfo location)
{
// set the new cursor position *twice* - the cursor sometimes end up in
// the wrong place if we try to cross the dead space between non-aligned

View File

@@ -2,26 +2,22 @@
// 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.ComponentModel;
using System.Linq;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.NativeMethods;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.NativeMethods;
using static MouseJump.Common.NativeMethods.Core;
using static MouseJump.Common.NativeMethods.User32;
using static MouseJumpUI.Common.NativeMethods.Core;
using static MouseJumpUI.Common.NativeMethods.User32;
namespace MouseJump.Common.Helpers;
namespace MouseJumpUI.Common.Helpers;
internal static class ScreenHelper
public static class ScreenHelper
{
/// <summary>
/// Duplicates functionality available in System.Windows.Forms.SystemInformation
/// to reduce the dependency on WinForms
/// </summary>
public static RectangleInfo GetVirtualScreen()
private static RectangleInfo GetVirtualScreen()
{
return new(
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_XVIRTUALSCREEN),
@@ -30,7 +26,7 @@ internal static class ScreenHelper
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN));
}
internal static IEnumerable<ScreenInfo> GetAllScreens()
public static IEnumerable<ScreenInfo> GetAllScreens()
{
// enumerate the monitors attached to the system
var hMonitors = new List<HMONITOR>();
@@ -80,7 +76,7 @@ internal static class ScreenHelper
}
}
internal static ScreenInfo GetScreenFromPoint(
public static ScreenInfo GetScreenFromPoint(
List<ScreenInfo> screens,
PointInfo pt)
{

View File

@@ -2,15 +2,12 @@
// 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.Drawing;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.Models.Styles;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Helpers;
namespace MouseJumpUI.Helpers;
internal static class StyleHelper
public static class StyleHelper
{
/// <summary>
/// Default v2 preview style

View File

@@ -2,22 +2,19 @@
// 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.Diagnostics;
using System.Drawing;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.NativeMethods;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.NativeMethods;
using static MouseJump.Common.NativeMethods.Core;
using static MouseJumpUI.Common.NativeMethods.Core;
namespace MouseJumpUI.Common.Imaging;
namespace MouseJump.Common.Imaging;
/// <summary>
/// Implements an IImageRegionCopyService that uses the current desktop window as the copy source.
/// This is used during the main application runtime to generate preview images of the desktop.
/// </summary>
internal sealed class DesktopImageRegionCopyService : IImageRegionCopyService
public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
{
/// <summary>
/// Copies the source region from the current desktop window

View File

@@ -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.Drawing;
using MouseJump.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Drawing;
namespace MouseJump.Common.Imaging;
namespace MouseJumpUI.Common.Imaging;
internal interface IImageRegionCopyService
public interface IImageRegionCopyService
{
/// <summary>
/// Copies the source region from the provider's source image (e.g. the interactive desktop,

View File

@@ -2,18 +2,15 @@
// 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.Drawing;
using MouseJump.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Drawing;
namespace MouseJumpUI.Common.Imaging;
namespace MouseJump.Common.Imaging;
/// <summary>
/// Implements an IImageRegionCopyService that uses the specified image as the copy source.
/// This is used for testing the DrawingHelper rather than as part of the main application.
/// </summary>
internal sealed class StaticImageRegionCopyService : IImageRegionCopyService
public sealed class StaticImageRegionCopyService : IImageRegionCopyService
{
public StaticImageRegionCopyService(Image sourceImage)
{

View File

@@ -2,9 +2,7 @@
// 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 MouseJumpUI.Common.Models.Drawing;
namespace MouseJump.Common.Models.Drawing;
public sealed class BoxBounds
{
@@ -28,7 +26,7 @@ public sealed class BoxBounds
*/
internal BoxBounds(
public BoxBounds(
RectangleInfo outerBounds,
RectangleInfo marginBounds,
RectangleInfo borderBounds,

View File

@@ -2,10 +2,7 @@
// 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.Drawing;
namespace MouseJumpUI.Common.Models.Drawing;
namespace MouseJump.Common.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Point object with some extra utility methods.

View File

@@ -2,15 +2,12 @@
// 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.Drawing;
using System.Text.Json.Serialization;
using MouseJumpUI.Common.Models.Styles;
using MouseJump.Common.Models.Styles;
using BorderStyle = MouseJump.Common.Models.Styles.BorderStyle;
using BorderStyle = MouseJumpUI.Common.Models.Styles.BorderStyle;
namespace MouseJumpUI.Common.Models.Drawing;
namespace MouseJump.Common.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods.

View File

@@ -2,19 +2,15 @@
// 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 static MouseJumpUI.Common.NativeMethods.Core;
namespace MouseJumpUI.Common.Models.Drawing;
namespace MouseJump.Common.Models.Drawing;
/// <summary>
/// Immutable version of a System.Windows.Forms.Screen object so we don't need to
/// take a dependency on WinForms just for screen info.
/// </summary>
internal sealed class ScreenInfo
public sealed class ScreenInfo
{
internal ScreenInfo(HMONITOR handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
public ScreenInfo(int handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
{
this.Handle = handle;
this.Primary = primary;

View File

@@ -2,14 +2,10 @@
// 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.Drawing;
using MouseJump.Common.Models.Styles;
using BorderStyle = MouseJump.Common.Models.Styles.BorderStyle;
using MouseJumpUI.Common.Models.Styles;
using BorderStyle = MouseJumpUI.Common.Models.Styles.BorderStyle;
namespace MouseJumpUI.Common.Models.Drawing;
namespace MouseJump.Common.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Size object with some extra utility methods.

View File

@@ -2,15 +2,12 @@
// 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 MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Styles;
using MouseJump.Common.Models.Drawing;
using MouseJump.Common.Models.Styles;
namespace MouseJumpUI.Common.Models.Layout;
namespace MouseJump.Common.Models.Layout;
public sealed class PreviewLayout
{

View File

@@ -2,9 +2,7 @@
// 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.Drawing;
namespace MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Models.Styles;
/// <summary>
/// Represents the background fill style for a drawing object.

View File

@@ -2,9 +2,7 @@
// 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.Drawing;
namespace MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Models.Styles;
/// <summary>
/// Represents the border style for a drawing object.

View File

@@ -2,9 +2,7 @@
// 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 MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Models.Styles;
/// <summary>
/// Represents the styles to apply to a simple box-layout based drawing object.

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Models.Styles;
/// <summary>
/// Represents the margin style for a drawing object.

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Models.Styles;
/// <summary>
/// Represents the margin style for a drawing object.

View File

@@ -2,11 +2,9 @@
// 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 MouseJump.Common.Models.Drawing;
using MouseJumpUI.Common.Models.Drawing;
namespace MouseJumpUI.Common.Models.Styles;
namespace MouseJump.Common.Models.Styles;
public sealed class PreviewStyle
{

View File

@@ -0,0 +1,24 @@
<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>{923DF87C-CA99-4D1C-B1D2-959174E95BFA}</ProjectGuid>
<AssemblyName>PowerToys.MouseJump.Common</AssemblyName>
<AssemblyTitle>PowerToys.MouseJump.Common</AssemblyTitle>
<AssemblyDescription>PowerToys MouseJump.Common</AssemblyDescription>
<OutputType>Library</OutputType>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -5,7 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -4,7 +4,7 @@
using System.Runtime.InteropServices;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,9 +2,7 @@
// 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 MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,9 +2,7 @@
// 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 MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,9 +2,7 @@
// 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 MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,10 +2,9 @@
// 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.Diagnostics.CodeAnalysis;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -1,9 +1,8 @@
// 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 MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,10 +2,9 @@
// 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.Runtime.InteropServices;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,10 +2,9 @@
// 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.Runtime.InteropServices;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -1,10 +1,10 @@
// 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.Runtime.InteropServices;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -5,7 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -5,7 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,9 +2,7 @@
// 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 MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Core
{

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.Common.NativeMethods;
namespace MouseJump.Common.NativeMethods;
internal static partial class Gdi32
{

Some files were not shown because too many files have changed in this diff Show More