mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-31 01:16:59 +01:00
Compare commits
59 Commits
dev/featur
...
dev/crloew
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e40f4b0c60 | ||
|
|
9d8972201f | ||
|
|
fa6ddbca4f | ||
|
|
a25fbe35e9 | ||
|
|
35e6375915 | ||
|
|
a8431528b1 | ||
|
|
b9532186bd | ||
|
|
7dc0f7f73b | ||
|
|
a536ec6e0b | ||
|
|
7640258c10 | ||
|
|
5b0f3f64d4 | ||
|
|
7e99389b6a | ||
|
|
e61460d26e | ||
|
|
ec0c300658 | ||
|
|
e79d86df9b | ||
|
|
883bd00132 | ||
|
|
749aa418a3 | ||
|
|
9591d75d4f | ||
|
|
de00e1d87c | ||
|
|
78953efe6e | ||
|
|
333ec5241b | ||
|
|
43654a32b4 | ||
|
|
afc469c7ab | ||
|
|
91d0c575c8 | ||
|
|
768e2c7a88 | ||
|
|
2fac6ed582 | ||
|
|
ae5a61edeb | ||
|
|
a59a07278a | ||
|
|
39741f492f | ||
|
|
d42cd4bd3b | ||
|
|
6408898cbe | ||
|
|
42cd02b20b | ||
|
|
f0a6a8462c | ||
|
|
62a8a9be52 | ||
|
|
31abbd54a4 | ||
|
|
663f26943b | ||
|
|
e882487d32 | ||
|
|
4413089af4 | ||
|
|
3c006f0abb | ||
|
|
12f21da35e | ||
|
|
320182dd89 | ||
|
|
d676064be5 | ||
|
|
fae78ae054 | ||
|
|
2189e7e1b9 | ||
|
|
2abd1058fa | ||
|
|
ed23e7eeb6 | ||
|
|
a5757fd525 | ||
|
|
579619952d | ||
|
|
2a8e211cfd | ||
|
|
bfa35d65a4 | ||
|
|
744c53cfcd | ||
|
|
4b9bb2f5a9 | ||
|
|
a163bbedc1 | ||
|
|
9f491c8f73 | ||
|
|
45ad7ebc5e | ||
|
|
808e6220bc | ||
|
|
f8269af125 | ||
|
|
1f5f43b154 | ||
|
|
9af757f5ce |
3
.github/actions/spell-check/allow/names.txt
vendored
3
.github/actions/spell-check/allow/names.txt
vendored
@@ -28,7 +28,7 @@ videoconference
|
||||
|
||||
# USERS
|
||||
|
||||
8LWXpg
|
||||
LWXpg # (number eight)LWXpg is actual user name but spell checker throws error with a numeric leading value ... which is kinda odd
|
||||
Adoumie
|
||||
Advaith
|
||||
alekhyareddy
|
||||
@@ -62,6 +62,7 @@ DHowett
|
||||
ductdo
|
||||
Essey
|
||||
ethanfangg
|
||||
ferraridavide
|
||||
frankychen
|
||||
gabime
|
||||
Galaxi
|
||||
|
||||
1
.github/actions/spell-check/excludes.txt
vendored
1
.github/actions/spell-check/excludes.txt
vendored
@@ -117,6 +117,7 @@
|
||||
^\Qsrc/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl\E$
|
||||
^\Qtools/project_template/ModuleTemplate/resource.h\E$
|
||||
^doc/devdocs/akaLinks\.md$
|
||||
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
||||
^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$
|
||||
^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$
|
||||
^src/modules/MouseWithoutBorders/App/Form/.*\.resx$
|
||||
|
||||
15
.github/actions/spell-check/expect.txt
vendored
15
.github/actions/spell-check/expect.txt
vendored
@@ -192,7 +192,6 @@ CLIPBOARDUPDATE
|
||||
CLIPCHILDREN
|
||||
CLIPSIBLINGS
|
||||
closesocket
|
||||
clrcall
|
||||
CLSCTX
|
||||
Clusion
|
||||
cmder
|
||||
@@ -203,8 +202,8 @@ CMINVOKECOMMANDINFO
|
||||
CMINVOKECOMMANDINFOEX
|
||||
CMock
|
||||
CMONITORS
|
||||
cmph
|
||||
cmpgt
|
||||
cmph
|
||||
cne
|
||||
CNF
|
||||
coclass
|
||||
@@ -251,10 +250,7 @@ countof
|
||||
cph
|
||||
CPower
|
||||
cppblog
|
||||
cppruntime
|
||||
cppstd
|
||||
cppwinrt
|
||||
CProj
|
||||
createdump
|
||||
CREATESCHEDULEDTASK
|
||||
CREATESTRUCT
|
||||
@@ -270,6 +266,7 @@ CSettings
|
||||
cso
|
||||
CSRW
|
||||
CStyle
|
||||
cswinrt
|
||||
CSY
|
||||
CTest
|
||||
currentculture
|
||||
@@ -609,7 +606,6 @@ hmenu
|
||||
hmodule
|
||||
hmonitor
|
||||
homljgmgpmcbpjbnjpfijnhipfkiclkd
|
||||
HOOKPROC
|
||||
Hostbackdropbrush
|
||||
hotkeycontrol
|
||||
hotkeys
|
||||
@@ -677,7 +673,6 @@ imageresizerinput
|
||||
imageresizersettings
|
||||
imagingdevices
|
||||
ime
|
||||
imperialounce
|
||||
inetcpl
|
||||
Infobar
|
||||
INFOEXAMPLE
|
||||
@@ -939,7 +934,6 @@ MRT
|
||||
mru
|
||||
mrw
|
||||
msc
|
||||
msclr
|
||||
mscorlib
|
||||
msdata
|
||||
msedge
|
||||
@@ -1119,6 +1113,7 @@ PATINVERT
|
||||
PATPAINT
|
||||
PAUDIO
|
||||
pbc
|
||||
pbi
|
||||
PBlob
|
||||
pcb
|
||||
pcch
|
||||
@@ -1132,6 +1127,7 @@ pdo
|
||||
pdto
|
||||
pdtobj
|
||||
pdw
|
||||
Peb
|
||||
pef
|
||||
PElems
|
||||
Pels
|
||||
@@ -1514,7 +1510,6 @@ STATICEDGE
|
||||
STATSTG
|
||||
stdafx
|
||||
STDAPI
|
||||
stdcpp
|
||||
stdcpplatest
|
||||
STDMETHODCALLTYPE
|
||||
STDMETHODIMP
|
||||
@@ -1686,7 +1681,6 @@ Userenv
|
||||
USESHOWWINDOW
|
||||
USESIZE
|
||||
USESTDHANDLES
|
||||
usounce
|
||||
USRDLL
|
||||
UType
|
||||
uuidv
|
||||
@@ -1723,6 +1717,7 @@ VIDEOINFOHEADER
|
||||
viewmodel
|
||||
vih
|
||||
VIRTUALDESK
|
||||
VISEGRADRELAY
|
||||
visiblecolorformats
|
||||
Visibletrue
|
||||
visualeffects
|
||||
|
||||
13
.github/policies/resourceManagement.yml
vendored
13
.github/policies/resourceManagement.yml
vendored
@@ -236,11 +236,22 @@ configuration:
|
||||
- if:
|
||||
- payloadType: Issue_Comment
|
||||
- commentContains:
|
||||
pattern: 'I would [like|love] [to help|helping|to contribute|contributing|to implement|implementing|to fix|fixing]'
|
||||
pattern: "I(( would|'d) (like|love|be happy)| want) (to help|helping|to contribute|contributing|to implement|implementing|to fix|fixing)"
|
||||
isRegex: True
|
||||
then:
|
||||
- addReply:
|
||||
reply: Hi! Your last comment indicates to our system, that you might want to contribute to this feature/fix this bug. Thank you! Please make us aware on our ["Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769), as we don't see all the comments. <br /><br />_I'm a bot (beep!) so please excuse any mistakes I may make_
|
||||
description:
|
||||
- if:
|
||||
- payloadType: Issues
|
||||
- isAction:
|
||||
action: Opened
|
||||
- bodyContains:
|
||||
pattern: 'Area\(s\) with issue\?\s*\nWorkspaces'
|
||||
isRegex: True
|
||||
then:
|
||||
- addLabel:
|
||||
label: Product-Workspaces
|
||||
description:
|
||||
onFailure:
|
||||
onSuccess:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"Microsoft.VisualStudio.Workload.ManagedDesktop",
|
||||
"Microsoft.VisualStudio.Workload.Universal",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.19041",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.20348",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.22621",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
|
||||
"Microsoft.VisualStudio.Component.UWP.VC.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre",
|
||||
|
||||
@@ -94,17 +94,16 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- Global props -->
|
||||
<PropertyGroup Label="Globals"
|
||||
Condition="'$(OverrideWindowsTargetPlatformVersion)'!='True'">
|
||||
<WindowsTargetPlatformVersion>10.0.20348.0</WindowsTargetPlatformVersion>
|
||||
<TargetPlatformVersion>10.0.20348.0</TargetPlatformVersion>
|
||||
<!-- Global props OverrideWindowsTargetPlatformVersion-->
|
||||
<PropertyGroup Label="Globals">
|
||||
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
|
||||
<TargetPlatformVersion>10.0.22621.0</TargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Props that are constant for both Debug and Release configurations -->
|
||||
<PropertyGroup Label="Configuration">
|
||||
<PlatformToolset Condition="'$(OverridePlatformToolset)'!='True'">v143</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<DesktopCompatible>true</DesktopCompatible>
|
||||
<SpectreMitigation>Spectre</SpectreMitigation>
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.12" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Collections" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.0.240109" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Collections" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.1.240821" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
|
||||
<PackageVersion Include="ControlzEx" Version="6.0.0" />
|
||||
@@ -32,15 +32,19 @@
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2365.46" />
|
||||
<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.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. -->
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.0.8" />
|
||||
<!--
|
||||
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.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.5.240428000" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.240829007" />
|
||||
<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" />
|
||||
@@ -79,7 +83,7 @@
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
<PackageVersion Include="UnitsNet" Version="5.50.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" />
|
||||
|
||||
24
NOTICE.md
24
NOTICE.md
@@ -1299,14 +1299,14 @@ EXHIBIT A -Mozilla Public License.
|
||||
- Appium.WebDriver 4.4.5
|
||||
- Azure.AI.OpenAI 1.0.0-beta.12
|
||||
- CommunityToolkit.Mvvm 8.2.2
|
||||
- CommunityToolkit.WinUI.Animations 8.0.240109
|
||||
- CommunityToolkit.WinUI.Collections 8.0.240109
|
||||
- CommunityToolkit.WinUI.Controls.Primitives 8.0.240109
|
||||
- CommunityToolkit.WinUI.Controls.Segmented 8.0.240109
|
||||
- CommunityToolkit.WinUI.Controls.SettingsControls 8.0.240109
|
||||
- CommunityToolkit.WinUI.Controls.Sizers 8.0.240109
|
||||
- CommunityToolkit.WinUI.Converters 8.0.240109
|
||||
- CommunityToolkit.WinUI.Extensions 8.0.240109
|
||||
- CommunityToolkit.WinUI.Animations 8.1.240821
|
||||
- CommunityToolkit.WinUI.Collections 8.1.240821
|
||||
- CommunityToolkit.WinUI.Controls.Primitives 8.1.240821
|
||||
- CommunityToolkit.WinUI.Controls.Segmented 8.1.240821
|
||||
- CommunityToolkit.WinUI.Controls.SettingsControls 8.1.240821
|
||||
- CommunityToolkit.WinUI.Controls.Sizers 8.1.240821
|
||||
- CommunityToolkit.WinUI.Converters 8.1.240821
|
||||
- CommunityToolkit.WinUI.Extensions 8.1.240821
|
||||
- CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2
|
||||
- CommunityToolkit.WinUI.UI.Controls.Markdown 7.1.2
|
||||
- ControlzEx 6.0.0
|
||||
@@ -1325,13 +1325,13 @@ EXHIBIT A -Mozilla Public License.
|
||||
- Microsoft.Extensions.Logging 8.0.0
|
||||
- Microsoft.Extensions.Logging.Abstractions 8.0.0
|
||||
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
|
||||
- Microsoft.Web.WebView2 1.0.2365.46
|
||||
- Microsoft.Web.WebView2 1.0.2739.15
|
||||
- Microsoft.Win32.SystemEvents 8.0.0
|
||||
- Microsoft.Windows.Compatibility 8.0.7
|
||||
- Microsoft.Windows.CsWin32 0.2.46-beta
|
||||
- Microsoft.Windows.CsWinRT 2.0.8
|
||||
- Microsoft.Windows.CsWinRT 2.1.1
|
||||
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
|
||||
- Microsoft.WindowsAppSDK 1.5.240428000
|
||||
- Microsoft.WindowsAppSDK 1.6.240829007
|
||||
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
|
||||
- Microsoft.Xaml.Behaviors.Wpf 1.1.39
|
||||
- ModernWpfUI 0.9.4
|
||||
@@ -1363,7 +1363,7 @@ EXHIBIT A -Mozilla Public License.
|
||||
- System.Text.Encoding.CodePages 8.0.0
|
||||
- System.Text.Json 8.0.4
|
||||
- UnicodeInformation 2.6.0
|
||||
- UnitsNet 5.50.0
|
||||
- UnitsNet 5.56.0
|
||||
- UTF.Unknown 2.5.1
|
||||
- WinUIEx 2.2.0
|
||||
- WPF-UI 3.0.0
|
||||
|
||||
140
README.md
140
README.md
@@ -25,7 +25,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
|
||||
| [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) |
|
||||
| [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |
|
||||
|
||||
## Installing and running Microsoft PowerToys
|
||||
|
||||
@@ -41,19 +41,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.84%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.83%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.83.0/PowerToysUserSetup-0.83.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.83.0/PowerToysUserSetup-0.83.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.83.0/PowerToysSetup-0.83.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.83.0/PowerToysSetup-0.83.0-arm64.exe
|
||||
[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
|
||||
|
||||
| Description | Filename | sha256 hash |
|
||||
|----------------|----------|-------------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.83.0-x64.exe][ptUserX64] | C78E24F21C611F2BD774D8460ADD4B9AC8519085CA1253941CB46129331AB8C8 |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.83.0-arm64.exe][ptUserArm64] | BA1C16003D55587D523A41B960D4A03718123CA37577D5F2A75E151D7653E6D3 |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.83.0-x64.exe][ptMachineX64] | 7EC435A10849187D21A383E56A69213C1FF110B7FECA65900D9319D2F8162F35 |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.83.0-arm64.exe][ptMachineArm64] | 5E147424D1D12DFCA88DC4AA0657B7CC1F3B02812F1EBA3E564FAF691908D840 |
|
||||
| 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 |
|
||||
|
||||
This is our preferred method.
|
||||
|
||||
@@ -99,117 +99,99 @@ 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.83 - July 2024 Update
|
||||
### 0.84 - August 2024 Update
|
||||
|
||||
In this release, we focused on stability and improvements.
|
||||
In this release, we focused on adding a new utility (PowerToys Workspaces), Advanced paste custom actions feature, stability, and improvements.
|
||||
|
||||
**Highlights**
|
||||
|
||||
- Awake Quality of Life changes, including changing the tray icon to reflect the current mode. Thanks [@dend](https://github.com/dend)!
|
||||
- Changes to general GPO policies and new policies for Mouse Without Borders. The names for some intune policy configuration sets might need to be updated as seen in https://github.com/MicrosoftDocs/windows-dev-docs/pull/5045/files . Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- 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.
|
||||
|
||||
### General
|
||||
- Reordered GPO policies, making it easier to find some policies. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
|
||||
- Added DSC support for ImageResizer resize sizes property.
|
||||
|
||||
### Advanced Paste
|
||||
|
||||
- Fixed CSV parser to support double quotes and escape delimiters when pasting as JSON. Thanks [@GhostVaibhav](https://github.com/GhostVaibhav)!
|
||||
- Improved double quote handling in the CSV parser when pasting as JSON. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- 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.
|
||||
|
||||
### Awake
|
||||
|
||||
- Different modes will now show different icons in the system tray. Thanks [@dend](https://github.com/dend), and [@niels9001](https://github.com/niels9001) for the icon design!
|
||||
- Removed the dependency on Windows Forms and used native Win32 APIs instead for the tray icon. Thanks [@dend](https://github.com/dend) and [@BrianPeek](https://github.com/BrianPeek)!
|
||||
- Fixed an issue where the UI would become non-responsive after selecting no time for the timed mode. Thanks [@dend](https://github.com/dend)!
|
||||
- Refactored code for easier maintenance. Thanks [@dend](https://github.com/dend)!
|
||||
- The tray icon will now be shown when running Awake standalone to signal mode. Thanks [@dend](https://github.com/dend)!
|
||||
- The tray icon tooltip shows how much time is left on the timer. Thanks [@dend](https://github.com/dend)!
|
||||
- Added DPI awareness to the tray icon context menu. Thanks [@dend](https://github.com/dend)!
|
||||
- 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)!
|
||||
|
||||
### Color Picker
|
||||
### Hosts File Editor
|
||||
|
||||
- Added support to using the mouse wheel to scroll through the color history. Thanks [@Fefedu973](https://github.com/Fefedu973)!
|
||||
- Fixed save failure when the hosts file is hidden. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### File Explorer add-ons
|
||||
|
||||
- Allow copying from the right-click menu in Monaco and Markdown previewers.
|
||||
- Fixed multiple preview form positioning issues causing floating, detached windows, CoreWebView2 related exception and process leak. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### File Locksmith
|
||||
### Keyboard Manager
|
||||
|
||||
- Fixed a crash when there were a big number of entries being shown by moving the opened files of a process to another dialog.
|
||||
|
||||
### Installer
|
||||
|
||||
- Fixed the path where DSC module files were installed for the user-scope installer. (This was a hotfix for 0.82)
|
||||
|
||||
### Mouse Without Borders
|
||||
|
||||
- Disabled non supported options in the old Mouse Without Borders UI. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added new GPO policies to control the use of some features. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- 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.
|
||||
|
||||
### Peek
|
||||
|
||||
- Allow copying from the right-click menu in Dev files and Markdown previews.
|
||||
|
||||
### PowerToys Run
|
||||
|
||||
- Fixed a crash on Windows 11 build 22000. (This was a hotfix for 0.82)
|
||||
- Blocked a transparency fix code from running on Windows 10, since it was causing graphical glitches. (This was a hotfix for 0.82)
|
||||
- Accept speed abbreviations like kilometers per hour (kmph) in the Unit Converter plugin. Thanks [@GhostVaibhav](https://github.com/GhostVaibhav)!
|
||||
- Added settings to configure behavior of the "First week of year" and "First day of week" calculations in the DateTime plugin. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fixed wrong initial position of the PowerToys Run when switching between monitors with different dpi values.
|
||||
- Started allowing interchangeable use of / and \ in the registry plugin paths.
|
||||
- Added support to automatic sign-in after rebooting with the System plugin. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added suggested use example results to the Value Generator plugin. Thanks [@azlkiniue](https://github.com/azlkiniue)!
|
||||
- Added long paths support. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### Quick Accent
|
||||
|
||||
- Added support for the Bulgarian character set. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
|
||||
- Moved number superscripts and subscripts from Portuguese to all languages definition. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
|
||||
|
||||
### Runner
|
||||
### PowerRename
|
||||
|
||||
- Add code to handle release tags with an upper V when trying to detect new updates. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Updated the tooltip text of the replace box info button. Thanks [@Agnibaan](https://github.com/Agnibaan)!
|
||||
|
||||
### 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.
|
||||
|
||||
### Screen Ruler
|
||||
|
||||
- Added multiple measurements support for all measuring tools.
|
||||
|
||||
### Settings
|
||||
|
||||
- Fixed the UI spacing in the "update available" card. Thanks [@Agnibaan](https://github.com/Agnibaan)!
|
||||
- Fixed the information bars in the Mouse Without Borders settings page to hide when the module is disabled. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Improved consistency of the icons used in the Mouse Without Borders settings page. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Improved action keyword information bar padding in the PowerToys Run plugins section. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fixed a crash in the dashboard when Keyboard Manager Editor settings file became locked.
|
||||
- Improved disabled animations InfoBar in Find My Mouse page. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### 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.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Added the RDP plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@anthony81799](https://github.com/anthony81799)!
|
||||
- Added the GitHubRepo and ProcessKiller plugins to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@8LWXpg](https://github.com/8LWXpg)!
|
||||
- Fixed a typo in the 0.82.0 release notes in README. Thanks [@walex999](https://github.com/walex999)!
|
||||
- Added ChatGPTPowerToys plugin mention to thirdPartyRunPlugins.md. Thanks [@ferraridavide](https://github.com/ferraridavide)!
|
||||
|
||||
### Development
|
||||
|
||||
- Disabled FancyZone UI tests, to unblock PRs. We plan to bring them back in the future. (This was a hotfix for 0.82)
|
||||
- Fixed an issue where flakiness in CI was causing the installer custom actions DLL from being signed. (This was a hotfix for 0.82)
|
||||
- Upgraded the Microsoft.Windows.Compatibility dependency to 8.0.7.
|
||||
- Upgraded the System.Text.Json dependency to 8.0.4.
|
||||
- Upgraded the Microsoft.Data.Sqlite dependency to 8.0.7.
|
||||
- Upgraded the MSBuildCache dependency to 0.1.283-preview. Thanks [@dfederm](https://github.com/dfederm)!
|
||||
- Removed an unneeded /Zm compiler flag from Keyboard Manager Editor common build flags.
|
||||
- Fixed the winget publish action to handle upper case V in the tag name. Thanks [@mdanish-kh](https://github.com/mdanish-kh)!
|
||||
- Removed wildcard items from vcxproj files. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Removed the similar issues bot GitHub actions. Thanks [@craigloewen-msft](https://github.com/craigloewen-msft)!
|
||||
- Fixed CODEOWNERS to better protect changes in some files.
|
||||
- Switched machines being used in CI and pointed status badges in README to the new machines.
|
||||
- Fixed NU1503 build warnings when building PowerToys. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Use the MSTest meta dependency for running the tests instead of the individual testing packages. Thanks [@stan-sz](https://github.com/stan-sz)!
|
||||
- Added missing CppWinRT references.
|
||||
- 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.
|
||||
|
||||
#### What is being planned for version 0.84
|
||||
|
||||
For [v0.84][github-next-release-work], we'll work on the items below:
|
||||
For [v0.85][github-next-release-work], we'll work on the items below:
|
||||
|
||||
- Stability / bug fixes
|
||||
- New utility: Dev Projects
|
||||
- Language selection
|
||||
- New module: File Actions Menu
|
||||
- New module: New+
|
||||
|
||||
## PowerToys Community
|
||||
|
||||
|
||||
@@ -10,12 +10,23 @@ The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it
|
||||
|
||||
The build ID moniker is made up of two components - a reference to a [Halo](https://en.wikipedia.org/wiki/Halo_(franchise)) character, and the date when the work on the specific build started in the format of `MMDDYYYY`.
|
||||
|
||||
| Build ID | Build Date |
|
||||
|:----------------------------------------------------------|:-----------------|
|
||||
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
|
||||
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
|
||||
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
|
||||
| `ARBITER_01312022` | January 31, 2022 |
|
||||
| Build ID | Build Date |
|
||||
|:-------------------------------------------------------------------|:----------------|
|
||||
| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 |
|
||||
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
|
||||
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
|
||||
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
|
||||
| `ARBITER_01312022` | January 31, 2022 |
|
||||
|
||||
### `VISEGRADRELAY_08152024` (August 15, 2024)
|
||||
|
||||
>[!NOTE]
|
||||
>See pull request: [Awake - `VISEGRADRELAY_08152024`](https://github.com/microsoft/PowerToys/pull/34316)
|
||||
|
||||
- [#34148](https://github.com/microsoft/PowerToys/issues/34148) Fixes the issue where the Awake icon is not displayed.
|
||||
- [#17969](https://github.com/microsoft/PowerToys/issues/17969) Add the ability to bind the process target to the parent of the Awake launcher.
|
||||
- PID binding now correctly ignores irrelevant parameters (e.g., expiration, interval) and only works for indefinite periods.
|
||||
- Amending the native API surface to make sure that the Win32 error is set correctly.
|
||||
|
||||
### `DAISY023_04102024` (April 10, 2024)
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ Contact the developers of a plugin directly for assistance with a specific plugi
|
||||
| [PowerHexInspector](https://github.com/NaroZeol/PowerHexInspector) | [NaroZeol](https://github.com/NaroZeol) | Peek other forms of an input number |
|
||||
| [GitHubRepo](https://github.com/8LWXpg/PowerToysRun-GitHubRepo) | [8LWXpg](https://github.com/8LWXpg) | Search and open GitHub repositories |
|
||||
| [ProcessKiller](https://github.com/8LWXpg/PowerToysRun-ProcessKiller) | [8LWXpg](https://github.com/8LWXpg) | Search and kill processes |
|
||||
| [ChatGPT](https://github.com/ferraridavide/ChatGPTPowerToys) | [ferraridavide](https://github.com/ferraridavide) | Ask a question to ChatGPT |
|
||||
|
||||
## Extending software plugins
|
||||
|
||||
@@ -49,7 +50,7 @@ Below are community created plugins that target a website or software. They are
|
||||
| [Edge Workspaces](https://github.com/quachpas/PowerToys-Run-EdgeWorkspaces) | [quachpas](https://github.com/quachpas) | Open Microsoft Edge workspaces|
|
||||
| [Everything](https://github.com/lin-ycv/EverythingPowerToys) | [Yu Chieh (Victor) Lin](https://github.com/Lin-ycv) | Get search results from Everything |
|
||||
| [GitKraken](https://github.com/davidegiacometti/PowerToys-Run-GitKraken) | [davidegiacometti](https://github.com/davidegiacometti) | Open GitKraken repositories |
|
||||
| [RDP](https://github.com/anthony81799/PowerToysRun-RDP)) | [anthony81799](https://github.com/anthony81799) | Open Remote Desktop connections |
|
||||
| [RDP](https://github.com/anthony81799/PowerToysRun-RDP) | [anthony81799](https://github.com/anthony81799) | Open Remote Desktop connections |
|
||||
| [Visual Studio Recents](https://github.com/davidegiacometti/PowerToys-Run-VisualStudio) | [davidegiacometti](https://github.com/davidegiacometti) | Open Visual Studio recents |
|
||||
| [WinGet](https://github.com/bostrot/PowerToysRunPluginWinget) | [bostrot](https://github.com/bostrot) | Search and install packages from WinGet |
|
||||
| [Scoop](https://github.com/Quriz/PowerToysRunScoop) | [Quriz](https://github.com/Quriz) | Search and install packages from Scoop |
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<!-- Some items may be set in Directory.Build.props in root -->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<WindowsSdkPackageVersion>10.0.22621.38</WindowsSdkPackageVersion>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
@@ -33,4 +34,11 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- this may need to be removed on future CsWinRT upgrades-->
|
||||
<Target Name="RemoveCsWinRTPackageAnalyzer" BeforeTargets="CoreCompile">
|
||||
<ItemGroup>
|
||||
<Analyzer Remove="@(Analyzer)" Condition="%(Analyzer.NuGetPackageId) == 'Microsoft.Windows.CsWinRT'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -51,17 +51,25 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT;
|
||||
}
|
||||
hstring Constants::ShowAdvancedPasteSharedEvent()
|
||||
hstring Constants::AdvancedPasteShowUIMessage()
|
||||
{
|
||||
return CommonSharedConstants::SHOW_ADVANCED_PASTE_SHARED_EVENT;
|
||||
return CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE;
|
||||
}
|
||||
hstring Constants::AdvancedPasteMarkdownEvent()
|
||||
hstring Constants::AdvancedPasteMarkdownMessage()
|
||||
{
|
||||
return CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_EVENT;
|
||||
return CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_MESSAGE;
|
||||
}
|
||||
hstring Constants::AdvancedPasteJsonEvent()
|
||||
hstring Constants::AdvancedPasteJsonMessage()
|
||||
{
|
||||
return CommonSharedConstants::ADVANCED_PASTE_JSON_EVENT;
|
||||
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;
|
||||
}
|
||||
hstring Constants::ShowPowerOCRSharedEvent()
|
||||
{
|
||||
@@ -147,4 +155,8 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT;
|
||||
}
|
||||
hstring Constants::WorkspacesHotkeyEvent()
|
||||
{
|
||||
return CommonSharedConstants::WORKSPACES_HOTKEY_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring FZEToggleEvent();
|
||||
static hstring ColorPickerSendSettingsTelemetryEvent();
|
||||
static hstring ShowColorPickerSharedEvent();
|
||||
static hstring ShowAdvancedPasteSharedEvent();
|
||||
static hstring AdvancedPasteMarkdownEvent();
|
||||
static hstring AdvancedPasteJsonEvent();
|
||||
static hstring AdvancedPasteShowUIMessage();
|
||||
static hstring AdvancedPasteMarkdownMessage();
|
||||
static hstring AdvancedPasteJsonMessage();
|
||||
static hstring AdvancedPasteAdditionalActionMessage();
|
||||
static hstring AdvancedPasteCustomActionMessage();
|
||||
static hstring ShowPowerOCRSharedEvent();
|
||||
static hstring MouseJumpShowPreviewEvent();
|
||||
static hstring AwakeExitEvent();
|
||||
@@ -40,6 +42,7 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring ShowEnvironmentVariablesSharedEvent();
|
||||
static hstring ShowEnvironmentVariablesAdminSharedEvent();
|
||||
static hstring WorkspacesLaunchEditorEvent();
|
||||
static hstring WorkspacesHotkeyEvent();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,11 @@ namespace PowerToys
|
||||
static String FZEToggleEvent();
|
||||
static String ColorPickerSendSettingsTelemetryEvent();
|
||||
static String ShowColorPickerSharedEvent();
|
||||
static String ShowAdvancedPasteSharedEvent();
|
||||
static String AdvancedPasteMarkdownEvent();
|
||||
static String AdvancedPasteJsonEvent();
|
||||
static String AdvancedPasteShowUIMessage();
|
||||
static String AdvancedPasteMarkdownMessage();
|
||||
static String AdvancedPasteJsonMessage();
|
||||
static String AdvancedPasteAdditionalActionMessage();
|
||||
static String AdvancedPasteCustomActionMessage();
|
||||
static String ShowPowerOCRSharedEvent();
|
||||
static String MouseJumpShowPreviewEvent();
|
||||
static String AwakeExitEvent();
|
||||
@@ -37,6 +39,7 @@ namespace PowerToys
|
||||
static String ShowEnvironmentVariablesSharedEvent();
|
||||
static String ShowEnvironmentVariablesAdminSharedEvent();
|
||||
static String WorkspacesLaunchEditorEvent();
|
||||
static String WorkspacesHotkeyEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,16 @@ namespace CommonSharedConstants
|
||||
|
||||
const wchar_t COLOR_PICKER_SEND_SETTINGS_TELEMETRY_EVENT[] = L"Local\\ColorPickerSettingsTelemetryEvent-6c7071d8-4014-46ec-b687-913bd8a422f1";
|
||||
|
||||
// Path to the event used to show Advanced Paste UI
|
||||
const wchar_t SHOW_ADVANCED_PASTE_SHARED_EVENT[] = L"Local\\ShowAdvancedPasteEvent-9a46be2a-3e05-4186-b56b-4ae986ef2526";
|
||||
// IPC Messages used in Advanced Paste
|
||||
const wchar_t ADVANCED_PASTE_SHOW_UI_MESSAGE[] = L"ShowUI";
|
||||
|
||||
const wchar_t ADVANCED_PASTE_MARKDOWN_EVENT[] = L"Local\\AdvancedPasteJsonEvent-a18c0798-3ee6-4fc5-bb9f-114c57ac0d47";
|
||||
const wchar_t ADVANCED_PASTE_MARKDOWN_MESSAGE[] = L"PasteMarkdown";
|
||||
|
||||
const wchar_t ADVANCED_PASTE_JSON_EVENT[] = L"Local\\AdvancedPasteJsonEvent-9ed021ab-b711-4cf3-9f33-135a698a9d21";
|
||||
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
|
||||
const wchar_t SHOW_COLOR_PICKER_SHARED_EVENT[] = L"Local\\ShowColorPickerEvent-8c46be2a-3e05-4186-b56b-4ae986ef2525";
|
||||
@@ -41,7 +45,9 @@ namespace CommonSharedConstants
|
||||
|
||||
const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14";
|
||||
|
||||
// Path to the event used by Workspaces
|
||||
const wchar_t WORKSPACES_LAUNCH_EDITOR_EVENT[] = L"Local\\Workspaces-LaunchEditorEvent-a55ff427-cf62-4994-a2cd-9f72139296bf";
|
||||
const wchar_t WORKSPACES_HOTKEY_EVENT[] = L"Local\\PowerToys-Workspaces-HotkeyEvent-2625C3C8-BAC9-4DB3-BCD6-3B4391A26FD0";
|
||||
|
||||
const wchar_t SHOW_HOSTS_EVENT[] = L"Local\\Hosts-ShowHostsEvent-5a0c0aae-5ff5-40f5-95c2-20e37ed671f0";
|
||||
|
||||
@@ -98,8 +104,6 @@ namespace CommonSharedConstants
|
||||
const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978";
|
||||
const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2";
|
||||
|
||||
const wchar_t WORKSPACES_EXIT_EVENT[] = L"Local\\PowerToys-Workspaces-ExitEvent-29a1566f-f4f8-4d56-9435-d2a437f727c6";
|
||||
|
||||
// Max DWORD for key code to disable keys.
|
||||
const DWORD VK_DISABLED = 0x100;
|
||||
}
|
||||
|
||||
@@ -24,4 +24,17 @@ properties:
|
||||
FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
|
||||
FileLocksmith:
|
||||
Enabled: false
|
||||
ImageResizer:
|
||||
ImageResizerSizes:
|
||||
- Name: Square2x
|
||||
Width: 200
|
||||
Height: 200
|
||||
Unit: "Percent"
|
||||
Fit: "Stretch"
|
||||
- Name: MyInchSize
|
||||
Width: 1024
|
||||
Height: 1024
|
||||
Unit: "Inch"
|
||||
Fit: "Fit"
|
||||
|
||||
configurationVersion: 0.2.0
|
||||
|
||||
@@ -21,7 +21,7 @@ internal sealed class DSCGeneration
|
||||
public string Type;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, AdditionalPropertiesInfo> AdditionalPropertiesInfoPerModule = new Dictionary<string, AdditionalPropertiesInfo> { { "PowerLauncher", new AdditionalPropertiesInfo { Name = "Plugins", Type = "Hashtable[]" } } };
|
||||
private static readonly Dictionary<string, AdditionalPropertiesInfo> AdditionalPropertiesInfoPerModule = new Dictionary<string, AdditionalPropertiesInfo> { { "PowerLauncher", new AdditionalPropertiesInfo { Name = "Plugins", Type = "Hashtable[]" } }, { "ImageResizer", new AdditionalPropertiesInfo { Name = "ImageresizerSizes", Type = "Hashtable[]" } } };
|
||||
|
||||
private static string EmitEnumDefinition(Type type)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License. -->
|
||||
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.11" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.12" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<displayName>PowerToys</displayName>
|
||||
<description>PowerToys</description>
|
||||
<resources>
|
||||
@@ -10,7 +10,6 @@
|
||||
<string id="InstallerUpdates">Installer and Updates</string>
|
||||
<string id="PowerToysRun">PowerToys Run</string>
|
||||
<string id="AdvancedPaste">Advanced Paste</string>
|
||||
<string id="Workspaces">Workspaces</string>
|
||||
<string id="MouseWithoutBorders">Mouse Without Borders</string>
|
||||
<string id="GeneralSettings">General settings</string>
|
||||
|
||||
@@ -112,12 +111,6 @@ If you don't configure this setting, users are able to enable or disable the plu
|
||||
You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins".
|
||||
|
||||
Note: Changes require a restart of PowerToys Run.
|
||||
</string>
|
||||
<string id="ConfigureEnabledUtilityWorkspaces">This policy configures the enabled disable state for the Workspaces utility.
|
||||
|
||||
If you enable or don't configure this policy, the user takes control over the enabled state of the Workspaces utility.
|
||||
|
||||
If you disable this policy, the user won't be able to enable Enable and use the Workspaces utility.
|
||||
</string>
|
||||
<string id="PowerToysRunIndividualPluginEnabledStateDescription">With this policy you can configure an individual enabled state for each PowerToys Run plugin that you add to the list.
|
||||
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
// 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;
|
||||
@@ -14,6 +22,7 @@ using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using WinUIEx;
|
||||
using static AdvancedPaste.Helpers.NativeMethods;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -26,12 +35,20 @@ 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;
|
||||
|
||||
private MainWindow window;
|
||||
|
||||
private nint windowHwnd;
|
||||
|
||||
private OptionsViewModel viewModel;
|
||||
|
||||
private bool disposedValue;
|
||||
|
||||
/// <summary>
|
||||
@@ -45,13 +62,17 @@ 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>();
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
|
||||
var throwAway = ShowWindow();
|
||||
}
|
||||
|
||||
public MainWindow GetMainWindow()
|
||||
@@ -74,7 +95,7 @@ namespace AdvancedPaste
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
if (cmdArgs?.Length > 1)
|
||||
@@ -88,9 +109,48 @@ namespace AdvancedPaste
|
||||
}
|
||||
}
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.ShowAdvancedPasteSharedEvent(), OnAdvancedPasteHotkey);
|
||||
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.AdvancedPasteMarkdownEvent(), OnAdvancedPasteMarkdownHotkey);
|
||||
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.AdvancedPasteJsonEvent(), OnAdvancedPasteJsonHotkey);
|
||||
if (cmdArgs?.Length > 2)
|
||||
{
|
||||
ProcessNamedPipe(cmdArgs[2]);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNamedPipe(string pipeName)
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task OnNamedPipeMessage(string message)
|
||||
{
|
||||
var messageParts = message.Split();
|
||||
var messageType = messageParts.First();
|
||||
|
||||
if (messageType == PowerToys.Interop.Constants.AdvancedPasteShowUIMessage())
|
||||
{
|
||||
await ShowWindow();
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteMarkdownMessage())
|
||||
{
|
||||
await viewModel.ExecutePasteFormatAsync(PasteFormats.Markdown, PasteActionSource.GlobalKeyboardShortcut);
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteJsonMessage())
|
||||
{
|
||||
await viewModel.ExecutePasteFormatAsync(PasteFormats.Json, PasteActionSource.GlobalKeyboardShortcut);
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteAdditionalActionMessage())
|
||||
{
|
||||
await OnAdvancedPasteAdditionalActionHotkey(messageParts);
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteCustomActionMessage())
|
||||
{
|
||||
await OnAdvancedPasteCustomActionHotkey(messageParts);
|
||||
}
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
@@ -98,21 +158,49 @@ namespace AdvancedPaste
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteJsonHotkey()
|
||||
private async Task OnAdvancedPasteAdditionalActionHotkey(string[] messageParts)
|
||||
{
|
||||
viewModel.GetClipboardData();
|
||||
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()
|
||||
private async Task OnAdvancedPasteCustomActionHotkey(string[] messageParts)
|
||||
{
|
||||
viewModel.GetClipboardData();
|
||||
viewModel.ToMarkdownFunction(true);
|
||||
if (messageParts.Length != 2)
|
||||
{
|
||||
Logger.LogWarning("Unexpected custom action message");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!int.TryParse(messageParts[1], CultureInfo.InvariantCulture, out int customActionId))
|
||||
{
|
||||
Logger.LogWarning($"Unexpected custom action message id {messageParts[1]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ShowWindow();
|
||||
await viewModel.ExecuteCustomAction(customActionId, PasteActionSource.GlobalKeyboardShortcut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteHotkey()
|
||||
private async Task ShowWindow()
|
||||
{
|
||||
viewModel.OnShow();
|
||||
await viewModel.OnShow();
|
||||
|
||||
if (window is null)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace AdvancedPaste.Controls
|
||||
{
|
||||
[TemplatePart(Name = LoadingGrid, Type = typeof(Grid))]
|
||||
[TemplatePart(Name = LoadingBrush, Type = typeof(AnimatedBorderBrush))]
|
||||
public class AnimatedContentControl : ContentControl
|
||||
public partial class AnimatedContentControl : ContentControl
|
||||
{
|
||||
internal const string LoadingGrid = "PART_LoadingGrid";
|
||||
internal const string LoadingBrush = "PART_LoadingBrush";
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:converters="using:AdvancedPaste.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:AdvancedPaste.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
@@ -323,13 +324,18 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter
|
||||
x:Key="BoolToInvertedVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
<converters:CountToVisibilityConverter x:Key="CountToVisibilityConverter" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid x:Name="PromptBoxGrid" Loaded="Grid_Loaded">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="40" />
|
||||
<RowDefinition Height="60" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:AnimatedContentControl
|
||||
x:Name="Loader"
|
||||
@@ -340,13 +346,12 @@
|
||||
x:Name="InputTxtBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:FieldModifier="public"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind ViewModel.ClipboardHasData, Mode=OneWay}"
|
||||
KeyDown="InputTxtBox_KeyDown"
|
||||
PlaceholderText="{x:Bind ViewModel.InputTxtBoxPlaceholderText, Mode=OneWay}"
|
||||
Style="{StaticResource CustomTextBoxStyle}"
|
||||
TabIndex="0"
|
||||
Text="{x:Bind Prompt, Mode=TwoWay}"
|
||||
TextChanging="InputTxtBox_TextChanging">
|
||||
Text="{x:Bind ViewModel.Query, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="InputTxtBoxTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
@@ -531,48 +536,63 @@
|
||||
<animations:OffsetAnimation Duration="0:0:1" />
|
||||
</animations:Implicit.Animations>
|
||||
</Button>-->
|
||||
<Button
|
||||
x:Name="SendBtn"
|
||||
x:Uid="SendButtonAutomation"
|
||||
<Grid
|
||||
Width="32"
|
||||
Height="32"
|
||||
Margin="0,0,4,0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Command="{x:Bind GenerateCustomCommand}"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
TabIndex="1"
|
||||
Visibility="Collapsed">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="SendBtnToolTip" TextWrapping="WrapWholeWords" />
|
||||
</ToolTipService.ToolTip>
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="0.4"
|
||||
To="1"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="0.0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="1"
|
||||
To="0.4"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="1.0"
|
||||
To="0.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Button>
|
||||
<!--</StackPanel>-->
|
||||
VerticalAlignment="Stretch">
|
||||
<Button
|
||||
x:Name="SendBtn"
|
||||
x:Uid="SendButtonAutomation"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Command="{x:Bind GenerateCustomCommand}"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
TabIndex="1"
|
||||
Visibility="{x:Bind ViewModel.Query.Length, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="SendBtnToolTip" TextWrapping="WrapWholeWords" />
|
||||
</ToolTipService.ToolTip>
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="0.4"
|
||||
To="1"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="0.0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="1"
|
||||
To="0.4"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="1.0"
|
||||
To="0.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Button>
|
||||
<!-- Transparent overlay to show tooltip -->
|
||||
<Grid
|
||||
x:Name="SendBtnOverlay"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
Visibility="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind ViewModel.AIDisabledErrorText}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</local:AnimatedContentControl>
|
||||
<ContentPresenter
|
||||
@@ -618,7 +638,7 @@
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.InputTxtBoxErrorText, Mode=OneWay}" />
|
||||
Text="{x:Bind ViewModel.ApiErrorText, Mode=OneWay}" />
|
||||
<HyperlinkButton
|
||||
x:Uid="SettingsBtn"
|
||||
Grid.Column="1"
|
||||
@@ -635,7 +655,6 @@
|
||||
<animations:OpacityAnimation To="0.0" Duration="0:0:0.167" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="DefaultState" />
|
||||
|
||||
@@ -2,15 +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.Net;
|
||||
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;
|
||||
|
||||
@@ -18,23 +16,8 @@ namespace AdvancedPaste.Controls
|
||||
{
|
||||
public sealed partial class PromptBox : Microsoft.UI.Xaml.Controls.UserControl
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
public static readonly DependencyProperty PromptProperty = DependencyProperty.Register(
|
||||
nameof(Prompt),
|
||||
typeof(string),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: string.Empty));
|
||||
|
||||
public OptionsViewModel ViewModel { get; private set; }
|
||||
|
||||
public string Prompt
|
||||
{
|
||||
get => (string)GetValue(PromptProperty);
|
||||
set => SetValue(PromptProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
|
||||
nameof(PlaceholderText),
|
||||
typeof(string),
|
||||
@@ -61,11 +44,31 @@ namespace AdvancedPaste.Controls
|
||||
|
||||
public PromptBox()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = App.GetService<OptionsViewModel>();
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
ViewModel.CustomActionActivated += ViewModel_CustomActionActivated;
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(ViewModel.Busy) || e.PropertyName == nameof(ViewModel.ApiErrorText))
|
||||
{
|
||||
var state = ViewModel.Busy ? "LoadingState" : string.IsNullOrEmpty(ViewModel.ApiErrorText) ? "DefaultState" : "ErrorState";
|
||||
VisualStateManager.GoToState(this, state, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_CustomActionActivated(object sender, Models.CustomActionActivatedEventArgs e)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
if (!e.PasteResult)
|
||||
{
|
||||
PreviewGrid.Width = InputTxtBox.ActualWidth;
|
||||
PreviewFlyout.ShowAt(InputTxtBox);
|
||||
}
|
||||
}
|
||||
|
||||
private void Grid_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -74,45 +77,7 @@ namespace AdvancedPaste.Controls
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void GenerateCustom()
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
VisualStateManager.GoToState(this, "LoadingState", true);
|
||||
string inputInstructions = InputTxtBox.Text;
|
||||
ViewModel.SaveQuery(inputInstructions);
|
||||
|
||||
var customFormatTask = ViewModel.GenerateCustomFunction(inputInstructions);
|
||||
|
||||
customFormatTask.ContinueWith(
|
||||
t =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
ViewModel.CustomFormatResult = t.Result;
|
||||
|
||||
if (ViewModel.ApiRequestStatus == (int)HttpStatusCode.OK)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "DefaultState", true);
|
||||
if (_userSettings.ShowCustomPreview)
|
||||
{
|
||||
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 GenerateCustom() => await ViewModel.GenerateCustomFunction(PasteActionSource.PromptBox);
|
||||
|
||||
[RelayCommand]
|
||||
private void Recall()
|
||||
@@ -130,34 +95,24 @@ namespace AdvancedPaste.Controls
|
||||
ClipboardHelper.SetClipboardTextContent(lastQuery.ClipboardData);
|
||||
}
|
||||
|
||||
private void InputTxtBox_TextChanging(Microsoft.UI.Xaml.Controls.TextBox sender, TextBoxTextChangingEventArgs args)
|
||||
private async void InputTxtBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
|
||||
{
|
||||
SendBtn.Visibility = InputTxtBox.Text.Length > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void InputTxtBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0)
|
||||
if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0 && ViewModel.IsCustomAIEnabled)
|
||||
{
|
||||
GenerateCustom();
|
||||
await GenerateCustom();
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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.Data;
|
||||
|
||||
namespace AdvancedPaste.Converters;
|
||||
|
||||
public sealed partial class CountToDoubleConverter : IValueConverter
|
||||
{
|
||||
public double ValueIfZero { get; set; }
|
||||
|
||||
public double ValueIfNonZero { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
bool hasCount = ((value is int intValue) && intValue > 0) || (value is IEnumerable collection && collection.GetEnumerator().MoveNext());
|
||||
|
||||
return hasCount ? ValueIfNonZero : ValueIfZero;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace AdvancedPaste.Converters;
|
||||
|
||||
public sealed partial class CountToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
bool hasCount = ((value is int intValue) && intValue > 0) || (value is IEnumerable collection && collection.GetEnumerator().MoveNext());
|
||||
|
||||
if (targetType == typeof(Visibility))
|
||||
{
|
||||
return hasCount ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
else if (targetType == typeof(bool))
|
||||
{
|
||||
return hasCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(targetType));
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -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;
|
||||
using System.Collections;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace AdvancedPaste.Converters;
|
||||
|
||||
public sealed partial class InfoBadgeStyleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return string.IsNullOrEmpty(value as string) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace AdvancedPaste.Converters
|
||||
{
|
||||
public sealed class ListViewIndexConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var presenter = value as ListViewItemPresenter;
|
||||
var item = VisualTreeHelper.GetParent(presenter) as ListViewItem;
|
||||
|
||||
var listView = ItemsControl.ItemsControlFromItemContainer(item);
|
||||
int index = listView.IndexFromContainer(item) + 1;
|
||||
#pragma warning disable CA1305 // Specify IFormatProvider
|
||||
return index.ToString();
|
||||
#pragma warning restore CA1305 // Specify IFormatProvider
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(Convert((value is ICollection collection) ? collection.Count : (value is int intValue) ? intValue : 0));
|
||||
|
||||
public int Convert(int itemCount) => Math.Min(MaxItems, itemCount) * ItemHeight;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AdvancedPaste.Converters;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
using AdvancedPaste.Settings;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Windowing;
|
||||
@@ -22,10 +25,28 @@ namespace AdvancedPaste
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
|
||||
var baseHeight = MinHeight;
|
||||
var coreActionCount = PasteFormat.MetadataDict.Values.Count(metadata => metadata.IsCoreAction);
|
||||
|
||||
void UpdateHeight()
|
||||
{
|
||||
double GetHeight(int maxCustomActionCount) =>
|
||||
baseHeight +
|
||||
new PasteFormatsToHeightConverter().Convert(coreActionCount + _userSettings.AdditionalActions.Count) +
|
||||
new PasteFormatsToHeightConverter() { MaxItems = maxCustomActionCount }.Convert(_userSettings.CustomActions.Count);
|
||||
|
||||
MinHeight = GetHeight(1);
|
||||
Height = GetHeight(5);
|
||||
}
|
||||
|
||||
UpdateHeight();
|
||||
|
||||
_userSettings.Changed += (_, _) => UpdateHeight();
|
||||
|
||||
AppWindow.SetIcon("Assets/AdvancedPaste/AdvancedPaste.ico");
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(titleBar);
|
||||
|
||||
@@ -5,14 +5,23 @@
|
||||
xmlns:controls="using:AdvancedPaste.Controls"
|
||||
xmlns:converters="using:AdvancedPaste.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:library="using:Microsoft.PowerToys.Settings.UI.Library"
|
||||
xmlns:local="using:AdvancedPaste.Models"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
KeyDown="Page_KeyDown"
|
||||
KeyboardAcceleratorPlacementMode="Hidden"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<converters:ListViewIndexConverter x:Name="listViewIndexConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter x:Name="BoolToVisibilityConverter" />
|
||||
<converters:CountToVisibilityConverter x:Name="countToVisibilityConverter" />
|
||||
<converters:PasteFormatsToHeightConverter x:Name="standardPasteFormatsToHeightConverter" />
|
||||
<converters:InfoBadgeStyleConverter x:Name="infoBadgeStyleConverter" />
|
||||
<converters:CountToDoubleConverter
|
||||
x:Name="customActionsToMinHeightConverter"
|
||||
ValueIfNonZero="40"
|
||||
ValueIfZero="0" />
|
||||
<Style
|
||||
x:Key="PaddingLessFlyoutPresenterStyle"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
||||
@@ -21,6 +30,51 @@
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<DataTemplate x:Key="PasteFormatTemplate" x:DataType="local:PasteFormat">
|
||||
<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>
|
||||
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked" />
|
||||
@@ -36,6 +90,30 @@
|
||||
Key="Number3"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number4"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number5"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number6"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number7"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number8"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number9"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
</Page.KeyboardAccelerators>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -46,53 +124,77 @@
|
||||
<controls:PromptBox
|
||||
x:Name="CustomFormatTextBox"
|
||||
x:Uid="CustomFormatTextBox"
|
||||
Margin="8,4,8,0"
|
||||
Margin="8,4,8,-12"
|
||||
x:FieldModifier="public"
|
||||
TabIndex="0">
|
||||
<controls:PromptBox.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run x:Uid="AIMistakeNote" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="4,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink
|
||||
x:Name="TermsHyperlink"
|
||||
NavigateUri="https://openai.com/policies/terms-of-use"
|
||||
TabIndex="3">
|
||||
<Run x:Uid="TermsLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/terms-of-use" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
ToolTipService.ToolTip="">
|
||||
<Run x:Uid="AIFooterSeparator" Foreground="{ThemeResource TextFillColorSecondaryBrush}">|</Run>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink NavigateUri="https://openai.com/policies/privacy-policy" TabIndex="3">
|
||||
<Run x:Uid="PrivacyLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/privacy-policy" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run x:Uid="AIMistakeNote" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="4,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink
|
||||
x:Name="TermsHyperlink"
|
||||
NavigateUri="https://openai.com/policies/terms-of-use"
|
||||
TabIndex="3">
|
||||
<Run x:Uid="TermsLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/terms-of-use" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
ToolTipService.ToolTip="">
|
||||
<Run x:Uid="AIFooterSeparator" Foreground="{ThemeResource TextFillColorSecondaryBrush}">|</Run>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink NavigateUri="https://openai.com/policies/privacy-policy" TabIndex="3">
|
||||
<Run x:Uid="PrivacyLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/privacy-policy" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="-5 5 0 0" Orientation="Horizontal">
|
||||
<TextBlock Foreground="#CCCCCC" Style="{StaticResource CaptionTextBlockStyle}">Choose a paste action. Clipboard has: </TextBlock>
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.AvailableFormatsText, Mode=OneWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Margin="2 0 0 0">
|
||||
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{Binding Item1}" ToolTipService.ToolTip="{Binding Item2}" />
|
||||
<InfoBadge Style="{ThemeResource InformationalDotInfoBadgeStyle}" Width="4" Height="4" Margin="-2 0 0 8">
|
||||
<InfoBadge.Visibility>
|
||||
<Binding Path="Item2" Converter="{StaticResource infoBadgeStyleConverter}" />
|
||||
</InfoBadge.Visibility>
|
||||
</InfoBadge>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:PromptBox.Footer>
|
||||
</controls:PromptBox>
|
||||
@@ -103,73 +205,71 @@
|
||||
BorderThickness="0,1,0,0"
|
||||
RowSpacing="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="{x:Bind ViewModel.StandardPasteFormats.Count, Mode=OneWay, Converter={StaticResource standardPasteFormatsToHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsToMinHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ListView
|
||||
x:Name="PasteOptionsListView"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Bottom"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PasteOptionsListView_ItemClick"
|
||||
IsItemClickEnabled="False"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemsSource="{x:Bind pasteFormats, Mode=OneWay}"
|
||||
ItemTemplate="{StaticResource PasteFormatTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Auto"
|
||||
SelectionMode="None"
|
||||
TabIndex="1">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:PasteFormat">
|
||||
<Grid>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{x:Bind Name}" />
|
||||
<Run Text="(" /><Run Text="Ctrl" /><Run Text="+" /><Run Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource listViewIndexConverter}}" /><Run Text=")" />
|
||||
</TextBlock>
|
||||
</ToolTipService.ToolTip>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="26" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Viewbox
|
||||
x:Name="IconHolderBox"
|
||||
MaxWidth="16"
|
||||
MaxHeight="16"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<ContentPresenter
|
||||
x:Name="IconHolder"
|
||||
x:Phase="2"
|
||||
Content="{x:Bind Icon}" />
|
||||
</Viewbox>
|
||||
<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}">
|
||||
<Run Text="Ctrl" /><Run Text="+" /><Run Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource listViewIndexConverter}}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
</ListView>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
Visibility="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
|
||||
|
||||
<ListView
|
||||
x:Name="CustomActionsListView"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Top"
|
||||
IsItemClickEnabled="False"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemTemplate="{StaticResource PasteFormatTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Auto"
|
||||
SelectionMode="None"
|
||||
TabIndex="2">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
</ListView>
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="3"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
<!-- x:Uid="ClipboardHistoryButton" -->
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Row="4"
|
||||
Height="32"
|
||||
Margin="4,0,4,4"
|
||||
Padding="{StaticResource ButtonPadding}"
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
@@ -25,8 +24,8 @@ namespace AdvancedPaste.Pages
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
private readonly ObservableCollection<ClipboardItem> clipboardHistory;
|
||||
private readonly ObservableCollection<PasteFormat> pasteFormats;
|
||||
private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
|
||||
private (VirtualKey Key, DateTime Timestamp) _lastKeyEvent = (VirtualKey.None, DateTime.MinValue);
|
||||
|
||||
public OptionsViewModel ViewModel { get; private set; }
|
||||
|
||||
@@ -34,13 +33,6 @@ namespace AdvancedPaste.Pages
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
pasteFormats =
|
||||
[
|
||||
new PasteFormat { Icon = new FontIcon() { Glyph = "\uE8E9" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText },
|
||||
new PasteFormat { Icon = new FontIcon() { Glyph = "\ue8a5" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown },
|
||||
new PasteFormat { Icon = new FontIcon() { Glyph = "\uE943" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json },
|
||||
];
|
||||
|
||||
ViewModel = App.GetService<OptionsViewModel>();
|
||||
|
||||
clipboardHistory = new ObservableCollection<ClipboardItem>();
|
||||
@@ -121,6 +113,8 @@ namespace AdvancedPaste.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private static MainWindow GetMainWindow() => (App.Current as App)?.GetMainWindow();
|
||||
|
||||
private void ClipboardHistoryItemDeleteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
@@ -135,83 +129,49 @@ namespace AdvancedPaste.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private void PasteAsPlain()
|
||||
private async void ListView_Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.ToPlainTextFunction();
|
||||
}
|
||||
|
||||
private void PasteAsMarkdown()
|
||||
{
|
||||
ViewModel.ToMarkdownFunction();
|
||||
}
|
||||
|
||||
private void PasteAsJson()
|
||||
{
|
||||
ViewModel.ToJsonFunction();
|
||||
}
|
||||
|
||||
private void PasteOptionsListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is PasteFormat format)
|
||||
if (sender is Button { DataContext: PasteFormat format })
|
||||
{
|
||||
switch (format.Format)
|
||||
{
|
||||
case PasteFormats.PlainText:
|
||||
{
|
||||
PasteAsPlain();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.PlainText));
|
||||
break;
|
||||
}
|
||||
|
||||
case PasteFormats.Markdown:
|
||||
{
|
||||
PasteAsMarkdown();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Markdown));
|
||||
break;
|
||||
}
|
||||
|
||||
case PasteFormats.Json:
|
||||
{
|
||||
PasteAsJson();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogTrace();
|
||||
|
||||
var thisKeyEvent = (sender.Key, Timestamp: DateTime.Now);
|
||||
if (thisKeyEvent.Key == _lastKeyEvent.Key && (thisKeyEvent.Timestamp - _lastKeyEvent.Timestamp) < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
// Sometimes, multiple keyboard accelerator events are raised for a single Ctrl + VirtualKey press.
|
||||
return;
|
||||
}
|
||||
|
||||
_lastKeyEvent = thisKeyEvent;
|
||||
|
||||
switch (sender.Key)
|
||||
{
|
||||
case VirtualKey.Escape:
|
||||
{
|
||||
(App.Current as App).GetMainWindow().Close();
|
||||
break;
|
||||
}
|
||||
GetMainWindow()?.Close();
|
||||
break;
|
||||
|
||||
case VirtualKey.Number1:
|
||||
{
|
||||
PasteAsPlain();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.PlainText));
|
||||
break;
|
||||
}
|
||||
|
||||
case VirtualKey.Number2:
|
||||
{
|
||||
PasteAsMarkdown();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Markdown));
|
||||
break;
|
||||
}
|
||||
|
||||
case VirtualKey.Number3:
|
||||
{
|
||||
PasteAsJson();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Json));
|
||||
break;
|
||||
}
|
||||
case VirtualKey.Number4:
|
||||
case VirtualKey.Number5:
|
||||
case VirtualKey.Number6:
|
||||
case VirtualKey.Number7:
|
||||
case VirtualKey.Number8:
|
||||
case VirtualKey.Number9:
|
||||
await ViewModel.ExecutePasteFormat(sender.Key);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -222,7 +182,7 @@ namespace AdvancedPaste.Pages
|
||||
{
|
||||
if (e.Key == VirtualKey.Escape)
|
||||
{
|
||||
(App.Current as App).GetMainWindow().Close();
|
||||
GetMainWindow()?.Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
// 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;
|
||||
|
||||
@@ -15,6 +19,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> GetAvailableClipboardFormats(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();
|
||||
@@ -25,31 +57,41 @@ 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.
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (flushed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Clipboard.Flush();
|
||||
}).Wait();
|
||||
|
||||
flushed = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Clipboard.Flush() failed", ex);
|
||||
}
|
||||
Task.Run(Clipboard.Flush).Wait();
|
||||
return true;
|
||||
}
|
||||
catch (Exception 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)
|
||||
@@ -62,30 +104,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,5 +154,63 @@ namespace AdvancedPaste.Helpers
|
||||
|
||||
Logger.LogInfo("Paste sent");
|
||||
}
|
||||
|
||||
internal static async Task<string> GetClipboardTextOrHtmlText(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> GetClipboardHtmlContent(DataPackageView clipboardData) =>
|
||||
clipboardData.Contains(StandardDataFormats.Html) ? await clipboardData.GetHtmlFormatAsync() : string.Empty;
|
||||
|
||||
internal static async Task<string> GetClipboardTextContent(DataPackageView clipboardData)
|
||||
{
|
||||
return clipboardData.Contains(StandardDataFormats.Text) ? await clipboardData.GetTextAsync() : 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AdvancedPaste.Models;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace AdvancedPaste.Settings
|
||||
{
|
||||
public interface IUserSettings
|
||||
@@ -11,5 +16,11 @@ namespace AdvancedPaste.Settings
|
||||
public bool SendPasteKeyCombination { get; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
|
||||
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions { get; }
|
||||
|
||||
public IReadOnlyList<PasteFormats> AdditionalActions { get; }
|
||||
|
||||
public event EventHandler Changed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AdvancedPaste.Helpers;
|
||||
|
||||
public static class NamedPipeProcessor
|
||||
{
|
||||
public static async Task ProcessNamedPipeAsync(string pipeName, TimeSpan connectTimeout, Action<string> messageHandler, CancellationToken cancellationToken)
|
||||
{
|
||||
using NamedPipeClientStream pipeClient = new(".", pipeName, PipeDirection.In);
|
||||
|
||||
await pipeClient.ConnectAsync(connectTimeout, cancellationToken);
|
||||
|
||||
using StreamReader streamReader = new(pipeClient, Encoding.Unicode);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var message = await streamReader.ReadLineAsync(cancellationToken);
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
messageHandler(message);
|
||||
}
|
||||
|
||||
var intraMessageDelay = TimeSpan.FromMilliseconds(10);
|
||||
await Task.Delay(intraMessageDelay, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace AdvancedPaste.Helpers
|
||||
{
|
||||
public static class NativeEventWaiter
|
||||
{
|
||||
public static void WaitForEventLoop(string eventName, Action callback)
|
||||
{
|
||||
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
new Thread(() =>
|
||||
{
|
||||
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
while (true)
|
||||
{
|
||||
if (eventHandle.WaitOne())
|
||||
{
|
||||
dispatcherQueue.TryEnqueue(() => callback());
|
||||
}
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> GetTextAsync(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();
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,45 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
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;
|
||||
|
||||
namespace AdvancedPaste.Settings
|
||||
{
|
||||
internal sealed class UserSettings : IUserSettings
|
||||
internal sealed class UserSettings : IUserSettings, IDisposable
|
||||
{
|
||||
private readonly SettingsUtils _settingsUtils;
|
||||
private readonly TaskScheduler _taskScheduler;
|
||||
private readonly IFileSystemWatcher _watcher;
|
||||
private readonly object _loadingSettingsLock = new object();
|
||||
private readonly object _loadingSettingsLock = new();
|
||||
private readonly List<AdvancedPasteCustomAction> _customActions;
|
||||
private readonly List<PasteFormats> _additionalActions;
|
||||
|
||||
private const string AdvancedPasteModuleName = "AdvancedPaste";
|
||||
private const int MaxNumberOfRetry = 5;
|
||||
|
||||
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 IReadOnlyList<PasteFormats> AdditionalActions => _additionalActions;
|
||||
|
||||
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions => _customActions;
|
||||
|
||||
public UserSettings()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
@@ -33,10 +49,25 @@ namespace AdvancedPaste.Settings
|
||||
ShowCustomPreview = true;
|
||||
SendPasteKeyCombination = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
_additionalActions = [];
|
||||
_customActions = [];
|
||||
_taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
LoadSettingsFromJson();
|
||||
|
||||
_watcher = Helper.GetFileWatcher(AdvancedPasteModuleName, "settings.json", () => LoadSettingsFromJson());
|
||||
_watcher = Helper.GetFileWatcher(AdvancedPasteModuleName, "settings.json", OnSettingsFileChanged);
|
||||
}
|
||||
|
||||
private void OnSettingsFileChanged()
|
||||
{
|
||||
lock (_loadingSettingsLock)
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
Task.Delay(TimeSpan.FromMilliseconds(500))
|
||||
.ContinueWith(_ => LoadSettingsFromJson(), _cancellationTokenSource.Token, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettingsFromJson()
|
||||
@@ -62,9 +93,37 @@ namespace AdvancedPaste.Settings
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<AdvancedPasteSettings>(AdvancedPasteModuleName);
|
||||
if (settings != null)
|
||||
{
|
||||
ShowCustomPreview = settings.Properties.ShowCustomPreview;
|
||||
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
|
||||
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
|
||||
void UpdateSettings()
|
||||
{
|
||||
var properties = settings.Properties;
|
||||
|
||||
ShowCustomPreview = properties.ShowCustomPreview;
|
||||
SendPasteKeyCombination = properties.SendPasteKeyCombination;
|
||||
CloseAfterLosingFocus = properties.CloseAfterLosingFocus;
|
||||
|
||||
var sourceAdditionalActions = properties.AdditionalActions;
|
||||
(PasteFormats Format, IAdvancedPasteAction[] Actions)[] additionalActionFormats =
|
||||
[
|
||||
(PasteFormats.AudioToText, [sourceAdditionalActions.AudioToText]),
|
||||
(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
|
||||
.StartNew(UpdateSettings, CancellationToken.None, TaskCreationOptions.None, _taskScheduler)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
retry = false;
|
||||
@@ -82,5 +141,30 @@ namespace AdvancedPaste.Settings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_cancellationTokenSource.Dispose();
|
||||
_watcher.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~UserSettings()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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 CustomActionActivatedEventArgs(string text, bool pasteResult) : EventArgs
|
||||
{
|
||||
public string Text { get; private set; } = text;
|
||||
|
||||
public bool PasteResult { get; private set; } = pasteResult;
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -2,16 +2,60 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace AdvancedPaste.Models
|
||||
namespace AdvancedPaste.Models;
|
||||
|
||||
[DebuggerDisplay("{Name} IsEnabled={IsEnabled} ShortcutText={ShortcutText}")]
|
||||
public sealed class PasteFormat
|
||||
{
|
||||
public class PasteFormat
|
||||
public static readonly IReadOnlyDictionary<PasteFormats, PasteFormatMetadataAttribute> MetadataDict =
|
||||
typeof(PasteFormats).GetFields()
|
||||
.Where(field => field.IsLiteral)
|
||||
.ToDictionary(field => (PasteFormats)field.GetRawConstantValue(), field => field.GetCustomAttribute<PasteFormatMetadataAttribute>());
|
||||
|
||||
private PasteFormat(PasteFormats format, ClipboardFormat clipboardFormats, bool isCustomAIEnabled)
|
||||
{
|
||||
public IconElement Icon { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public PasteFormats Format { get; set; }
|
||||
Format = format;
|
||||
IsEnabled = ((clipboardFormats & Metadata.SupportedClipboardFormats) != ClipboardFormat.None) && (isCustomAIEnabled || !Metadata.RequiresAIService);
|
||||
}
|
||||
|
||||
public PasteFormat(PasteFormats format, ClipboardFormat clipboardFormats, bool isCustomAIEnabled, Func<string, string> resourceLoader)
|
||||
: this(format, clipboardFormats, isCustomAIEnabled)
|
||||
{
|
||||
Name = Metadata.ResourceId == null ? string.Empty : resourceLoader(Metadata.ResourceId);
|
||||
Prompt = string.Empty;
|
||||
}
|
||||
|
||||
public PasteFormat(AdvancedPasteCustomAction customAction, ClipboardFormat clipboardFormats, bool isCustomAIEnabled)
|
||||
: this(PasteFormats.Custom, clipboardFormats, isCustomAIEnabled)
|
||||
{
|
||||
Name = customAction.Name;
|
||||
Prompt = customAction.Prompt;
|
||||
}
|
||||
|
||||
public PasteFormatMetadataAttribute Metadata => MetadataDict[Format];
|
||||
|
||||
public string IconGlyph => Metadata.IconGlyph;
|
||||
|
||||
public string Name { get; private init; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -2,13 +2,36 @@
|
||||
// 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 = "AudioToText", IconGlyph = "\uF8B1", RequiresAIService = true, SupportedClipboardFormats = ClipboardFormat.Audio, IPCKey = AdvancedPasteAdditionalActions.PropertyNames.AudioToText)]
|
||||
AudioToText,
|
||||
|
||||
[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,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
// 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.AudioToText:
|
||||
throw new NotImplementedException();
|
||||
|
||||
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", 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.GetTextAsync(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.GetClipboardTextOrHtmlText(clipboardData);
|
||||
await SetClipboardFileContentAsync(text, "txt");
|
||||
}
|
||||
|
||||
private async Task ToHtmlFileAsync(DataPackageView clipboardData)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var html = await ClipboardHelper.GetClipboardHtmlContent(clipboardData);
|
||||
var cleanedHtml = RemoveHtmlMetadata(html);
|
||||
|
||||
await SetClipboardFileContentAsync(cleanedHtml, "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 htmlFormat)
|
||||
{
|
||||
int? GetIntTagValue(string tagName)
|
||||
{
|
||||
var tagNameWithColon = tagName + ":";
|
||||
int tagStartPos = htmlFormat.IndexOf(tagNameWithColon, StringComparison.InvariantCulture);
|
||||
|
||||
const int tagValueLength = 10;
|
||||
return tagStartPos != -1 && int.TryParse(htmlFormat.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) ? htmlFormat : htmlFormat[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),
|
||||
};
|
||||
}
|
||||
@@ -59,10 +59,7 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root"
|
||||
xmlns=""
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
@@ -123,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 is empty</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>
|
||||
@@ -138,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>
|
||||
@@ -154,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>
|
||||
@@ -165,6 +168,21 @@
|
||||
<data name="PasteAsPlainText" xml:space="preserve">
|
||||
<value>Paste as plain text</value>
|
||||
</data>
|
||||
<data name="AudioToText" xml:space="preserve">
|
||||
<value>Audio to 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,10 @@
|
||||
<data name="OpenAIGpoDisabled" xml:space="preserve">
|
||||
<value>To custom with AI is disabled by your organization</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="CtrlKey" xml:space="preserve">
|
||||
<value>Ctrl</value>
|
||||
</data>
|
||||
<data name="PasteAsFile_FilePrefix" xml:space="preserve">
|
||||
<value>PowerToys_Paste_</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -3,62 +3,86 @@
|
||||
// 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.Net;
|
||||
using System.Linq;
|
||||
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.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Win32;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.System;
|
||||
using WinUIEx;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
public partial class OptionsViewModel : ObservableObject
|
||||
public sealed partial class OptionsViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly DispatcherTimer _clipboardTimer;
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
private App app = App.Current as App;
|
||||
|
||||
private AICompletionsHelper aiHelper;
|
||||
private readonly IPasteFormatExecutor _pasteFormatExecutor;
|
||||
private readonly AICompletionsHelper _aiHelper;
|
||||
private readonly App app = App.Current as App;
|
||||
|
||||
public DataPackageView ClipboardData { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
|
||||
private bool _isClipboardDataText;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ClipboardHasData))]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
private bool _isCustomAIEnabled;
|
||||
[NotifyPropertyChangedFor(nameof(AIDisabledErrorText))]
|
||||
private ClipboardFormat _availableClipboardFormats;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _clipboardHistoryEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxErrorText))]
|
||||
private int _apiRequestStatus;
|
||||
[NotifyPropertyChangedFor(nameof(AIDisabledErrorText))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
|
||||
private bool _isAllowedByGPO;
|
||||
|
||||
public OptionsViewModel(IUserSettings userSettings)
|
||||
[ObservableProperty]
|
||||
private string _apiErrorText;
|
||||
|
||||
[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 IsCustomAIEnabled => IsAllowedByGPO && _aiHelper.IsAIEnabled && ClipboardHasText;
|
||||
|
||||
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
|
||||
|
||||
private bool ClipboardHasText => AvailableClipboardFormats.HasFlag(ClipboardFormat.Text);
|
||||
|
||||
private bool Visible => app?.GetMainWindow()?.Visible is true;
|
||||
|
||||
public event EventHandler<CustomActionActivatedEventArgs> CustomActionActivated;
|
||||
|
||||
public OptionsViewModel(AICompletionsHelper aiHelper, IUserSettings userSettings, IPasteFormatExecutor pasteFormatExecutor)
|
||||
{
|
||||
aiHelper = new AICompletionsHelper();
|
||||
_aiHelper = aiHelper;
|
||||
_userSettings = userSettings;
|
||||
_pasteFormatExecutor = pasteFormatExecutor;
|
||||
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
|
||||
ApiRequestStatus = (int)HttpStatusCode.OK;
|
||||
|
||||
GeneratedResponses = new ObservableCollection<string>();
|
||||
GeneratedResponses = [];
|
||||
GeneratedResponses.CollectionChanged += (s, e) =>
|
||||
{
|
||||
OnPropertyChanged(nameof(HasMultipleResponses));
|
||||
@@ -66,61 +90,205 @@ namespace AdvancedPaste.ViewModels
|
||||
};
|
||||
|
||||
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
|
||||
GetClipboardData();
|
||||
}
|
||||
_clipboardTimer = new() { Interval = TimeSpan.FromSeconds(1) };
|
||||
_clipboardTimer.Tick += ClipboardTimer_Tick;
|
||||
_clipboardTimer.Start();
|
||||
|
||||
public void GetClipboardData()
|
||||
{
|
||||
ClipboardData = Clipboard.GetContent();
|
||||
IsClipboardDataText = ClipboardData.Contains(StandardDataFormats.Text);
|
||||
}
|
||||
|
||||
public void OnShow()
|
||||
{
|
||||
GetClipboardData();
|
||||
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
RefreshPasteFormats();
|
||||
_userSettings.Changed += (_, _) => EnqueueRefreshPasteFormats();
|
||||
PropertyChanged += (_, e) =>
|
||||
{
|
||||
IsCustomAIEnabled = false;
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
string[] dirtyingProperties = [nameof(Query), nameof(IsCustomAIEnabled), nameof(AvailableClipboardFormats)];
|
||||
|
||||
if (dirtyingProperties.Contains(e.PropertyName))
|
||||
{
|
||||
EnqueueRefreshPasteFormats();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async void ClipboardTimer_Tick(object sender, object e)
|
||||
{
|
||||
if (Visible)
|
||||
{
|
||||
await ReadClipboard();
|
||||
UpdateAllowedByGPO();
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
private void EnqueueRefreshPasteFormats()
|
||||
{
|
||||
if (_pasteFormatsDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pasteFormatsDirty = true;
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
RefreshPasteFormats();
|
||||
_pasteFormatsDirty = false;
|
||||
});
|
||||
}
|
||||
|
||||
private PasteFormat CreatePasteFormat(PasteFormats format) => new(format, AvailableClipboardFormats, IsCustomAIEnabled, ResourceLoaderInstance.ResourceLoader.GetString);
|
||||
|
||||
private PasteFormat CreatePasteFormat(AdvancedPasteCustomAction customAction) => new(customAction, AvailableClipboardFormats, IsCustomAIEnabled);
|
||||
|
||||
private void RefreshPasteFormats()
|
||||
{
|
||||
var ctrlString = ResourceLoaderInstance.ResourceLoader.GetString("CtrlKey");
|
||||
int shortcutNum = 0;
|
||||
|
||||
string GetNextShortcutText()
|
||||
{
|
||||
shortcutNum++;
|
||||
return shortcutNum <= 9 ? $"{ctrlString}+{shortcutNum}" : string.Empty;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
collection.Clear();
|
||||
|
||||
foreach (var format in FilterAndSort(pasteFormats))
|
||||
{
|
||||
if (format.IsEnabled)
|
||||
{
|
||||
format.ShortcutText = GetNextShortcutText();
|
||||
}
|
||||
|
||||
collection.Add(format);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFormats(StandardPasteFormats, Enum.GetValues<PasteFormats>()
|
||||
.Where(format => PasteFormat.MetadataDict[format].IsCoreAction || _userSettings.AdditionalActions.Contains(format))
|
||||
.Select(CreatePasteFormat));
|
||||
|
||||
UpdateFormats(CustomActionPasteFormats, _userSettings.CustomActions.Select(CreatePasteFormat));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clipboardTimer.Stop();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task ReadClipboard()
|
||||
{
|
||||
if (Busy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClipboardData = Clipboard.GetContent();
|
||||
AvailableClipboardFormats = await ClipboardHelper.GetAvailableClipboardFormats(ClipboardData);
|
||||
}
|
||||
|
||||
public async Task OnShow()
|
||||
{
|
||||
ApiErrorText = string.Empty;
|
||||
Query = string.Empty;
|
||||
|
||||
await ReadClipboard();
|
||||
UpdateAllowedByGPO();
|
||||
|
||||
if (IsAllowedByGPO)
|
||||
{
|
||||
var openAIKey = AICompletionsHelper.LoadOpenAIKey();
|
||||
var currentKey = aiHelper.GetKey();
|
||||
var currentKey = _aiHelper.GetKey();
|
||||
bool keyChanged = openAIKey != currentKey;
|
||||
|
||||
if (keyChanged)
|
||||
{
|
||||
app.GetMainWindow().StartLoading();
|
||||
|
||||
Task.Run(() =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
aiHelper.SetOpenAIKey(openAIKey);
|
||||
_aiHelper.SetOpenAIKey(openAIKey);
|
||||
}).ContinueWith(
|
||||
(t) =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled);
|
||||
app.GetMainWindow().FinishLoading(_aiHelper.IsAIEnabled);
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
OnPropertyChanged(nameof(AIDisabledErrorText));
|
||||
OnPropertyChanged(nameof(IsCustomAIEnabled));
|
||||
});
|
||||
},
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
|
||||
GeneratedResponses.Clear();
|
||||
await GenerateAvailableFormatsText();
|
||||
}
|
||||
|
||||
public async Task<bool> GenerateAvailableFormatsText()
|
||||
{
|
||||
AvailableFormatsText.Clear();
|
||||
|
||||
List<Tuple<ClipboardFormat, string>> formatQueryList = new()
|
||||
{
|
||||
new Tuple<ClipboardFormat, string>(ClipboardFormat.Text, "Text,"),
|
||||
new Tuple<ClipboardFormat, string>(ClipboardFormat.Html, "Html,"),
|
||||
new Tuple<ClipboardFormat, string>(ClipboardFormat.Audio, "Audio,"),
|
||||
new Tuple<ClipboardFormat, string>(ClipboardFormat.Image, "Image,"),
|
||||
new Tuple<ClipboardFormat, string>(ClipboardFormat.ImageFile, "ImageFile,"),
|
||||
};
|
||||
|
||||
ObservableCollection<Tuple<string, string>> returnList = new();
|
||||
|
||||
foreach (var formatQuery in formatQueryList)
|
||||
{
|
||||
if (AvailableClipboardFormats.HasFlag(formatQuery.Item1))
|
||||
{
|
||||
string presentedString = formatQuery.Item2;
|
||||
string tooltipContent = null;
|
||||
|
||||
if (formatQuery.Item1 == ClipboardFormat.Text)
|
||||
{
|
||||
tooltipContent = await ClipboardHelper.GetClipboardTextContent(ClipboardData);
|
||||
}
|
||||
else if (formatQuery.Item1 == ClipboardFormat.Html)
|
||||
{
|
||||
tooltipContent = await ClipboardHelper.GetClipboardHtmlContent(ClipboardData);
|
||||
}
|
||||
|
||||
returnList.Add(new Tuple<string, string>(formatQuery.Item2, tooltipContent));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove comma from last item
|
||||
if (returnList.Count > 0)
|
||||
{
|
||||
Tuple<string, string> lastItem = returnList.Last();
|
||||
if (!string.IsNullOrEmpty(lastItem.Item1))
|
||||
{
|
||||
lastItem = new Tuple<string, string>(lastItem.Item1.Substring(0, lastItem.Item1.Length - 1), lastItem.Item2);
|
||||
returnList[returnList.Count - 1] = lastItem;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in returnList)
|
||||
{
|
||||
AvailableFormatsText.Add(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -139,61 +307,39 @@ 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();
|
||||
if (!ClipboardHasText)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataNotTextWarning");
|
||||
}
|
||||
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
if (!IsAllowedByGPO)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
|
||||
}
|
||||
else if (!aiHelper.IsAIEnabled)
|
||||
|
||||
if (!_aiHelper.IsAIEnabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
|
||||
}
|
||||
else if (!IsClipboardDataText)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataTypeMismatchWarning");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string InputTxtBoxErrorText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ApiRequestStatus != (int)HttpStatusCode.OK)
|
||||
{
|
||||
if (ApiRequestStatus == (int)HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests");
|
||||
}
|
||||
else if (ApiRequestStatus == (int)HttpStatusCode.Unauthorized)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + ApiRequestStatus.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
public ObservableCollection<Tuple<string, string>> AvailableFormatsText { get; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _customFormatResult;
|
||||
@@ -201,7 +347,20 @@ namespace AdvancedPaste.ViewModels
|
||||
[RelayCommand]
|
||||
public void PasteCustom()
|
||||
{
|
||||
PasteCustomFunction(GeneratedResponses[CurrentResponseIndex]);
|
||||
var text = GeneratedResponses.ElementAtOrDefault(CurrentResponseIndex);
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
ClipboardHelper.SetClipboardTextContent(text);
|
||||
HideWindow();
|
||||
|
||||
if (_userSettings.SendPasteKeyCombination)
|
||||
{
|
||||
ClipboardHelper.SendPasteKeyCombination();
|
||||
}
|
||||
|
||||
Query = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Command to select the previous custom format
|
||||
@@ -232,137 +391,118 @@ namespace AdvancedPaste.ViewModels
|
||||
(App.Current as App).GetMainWindow().Close();
|
||||
}
|
||||
|
||||
private void SetClipboardContentAndHideWindow(string content)
|
||||
internal async Task ExecutePasteFormatAsync(PasteFormats format, PasteActionSource source)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
await ReadClipboard();
|
||||
await ExecutePasteFormatAsync(CreatePasteFormat(format), source);
|
||||
}
|
||||
|
||||
internal async Task ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source)
|
||||
{
|
||||
if (Busy)
|
||||
{
|
||||
ClipboardHelper.SetClipboardTextContent(content);
|
||||
Logger.LogWarning($"Execution of {pasteFormat.Name} from {source} suppressed as busy");
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.GetMainWindow() != null)
|
||||
if (!pasteFormat.IsEnabled)
|
||||
{
|
||||
Windows.Win32.Foundation.HWND hwnd = (Windows.Win32.Foundation.HWND)app.GetMainWindow().GetWindowHandle();
|
||||
return;
|
||||
}
|
||||
|
||||
Busy = true;
|
||||
ApiErrorText = string.Empty;
|
||||
Query = pasteFormat.Query;
|
||||
|
||||
if (pasteFormat.Format == PasteFormats.Custom)
|
||||
{
|
||||
SaveQuery(Query);
|
||||
}
|
||||
|
||||
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);
|
||||
ApiErrorText = ex is PasteActionException ? ex.Message : ResourceLoaderInstance.ResourceLoader.GetString("PasteError");
|
||||
}
|
||||
|
||||
Busy = false;
|
||||
}
|
||||
|
||||
internal async Task ExecutePasteFormat(VirtualKey key)
|
||||
{
|
||||
var pasteFormat = StandardPasteFormats.Concat(CustomActionPasteFormats)
|
||||
.Where(pasteFormat => pasteFormat.IsEnabled)
|
||||
.ElementAtOrDefault(key - VirtualKey.Number1);
|
||||
|
||||
if (pasteFormat != null)
|
||||
{
|
||||
await ExecutePasteFormatAsync(pasteFormat, PasteActionSource.InAppKeyboardShortcut);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task ExecuteCustomAction(int customActionId, PasteActionSource source)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
await ReadClipboard();
|
||||
|
||||
var customAction = _userSettings.CustomActions.FirstOrDefault(customAction => customAction.Id == customActionId);
|
||||
|
||||
if (customAction != null)
|
||||
{
|
||||
await ExecutePasteFormatAsync(CreatePasteFormat(customAction), source);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task GenerateCustomFunction(PasteActionSource triggerSource)
|
||||
{
|
||||
AdvancedPasteCustomAction customAction = new() { Name = "Default", Prompt = Query };
|
||||
await ExecutePasteFormatAsync(CreatePasteFormat(customAction), triggerSource);
|
||||
}
|
||||
|
||||
private void HideWindow()
|
||||
{
|
||||
var mainWindow = app.GetMainWindow();
|
||||
|
||||
if (mainWindow != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ToPlainTextFunction()
|
||||
{
|
||||
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 async Task<string> GenerateCustomFunction(string inputInstructions)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(inputInstructions))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (ClipboardData == null || !ClipboardData.Contains(StandardDataFormats.Text))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
internal void PasteCustomFunction(string text)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
SetClipboardContentAndHideWindow(text);
|
||||
|
||||
if (_userSettings.SendPasteKeyCombination)
|
||||
{
|
||||
ClipboardHelper.SendPasteKeyCombination();
|
||||
}
|
||||
}
|
||||
|
||||
internal CustomQuery RecallPreviousCustomQuery()
|
||||
{
|
||||
return LoadPreviousQuery();
|
||||
@@ -380,11 +520,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
|
||||
{
|
||||
@@ -392,13 +528,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;
|
||||
}
|
||||
@@ -416,5 +552,10 @@ namespace AdvancedPaste.ViewModels
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAllowedByGPO()
|
||||
{
|
||||
IsAllowedByGPO = PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
#include <atlfile.h>
|
||||
#include <atlstr.h>
|
||||
#include <vector>
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
@@ -35,6 +39,11 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lp
|
||||
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";
|
||||
const wchar_t JSON_KEY_WIN[] = L"win";
|
||||
const wchar_t JSON_KEY_ALT[] = L"alt";
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
@@ -60,33 +69,39 @@ private:
|
||||
|
||||
HANDLE m_hProcess;
|
||||
|
||||
std::unique_ptr<CAtlFile> m_write_pipe;
|
||||
|
||||
// Time to wait for process to close after sending WM_CLOSE signal
|
||||
static const int MAX_WAIT_MILLISEC = 10000;
|
||||
static const constexpr int MAX_WAIT_MILLISEC = 10000;
|
||||
|
||||
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
|
||||
|
||||
Hotkey m_paste_as_plain_hotkey = { .win = true, .ctrl = true, .shift = false, .alt = true, .key = 'V' };
|
||||
Hotkey m_advanced_paste_ui_hotkey = { .win = true, .ctrl = false, .shift = true, .alt = false, .key = 'V' };
|
||||
Hotkey m_paste_as_markdown_hotkey{};
|
||||
Hotkey m_paste_as_json_hotkey{};
|
||||
|
||||
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;
|
||||
|
||||
// Handle to event used to invoke AdvancedPaste
|
||||
HANDLE m_hShowUIEvent;
|
||||
HANDLE m_hPasteMarkdownEvent;
|
||||
HANDLE m_hPasteJsonEvent;
|
||||
|
||||
Hotkey parse_single_hotkey(const wchar_t* hotkey, const winrt::Windows::Data::Json::JsonObject& settingsObject)
|
||||
Hotkey parse_single_hotkey(const wchar_t* keyName, const winrt::Windows::Data::Json::JsonObject& settingsObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
Hotkey _temp_paste_as_plain;
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(hotkey);
|
||||
_temp_paste_as_plain.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
_temp_paste_as_plain.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
_temp_paste_as_plain.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
_temp_paste_as_plain.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
_temp_paste_as_plain.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
return _temp_paste_as_plain;
|
||||
const auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(keyName);
|
||||
return parse_single_hotkey(jsonHotkeyObject);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -96,6 +111,38 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
Hotkey hotkey;
|
||||
hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
return hotkey;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize AdvancedPaste shortcut from settings. Value will keep unchanged.");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static json::JsonObject to_json_object(const Hotkey& hotkey)
|
||||
{
|
||||
json::JsonObject jsonObject;
|
||||
jsonObject.SetNamedValue(JSON_KEY_WIN, json::value(hotkey.win));
|
||||
jsonObject.SetNamedValue(JSON_KEY_ALT, json::value(hotkey.alt));
|
||||
jsonObject.SetNamedValue(JSON_KEY_SHIFT, json::value(hotkey.shift));
|
||||
jsonObject.SetNamedValue(JSON_KEY_CTRL, json::value(hotkey.ctrl));
|
||||
jsonObject.SetNamedValue(JSON_KEY_CODE, json::value(hotkey.key));
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
|
||||
{
|
||||
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
|
||||
@@ -127,11 +174,44 @@ 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();
|
||||
|
||||
// Migrate Paste As PLain text shortcut
|
||||
// Migrate Paste As Plain text shortcut
|
||||
Hotkey old_paste_as_plain_hotkey;
|
||||
bool old_data_migrated = migrate_data_and_remove_data_file(old_paste_as_plain_hotkey);
|
||||
if (old_data_migrated)
|
||||
@@ -139,12 +219,7 @@ private:
|
||||
m_paste_as_plain_hotkey = old_paste_as_plain_hotkey;
|
||||
|
||||
// override settings file
|
||||
json::JsonObject new_hotkey_value;
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_WIN, json::value(old_paste_as_plain_hotkey.win));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_ALT, json::value(old_paste_as_plain_hotkey.alt));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_SHIFT, json::value(old_paste_as_plain_hotkey.shift));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_CTRL, json::value(old_paste_as_plain_hotkey.ctrl));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_CODE, json::value(old_paste_as_plain_hotkey.key));
|
||||
const auto new_hotkey_value = to_json_object(old_paste_as_plain_hotkey);
|
||||
|
||||
if (!settingsObject.HasKey(JSON_KEY_PROPERTIES))
|
||||
{
|
||||
@@ -153,13 +228,7 @@ private:
|
||||
|
||||
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_PASTE_AS_PLAIN_HOTKEY, new_hotkey_value);
|
||||
|
||||
|
||||
json::JsonObject ui_hotkey;
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_WIN, json::value(m_advanced_paste_ui_hotkey.win));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_ALT, json::value(m_advanced_paste_ui_hotkey.alt));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_SHIFT, json::value(m_advanced_paste_ui_hotkey.shift));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_CTRL, json::value(m_advanced_paste_ui_hotkey.ctrl));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_CODE, json::value(m_advanced_paste_ui_hotkey.key));
|
||||
const auto ui_hotkey = to_json_object(m_advanced_paste_ui_hotkey);
|
||||
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY, ui_hotkey);
|
||||
|
||||
settings.save_to_settings_file();
|
||||
@@ -168,40 +237,71 @@ private:
|
||||
{
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_PLAIN_HOTKEY))
|
||||
const std::array<std::pair<Hotkey*, LPCWSTR>, NUM_DEFAULT_HOTKEYS> defaultHotkeys{
|
||||
{ { &m_paste_as_plain_hotkey, JSON_KEY_PASTE_AS_PLAIN_HOTKEY },
|
||||
{ &m_advanced_paste_ui_hotkey, JSON_KEY_ADVANCED_PASTE_UI_HOTKEY },
|
||||
{ &m_paste_as_markdown_hotkey, JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY },
|
||||
{ &m_paste_as_json_hotkey, JSON_KEY_PASTE_AS_JSON_HOTKEY } }
|
||||
};
|
||||
|
||||
for (auto& [hotkey, keyName] : defaultHotkeys)
|
||||
{
|
||||
m_paste_as_plain_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_PLAIN_HOTKEY, settingsObject);
|
||||
*hotkey = parse_single_hotkey(keyName, settingsObject);
|
||||
}
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY))
|
||||
|
||||
m_additional_actions.clear();
|
||||
m_custom_actions.clear();
|
||||
|
||||
if (settingsObject.HasKey(JSON_KEY_PROPERTIES))
|
||||
{
|
||||
m_advanced_paste_ui_hotkey = parse_single_hotkey(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY, settingsObject);
|
||||
}
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY))
|
||||
{
|
||||
m_paste_as_markdown_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY, settingsObject);
|
||||
}
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_JSON_HOTKEY))
|
||||
{
|
||||
m_paste_as_json_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_JSON_HOTKEY, settingsObject);
|
||||
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);
|
||||
|
||||
for (const auto& customAction : customActions)
|
||||
{
|
||||
const auto object = customAction.GetObjectW();
|
||||
|
||||
if (object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_process_running()
|
||||
bool is_process_running() const
|
||||
{
|
||||
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void launch_process(const std::wstring& arg = L"")
|
||||
void launch_process(const std::wstring& pipe_name)
|
||||
{
|
||||
Logger::trace(L"Starting AdvancedPaste process");
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
const unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
|
||||
std::wstring executable_args = L"";
|
||||
executable_args.append(std::to_wstring(powertoys_pid));
|
||||
|
||||
executable_args += L" " + arg;
|
||||
const auto executable_args = std::format(L"{} {}", std::to_wstring(powertoys_pid), pipe_name);
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
||||
@@ -221,6 +321,55 @@ private:
|
||||
m_hProcess = sei.hProcess;
|
||||
}
|
||||
|
||||
|
||||
std::optional<std::wstring> get_pipe_name(const std::wstring& prefix) const
|
||||
{
|
||||
UUID temp_uuid;
|
||||
wchar_t* uuid_chars = nullptr;
|
||||
if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
|
||||
{
|
||||
const auto val = get_last_error_message(GetLastError());
|
||||
Logger::error(L"UuidCreate cannot create guid. {}", val.has_value() ? val.value() : L"");
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK)
|
||||
{
|
||||
const auto val = get_last_error_message(GetLastError());
|
||||
Logger::error(L"UuidToString cannot convert to string. {}", val.has_value() ? val.value() : L"");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto pipe_name = std::format(L"{}{}", prefix, std::wstring(uuid_chars));
|
||||
RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
|
||||
|
||||
return pipe_name;
|
||||
}
|
||||
|
||||
void launch_process_and_named_pipe()
|
||||
{
|
||||
const auto pipe_name = get_pipe_name(L"powertoys_advanced_paste_");
|
||||
|
||||
if (!pipe_name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::thread create_pipe_thread ([&]{ start_named_pipe_server(pipe_name.value()); });
|
||||
launch_process(pipe_name.value());
|
||||
create_pipe_thread.join();
|
||||
}
|
||||
|
||||
void send_named_pipe_message(const std::wstring& message_type, const std::wstring& message_arg = L"")
|
||||
{
|
||||
if (m_write_pipe)
|
||||
{
|
||||
const auto message = message_arg.empty() ? std::format(L"{}\r\n", message_type) : std::format(L"{} {}\r\n", message_type, message_arg);
|
||||
|
||||
const CString file_name(message.c_str());
|
||||
m_write_pipe->Write(file_name, file_name.GetLength() * sizeof(TCHAR));
|
||||
}
|
||||
}
|
||||
|
||||
// Load the settings file.
|
||||
void init_settings()
|
||||
{
|
||||
@@ -258,7 +407,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void try_inject_modifier_key_restore(std::vector<INPUT> &inputs, short modifier)
|
||||
void try_inject_modifier_key_restore(std::vector<INPUT>& inputs, short modifier)
|
||||
{
|
||||
// Most significant bit is set if key is down
|
||||
if ((GetAsyncKeyState(static_cast<int>(modifier)) & 0x8000) != 0)
|
||||
@@ -487,15 +636,54 @@ private:
|
||||
EnumWindows(enum_windows, (LPARAM)m_hProcess);
|
||||
}
|
||||
|
||||
HRESULT start_named_pipe_server(const std::wstring& pipe_name)
|
||||
{
|
||||
const constexpr DWORD BUFSIZE = 4096 * 4;
|
||||
|
||||
const auto full_pipe_name = std::format(L"\\\\.\\pipe\\{}", pipe_name);
|
||||
|
||||
const auto hPipe = CreateNamedPipe(
|
||||
full_pipe_name.c_str(), // pipe name
|
||||
PIPE_ACCESS_OUTBOUND, // write access
|
||||
PIPE_TYPE_MESSAGE | // message type pipe
|
||||
PIPE_READMODE_MESSAGE | // message-read mode
|
||||
PIPE_WAIT, // blocking mode
|
||||
1, // max. instances
|
||||
BUFSIZE, // output buffer size
|
||||
0, // input buffer size
|
||||
0, // client time-out
|
||||
NULL); // default security attribute
|
||||
|
||||
if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// This call blocks until a client process connects to the pipe
|
||||
BOOL connected = ConnectNamedPipe(hPipe, NULL);
|
||||
if (!connected)
|
||||
{
|
||||
if (GetLastError() == ERROR_PIPE_CONNECTED)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseHandle(hPipe);
|
||||
}
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
m_write_pipe = std::make_unique<CAtlFile>(hPipe);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
public:
|
||||
AdvancedPaste()
|
||||
{
|
||||
app_name = GET_RESOURCE_STRING(IDS_ADVANCED_PASTE_NAME);
|
||||
app_key = AdvancedPasteConstants::ModuleKey;
|
||||
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "AdvancedPaste");
|
||||
m_hShowUIEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_ADVANCED_PASTE_SHARED_EVENT);
|
||||
m_hPasteMarkdownEvent = CreateDefaultEvent(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_EVENT);
|
||||
m_hPasteJsonEvent = CreateDefaultEvent(CommonSharedConstants::ADVANCED_PASTE_JSON_EVENT);
|
||||
init_settings();
|
||||
}
|
||||
|
||||
@@ -559,7 +747,7 @@ public:
|
||||
|
||||
parse_hotkeys(values);
|
||||
|
||||
auto settingsObject = values.get_raw_json();
|
||||
const auto settingsObject = values.get_raw_json();
|
||||
if (settingsObject.GetView().Size() && settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))
|
||||
{
|
||||
m_preview_custom_format_output = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
@@ -567,10 +755,10 @@ public:
|
||||
|
||||
// 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_advanced_paste_ui_hotkey,
|
||||
m_paste_as_markdown_hotkey,
|
||||
m_paste_as_json_hotkey,
|
||||
m_preview_custom_format_output);
|
||||
|
||||
// If you don't need to do any custom processing of the settings, proceed
|
||||
// to persists the values calling:
|
||||
@@ -588,12 +776,9 @@ public:
|
||||
{
|
||||
Logger::trace("AdvancedPaste::enable()");
|
||||
Trace::AdvancedPaste_Enable(true);
|
||||
ResetEvent(m_hShowUIEvent);
|
||||
ResetEvent(m_hPasteMarkdownEvent);
|
||||
ResetEvent(m_hPasteJsonEvent);
|
||||
m_enabled = true;
|
||||
|
||||
launch_process();
|
||||
launch_process_and_named_pipe();
|
||||
};
|
||||
|
||||
virtual void disable()
|
||||
@@ -601,9 +786,8 @@ public:
|
||||
Logger::trace("AdvancedPaste::disable()");
|
||||
if (m_enabled)
|
||||
{
|
||||
ResetEvent(m_hShowUIEvent);
|
||||
ResetEvent(m_hPasteMarkdownEvent);
|
||||
ResetEvent(m_hPasteJsonEvent);
|
||||
m_write_pipe = nullptr;
|
||||
|
||||
TerminateProcess(m_hProcess, 1);
|
||||
Trace::AdvancedPaste_Enable(false);
|
||||
|
||||
@@ -622,13 +806,14 @@ public:
|
||||
if (!is_process_running())
|
||||
{
|
||||
Logger::trace(L"Launching new process");
|
||||
launch_process();
|
||||
launch_process_and_named_pipe();
|
||||
|
||||
Trace::AdvancedPaste_Invoked(L"AdvancedPasteUI");
|
||||
}
|
||||
|
||||
// hotkeyId in same order as set by get_hotkeys
|
||||
if (hotkeyId == 0) { // m_paste_as_plain_hotkey
|
||||
if (hotkeyId == 0)
|
||||
{ // m_paste_as_plain_hotkey
|
||||
Logger::trace(L"Paste as plain text hotkey pressed");
|
||||
|
||||
std::thread([=]() {
|
||||
@@ -641,21 +826,47 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hotkeyId == 1) { // m_advanced_paste_ui_hotkey
|
||||
if (hotkeyId == 1)
|
||||
{ // m_advanced_paste_ui_hotkey
|
||||
Logger::trace(L"Setting start up event");
|
||||
|
||||
bring_process_to_front();
|
||||
SetEvent(m_hShowUIEvent);
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE);
|
||||
return true;
|
||||
}
|
||||
if (hotkeyId == 2) { // m_paste_as_markdown_hotkey
|
||||
if (hotkeyId == 2)
|
||||
{ // m_paste_as_markdown_hotkey
|
||||
Logger::trace(L"Starting paste as markdown directly");
|
||||
SetEvent(m_hPasteMarkdownEvent);
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_MESSAGE);
|
||||
return true;
|
||||
}
|
||||
if (hotkeyId == 3) { // m_paste_as_json_hotkey
|
||||
if (hotkeyId == 3)
|
||||
{ // m_paste_as_json_hotkey
|
||||
Logger::trace(L"Starting paste as json directly");
|
||||
SetEvent(m_hPasteJsonEvent);
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const auto additional_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
|
||||
if (additional_action_index < m_additional_actions.size())
|
||||
{
|
||||
const auto& id = m_additional_actions.at(additional_action_index).id;
|
||||
|
||||
Logger::trace(L"Starting additional action id={}", 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);
|
||||
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE, std::to_wstring(id));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -665,14 +876,22 @@ public:
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
if (hotkeys && buffer_size >= 4)
|
||||
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_additional_actions.size() + m_custom_actions.size();
|
||||
|
||||
if (hotkeys && buffer_size >= num_hotkeys)
|
||||
{
|
||||
hotkeys[0] = m_paste_as_plain_hotkey;
|
||||
hotkeys[1] = m_advanced_paste_ui_hotkey;
|
||||
hotkeys[2] = m_paste_as_markdown_hotkey;
|
||||
hotkeys[3] = m_paste_as_json_hotkey;
|
||||
const std::array default_hotkeys = { m_paste_as_plain_hotkey,
|
||||
m_advanced_paste_ui_hotkey,
|
||||
m_paste_as_markdown_hotkey,
|
||||
m_paste_as_json_hotkey };
|
||||
std::copy(default_hotkeys.begin(), default_hotkeys.end(), 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 4;
|
||||
|
||||
return num_hotkeys;
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ProjectGuid>{f5e1146e-b7b3-4e11-85fd-270a500bd78c}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>CropAndLock</RootNamespace>
|
||||
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.20348.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.22621.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
@@ -8,7 +8,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToBoolConverter : IValueConverter
|
||||
public partial class EnvironmentStateToBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToMessageConverter : IValueConverter
|
||||
public partial class EnvironmentStateToMessageConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToTitleConverter : IValueConverter
|
||||
public partial class EnvironmentStateToTitleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToVisibilityConverter : IValueConverter
|
||||
public partial class EnvironmentStateToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class VariableTypeToGlyphConverter : IValueConverter
|
||||
public partial class VariableTypeToGlyphConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<ProjectGuid>{c604b37e-9d0e-4484-8778-e8b31b0e1b3a}</ProjectGuid>
|
||||
<ProjectName>PowerToys.FileLocksmithLib.Interop</ProjectName>
|
||||
<RootNamespace>PowerToys.FileLocksmithLib.Interop</RootNamespace>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
|
||||
<TargetName>PowerToys.FileLocksmithLib.Interop</TargetName>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
|
||||
using System;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using PowerToys.FileLocksmithLib.Interop;
|
||||
|
||||
namespace PowerToys.FileLocksmithUI.Converters
|
||||
{
|
||||
public sealed class FileCountConverter : IValueConverter
|
||||
public sealed partial class FileCountConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using PowerToys.FileLocksmithLib.Interop;
|
||||
|
||||
namespace PowerToys.FileLocksmithUI.Converters
|
||||
{
|
||||
public sealed class FileListToDescriptionConverter : IValueConverter
|
||||
public sealed partial class FileListToDescriptionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -7,11 +7,10 @@ using System.Drawing;
|
||||
using System.IO;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace PowerToys.FileLocksmithUI.Converters
|
||||
{
|
||||
public sealed class PidToIconConverter : IValueConverter
|
||||
public sealed partial class PidToIconConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using PowerToys.FileLocksmithLib.Interop;
|
||||
|
||||
namespace PowerToys.FileLocksmithUI.Converters
|
||||
{
|
||||
public sealed class UserToSystemWarningVisibilityConverter : IValueConverter
|
||||
public sealed partial class UserToSystemWarningVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<PublishDir>$(PowerToysRoot)\$(Platform)\$(Configuration)\WinUI3Apps</PublishDir>
|
||||
|
||||
@@ -2,6 +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.IO;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -248,25 +249,53 @@ namespace Hosts.Tests
|
||||
{
|
||||
var fileSystem = new CustomMockFileSystem();
|
||||
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
|
||||
var hostsFile = new MockFileData(string.Empty);
|
||||
hostsFile.Attributes = System.IO.FileAttributes.ReadOnly;
|
||||
|
||||
var hostsFile = new MockFileData(string.Empty)
|
||||
{
|
||||
Attributes = FileAttributes.ReadOnly,
|
||||
};
|
||||
|
||||
fileSystem.AddFile(service.HostsFilePath, hostsFile);
|
||||
|
||||
await Assert.ThrowsExceptionAsync<ReadOnlyHostsException>(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>()));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Remove_ReadOnly()
|
||||
public void Remove_ReadOnly_Attribute()
|
||||
{
|
||||
var fileSystem = new CustomMockFileSystem();
|
||||
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
|
||||
var hostsFile = new MockFileData(string.Empty);
|
||||
hostsFile.Attributes = System.IO.FileAttributes.ReadOnly;
|
||||
|
||||
var hostsFile = new MockFileData(string.Empty)
|
||||
{
|
||||
Attributes = FileAttributes.ReadOnly,
|
||||
};
|
||||
|
||||
fileSystem.AddFile(service.HostsFilePath, hostsFile);
|
||||
|
||||
service.RemoveReadOnly();
|
||||
var readOnly = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(System.IO.FileAttributes.ReadOnly);
|
||||
service.RemoveReadOnlyAttribute();
|
||||
|
||||
var readOnly = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(FileAttributes.ReadOnly);
|
||||
Assert.IsFalse(readOnly);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Save_Hidden_Hosts()
|
||||
{
|
||||
var fileSystem = new CustomMockFileSystem();
|
||||
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
|
||||
|
||||
var hostsFile = new MockFileData(string.Empty)
|
||||
{
|
||||
Attributes = FileAttributes.Hidden,
|
||||
};
|
||||
|
||||
fileSystem.AddFile(service.HostsFilePath, hostsFile);
|
||||
|
||||
await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>());
|
||||
|
||||
var hidden = fileSystem.FileInfo.FromFileName(service.HostsFilePath).Attributes.HasFlag(FileAttributes.Hidden);
|
||||
Assert.IsTrue(hidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace HostsUILib.Helpers
|
||||
public class HostsService : IHostsService, IDisposable
|
||||
{
|
||||
private const string _backupSuffix = $"_PowerToysBackup_";
|
||||
private const int _defaultBufferSize = 4096; // From System.IO.File source code
|
||||
|
||||
private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
|
||||
private readonly IFileSystem _fileSystem;
|
||||
@@ -197,7 +198,16 @@ namespace HostsUILib.Helpers
|
||||
_backupDone = true;
|
||||
}
|
||||
|
||||
await _fileSystem.File.WriteAllLinesAsync(HostsFilePath, lines, Encoding);
|
||||
// FileMode.OpenOrCreate is necessary to prevent UnauthorizedAccessException when the hosts file is hidden
|
||||
using var stream = _fileSystem.FileStream.Create(HostsFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, _defaultBufferSize, FileOptions.Asynchronous);
|
||||
using var writer = new StreamWriter(stream, Encoding);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
await writer.WriteLineAsync(line.AsMemory());
|
||||
}
|
||||
|
||||
stream.SetLength(stream.Position);
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -292,7 +302,7 @@ namespace HostsUILib.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveReadOnly()
|
||||
public void RemoveReadOnlyAttribute()
|
||||
{
|
||||
var fileInfo = _fileSystem.FileInfo.FromFileName(HostsFilePath);
|
||||
if (fileInfo.IsReadOnly)
|
||||
|
||||
@@ -25,6 +25,6 @@ namespace HostsUILib.Helpers
|
||||
|
||||
void OpenHostsFile();
|
||||
|
||||
void RemoveReadOnly();
|
||||
void RemoveReadOnlyAttribute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ namespace HostsUILib.ViewModels
|
||||
[RelayCommand]
|
||||
public void OverwriteHosts()
|
||||
{
|
||||
_hostsService.RemoveReadOnly();
|
||||
_hostsService.RemoveReadOnlyAttribute();
|
||||
_ = Task.Run(SaveAsync);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,40 @@
|
||||
#include "BoundsToolOverlayUI.h"
|
||||
#include "CoordinateSystemConversion.h"
|
||||
#include "Clipboard.h"
|
||||
#include "constants.h"
|
||||
|
||||
#include <common/utils/window.h>
|
||||
|
||||
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
Measurement GetMeasurement(const CursorDrag& currentBounds, POINT cursorPos)
|
||||
{
|
||||
D2D1_RECT_F rect;
|
||||
std::tie(rect.left, rect.right) =
|
||||
std::minmax(static_cast<float>(cursorPos.x), currentBounds.startPos.x);
|
||||
std::tie(rect.top, rect.bottom) =
|
||||
std::minmax(static_cast<float>(cursorPos.y), currentBounds.startPos.y);
|
||||
|
||||
return Measurement(rect);
|
||||
}
|
||||
|
||||
void CopyToClipboard(HWND window, const BoundsToolState& toolState, POINT cursorPos)
|
||||
{
|
||||
std::vector<Measurement> allMeasurements;
|
||||
for (const auto& [handle, perScreen] : toolState.perScreen)
|
||||
{
|
||||
allMeasurements.append_range(perScreen.measurements);
|
||||
|
||||
if (handle == window && perScreen.currentBounds)
|
||||
{
|
||||
allMeasurements.push_back(GetMeasurement(*perScreen.currentBounds, cursorPos));
|
||||
}
|
||||
}
|
||||
|
||||
SetClipboardToMeasurements(allMeasurements, true, true, toolState.commonState->units);
|
||||
}
|
||||
|
||||
void ToggleCursor(const bool show)
|
||||
{
|
||||
if (show)
|
||||
@@ -52,22 +79,16 @@ namespace
|
||||
{
|
||||
ToggleCursor(true);
|
||||
ClipCursor(nullptr);
|
||||
CopyToClipboard(window, *toolState, cursorPos);
|
||||
|
||||
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer.data());
|
||||
});
|
||||
auto& perScreen = toolState->perScreen[window];
|
||||
|
||||
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress && toolState->perScreen[window].currentBounds)
|
||||
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x80000; shiftPress && perScreen.currentBounds)
|
||||
{
|
||||
D2D1_RECT_F rect;
|
||||
std::tie(rect.left, rect.right) =
|
||||
std::minmax(static_cast<float>(cursorPos.x), toolState->perScreen[window].currentBounds->startPos.x);
|
||||
std::tie(rect.top, rect.bottom) =
|
||||
std::minmax(static_cast<float>(cursorPos.y), toolState->perScreen[window].currentBounds->startPos.y);
|
||||
toolState->perScreen[window].measurements.push_back(Measurement{ rect });
|
||||
perScreen.measurements.push_back(GetMeasurement(*perScreen.currentBounds, cursorPos));
|
||||
}
|
||||
|
||||
toolState->perScreen[window].currentBounds = std::nullopt;
|
||||
perScreen.currentBounds = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,12 +107,17 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
||||
case WM_KEYUP:
|
||||
if (wparam == VK_ESCAPE)
|
||||
{
|
||||
if (const auto* toolState = GetWindowParam<BoundsToolState*>(window))
|
||||
{
|
||||
CopyToClipboard(window, *toolState, convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
|
||||
}
|
||||
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
}
|
||||
break;
|
||||
case WM_LBUTTONDOWN:
|
||||
{
|
||||
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
|
||||
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
|
||||
if (touchEvent)
|
||||
break;
|
||||
|
||||
@@ -164,7 +190,7 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
||||
|
||||
case WM_MOUSEMOVE:
|
||||
{
|
||||
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
|
||||
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
|
||||
if (touchEvent)
|
||||
break;
|
||||
|
||||
@@ -180,7 +206,7 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
||||
|
||||
case WM_LBUTTONUP:
|
||||
{
|
||||
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
|
||||
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
|
||||
if (touchEvent)
|
||||
break;
|
||||
|
||||
@@ -195,24 +221,32 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
||||
}
|
||||
case WM_RBUTTONUP:
|
||||
{
|
||||
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
|
||||
const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH;
|
||||
if (touchEvent)
|
||||
break;
|
||||
|
||||
ToggleCursor(true);
|
||||
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
auto* toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState)
|
||||
break;
|
||||
|
||||
if (toolState->perScreen[window].currentBounds)
|
||||
toolState->perScreen[window].currentBounds = std::nullopt;
|
||||
auto& perScreen = toolState->perScreen[window];
|
||||
|
||||
if (perScreen.currentBounds)
|
||||
{
|
||||
perScreen.currentBounds = std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (toolState->perScreen[window].measurements.empty())
|
||||
if (perScreen.measurements.empty())
|
||||
{
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
}
|
||||
else
|
||||
toolState->perScreen[window].measurements.clear();
|
||||
{
|
||||
perScreen.measurements.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -242,10 +276,6 @@ namespace
|
||||
true,
|
||||
commonState.units);
|
||||
|
||||
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
|
||||
v = text;
|
||||
});
|
||||
|
||||
D2D_POINT_2F textBoxPos;
|
||||
if (textBoxCenter)
|
||||
textBoxPos = *textBoxCenter;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "Clipboard.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
void SetClipBoardToText(const std::wstring_view text)
|
||||
{
|
||||
if (!OpenClipboard(nullptr))
|
||||
@@ -26,3 +28,25 @@ void SetClipBoardToText(const std::wstring_view text)
|
||||
SetClipboardData(CF_UNICODETEXT, handle.get());
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
void SetClipboardToMeasurements(const std::vector<Measurement>& measurements,
|
||||
bool printWidth,
|
||||
bool printHeight,
|
||||
Measurement::Unit units)
|
||||
{
|
||||
if (measurements.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::wostringstream stream;
|
||||
bool isFirst = true;
|
||||
|
||||
for (const auto& measurement : measurements)
|
||||
{
|
||||
measurement.PrintToStream(stream, !isFirst, printWidth, printHeight, units);
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
SetClipBoardToText(stream.str());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include "Measurement.h"
|
||||
|
||||
void SetClipBoardToText(const std::wstring_view text);
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
void SetClipBoardToText(const std::wstring_view text);
|
||||
|
||||
void SetClipboardToMeasurements(const std::vector<Measurement>& measurements,
|
||||
bool printWidth,
|
||||
bool printHeight,
|
||||
Measurement::Unit units);
|
||||
@@ -8,8 +8,51 @@
|
||||
|
||||
#include <common/utils/window.h>
|
||||
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr std::pair<bool, bool> GetHorizontalVerticalLines(MeasureToolState::Mode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case MeasureToolState::Mode::Cross:
|
||||
return { true, true };
|
||||
|
||||
case MeasureToolState::Mode::Vertical:
|
||||
return { false, true };
|
||||
|
||||
case MeasureToolState::Mode::Horizontal:
|
||||
return { true, false };
|
||||
|
||||
default:
|
||||
throw std::runtime_error("Unknown MeasureToolState Mode");
|
||||
}
|
||||
}
|
||||
|
||||
void CopyToClipboard(HWND window, const MeasureToolState& toolState)
|
||||
{
|
||||
std::vector<Measurement> allMeasurements;
|
||||
for (const auto& [handle, perScreen] : toolState.perScreen)
|
||||
{
|
||||
for (const auto& [_, measurement] : perScreen.prevMeasurements)
|
||||
{
|
||||
allMeasurements.push_back(measurement);
|
||||
}
|
||||
|
||||
if (handle == window && perScreen.measuredEdges)
|
||||
{
|
||||
allMeasurements.push_back(*perScreen.measuredEdges);
|
||||
}
|
||||
}
|
||||
|
||||
const auto [printWidth, printHeight] = GetHorizontalVerticalLines(toolState.global.mode);
|
||||
SetClipboardToMeasurements(allMeasurements, printWidth, printHeight, toolState.commonState->units);
|
||||
}
|
||||
|
||||
inline std::pair<D2D_POINT_2F, D2D_POINT_2F> ComputeCrossFeetLine(D2D_POINT_2F center, const bool horizontal)
|
||||
{
|
||||
D2D_POINT_2F start = center, end = center;
|
||||
@@ -27,6 +70,92 @@ namespace
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
bool HandleCursorUp(HWND window, MeasureToolState* toolState, const POINT cursorPos)
|
||||
{
|
||||
ClipCursor(nullptr);
|
||||
CopyToClipboard(window, *toolState);
|
||||
|
||||
auto& perScreen = toolState->perScreen[window];
|
||||
|
||||
const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000;
|
||||
if (shiftPress && perScreen.measuredEdges)
|
||||
{
|
||||
perScreen.prevMeasurements.push_back(MeasureToolState::PerScreen::PrevMeasurement(cursorPos, perScreen.measuredEdges.value()));
|
||||
}
|
||||
|
||||
perScreen.measuredEdges = std::nullopt;
|
||||
|
||||
return !shiftPress;
|
||||
}
|
||||
|
||||
void DrawMeasurement(const Measurement& measurement,
|
||||
D2DState& d2dState,
|
||||
bool drawFeetOnCross,
|
||||
MeasureToolState::Mode mode,
|
||||
POINT cursorPos,
|
||||
const CommonState& commonState,
|
||||
HWND window)
|
||||
{
|
||||
const auto [drawHorizontalCrossLine, drawVerticalCrossLine] = GetHorizontalVerticalLines(mode);
|
||||
|
||||
const float hMeasure = measurement.Width(Measurement::Unit::Pixel);
|
||||
const float vMeasure = measurement.Height(Measurement::Unit::Pixel);
|
||||
|
||||
d2dState.ToggleAliasedLinesMode(true);
|
||||
if (drawHorizontalCrossLine)
|
||||
{
|
||||
const D2D_POINT_2F hLineStart{ .x = measurement.rect.left, .y = static_cast<float>(cursorPos.y) };
|
||||
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
|
||||
d2dState.dxgiWindowState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||
|
||||
if (drawFeetOnCross)
|
||||
{
|
||||
// To fill all pixels which are close, we call DrawLine with end point one pixel too far, since
|
||||
// it doesn't get filled, i.e. end point of the range is excluded. However, we want to draw cross
|
||||
// feet *on* the last pixel row, so we must subtract 1px from the corresponding axis.
|
||||
hLineEnd.x -= 1.f;
|
||||
const auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
|
||||
const auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
|
||||
d2dState.dxgiWindowState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
|
||||
d2dState.dxgiWindowState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
|
||||
}
|
||||
}
|
||||
|
||||
if (drawVerticalCrossLine)
|
||||
{
|
||||
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = measurement.rect.top };
|
||||
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
|
||||
d2dState.dxgiWindowState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||
|
||||
if (drawFeetOnCross)
|
||||
{
|
||||
vLineEnd.y -= 1.f;
|
||||
const auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
|
||||
const auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
|
||||
d2dState.dxgiWindowState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
|
||||
d2dState.dxgiWindowState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
|
||||
}
|
||||
}
|
||||
|
||||
d2dState.ToggleAliasedLinesMode(false);
|
||||
|
||||
OverlayBoxText text;
|
||||
|
||||
const auto [crossSymbolPos, measureStringBufLen] =
|
||||
measurement.Print(text.buffer.data(),
|
||||
text.buffer.size(),
|
||||
drawHorizontalCrossLine,
|
||||
drawVerticalCrossLine,
|
||||
commonState.units);
|
||||
|
||||
d2dState.DrawTextBox(text.buffer.data(),
|
||||
measureStringBufLen,
|
||||
crossSymbolPos,
|
||||
D2D_POINT_2F{ static_cast<float>(cursorPos.x), static_cast<float>(cursorPos.y) },
|
||||
true,
|
||||
window);
|
||||
}
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID2D1Bitmap> ConvertID3D11Texture2DToD2D1Bitmap(winrt::com_ptr<ID2D1RenderTarget> rt,
|
||||
@@ -85,17 +214,29 @@ LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
|
||||
}
|
||||
break;
|
||||
case WM_RBUTTONUP:
|
||||
{
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONUP:
|
||||
{
|
||||
bool shouldClose = true;
|
||||
|
||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||
{
|
||||
state->Read([](const MeasureToolState& s) { s.commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer.data());
|
||||
}); });
|
||||
state->Access([&](MeasureToolState& s) {
|
||||
shouldClose = HandleCursorUp(window,
|
||||
&s,
|
||||
convert::FromSystemToWindow(window, s.commonState->cursorPosSystemSpace));
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldClose)
|
||||
{
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
}
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
break;
|
||||
}
|
||||
case WM_MOUSEWHEEL:
|
||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||
{
|
||||
@@ -119,29 +260,29 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
||||
{
|
||||
bool continuousCapture = {};
|
||||
bool drawFeetOnCross = {};
|
||||
bool drawHorizontalCrossLine = true;
|
||||
bool drawVerticalCrossLine = true;
|
||||
|
||||
Measurement measuredEdges{};
|
||||
std::optional<Measurement> measuredEdges{};
|
||||
MeasureToolState::Mode mode = {};
|
||||
winrt::com_ptr<ID2D1Bitmap> backgroundBitmap;
|
||||
const MappedTextureView* backgroundTextureToConvert = nullptr;
|
||||
|
||||
bool gotMeasurement = false;
|
||||
std::vector<MeasureToolState::PerScreen::PrevMeasurement> prevMeasurements;
|
||||
toolState.Read([&](const MeasureToolState& state) {
|
||||
continuousCapture = state.global.continuousCapture;
|
||||
drawFeetOnCross = state.global.drawFeetOnCross;
|
||||
mode = state.global.mode;
|
||||
if (auto it = state.perScreen.find(window); it != end(state.perScreen))
|
||||
|
||||
if (const auto it = state.perScreen.find(window); it != end(state.perScreen))
|
||||
{
|
||||
const auto& perScreen = it->second;
|
||||
|
||||
prevMeasurements = perScreen.prevMeasurements;
|
||||
|
||||
if (!perScreen.measuredEdges)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gotMeasurement = true;
|
||||
measuredEdges = *perScreen.measuredEdges;
|
||||
measuredEdges = perScreen.measuredEdges;
|
||||
|
||||
if (continuousCapture)
|
||||
return;
|
||||
@@ -157,23 +298,9 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
||||
}
|
||||
});
|
||||
|
||||
if (!gotMeasurement)
|
||||
return;
|
||||
|
||||
switch (mode)
|
||||
if (!measuredEdges && prevMeasurements.empty())
|
||||
{
|
||||
case MeasureToolState::Mode::Cross:
|
||||
drawHorizontalCrossLine = true;
|
||||
drawVerticalCrossLine = true;
|
||||
break;
|
||||
case MeasureToolState::Mode::Vertical:
|
||||
drawHorizontalCrossLine = false;
|
||||
drawVerticalCrossLine = true;
|
||||
break;
|
||||
case MeasureToolState::Mode::Horizontal:
|
||||
drawHorizontalCrossLine = true;
|
||||
drawVerticalCrossLine = false;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!continuousCapture && !backgroundBitmap && backgroundTextureToConvert)
|
||||
@@ -189,73 +316,23 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
||||
}
|
||||
|
||||
if (continuousCapture || !backgroundBitmap)
|
||||
{
|
||||
d2dState.dxgiWindowState.rt->Clear();
|
||||
|
||||
const float hMeasure = measuredEdges.Width(Measurement::Unit::Pixel);
|
||||
const float vMeasure = measuredEdges.Height(Measurement::Unit::Pixel);
|
||||
}
|
||||
|
||||
if (!continuousCapture && backgroundBitmap)
|
||||
{
|
||||
d2dState.dxgiWindowState.rt->DrawBitmap(backgroundBitmap.get());
|
||||
}
|
||||
|
||||
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
|
||||
|
||||
d2dState.ToggleAliasedLinesMode(true);
|
||||
if (drawHorizontalCrossLine)
|
||||
for (const auto& [prevCursorPos, prevMeasurement] : prevMeasurements)
|
||||
{
|
||||
const D2D_POINT_2F hLineStart{ .x = measuredEdges.rect.left, .y = static_cast<float>(cursorPos.y) };
|
||||
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
|
||||
d2dState.dxgiWindowState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||
|
||||
if (drawFeetOnCross)
|
||||
{
|
||||
// To fill all pixels which are close, we call DrawLine with end point one pixel too far, since
|
||||
// it doesn't get filled, i.e. end point of the range is excluded. However, we want to draw cross
|
||||
// feet *on* the last pixel row, so we must subtract 1px from the corresponding axis.
|
||||
hLineEnd.x -= 1.f;
|
||||
auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
|
||||
auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
|
||||
d2dState.dxgiWindowState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
|
||||
d2dState.dxgiWindowState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
|
||||
}
|
||||
DrawMeasurement(prevMeasurement, d2dState, drawFeetOnCross, mode, prevCursorPos, commonState, window);
|
||||
}
|
||||
|
||||
if (drawVerticalCrossLine)
|
||||
if (measuredEdges)
|
||||
{
|
||||
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = measuredEdges.rect.top };
|
||||
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
|
||||
d2dState.dxgiWindowState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||
|
||||
if (drawFeetOnCross)
|
||||
{
|
||||
vLineEnd.y -= 1.f;
|
||||
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
|
||||
auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
|
||||
d2dState.dxgiWindowState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
|
||||
d2dState.dxgiWindowState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
|
||||
}
|
||||
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
|
||||
DrawMeasurement(*measuredEdges, d2dState, drawFeetOnCross, mode, cursorPos, commonState, window);
|
||||
}
|
||||
|
||||
d2dState.ToggleAliasedLinesMode(false);
|
||||
|
||||
OverlayBoxText text;
|
||||
|
||||
const auto [crossSymbolPos, measureStringBufLen] =
|
||||
measuredEdges.Print(text.buffer.data(),
|
||||
text.buffer.size(),
|
||||
drawHorizontalCrossLine,
|
||||
drawVerticalCrossLine,
|
||||
commonState.units);
|
||||
|
||||
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
|
||||
v = text;
|
||||
});
|
||||
|
||||
d2dState.DrawTextBox(text.buffer.data(),
|
||||
measureStringBufLen,
|
||||
crossSymbolPos,
|
||||
D2D_POINT_2F{ static_cast<float>(cursorPos.x), static_cast<float>(cursorPos.y) },
|
||||
true,
|
||||
window);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "Measurement.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
Measurement::Measurement(RECT winRect)
|
||||
{
|
||||
rect.left = static_cast<float>(winRect.left);
|
||||
@@ -89,3 +91,41 @@ Measurement::PrintResult Measurement::Print(wchar_t* buf,
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Measurement::PrintToStream(std::wostream& stream,
|
||||
const bool prependNewLine,
|
||||
const bool printWidth,
|
||||
const bool printHeight,
|
||||
const Unit units) const
|
||||
{
|
||||
if (prependNewLine)
|
||||
{
|
||||
stream << std::endl;
|
||||
}
|
||||
|
||||
if (printWidth)
|
||||
{
|
||||
stream << Width(units);
|
||||
if (printHeight)
|
||||
{
|
||||
stream << L" \x00D7 ";
|
||||
}
|
||||
}
|
||||
|
||||
if (printHeight)
|
||||
{
|
||||
stream << Height(units);
|
||||
}
|
||||
|
||||
switch (units)
|
||||
{
|
||||
case Measurement::Unit::Inch:
|
||||
stream << L" in";
|
||||
|
||||
break;
|
||||
case Measurement::Unit::Centimetre:
|
||||
stream << L" cm";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <dcommon.h>
|
||||
#include <windef.h>
|
||||
#include <iosfwd>
|
||||
|
||||
struct Measurement
|
||||
{
|
||||
@@ -35,4 +36,10 @@ struct Measurement
|
||||
const bool printWidth,
|
||||
const bool printHeight,
|
||||
const Unit units) const;
|
||||
|
||||
void PrintToStream(std::wostream& stream,
|
||||
const bool prependNewLine,
|
||||
const bool printWidth,
|
||||
const bool printHeight,
|
||||
const Unit units) const;
|
||||
};
|
||||
|
||||
@@ -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.5.240428000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.5.240428000\build\native\Microsoft.WindowsAppSDK.props')" />
|
||||
<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.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">
|
||||
@@ -21,10 +21,6 @@
|
||||
<AppxPackage>false</AppxPackage>
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<!-- Even though these are defined in Cpp.Build.props some of the other props referred here override these vales, so we need to specify them. -->
|
||||
<WindowsTargetPlatformVersion>10.0.20348.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
@@ -141,7 +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.5.240428000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.5.240428000\build\native\Microsoft.WindowsAppSDK.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')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
@@ -152,7 +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.5.240428000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.5.240428000\build\native\Microsoft.WindowsAppSDK.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.5.240428000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.5.240428000\build\native\Microsoft.WindowsAppSDK.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'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -31,7 +31,6 @@ struct CommonState
|
||||
|
||||
Measurement::Unit units = Measurement::Unit::Pixel;
|
||||
|
||||
mutable Serialized<OverlayBoxText> overlayBoxText;
|
||||
POINT cursorPosSystemSpace = {}; // updated atomically
|
||||
std::atomic_bool closeOnOtherMonitors = false;
|
||||
};
|
||||
@@ -77,9 +76,13 @@ struct MeasureToolState
|
||||
|
||||
struct PerScreen
|
||||
{
|
||||
using PrevMeasurement = std::pair<POINT, Measurement>;
|
||||
|
||||
bool cursorInLeftScreenHalf = false;
|
||||
bool cursorInTopScreenHalf = false;
|
||||
std::optional<Measurement> measuredEdges;
|
||||
std::vector<PrevMeasurement> prevMeasurements;
|
||||
|
||||
// While not in a continuous capturing mode, we need to draw captured backgrounds. These are passed
|
||||
// directly from a capturing thread.
|
||||
const MappedTextureView* capturedScreenTexture = nullptr;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <windef.h>
|
||||
#include <chrono>
|
||||
|
||||
namespace consts
|
||||
@@ -18,4 +19,6 @@ namespace consts
|
||||
/* Offset to not try not to use the cursor immediate pixels in measuring, but it seems only necessary for continuous mode. */
|
||||
constexpr inline long CURSOR_OFFSET_AMOUNT_X = 4;
|
||||
constexpr inline long CURSOR_OFFSET_AMOUNT_Y = 4;
|
||||
|
||||
constexpr inline LPARAM MOUSEEVENTF_FROMTOUCH = 0xFF515700;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Web.WebView2" version="1.0.2739.15" targetFramework="native" />
|
||||
<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.5.240428000" targetFramework="native" />
|
||||
<package id="Microsoft.WindowsAppSDK" version="1.6.240829007" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -56,6 +56,11 @@ namespace MeasureToolUI
|
||||
this.SetIsMaximizable(false);
|
||||
IsTitleBarVisible = false;
|
||||
|
||||
// Remove the caption style from the window style. Windows App SDK 1.6 added it, which made the title bar and borders appear for Measure Tool. This code removes it.
|
||||
var windowStyle = GetWindowLong(hwnd, GWL_STYLE);
|
||||
windowStyle = windowStyle & (~WS_CAPTION);
|
||||
_ = SetWindowLong(hwnd, GWL_STYLE, windowStyle);
|
||||
|
||||
_coreLogic = core;
|
||||
Closed += MainWindow_Closed;
|
||||
DisplayArea displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
|
||||
|
||||
@@ -15,4 +15,13 @@ internal static class NativeMethods
|
||||
internal const uint SWP_NOMOVE = 0x0002;
|
||||
internal const uint SWP_NOACTIVATE = 0x0010;
|
||||
internal const uint SWP_SHOWWINDOW = 0x0040;
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
internal const int GWL_STYLE = -16;
|
||||
internal const int WS_CAPTION = 0x00C00000;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,20 @@ namespace WorkspacesEditor
|
||||
return;
|
||||
}
|
||||
|
||||
var args = e?.Args;
|
||||
int powerToysRunnerPid;
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
_ = int.TryParse(args[0], out powerToysRunnerPid);
|
||||
|
||||
Logger.LogInfo($"WorkspacesEditor started from the PowerToys Runner. Runner pid={powerToysRunnerPid}");
|
||||
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting WorkspacesEditor");
|
||||
Dispatcher.Invoke(Shutdown);
|
||||
});
|
||||
}
|
||||
|
||||
ThemeManager = new ThemeManager(this);
|
||||
|
||||
if (_mainViewModel == null)
|
||||
@@ -89,6 +103,7 @@ namespace WorkspacesEditor
|
||||
}
|
||||
|
||||
Dispose();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||
|
||||
@@ -16,14 +16,6 @@ namespace WorkspacesEditor.Data
|
||||
}
|
||||
}
|
||||
|
||||
public static string LaunchFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return FolderUtils.DataFolder() + "\\tempLaunch-workspaces.json";
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteTempFile()
|
||||
{
|
||||
if (System.IO.File.Exists(File))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Page
|
||||
<Page
|
||||
x:Class="WorkspacesEditor.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@@ -150,6 +150,7 @@
|
||||
Width="140"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
BorderThickness="2"
|
||||
SelectedIndex="{Binding OrderByIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ComboBoxItem Content="{x:Static props:Resources.LastLaunched}" />
|
||||
<ComboBoxItem Content="{x:Static props:Resources.Created}" />
|
||||
@@ -198,7 +199,7 @@
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="110" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Margin="12,14,10,10"
|
||||
@@ -307,7 +308,7 @@
|
||||
AutomationProperties.Name="{x:Static props:Resources.Launch}"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource SecondaryBorderBrush}"
|
||||
BorderThickness="1"
|
||||
BorderThickness="2"
|
||||
Click="LaunchButton_Click"
|
||||
Content="{x:Static props:Resources.Launch}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using ManagedCommon;
|
||||
@@ -14,10 +15,12 @@ namespace WorkspacesEditor
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
public partial class MainWindow : Window, IDisposable
|
||||
{
|
||||
public MainViewModel MainViewModel { get; set; }
|
||||
|
||||
private CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
|
||||
private static MainPage _mainPage;
|
||||
|
||||
public MainWindow(MainViewModel mainViewModel)
|
||||
@@ -41,10 +44,36 @@ namespace WorkspacesEditor
|
||||
|
||||
MaxWidth = SystemParameters.PrimaryScreenWidth;
|
||||
MaxHeight = SystemParameters.PrimaryScreenHeight;
|
||||
|
||||
Common.UI.NativeEventWaiter.WaitForEventLoop(
|
||||
PowerToys.Interop.Constants.WorkspacesHotkeyEvent(),
|
||||
() =>
|
||||
{
|
||||
if (ApplicationIsInFocus())
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (WindowState == WindowState.Minimized)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
// Get the window handle of the Workspaces Editor window
|
||||
IntPtr handle = new WindowInteropHelper(this).Handle;
|
||||
WindowHelpers.BringToForeground(handle);
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
},
|
||||
Application.Current.Dispatcher,
|
||||
cancellationToken.Token);
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, EventArgs e)
|
||||
{
|
||||
cancellationToken.Dispose();
|
||||
App.Current.Shutdown();
|
||||
}
|
||||
|
||||
@@ -67,5 +96,25 @@ namespace WorkspacesEditor
|
||||
{
|
||||
ContentFrame.GoBack();
|
||||
}
|
||||
|
||||
public static bool ApplicationIsInFocus()
|
||||
{
|
||||
var activatedHandle = NativeMethods.GetForegroundWindow();
|
||||
if (activatedHandle == IntPtr.Zero)
|
||||
{
|
||||
return false; // No window is currently activated
|
||||
}
|
||||
|
||||
var procId = Environment.ProcessId;
|
||||
int activeProcId;
|
||||
_ = NativeMethods.GetWindowThreadProcessId(activatedHandle, out activeProcId);
|
||||
|
||||
return activeProcId == procId;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,9 +207,9 @@ namespace WorkspacesEditor.Models
|
||||
private BitmapImage _previewImage;
|
||||
private double _previewImageWidth;
|
||||
|
||||
public Project(Project selectedProject, string newId = "")
|
||||
public Project(Project selectedProject)
|
||||
{
|
||||
Id = newId == string.Empty ? selectedProject.Id : newId;
|
||||
Id = selectedProject.Id;
|
||||
Name = selectedProject.Name;
|
||||
PreviewIcons = selectedProject.PreviewIcons;
|
||||
PreviewImage = selectedProject.PreviewImage;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
Title="{x:Static props:Resources.SnapshotWindowTitle}"
|
||||
Width="350"
|
||||
Height="140"
|
||||
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
ui:TitleBar.InactiveBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
@@ -18,6 +17,7 @@
|
||||
BorderThickness="5"
|
||||
Closing="Window_Closing"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Grid Margin="5" Background="Transparent">
|
||||
@@ -31,9 +31,10 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="5"
|
||||
Margin="5,5,5,15"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="{x:Static props:Resources.SnapshotDescription}" />
|
||||
Text="{x:Static props:Resources.SnapshotDescription}"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="CancelButton"
|
||||
Grid.Row="1"
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace WorkspacesEditor.Utils
|
||||
public static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
|
||||
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int processId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetCurrentThreadId();
|
||||
|
||||
@@ -15,13 +15,6 @@ namespace WorkspacesEditor.Utils
|
||||
{
|
||||
public class WorkspacesEditorIO
|
||||
{
|
||||
public enum StorageFile
|
||||
{
|
||||
Common,
|
||||
Temporal,
|
||||
TemporalLaunch,
|
||||
}
|
||||
|
||||
public WorkspacesEditorIO()
|
||||
{
|
||||
}
|
||||
@@ -79,7 +72,7 @@ namespace WorkspacesEditor.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public void SerializeWorkspaces(List<Project> workspaces, StorageFile storageFile = StorageFile.Common)
|
||||
public void SerializeWorkspaces(List<Project> workspaces, bool useTempFile = false)
|
||||
{
|
||||
WorkspacesData serializer = new WorkspacesData();
|
||||
WorkspacesData.WorkspacesListWrapper workspacesWrapper = new WorkspacesData.WorkspacesListWrapper { };
|
||||
@@ -155,14 +148,7 @@ namespace WorkspacesEditor.Utils
|
||||
try
|
||||
{
|
||||
IOUtils ioUtils = new IOUtils();
|
||||
string fileName = storageFile switch
|
||||
{
|
||||
StorageFile.Temporal => TempProjectData.File,
|
||||
StorageFile.TemporalLaunch => TempProjectData.LaunchFile,
|
||||
_ => serializer.File,
|
||||
};
|
||||
|
||||
ioUtils.WriteFile(fileName, serializer.Serialize(workspacesWrapper));
|
||||
ioUtils.WriteFile(useTempFile ? TempProjectData.File : serializer.File, serializer.Serialize(workspacesWrapper));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -190,22 +176,7 @@ namespace WorkspacesEditor.Utils
|
||||
|
||||
internal void SerializeTempProject(Project project)
|
||||
{
|
||||
SerializeWorkspaces(new List<Project>() { project }, StorageFile.Temporal);
|
||||
}
|
||||
|
||||
internal void RemoveFile(StorageFile storageFile)
|
||||
{
|
||||
string fileName = storageFile switch
|
||||
{
|
||||
StorageFile.Temporal => TempProjectData.File,
|
||||
StorageFile.TemporalLaunch => TempProjectData.LaunchFile,
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
SerializeWorkspaces(new List<Project>() { project }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,13 +509,7 @@ namespace WorkspacesEditor.ViewModels
|
||||
|
||||
internal async void LaunchAndEdit(Project project)
|
||||
{
|
||||
// the project might contain removed apps, creating a temporal copy of it (without removed apps) and launching the copy.
|
||||
Project launchProject = new Project(project, Guid.NewGuid().ToString());
|
||||
_workspacesEditorIO.SerializeWorkspaces(new List<Project>() { launchProject }, WorkspacesEditorIO.StorageFile.TemporalLaunch);
|
||||
|
||||
await Task.Run(() => RunLauncher(launchProject.Id, InvokePoint.LaunchAndEdit));
|
||||
|
||||
_workspacesEditorIO.RemoveFile(WorkspacesEditorIO.StorageFile.TemporalLaunch);
|
||||
await Task.Run(() => RunLauncher(project.Id, InvokePoint.LaunchAndEdit));
|
||||
projectBeforeLaunch = new Project(project);
|
||||
EnterSnapshotMode(true);
|
||||
}
|
||||
|
||||
@@ -104,11 +104,13 @@
|
||||
</StackPanel>
|
||||
<controls:ResetIsEnabled Grid.Column="4">
|
||||
<Button
|
||||
Width="120"
|
||||
Width="Auto"
|
||||
Margin="10,5"
|
||||
Padding="24,6"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource SecondaryBorderBrush}"
|
||||
BorderThickness="2"
|
||||
Click="DeleteButtonClicked"
|
||||
Content="{Binding DeleteButtonContent, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsEnabled="True" />
|
||||
@@ -338,6 +340,8 @@
|
||||
HorizontalAlignment="Right"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Revert}"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
BorderThickness="2"
|
||||
Click="RevertButtonClicked"
|
||||
Content="{x:Static props:Resources.Revert}"
|
||||
DockPanel.Dock="Right"
|
||||
@@ -350,6 +354,8 @@
|
||||
HorizontalAlignment="Right"
|
||||
AutomationProperties.Name="{x:Static props:Resources.LaunchEdit}"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
BorderThickness="2"
|
||||
Click="LaunchEditButtonClicked"
|
||||
Content="{x:Static props:Resources.LaunchEdit}"
|
||||
DockPanel.Dock="Right" />
|
||||
@@ -402,6 +408,8 @@
|
||||
Padding="24,0,24,0"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
BorderThickness="2"
|
||||
Click="CancelButtonClicked"
|
||||
Content="{x:Static props:Resources.Cancel}" />
|
||||
<Button
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">System</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 456 KiB |
@@ -240,6 +240,8 @@ namespace
|
||||
|
||||
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, ErrorList& launchErrors)
|
||||
{
|
||||
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
||||
|
||||
SHELLEXECUTEINFO sei = { 0 };
|
||||
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
sei.hwnd = nullptr;
|
||||
@@ -247,7 +249,7 @@ bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs,
|
||||
sei.lpVerb = elevated ? L"runas" : L"open";
|
||||
sei.lpFile = appPath.c_str();
|
||||
sei.lpParameters = commandLineArgs.c_str();
|
||||
sei.lpDirectory = nullptr;
|
||||
sei.lpDirectory = dir.c_str();
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
|
||||
if (!ShellExecuteEx(&sei))
|
||||
|
||||
@@ -24,7 +24,7 @@ const std::wstring internalPath = L"";
|
||||
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cmdShow)
|
||||
{
|
||||
LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::workspacesLauncherLoggerName);
|
||||
InitUnhandledExceptionHandler();
|
||||
InitUnhandledExceptionHandler();
|
||||
|
||||
if (powertoys_gpo::getConfiguredWorkspacesEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
@@ -41,7 +41,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
||||
GetModuleFileNameW(nullptr, exe_path.get(), exe_path_size);
|
||||
|
||||
const auto modulePath = get_module_folderpath();
|
||||
|
||||
|
||||
std::string cmdLineStr(cmdline);
|
||||
std::wstring cmdLineWStr(cmdLineStr.begin(), cmdLineStr.end());
|
||||
|
||||
@@ -57,7 +57,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
||||
}
|
||||
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
|
||||
std::string cmdLineStr(cmdline);
|
||||
auto cmdArgs = split(cmdLineStr, " ");
|
||||
if (cmdArgs.size() < 1)
|
||||
@@ -66,7 +66,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
||||
MessageBox(NULL, GET_RESOURCE_STRING(IDS_INCORRECT_ARGS).c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
std::wstring id(cmdArgs[0].begin(), cmdArgs[0].end());
|
||||
if (id.empty())
|
||||
{
|
||||
@@ -94,103 +94,45 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
||||
WorkspacesData::WorkspacesProject projectToLaunch{};
|
||||
if (invokePoint == InvokePoint::LaunchAndEdit)
|
||||
{
|
||||
// check the temp launch file in case the project is launched from the editor
|
||||
if (std::filesystem::exists(WorkspacesData::TempLaunchWorkspacesFile()))
|
||||
// check the temp file in case the project is just created and not saved to the workspaces.json yet
|
||||
if (std::filesystem::exists(WorkspacesData::TempWorkspacesFile()))
|
||||
{
|
||||
try
|
||||
{
|
||||
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempLaunchWorkspacesFile());
|
||||
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempWorkspacesFile());
|
||||
if (savedWorkspacesJson.has_value())
|
||||
{
|
||||
auto savedWorkspaces = WorkspacesData::WorkspacesListJSON::FromJson(savedWorkspacesJson.value());
|
||||
auto savedWorkspaces = WorkspacesData::WorkspacesProjectJSON::FromJson(savedWorkspacesJson.value());
|
||||
if (savedWorkspaces.has_value())
|
||||
{
|
||||
workspaces = savedWorkspaces.value();
|
||||
projectToLaunch = savedWorkspaces.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect temporal launch Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempLaunchWorkspacesFile());
|
||||
Logger::critical("Incorrect Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect temporal launch Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempLaunchWorkspacesFile());
|
||||
Logger::critical("Incorrect Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
Logger::critical("Exception on reading temporal launch Workspaces file: {}", ex.what());
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempLaunchWorkspacesFile());
|
||||
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (workspaces.empty())
|
||||
{
|
||||
Logger::warn("Temp Launch Workspaces file is empty");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_EMPTY_FILE), WorkspacesData::TempLaunchWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (const auto& proj : workspaces)
|
||||
{
|
||||
if (proj.id == id)
|
||||
{
|
||||
projectToLaunch = proj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectToLaunch.id.empty())
|
||||
{
|
||||
// check the temp file in case the project is just created and not saved to the workspaces.json yet
|
||||
if (std::filesystem::exists(WorkspacesData::TempWorkspacesFile()))
|
||||
{
|
||||
try
|
||||
{
|
||||
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempWorkspacesFile());
|
||||
if (savedWorkspacesJson.has_value())
|
||||
{
|
||||
auto savedWorkspaces = WorkspacesData::WorkspacesProjectJSON::FromJson(savedWorkspacesJson.value());
|
||||
if (savedWorkspaces.has_value())
|
||||
{
|
||||
projectToLaunch = savedWorkspaces.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect temporal Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::critical("Incorrect temporal Workspaces file");
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
Logger::critical("Exception on reading temporal Workspaces file: {}", ex.what());
|
||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempWorkspacesFile());
|
||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (projectToLaunch.id.empty())
|
||||
{
|
||||
try
|
||||
@@ -259,7 +201,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
||||
std::vector<std::pair<std::wstring, std::wstring>> launchErrors{};
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
bool launchedSuccessfully = Launch(projectToLaunch, monitors, launchErrors);
|
||||
|
||||
|
||||
// update last-launched time
|
||||
if (invokePoint != InvokePoint::LaunchAndEdit)
|
||||
{
|
||||
|
||||
@@ -3,22 +3,17 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using ManagedCommon;
|
||||
using Microsoft.VisualBasic.Devices;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Management.Deployment;
|
||||
|
||||
namespace WorkspacesLauncherUI.Models
|
||||
@@ -34,6 +29,8 @@ namespace WorkspacesLauncherUI.Models
|
||||
|
||||
public string AppPath { get; set; }
|
||||
|
||||
public bool Loading => LaunchState == "waiting";
|
||||
|
||||
private Icon _icon;
|
||||
|
||||
public Icon Icon
|
||||
@@ -72,14 +69,23 @@ namespace WorkspacesLauncherUI.Models
|
||||
|
||||
public string LaunchState { get; set; }
|
||||
|
||||
public string StateImageSource
|
||||
public string StateGlyph
|
||||
{
|
||||
get => LaunchState switch
|
||||
{
|
||||
"waiting" => "../images/spinner.gif",
|
||||
"launched" => "../images/checkmark.png",
|
||||
"failed" => "../images/failed.png",
|
||||
_ => "../images/spinner.gif",
|
||||
"launched" => "\U0000F78C",
|
||||
"failed" => "\U0000EF2C",
|
||||
_ => "\U0000EF2C",
|
||||
};
|
||||
}
|
||||
|
||||
public System.Windows.Media.Brush StateColor
|
||||
{
|
||||
get => LaunchState switch
|
||||
{
|
||||
"launched" => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 0, 128, 0)),
|
||||
"failed" => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
|
||||
_ => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace WorkspacesLauncherUI.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your project is launching. Waiting on ....
|
||||
/// Looks up a localized string similar to Your workspace is launching. Waiting on ....
|
||||
/// </summary>
|
||||
public static string LauncherWindowTitle {
|
||||
get {
|
||||
|
||||
@@ -124,6 +124,6 @@
|
||||
<value>Dismiss</value>
|
||||
</data>
|
||||
<data name="LauncherWindowTitle" xml:space="preserve">
|
||||
<value>Your project is launching. Waiting on ...</value>
|
||||
<value>Your workspace is launching. Waiting on ...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2,6 +2,7 @@
|
||||
x:Class="WorkspacesLauncherUI.StatusWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:WorkspacesLauncherUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WorkspacesLauncherUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -21,6 +22,11 @@
|
||||
Topmost="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Window.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
<converters:BooleanToInvertedVisibilityConverter x:Key="BooleanToInvertedVisibilityConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="5" Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1*" />
|
||||
@@ -55,14 +61,28 @@
|
||||
FontWeight="Normal"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="{Binding Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<Image
|
||||
<ui:ProgressRing
|
||||
x:Name="ProgressRing"
|
||||
Grid.Column="2"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Source="{Binding StateImageSource}" />
|
||||
IsActive="True"
|
||||
Visibility="{Binding Loading, Mode=OneWay, Converter={StaticResource BoolToVis}, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
FontSize="20"
|
||||
Foreground="{Binding StateColor}"
|
||||
Text="{Binding StateGlyph}"
|
||||
Visibility="{Binding Loading, Mode=OneWay, Converter={StaticResource BooleanToInvertedVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
@@ -30,10 +30,7 @@
|
||||
<AssemblyName>PowerToys.WorkspacesLauncherUI</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="images\checkmark.png" />
|
||||
<None Remove="images\DefaultIcon.ico" />
|
||||
<None Remove="images\failed.png" />
|
||||
<None Remove="images\spinner.gif" />
|
||||
<None Remove="images\Workspaces.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -87,18 +84,6 @@
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="images\checkmark.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="images\failed.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="images\spinner.gif">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 456 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user