mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-30 00:46:20 +01:00
Compare commits
34 Commits
dev/featur
...
v0.84.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -76,6 +76,7 @@ body:
|
||||
- System tray interaction
|
||||
- TextExtractor
|
||||
- Video Conference Mute
|
||||
- Workspaces
|
||||
- Welcome / PowerToys Tour window
|
||||
validations:
|
||||
required: true
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/translation_issue.yml
vendored
1
.github/ISSUE_TEMPLATE/translation_issue.yml
vendored
@@ -50,6 +50,7 @@ body:
|
||||
- System tray interaction
|
||||
- TextExtractor
|
||||
- Video Conference Mute
|
||||
- Workspaces
|
||||
- Welcome / PowerToys Tour window
|
||||
validations:
|
||||
required: true
|
||||
|
||||
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$
|
||||
|
||||
30
.github/actions/spell-check/expect.txt
vendored
30
.github/actions/spell-check/expect.txt
vendored
@@ -56,11 +56,14 @@ APPBARDATA
|
||||
appdata
|
||||
APPEXECLINK
|
||||
Appium
|
||||
applayout
|
||||
Applicationcan
|
||||
APPLICATIONFRAMEHOST
|
||||
appmanifest
|
||||
APPNAME
|
||||
appref
|
||||
appsettings
|
||||
appsfolder
|
||||
appwindow
|
||||
appwiz
|
||||
APSTUDIO
|
||||
@@ -189,7 +192,6 @@ CLIPBOARDUPDATE
|
||||
CLIPCHILDREN
|
||||
CLIPSIBLINGS
|
||||
closesocket
|
||||
clrcall
|
||||
CLSCTX
|
||||
Clusion
|
||||
cmder
|
||||
@@ -200,8 +202,8 @@ CMINVOKECOMMANDINFO
|
||||
CMINVOKECOMMANDINFOEX
|
||||
CMock
|
||||
CMONITORS
|
||||
cmph
|
||||
cmpgt
|
||||
cmph
|
||||
cne
|
||||
CNF
|
||||
coclass
|
||||
@@ -241,16 +243,14 @@ CONTEXTMENUHANDLER
|
||||
CONTROLL
|
||||
CONTROLPARENT
|
||||
copiedcolorrepresentation
|
||||
COREWINDOW
|
||||
cotaskmem
|
||||
COULDNOT
|
||||
countof
|
||||
cph
|
||||
CPower
|
||||
cppblog
|
||||
cppruntime
|
||||
cppstd
|
||||
cppwinrt
|
||||
CProj
|
||||
createdump
|
||||
CREATESCHEDULEDTASK
|
||||
CREATESTRUCT
|
||||
@@ -605,7 +605,6 @@ hmenu
|
||||
hmodule
|
||||
hmonitor
|
||||
homljgmgpmcbpjbnjpfijnhipfkiclkd
|
||||
HOOKPROC
|
||||
Hostbackdropbrush
|
||||
hotkeycontrol
|
||||
hotkeys
|
||||
@@ -673,7 +672,6 @@ imageresizerinput
|
||||
imageresizersettings
|
||||
imagingdevices
|
||||
ime
|
||||
imperialounce
|
||||
inetcpl
|
||||
Infobar
|
||||
INFOEXAMPLE
|
||||
@@ -838,6 +836,7 @@ lpwcx
|
||||
lpwndpl
|
||||
LReader
|
||||
LRESULT
|
||||
LSTATUS
|
||||
lstrcmp
|
||||
lstrcmpi
|
||||
lstrlen
|
||||
@@ -934,7 +933,6 @@ MRT
|
||||
mru
|
||||
mrw
|
||||
msc
|
||||
msclr
|
||||
mscorlib
|
||||
msdata
|
||||
msedge
|
||||
@@ -1114,6 +1112,7 @@ PATINVERT
|
||||
PATPAINT
|
||||
PAUDIO
|
||||
pbc
|
||||
pbi
|
||||
PBlob
|
||||
pcb
|
||||
pcch
|
||||
@@ -1127,6 +1126,7 @@ pdo
|
||||
pdto
|
||||
pdtobj
|
||||
pdw
|
||||
Peb
|
||||
pef
|
||||
PElems
|
||||
Pels
|
||||
@@ -1210,6 +1210,8 @@ projectname
|
||||
PROPBAG
|
||||
PROPERTYKEY
|
||||
propkey
|
||||
propsys
|
||||
PROPVARIANT
|
||||
propvarutil
|
||||
prvpane
|
||||
psapi
|
||||
@@ -1375,6 +1377,7 @@ sddl
|
||||
SDKDDK
|
||||
sdns
|
||||
searchterm
|
||||
SEARCHUI
|
||||
secpol
|
||||
SENDCHANGE
|
||||
sendinput
|
||||
@@ -1506,7 +1509,6 @@ STATICEDGE
|
||||
STATSTG
|
||||
stdafx
|
||||
STDAPI
|
||||
stdcpp
|
||||
stdcpplatest
|
||||
STDMETHODCALLTYPE
|
||||
STDMETHODIMP
|
||||
@@ -1558,7 +1560,9 @@ SYSKEYUP
|
||||
SYSLIB
|
||||
SYSMENU
|
||||
SYSTEMAPPS
|
||||
systemsettings
|
||||
SYSTEMTIME
|
||||
SYSTEMWOW
|
||||
tapp
|
||||
TApplication
|
||||
TApplied
|
||||
@@ -1670,11 +1674,12 @@ urlmon
|
||||
Usb
|
||||
USEDEFAULT
|
||||
USEFILEATTRIBUTES
|
||||
USEPOSITION
|
||||
USERDATA
|
||||
Userenv
|
||||
USESHOWWINDOW
|
||||
USESIZE
|
||||
USESTDHANDLES
|
||||
usounce
|
||||
USRDLL
|
||||
UType
|
||||
uuidv
|
||||
@@ -1711,6 +1716,7 @@ VIDEOINFOHEADER
|
||||
viewmodel
|
||||
vih
|
||||
VIRTUALDESK
|
||||
VISEGRADRELAY
|
||||
visiblecolorformats
|
||||
Visibletrue
|
||||
visualeffects
|
||||
@@ -1740,6 +1746,7 @@ vswhere
|
||||
Vtbl
|
||||
WANTPALM
|
||||
wbem
|
||||
Wbemidl
|
||||
wbemuuid
|
||||
WBounds
|
||||
Wca
|
||||
@@ -1827,6 +1834,9 @@ WNDCLASSEXW
|
||||
WNDCLASSW
|
||||
WNDPROC
|
||||
workarounds
|
||||
WORKSPACESEDITOR
|
||||
WORKSPACESLAUNCHER
|
||||
WORKSPACESSNAPSHOTTOOL
|
||||
wox
|
||||
wparam
|
||||
wpf
|
||||
|
||||
@@ -189,6 +189,14 @@
|
||||
"WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll",
|
||||
"WinUI3Apps\\PowerRenameContextMenuPackage.msix",
|
||||
|
||||
"PowerToys.WorkspacesSnapshotTool.exe",
|
||||
"PowerToys.WorkspacesLauncher.exe",
|
||||
"PowerToys.WorkspacesEditor.exe",
|
||||
"PowerToys.WorkspacesEditor.dll",
|
||||
"PowerToys.WorkspacesLauncherUI.exe",
|
||||
"PowerToys.WorkspacesLauncherUI.dll",
|
||||
"PowerToys.WorkspacesModuleInterface.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.RegistryPreviewExt.dll",
|
||||
"WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll",
|
||||
"WinUI3Apps\\PowerToys.RegistryPreview.dll",
|
||||
|
||||
@@ -79,7 +79,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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
119
PowerToys.sln
119
PowerToys.sln
@@ -171,14 +171,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\.editorconfig = src\.editorconfig
|
||||
.vsconfig = .vsconfig
|
||||
src\Common.Dotnet.CsWinRT.props = src\Common.Dotnet.CsWinRT.props
|
||||
src\Common.SelfContained.props = src\Common.SelfContained.props
|
||||
Cpp.Build.props = Cpp.Build.props
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
Solution.props = Solution.props
|
||||
src\Version.props = src\Version.props
|
||||
src\Common.SelfContained.props = src\Common.SelfContained.props
|
||||
src\Common.Dotnet.CsWinRT.props = src\Common.Dotnet.CsWinRT.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Settings.UI.Library", "src\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj", "{B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}"
|
||||
@@ -277,6 +277,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
|
||||
src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h
|
||||
src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h
|
||||
src\common\utils\MsWindowsSettings.h = src\common\utils\MsWindowsSettings.h
|
||||
src\common\utils\OnThreadExecutor.h = src\common\utils\OnThreadExecutor.h
|
||||
src\common\utils\os-detect.h = src\common\utils\os-detect.h
|
||||
src\common\utils\package.h = src\common\utils\package.h
|
||||
src\common\utils\ProcessWaiter.h = src\common\utils\ProcessWaiter.h
|
||||
@@ -456,7 +457,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithExt", "src\mod
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLocksmithUI", "src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj", "{E69B044A-2F8A-45AA-AD0B-256C59421807}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLibInterop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.FileLocksmithLib.Interop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPOWrapper", "src\common\GPOWrapper\GPOWrapper.vcxproj", "{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}"
|
||||
EndProject
|
||||
@@ -583,6 +584,37 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings.DSC.Sche
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "src\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workspaces", "Workspaces", "{A2221D7E-55E7-4BEA-90D1-4F162D670BBF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workspaces-common", "workspaces-common", "{BE126CBB-AE12-406A-9837-A05ACFCA57A7}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\modules\Workspaces\workspaces-common\GuidUtils.h = src\modules\Workspaces\workspaces-common\GuidUtils.h
|
||||
src\modules\Workspaces\workspaces-common\InvokePoint.h = src\modules\Workspaces\workspaces-common\InvokePoint.h
|
||||
src\modules\Workspaces\workspaces-common\MonitorEnumerator.h = src\modules\Workspaces\workspaces-common\MonitorEnumerator.h
|
||||
src\modules\Workspaces\workspaces-common\MonitorUtils.h = src\modules\Workspaces\workspaces-common\MonitorUtils.h
|
||||
src\modules\Workspaces\workspaces-common\VirtualDesktop.h = src\modules\Workspaces\workspaces-common\VirtualDesktop.h
|
||||
src\modules\Workspaces\workspaces-common\WindowEnumerator.h = src\modules\Workspaces\workspaces-common\WindowEnumerator.h
|
||||
src\modules\Workspaces\workspaces-common\WindowFilter.h = src\modules\Workspaces\workspaces-common\WindowFilter.h
|
||||
src\modules\Workspaces\workspaces-common\WindowUtils.h = src\modules\Workspaces\workspaces-common\WindowUtils.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowProperties", "WindowProperties", "{14CB58B7-D280-4A7A-95DE-4B2DF14EA000}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h = src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLib", "src\modules\Workspaces\WorkspacesLib\WorkspacesLib.vcxproj", "{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesLauncherUI", "src\modules\Workspaces\WorkspacesLauncherUI\WorkspacesLauncherUI.csproj", "{9C53CC25-0623-4569-95BC-B05410675EE3}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesModuleInterface", "src\modules\Workspaces\WorkspacesModuleInterface\WorkspacesModuleInterface.vcxproj", "{45285DF2-9742-4ECA-9AC9-58951FC26489}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesSnapshotTool", "src\modules\Workspaces\WorkspacesSnapshotTool\WorkspacesSnapshotTool.vcxproj", "{3D63307B-9D27-44FD-B033-B26F39245B85}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesEditor", "src\modules\Workspaces\WorkspacesEditor\WorkspacesEditor.csproj", "{367D7543-7DBA-4381-99F1-BF6142A996C4}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLauncher", "src\modules\Workspaces\WorkspacesLauncher\WorkspacesLauncher.vcxproj", "{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -2601,6 +2633,78 @@ Global
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.Build.0 = Debug|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.Build.0 = Debug|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.ActiveCfg = Release|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.Build.0 = Release|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.ActiveCfg = Release|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.Build.0 = Release|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.Build.0 = Debug|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x86.Build.0 = Debug|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.ActiveCfg = Release|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.Build.0 = Release|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x86.ActiveCfg = Release|x64
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x86.Build.0 = Release|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.Build.0 = Debug|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x86.Build.0 = Debug|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.ActiveCfg = Release|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.Build.0 = Release|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.ActiveCfg = Release|x64
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.Build.0 = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.Build.0 = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.Build.0 = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.ActiveCfg = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.Build.0 = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.ActiveCfg = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.Build.0 = Release|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.Build.0 = Debug|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x86.Build.0 = Debug|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.ActiveCfg = Release|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.Build.0 = Release|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x86.ActiveCfg = Release|x64
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x86.Build.0 = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.Build.0 = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.Build.0 = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.ActiveCfg = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.ActiveCfg = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -2817,6 +2921,15 @@ Global
|
||||
{C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
||||
{1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95}
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
|
||||
{A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{9C53CC25-0623-4569-95BC-B05410675EE3} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{45285DF2-9742-4ECA-9AC9-58951FC26489} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{367D7543-7DBA-4381-99F1-BF6142A996C4} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
BIN
doc/images/icons/Workspaces.png
Normal file
BIN
doc/images/icons/Workspaces.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -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
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<?define AdvancedPasteProjectName="AdvancedPaste"?>
|
||||
<?define RegistryPreviewProjectName="RegistryPreview"?>
|
||||
<?define PeekProjectName="Peek"?>
|
||||
<?define WorkspacesProjectName="Workspaces"?>
|
||||
|
||||
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
|
||||
<?if $(var.Platform) = x64?>
|
||||
|
||||
@@ -449,6 +449,15 @@
|
||||
</RegistryKey>
|
||||
<File Id="PowerOCR_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.PowerOCR.resources.dll" />
|
||||
</Component>
|
||||
<Component
|
||||
Id="WorkspacesEditor_$(var.IdSafeLanguage)_Component"
|
||||
Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER"
|
||||
Guid="$(var.CompGUIDPrefix)21">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="WorkspacesEditor_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<File Id="WorkspacesEditor_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.WorkspacesEditor.resources.dll" />
|
||||
</Component>
|
||||
<?undef IdSafeLanguage?>
|
||||
<?undef CompGUIDPrefix?>
|
||||
<?endforeach?>
|
||||
|
||||
@@ -1223,7 +1223,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
}
|
||||
processes.resize(bytes / sizeof(processes[0]));
|
||||
|
||||
std::array<std::wstring_view, 32> processesToTerminate = {
|
||||
std::array<std::wstring_view, 36> processesToTerminate = {
|
||||
L"PowerToys.PowerLauncher.exe",
|
||||
L"PowerToys.Settings.exe",
|
||||
L"PowerToys.AdvancedPaste.exe",
|
||||
@@ -1255,6 +1255,10 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
L"PowerToys.MouseWithoutBordersService.exe",
|
||||
L"PowerToys.CropAndLock.exe",
|
||||
L"PowerToys.EnvironmentVariables.exe",
|
||||
L"PowerToys.WorkspacesSnapshotTool.exe",
|
||||
L"PowerToys.WorkspacesLauncher.exe",
|
||||
L"PowerToys.WorkspacesLauncherUI.exe",
|
||||
L"PowerToys.WorkspacesEditor.exe",
|
||||
L"PowerToys.exe",
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Common.UI
|
||||
EnvironmentVariables,
|
||||
Dashboard,
|
||||
AdvancedPaste,
|
||||
Workspaces,
|
||||
}
|
||||
|
||||
private static string SettingsWindowNameToString(SettingsWindow value)
|
||||
@@ -77,6 +78,8 @@ namespace Common.UI
|
||||
return "Dashboard";
|
||||
case SettingsWindow.AdvancedPaste:
|
||||
return "AdvancedPaste";
|
||||
case SettingsWindow.Workspaces:
|
||||
return "Workspaces";
|
||||
default:
|
||||
{
|
||||
return string.Empty;
|
||||
|
||||
@@ -24,15 +24,18 @@
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\..\..\;..\..\common;.\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DisplayUtils.h" />
|
||||
<ClInclude Include="MonitorEnumerator.h" />
|
||||
<ClInclude Include="monitors.h" />
|
||||
<ClInclude Include="dpi_aware.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="DisplayUtils.cpp" />
|
||||
<ClCompile Include="monitors.cpp" />
|
||||
<ClCompile Include="dpi_aware.cpp" />
|
||||
</ItemGroup>
|
||||
|
||||
143
src/common/Display/DisplayUtils.cpp
Normal file
143
src/common/Display/DisplayUtils.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "DisplayUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cwctype>
|
||||
#include <iterator>
|
||||
|
||||
#include <dpi_aware.h>
|
||||
#include <MonitorEnumerator.h>
|
||||
|
||||
#include <utils/OnThreadExecutor.h>
|
||||
|
||||
namespace DisplayUtils
|
||||
{
|
||||
std::wstring remove_non_digits(const std::wstring& input)
|
||||
{
|
||||
std::wstring result;
|
||||
std::copy_if(input.begin(), input.end(), std::back_inserter(result), [](wchar_t ch) { return std::iswdigit(ch); });
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<std::wstring, std::wstring> SplitDisplayDeviceId(const std::wstring& str) noexcept
|
||||
{
|
||||
// format: \\?\DISPLAY#{device id}#{instance id}#{some other id}
|
||||
// example: \\?\DISPLAY#GSM1388#4&125707d6&0&UID8388688#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||
// output: { GSM1388, 4&125707d6&0&UID8388688 }
|
||||
|
||||
size_t nameStartPos = str.find_first_of('#');
|
||||
size_t uidStartPos = str.find('#', nameStartPos + 1);
|
||||
size_t uidEndPos = str.find('#', uidStartPos + 1);
|
||||
|
||||
if (nameStartPos == std::string::npos || uidStartPos == std::string::npos || uidEndPos == std::string::npos)
|
||||
{
|
||||
return { str, L"" };
|
||||
}
|
||||
|
||||
return { str.substr(nameStartPos + 1, uidStartPos - nameStartPos - 1), str.substr(uidStartPos + 1, uidEndPos - uidStartPos - 1) };
|
||||
}
|
||||
|
||||
std::pair<bool, std::vector<DisplayUtils::DisplayData>> GetDisplays()
|
||||
{
|
||||
bool success = true;
|
||||
std::vector<DisplayUtils::DisplayData> result{};
|
||||
auto allMonitors = MonitorEnumerator::Enumerate();
|
||||
|
||||
OnThreadExecutor dpiUnawareThread;
|
||||
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
|
||||
} }).wait();
|
||||
|
||||
for (auto& monitorData : allMonitors)
|
||||
{
|
||||
MONITORINFOEX monitorInfo = monitorData.second;
|
||||
MONITORINFOEX dpiUnawareMonitorInfo{};
|
||||
|
||||
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||
dpiUnawareMonitorInfo.cbSize = sizeof(dpiUnawareMonitorInfo);
|
||||
if (!GetMonitorInfo(monitorData.first, &dpiUnawareMonitorInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} }).wait();
|
||||
|
||||
UINT dpi = 0;
|
||||
if (DPIAware::GetScreenDPIForMonitor(monitorData.first, dpi) != S_OK)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
DisplayUtils::DisplayData data{
|
||||
.monitor = monitorData.first,
|
||||
.dpi = dpi,
|
||||
.monitorRectDpiAware = monitorInfo.rcMonitor,
|
||||
.monitorRectDpiUnaware = dpiUnawareMonitorInfo.rcMonitor,
|
||||
};
|
||||
|
||||
bool foundActiveMonitor = false;
|
||||
DISPLAY_DEVICE displayDevice{ .cb = sizeof(displayDevice) };
|
||||
DWORD displayDeviceIndex = 0;
|
||||
while (EnumDisplayDevicesW(monitorInfo.szDevice, displayDeviceIndex, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME))
|
||||
{
|
||||
/*
|
||||
* if (WI_IsFlagSet(displayDevice.StateFlags, DISPLAY_DEVICE_ACTIVE) &&
|
||||
WI_IsFlagClear(displayDevice.StateFlags, DISPLAY_DEVICE_MIRRORING_DRIVER))
|
||||
*/
|
||||
if (((displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE) == DISPLAY_DEVICE_ACTIVE) &&
|
||||
(displayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0)
|
||||
{
|
||||
// Find display devices associated with the display.
|
||||
foundActiveMonitor = true;
|
||||
break;
|
||||
}
|
||||
|
||||
displayDeviceIndex++;
|
||||
}
|
||||
|
||||
if (foundActiveMonitor)
|
||||
{
|
||||
auto deviceId = SplitDisplayDeviceId(displayDevice.DeviceID);
|
||||
data.id = deviceId.first;
|
||||
data.instanceId = deviceId.second;
|
||||
try
|
||||
{
|
||||
std::wstring numberStr = displayDevice.DeviceName; // \\.\DISPLAY1\Monitor0
|
||||
numberStr = numberStr.substr(0, numberStr.find_last_of('\\')); // \\.\DISPLAY1
|
||||
numberStr = remove_non_digits(numberStr);
|
||||
data.number = std::stoi(numberStr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
|
||||
// Use the display name as a fallback value when no proper device was found.
|
||||
data.id = monitorInfo.szDevice;
|
||||
data.instanceId = L"";
|
||||
|
||||
try
|
||||
{
|
||||
std::wstring numberStr = monitorInfo.szDevice; // \\.\DISPLAY1
|
||||
numberStr = remove_non_digits(numberStr);
|
||||
data.number = std::stoi(numberStr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(data);
|
||||
}
|
||||
|
||||
return { success, result };
|
||||
}
|
||||
|
||||
}
|
||||
21
src/common/Display/DisplayUtils.h
Normal file
21
src/common/Display/DisplayUtils.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace DisplayUtils
|
||||
{
|
||||
struct DisplayData
|
||||
{
|
||||
HMONITOR monitor{};
|
||||
std::wstring id;
|
||||
std::wstring instanceId;
|
||||
unsigned int number{};
|
||||
unsigned int dpi{};
|
||||
RECT monitorRectDpiAware{};
|
||||
RECT monitorRectDpiUnaware{};
|
||||
};
|
||||
|
||||
std::pair<bool, std::vector<DisplayData>> GetDisplays();
|
||||
};
|
||||
35
src/common/Display/MonitorEnumerator.h
Normal file
35
src/common/Display/MonitorEnumerator.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <Windows.h>
|
||||
|
||||
class MonitorEnumerator
|
||||
{
|
||||
public:
|
||||
static std::vector<std::pair<HMONITOR, MONITORINFOEX>> Enumerate()
|
||||
{
|
||||
MonitorEnumerator inst;
|
||||
EnumDisplayMonitors(NULL, NULL, Callback, reinterpret_cast<LPARAM>(&inst));
|
||||
return inst.m_monitors;
|
||||
}
|
||||
|
||||
private:
|
||||
MonitorEnumerator() = default;
|
||||
~MonitorEnumerator() = default;
|
||||
|
||||
static BOOL CALLBACK Callback(HMONITOR monitor, HDC /*hdc*/, LPRECT /*pRect*/, LPARAM param)
|
||||
{
|
||||
MonitorEnumerator* inst = reinterpret_cast<MonitorEnumerator*>(param);
|
||||
MONITORINFOEX mi;
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfo(monitor, &mi))
|
||||
{
|
||||
inst->m_monitors.push_back({monitor, mi});
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::vector<std::pair<HMONITOR, MONITORINFOEX>> m_monitors;
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "dpi_aware.h"
|
||||
|
||||
#include "monitors.h"
|
||||
#include <ShellScalingApi.h>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace DPIAware
|
||||
{
|
||||
@@ -60,6 +62,24 @@ namespace DPIAware
|
||||
}
|
||||
}
|
||||
|
||||
void Convert(HMONITOR monitor_handle, RECT& rect)
|
||||
{
|
||||
if (monitor_handle == NULL)
|
||||
{
|
||||
const POINT ptZero = { 0, 0 };
|
||||
monitor_handle = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
|
||||
}
|
||||
|
||||
UINT dpi_x, dpi_y;
|
||||
if (GetDpiForMonitor(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y) == S_OK)
|
||||
{
|
||||
rect.left = static_cast<long>(std::round(rect.left * static_cast<float>(dpi_x) / DEFAULT_DPI));
|
||||
rect.right = static_cast<long>(std::round(rect.right * static_cast<float>(dpi_x) / DEFAULT_DPI));
|
||||
rect.top = static_cast<long>(std::round(rect.top * static_cast<float>(dpi_y) / DEFAULT_DPI));
|
||||
rect.bottom = static_cast<long>(std::round(rect.bottom * static_cast<float>(dpi_y) / DEFAULT_DPI));
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertByCursorPosition(float& width, float& height)
|
||||
{
|
||||
HMONITOR targetMonitor = nullptr;
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace DPIAware
|
||||
HRESULT GetScreenDPIForPoint(POINT p, UINT& dpi);
|
||||
HRESULT GetScreenDPIForCursor(UINT& dpi);
|
||||
void Convert(HMONITOR monitor_handle, float& width, float& height);
|
||||
void Convert(HMONITOR monitor_handle, RECT& rect);
|
||||
void ConvertByCursorPosition(float& width, float& height);
|
||||
void InverseConvert(HMONITOR monitor_handle, float& width, float& height);
|
||||
void EnableDPIAwarenessForThisProcess();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "pch.h"
|
||||
#include "pch.h"
|
||||
#include "GPOWrapper.h"
|
||||
#include "GPOWrapper.g.cpp"
|
||||
|
||||
@@ -176,6 +176,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredWorkspacesEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredWorkspacesEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredMwbClipboardSharingEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "GPOWrapper.g.h"
|
||||
#include <common/utils/gpo.h>
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
||||
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
||||
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
||||
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
||||
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
|
||||
|
||||
@@ -61,5 +61,10 @@ namespace PowerToys.GPOWrapperProjection
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetRunPluginEnabledValue(pluginID);
|
||||
}
|
||||
|
||||
public static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue()
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,5 +30,6 @@ namespace ManagedCommon
|
||||
MeasureTool,
|
||||
ShortcutGuide,
|
||||
PowerOCR,
|
||||
Workspaces,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,17 +51,21 @@ 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::AdvancedPasteCustomActionMessage()
|
||||
{
|
||||
return CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE;
|
||||
}
|
||||
hstring Constants::ShowPowerOCRSharedEvent()
|
||||
{
|
||||
@@ -143,4 +147,12 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT;
|
||||
}
|
||||
hstring Constants::WorkspacesLaunchEditorEvent()
|
||||
{
|
||||
return CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT;
|
||||
}
|
||||
hstring Constants::WorkspacesHotkeyEvent()
|
||||
{
|
||||
return CommonSharedConstants::WORKSPACES_HOTKEY_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@ 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 AdvancedPasteCustomActionMessage();
|
||||
static hstring ShowPowerOCRSharedEvent();
|
||||
static hstring MouseJumpShowPreviewEvent();
|
||||
static hstring AwakeExitEvent();
|
||||
@@ -39,6 +40,8 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring CropAndLockReparentEvent();
|
||||
static hstring ShowEnvironmentVariablesSharedEvent();
|
||||
static hstring ShowEnvironmentVariablesAdminSharedEvent();
|
||||
static hstring WorkspacesLaunchEditorEvent();
|
||||
static hstring WorkspacesHotkeyEvent();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@ 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 AdvancedPasteCustomActionMessage();
|
||||
static String ShowPowerOCRSharedEvent();
|
||||
static String MouseJumpShowPreviewEvent();
|
||||
static String AwakeExitEvent();
|
||||
@@ -36,6 +37,8 @@ namespace PowerToys
|
||||
static String CropAndLockReparentEvent();
|
||||
static String ShowEnvironmentVariablesSharedEvent();
|
||||
static String ShowEnvironmentVariablesAdminSharedEvent();
|
||||
static String WorkspacesLaunchEditorEvent();
|
||||
static String WorkspacesHotkeyEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,14 @@ 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_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,6 +43,10 @@ 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";
|
||||
|
||||
const wchar_t SHOW_HOSTS_ADMIN_EVENT[] = L"Local\\Hosts-ShowHostsAdminEvent-60ff44e2-efd3-43bf-928a-f4d269f98bec";
|
||||
|
||||
@@ -69,6 +69,10 @@ struct LogSettings
|
||||
inline const static std::string environmentVariablesLoggerName = "environment-variables";
|
||||
inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt";
|
||||
inline const static std::string cmdNotFoundLoggerName = "cmd-not-found";
|
||||
inline const static std::string workspacesLauncherLoggerName = "workspaces-launcher";
|
||||
inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.txt";
|
||||
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
|
||||
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.txt";
|
||||
inline const static int retention = 30;
|
||||
std::wstring logLevel;
|
||||
LogSettings();
|
||||
|
||||
72
src/common/utils/OnThreadExecutor.h
Normal file
72
src/common/utils/OnThreadExecutor.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
|
||||
// OnThreadExecutor allows its caller to off-load some work to a persistently running background thread.
|
||||
// This might come in handy if you use the API which sets thread-wide global state and the state needs
|
||||
// to be isolated.
|
||||
|
||||
class OnThreadExecutor final
|
||||
{
|
||||
public:
|
||||
using task_t = std::packaged_task<void()>;
|
||||
|
||||
OnThreadExecutor() :
|
||||
_shutdown_request{ false },
|
||||
_worker_thread{ [this] { worker_thread(); } }
|
||||
{
|
||||
}
|
||||
|
||||
~OnThreadExecutor()
|
||||
{
|
||||
_shutdown_request = true;
|
||||
_task_cv.notify_one();
|
||||
_worker_thread.join();
|
||||
}
|
||||
|
||||
std::future<void> submit(task_t task)
|
||||
{
|
||||
auto future = task.get_future();
|
||||
std::lock_guard lock{ _task_mutex };
|
||||
_task_queue.emplace(std::move(task));
|
||||
_task_cv.notify_one();
|
||||
return future;
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
std::lock_guard lock{ _task_mutex };
|
||||
_task_queue = {};
|
||||
_task_cv.notify_one();
|
||||
}
|
||||
|
||||
private:
|
||||
void worker_thread()
|
||||
{
|
||||
while (!_shutdown_request)
|
||||
{
|
||||
task_t task;
|
||||
{
|
||||
std::unique_lock task_lock{ _task_mutex };
|
||||
_task_cv.wait(task_lock, [this] { return !_task_queue.empty() || _shutdown_request; });
|
||||
if (_shutdown_request)
|
||||
{
|
||||
return;
|
||||
}
|
||||
task = std::move(_task_queue.front());
|
||||
_task_queue.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex _task_mutex;
|
||||
std::condition_variable _task_cv;
|
||||
std::atomic_bool _shutdown_request;
|
||||
std::queue<std::packaged_task<void()>> _task_queue;
|
||||
std::thread _worker_thread;
|
||||
};
|
||||
@@ -60,6 +60,7 @@ namespace powertoys_gpo {
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_WORKSPACES = L"ConfigureEnabledUtilityWorkspaces";
|
||||
|
||||
// The registry value names for PowerToys installer and update policies.
|
||||
const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled";
|
||||
@@ -399,6 +400,11 @@ namespace powertoys_gpo {
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredWorkspacesEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_WORKSPACES);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredVideoConferenceMuteEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE);
|
||||
|
||||
@@ -57,6 +57,8 @@ properties:
|
||||
EnableQoiThumbnail: false
|
||||
PowerOcr:
|
||||
Enabled: false
|
||||
Workspaces:
|
||||
Enabled: false
|
||||
ShortcutGuide:
|
||||
Enabled: false
|
||||
VideoConference:
|
||||
|
||||
@@ -57,6 +57,8 @@ properties:
|
||||
EnableQoiThumbnail: true
|
||||
PowerOcr:
|
||||
Enabled: true
|
||||
Workspaces:
|
||||
Enabled: true
|
||||
ShortcutGuide:
|
||||
Enabled: true
|
||||
VideoConference:
|
||||
|
||||
@@ -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,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License. -->
|
||||
<policyDefinitions 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">
|
||||
<policyDefinitions 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">
|
||||
<policyNamespaces>
|
||||
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
|
||||
</policyNamespaces>
|
||||
<resources minRequiredRevision="1.11"/><!-- Last changed with PowerToys v0.83.0 -->
|
||||
<resources minRequiredRevision="1.12"/><!-- Last changed with PowerToys v0.84.0 -->
|
||||
<supportedOn>
|
||||
<definitions>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
|
||||
@@ -20,6 +20,7 @@
|
||||
<definition name="SUPPORTED_POWERTOYS_0_81_0" displayName="$(string.SUPPORTED_POWERTOYS_0_81_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_81_1" displayName="$(string.SUPPORTED_POWERTOYS_0_81_1)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_83_0" displayName="$(string.SUPPORTED_POWERTOYS_0_83_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_84_0" displayName="$(string.SUPPORTED_POWERTOYS_0_84_0)"/>
|
||||
</definitions>
|
||||
</supportedOn>
|
||||
<categories>
|
||||
@@ -364,6 +365,16 @@
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityWorkspaces" class="Both" displayName="$(string.ConfigureEnabledUtilityWorkspaces)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityWorkspaces">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_84_0" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityQuickAccent" class="Both" displayName="$(string.ConfigureEnabledUtilityQuickAccent)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityQuickAccent">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />
|
||||
|
||||
@@ -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>
|
||||
@@ -25,6 +25,7 @@
|
||||
<string id="SUPPORTED_POWERTOYS_0_81_0">PowerToys version 0.81.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_81_1">PowerToys version 0.81.1 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_83_0">PowerToys version 0.83.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_84_0">PowerToys version 0.84.0 or later</string>
|
||||
|
||||
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
|
||||
|
||||
@@ -219,6 +220,7 @@ If you disable or don't configure this policy, no predefined rules are applied.
|
||||
<string id="ConfigureEnabledUtilityPeek">Peek: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityPowerRename">Power Rename: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityPowerLauncher">PowerToys Run: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityWorkspaces">PowerToys Workspaces: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityQuickAccent">Quick Accent: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityRegistryPreview">Registry Preview: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityScreenRuler">Screen Ruler: Configure enabled state</string>
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Settings;
|
||||
using AdvancedPaste.ViewModels;
|
||||
@@ -14,6 +18,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 +31,13 @@ namespace AdvancedPaste
|
||||
{
|
||||
public IHost Host { get; private set; }
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly OptionsViewModel viewModel;
|
||||
|
||||
private MainWindow window;
|
||||
|
||||
private nint windowHwnd;
|
||||
|
||||
private OptionsViewModel viewModel;
|
||||
|
||||
private bool disposedValue;
|
||||
|
||||
/// <summary>
|
||||
@@ -74,7 +80,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 +94,44 @@ 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(() => OnNamedPipeMessage(message));
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var connectTimeout = TimeSpan.FromSeconds(10);
|
||||
await NamedPipeProcessor.ProcessNamedPipeAsync(pipeName, connectTimeout, OnMessage, CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnNamedPipeMessage(string message)
|
||||
{
|
||||
var messageParts = message.Split();
|
||||
var messageType = messageParts.First();
|
||||
|
||||
if (messageType == PowerToys.Interop.Constants.AdvancedPasteShowUIMessage())
|
||||
{
|
||||
OnAdvancedPasteHotkey();
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteMarkdownMessage())
|
||||
{
|
||||
OnAdvancedPasteMarkdownHotkey();
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteJsonMessage())
|
||||
{
|
||||
OnAdvancedPasteJsonHotkey();
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteCustomActionMessage())
|
||||
{
|
||||
OnAdvancedPasteCustomActionHotkey(messageParts);
|
||||
}
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
@@ -100,17 +141,43 @@ namespace AdvancedPaste
|
||||
|
||||
private void OnAdvancedPasteJsonHotkey()
|
||||
{
|
||||
viewModel.GetClipboardData();
|
||||
viewModel.ReadClipboard();
|
||||
viewModel.ToJsonFunction(true);
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteMarkdownHotkey()
|
||||
{
|
||||
viewModel.GetClipboardData();
|
||||
viewModel.ReadClipboard();
|
||||
viewModel.ToMarkdownFunction(true);
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteHotkey()
|
||||
{
|
||||
ShowWindow();
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteCustomActionHotkey(string[] messageParts)
|
||||
{
|
||||
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
|
||||
{
|
||||
ShowWindow();
|
||||
viewModel.ReadClipboard();
|
||||
viewModel.ExecuteCustomActionWithPaste(customActionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowWindow()
|
||||
{
|
||||
viewModel.OnShow();
|
||||
|
||||
|
||||
@@ -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,7 +324,12 @@
|
||||
</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">
|
||||
@@ -340,13 +346,12 @@
|
||||
x:Name="InputTxtBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:FieldModifier="public"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, 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.GeneralErrorText}" />
|
||||
</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,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;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -18,23 +19,14 @@ namespace AdvancedPaste.Controls
|
||||
{
|
||||
public sealed partial class PromptBox : Microsoft.UI.Xaml.Controls.UserControl
|
||||
{
|
||||
// Minimum time to show spinner when generating custom format using forcePasteCustom
|
||||
private static readonly TimeSpan MinTaskTime = TimeSpan.FromSeconds(2);
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
public 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),
|
||||
@@ -66,6 +58,7 @@ namespace AdvancedPaste.Controls
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
|
||||
ViewModel = App.GetService<OptionsViewModel>();
|
||||
ViewModel.CustomActionActivated += (_, e) => GenerateCustom(e.ForcePasteCustom);
|
||||
}
|
||||
|
||||
private void Grid_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -74,27 +67,30 @@ namespace AdvancedPaste.Controls
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void GenerateCustom()
|
||||
private void GenerateCustom() => GenerateCustom(false);
|
||||
|
||||
private void GenerateCustom(bool forcePasteCustom)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
VisualStateManager.GoToState(this, "LoadingState", true);
|
||||
string inputInstructions = InputTxtBox.Text;
|
||||
string inputInstructions = ViewModel.Query;
|
||||
ViewModel.SaveQuery(inputInstructions);
|
||||
|
||||
var customFormatTask = ViewModel.GenerateCustomFunction(inputInstructions);
|
||||
|
||||
customFormatTask.ContinueWith(
|
||||
t =>
|
||||
var delayTask = forcePasteCustom ? Task.Delay(MinTaskTime) : Task.CompletedTask;
|
||||
Task.WhenAll(customFormatTask, delayTask)
|
||||
.ContinueWith(
|
||||
_ =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
ViewModel.CustomFormatResult = t.Result;
|
||||
ViewModel.CustomFormatResult = customFormatTask.Result;
|
||||
|
||||
if (ViewModel.ApiRequestStatus == (int)HttpStatusCode.OK)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "DefaultState", true);
|
||||
if (_userSettings.ShowCustomPreview)
|
||||
if (_userSettings.ShowCustomPreview && !forcePasteCustom)
|
||||
{
|
||||
PreviewGrid.Width = InputTxtBox.ActualWidth;
|
||||
PreviewFlyout.ShowAt(InputTxtBox);
|
||||
@@ -130,14 +126,9 @@ namespace AdvancedPaste.Controls
|
||||
ClipboardHelper.SetClipboardTextContent(lastQuery.ClipboardData);
|
||||
}
|
||||
|
||||
private void InputTxtBox_TextChanging(Microsoft.UI.Xaml.Controls.TextBox sender, TextBoxTextChangingEventArgs args)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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 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 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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,18 @@ namespace AdvancedPaste
|
||||
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
|
||||
var baseHeight = MinHeight;
|
||||
|
||||
void UpdateHeight()
|
||||
{
|
||||
var trimmedCustomActionCount = Math.Min(_userSettings.CustomActions.Count, 5);
|
||||
Height = MinHeight = baseHeight + (trimmedCustomActionCount * 40);
|
||||
}
|
||||
|
||||
UpdateHeight();
|
||||
|
||||
_userSettings.CustomActions.CollectionChanged += (_, _) => UpdateHeight();
|
||||
|
||||
AppWindow.SetIcon("Assets/AdvancedPaste/AdvancedPaste.ico");
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(titleBar);
|
||||
|
||||
@@ -5,14 +5,21 @@
|
||||
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:CountToDoubleConverter
|
||||
x:Name="customActionsCountToMinHeightConverter"
|
||||
ValueIfNonZero="40"
|
||||
ValueIfZero="0" />
|
||||
<Style
|
||||
x:Key="PaddingLessFlyoutPresenterStyle"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
||||
@@ -21,6 +28,38 @@
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<DataTemplate x:Key="PasteFormatTemplate" x:DataType="local:PasteFormat">
|
||||
<Grid>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="{x:Bind ToolTip}" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="26" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Margin="0,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="16"
|
||||
Glyph="{x:Bind IconGlyph}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
x:Phase="1"
|
||||
Text="{x:Bind Name}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="0,0,8,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ShortcutText, Mode=OneWay}"
|
||||
Visibility="{x:Bind ShortcutText.Length, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
<Page.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked" />
|
||||
@@ -36,6 +75,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>
|
||||
@@ -103,73 +166,55 @@
|
||||
BorderThickness="0,1,0,0"
|
||||
RowSpacing="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsCountToMinHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ListView
|
||||
x:Name="PasteOptionsListView"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, Mode=OneWay}"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PasteOptionsListView_ItemClick"
|
||||
ItemClick="ListView_Click"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemsSource="{x:Bind pasteFormats, Mode=OneWay}"
|
||||
ItemTemplate="{StaticResource PasteFormatTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
|
||||
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>
|
||||
TabIndex="1" />
|
||||
|
||||
<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"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_Click"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemTemplate="{StaticResource PasteFormatTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Auto"
|
||||
SelectionMode="None"
|
||||
TabIndex="2" />
|
||||
|
||||
<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()
|
||||
{
|
||||
ViewModel.ToPlainTextFunction();
|
||||
}
|
||||
|
||||
private void PasteAsMarkdown()
|
||||
{
|
||||
ViewModel.ToMarkdownFunction();
|
||||
}
|
||||
|
||||
private void PasteAsJson()
|
||||
{
|
||||
ViewModel.ToJsonFunction();
|
||||
}
|
||||
|
||||
private void PasteOptionsListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
private void ListView_Click(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is 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;
|
||||
}
|
||||
}
|
||||
ViewModel.ExecutePasteFormat(format);
|
||||
}
|
||||
}
|
||||
|
||||
private 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:
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace AdvancedPaste.Settings
|
||||
{
|
||||
public interface IUserSettings
|
||||
@@ -11,5 +14,7 @@ namespace AdvancedPaste.Settings
|
||||
public bool SendPasteKeyCombination { get; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
|
||||
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,37 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
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 const string AdvancedPasteModuleName = "AdvancedPaste";
|
||||
private const int MaxNumberOfRetry = 5;
|
||||
|
||||
private bool _disposedValue;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public bool ShowCustomPreview { get; private set; }
|
||||
|
||||
public bool SendPasteKeyCombination { get; private set; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; private set; }
|
||||
|
||||
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; private set; }
|
||||
|
||||
public UserSettings()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
@@ -33,10 +41,25 @@ namespace AdvancedPaste.Settings
|
||||
ShowCustomPreview = true;
|
||||
SendPasteKeyCombination = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
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 +85,25 @@ 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()
|
||||
{
|
||||
ShowCustomPreview = settings.Properties.ShowCustomPreview;
|
||||
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
|
||||
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
|
||||
|
||||
CustomActions.Clear();
|
||||
foreach (var customAction in settings.Properties.CustomActions.Value)
|
||||
{
|
||||
if (customAction.IsShown && customAction.IsValid)
|
||||
{
|
||||
CustomActions.Add(customAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task.Factory
|
||||
.StartNew(UpdateSettings, CancellationToken.None, TaskCreationOptions.None, _taskScheduler)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
retry = false;
|
||||
@@ -82,5 +121,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,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 forcePasteCustom) : EventArgs
|
||||
{
|
||||
public string Text { get; private set; } = text;
|
||||
|
||||
public bool ForcePasteCustom { get; private set; } = forcePasteCustom;
|
||||
}
|
||||
@@ -2,16 +2,38 @@
|
||||
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace AdvancedPaste.Models
|
||||
namespace AdvancedPaste.Models;
|
||||
|
||||
public partial class PasteFormat : ObservableObject
|
||||
{
|
||||
public class PasteFormat
|
||||
[ObservableProperty]
|
||||
private string _shortcutText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _toolTip = string.Empty;
|
||||
|
||||
public PasteFormat()
|
||||
{
|
||||
public IconElement Icon { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public PasteFormats Format { get; set; }
|
||||
}
|
||||
|
||||
public PasteFormat(AdvancedPasteCustomAction customAction, string shortcutText)
|
||||
{
|
||||
IconGlyph = "\uE945";
|
||||
Name = customAction.Name;
|
||||
Prompt = customAction.Prompt;
|
||||
Format = PasteFormats.Custom;
|
||||
ShortcutText = shortcutText;
|
||||
ToolTip = customAction.Prompt;
|
||||
}
|
||||
|
||||
public string IconGlyph { get; init; }
|
||||
|
||||
public string Name { get; init; }
|
||||
|
||||
public PasteFormats Format { get; init; }
|
||||
|
||||
public string Prompt { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -228,4 +225,7 @@
|
||||
<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>
|
||||
</root>
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -15,49 +16,73 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
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 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 AICompletionsHelper aiHelper;
|
||||
private readonly App app = App.Current as App;
|
||||
private readonly PasteFormat[] _allStandardPasteFormats;
|
||||
|
||||
public DataPackageView ClipboardData { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
|
||||
private bool _isClipboardDataText;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
private bool _isCustomAIEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _clipboardHistoryEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxErrorText))]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
|
||||
private bool _isAllowedByGPO;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ApiErrorText))]
|
||||
private int _apiRequestStatus;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _query = string.Empty;
|
||||
|
||||
private bool _pasteFormatsDirty;
|
||||
|
||||
public ObservableCollection<PasteFormat> StandardPasteFormats { get; } = [];
|
||||
|
||||
public ObservableCollection<PasteFormat> CustomActionPasteFormats { get; } = [];
|
||||
|
||||
public bool IsCustomAIEnabled => IsAllowedByGPO && IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
|
||||
public event EventHandler<CustomActionActivatedEventArgs> CustomActionActivated;
|
||||
|
||||
public OptionsViewModel(IUserSettings userSettings)
|
||||
{
|
||||
aiHelper = new AICompletionsHelper();
|
||||
_userSettings = userSettings;
|
||||
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
|
||||
ApiRequestStatus = (int)HttpStatusCode.OK;
|
||||
|
||||
_allStandardPasteFormats =
|
||||
[
|
||||
new PasteFormat { IconGlyph = "\uE8E9", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText },
|
||||
new PasteFormat { IconGlyph = "\ue8a5", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown },
|
||||
new PasteFormat { IconGlyph = "\uE943", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json },
|
||||
];
|
||||
|
||||
GeneratedResponses = new ObservableCollection<string>();
|
||||
GeneratedResponses.CollectionChanged += (s, e) =>
|
||||
{
|
||||
@@ -66,10 +91,87 @@ namespace AdvancedPaste.ViewModels
|
||||
};
|
||||
|
||||
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
|
||||
GetClipboardData();
|
||||
ReadClipboard();
|
||||
_clipboardTimer = new() { Interval = TimeSpan.FromSeconds(1) };
|
||||
_clipboardTimer.Tick += ClipboardTimer_Tick;
|
||||
_clipboardTimer.Start();
|
||||
|
||||
RefreshPasteFormats();
|
||||
_userSettings.CustomActions.CollectionChanged += (_, _) => EnqueueRefreshPasteFormats();
|
||||
PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Query))
|
||||
{
|
||||
EnqueueRefreshPasteFormats();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void GetClipboardData()
|
||||
private void ClipboardTimer_Tick(object sender, object e)
|
||||
{
|
||||
if (app.GetMainWindow()?.Visible is true)
|
||||
{
|
||||
ReadClipboard();
|
||||
UpdateAllowedByGPO();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnqueueRefreshPasteFormats()
|
||||
{
|
||||
if (_pasteFormatsDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pasteFormatsDirty = true;
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
RefreshPasteFormats();
|
||||
_pasteFormatsDirty = false;
|
||||
});
|
||||
}
|
||||
|
||||
private void RefreshPasteFormats()
|
||||
{
|
||||
bool Filter(string text) => text.Contains(Query, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
var ctrlString = ResourceLoaderInstance.ResourceLoader.GetString("CtrlKey");
|
||||
int shortcutNum = 0;
|
||||
|
||||
string GetNextShortcutText()
|
||||
{
|
||||
shortcutNum++;
|
||||
return shortcutNum <= 9 ? $"{ctrlString}+{shortcutNum}" : string.Empty;
|
||||
}
|
||||
|
||||
StandardPasteFormats.Clear();
|
||||
foreach (var format in _allStandardPasteFormats)
|
||||
{
|
||||
if (Filter(format.Name))
|
||||
{
|
||||
format.ShortcutText = GetNextShortcutText();
|
||||
format.ToolTip = $"{format.Name} ({format.ShortcutText})";
|
||||
StandardPasteFormats.Add(format);
|
||||
}
|
||||
}
|
||||
|
||||
CustomActionPasteFormats.Clear();
|
||||
foreach (var customAction in _userSettings.CustomActions)
|
||||
{
|
||||
if (Filter(customAction.Name) || Filter(customAction.Prompt))
|
||||
{
|
||||
CustomActionPasteFormats.Add(new PasteFormat(customAction, GetNextShortcutText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clipboardTimer.Stop();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void ReadClipboard()
|
||||
{
|
||||
ClipboardData = Clipboard.GetContent();
|
||||
IsClipboardDataText = ClipboardData.Contains(StandardDataFormats.Text);
|
||||
@@ -77,14 +179,10 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
public void OnShow()
|
||||
{
|
||||
GetClipboardData();
|
||||
ReadClipboard();
|
||||
UpdateAllowedByGPO();
|
||||
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
IsCustomAIEnabled = false;
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
}
|
||||
else
|
||||
if (IsAllowedByGPO)
|
||||
{
|
||||
var openAIKey = AICompletionsHelper.LoadOpenAIKey();
|
||||
var currentKey = aiHelper.GetKey();
|
||||
@@ -104,15 +202,12 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled);
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
OnPropertyChanged(nameof(GeneralErrorText));
|
||||
OnPropertyChanged(nameof(IsCustomAIEnabled));
|
||||
});
|
||||
},
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
|
||||
@@ -152,47 +247,44 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
app.GetMainWindow().ClearInputText();
|
||||
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
|
||||
}
|
||||
else if (!aiHelper.IsAIEnabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
|
||||
}
|
||||
else if (!IsClipboardDataText)
|
||||
return IsClipboardDataText ? ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText") : GeneralErrorText;
|
||||
}
|
||||
}
|
||||
|
||||
public string GeneralErrorText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsClipboardDataText)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataTypeMismatchWarning");
|
||||
}
|
||||
|
||||
if (!IsAllowedByGPO)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
|
||||
}
|
||||
|
||||
if (!aiHelper.IsAIEnabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string InputTxtBoxErrorText
|
||||
public string ApiErrorText
|
||||
{
|
||||
get
|
||||
get => (HttpStatusCode)ApiRequestStatus switch
|
||||
{
|
||||
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;
|
||||
}
|
||||
HttpStatusCode.TooManyRequests => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests"),
|
||||
HttpStatusCode.Unauthorized => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized"),
|
||||
HttpStatusCode.OK => string.Empty,
|
||||
_ => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + ApiRequestStatus.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -201,7 +293,12 @@ namespace AdvancedPaste.ViewModels
|
||||
[RelayCommand]
|
||||
public void PasteCustom()
|
||||
{
|
||||
PasteCustomFunction(GeneratedResponses[CurrentResponseIndex]);
|
||||
var text = GeneratedResponses.ElementAtOrDefault(CurrentResponseIndex);
|
||||
|
||||
if (text != null)
|
||||
{
|
||||
PasteCustomFunction(text);
|
||||
}
|
||||
}
|
||||
|
||||
// Command to select the previous custom format
|
||||
@@ -306,6 +403,59 @@ namespace AdvancedPaste.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecutePasteFormat(VirtualKey key)
|
||||
{
|
||||
var index = key - VirtualKey.Number1;
|
||||
var pasteFormat = StandardPasteFormats.ElementAtOrDefault(index) ?? CustomActionPasteFormats.ElementAtOrDefault(index - StandardPasteFormats.Count);
|
||||
|
||||
if (pasteFormat != null)
|
||||
{
|
||||
ExecutePasteFormat(pasteFormat);
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(pasteFormat.Format));
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecutePasteFormat(PasteFormat pasteFormat)
|
||||
{
|
||||
if (!IsClipboardDataText || (pasteFormat.Format == PasteFormats.Custom && !IsCustomAIEnabled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pasteFormat.Format)
|
||||
{
|
||||
case PasteFormats.PlainText:
|
||||
ToPlainTextFunction();
|
||||
break;
|
||||
|
||||
case PasteFormats.Markdown:
|
||||
ToMarkdownFunction();
|
||||
break;
|
||||
|
||||
case PasteFormats.Json:
|
||||
ToJsonFunction();
|
||||
break;
|
||||
|
||||
case PasteFormats.Custom:
|
||||
Query = pasteFormat.Prompt;
|
||||
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(pasteFormat.Prompt, false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecuteCustomActionWithPaste(int customActionId)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var customAction = _userSettings.CustomActions.FirstOrDefault(customAction => customAction.Id == customActionId);
|
||||
|
||||
if (customAction != null)
|
||||
{
|
||||
Query = customAction.Prompt;
|
||||
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(customAction.Prompt, true));
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<string> GenerateCustomFunction(string inputInstructions)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
@@ -315,7 +465,7 @@ namespace AdvancedPaste.ViewModels
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (ClipboardData == null || !ClipboardData.Contains(StandardDataFormats.Text))
|
||||
if (!IsClipboardDataText)
|
||||
{
|
||||
Logger.LogWarning("Clipboard does not contain text data");
|
||||
return string.Empty;
|
||||
@@ -416,5 +566,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,10 @@ 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_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 +68,30 @@ private:
|
||||
|
||||
HANDLE m_hProcess;
|
||||
|
||||
std::thread create_pipe_thread;
|
||||
std::unique_ptr<CAtlFile> m_write_pipe;
|
||||
|
||||
// Time to wait for process to close after sending WM_CLOSE signal
|
||||
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{};
|
||||
|
||||
std::vector<Hotkey> m_custom_action_hotkeys;
|
||||
std::vector<int> m_custom_action_ids;
|
||||
|
||||
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 +101,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";
|
||||
@@ -131,7 +168,7 @@ private:
|
||||
{
|
||||
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 +176,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 +185,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 +194,56 @@ 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_custom_action_hotkeys.clear();
|
||||
m_custom_action_ids.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_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))
|
||||
{
|
||||
m_custom_action_hotkeys.push_back(parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT)));
|
||||
m_custom_action_ids.push_back(static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 +263,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;
|
||||
}
|
||||
|
||||
create_pipe_thread = std::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 +349,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 +578,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 +689,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 +697,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 +718,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 +728,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 +748,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 +768,36 @@ 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 custom_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
|
||||
|
||||
if (custom_action_index < m_custom_action_ids.size())
|
||||
{
|
||||
const auto id = m_custom_action_ids.at(custom_action_index);
|
||||
|
||||
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 +807,20 @@ 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_custom_action_hotkeys.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);
|
||||
std::copy(m_custom_action_hotkeys.begin(), m_custom_action_hotkeys.end(), hotkeys + NUM_DEFAULT_HOTKEYS);
|
||||
}
|
||||
return 4;
|
||||
|
||||
return num_hotkeys;
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
BIN
src/modules/Workspaces/Assets/Workspaces.ico
Normal file
BIN
src/modules/Workspaces/Assets/Workspaces.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 456 KiB |
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
namespace WorkspacesWindowProperties
|
||||
{
|
||||
namespace Properties
|
||||
{
|
||||
const wchar_t LaunchedByWorkspacesID[] = L"PowerToys_LaunchedByWorkspaces";
|
||||
}
|
||||
|
||||
inline void StampWorkspacesLaunchedProperty(HWND window)
|
||||
{
|
||||
::SetPropW(window, Properties::LaunchedByWorkspacesID, reinterpret_cast<HANDLE>(1));
|
||||
}
|
||||
}
|
||||
9
src/modules/Workspaces/WorkspacesEditor/App.config
Normal file
9
src/modules/Workspaces/WorkspacesEditor/App.config
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
</startup>
|
||||
<runtime>
|
||||
<AppContextSwitchOverrides value = "Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
|
||||
</runtime>
|
||||
</configuration>
|
||||
20
src/modules/Workspaces/WorkspacesEditor/App.xaml
Normal file
20
src/modules/Workspaces/WorkspacesEditor/App.xaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<Application
|
||||
x:Class="WorkspacesEditor.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
Exit="OnExit"
|
||||
Startup="OnStartup">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources />
|
||||
<ui:XamlControlsResources />
|
||||
<ResourceDictionary Source="pack://application:,,,/Styles/ButtonStyles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style x:Key="HeadingTextBlock" TargetType="TextBlock" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
135
src/modules/Workspaces/WorkspacesEditor/App.xaml.cs
Normal file
135
src/modules/Workspaces/WorkspacesEditor/App.xaml.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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 System.Windows;
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
using WorkspacesEditor.Utils;
|
||||
using WorkspacesEditor.ViewModels;
|
||||
|
||||
namespace WorkspacesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application, IDisposable
|
||||
{
|
||||
private static Mutex _instanceMutex;
|
||||
|
||||
public static WorkspacesEditorIO WorkspacesEditorIO { get; private set; }
|
||||
|
||||
private MainWindow _mainWindow;
|
||||
|
||||
private MainViewModel _mainViewModel;
|
||||
|
||||
public static ThemeManager ThemeManager { get; set; }
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public App()
|
||||
{
|
||||
WorkspacesEditorIO = new WorkspacesEditorIO();
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
Logger.InitializeLogger("\\Workspaces\\Logs");
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
|
||||
const string appName = "Local\\PowerToys_Workspaces_Editor_InstanceMutex";
|
||||
bool createdNew;
|
||||
_instanceMutex = new Mutex(true, appName, out createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
Logger.LogWarning("Another instance of Workspaces Editor is already running. Exiting this instance.");
|
||||
_instanceMutex = null;
|
||||
Shutdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredWorkspacesEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
||||
Shutdown(0);
|
||||
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)
|
||||
{
|
||||
_mainViewModel = new MainViewModel(WorkspacesEditorIO);
|
||||
}
|
||||
|
||||
var parseResult = WorkspacesEditorIO.ParseWorkspaces(_mainViewModel);
|
||||
|
||||
// normal start of editor
|
||||
if (_mainWindow == null)
|
||||
{
|
||||
_mainWindow = new MainWindow(_mainViewModel);
|
||||
}
|
||||
|
||||
// reset main window owner to keep it on the top
|
||||
_mainWindow.ShowActivated = true;
|
||||
_mainWindow.Topmost = true;
|
||||
_mainWindow.Show();
|
||||
|
||||
// we can reset topmost flag after it's opened
|
||||
_mainWindow.Topmost = false;
|
||||
}
|
||||
|
||||
private void OnExit(object sender, ExitEventArgs e)
|
||||
{
|
||||
if (_instanceMutex != null)
|
||||
{
|
||||
_instanceMutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
Dispose();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||
{
|
||||
Logger.LogError("Unhandled exception occurred", args.ExceptionObject as Exception);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
ThemeManager?.Dispose();
|
||||
_instanceMutex?.Dispose();
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace WorkspacesEditor.Controls
|
||||
{
|
||||
public class ResetIsEnabled : ContentControl
|
||||
{
|
||||
static ResetIsEnabled()
|
||||
{
|
||||
IsEnabledProperty.OverrideMetadata(
|
||||
typeof(ResetIsEnabled),
|
||||
new UIPropertyMetadata(
|
||||
defaultValue: true,
|
||||
propertyChangedCallback: (_, __) => { },
|
||||
coerceValueCallback: (_, x) => x));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace WorkspacesEditor.Converters
|
||||
{
|
||||
public class BooleanToInvertedVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/modules/Workspaces/WorkspacesEditor/Data/InvokePoint.cs
Normal file
14
src/modules/Workspaces/WorkspacesEditor/Data/InvokePoint.cs
Normal file
@@ -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.
|
||||
|
||||
namespace WorkspacesEditor.Data
|
||||
{
|
||||
/* sync with workspaces-common */
|
||||
public enum InvokePoint
|
||||
{
|
||||
EditorButton = 0,
|
||||
Shortcut,
|
||||
LaunchAndEdit,
|
||||
}
|
||||
}
|
||||
96
src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs
Normal file
96
src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Workspaces.Data;
|
||||
using static WorkspacesEditor.Data.ProjectData;
|
||||
|
||||
namespace WorkspacesEditor.Data
|
||||
{
|
||||
public class ProjectData : WorkspacesEditorData<ProjectWrapper>
|
||||
{
|
||||
public struct ApplicationWrapper
|
||||
{
|
||||
public struct WindowPositionWrapper
|
||||
{
|
||||
public int X { get; set; }
|
||||
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
public string Application { get; set; }
|
||||
|
||||
public string ApplicationPath { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public string PackageFullName { get; set; }
|
||||
|
||||
public string AppUserModelId { get; set; }
|
||||
|
||||
public string CommandLineArguments { get; set; }
|
||||
|
||||
public bool IsElevated { get; set; }
|
||||
|
||||
public bool CanLaunchElevated { get; set; }
|
||||
|
||||
public bool Minimized { get; set; }
|
||||
|
||||
public bool Maximized { get; set; }
|
||||
|
||||
public WindowPositionWrapper Position { get; set; }
|
||||
|
||||
public int Monitor { get; set; }
|
||||
}
|
||||
|
||||
public struct MonitorConfigurationWrapper
|
||||
{
|
||||
public struct MonitorRectWrapper
|
||||
{
|
||||
public int Top { get; set; }
|
||||
|
||||
public int Left { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
public string InstanceId { get; set; }
|
||||
|
||||
public int MonitorNumber { get; set; }
|
||||
|
||||
public int Dpi { get; set; }
|
||||
|
||||
public MonitorRectWrapper MonitorRectDpiAware { get; set; }
|
||||
|
||||
public MonitorRectWrapper MonitorRectDpiUnaware { get; set; }
|
||||
}
|
||||
|
||||
public struct ProjectWrapper
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public long CreationTime { get; set; }
|
||||
|
||||
public long LastLaunchedTime { get; set; }
|
||||
|
||||
public bool IsShortcutNeeded { get; set; }
|
||||
|
||||
public bool MoveExistingWindows { get; set; }
|
||||
|
||||
public List<MonitorConfigurationWrapper> MonitorConfiguration { get; set; }
|
||||
|
||||
public List<ApplicationWrapper> Applications { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 WorkspacesEditor.Utils;
|
||||
|
||||
namespace WorkspacesEditor.Data
|
||||
{
|
||||
public class TempProjectData : ProjectData
|
||||
{
|
||||
public static string File
|
||||
{
|
||||
get
|
||||
{
|
||||
return FolderUtils.DataFolder() + "\\temp-workspaces.json";
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteTempFile()
|
||||
{
|
||||
if (System.IO.File.Exists(File))
|
||||
{
|
||||
System.IO.File.Delete(File);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Workspaces.Data;
|
||||
using WorkspacesEditor.Utils;
|
||||
using static WorkspacesEditor.Data.ProjectData;
|
||||
using static WorkspacesEditor.Data.WorkspacesData;
|
||||
|
||||
namespace WorkspacesEditor.Data
|
||||
{
|
||||
public class WorkspacesData : WorkspacesEditorData<WorkspacesListWrapper>
|
||||
{
|
||||
public string File
|
||||
{
|
||||
get
|
||||
{
|
||||
return FolderUtils.DataFolder() + "\\workspaces.json";
|
||||
}
|
||||
}
|
||||
|
||||
public struct WorkspacesListWrapper
|
||||
{
|
||||
public List<ProjectWrapper> Workspaces { get; set; }
|
||||
}
|
||||
|
||||
public enum OrderBy
|
||||
{
|
||||
LastViewed = 0,
|
||||
Created = 1,
|
||||
Name = 2,
|
||||
Unknown = 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json;
|
||||
using WorkspacesEditor.Utils;
|
||||
|
||||
namespace Workspaces.Data
|
||||
{
|
||||
public class WorkspacesEditorData<T>
|
||||
{
|
||||
protected JsonSerializerOptions JsonOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
WriteIndented = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public T Read(string file)
|
||||
{
|
||||
IOUtils ioUtils = new IOUtils();
|
||||
string data = ioUtils.ReadFile(file);
|
||||
return JsonSerializer.Deserialize<T>(data, JsonOptions);
|
||||
}
|
||||
|
||||
public string Serialize(T data)
|
||||
{
|
||||
return JsonSerializer.Serialize(data, JsonOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/modules/Workspaces/WorkspacesEditor/HeadingTextBlock.cs
Normal file
31
src/modules/Workspaces/WorkspacesEditor/HeadingTextBlock.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Automation.Peers;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace WorkspacesEditor
|
||||
{
|
||||
public class HeadingTextBlock : TextBlock
|
||||
{
|
||||
protected override AutomationPeer OnCreateAutomationPeer()
|
||||
{
|
||||
return new HeadingTextBlockAutomationPeer(this);
|
||||
}
|
||||
|
||||
internal sealed class HeadingTextBlockAutomationPeer : TextBlockAutomationPeer
|
||||
{
|
||||
public HeadingTextBlockAutomationPeer(HeadingTextBlock owner)
|
||||
: base(owner)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AutomationControlType GetAutomationControlTypeCore()
|
||||
{
|
||||
return AutomationControlType.Header;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
322
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml
Normal file
322
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml
Normal file
@@ -0,0 +1,322 @@
|
||||
<Page
|
||||
x:Class="WorkspacesEditor.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:WorkspacesEditor.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:props="clr-namespace:WorkspacesEditor.Properties"
|
||||
Title="MainPage"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
<converters:BooleanToInvertedVisibilityConverter x:Key="BooleanToInvertedVisibilityConverter" />
|
||||
<Thickness x:Key="ContentDialogPadding">24,16,0,24</Thickness>
|
||||
<Thickness x:Key="ContentDialogCommandSpaceMargin">0,24,24,0</Thickness>
|
||||
<Style x:Key="DeleteButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{DynamicResource TertiaryBackgroundBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Padding="26,6,26,6"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="Transparent">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource TitleBarSecondaryForegroundBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource TitleBarSecondaryForegroundBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<local:HeadingTextBlock
|
||||
x:Name="WorkspacesHeaderBlock"
|
||||
Grid.Row="0"
|
||||
Margin="40,20,40,20"
|
||||
AutomationProperties.HeadingLevel="Level1"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="{x:Static props:Resources.Workspaces}" />
|
||||
|
||||
<Button
|
||||
x:Name="NewProjectButton"
|
||||
Grid.Row="0"
|
||||
Height="36"
|
||||
Margin="0,20,40,20"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
AutomationProperties.Name="{x:Static props:Resources.CreateWorkspace}"
|
||||
Click="NewProjectButton_Click"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
TabIndex="3">
|
||||
<StackPanel Margin="12,8,12,8" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
AutomationProperties.Name="{x:Static props:Resources.CreateWorkspace}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="" />
|
||||
<TextBlock
|
||||
Margin="12,-3,0,0"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="{x:Static props:Resources.CreateWorkspace}" />
|
||||
</StackPanel>
|
||||
<Button.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="6"
|
||||
Opacity="0.32"
|
||||
ShadowDepth="1" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="40,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
BorderThickness="2"
|
||||
CornerRadius="5">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Grid>
|
||||
<TextBox
|
||||
x:Name="SearchTextBox"
|
||||
Width="320"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
Text="{Binding SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="{x:Static props:Resources.SearchExplanation}" />
|
||||
<TextBlock
|
||||
Margin="10,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||
IsHitTestVisible="False"
|
||||
Text="{x:Static props:Resources.Search}"
|
||||
ToolTip="{x:Static props:Resources.SearchExplanation}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Text, ElementName=SearchTextBox}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
Margin="-50,0,34,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Search}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||
Text="" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0,0,40,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="10,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="{x:Static props:Resources.SortBy}" />
|
||||
<ComboBox
|
||||
Width="140"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
SelectedIndex="{Binding OrderByIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ComboBoxItem Content="{x:Static props:Resources.LastLaunched}" />
|
||||
<ComboBoxItem Content="{x:Static props:Resources.Created}" />
|
||||
<ComboBoxItem Content="{x:Static props:Resources.Name}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="20"
|
||||
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||
Text="{Binding EmptyWorkspacesViewMessage, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextAlignment="Center"
|
||||
Visibility="{Binding IsWorkspacesViewEmpty, Mode=OneWay, Converter={StaticResource BoolToVis}, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<ScrollViewer
|
||||
Grid.Row="2"
|
||||
Margin="40,15,40,40"
|
||||
VerticalContentAlignment="Stretch"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Visibility="{Binding IsWorkspacesViewEmpty, Mode=OneWay, Converter={StaticResource BooleanToInvertedVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ItemsControl ItemsSource="{Binding WorkspacesView, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
IsItemsHost="True"
|
||||
Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="models:Project">
|
||||
<Button
|
||||
x:Name="EditButton"
|
||||
Margin="0,12,0,0"
|
||||
Padding="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
Click="EditButtonClicked">
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
CornerRadius="5">
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Margin="12,14,10,10"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Vertical">
|
||||
<TextBlock
|
||||
Margin="0,0,0,8"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<StackPanel
|
||||
Margin="0,0,0,8"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Image Height="20" Source="{Binding PreviewIcons, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Margin="6,0,4,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding AppsCountString}" />
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,3,10,0"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="" />
|
||||
<TextBlock Text="{Binding LastLaunched, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="12,12,12,12"
|
||||
Orientation="Vertical">
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="MoreButton"
|
||||
HorizontalAlignment="Right"
|
||||
Click="MoreButton_Click"
|
||||
Style="{StaticResource IconOnlyButtonStyle}">
|
||||
<TextBlock
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="" />
|
||||
</Button>
|
||||
<Popup
|
||||
AllowsTransparency="True"
|
||||
IsOpen="{Binding IsPopupVisible, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Placement="Left"
|
||||
PlacementTarget="{Binding ElementName=MoreButton}"
|
||||
StaysOpen="False">
|
||||
<Grid Background="{DynamicResource PrimaryBackgroundBrush}">
|
||||
<Grid.OpacityMask>
|
||||
<VisualBrush Visual="{Binding ElementName=OpacityBorder}" />
|
||||
</Grid.OpacityMask>
|
||||
<Border
|
||||
x:Name="OpacityBorder"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Black"
|
||||
CornerRadius="5" />
|
||||
<StackPanel Background="{DynamicResource PrimaryBackgroundBrush}" Orientation="Vertical">
|
||||
<Button
|
||||
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||
Click="EditButtonClicked"
|
||||
Style="{StaticResource DeleteButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="" />
|
||||
<TextBlock
|
||||
Margin="10,0,0,0"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="{x:Static props:Resources.Edit}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button
|
||||
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||
Click="DeleteButtonClicked"
|
||||
Style="{StaticResource DeleteButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="" />
|
||||
<TextBlock
|
||||
Margin="10,0,0,0"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="{x:Static props:Resources.Delete}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
<Button
|
||||
Margin="0,6,0,0"
|
||||
Padding="20,4,20,4"
|
||||
HorizontalAlignment="Right"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Launch}"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource SecondaryBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Click="LaunchButton_Click"
|
||||
Content="{x:Static props:Resources.Launch}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
63
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml.cs
Normal file
63
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using WorkspacesEditor.Models;
|
||||
using WorkspacesEditor.ViewModels;
|
||||
|
||||
namespace WorkspacesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainPage.xaml
|
||||
/// </summary>
|
||||
public partial class MainPage : Page
|
||||
{
|
||||
private MainViewModel _mainViewModel;
|
||||
|
||||
public MainPage(MainViewModel mainViewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
_mainViewModel = mainViewModel;
|
||||
this.DataContext = _mainViewModel;
|
||||
}
|
||||
|
||||
private /*async*/ void NewProjectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_mainViewModel.EnterSnapshotMode(false);
|
||||
}
|
||||
|
||||
private void EditButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_mainViewModel.CloseAllPopups();
|
||||
Button button = sender as Button;
|
||||
Project selectedProject = button.DataContext as Project;
|
||||
_mainViewModel.EditProject(selectedProject);
|
||||
}
|
||||
|
||||
private void DeleteButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
Button button = sender as Button;
|
||||
Project selectedProject = button.DataContext as Project;
|
||||
_mainViewModel.DeleteProject(selectedProject);
|
||||
}
|
||||
|
||||
private void MoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
Button button = sender as Button;
|
||||
Project project = button.DataContext as Project;
|
||||
project.IsPopupVisible = true;
|
||||
}
|
||||
|
||||
private void LaunchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
Button button = sender as Button;
|
||||
Project project = button.DataContext as Project;
|
||||
_mainViewModel.LaunchProject(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml
Normal file
29
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml
Normal file
@@ -0,0 +1,29 @@
|
||||
<Window
|
||||
x:Class="WorkspacesEditor.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:props="clr-namespace:WorkspacesEditor.Properties"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
x:Name="WorkspacesMainWindow"
|
||||
Title="{x:Static props:Resources.MainTitle}"
|
||||
MinWidth="700"
|
||||
MinHeight="680"
|
||||
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
ui:TitleBar.InactiveBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||
ui:TitleBar.IsIconVisible="True"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
AutomationProperties.Name="Workspaces Editor"
|
||||
Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
Closing="OnClosing"
|
||||
ContentRendered="OnContentRendered"
|
||||
ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Border BorderThickness="1" CornerRadius="20">
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Frame x:Name="ContentFrame" NavigationUIVisibility="Hidden" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
120
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml.cs
Normal file
120
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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 System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using ManagedCommon;
|
||||
using WorkspacesEditor.Utils;
|
||||
using WorkspacesEditor.ViewModels;
|
||||
|
||||
namespace WorkspacesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window, IDisposable
|
||||
{
|
||||
public MainViewModel MainViewModel { get; set; }
|
||||
|
||||
private CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
|
||||
private static MainPage _mainPage;
|
||||
|
||||
public MainWindow(MainViewModel mainViewModel)
|
||||
{
|
||||
MainViewModel = mainViewModel;
|
||||
mainViewModel.SetMainWindow(this);
|
||||
|
||||
WindowInteropHelper windowInteropHelper = new WindowInteropHelper(this);
|
||||
System.Windows.Forms.Screen screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
|
||||
double dpi = MonitorHelper.GetScreenDpiFromScreen(screen);
|
||||
this.Height = screen.WorkingArea.Height / dpi * 0.90;
|
||||
this.Width = screen.WorkingArea.Width / dpi * 0.75;
|
||||
this.Top = screen.WorkingArea.Top + (int)(screen.WorkingArea.Height / dpi * 0.05);
|
||||
this.Left = screen.WorkingArea.Left + (int)(screen.WorkingArea.Width / dpi * 0.125);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
_mainPage = new MainPage(mainViewModel);
|
||||
|
||||
ContentFrame.Navigate(_mainPage);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// This is required to fix a WPF rendering bug when using custom chrome
|
||||
private void OnContentRendered(object sender, EventArgs e)
|
||||
{
|
||||
// Get the window handle of the Workspaces Editor window
|
||||
IntPtr handle = new WindowInteropHelper(this).Handle;
|
||||
WindowHelpers.BringToForeground(handle);
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
public void ShowPage(ProjectEditor editPage)
|
||||
{
|
||||
ContentFrame.Navigate(editPage);
|
||||
}
|
||||
|
||||
public void SwitchToMainView()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 WorkspacesEditor.Models
|
||||
{
|
||||
public sealed class AppListDataTemplateSelector : System.Windows.Controls.DataTemplateSelector
|
||||
{
|
||||
public System.Windows.DataTemplate HeaderTemplate { get; set; }
|
||||
|
||||
public System.Windows.DataTemplate AppTemplate { get; set; }
|
||||
|
||||
public AppListDataTemplateSelector()
|
||||
{
|
||||
HeaderTemplate = new System.Windows.DataTemplate();
|
||||
AppTemplate = new System.Windows.DataTemplate();
|
||||
}
|
||||
|
||||
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
|
||||
{
|
||||
if (item is MonitorHeaderRow)
|
||||
{
|
||||
return HeaderTemplate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
474
src/modules/Workspaces/WorkspacesEditor/Models/Application.cs
Normal file
474
src/modules/Workspaces/WorkspacesEditor/Models/Application.cs
Normal file
@@ -0,0 +1,474 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Media.Imaging;
|
||||
using ManagedCommon;
|
||||
using Windows.Management.Deployment;
|
||||
|
||||
namespace WorkspacesEditor.Models
|
||||
{
|
||||
public class Application : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private bool _isInitialized;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public Application()
|
||||
{
|
||||
}
|
||||
|
||||
public Application(Application other)
|
||||
{
|
||||
AppName = other.AppName;
|
||||
AppPath = other.AppPath;
|
||||
AppTitle = other.AppTitle;
|
||||
PackageFullName = other.PackageFullName;
|
||||
AppUserModelId = other.AppUserModelId;
|
||||
CommandLineArguments = other.CommandLineArguments;
|
||||
IsElevated = other.IsElevated;
|
||||
CanLaunchElevated = other.CanLaunchElevated;
|
||||
Minimized = other.Minimized;
|
||||
Maximized = other.Maximized;
|
||||
Position = other.Position;
|
||||
MonitorNumber = other.MonitorNumber;
|
||||
|
||||
Parent = other.Parent;
|
||||
IsNotFound = other.IsNotFound;
|
||||
IsHighlighted = other.IsHighlighted;
|
||||
RepeatIndex = other.RepeatIndex;
|
||||
PackagedId = other.PackagedId;
|
||||
PackagedName = other.PackagedName;
|
||||
PackagedPublisherID = other.PackagedPublisherID;
|
||||
Aumid = other.Aumid;
|
||||
IsExpanded = other.IsExpanded;
|
||||
IsIncluded = other.IsIncluded;
|
||||
}
|
||||
|
||||
public Project Parent { get; set; }
|
||||
|
||||
public struct WindowPosition
|
||||
{
|
||||
public int X { get; set; }
|
||||
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
|
||||
public static bool operator ==(WindowPosition left, WindowPosition right)
|
||||
{
|
||||
return left.X == right.X && left.Y == right.Y && left.Width == right.Width && left.Height == right.Height;
|
||||
}
|
||||
|
||||
public static bool operator !=(WindowPosition left, WindowPosition right)
|
||||
{
|
||||
return left.X != right.X || left.Y != right.Y || left.Width != right.Width || left.Height != right.Height;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WindowPosition pos = (WindowPosition)obj;
|
||||
return X == pos.X && Y == pos.Y && Width == pos.Width && Height == pos.Height;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public string AppName { get; set; }
|
||||
|
||||
public string AppPath { get; set; }
|
||||
|
||||
public string AppTitle { get; set; }
|
||||
|
||||
public string PackageFullName { get; set; }
|
||||
|
||||
public string AppUserModelId { get; set; }
|
||||
|
||||
public string CommandLineArguments { get; set; }
|
||||
|
||||
private bool _isElevated;
|
||||
|
||||
public bool IsElevated
|
||||
{
|
||||
get => _isElevated;
|
||||
set
|
||||
{
|
||||
_isElevated = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams)));
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanLaunchElevated { get; set; }
|
||||
|
||||
internal void SwitchDeletion()
|
||||
{
|
||||
IsIncluded = !IsIncluded;
|
||||
RedrawPreviewImage();
|
||||
}
|
||||
|
||||
private void RedrawPreviewImage()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Parent.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _minimized;
|
||||
|
||||
public bool Minimized
|
||||
{
|
||||
get => _minimized;
|
||||
set
|
||||
{
|
||||
_minimized = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Minimized)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled)));
|
||||
RedrawPreviewImage();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _maximized;
|
||||
|
||||
public bool Maximized
|
||||
{
|
||||
get => _maximized;
|
||||
set
|
||||
{
|
||||
_maximized = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Maximized)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled)));
|
||||
RedrawPreviewImage();
|
||||
}
|
||||
}
|
||||
|
||||
public bool EditPositionEnabled { get => !Minimized && !Maximized; }
|
||||
|
||||
private string _appMainParams;
|
||||
|
||||
public string AppMainParams
|
||||
{
|
||||
get
|
||||
{
|
||||
_appMainParams = _isElevated ? Properties.Resources.Admin : string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(CommandLineArguments))
|
||||
{
|
||||
_appMainParams += (_appMainParams == string.Empty ? string.Empty : " | ") + Properties.Resources.Args + ": " + CommandLineArguments;
|
||||
}
|
||||
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsAppMainParamVisible)));
|
||||
return _appMainParams;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAppMainParamVisible { get => !string.IsNullOrWhiteSpace(_appMainParams); }
|
||||
|
||||
private bool _isNotFound;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsNotFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isNotFound;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isNotFound != value)
|
||||
{
|
||||
_isNotFound = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsHighlighted { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int RepeatIndex { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string RepeatIndexString
|
||||
{
|
||||
get
|
||||
{
|
||||
return RepeatIndex <= 1 ? string.Empty : RepeatIndex.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private Icon _icon = null;
|
||||
|
||||
[JsonIgnore]
|
||||
public Icon Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_icon == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPackagedApp)
|
||||
{
|
||||
Uri uri = GetAppLogoByPackageFamilyName();
|
||||
var bitmap = new Bitmap(uri.LocalPath);
|
||||
var iconHandle = bitmap.GetHicon();
|
||||
_icon = Icon.FromHandle(iconHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
_icon = Icon.ExtractAssociatedIcon(AppPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.LogWarning($"Icon not found on app path: {AppPath}. Using default icon");
|
||||
IsNotFound = true;
|
||||
_icon = new Icon(@"images\DefaultIcon.ico");
|
||||
}
|
||||
}
|
||||
|
||||
return _icon;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri GetAppLogoByPackageFamilyName()
|
||||
{
|
||||
var pkgManager = new PackageManager();
|
||||
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
|
||||
|
||||
if (pkg == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return pkg.Logo;
|
||||
}
|
||||
|
||||
private BitmapImage _iconBitmapImage;
|
||||
|
||||
public BitmapImage IconBitmapImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconBitmapImage == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap previewBitmap = new Bitmap(32, 32);
|
||||
using (Graphics graphics = Graphics.FromImage(previewBitmap))
|
||||
{
|
||||
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
|
||||
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
|
||||
}
|
||||
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
previewBitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
_iconBitmapImage = bitmapImage;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return _iconBitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
private WindowPosition _position;
|
||||
|
||||
public WindowPosition Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
_position = value;
|
||||
_scaledPosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
private WindowPosition? _scaledPosition;
|
||||
|
||||
public WindowPosition ScaledPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_scaledPosition == null)
|
||||
{
|
||||
double scaleFactor = MonitorSetup.Dpi / 96.0;
|
||||
_scaledPosition = new WindowPosition()
|
||||
{
|
||||
X = (int)(scaleFactor * Position.X),
|
||||
Y = (int)(scaleFactor * Position.Y),
|
||||
Height = (int)(scaleFactor * Position.Height),
|
||||
Width = (int)(scaleFactor * Position.Width),
|
||||
};
|
||||
}
|
||||
|
||||
return _scaledPosition.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public int MonitorNumber { get; set; }
|
||||
|
||||
private MonitorSetup _monitorSetup;
|
||||
|
||||
public MonitorSetup MonitorSetup
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_monitorSetup == null)
|
||||
{
|
||||
_monitorSetup = Parent.Monitors.Where(x => x.MonitorNumber == MonitorNumber).FirstOrDefault();
|
||||
}
|
||||
|
||||
return _monitorSetup;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public void InitializationFinished()
|
||||
{
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private bool? _isPackagedApp;
|
||||
|
||||
public string PackagedId { get; set; }
|
||||
|
||||
public string PackagedName { get; set; }
|
||||
|
||||
public string PackagedPublisherID { get; set; }
|
||||
|
||||
public string Aumid { get; set; }
|
||||
|
||||
public bool IsPackagedApp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isPackagedApp == null)
|
||||
{
|
||||
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_isPackagedApp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
Match match = packagedAppPathRegex.Match(appPath);
|
||||
_isPackagedApp = match.Success;
|
||||
if (match.Success)
|
||||
{
|
||||
PackagedName = match.Groups["APPID"].Value;
|
||||
PackagedPublisherID = match.Groups["PublisherID"].Value;
|
||||
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
|
||||
Aumid = $"{PackagedId}!App";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _isPackagedApp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isExpanded;
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set
|
||||
{
|
||||
if (_isExpanded != value)
|
||||
{
|
||||
_isExpanded = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsExpanded)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DeleteButtonContent { get => _isIncluded ? Properties.Resources.Delete : Properties.Resources.AddBack; }
|
||||
|
||||
private bool _isIncluded = true;
|
||||
|
||||
public bool IsIncluded
|
||||
{
|
||||
get => _isIncluded;
|
||||
set
|
||||
{
|
||||
if (_isIncluded != value)
|
||||
{
|
||||
_isIncluded = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsIncluded)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(DeleteButtonContent)));
|
||||
if (!_isIncluded)
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal void CommandLineTextChanged(string newCommandLineValue)
|
||||
{
|
||||
CommandLineArguments = newCommandLineValue;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams)));
|
||||
}
|
||||
|
||||
internal void MaximizedChecked()
|
||||
{
|
||||
Minimized = false;
|
||||
}
|
||||
|
||||
internal void MinimizedChecked()
|
||||
{
|
||||
Maximized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/modules/Workspaces/WorkspacesEditor/Models/Monitor.cs
Normal file
33
src/modules/Workspaces/WorkspacesEditor/Models/Monitor.cs
Normal file
@@ -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.Windows;
|
||||
|
||||
namespace WorkspacesEditor.Models
|
||||
{
|
||||
public class Monitor
|
||||
{
|
||||
public string MonitorName { get; private set; }
|
||||
|
||||
public string MonitorInstanceId { get; private set; }
|
||||
|
||||
public int MonitorNumber { get; private set; }
|
||||
|
||||
public int Dpi { get; private set; }
|
||||
|
||||
public Rect MonitorDpiUnawareBounds { get; private set; }
|
||||
|
||||
public Rect MonitorDpiAwareBounds { get; private set; }
|
||||
|
||||
public Monitor(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
|
||||
{
|
||||
MonitorName = monitorName;
|
||||
MonitorInstanceId = monitorInstanceId;
|
||||
MonitorNumber = number;
|
||||
Dpi = dpi;
|
||||
MonitorDpiAwareBounds = dpiAwareBounds;
|
||||
MonitorDpiUnawareBounds = dpiUnawareBounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 WorkspacesEditor.Models
|
||||
{
|
||||
internal sealed class MonitorHeaderRow
|
||||
{
|
||||
public string MonitorName { get; set; }
|
||||
|
||||
public string SelectString { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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.ComponentModel;
|
||||
using System.Windows;
|
||||
|
||||
namespace WorkspacesEditor.Models
|
||||
{
|
||||
public class MonitorSetup : Monitor, INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public string MonitorInfo { get => MonitorName; }
|
||||
|
||||
public string MonitorInfoWithResolution { get => $"{MonitorName} {MonitorDpiAwareBounds.Width}x{MonitorDpiAwareBounds.Height}"; }
|
||||
|
||||
public MonitorSetup(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
|
||||
: base(monitorName, monitorInstanceId, number, dpi, dpiAwareBounds, dpiUnawareBounds)
|
||||
{
|
||||
}
|
||||
|
||||
public MonitorSetup(MonitorSetup other)
|
||||
: base(other.MonitorName, other.MonitorInstanceId, other.MonitorNumber, other.Dpi, other.MonitorDpiAwareBounds, other.MonitorDpiUnawareBounds)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
376
src/modules/Workspaces/WorkspacesEditor/Models/Project.cs
Normal file
376
src/modules/Workspaces/WorkspacesEditor/Models/Project.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
using ManagedCommon;
|
||||
using WorkspacesEditor.Data;
|
||||
using WorkspacesEditor.Utils;
|
||||
|
||||
namespace WorkspacesEditor.Models
|
||||
{
|
||||
public class Project : INotifyPropertyChanged
|
||||
{
|
||||
[JsonIgnore]
|
||||
public string EditorWindowTitle { get; set; }
|
||||
|
||||
public string Id { get; private set; }
|
||||
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Name)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanBeSaved)));
|
||||
}
|
||||
}
|
||||
|
||||
public long CreationTime { get; } // in seconds
|
||||
|
||||
public long LastLaunchedTime { get; } // in seconds
|
||||
|
||||
public bool IsShortcutNeeded { get; set; }
|
||||
|
||||
public bool MoveExistingWindows { get; set; }
|
||||
|
||||
public string LastLaunched
|
||||
{
|
||||
get
|
||||
{
|
||||
string lastLaunched = WorkspacesEditor.Properties.Resources.LastLaunched + ": ";
|
||||
if (LastLaunchedTime == 0)
|
||||
{
|
||||
return lastLaunched + WorkspacesEditor.Properties.Resources.Never;
|
||||
}
|
||||
|
||||
const int SECOND = 1;
|
||||
const int MINUTE = 60 * SECOND;
|
||||
const int HOUR = 60 * MINUTE;
|
||||
const int DAY = 24 * HOUR;
|
||||
const int MONTH = 30 * DAY;
|
||||
|
||||
DateTime lastLaunchDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(LastLaunchedTime);
|
||||
|
||||
var now = DateTime.UtcNow.Ticks;
|
||||
var ts = DateTime.UtcNow - lastLaunchDateTime;
|
||||
double delta = Math.Abs(ts.TotalSeconds);
|
||||
|
||||
if (delta < 1 * MINUTE)
|
||||
{
|
||||
return lastLaunched + WorkspacesEditor.Properties.Resources.Recently;
|
||||
}
|
||||
|
||||
if (delta < 2 * MINUTE)
|
||||
{
|
||||
return lastLaunched + WorkspacesEditor.Properties.Resources.OneMinuteAgo;
|
||||
}
|
||||
|
||||
if (delta < 45 * MINUTE)
|
||||
{
|
||||
return lastLaunched + ts.Minutes + " " + WorkspacesEditor.Properties.Resources.MinutesAgo;
|
||||
}
|
||||
|
||||
if (delta < 90 * MINUTE)
|
||||
{
|
||||
return lastLaunched + WorkspacesEditor.Properties.Resources.OneHourAgo;
|
||||
}
|
||||
|
||||
if (delta < 24 * HOUR)
|
||||
{
|
||||
return lastLaunched + ts.Hours + " " + WorkspacesEditor.Properties.Resources.HoursAgo;
|
||||
}
|
||||
|
||||
if (delta < 48 * HOUR)
|
||||
{
|
||||
return lastLaunched + WorkspacesEditor.Properties.Resources.Yesterday;
|
||||
}
|
||||
|
||||
if (delta < 30 * DAY)
|
||||
{
|
||||
return lastLaunched + ts.Days + " " + WorkspacesEditor.Properties.Resources.DaysAgo;
|
||||
}
|
||||
|
||||
if (delta < 12 * MONTH)
|
||||
{
|
||||
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
|
||||
return lastLaunched + (months <= 1 ? WorkspacesEditor.Properties.Resources.OneMonthAgo : months + " " + WorkspacesEditor.Properties.Resources.MonthsAgo);
|
||||
}
|
||||
else
|
||||
{
|
||||
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
|
||||
return lastLaunched + (years <= 1 ? WorkspacesEditor.Properties.Resources.OneYearAgo : years + " " + WorkspacesEditor.Properties.Resources.YearsAgo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanBeSaved
|
||||
{
|
||||
get => Name.Length > 0 && Applications.Count > 0;
|
||||
}
|
||||
|
||||
private bool _isRevertEnabled;
|
||||
|
||||
public bool IsRevertEnabled
|
||||
{
|
||||
get => _isRevertEnabled;
|
||||
set
|
||||
{
|
||||
if (_isRevertEnabled != value)
|
||||
{
|
||||
_isRevertEnabled = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsRevertEnabled)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isPopupVisible;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsPopupVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isPopupVisible;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_isPopupVisible = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsPopupVisible)));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Application> Applications { get; set; }
|
||||
|
||||
public List<object> ApplicationsListed
|
||||
{
|
||||
get
|
||||
{
|
||||
List<object> applicationsListed = new List<object>();
|
||||
ILookup<MonitorSetup, Application> apps = Applications.Where(x => !x.Minimized).ToLookup(x => x.MonitorSetup);
|
||||
foreach (var appItem in apps.OrderBy(x => x.Key.MonitorDpiUnawareBounds.Left).ThenBy(x => x.Key.MonitorDpiUnawareBounds.Top))
|
||||
{
|
||||
MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = "Screen " + appItem.Key.MonitorNumber, SelectString = Properties.Resources.SelectAllAppsOnMonitor + " " + appItem.Key.MonitorInfo };
|
||||
applicationsListed.Add(headerRow);
|
||||
foreach (Application app in appItem)
|
||||
{
|
||||
applicationsListed.Add(app);
|
||||
}
|
||||
}
|
||||
|
||||
var minimizedApps = Applications.Where(x => x.Minimized);
|
||||
if (minimizedApps.Any())
|
||||
{
|
||||
MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = Properties.Resources.Minimized_Apps, SelectString = Properties.Resources.SelectAllMinimizedApps };
|
||||
applicationsListed.Add(headerRow);
|
||||
foreach (Application app in minimizedApps)
|
||||
{
|
||||
applicationsListed.Add(app);
|
||||
}
|
||||
}
|
||||
|
||||
return applicationsListed;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string AppsCountString
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = Applications.Count;
|
||||
return count.ToString(CultureInfo.InvariantCulture) + " " + (count == 1 ? Properties.Resources.App : Properties.Resources.Apps);
|
||||
}
|
||||
}
|
||||
|
||||
public List<MonitorSetup> Monitors { get; }
|
||||
|
||||
public bool IsPositionChangedManually { get; set; } // telemetry
|
||||
|
||||
private BitmapImage _previewIcons;
|
||||
private BitmapImage _previewImage;
|
||||
private double _previewImageWidth;
|
||||
|
||||
public Project(Project selectedProject)
|
||||
{
|
||||
Id = selectedProject.Id;
|
||||
Name = selectedProject.Name;
|
||||
PreviewIcons = selectedProject.PreviewIcons;
|
||||
PreviewImage = selectedProject.PreviewImage;
|
||||
IsShortcutNeeded = selectedProject.IsShortcutNeeded;
|
||||
MoveExistingWindows = selectedProject.MoveExistingWindows;
|
||||
|
||||
int screenIndex = 1;
|
||||
|
||||
Monitors = new List<MonitorSetup>();
|
||||
foreach (var item in selectedProject.Monitors.OrderBy(x => x.MonitorDpiAwareBounds.Left).ThenBy(x => x.MonitorDpiAwareBounds.Top))
|
||||
{
|
||||
Monitors.Add(item);
|
||||
screenIndex++;
|
||||
}
|
||||
|
||||
Applications = new List<Application>();
|
||||
foreach (var item in selectedProject.Applications)
|
||||
{
|
||||
Application newApp = new Application(item);
|
||||
newApp.Parent = this;
|
||||
newApp.InitializationFinished();
|
||||
Applications.Add(newApp);
|
||||
}
|
||||
}
|
||||
|
||||
public Project(ProjectData.ProjectWrapper project)
|
||||
{
|
||||
Id = project.Id;
|
||||
Name = project.Name;
|
||||
CreationTime = project.CreationTime;
|
||||
LastLaunchedTime = project.LastLaunchedTime;
|
||||
IsShortcutNeeded = project.IsShortcutNeeded;
|
||||
MoveExistingWindows = project.MoveExistingWindows;
|
||||
Monitors = new List<MonitorSetup>() { };
|
||||
Applications = new List<Models.Application> { };
|
||||
|
||||
foreach (var app in project.Applications)
|
||||
{
|
||||
Models.Application newApp = new Models.Application()
|
||||
{
|
||||
AppName = app.Application,
|
||||
AppPath = app.ApplicationPath,
|
||||
AppTitle = app.Title,
|
||||
PackageFullName = app.PackageFullName,
|
||||
AppUserModelId = app.AppUserModelId,
|
||||
Parent = this,
|
||||
CommandLineArguments = app.CommandLineArguments,
|
||||
IsElevated = app.IsElevated,
|
||||
CanLaunchElevated = app.CanLaunchElevated,
|
||||
Maximized = app.Maximized,
|
||||
Minimized = app.Minimized,
|
||||
IsNotFound = false,
|
||||
Position = new Models.Application.WindowPosition()
|
||||
{
|
||||
Height = app.Position.Height,
|
||||
Width = app.Position.Width,
|
||||
X = app.Position.X,
|
||||
Y = app.Position.Y,
|
||||
},
|
||||
MonitorNumber = app.Monitor,
|
||||
};
|
||||
newApp.InitializationFinished();
|
||||
Applications.Add(newApp);
|
||||
}
|
||||
|
||||
foreach (var monitor in project.MonitorConfiguration)
|
||||
{
|
||||
System.Windows.Rect dpiAware = new System.Windows.Rect(monitor.MonitorRectDpiAware.Left, monitor.MonitorRectDpiAware.Top, monitor.MonitorRectDpiAware.Width, monitor.MonitorRectDpiAware.Height);
|
||||
System.Windows.Rect dpiUnaware = new System.Windows.Rect(monitor.MonitorRectDpiUnaware.Left, monitor.MonitorRectDpiUnaware.Top, monitor.MonitorRectDpiUnaware.Width, monitor.MonitorRectDpiUnaware.Height);
|
||||
Monitors.Add(new MonitorSetup(monitor.Id, monitor.InstanceId, monitor.MonitorNumber, monitor.Dpi, dpiAware, dpiUnaware));
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapImage PreviewIcons
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previewIcons;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_previewIcons = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewIcons)));
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapImage PreviewImage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previewImage;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_previewImage = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImage)));
|
||||
}
|
||||
}
|
||||
|
||||
public double PreviewImageWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previewImageWidth;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_previewImageWidth = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImageWidth)));
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public async void Initialize(Theme currentTheme)
|
||||
{
|
||||
PreviewIcons = await Task.Run(() => DrawHelper.DrawPreviewIcons(this));
|
||||
Rectangle commonBounds = GetCommonBounds();
|
||||
PreviewImage = await Task.Run(() => DrawHelper.DrawPreview(this, commonBounds, currentTheme));
|
||||
PreviewImageWidth = commonBounds.Width / (commonBounds.Height * 1.2 / 200);
|
||||
}
|
||||
|
||||
private Rectangle GetCommonBounds()
|
||||
{
|
||||
double minX = Monitors.First().MonitorDpiAwareBounds.Left;
|
||||
double minY = Monitors.First().MonitorDpiAwareBounds.Top;
|
||||
double maxX = Monitors.First().MonitorDpiAwareBounds.Right;
|
||||
double maxY = Monitors.First().MonitorDpiAwareBounds.Bottom;
|
||||
for (int monitorIndex = 1; monitorIndex < Monitors.Count; monitorIndex++)
|
||||
{
|
||||
Monitor monitor = Monitors[monitorIndex];
|
||||
minX = Math.Min(minX, monitor.MonitorDpiAwareBounds.Left);
|
||||
minY = Math.Min(minY, monitor.MonitorDpiAwareBounds.Top);
|
||||
maxX = Math.Max(maxX, monitor.MonitorDpiAwareBounds.Right);
|
||||
maxY = Math.Max(maxY, monitor.MonitorDpiAwareBounds.Bottom);
|
||||
}
|
||||
|
||||
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
|
||||
}
|
||||
|
||||
public void UpdateAfterLaunchAndEdit(Project other)
|
||||
{
|
||||
Id = other.Id;
|
||||
Name = other.Name;
|
||||
IsRevertEnabled = true;
|
||||
}
|
||||
|
||||
internal void CloseExpanders()
|
||||
{
|
||||
foreach (Application app in Applications)
|
||||
{
|
||||
app.IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml
Normal file
16
src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<Window
|
||||
x:Class="WorkspacesEditor.OverlayWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
WindowStyle="None"
|
||||
mc:Ignorable="d">
|
||||
<Border
|
||||
Background="Transparent"
|
||||
BorderBrush="Red"
|
||||
BorderThickness="3" />
|
||||
</Window>
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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.Windows;
|
||||
|
||||
namespace WorkspacesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for OverlayWindow.xaml
|
||||
/// </summary>
|
||||
public partial class OverlayWindow : Window
|
||||
{
|
||||
public OverlayWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
693
src/modules/Workspaces/WorkspacesEditor/Properties/Resources.Designer.cs
generated
Normal file
693
src/modules/Workspaces/WorkspacesEditor/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,693 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace WorkspacesEditor.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WorkspacesEditor.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add Back.
|
||||
/// </summary>
|
||||
public static string AddBack {
|
||||
get {
|
||||
return ResourceManager.GetString("AddBack", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Admin.
|
||||
/// </summary>
|
||||
public static string Admin {
|
||||
get {
|
||||
return ResourceManager.GetString("Admin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch new app instances.
|
||||
/// </summary>
|
||||
public static string AlwaysLaunch {
|
||||
get {
|
||||
return ResourceManager.GetString("AlwaysLaunch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to app.
|
||||
/// </summary>
|
||||
public static string App {
|
||||
get {
|
||||
return ResourceManager.GetString("App", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to App name.
|
||||
/// </summary>
|
||||
public static string App_name {
|
||||
get {
|
||||
return ResourceManager.GetString("App_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to apps.
|
||||
/// </summary>
|
||||
public static string Apps {
|
||||
get {
|
||||
return ResourceManager.GetString("Apps", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure?.
|
||||
/// </summary>
|
||||
public static string Are_You_Sure {
|
||||
get {
|
||||
return ResourceManager.GetString("Are_You_Sure", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure you want to delete this Workspace?.
|
||||
/// </summary>
|
||||
public static string Are_You_Sure_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Are_You_Sure_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Args.
|
||||
/// </summary>
|
||||
public static string Args {
|
||||
get {
|
||||
return ResourceManager.GetString("Args", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cancel.
|
||||
/// </summary>
|
||||
public static string Cancel {
|
||||
get {
|
||||
return ResourceManager.GetString("Cancel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to CLI arguments.
|
||||
/// </summary>
|
||||
public static string CliArguments {
|
||||
get {
|
||||
return ResourceManager.GetString("CliArguments", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Created.
|
||||
/// </summary>
|
||||
public static string Created {
|
||||
get {
|
||||
return ResourceManager.GetString("Created", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Create Desktop Shortcut.
|
||||
/// </summary>
|
||||
public static string CreateShortcut {
|
||||
get {
|
||||
return ResourceManager.GetString("CreateShortcut", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Create Workspace.
|
||||
/// </summary>
|
||||
public static string CreateWorkspace {
|
||||
get {
|
||||
return ResourceManager.GetString("CreateWorkspace", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to days ago.
|
||||
/// </summary>
|
||||
public static string DaysAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("DaysAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Workspace.
|
||||
/// </summary>
|
||||
public static string DefaultWorkspaceNamePrefix {
|
||||
get {
|
||||
return ResourceManager.GetString("DefaultWorkspaceNamePrefix", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove.
|
||||
/// </summary>
|
||||
public static string Delete {
|
||||
get {
|
||||
return ResourceManager.GetString("Delete", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delete Workspace dialog..
|
||||
/// </summary>
|
||||
public static string Delete_Workspace_Dialog_Announce {
|
||||
get {
|
||||
return ResourceManager.GetString("Delete_Workspace_Dialog_Announce", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove Selected Apps.
|
||||
/// </summary>
|
||||
public static string DeleteSelected {
|
||||
get {
|
||||
return ResourceManager.GetString("DeleteSelected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit.
|
||||
/// </summary>
|
||||
public static string Edit {
|
||||
get {
|
||||
return ResourceManager.GetString("Edit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to opened.
|
||||
/// </summary>
|
||||
public static string Edit_Project_Open_Announce {
|
||||
get {
|
||||
return ResourceManager.GetString("Edit_Project_Open_Announce", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit Workspace.
|
||||
/// </summary>
|
||||
public static string EditWorkspace {
|
||||
get {
|
||||
return ResourceManager.GetString("EditWorkspace", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error parsing Workspaces data..
|
||||
/// </summary>
|
||||
public static string Error_Parsing_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("Error_Parsing_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Height.
|
||||
/// </summary>
|
||||
public static string Height {
|
||||
get {
|
||||
return ResourceManager.GetString("Height", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to hours ago.
|
||||
/// </summary>
|
||||
public static string HoursAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("HoursAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Last launched.
|
||||
/// </summary>
|
||||
public static string LastLaunched {
|
||||
get {
|
||||
return ResourceManager.GetString("LastLaunched", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch.
|
||||
/// </summary>
|
||||
public static string Launch {
|
||||
get {
|
||||
return ResourceManager.GetString("Launch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch args.
|
||||
/// </summary>
|
||||
public static string Launch_args {
|
||||
get {
|
||||
return ResourceManager.GetString("Launch_args", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch as Admin.
|
||||
/// </summary>
|
||||
public static string LaunchAsAdmin {
|
||||
get {
|
||||
return ResourceManager.GetString("LaunchAsAdmin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch & Edit.
|
||||
/// </summary>
|
||||
public static string LaunchEdit {
|
||||
get {
|
||||
return ResourceManager.GetString("LaunchEdit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Left.
|
||||
/// </summary>
|
||||
public static string Left {
|
||||
get {
|
||||
return ResourceManager.GetString("Left", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Workspaces Editor.
|
||||
/// </summary>
|
||||
public static string MainTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("MainTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Maximized.
|
||||
/// </summary>
|
||||
public static string Maximized {
|
||||
get {
|
||||
return ResourceManager.GetString("Maximized", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Minimized.
|
||||
/// </summary>
|
||||
public static string Minimized {
|
||||
get {
|
||||
return ResourceManager.GetString("Minimized", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Minimized Apps.
|
||||
/// </summary>
|
||||
public static string Minimized_Apps {
|
||||
get {
|
||||
return ResourceManager.GetString("Minimized_Apps", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to minutes ago.
|
||||
/// </summary>
|
||||
public static string MinutesAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("MinutesAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to months ago.
|
||||
/// </summary>
|
||||
public static string MonthsAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("MonthsAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Move apps if present.
|
||||
/// </summary>
|
||||
public static string MoveIfExist {
|
||||
get {
|
||||
return ResourceManager.GetString("MoveIfExist", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name.
|
||||
/// </summary>
|
||||
public static string Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to never.
|
||||
/// </summary>
|
||||
public static string Never {
|
||||
get {
|
||||
return ResourceManager.GetString("Never", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New Workspace.
|
||||
/// </summary>
|
||||
public static string New_Workspace {
|
||||
get {
|
||||
return ResourceManager.GetString("New_Workspace", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There are no saved Workspaces..
|
||||
/// </summary>
|
||||
public static string No_Workspaces_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("No_Workspaces_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The application cannot be found.
|
||||
/// </summary>
|
||||
public static string NotFoundTooltip {
|
||||
get {
|
||||
return ResourceManager.GetString("NotFoundTooltip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No Workspaces match the current search..
|
||||
/// </summary>
|
||||
public static string NoWorkspacesMatch {
|
||||
get {
|
||||
return ResourceManager.GetString("NoWorkspacesMatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to an hour ago.
|
||||
/// </summary>
|
||||
public static string OneHourAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneHourAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to a minute ago.
|
||||
/// </summary>
|
||||
public static string OneMinuteAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneMinuteAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to one month ago.
|
||||
/// </summary>
|
||||
public static string OneMonthAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneMonthAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to one second ago.
|
||||
/// </summary>
|
||||
public static string OneSecondAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneSecondAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to one year ago.
|
||||
/// </summary>
|
||||
public static string OneYearAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneYearAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pin Workspaces to Taskbar.
|
||||
/// </summary>
|
||||
public static string PinToTaskbar {
|
||||
get {
|
||||
return ResourceManager.GetString("PinToTaskbar", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to recently.
|
||||
/// </summary>
|
||||
public static string Recently {
|
||||
get {
|
||||
return ResourceManager.GetString("Recently", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Revert.
|
||||
/// </summary>
|
||||
public static string Revert {
|
||||
get {
|
||||
return ResourceManager.GetString("Revert", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save Workspace.
|
||||
/// </summary>
|
||||
public static string Save_Workspace {
|
||||
get {
|
||||
return ResourceManager.GetString("Save_Workspace", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search.
|
||||
/// </summary>
|
||||
public static string Search {
|
||||
get {
|
||||
return ResourceManager.GetString("Search", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search for Workspaces or apps.
|
||||
/// </summary>
|
||||
public static string SearchExplanation {
|
||||
get {
|
||||
return ResourceManager.GetString("SearchExplanation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to seconds ago.
|
||||
/// </summary>
|
||||
public static string SecondsAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("SecondsAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select All Apps on.
|
||||
/// </summary>
|
||||
public static string SelectAllAppsOnMonitor {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectAllAppsOnMonitor", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select All Minimized Apps.
|
||||
/// </summary>
|
||||
public static string SelectAllMinimizedApps {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectAllMinimizedApps", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select All Apps in Workspace.
|
||||
/// </summary>
|
||||
public static string SelectedAllInWorkspace {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectedAllInWorkspace", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit your layout and click "Capture" when finished..
|
||||
/// </summary>
|
||||
public static string SnapshotDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("SnapshotDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Snapshot Creator.
|
||||
/// </summary>
|
||||
public static string SnapshotWindowTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("SnapshotWindowTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Sort by.
|
||||
/// </summary>
|
||||
public static string SortBy {
|
||||
get {
|
||||
return ResourceManager.GetString("SortBy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Capture.
|
||||
/// </summary>
|
||||
public static string Take_Snapshot {
|
||||
get {
|
||||
return ResourceManager.GetString("Take_Snapshot", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Top.
|
||||
/// </summary>
|
||||
public static string Top {
|
||||
get {
|
||||
return ResourceManager.GetString("Top", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Width.
|
||||
/// </summary>
|
||||
public static string Width {
|
||||
get {
|
||||
return ResourceManager.GetString("Width", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Workspace name.
|
||||
/// </summary>
|
||||
public static string WorkspaceName {
|
||||
get {
|
||||
return ResourceManager.GetString("WorkspaceName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Workspaces.
|
||||
/// </summary>
|
||||
public static string Workspaces {
|
||||
get {
|
||||
return ResourceManager.GetString("Workspaces", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Write arguments here.
|
||||
/// </summary>
|
||||
public static string WriteArgs {
|
||||
get {
|
||||
return ResourceManager.GetString("WriteArgs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to years ago.
|
||||
/// </summary>
|
||||
public static string YearsAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("YearsAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to yesterday.
|
||||
/// </summary>
|
||||
public static string Yesterday {
|
||||
get {
|
||||
return ResourceManager.GetString("Yesterday", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: 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:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AddBack" xml:space="preserve">
|
||||
<value>Add back</value>
|
||||
</data>
|
||||
<data name="Admin" xml:space="preserve">
|
||||
<value>Admin</value>
|
||||
</data>
|
||||
<data name="AlwaysLaunch" xml:space="preserve">
|
||||
<value>Launch new app instances</value>
|
||||
</data>
|
||||
<data name="App" xml:space="preserve">
|
||||
<value>app</value>
|
||||
</data>
|
||||
<data name="Apps" xml:space="preserve">
|
||||
<value>apps</value>
|
||||
</data>
|
||||
<data name="App_name" xml:space="preserve">
|
||||
<value>App name</value>
|
||||
</data>
|
||||
<data name="Are_You_Sure" xml:space="preserve">
|
||||
<value>Are you sure?</value>
|
||||
</data>
|
||||
<data name="Are_You_Sure_Description" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this Workspace?</value>
|
||||
</data>
|
||||
<data name="Args" xml:space="preserve">
|
||||
<value>Args</value>
|
||||
<comment>Arguments</comment>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="CliArguments" xml:space="preserve">
|
||||
<value>CLI arguments</value>
|
||||
</data>
|
||||
<data name="Created" xml:space="preserve">
|
||||
<value>Created</value>
|
||||
</data>
|
||||
<data name="CreateWorkspace" xml:space="preserve">
|
||||
<value>Create Workspace</value>
|
||||
</data>
|
||||
<data name="CreateShortcut" xml:space="preserve">
|
||||
<value>Create desktop shortcut</value>
|
||||
</data>
|
||||
<data name="DaysAgo" xml:space="preserve">
|
||||
<value>days ago</value>
|
||||
</data>
|
||||
<data name="DefaultWorkspaceNamePrefix" xml:space="preserve">
|
||||
<value>Workspace</value>
|
||||
</data>
|
||||
<data name="Delete" xml:space="preserve">
|
||||
<value>Remove</value>
|
||||
</data>
|
||||
<data name="DeleteSelected" xml:space="preserve">
|
||||
<value>Remove selected apps</value>
|
||||
</data>
|
||||
<data name="Delete_Workspace_Dialog_Announce" xml:space="preserve">
|
||||
<value>Delete Workspace dialog.</value>
|
||||
</data>
|
||||
<data name="Edit" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="EditWorkspace" xml:space="preserve">
|
||||
<value>Edit Workspace</value>
|
||||
</data>
|
||||
<data name="Edit_Project_Open_Announce" xml:space="preserve">
|
||||
<value>opened</value>
|
||||
</data>
|
||||
<data name="Error_Parsing_Message" xml:space="preserve">
|
||||
<value>Error parsing Workspaces data.</value>
|
||||
</data>
|
||||
<data name="Height" xml:space="preserve">
|
||||
<value>Height</value>
|
||||
</data>
|
||||
<data name="HoursAgo" xml:space="preserve">
|
||||
<value>hours ago</value>
|
||||
</data>
|
||||
<data name="LastLaunched" xml:space="preserve">
|
||||
<value>Last launched</value>
|
||||
</data>
|
||||
<data name="Launch" xml:space="preserve">
|
||||
<value>Launch</value>
|
||||
</data>
|
||||
<data name="LaunchAsAdmin" xml:space="preserve">
|
||||
<value>Launch as Admin</value>
|
||||
</data>
|
||||
<data name="LaunchEdit" xml:space="preserve">
|
||||
<value>Launch & edit</value>
|
||||
</data>
|
||||
<data name="Launch_args" xml:space="preserve">
|
||||
<value>Launch args</value>
|
||||
</data>
|
||||
<data name="Left" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
<comment>the left x coordinate</comment>
|
||||
</data>
|
||||
<data name="MainTitle" xml:space="preserve">
|
||||
<value>Workspaces Editor</value>
|
||||
</data>
|
||||
<data name="Maximized" xml:space="preserve">
|
||||
<value>Maximized</value>
|
||||
</data>
|
||||
<data name="Minimized" xml:space="preserve">
|
||||
<value>Minimized</value>
|
||||
</data>
|
||||
<data name="Minimized_Apps" xml:space="preserve">
|
||||
<value>Minimized apps</value>
|
||||
</data>
|
||||
<data name="MinutesAgo" xml:space="preserve">
|
||||
<value>minutes ago</value>
|
||||
</data>
|
||||
<data name="MonthsAgo" xml:space="preserve">
|
||||
<value>months ago</value>
|
||||
</data>
|
||||
<data name="MoveIfExist" xml:space="preserve">
|
||||
<value>Move apps if present</value>
|
||||
</data>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Never" xml:space="preserve">
|
||||
<value>never</value>
|
||||
</data>
|
||||
<data name="New_Workspace" xml:space="preserve">
|
||||
<value>New Workspace</value>
|
||||
</data>
|
||||
<data name="NoWorkspacesMatch" xml:space="preserve">
|
||||
<value>No Workspaces match the current search.</value>
|
||||
</data>
|
||||
<data name="NotFoundTooltip" xml:space="preserve">
|
||||
<value>The application cannot be found</value>
|
||||
</data>
|
||||
<data name="No_Workspaces_Message" xml:space="preserve">
|
||||
<value>There are no saved Workspaces.</value>
|
||||
</data>
|
||||
<data name="OneHourAgo" xml:space="preserve">
|
||||
<value>an hour ago</value>
|
||||
</data>
|
||||
<data name="OneMinuteAgo" xml:space="preserve">
|
||||
<value>a minute ago</value>
|
||||
</data>
|
||||
<data name="OneMonthAgo" xml:space="preserve">
|
||||
<value>one month ago</value>
|
||||
</data>
|
||||
<data name="OneSecondAgo" xml:space="preserve">
|
||||
<value>one second ago</value>
|
||||
</data>
|
||||
<data name="OneYearAgo" xml:space="preserve">
|
||||
<value>one year ago</value>
|
||||
</data>
|
||||
<data name="PinToTaskbar" xml:space="preserve">
|
||||
<value>Pin Workspaces to taskbar</value>
|
||||
</data>
|
||||
<data name="WorkspaceName" xml:space="preserve">
|
||||
<value>Workspace name</value>
|
||||
</data>
|
||||
<data name="Workspaces" xml:space="preserve">
|
||||
<value>Workspaces</value>
|
||||
</data>
|
||||
<data name="Recently" xml:space="preserve">
|
||||
<value>recently</value>
|
||||
</data>
|
||||
<data name="Revert" xml:space="preserve">
|
||||
<value>Revert</value>
|
||||
</data>
|
||||
<data name="Save_Workspace" xml:space="preserve">
|
||||
<value>Save Workspace</value>
|
||||
</data>
|
||||
<data name="Search" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
<data name="SearchExplanation" xml:space="preserve">
|
||||
<value>Search for Workspaces or apps</value>
|
||||
</data>
|
||||
<data name="SecondsAgo" xml:space="preserve">
|
||||
<value>seconds ago</value>
|
||||
</data>
|
||||
<data name="SelectAllAppsOnMonitor" xml:space="preserve">
|
||||
<value>Select all apps on</value>
|
||||
</data>
|
||||
<data name="SelectAllMinimizedApps" xml:space="preserve">
|
||||
<value>Select all minimized apps</value>
|
||||
</data>
|
||||
<data name="SelectedAllInWorkspace" xml:space="preserve">
|
||||
<value>Select all apps in Workspace</value>
|
||||
</data>
|
||||
<data name="SnapshotDescription" xml:space="preserve">
|
||||
<value>Edit your layout and click "Capture" when finished.</value>
|
||||
</data>
|
||||
<data name="SnapshotWindowTitle" xml:space="preserve">
|
||||
<value>Snapshot Creator</value>
|
||||
</data>
|
||||
<data name="SortBy" xml:space="preserve">
|
||||
<value>Sort by</value>
|
||||
</data>
|
||||
<data name="Take_Snapshot" xml:space="preserve">
|
||||
<value>Capture</value>
|
||||
</data>
|
||||
<data name="Top" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
<comment>the top y coordinate</comment>
|
||||
</data>
|
||||
<data name="Width" xml:space="preserve">
|
||||
<value>Width</value>
|
||||
</data>
|
||||
<data name="WriteArgs" xml:space="preserve">
|
||||
<value>Write arguments here</value>
|
||||
</data>
|
||||
<data name="YearsAgo" xml:space="preserve">
|
||||
<value>years ago</value>
|
||||
</data>
|
||||
<data name="Yesterday" xml:space="preserve">
|
||||
<value>yesterday</value>
|
||||
</data>
|
||||
</root>
|
||||
26
src/modules/Workspaces/WorkspacesEditor/Properties/Settings.Designer.cs
generated
Normal file
26
src/modules/Workspaces/WorkspacesEditor/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace WorkspacesEditor.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
60
src/modules/Workspaces/WorkspacesEditor/SnapshotWindow.xaml
Normal file
60
src/modules/Workspaces/WorkspacesEditor/SnapshotWindow.xaml
Normal file
@@ -0,0 +1,60 @@
|
||||
<Window
|
||||
x:Class="WorkspacesEditor.SnapshotWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:props="clr-namespace:WorkspacesEditor.Properties"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
Title="{x:Static props:Resources.SnapshotWindowTitle}"
|
||||
Width="350"
|
||||
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
ui:TitleBar.InactiveBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
BorderBrush="Red"
|
||||
BorderThickness="5"
|
||||
Closing="Window_Closing"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Grid Margin="5" Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="5,5,5,15"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text="{x:Static props:Resources.SnapshotDescription}"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="CancelButton"
|
||||
Grid.Row="1"
|
||||
Height="36"
|
||||
Margin="5,5,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
Click="CancelButtonClicked"
|
||||
Content="{x:Static props:Resources.Cancel}" />
|
||||
<Button
|
||||
x:Name="SnapshotButton"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Height="36"
|
||||
Margin="5,5,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Take_Snapshot}"
|
||||
Click="SnapshotButtonClicked"
|
||||
Content="{x:Static props:Resources.Take_Snapshot}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</Grid>
|
||||
</Window>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user