Compare commits

...

36 Commits

Author SHA1 Message Date
Jaime Bernardo
f9f50e3f92 Add hack for dll version matching 2024-10-22 16:40:17 +01:00
Jaime Bernardo
374d6873c7 Also update StreamJsonRpc to latest 2024-10-22 16:40:17 +01:00
Jaime Bernardo
90611e854a Add updated MessagePack dependency for StreamJsonRpc 2024-10-22 16:40:17 +01:00
Sergey Chernyaev
e88b22937e [QuickAccent]Add support for Serbian Cyrillic (#35500)
* **Languages.cs**
  - Add `GetDefaultLetterKeySRCyrillic` method for Serbian Cyrillic letters.
  - Add `SR_CYRL` entry to `Language` enum.
  - Update `GetDefaultLetterKey` method to include `Language.SR_CYRL`.

* **PowerAccentPage.xaml**
  - Add `ComboBoxItem` for Serbian Cyrillic in `SelectedLanguage` `ComboBox`.

* **Resources.resw**
  - Add entry for Serbian Cyrillic.

* **PowerAccentViewModel.cs**
  - Add `SR_CYRL` to the list of supported languages.
2024-10-22 08:01:15 -07:00
Michael Clayton
dc7c7ef2b7 [MouseJump][CQ]Refactor "common" classes into a separate project (#34333)
* [Mouse Jump] - moving common code to MouseJump.Common project - #25482

* [Mouse Jump] - fixing warnings in MouseJump.Common - #25482

* [MouseJump] - cherrypick 5653b4 - Exclude MouseJump Common tests from the checks

# Conflicts:
#	src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj

* [mOuSEjUMP] - cherry pick 61aab9 - Fix ci build issues

# Conflicts:
#	src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj

* [Mouse Jump] - remove project type guids - #25482

* [Mouse Jump] - simplify mousejump *.csproj files - #25482

* [Mouse Jump] - fixing broken tests - #25482

* [Mouse Jump] - fixing broken build - #25482

* [Mouse Jump] - editing MouseJump.Common.UnitTests.csproj - #25482

* [Mouse Jump] - editing MouseJump.Common.csproj (UseWindowsForms=true) - #25482

* [Mouse Jump] - fixing spellcheck - #25482

* [MouseJump] - enabled implicit usings - #25482

* [Mouse Jump] - re-add csproj attributes - #27511

* ci: Fix signing of Common dll

---------

Co-authored-by: Clayton <mike.clayton@delinian.com>
2024-10-21 17:38:07 +01:00
PesBandi
db3c8e621e [PTRun]Improve copying error handling on plugins (#35459) 2024-10-21 13:42:11 +01:00
Christian Gaarden Gaardmark
5d2bb16e92 [New+]When new folder name collides, create copy instead of creating inside existing (#35464)
* Fixed target folder path when source is a folder
2024-10-21 11:51:14 +01:00
Christian Gaarden Gaardmark
3a6536ce54 [New+]Fix for bug when creating folders or files rhat contain Unicode characters (#35465)
wstring all the way
2024-10-21 11:38:25 +01:00
Clint Rutkas
278424e9e9 Trying to correct spelling error around having leading numeric value for user name (#35447)
* Update expect.txt

* Update names.txt
2024-10-18 11:25:24 -07:00
8LWXpg
83997a5604 [PTRun][Docs] Add SSH to Third-Party plugins (#35183)
Update thirdPartyRunPlugins.md
2024-10-18 10:57:51 -07:00
Michael Clayton
5cba82f929 [MWB][CQ]Refactoring "Common" classes - Common.Log.cs (#35156)
* [MouseWithoutBorders] - split "Common.Log.cs" into "Logger.cs"

* [MouseWithoutBorders] - fix references to Logger

* [MouseWithoutBorders] - add MouseWithoutBorders.UnitTests

* [MouseWithoutBorders] - fixing broken tests

* [MouseWithoutBorders] - fixing spelling

* [MouseWithoutBorders] - fixing spelling

* [MouseWithoutBorders] - fixing resource filename casing

* [MouseWithoutBorders] - fixing resource filename casing

* [MouseWithoutBorders] - fixing resource filename casing

* [MouseWithoutBorders] - fixing resource filename casing

* [MouseWithoutBorders] - fixed compile error

* [MouseWithoutBorders] - fixed failing test

* [MouseWithoutBorders] - fixed failing build

* [MouseWithoutBorders] - ignore flakey test
2024-10-18 17:32:08 +01:00
Heiko
37eaf10bf0 [Settings > New+ page] Fix enabled state of template backup info bar (#35235)
Update NewPlusPage.xaml
2024-10-18 15:02:25 +01:00
Dave Rayment
8fb45b5a55 [Peek] Improve folder enumeration (#35076)
* Improve folder enumeration. Fix initial display of folder details. Simplify UnsupportedFilePreviewer. Use Progress method for reporting progress. #35008

* Remove unused using.

* Apply suggestions from code review

Co-authored-by: Davide Giacometti <25966642+davidegiacometti@users.noreply.github.com>

* Implement PR review suggestions, including fix for InaccessibleFiles not being skipped.

* Folder enumeration avoids Reparse Points. Moved enumeration options to field.

* Remove unused using.

* Make folder enumeration options static...again.
2024-10-18 14:52:47 +01:00
PesBandi
e99b52fb35 [PTRun][UnitConverter]Preserve more significant digits (#35073)
* [PTRun][UnitConverter]Preserve more significant digits

* Use StringComparison.OrdinalIgnoreCase
2024-10-18 14:51:57 +01:00
Ani
dd5cd2a570 [AdvancedPaste]Additional actions - Image to text, Paste as file (txt, png, html) (#35167)
* [AdvancedPaste] Additional actions, including Image to text

* Spellcheck issue

* [AdvancedPaste] Paste as file and many other improvements

* Fixed typo

* Fixed typo

* [AdvancedPaste] Improved paste window menu layout

* [AdvancedPaste] Improved settings window layout

* [AdvancedPaste] Removed AudioToText for the moment

* Code cleanup

* Minor fixes

* Changed log-line with potentially sensitive info

* Extra telemetry for AdvancedPaste

* Added 'Hotkey' suffix to AdvancedPaste_Settings telemetry event
2024-10-18 14:34:09 +01:00
Davide Giacometti
14139affc5 [Settings][QuickAccent]Sort Languages ComboBox (#35462)
sort languages
2024-10-18 14:09:29 +01:00
octastylos-pseudodipteros
2335501421 [PTRun][WebSearch]Fix title not displaying the last letter (#35176) 2024-10-18 13:37:09 +01:00
Davide Giacometti
2ef987e1d9 [Chore]Common Logger: Log missing Exception type (#35084)
log type for exception and inner exception
2024-10-18 10:11:18 +01:00
Jeremy Sinclair
195c6f588a [Analyzers] Resolve Stylecop SA1516 violations and others to enable fully building on VS 17.12 (#35248)
* [Analyzers][Settings] Fix SA1516

* [Analyzers][Workspaces] Fix SA1516

* [Analyzers][Awake] Fix SA1516

* [Analyzers][Wox] Fix SA1516

* [MWB] Disable CA1716 warning on class name

* [Wox] Update ExecuteFilePath property visibility for Json Source Generator

* [Analyzers][MWB] Fix CA1716 on NativeMethods.

---------

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
2024-10-17 05:14:57 -04:00
Jaime Bernardo
d51ca9f8d4 [ci]Fix running xaml styles in CI (#35426)
XAML style checkers aren't running right now in PR CI. This allowed some XAML style errors to cause build errors in release CI.

This PR contains the following fixes:
- Fix XAML style of files that have slipped.
- Add errors to the scripts that depend on dotnet commands if it fails.
- Add .NET 6 on CI so that applyXamlStyling.ps1 and verifyNugetPackages.ps1 run correctly again.
2024-10-14 08:59:10 -05:00
Seraphima Zykova
9994fd7715 [Workspaces] Sequential launch (#35297) 2024-10-14 15:51:02 +02:00
Clint Rutkas
89ec5be5ba Removing Markdown exclude (#35380)
Update ci.yml
2024-10-11 11:56:55 -07:00
Clint Rutkas
1d352c686a removing dup entry in spelling (#35379)
Update expect.txt
2024-10-11 11:40:36 -07:00
gokcekantarci
3a228cc46e [PTRun]Fix CultureNotFoundException Handling (#35256) 2024-10-11 15:51:22 +01:00
Jeremy Sinclair
236d11a67b [Deps] WinAppSDK 1.6.1 and CsWinRT 2.1.5 update (#35381)
* [Deps] Update CsWinRT to 2.1.5

* [Deps] Update WinAppSDK to 1.6.1

* [Props] Updated WindowsSdkPackageVersion to 22621.48 in Common.Dotnet.CsWinRT.props

* Updated NOTICE.md

* [Peek] Resolve ambiguity with Color struct
2024-10-11 08:35:13 -05:00
gokcekantarci
ca1b0acbc9 [Deps]Upgrade WpfUI to 3.0.5 2024-10-11 11:51:02 +01:00
8LWXpg
d3869942c3 [PTRun][Docs] Add HackMD to Third-Party plugins (#35275)
Update thirdPartyRunPlugins.md
2024-10-10 15:18:49 -07:00
Jeremy Sinclair
e1231235fe [Security][Deps]Update System.Text.Json, System.Runtime.Caching and their dependencies to latest versions (#35353)
* [Deps] Update System.Text.Json to 8.0.5

* [Deps] Update System.Runtime.Caching to 8.0.1

* Update NOTICE.md

* [Deps] Update Microsoft.Windows.Compatibility to 8.0.10

* Update NOTICE.md

* Hack cleanup

---------

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
2024-10-10 21:02:40 +01:00
Aaron Junker-Wildi
2146dff3bf Add the new issue types to the issue templates (#35352)
We added issue types to our GitHub org.
This PR adds those to the corresponding issue templates.
2024-10-09 16:00:51 -07:00
Dustin L. Howett
07ec7980d1 ci: move the precheck dependency+condition up to the stage (#35321)
This should prevent the tests from running (and failing) when PreCheck skips the build.
2024-10-08 14:12:54 -05:00
Ethan Fang
10b8687174 Updated README.md for 0.85.1 release (#35296) 2024-10-07 14:42:14 -07:00
Clint Rutkas
300d44f17b Adjusting 8LWXpg user name in so it doesn't throw a spell check (#35221)
* Update names.txt

* Update expect.txt

* Update expect.txt
2024-10-07 09:44:25 -07:00
Jaime Bernardo
e7175302c9 [Settings]Fix crash showing non-existent New+ template folder (#35237)
[Settings]Fix crash showing non-existent template folder
2024-10-03 17:24:23 +01:00
Seraphima Zykova
577044163e [Workspaces]Fix launching incorrect workspace via shortcut (#35233)
ensure one launcher instance is running
2024-10-03 15:09:13 +01:00
Jaime Bernardo
bcb5ce895c [New+]Fix call enabled telemetry event (#35236) 2024-10-03 15:08:28 +01:00
Jaime Bernardo
6d69a79c75 0.85 changelog (#35095)
* 0.85 changelog

* Mention ZoomIt

* Update README.md

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>

* Address PR feedback

* Update with latest repo changes

* Update binary hashes

* Fix spellcheck

* GPO line in General instead

---------

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
2024-10-01 10:54:01 -07:00
447 changed files with 4770 additions and 2612 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,7 +28,8 @@ videoconference
# USERS
LWXpg # (number eight)LWXpg is actual user name but spell checker throws error with a numeric leading value ... which is kinda odd
# 8LWXpg is user name but user folder causes a flag
LWXpg
Adoumie
Advaith
alekhyareddy

View File

@@ -1,6 +1,7 @@
# FALSE POSITIVES
## "PackagemanagerWrapper.cs" should be "PackageManagerWrapper.cs"
## NOTICE.MD > MOZILLA PUBLIC LICENSE v1.1
aaaa
abcdefghjkmnpqrstuvxyz
abgr
@@ -29,7 +30,6 @@ AFFINETRANSFORM
AFX
AGGREGATABLE
AHybrid
AKV
akv
ALarger
ALLAPPS
@@ -1909,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

@@ -32,12 +32,12 @@ stages:
displayName: Build ${{ platform }}
${{ if ne(variables['Build.Reason'], 'Manual') }}:
dependsOn: [Precheck]
condition: and(succeeded(), ne(dependencies.Precheck.outputs['Precheck.verifyBuildRequest.skipBuild'], 'Yes'))
${{ else }}:
dependsOn: []
jobs:
- template: job-build-project.yml
parameters:
condition: and(succeeded(), or(eq(variables['Build.Reason'], 'Manual'), ne(stageDependencies.Precheck.Precheck.outputs['verifyBuildRequest.skipBuild'], 'Yes')))
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L

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

@@ -24,6 +24,8 @@
<PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="Mages" Version="2.0.2" />
<PackageVersion Include="Markdig.Signed" Version="0.34.0" />
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="2.5.187" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
@@ -35,16 +37,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" />
@@ -57,40 +59,36 @@
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
<PackageVersion Include="SharpCompress" Version="0.37.2" />
<PackageVersion Include="StreamJsonRpc" Version="2.14.24" />
<PackageVersion Include="StreamJsonRpc" Version="2.19.27" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom 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.Management but the 8.0.1 version wasn't published to nuget. -->
<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" />
</ItemGroup>
<ItemGroup Condition="'$(IsExperimentationLive)'!=''">
<!-- Additional dependencies used by experimentation -->
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1317,6 +1317,7 @@ EXHIBIT A -Mozilla Public License.
- LazyCache 2.4.0
- Mages 2.0.2
- Markdig.Signed 0.34.0
- MessagePack 2.5.187
- Microsoft.CodeAnalysis.NetAnalyzers 8.0.0
- Microsoft.Data.Sqlite 8.0.7
- Microsoft.Extensions.DependencyInjection 8.0.0
@@ -1327,11 +1328,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
@@ -1342,28 +1343,26 @@ EXHIBIT A -Mozilla Public License.
- ReverseMarkdown 4.1.0
- ScipBe.Common.Office.OneNote 3.0.1
- SharpCompress 0.37.2
- StreamJsonRpc 2.14.24
- StreamJsonRpc 2.19.27
- StyleCop.Analyzers 1.2.0-beta.556
- 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}

148
README.md
View File

@@ -15,10 +15,10 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
| [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) |
| [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) |
| [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) |
| [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) |
| [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) |
| [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) |
| [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
| [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |
| [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) |
| [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) |
| [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
## Installing and running Microsoft PowerToys
@@ -34,19 +34,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.85%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.84%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysUserSetup-0.84.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysUserSetup-0.84.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysSetup-0.84.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.84.1/PowerToysSetup-0.84.1-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.86%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.85%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.1/PowerToysUserSetup-0.85.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.1/PowerToysUserSetup-0.85.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.1/PowerToysSetup-0.85.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.85.1/PowerToysSetup-0.85.1-arm64.exe
| Description | Filename | sha256 hash |
|----------------|----------|-------------|
| Per user - x64 | [PowerToysUserSetup-0.84.1-x64.exe][ptUserX64] | 1CDAF3482B031D84DAE15188DE292FB44C5D211698089921040D94B256EBD3CA |
| Per user - ARM64 | [PowerToysUserSetup-0.84.1-arm64.exe][ptUserArm64] | E0207EF5147EE281D4F438E87A30586D8CAA24DE948950FF1B12E05454622CD9 |
| Machine wide - x64 | [PowerToysSetup-0.84.1-x64.exe][ptMachineX64] | 10DF9774DE1857051E135B9790A18A92C5C7F42587C733DEE991186E67231EE0 |
| Machine wide - ARM64 | [PowerToysSetup-0.84.1-arm64.exe][ptMachineArm64] | EB5DDA5EFBA17E813DBF24AFF668DDF5424ED3659234ABBC15441D478D812699 |
| Per user - x64 | [PowerToysUserSetup-0.85.1-x64.exe][ptUserX64] | 5F287C34BF68972C55D7C26585EA5C449B0DBA7D458BF7039CFF448E1D7B732B |
| Per user - ARM64 | [PowerToysUserSetup-0.85.1-arm64.exe][ptUserArm64] | 6D5C3B24156E6E66FD38AD15076B8442F0A1C5CFCBBDC33AD478FB27E5E086AE |
| Machine wide - x64 | [PowerToysSetup-0.85.1-x64.exe][ptMachineX64] | 1CDD3C9602F6E5DDC19C66A4FDFE4231389C08E6A037DD22C0A6471F10C7BE02 |
| Machine wide - ARM64 | [PowerToysSetup-0.85.1-arm64.exe][ptMachineArm64] | 6F4DC0217495973B974B7AC1099FD01A2A0FCEE96E8719074EC97FBBC0ECAC4A |
This is our preferred method.
@@ -92,99 +92,117 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.84 - August 2024 Update
### 0.85 - September 2024 Update
In this release, we focused on adding a new utility (PowerToys Workspaces), Advanced paste custom actions feature, stability, and improvements.
In this release, we focused on new features, stability, and improvements.
**Highlights**
- New utility: PowerToys Workspaces - this utility can launch a set of applications to a custom layout and configuration on the desktop. App arrangements can be saved as a workspace and then relaunched with one click from the Workspaces Editor or from a desktop shortcut. In the editor, app configuration can be customized using CLI arguments and "launch as admin" modifiers, and app window sizes and positions can be updated as desired. This is our first public version of Workspaces and we are excited for you to try it out for yourself! Make sure to file issues you encounter on our GitHub so the team can continue to improve the utility.
- Known issues - the team is actively working on fixing these:
- Apps that launch as admin are unable to be repositioned to the desired layout.
- Border of "Remove" / "Add Back" app button in editor is not clearly visible on light themes.
- Added Awake --use-parent-pid CLI argument to attach to parent process. Thanks [@dend](https://github.com/dend)!
- Added custom actions - user-specified pre-defined prompts for the AI model. Additionally, actions (both standard and custom) are now searchable from prompt box and Ctrl + number in-app shortcuts are now applicable for first 9 search results.
- Ported all C++/CX code to C++/WinRT as part of a refactor and upgrade series aimed at enabling AOT (Ahead of Time) compilation for enhanced performance and reduced disk footprint.
- New utility: New+ - allows setting a personalized set of templates to quickly create files and folders from a File Explorer context menu. Thanks [@cgaarden](https://github.com/cgaarden)!
- Language selection - it's now possible to select which UI language should be used by PowerToys utilities.
- Lots of quality fixes for Workspaces, improving the number of supported applications.
- Reduced Peek memory usage by fixing image leaks. Thanks [@daverayment](https://github.com/daverayment)!
### General
- Added DSC support for ImageResizer resize sizes property.
- Added a general setting to select which UI language should be used in PowerToys utilities.
- Fixed internal code of some policies for Group Policy Objects, that were reading registry entries using the wrong internal functions, and structured code better to avoid future mistakes of the same kind. Thanks [@htcfreek](https://github.com/htcfreek)!
### Advanced Paste
- Added custom actions - user-specified pre-defined prompts for the AI model. Additionally, actions (both standard and custom) are now searchable from prompt box and Ctrl + number in-app shortcuts are now applicable for first 9 search results.
- Fixed some telemetry calls to signal Advanced Paste activation on the cases where a direct shortcut is being used without showing the UI.
- User-defined custom actions can only be used with AI turned on, so custom actions were disabled on Settings when AI is disabled and were hidden from the Advanced Paste UI.
### Awake
- Added --use-parent-pid CLI argument to attach to parent process and fixed issue causing tray icon to disappear. Thanks [@dend](https://github.com/dend)!
- Fixed tray icon behaviors, not appearing and showing incorrect time. Thanks [@dend](https://github.com/dend)!
### Hosts File Editor
### Environment Variables Editor
- Fixed save failure when the hosts file is hidden. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Added the `_NT_SYMBOL_PATH`, `_NT_ALT_SYMBOL_PATH` and `_NT_SYMCACHE_PATH` as variables that are shown as lists. Thanks [@chwarr](https://github.com/chwarr)!
### File Explorer add-ons
### FancyZones
- Fixed multiple preview form positioning issues causing floating, detached windows, CoreWebView2 related exception and process leak. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Allow snapping applications that were launched by Workspaces.
### Keyboard Manager
### File Locksmith
- Convert RemapBufferRow to a struct with descriptive field names. Thanks [@masaru-iritani](https://github.com/masaru-iritani)!
- Fixed issue causing stuck Ctrl key when shortcuts contain AltGr key.
- Fixed an issue causing File Locksmith to be triggered by unrelated verbs in the context menu.
### Mouse Pointer Crosshairs
- Allow crosshairs radius to be 0 pixels. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
### New+
- New utility - Allows setting a personalized set of templates to quickly create files and folders from a File Explorer context menu. Thanks [@cgaarden](https://github.com/cgaarden)!
- Added missing entry for New+ policy state reporting in the Bug Report tool. Thanks [@htcfreek](https://github.com/htcfreek)!
- Added a policy for enabling/disabling whether filename extensions should be shown. Thanks [@htcfreek](https://github.com/htcfreek)!
### Peek
- Added long paths support. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
### Quick Accent
- Moved number superscripts and subscripts from Portuguese to all languages definition. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
### PowerRename
- Updated the tooltip text of the replace box info button. Thanks [@Agnibaan](https://github.com/Agnibaan)!
- Properly show file's modified date instead of creation date in the file previewer. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed memory leak caused by unmanaged bitmap images not being freed. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed an issue causing Peek to not be displayed the first time when using a preview handler to display files. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Prevent tooltip in file previewer from overlapping with title bar controls. Thanks [@daverayment](https://github.com/daverayment)!
- Fixed memory leaks in thumbnails and refactored image previewer. Thanks [@daverayment](https://github.com/daverayment)!
### PowerToys Run
- Fixed window positioning on start-up introduced in 0.83.
- Improved default web browser detection. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed volume ounces conversion to support both imperial and metric. Thanks [@GhostVaibhav](https://github.com/GhostVaibhav)!
- Fixed thread-safety issue causing results not to be shown on first launch.
- Improved the message boxes to be more specific when PowerToys Run failed to initialize itself or any plugin. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Use capital letters when showing degree results in the Unit Converter plugin. Thanks [@PesBandi](https://github.com/PesBandi)!
### Screen Ruler
### Quick Accent
- Added multiple measurements support for all measuring tools.
- Add the Middle Eastern Romanization character set. Thanks [@PesBandi](https://github.com/PesBandi)!
- Add the degree sign, integral and vertical ellipsis when "All Languages" is selected. Thanks [@rddunphy](https://github.com/rddunphy)!
### Settings
- Improved disabled animations InfoBar in Find My Mouse page. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed the link to the Workspaces documentation. (This was a hotfix for 0.84)
- Fixed flyout issues after the Windows App SDK upgrade. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed initialization for the New+ settings page. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed enabled state of a control on the New+ settings page if the module is enabled by policy. Thanks [@htcfreek](https://github.com/htcfreek)!
- Fixed a crash when cancelling the template folder selection in the New+ settings page.
### Workspaces
- New utility: PowerToys Workspaces - this utility can launch a set of applications to a custom layout and configuration on the desktop. App arrangements can be saved as a workspace and then relaunched with one click from the Workspaces Editor or from a desktop shortcut. In the editor, app configuration can be customized using CLI arguments and "launch as admin" modifiers, and app window sizes and positions can be updated as desired. This is our first public version of Workspaces and we are excited for you to try it out for yourself! Make sure to file issues you encounter on our GitHub so the team can continue to improve the utility.
- Fixed detecting and snapping applications like Discord. (This was a hotfix for 0.84)
- Fixed detecting and snapping applications like Steam. (This was a hotfix for 0.84)
- Fixed button visibility in the UI. (This was a hotfix for 0.84)
- Fixed an issue launching the wrong project when the editor was closed without saving or cancelling a new project.
- Properly handle repositioning windows running as administrator.
- Properly handle cases where the monitor where a workspace was saved is no longer present.
- Fixed the workspace launcher restarting itself in a loop without success.
- Properly handle standalone applications.
- Fixed issues causing icons to not show.
### Documentation
- Added ChatGPTPowerToys plugin mention to thirdPartyRunPlugins.md. Thanks [@ferraridavide](https://github.com/ferraridavide)!
- Fixed the thirdPartyRunPlugins.md entry for the RDP plugin. Thanks [@YisroelTech](https://github.com/YisroelTech)!
### Development
- Ported all C++/CX code to C++/WinRT.
- Moved Version.props import to Directory.Build.props.
- Extracted self-containment related .csproj properties to src/Common.SelfContained.props.
- Unused and obsolete dependencies cleanup. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Extracted CSWinRT related .csproj properties to src/Common.Dotnet.CsWinRT.props.
- Upgraded Microsoft.Windows.CsWinRT to 2.0.8 and updated verifyDepsJsonLibraryVersions.ps1 to unblock PRs.
- Explicitly Set NuGet Audit Mode to Direct in Directory.Build.props to revert changes made with VS 17.12 update. Thanks [@snickler](https://github.com/snickler)!
- Upgraded UnitsNet to 5.56.0.
- Upgraded Windows App SDK to 1.6.
- Upgraded the Target Platform Version to 10.0.22621.0.
- Added a bot trigger to automatically add a label to Workspaces issues. Thanks [@plante-msft](https://github.com/plante-msft)!
- Fixed a regular expression in the bot triggers for wanting to submit community contributions. Thanks [@PesBandi](https://github.com/PesBandi)!
- Fixed analyzer errors after the Visual Studio 17.12 update. Thanks [@snickler](https://github.com/snickler)!
- Fixed the TSA configuration for release CI builds.
- Refactored automated file component generation during installer builds.
- Rewrote the Azure Devops build system to be more modular and share more definitions between PR CI and Release CI.
- Fixed debugging of the New+ page of the Settings application when a settings file was not present.
- Fixed setting the version of the App Manifest in the File Locksmith and New+ context menu app packages.
- Fixed abstracted UI library nuget package signing on release CI.
- Removed build status from GitHub README.
#### What is being planned for version 0.84
#### What is being planned for version 0.86
For [v0.85][github-next-release-work], we'll work on the items below:
For [v0.86][github-next-release-work], we'll work on the items below:
- Stability / bug fixes
- Language selection
- New module: File Actions Menu
- New module: New+
- Integrate Sysinternals ZoomIt
## PowerToys Community

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

@@ -54,12 +54,15 @@
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageReference Include="MessagePack" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<!-- HACK: To align Microsoft.Bcl.AsyncInterfaces.dll version with Mouse Without Borders version. -->
<PackageReference Include="Microsoft.Windows.Compatibility" />
<PackageReference Include="Microsoft.Windows.CsWin32" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="ReverseMarkdown" />
<!-- HACK: To align Microsoft.Bcl.AsyncInterfaces.dll version with PowerToys.Settings.csproj. -->
<PackageReference Include="StreamJsonRpc" />
<PackageReference Include="WinUIEx" />
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->

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
{

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