mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-29 16:36:40 +01:00
Compare commits
45 Commits
releaseChe
...
shawn/AddI
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e423c64b68 | ||
|
|
f57e7cb9cd | ||
|
|
43be9f88ee | ||
|
|
f95e6c8f1b | ||
|
|
aaa6e03012 | ||
|
|
b075a021df | ||
|
|
9e43c23216 | ||
|
|
bece9c9217 | ||
|
|
32c13cead4 | ||
|
|
33808fdb9c | ||
|
|
f510be4c53 | ||
|
|
4d3c223402 | ||
|
|
1ba5a258e9 | ||
|
|
8aea589b01 | ||
|
|
afd9d4cc3c | ||
|
|
bc0a760aff | ||
|
|
06afe09973 | ||
|
|
0de60445ea | ||
|
|
47d4a65223 | ||
|
|
1b72c0b969 | ||
|
|
9160c82fc2 | ||
|
|
452e0dcf51 | ||
|
|
2c9a9e9fca | ||
|
|
09c8c1d79a | ||
|
|
95c8a83f79 | ||
|
|
2830ea919c | ||
|
|
725ad21952 | ||
|
|
ebc3a139c5 | ||
|
|
28dba2633e | ||
|
|
9fbd3de3a2 | ||
|
|
4a0d9912ae | ||
|
|
15c79a0176 | ||
|
|
97d46efec2 | ||
|
|
46242b384e | ||
|
|
84be261581 | ||
|
|
5a8095b704 | ||
|
|
417c1a6b98 | ||
|
|
2593149d22 | ||
|
|
0b50c38fe1 | ||
|
|
840808b465 | ||
|
|
b50df36b70 | ||
|
|
b94593ef73 | ||
|
|
7a01d56179 | ||
|
|
130e9a0a68 | ||
|
|
34c37f2d38 |
111
.github/actions/spell-check/expect.txt
vendored
111
.github/actions/spell-check/expect.txt
vendored
@@ -2,8 +2,8 @@ AAAAs
|
||||
abcdefghjkmnpqrstuvxyz
|
||||
abgr
|
||||
ABlocked
|
||||
ABOUTBOX
|
||||
ABORTIFHUNG
|
||||
ABOUTBOX
|
||||
Abug
|
||||
Acceleratorkeys
|
||||
ACCEPTFILES
|
||||
@@ -56,6 +56,7 @@ ANull
|
||||
AOC
|
||||
aocfnapldcnfbofgmbbllojgocaelgdd
|
||||
AOklab
|
||||
aot
|
||||
APARTMENTTHREADED
|
||||
APeriod
|
||||
apicontract
|
||||
@@ -97,8 +98,8 @@ ASSOCSTR
|
||||
ASYNCWINDOWPLACEMENT
|
||||
ASYNCWINDOWPOS
|
||||
atl
|
||||
ATX
|
||||
ATRIOX
|
||||
ATX
|
||||
aumid
|
||||
authenticode
|
||||
AUTOBUDDY
|
||||
@@ -117,10 +118,10 @@ azman
|
||||
azureaiinference
|
||||
azureinference
|
||||
azureopenai
|
||||
backticks
|
||||
bbwe
|
||||
BCIE
|
||||
bck
|
||||
backticks
|
||||
BESTEFFORT
|
||||
bezelled
|
||||
bhid
|
||||
@@ -148,8 +149,8 @@ bmi
|
||||
BNumber
|
||||
BODGY
|
||||
BOklab
|
||||
Bootstrappers
|
||||
BOOTSTRAPPERINSTALLFOLDER
|
||||
Bootstrappers
|
||||
BOTTOMALIGN
|
||||
boxmodel
|
||||
BPBF
|
||||
@@ -176,17 +177,16 @@ BYPOSITION
|
||||
CALCRECT
|
||||
CALG
|
||||
callbackptr
|
||||
cabstr
|
||||
calpwstr
|
||||
caub
|
||||
Cangjie
|
||||
CANRENAME
|
||||
Carlseibert
|
||||
Canvascustomlayout
|
||||
CAPTUREBLT
|
||||
CAPTURECHANGED
|
||||
CARETBLINKING
|
||||
Carlseibert
|
||||
CAtl
|
||||
caub
|
||||
CBN
|
||||
cch
|
||||
CCHDEVICENAME
|
||||
@@ -206,11 +206,9 @@ changecursor
|
||||
CHILDACTIVATE
|
||||
CHILDWINDOW
|
||||
CHOOSEFONT
|
||||
CIBUILD
|
||||
cidl
|
||||
CIELCh
|
||||
cim
|
||||
claude
|
||||
CImage
|
||||
cla
|
||||
CLASSDC
|
||||
@@ -264,7 +262,6 @@ CONFIGW
|
||||
CONFLICTINGMODIFIERKEY
|
||||
CONFLICTINGMODIFIERSHORTCUT
|
||||
CONOUT
|
||||
coreclr
|
||||
constexpr
|
||||
contentdialog
|
||||
contentfiles
|
||||
@@ -276,6 +273,7 @@ copiedcolorrepresentation
|
||||
coppied
|
||||
copyable
|
||||
COPYPEN
|
||||
coreclr
|
||||
COREWINDOW
|
||||
Corpor
|
||||
cotaskmem
|
||||
@@ -284,18 +282,18 @@ countof
|
||||
covrun
|
||||
cpcontrols
|
||||
cph
|
||||
cppcoreguidelines
|
||||
cplusplus
|
||||
CPower
|
||||
cppcoreguidelines
|
||||
cpptools
|
||||
cppvsdbg
|
||||
cppwinrt
|
||||
createdump
|
||||
creativecommons
|
||||
CREATEPROCESS
|
||||
CREATESCHEDULEDTASK
|
||||
CREATESTRUCT
|
||||
CREATEWINDOWFAILED
|
||||
creativecommons
|
||||
CRECT
|
||||
CRH
|
||||
critsec
|
||||
@@ -331,7 +329,6 @@ CYSCREEN
|
||||
CYSMICON
|
||||
CYVIRTUALSCREEN
|
||||
Czechia
|
||||
cziplib
|
||||
Dac
|
||||
dacl
|
||||
DAffine
|
||||
@@ -355,9 +352,7 @@ Deact
|
||||
debugbreak
|
||||
decryptor
|
||||
Dedup
|
||||
dfx
|
||||
Deduplicator
|
||||
Deeplink
|
||||
DEFAULTBOOTSTRAPPERINSTALLFOLDER
|
||||
DEFAULTCOLOR
|
||||
DEFAULTFLAGS
|
||||
@@ -404,7 +399,6 @@ DISPLAYFREQUENCY
|
||||
displayname
|
||||
DISPLAYORIENTATION
|
||||
divyan
|
||||
djwsxzxb
|
||||
Dlg
|
||||
DLGFRAME
|
||||
DLGMODALFRAME
|
||||
@@ -417,7 +411,6 @@ DONTVALIDATEPATH
|
||||
dotnet
|
||||
downsampled
|
||||
downsampling
|
||||
Downsampled
|
||||
downscale
|
||||
DPICHANGED
|
||||
DPIs
|
||||
@@ -531,7 +524,6 @@ EXTRINSICPROPERTIES
|
||||
eyetracker
|
||||
FANCYZONESDRAWLAYOUTTEST
|
||||
FANCYZONESEDITOR
|
||||
FNumber
|
||||
FARPROC
|
||||
fdx
|
||||
fesf
|
||||
@@ -563,8 +555,8 @@ FIXEDSYS
|
||||
flac
|
||||
flyouts
|
||||
FMask
|
||||
foundrylocal
|
||||
fmtid
|
||||
FNumber
|
||||
FOF
|
||||
FOFX
|
||||
FOLDERID
|
||||
@@ -575,6 +567,7 @@ FORCEMINIMIZE
|
||||
FORMATDLGORD
|
||||
formatetc
|
||||
FORPARSING
|
||||
foundrylocal
|
||||
FRAMECHANGED
|
||||
frm
|
||||
FROMTOUCH
|
||||
@@ -593,13 +586,13 @@ gdi
|
||||
gdiplus
|
||||
GDIPVER
|
||||
GDISCALED
|
||||
geolocator
|
||||
GETCLIENTAREAANIMATION
|
||||
GETCURSEL
|
||||
GETDESKWALLPAPER
|
||||
GETDLGCODE
|
||||
GETDPISCALEDSIZE
|
||||
getfilesiginforedist
|
||||
geolocator
|
||||
GETHOTKEY
|
||||
GETICON
|
||||
GETLBTEXT
|
||||
@@ -610,11 +603,12 @@ GETSCREENSAVERRUNNING
|
||||
GETSECKEY
|
||||
GETSTICKYKEYS
|
||||
GETTEXTLENGTH
|
||||
GIFs
|
||||
gitmodules
|
||||
GHND
|
||||
gitmodules
|
||||
GMEM
|
||||
GNumber
|
||||
googleai
|
||||
googlegemini
|
||||
gpedit
|
||||
gpo
|
||||
GPOCA
|
||||
@@ -631,8 +625,6 @@ GValue
|
||||
gwl
|
||||
GWLP
|
||||
GWLSTYLE
|
||||
googleai
|
||||
googlegemini
|
||||
hangeul
|
||||
Hanzi
|
||||
Hardlines
|
||||
@@ -743,9 +735,7 @@ IDCANCEL
|
||||
IDD
|
||||
idk
|
||||
idl
|
||||
IIM
|
||||
idlist
|
||||
ifd
|
||||
IDOK
|
||||
IDOn
|
||||
IDR
|
||||
@@ -754,15 +744,16 @@ ietf
|
||||
IEXPLORE
|
||||
IFACEMETHOD
|
||||
IFACEMETHODIMP
|
||||
ifd
|
||||
IGNOREUNKNOWN
|
||||
IGo
|
||||
iid
|
||||
IIM
|
||||
Iindex
|
||||
Ijwhost
|
||||
ILD
|
||||
IMAGEHLP
|
||||
IMAGERESIZERCONTEXTMENU
|
||||
IPTC
|
||||
IMAGERESIZEREXT
|
||||
imageresizerinput
|
||||
imageresizersettings
|
||||
@@ -798,7 +789,6 @@ INSTALLFOLDERTOPREVIOUSINSTALLFOLDER
|
||||
INSTALLLOCATION
|
||||
INSTALLMESSAGE
|
||||
INSTALLPROPERTY
|
||||
installscopeperuser
|
||||
INSTALLSTARTMENUSHORTCUT
|
||||
INSTALLSTATE
|
||||
Inste
|
||||
@@ -811,6 +801,7 @@ invokecommand
|
||||
ipcmanager
|
||||
IPREVIEW
|
||||
ipreviewhandlervisualssetfont
|
||||
IPTC
|
||||
irow
|
||||
irprops
|
||||
isbi
|
||||
@@ -854,15 +845,14 @@ keyvault
|
||||
KILLFOCUS
|
||||
killrunner
|
||||
kmph
|
||||
ksa
|
||||
kvp
|
||||
Kybd
|
||||
LARGEICON
|
||||
lastcodeanalysissucceeded
|
||||
LASTEXITCODE
|
||||
LAYOUTRTL
|
||||
LCh
|
||||
lbl
|
||||
LCh
|
||||
lcid
|
||||
LCIDTo
|
||||
lcl
|
||||
@@ -878,10 +868,10 @@ LExit
|
||||
lhwnd
|
||||
LIBFUZZER
|
||||
LIBID
|
||||
lightswitch
|
||||
LIMITSIZE
|
||||
LIMITTEXT
|
||||
lindex
|
||||
lightswitch
|
||||
linkid
|
||||
LINKOVERLAY
|
||||
LINQTo
|
||||
@@ -892,6 +882,7 @@ LLKH
|
||||
llkhf
|
||||
LMEM
|
||||
LMENU
|
||||
lng
|
||||
LOADFROMFILE
|
||||
LOBYTE
|
||||
localappdata
|
||||
@@ -901,17 +892,14 @@ LOCATIONCHANGE
|
||||
LOCKTYPE
|
||||
LOGFONT
|
||||
LOGFONTW
|
||||
logon
|
||||
lon
|
||||
LOGMSG
|
||||
logon
|
||||
LOGPIXELSX
|
||||
LOGPIXELSY
|
||||
lng
|
||||
lon
|
||||
longdate
|
||||
LONGNAMES
|
||||
lowlevel
|
||||
lquadrant
|
||||
LOWORD
|
||||
lparam
|
||||
LPBITMAPINFOHEADER
|
||||
@@ -945,6 +933,7 @@ lpv
|
||||
LPW
|
||||
lpwcx
|
||||
lpwndpl
|
||||
lquadrant
|
||||
LReader
|
||||
LRESULT
|
||||
LSTATUS
|
||||
@@ -971,6 +960,7 @@ MAKELONG
|
||||
MAKELPARAM
|
||||
makepri
|
||||
MAKEWPARAM
|
||||
Malware
|
||||
manifestdependency
|
||||
MAPPEDTOSAMEKEY
|
||||
MAPTOSAMESHORTCUT
|
||||
@@ -993,8 +983,8 @@ MENUITEMINFO
|
||||
MENUITEMINFOW
|
||||
MERGECOPY
|
||||
MERGEPAINT
|
||||
Metadatas
|
||||
metadatamatters
|
||||
Metadatas
|
||||
metafile
|
||||
mfc
|
||||
Mgmt
|
||||
@@ -1040,9 +1030,6 @@ mousepointer
|
||||
mouseutils
|
||||
MOVESIZEEND
|
||||
MOVESIZESTART
|
||||
muxx
|
||||
muxxc
|
||||
muxxh
|
||||
MRM
|
||||
MRT
|
||||
mru
|
||||
@@ -1071,10 +1058,14 @@ msrc
|
||||
msstore
|
||||
msvcp
|
||||
MT
|
||||
mstsc
|
||||
MTND
|
||||
MULTIPLEUSE
|
||||
multizone
|
||||
muxc
|
||||
muxx
|
||||
muxxc
|
||||
muxxh
|
||||
MVPs
|
||||
mvvm
|
||||
MVVMTK
|
||||
@@ -1157,7 +1148,6 @@ nonstd
|
||||
NOOWNERZORDER
|
||||
NOPARENTNOTIFY
|
||||
NOPREFIX
|
||||
NPU
|
||||
NOREDIRECTIONBITMAP
|
||||
NOREDRAW
|
||||
NOREMOVE
|
||||
@@ -1186,6 +1176,7 @@ nowarn
|
||||
NOZORDER
|
||||
NPH
|
||||
npmjs
|
||||
NPU
|
||||
NResize
|
||||
NTAPI
|
||||
ntdll
|
||||
@@ -1210,15 +1201,17 @@ oldpath
|
||||
oldtheme
|
||||
oleaut
|
||||
OLECHAR
|
||||
ollama
|
||||
onebranch
|
||||
onnx
|
||||
OOBEUI
|
||||
openas
|
||||
opencode
|
||||
OPENFILENAME
|
||||
openrdp
|
||||
opensource
|
||||
openxmlformats
|
||||
ollama
|
||||
Olllama
|
||||
onnx
|
||||
OPTIMIZEFORINVOKE
|
||||
ORPHANEDDIALOGTITLE
|
||||
@@ -1292,6 +1285,7 @@ pguid
|
||||
phbm
|
||||
phbmp
|
||||
phicon
|
||||
Photoshop
|
||||
phwnd
|
||||
pici
|
||||
pidl
|
||||
@@ -1314,7 +1308,6 @@ pnid
|
||||
PNMLINK
|
||||
Poc
|
||||
Podcasts
|
||||
Photoshop
|
||||
POINTERID
|
||||
POINTERUPDATE
|
||||
Pokedex
|
||||
@@ -1409,10 +1402,9 @@ pwsz
|
||||
pwtd
|
||||
QDC
|
||||
qit
|
||||
QNN
|
||||
Qualcomm
|
||||
QITAB
|
||||
QITABENT
|
||||
QNN
|
||||
qoi
|
||||
Quarternary
|
||||
QUERYENDSESSION
|
||||
@@ -1422,8 +1414,8 @@ quickaccent
|
||||
QUNS
|
||||
RAII
|
||||
RAlt
|
||||
RAquadrant
|
||||
randi
|
||||
RAquadrant
|
||||
rasterization
|
||||
Rasterize
|
||||
RAWINPUTDEVICE
|
||||
@@ -1433,6 +1425,8 @@ RAWPATH
|
||||
rbhid
|
||||
rclsid
|
||||
RCZOOMIT
|
||||
remotedesktop
|
||||
rdp
|
||||
RDW
|
||||
READMODE
|
||||
READOBJECTS
|
||||
@@ -1450,9 +1444,7 @@ regfile
|
||||
REGISTERCLASSFAILED
|
||||
REGISTRYHEADER
|
||||
REGISTRYPREVIEWEXT
|
||||
registryroot
|
||||
regkey
|
||||
regroot
|
||||
regsvr
|
||||
REINSTALLMODE
|
||||
releaseblog
|
||||
@@ -1505,7 +1497,6 @@ rstringalpha
|
||||
rstringdigit
|
||||
rtb
|
||||
RTLREADING
|
||||
rtm
|
||||
runas
|
||||
rundll
|
||||
rungameid
|
||||
@@ -1562,8 +1553,8 @@ SETRULES
|
||||
SETSCREENSAVEACTIVE
|
||||
SETSTICKYKEYS
|
||||
SETTEXT
|
||||
settingscard
|
||||
SETTINGCHANGE
|
||||
settingscard
|
||||
SETTINGSCHANGED
|
||||
settingsheader
|
||||
settingshotkeycontrol
|
||||
@@ -1708,6 +1699,7 @@ stringtable
|
||||
stringval
|
||||
Strm
|
||||
strret
|
||||
STRSAFE
|
||||
stscanf
|
||||
sttngs
|
||||
Stubless
|
||||
@@ -1719,7 +1711,6 @@ sublang
|
||||
SUBMODULEUPDATE
|
||||
subresource
|
||||
Superbar
|
||||
suntimes
|
||||
sut
|
||||
svchost
|
||||
SVGIn
|
||||
@@ -1753,7 +1744,6 @@ SYSTEMMODAL
|
||||
SYSTEMTIME
|
||||
TARG
|
||||
TARGETAPPHEADER
|
||||
TARGETDIR
|
||||
targetentrypoint
|
||||
TARGETHEADER
|
||||
targetver
|
||||
@@ -1783,10 +1773,10 @@ textextractor
|
||||
TEXTINCLUDE
|
||||
tfopen
|
||||
tgz
|
||||
THEMECHANGED
|
||||
themeresources
|
||||
THH
|
||||
THICKFRAME
|
||||
THEMECHANGED
|
||||
THISCOMPONENT
|
||||
throughs
|
||||
TILEDWINDOW
|
||||
@@ -1883,7 +1873,6 @@ USEINSTALLERFORTEST
|
||||
USESHOWWINDOW
|
||||
USESTDHANDLES
|
||||
USRDLL
|
||||
utm
|
||||
UType
|
||||
uuidv
|
||||
uwp
|
||||
@@ -1956,11 +1945,11 @@ Wca
|
||||
WCE
|
||||
wcex
|
||||
WClass
|
||||
WCRAPI
|
||||
wcsicmp
|
||||
wcsncpy
|
||||
wcsnicmp
|
||||
WCT
|
||||
WCRAPI
|
||||
WDA
|
||||
wdm
|
||||
wdp
|
||||
@@ -1988,6 +1977,7 @@ WINDOWPLACEMENT
|
||||
WINDOWPOSCHANGED
|
||||
WINDOWPOSCHANGING
|
||||
WINDOWSBUILDNUMBER
|
||||
windowsml
|
||||
windowssearch
|
||||
windowssettings
|
||||
WINDOWSTYLES
|
||||
@@ -2003,9 +1993,8 @@ Winhook
|
||||
WINL
|
||||
winlogon
|
||||
winmd
|
||||
WINNT
|
||||
windowsml
|
||||
winml
|
||||
WINNT
|
||||
winres
|
||||
winrt
|
||||
winsdk
|
||||
@@ -2067,20 +2056,21 @@ WTSAT
|
||||
Wubi
|
||||
WUX
|
||||
Wwanpp
|
||||
xap
|
||||
XAxis
|
||||
XButton
|
||||
xclip
|
||||
xcopy
|
||||
xap
|
||||
XDeployment
|
||||
XDimension
|
||||
xdf
|
||||
XDimension
|
||||
XDocument
|
||||
XElement
|
||||
xfd
|
||||
XFile
|
||||
XIncrement
|
||||
XLoc
|
||||
xmp
|
||||
XNamespace
|
||||
Xoshiro
|
||||
XPels
|
||||
@@ -2091,23 +2081,22 @@ xsi
|
||||
XSpeed
|
||||
XStr
|
||||
xstyler
|
||||
xmp
|
||||
XTimer
|
||||
XUP
|
||||
XVIRTUALSCREEN
|
||||
xxxxxx
|
||||
YAxis
|
||||
ycombinator
|
||||
YIncrement
|
||||
YDimension
|
||||
YIncrement
|
||||
yinle
|
||||
yinyue
|
||||
YPels
|
||||
YPos
|
||||
YResolution
|
||||
YSpeed
|
||||
YTimer
|
||||
YStr
|
||||
YTimer
|
||||
YVIRTUALSCREEN
|
||||
ZEROINIT
|
||||
zonability
|
||||
|
||||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@@ -21,6 +21,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v4
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run GenAI Issue Deduplicator
|
||||
uses: pelikhan/action-genai-issue-dedup@v0
|
||||
|
||||
@@ -291,6 +291,7 @@
|
||||
"Mono.Cecil.Rocks.dll",
|
||||
"Newtonsoft.Json.dll",
|
||||
"CommunityToolkit.WinUI.Controls.TitleBar.dll",
|
||||
"CommunityToolkit.WinUI.Controls.OpacityMaskView.dll",
|
||||
|
||||
"NLog.dll",
|
||||
"HtmlAgilityPack.dll",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.PowerToys.Telemetry" version="2.0.3" />
|
||||
<package id="Microsoft.PowerToys.Telemetry" version="2.0.4" />
|
||||
</packages>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
|
||||
-->
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />
|
||||
|
||||
@@ -834,6 +834,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LanguageModelProvider", "sr
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UI.ViewModels.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.UI.ViewModels.UnitTests\Microsoft.CmdPal.UI.ViewModels.UnitTests.csproj", "{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.RemoteDesktop", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj", "{2B3FB837-23DE-629F-82C6-42304E7083C9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj", "{DB34808A-FF91-D06E-A426-AFB5A8BD583B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -3036,6 +3040,22 @@ Global
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.ActiveCfg = Release|x64
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.Build.0 = Release|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|x64.Build.0 = Debug|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|x64.ActiveCfg = Release|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|x64.Build.0 = Release|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|x64.Build.0 = Debug|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|x64.ActiveCfg = Release|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3367,6 +3387,8 @@ Global
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{45354F4F-1414-45CE-B600-51CD1209FD19} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
240
README.md
240
README.md
@@ -51,19 +51,20 @@ But to get started quickly, choose one of the installation methods below:
|
||||
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
|
||||
|
||||
<!-- items that need to be updated release to release -->
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysUserSetup-0.95.1-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysUserSetup-0.95.1-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysSetup-0.95.1-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysSetup-0.95.1-arm64.exe
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysUserSetup-0.96.1-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysUserSetup-0.96.1-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysSetup-0.96.1-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysSetup-0.96.1-arm64.exe
|
||||
|
||||
| Description | Filename |
|
||||
|----------------|----------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.95.1-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.96.1-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.96.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.96.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.96.1-arm64.exe][ptMachineArm64] |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -102,156 +103,131 @@ There are [community driven install methods](./doc/unofficialInstallMethods.md)
|
||||
</details>
|
||||
|
||||
## ✨ What's new
|
||||
**Version 0.95 (October 2025)**
|
||||
**Version 0.96 (November 2025)**
|
||||
|
||||
For an in-depth look at the latest changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
|
||||
|
||||
**✨ Highlights**
|
||||
- **NEW:** The **Light Switch** utility in PowerToys allows you to automatically switch between light and dark themes in Windows based on the time of day.
|
||||
- Command Palette delivered major search performance gains (new fuzzy matcher and smarter fallbacks) improving relevance and speed.
|
||||
- Peek can now be activated using just the Spacebar!
|
||||
- Find My Mouse added transparent spotlight with independent backdrop opacity, boosting focus and accessibility.
|
||||
- Settings now lets you delete shortcuts entirely and ignore conflicts.
|
||||
- Mouse Pointer Crosshairs gained orientation options (vertical / horizontal / both) for customizable accessibility. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
- PowerRename fixed enumeration counter skipping ensuring reliable batch renames. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- ZoomIt restored legacy draw and snipping behaviors, and fixed recording issues, improving reliability. Thanks [@chakrik73](https://github.com/chakrik73)!
|
||||
- Advanced Paste now supports multiple online and on-device AI model providers: Azure OpenAI, OpenAI, Google Gemini, Mistral, Foundry Local and Ollama.
|
||||
- Command Palette received extensive improvements including file search filters, better clipboard history metadata, context-menu styling, and dozens of bug fixes and enhancements.
|
||||
- PowerRename can now extract and use photo metadata (EXIF, XMP) in renaming patterns like `%Camera`, `%Lens`, and `%ExposureTime`.
|
||||
|
||||
### Advanced Paste
|
||||
- Advanced Paste now lets you connect to multiple AI providers instead of being limited to a single OpenAI provider. See [Advanced Paste documentation](https://learn.microsoft.com/windows/powertoys/advanced-paste) for usage.
|
||||
|
||||
### Awake
|
||||
- The Awake countdown timer now stays accurate over long periods. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed Awake context menu positioning. The fix removed the conversion of the mouse cursor from screen to client-window coordinates, instead using the raw screen coordinates returned by GetCursorPos; the context menu now appears at the correct screen position. Thanks [@lzandman](https://github.com/lzandman)!
|
||||
|
||||
### Command Palette
|
||||
- Applied conditional margin for icon-only tags to tighten layout. Thanks [@samrueby](https://github.com/samrueby)
|
||||
- Improved the reliability of accessing Command Palette settings through PowerToys Settings and executing other x-cmdpal:// protocol commands. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Enabled AOT by default for improved performance while simplifying publish configs.
|
||||
- Replaced service state color dots with play/pause/stop icons for enhanced accessibility. Thanks [@samrueby](https://github.com/samrueby)
|
||||
- Fixed filter dropdown sync and crash by binding SelectedValue and raising UI-thread notifications. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Ensured long links wrap correctly in details view.
|
||||
- Removed animation and enforced minimum width on filter dropdown for clarity. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Restored focus to More button after ESC closes context menu, improving keyboard flow. Thanks [@chatasweetie](https://github.com/chatasweetie)
|
||||
- Marked main and toast windows as tool windows to keep them out of Alt+Tab while preserving style. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Fixed AOT template and theming issues for filter separators. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Introduced grid layouts (small, medium, gallery) for richer page presentation.
|
||||
- Materialized result lists to avoid rescoring overhead.
|
||||
- Disabled problematic selection TextToSuggest behind environment flag.
|
||||
- Major search performance improvements (new fuzzy matcher, smarter fallbacks, fewer exceptions).
|
||||
- Added context menu "Show Details" command when details pane is hidden.
|
||||
- Reduced window flicker by avoiding unnecessary cloaking. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Restored EmptyContent rendering for blank states. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme)
|
||||
- Saved new state even if prior app state file was corrupt (better resilience). Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Migrated settings window to WinUI TitleBar control. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Prevented crash on duplicate keybindings and simplified matching. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Hotkeys now always respect the “Ignore shortcut in fullscreen” setting. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Hid search box on content pages, improving focus and accessibility, and added Home title. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Blocked Ctrl+I from inserting stray tabs in search box.
|
||||
- Logged HRESULT codes in error logs for deeper diagnostics. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Advanced font and emoji icon classification and alignment improvements. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Ensured that fallback command icons are visible on the extension settings page. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Fixed breadcrumb margin misalignment (visual polish). Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Truncated overly long command labels with ellipsis to prevent overflow.
|
||||
- Added a setting to configure the page transition animation.
|
||||
- Collection of small improvements and nits for Run Commands.
|
||||
- Improved bookmarks performance and experience. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Added Ctrl+O shortcut in Clipboard History to open links directly.
|
||||
- Resolved conflict with external software that blocked Command Palette from hiding.
|
||||
- Updated context menu items to reflect name and icon changes, and ensured application icons are displayed correctly. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Added Alt+Home shortcut to return immediately to the Command Palette home page. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Fixed a crash when displaying code blocks in markdown on detail or content pages. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Fixed an issue where the search bar icon and title were not updated when rapidly switching pages. Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Improved the appearance of the search box in the context menu.
|
||||
|
||||
- The search field in context menus now matches the look of the Command Palette, with a smoke backdrop and improved padding.
|
||||
- Fallback items such as math calculations or the Run command now appear in results more quickly. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Ensured the command bar updates correctly after navigating to another page and commands are displayed correctly. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- The Command Palette settings page has been reorganized. Activation-key options are grouped under an expander and extension settings are framed for improved readability.
|
||||
- When you modify a command, its alias, hotkey, and tags now update in the top-level list, keeping the displayed information in sync. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Press `Ctrl + ,` to open Command Palette settings from anywhere. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- You can use `Page Up` and `Page Down` to navigate the list while focus is in the search box. Thanks [@samrueby](https://github.com/samrueby)!
|
||||
- Fixed an issue where the search box could disappear when navigating pages. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Ensured search text is selected when *Go home when activated* and *Highlight search on activate* are both enabled. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Fixed an issue where Command Palette window occasionally appeared on the taskbar under certain Windows settings. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Ensured that labels and icons of list items and menu items update when they change. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Fixed visibility of list filters when navigating to a content page. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme)!
|
||||
- Added search to the extension list and a link to extensions on the Microsoft Store. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added options to open the Command Palette window at its last position or re-center it.
|
||||
- The Command Palette now remembers its window size after restarting.
|
||||
- Added a global error handler that logs fatal errors and provides feedback when unexpected failures force Command Palette to close. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Fixed forms and extension settings not showing on some machines due to a missing VC++ runtime.
|
||||
- Restored ranking of fallback commands for built-in extensions (Sleep, Shutdown, Windows settings, Web search, etc.). Thanks [@jiripolasek](https://github.com/jiripolasek).
|
||||
- Improved and unified labels and texts across the application!
|
||||
- Maintainance: Resolved numerous build warnings in Command Palette projects; no user-visible impact. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Maintainance: Fixed a logging issue so exception messages are properly recorded instead of placeholder text, improving troubleshooting. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
|
||||
### Command Palette Extensions
|
||||
- Replaced localized WebSearch setting keys with stable literals and numeric history count. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Enabled advanced markdown tables and emphasis extensions. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added setting to choose Clipboard History primary action (Paste vs Copy). Thanks [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Added actionable empty-state hints for File Search (search PC / open indexing settings). Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Ensured all WinGet extension assets copy reliably to output. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Improved Run command line parsing for paths with spaces; sped up related tests.
|
||||
- Updated WebSearch extension icon set for enhanced clarity and contrast. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added Terminal profile sort order setting including MRU tracking. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added Uninstall Application command (UWP direct, Win32 via Settings). Thanks [@mKpwnz](https://github.com/mKpwnz)!
|
||||
- Deferred WinGet details loading and added timing logs.
|
||||
- Removed LINQ from All Apps extension for performance.
|
||||
- Added standardized key chord system + shortcuts to File Search commands. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added Terminal channel filter & remembered selection option. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Enabled loading local/data/app images in markdown with sizing hints. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added external extension reload via x-cmdpal://reload (configurable). Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Instant WebSearch history updates with in-memory store & events. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added keep-after-paste option and safe delete with confirmation for Clipboard History. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
|
||||
### Environment Variables
|
||||
- Replaced custom window chrome with WinUI TitleBar for cleaner, maintainable Environment Variables UI.
|
||||
|
||||
### File Locksmith
|
||||
- Adopted WinUI TitleBar to simplify window chrome while preserving appearance.
|
||||
- Bookmarks: Added hints about bookmark placeholders to the Add/Edit Bookmark form. — Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Bookmarks: Improved migration of bookmarks from older versions and fixed an issue where aliases or keyboard shortcuts could be lost after restart. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Clipboard history: Items shown in Command Palette’s clipboard history now include helpful metadata. For example, image items show dimensions, text files show names and sizes, web links include page titles, and text entries display word counts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- File search: Added filter buttons to show *all items*, *files only*, or *folders only*. Selecting a filter adds `kind:folders` or `kind:not folders` to narrow results.
|
||||
- System commands: Replaced the `:red_circle:` placeholder with an actual red-circle emoji so the correct icon appears in the UI. Thanks [@samrueby](https://github.com/samrueby)!
|
||||
- WinGet: Search performance feels more responsive because typed input is now processed via a task queue rather than complex cancellation tokens!
|
||||
- Window Walker: UWP apps no longer show a "not responding" label when suspended. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Window Walker: Now displays the actual icon of each window rather than using the process icon, improving recognition of PWAs and Python GUIs. Thanks [@Lee-WonJun](https://github.com/Lee-WonJun)!
|
||||
- Windows Terminal profiles: Fixed a rare crash in the Windows Terminal extension when the `LOCALAPPDATA` environment variable was missing. The path is now retrieved via a reliable API. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
|
||||
### Find My Mouse
|
||||
- Added transparent spotlight support with separate backdrop opacity; migrated to Windows App SDK composition APIs.
|
||||
- Activating Find My Mouse no longer makes the cursor change to the busy (hourglass) icon or steals focus from your active application.
|
||||
|
||||
### Hosts File Editor
|
||||
- Migrated to native WinUI TitleBar for cleaner, maintainable window chrome.
|
||||
- Added customizable backup settings allowing users to configure backup frequency, location, and auto-deletion policies. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### Image Resizer
|
||||
- Fixed settings consistency during batch resize operations by capturing settings once before processing. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
|
||||
### Light Switch
|
||||
- Introduced as a brand-new PowerToy module.
|
||||
- Automatically switches between light and dark themes.
|
||||
- Supports time-based scheduling or location-based sunrise/sunset switching.
|
||||
- Supports using a keyboard shortcut to force a change.
|
||||
- Supports filtering changes for Apps and/or System Theme.
|
||||
- Introduced new UI to allow users to manually enter their latitude and longitude in Sunrise to Sunset mode.
|
||||
- Refactored service with cleaner state management for stability.
|
||||
- Removed logs from every tick, only logging key events to largely reduce log size.
|
||||
|
||||
### Mouse Pointer Crosshairs
|
||||
- Added Esc key to cancel active gliding cursor sequence. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
- Added orientation option (vertical / horizontal / both) for crosshairs customization. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
- Enabled switching between Mouse Pointer Crosshairs and Gliding Cursor modes. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
|
||||
### Mouse Without Borders
|
||||
- Continued Common class refactor (part 5/7) by extracting clipboard and init/cleanup logic into focused classes. Thanks [@mikeclayton](https://github.com/mikeclayton)!
|
||||
|
||||
- Fix connection failures caused by conflicting MachineId across machines. Thanks [@noraa-junker](https://github.com/noraa-junker) for troubleshooting!
|
||||
- Added horizontal scrolling support. Thanks [@MasonBergstrom](https://github.com/MasonBergstrom)!
|
||||
|
||||
### Peek
|
||||
- Added the option to activate Peek with just the Spacebar.
|
||||
- Fixed media files remaining locked after preview window closes. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Added a command-line interface for file previewing. See the [Peek documentation](https://learn.microsoft.com/windows/powertoys/peek) for usage. Thanks [@prochan2](https://github.com/prochan2)!
|
||||
|
||||
### PowerRename
|
||||
- Fixed enumeration counter skipping when regex replacement equals original filename (counters now advance reliably). Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- PowerRename no longer crashes due to a missing resources file.
|
||||
- Added photo metadata extraction support using EXIF and XMP for pattern-based renaming with camera info, GPS coordinates, and date taken. See [PowerRename Documentation](https://learn.microsoft.com/en-us/windows/powertoys/powerrename).
|
||||
|
||||
### Quick Accent
|
||||
- Expanded Welsh layout with acute, grave, and dieresis variants for vowels (consistent ordering). Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
### PowerToys Run
|
||||
- Added retry logic with exponential backoff to handle DWM composition errors during theme changes. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Updated OneNote icons to reflect new Microsoft 365 design. Thanks [@trevorNgo](https://github.com/trevorNgo)!
|
||||
|
||||
### Registry Preview
|
||||
- Migrated to native TitleBar and AppWindow APIs for cleaner window chrome.
|
||||
### Quick Accent
|
||||
- Added diameter symbol (⌀) for Shift+O in Special Characters mode, thanks to [@anselumjuju](https://github.com/anselumjuju)!
|
||||
|
||||
### Screen Ruler
|
||||
- Fixed ARM64 crash by aligning cursor position structure to 8-byte boundary.
|
||||
### Zoomit
|
||||
- Smoothed out zoom-animation in ZoomIt by coalescing mouse-move and timer events, thanks to [@foxmsft](https://github.com/foxmsft)!
|
||||
- Enabled GIF support for ZoomIt, thanks to [@MarioHewardt](https://github.com/MarioHewardt)!
|
||||
- Fixed spelling mistakes, and refactored some literal strings to string constants, thanks to [@lzandman](https://github.com/lzandman)!
|
||||
- Fixed inaccurate "actual size" screenshots in ZoomIt and resolves a GDI handle leak, improving capture fidelity and long-session stability. thanks to [@daverayment](https://github.com/daverayment)!
|
||||
|
||||
### Settings
|
||||
- Added ability to ignore specific hotkey conflicts to reduce noise.
|
||||
- Stopped creating backup directory during dry-run status checks (cleaner first-run).
|
||||
- Standardized casing and localization for ZoomIt and modules header.
|
||||
- Improved search results page accessibility and conditional module grouping.
|
||||
- Fixed title bar overlapping issue at smaller window sizes.
|
||||
- Refined shortcut control visual design with improved consistency and spacing.
|
||||
- Added dashboard utilities sorting by name or status.
|
||||
- Made update notification InfoBar in flyout clickable for direct navigation to update page.
|
||||
- Expanded installation instructions by default in README.
|
||||
- Improved accessibility for shortcut conflict button with static resource-based automation properties.
|
||||
- Added ScrollViewer to Command Palette page in PowerToys Settings. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Fixed module list glitches and Sort Status checkmark issue. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
|
||||
### ZoomIt
|
||||
- Updated resource file to reflect standalone v9.01 and current copyright year. Thanks [@foxmsft](https://github.com/foxmsft)!
|
||||
- Restored legacy draw/snipping behaviors and fixed recording race conditions. Thanks [@chakrik73](https://github.com/chakrik73)!
|
||||
- Added smooth image option for improved zoom quality using GDI+ for static zoom and Magnifier API for live zoom. Thanks [@markrussinovich](https://github.com/markrussinovich)!
|
||||
|
||||
### Documentation
|
||||
- New Microsoft Learn documentation for the Light Switch module.
|
||||
- New dev docs for the Light Switch module.
|
||||
|
||||
### Development (Area-Build & Area-Tests)
|
||||
- Allowed debug launches to continue when modules fail to load, speeding developer iteration.
|
||||
- Fixed spell checker dictionary entry (advapi) to eliminate false error.
|
||||
- Added VS Code development guide and launch configs to streamline cross-editor workflows.
|
||||
- Upgraded Windows App SDK and related dependencies to 1.8 for newer platform features.
|
||||
- Rewrote YAML comment to resolve new spell checker forbidden pattern. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Corrected solution structure by returning misplaced Common project, reducing build confusion.
|
||||
- Modernized build scripts with shared helpers and VS environment autodetection for simpler CLI builds.
|
||||
- Standardized build scripts and platform detection to improve reliability and reuse.
|
||||
- Added missing Command Palette version bump to align module release cadence.
|
||||
- Added EXECUTEDEFAULT term to dictionary to prevent regression build failures. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Introduced nightly pre-warm pipeline and configurable MSBuild cache mode to improve CI performance.
|
||||
- Resolved CI forbidden pattern spelling complaint to keep pipelines green.
|
||||
- Added AI contributor instruction set to clarify code area expectations.
|
||||
- Added accessibility IDs to settings and FancyZones toggles, stabilizing UI tests.
|
||||
- Added automatic log collection on UI test failures to speed root cause analysis.
|
||||
- Stabilized Mouse Utils tests by switching to AccessibilityId selectors.
|
||||
- Added Screen Ruler UI test coverage to validate core measurement workflows.
|
||||
### Development
|
||||
- Fixed accessibility by associating controls with labels for screen readers.
|
||||
- Added accessible name to Shortcut Conflicts button for screen readers.
|
||||
- Excluded TitleBars from tab navigation across multiple utilities. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Migrated build infrastructure from Windows Server 2019 to Server 2022 with improved failure logging and predictable NuGet package paths.
|
||||
- Configured build agents to use larger P: drive for release builds to address disk space constraints.
|
||||
- Enhanced DSC v3 support by organizing resource manifests in a dedicated subfolder with PATH configuration.
|
||||
- Reduced installer bundle size by 6-7MB through centralized Hybrid CRT configuration across all C++ projects.
|
||||
- Updated .NET packages to version 9.0.10 for security fixes. Thanks [@snickler](https://github.com/snickler)!
|
||||
- Fixed spell check dictionary entries for consistency.
|
||||
- Restored accidentally deleted NuGet configuration file for Command Palette extensions.
|
||||
- Fixed package identity build by updating AppxManifest entry points to use PowerShell Core.
|
||||
- Optimized CI pipeline by replacing file copy operations with hard links and moves, reducing build time and disk usage by 10-15GB.
|
||||
- Updated Copilot guidance and PR prompt workflow.
|
||||
- Included high-volume bugs in issue template header. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed incorrect HRESULT logging for inner exceptions. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Introduced shared sparse package identity for PowerToys Win32 components to enable access to Windows platform APIs.
|
||||
- Consolidated installer builds to produce both machine and user installers simultaneously, reducing build time and complexity.
|
||||
- Migrated exclusively to WiX v5 installer infrastructure, removing legacy WiX v3 support.
|
||||
- Temporarily removed PowerToys installer path from PATH environment variable to prevent application crashes.
|
||||
- Added complete OCR UI test coverage with automated tests for activation, settings, language selection, and text extraction.
|
||||
- Fixed test input for drive path normalization in bookmark resolver unit tests.
|
||||
- Fixed Peek UI tests by restoring Ctrl+Space activation shortcut for test scenarios.
|
||||
- Hided apps in PowerToys.SpareApps package from Start Menu. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
|
||||
## 🛣️ Roadmap
|
||||
We are planning some nice new features and improvements for the next releases – a revamped Keyboard Manager UI, custom endpoint and local model support for Advanced Paste, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.96][github-next-release-work]!
|
||||
|
||||
@@ -33,9 +33,12 @@ The **Light Switch** module lets users automatically transition between light an
|
||||
|
||||
> **Note:** Using the shortcut overrides the current schedule until the next transition event.
|
||||
|
||||
* **LightSwitchService**
|
||||
Reads settings and applies theming. Runs a check every minute to ensure the state is correct.
|
||||
|
||||
* **LightSwitchService.cpp**
|
||||
is the heart beat of the module. Controls ticking every minute and depending on user actions (manual override, settings changing, etc) triggers the state manager to perform the corresponding operation.
|
||||
|
||||
* **LightSwitchStateManager.cpp**
|
||||
handles updating the state based on the signals sent by LightSwitchService.
|
||||
|
||||
* **SettingsXAML/LightSwitch**
|
||||
Provides the settings UI for configuring schedules, syncing location, and customizing shortcuts.
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
Description="PowerToys OCR Module"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
Square44x44Logo="Images\Square44x44Logo.png"
|
||||
AppListEntry="none">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
<Application Id="PowerToys.SettingsUI" Executable="WinUI3Apps\PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
@@ -51,7 +52,8 @@
|
||||
Description="PowerToys Settings UI"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
Square44x44Logo="Images\Square44x44Logo.png"
|
||||
AppListEntry="none">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
@@ -60,7 +62,8 @@
|
||||
Description="PowerToys Image Resizer UI"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
Square44x44Logo="Images\Square44x44Logo.png"
|
||||
AppListEntry="none">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
@@ -10,6 +10,23 @@ namespace LanguageModelProvider.FoundryLocal;
|
||||
internal sealed class FoundryClient
|
||||
{
|
||||
public static async Task<FoundryClient?> CreateAsync()
|
||||
{
|
||||
// First attempt with current environment
|
||||
var client = await TryCreateClientAsync().ConfigureAwait(false);
|
||||
if (client != null)
|
||||
{
|
||||
return client;
|
||||
}
|
||||
|
||||
// If failed, refresh PATH from registry and retry once
|
||||
// This handles cases where PowerToys was launched by MSI installer.
|
||||
Logger.LogInfo("[FoundryClient] First attempt failed, refreshing PATH and retrying");
|
||||
RefreshEnvironmentPath();
|
||||
|
||||
return await TryCreateClientAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<FoundryClient?> TryCreateClientAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -195,4 +212,68 @@ internal sealed class FoundryClient
|
||||
await _foundryManager.StartServiceAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the PATH environment variable from the system registry.
|
||||
/// This is necessary when tools are installed while PowerToys is running,
|
||||
/// as the installer updates the system PATH but running processes don't see the change.
|
||||
/// </summary>
|
||||
private static void RefreshEnvironmentPath()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo("[FoundryClient] Refreshing PATH environment variable from system");
|
||||
|
||||
var currentPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) ?? string.Empty;
|
||||
var machinePath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) ?? string.Empty;
|
||||
var userPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty;
|
||||
|
||||
var pathsToAdd = new List<string>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(currentPath))
|
||||
{
|
||||
pathsToAdd.AddRange(currentPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userPath))
|
||||
{
|
||||
var userPaths = userPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var path in userPaths)
|
||||
{
|
||||
if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
pathsToAdd.Add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(machinePath))
|
||||
{
|
||||
var machinePaths = machinePath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var path in machinePaths)
|
||||
{
|
||||
if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
pathsToAdd.Add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newPath = string.Join(Path.PathSeparator.ToString(), pathsToAdd);
|
||||
|
||||
if (currentPath != newPath)
|
||||
{
|
||||
Logger.LogInfo("[FoundryClient] Updating process PATH with latest system values");
|
||||
Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.Process);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo("[FoundryClient] PATH is already up to date");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"[FoundryClient] Failed to refresh PATH: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@ namespace LanguageModelProvider;
|
||||
|
||||
public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
{
|
||||
private IEnumerable<ModelDetails>? _downloadedModels;
|
||||
private IEnumerable<FoundryCatalogModel>? _catalogModels;
|
||||
private FoundryClient? _foundryClient;
|
||||
private IEnumerable<FoundryCatalogModel>? _catalogModels;
|
||||
private string? _serviceUrl;
|
||||
|
||||
public static FoundryLocalModelProvider Instance { get; } = new();
|
||||
@@ -43,15 +42,6 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
// Check if model is cached
|
||||
var isInCache = _downloadedModels?.Any(m => m.ProviderModelDetails is FoundryCachedModel cached && cached.Name == modelId) ?? false;
|
||||
if (!isInCache)
|
||||
{
|
||||
var errorMessage = $"The requested model '{modelId}' is not cached. Please download it using Foundry Local.";
|
||||
Logger.LogError($"[FoundryLocal] {errorMessage}");
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
// Ensure the model is loaded before returning chat client
|
||||
var isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
if (!isLoaded)
|
||||
@@ -74,7 +64,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
|
||||
return new OpenAIClient(
|
||||
new ApiKeyCredential("none"),
|
||||
new OpenAIClientOptions { Endpoint = endpointUri })
|
||||
new OpenAIClientOptions { Endpoint = endpointUri, NetworkTimeout = TimeSpan.FromMinutes(5) })
|
||||
.GetChatClient(modelId)
|
||||
.AsIChatClient();
|
||||
}
|
||||
@@ -100,30 +90,38 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
return $"new OpenAIClient(new ApiKeyCredential(\"none\"), new OpenAIClientOptions{{ Endpoint = new Uri(\"{_serviceUrl}/v1\") }}).GetChatClient(\"{modelId}\").AsIChatClient()";
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ModelDetails>> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default)
|
||||
public async Task<IEnumerable<ModelDetails>> GetModelsAsync(CancellationToken cancelationToken = default)
|
||||
{
|
||||
if (ignoreCached)
|
||||
{
|
||||
Logger.LogInfo("[FoundryLocal] Ignoring cached models, resetting");
|
||||
Reset();
|
||||
}
|
||||
|
||||
await InitializeAsync(cancelationToken);
|
||||
|
||||
Logger.LogInfo($"[FoundryLocal] Returning {_downloadedModels?.Count() ?? 0} downloaded models");
|
||||
return _downloadedModels ?? [];
|
||||
}
|
||||
if (_foundryClient == null)
|
||||
{
|
||||
return Array.Empty<ModelDetails>();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_downloadedModels = null;
|
||||
_catalogModels = null;
|
||||
_ = InitializeAsync();
|
||||
var cachedModels = await _foundryClient.ListCachedModels();
|
||||
List<ModelDetails> downloadedModels = [];
|
||||
|
||||
foreach (var model in cachedModels)
|
||||
{
|
||||
Logger.LogInfo($"[FoundryLocal] Adding unmatched cached model: {model.Name}");
|
||||
downloadedModels.Add(new ModelDetails
|
||||
{
|
||||
Id = $"fl-{model.Name}",
|
||||
Name = model.Name,
|
||||
Url = $"fl://{model.Name}",
|
||||
Description = $"{model.Name} running locally with Foundry Local",
|
||||
HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
|
||||
ProviderModelDetails = model,
|
||||
});
|
||||
}
|
||||
|
||||
return downloadedModels;
|
||||
}
|
||||
|
||||
private async Task InitializeAsync(CancellationToken cancelationToken = default)
|
||||
{
|
||||
if (_foundryClient != null && _downloadedModels != null && _downloadedModels.Any() && _catalogModels != null && _catalogModels.Any())
|
||||
if (_foundryClient != null && _catalogModels != null && _catalogModels.Any())
|
||||
{
|
||||
await _foundryClient.EnsureRunning().ConfigureAwait(false);
|
||||
return;
|
||||
@@ -145,29 +143,6 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
var catalogModels = await _foundryClient.ListCatalogModels();
|
||||
Logger.LogInfo($"[FoundryLocal] Found {catalogModels.Count} catalog models");
|
||||
_catalogModels = catalogModels;
|
||||
|
||||
var cachedModels = await _foundryClient.ListCachedModels();
|
||||
Logger.LogInfo($"[FoundryLocal] Found {cachedModels.Count} cached models");
|
||||
|
||||
List<ModelDetails> downloadedModels = [];
|
||||
|
||||
foreach (var model in cachedModels)
|
||||
{
|
||||
Logger.LogInfo($"[FoundryLocal] Adding unmatched cached model: {model.Name}");
|
||||
downloadedModels.Add(new ModelDetails
|
||||
{
|
||||
Id = $"fl-{model.Name}",
|
||||
Name = model.Name,
|
||||
Url = $"fl://{model.Name}",
|
||||
Description = $"{model.Name} running locally with Foundry Local",
|
||||
HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
|
||||
SupportedOnQualcomm = true,
|
||||
ProviderModelDetails = model,
|
||||
});
|
||||
}
|
||||
|
||||
_downloadedModels = downloadedModels;
|
||||
Logger.LogInfo($"[FoundryLocal] Initialization complete. Total downloaded models: {downloadedModels.Count}");
|
||||
}
|
||||
|
||||
public async Task<bool> IsAvailable()
|
||||
|
||||
@@ -12,7 +12,7 @@ public interface ILanguageModelProvider
|
||||
|
||||
string ProviderDescription { get; }
|
||||
|
||||
Task<IEnumerable<ModelDetails>> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default);
|
||||
Task<IEnumerable<ModelDetails>> GetModelsAsync(CancellationToken cancelationToken = default);
|
||||
|
||||
IChatClient? GetIChatClient(string modelId);
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ public class ModelDetails
|
||||
|
||||
public List<HardwareAccelerator> HardwareAccelerators { get; set; } = [];
|
||||
|
||||
public bool SupportedOnQualcomm { get; set; }
|
||||
|
||||
public string License { get; set; } = string.Empty;
|
||||
|
||||
public object? ProviderModelDetails { get; set; }
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace ManagedCommon
|
||||
/// </summary>
|
||||
public static string CurrentVersionLogDirectoryPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the current log file.
|
||||
/// </summary>
|
||||
public static string CurrentLogFile { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the log directory for the app.
|
||||
/// </summary>
|
||||
@@ -55,7 +60,9 @@ namespace ManagedCommon
|
||||
AppLogDirectoryPath = basePath;
|
||||
CurrentVersionLogDirectoryPath = versionedPath;
|
||||
|
||||
var logFilePath = Path.Combine(versionedPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
|
||||
var logFile = "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log";
|
||||
var logFilePath = Path.Combine(versionedPath, logFile);
|
||||
CurrentLogFile = logFilePath;
|
||||
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ public sealed class AIServiceBatchIntegrationTests
|
||||
switch (format)
|
||||
{
|
||||
case PasteFormats.CustomTextTransformation:
|
||||
var transformResult = await services.CustomActionTransformService.TransformTextAsync(batchTestInput.Prompt, batchTestInput.Clipboard, CancellationToken.None, progress);
|
||||
var transformResult = await services.CustomActionTransformService.TransformAsync(batchTestInput.Prompt, batchTestInput.Clipboard, null, CancellationToken.None, progress);
|
||||
return DataPackageHelpers.CreateFromText(transformResult.Content ?? string.Empty);
|
||||
|
||||
case PasteFormats.KernelQuery:
|
||||
|
||||
@@ -299,47 +299,49 @@
|
||||
</StackPanel>
|
||||
</controls:PromptBox.Footer>
|
||||
</controls:PromptBox>
|
||||
<Grid Grid.Row="2" RowSpacing="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{x:Bind ViewModel.StandardPasteFormats.Count, Mode=OneWay, Converter={StaticResource standardPasteFormatsToHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsToMinHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListView
|
||||
x:Name="PasteOptionsListView"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Bottom"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PasteFormat_ItemClick"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Auto"
|
||||
SelectionMode="None"
|
||||
TabIndex="1" />
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
Visibility="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
|
||||
<ScrollViewer Grid.Row="2">
|
||||
<Grid RowSpacing="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{x:Bind ViewModel.StandardPasteFormats.Count, Mode=OneWay, Converter={StaticResource standardPasteFormatsToHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsToMinHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListView
|
||||
x:Name="PasteOptionsListView"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Bottom"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PasteFormat_ItemClick"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
SelectionMode="None"
|
||||
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"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PasteFormat_ItemClick"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Auto"
|
||||
SelectionMode="None"
|
||||
TabIndex="2" />
|
||||
</Grid>
|
||||
<ListView
|
||||
x:Name="CustomActionsListView"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Top"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PasteFormat_ItemClick"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
SelectionMode="None"
|
||||
TabIndex="2" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -225,6 +225,24 @@ internal static class DataPackageHelpers
|
||||
internal static async Task<string> GetHtmlContentAsync(this DataPackageView dataPackageView) =>
|
||||
dataPackageView.Contains(StandardDataFormats.Html) ? await dataPackageView.GetHtmlFormatAsync() : string.Empty;
|
||||
|
||||
internal static async Task<byte[]> GetImageAsPngBytesAsync(this DataPackageView dataPackageView)
|
||||
{
|
||||
var bitmap = await dataPackageView.GetImageContentAsync();
|
||||
if (bitmap == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using var pngStream = new InMemoryRandomAccessStream();
|
||||
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, pngStream);
|
||||
encoder.SetSoftwareBitmap(bitmap);
|
||||
await encoder.FlushAsync();
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
await pngStream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
internal static async Task<SoftwareBitmap> GetImageContentAsync(this DataPackageView dataPackageView)
|
||||
{
|
||||
using var stream = await dataPackageView.GetImageStreamAsync();
|
||||
|
||||
@@ -166,5 +166,8 @@ namespace AdvancedPaste.Helpers
|
||||
|
||||
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
internal static extern HResult AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, [In][Out] ref uint pcchOut);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern uint GetClipboardSequenceNumber();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public enum PasteFormats
|
||||
CanPreview = true,
|
||||
SupportedClipboardFormats = ClipboardFormat.Image,
|
||||
IPCKey = AdvancedPasteAdditionalActions.PropertyNames.ImageToText,
|
||||
KernelFunctionDescription = "Takes an image in the clipboard and extracts all text from it using OCR.")]
|
||||
KernelFunctionDescription = "Takes an image from the clipboard and extracts text using OCR. This function is intended only for explicit text extraction or OCR requests.")]
|
||||
ImageToText,
|
||||
|
||||
[PasteFormatMetadata(
|
||||
@@ -118,8 +118,8 @@ public enum PasteFormats
|
||||
IconGlyph = "\uE945",
|
||||
RequiresAIService = true,
|
||||
CanPreview = true,
|
||||
SupportedClipboardFormats = ClipboardFormat.Text,
|
||||
KernelFunctionDescription = "Takes input instructions and transforms clipboard text (not TXT files) with these input instructions, putting the result back on the clipboard. This uses AI to accomplish the task.",
|
||||
SupportedClipboardFormats = ClipboardFormat.Text | ClipboardFormat.Image,
|
||||
KernelFunctionDescription = "Takes user instructions and applies them to the current clipboard content (text or image). Use this function for image analysis, description, or transformation tasks beyond simple OCR.",
|
||||
RequiresPrompt = true)]
|
||||
CustomTextTransformation,
|
||||
}
|
||||
|
||||
@@ -215,7 +215,6 @@ public sealed class AdvancedAIKernelService : KernelServiceBase
|
||||
return new OpenAIPromptExecutionSettings
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
|
||||
Temperature = 0.01,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,15 +40,15 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
this.userSettings = userSettings;
|
||||
}
|
||||
|
||||
public async Task<CustomActionTransformResult> TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public async Task<CustomActionTransformResult> TransformAsync(string prompt, string inputText, byte[] imageBytes, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var pasteConfig = userSettings?.PasteAIConfiguration;
|
||||
var providerConfig = BuildProviderConfig(pasteConfig);
|
||||
|
||||
return await TransformAsync(prompt, inputText, providerConfig, cancellationToken, progress);
|
||||
return await TransformAsync(prompt, inputText, imageBytes, providerConfig, cancellationToken, progress);
|
||||
}
|
||||
|
||||
private async Task<CustomActionTransformResult> TransformAsync(string prompt, string inputText, PasteAIConfig providerConfig, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
private async Task<CustomActionTransformResult> TransformAsync(string prompt, string inputText, byte[] imageBytes, PasteAIConfig providerConfig, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(providerConfig);
|
||||
|
||||
@@ -57,9 +57,9 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
return new CustomActionTransformResult(string.Empty, AIServiceUsage.None);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(inputText))
|
||||
if (string.IsNullOrWhiteSpace(inputText) && imageBytes is null)
|
||||
{
|
||||
Logger.LogWarning("Clipboard has no usable text data");
|
||||
Logger.LogWarning("Clipboard has no usable data");
|
||||
return new CustomActionTransformResult(string.Empty, AIServiceUsage.None);
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
{
|
||||
Prompt = prompt,
|
||||
InputText = inputText,
|
||||
ImageBytes = imageBytes,
|
||||
ImageMimeType = imageBytes != null ? "image/png" : null,
|
||||
SystemPrompt = systemPrompt,
|
||||
};
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
||||
var options = new ChatOptions
|
||||
{
|
||||
ModelId = modelReference,
|
||||
MaxOutputTokens = 2048,
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(systemPrompt))
|
||||
|
||||
@@ -12,6 +12,6 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
{
|
||||
public interface ICustomActionTransformService
|
||||
{
|
||||
Task<CustomActionTransformResult> TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress<double> progress);
|
||||
Task<CustomActionTransformResult> TransformAsync(string prompt, string inputText, byte[] imageBytes, CancellationToken cancellationToken, IProgress<double> progress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
|
||||
public string InputText { get; init; }
|
||||
|
||||
public byte[] ImageBytes { get; init; }
|
||||
|
||||
public string ImageMimeType { get; init; }
|
||||
|
||||
public string SystemPrompt { get; init; }
|
||||
|
||||
public AIServiceUsage Usage { get; set; } = AIServiceUsage.None;
|
||||
|
||||
@@ -64,21 +64,13 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
|
||||
var prompt = request.Prompt;
|
||||
var inputText = request.InputText;
|
||||
if (string.IsNullOrWhiteSpace(prompt) || string.IsNullOrWhiteSpace(inputText))
|
||||
var imageBytes = request.ImageBytes;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(prompt) || (string.IsNullOrWhiteSpace(inputText) && imageBytes is null))
|
||||
{
|
||||
throw new ArgumentException("Prompt and input text must be provided", nameof(request));
|
||||
throw new ArgumentException("Prompt and input content must be provided", nameof(request));
|
||||
}
|
||||
|
||||
var userMessageContent = $"""
|
||||
User instructions:
|
||||
{prompt}
|
||||
|
||||
Clipboard Content:
|
||||
{inputText}
|
||||
|
||||
Output:
|
||||
""";
|
||||
|
||||
var executionSettings = CreateExecutionSettings();
|
||||
var kernel = CreateKernel();
|
||||
var modelId = _config.Model;
|
||||
@@ -102,7 +94,32 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
|
||||
var chatHistory = new ChatHistory();
|
||||
chatHistory.AddSystemMessage(systemPrompt);
|
||||
chatHistory.AddUserMessage(userMessageContent);
|
||||
|
||||
if (imageBytes != null)
|
||||
{
|
||||
var collection = new ChatMessageContentItemCollection();
|
||||
if (!string.IsNullOrWhiteSpace(inputText))
|
||||
{
|
||||
collection.Add(new TextContent($"Clipboard Content:\n{inputText}"));
|
||||
}
|
||||
|
||||
collection.Add(new ImageContent(imageBytes, request.ImageMimeType ?? "image/png"));
|
||||
collection.Add(new TextContent($"User instructions:\n{prompt}\n\nOutput:"));
|
||||
chatHistory.AddUserMessage(collection);
|
||||
}
|
||||
else
|
||||
{
|
||||
var userMessageContent = $"""
|
||||
User instructions:
|
||||
{prompt}
|
||||
|
||||
Clipboard Content:
|
||||
{inputText}
|
||||
|
||||
Output:
|
||||
""";
|
||||
chatHistory.AddUserMessage(userMessageContent);
|
||||
}
|
||||
|
||||
var response = await chatService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel, cancellationToken);
|
||||
chatHistory.Add(response);
|
||||
@@ -157,8 +174,6 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
{
|
||||
AIServiceType.OpenAI or AIServiceType.AzureOpenAI => new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Temperature = 0.01,
|
||||
MaxTokens = 2000,
|
||||
FunctionChoiceBehavior = null,
|
||||
},
|
||||
_ => new PromptExecutionSettings(),
|
||||
|
||||
@@ -67,12 +67,36 @@ public abstract class KernelServiceBase(
|
||||
|
||||
LogResult(cacheUsed, isSavedQuery, kernel.GetOrAddActionChain(), usage);
|
||||
|
||||
var outputPackage = kernel.GetDataPackage();
|
||||
var hasUsableData = await outputPackage.GetView().HasUsableDataAsync();
|
||||
|
||||
if (kernel.GetLastError() is Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
// If we have an error, but the AI provided a final text response, we can ignore the error (likely a tool failure that the AI handled).
|
||||
// However, if we have usable data (e.g. from a successful tool call before the error?), we might want to keep it?
|
||||
// In the case of ImageToText failure, outputPackage is empty (new DataPackage), hasUsableData is false.
|
||||
// So we check if there is a valid response in the chat history.
|
||||
var lastMessage = chatHistory.LastOrDefault();
|
||||
bool hasAssistantResponse = lastMessage != null && lastMessage.Role == AuthorRole.Assistant && !string.IsNullOrEmpty(lastMessage.Content);
|
||||
|
||||
if (!hasAssistantResponse && !hasUsableData)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// If we have a response or data, we log the error but proceed.
|
||||
Logger.LogWarning($"Kernel operation encountered an error but proceeded with available response/data: {ex.Message}");
|
||||
}
|
||||
|
||||
var outputPackage = kernel.GetDataPackage();
|
||||
if (!hasUsableData)
|
||||
{
|
||||
var lastMessage = chatHistory.LastOrDefault();
|
||||
if (lastMessage != null && lastMessage.Role == AuthorRole.Assistant && !string.IsNullOrEmpty(lastMessage.Content))
|
||||
{
|
||||
outputPackage = DataPackageHelpers.CreateFromText(lastMessage.Content);
|
||||
kernel.SetDataPackage(outputPackage);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(await outputPackage.GetView().HasUsableDataAsync()))
|
||||
{
|
||||
@@ -148,7 +172,21 @@ public abstract class KernelServiceBase(
|
||||
var systemPrompt = string.IsNullOrWhiteSpace(runtimeConfig.SystemPrompt) ? DefaultSystemPrompt : runtimeConfig.SystemPrompt;
|
||||
chatHistory.AddSystemMessage(systemPrompt);
|
||||
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
|
||||
chatHistory.AddUserMessage(prompt);
|
||||
|
||||
var imageBytes = await kernel.GetDataPackageView().GetImageAsPngBytesAsync();
|
||||
if (imageBytes != null)
|
||||
{
|
||||
var collection = new ChatMessageContentItemCollection
|
||||
{
|
||||
new TextContent(prompt),
|
||||
new ImageContent(imageBytes, "image/png"),
|
||||
};
|
||||
chatHistory.AddUserMessage(collection);
|
||||
}
|
||||
else
|
||||
{
|
||||
chatHistory.AddUserMessage(prompt);
|
||||
}
|
||||
|
||||
if (ShouldModerateAdvancedAI())
|
||||
{
|
||||
@@ -302,8 +340,16 @@ public abstract class KernelServiceBase(
|
||||
new ActionChainItem(PasteFormats.CustomTextTransformation, Arguments: new() { { PromptParameterName, fixedPrompt } }),
|
||||
async dataPackageView =>
|
||||
{
|
||||
var input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken());
|
||||
var result = await _customActionTransformService.TransformTextAsync(fixedPrompt, input, kernel.GetCancellationToken(), kernel.GetProgress());
|
||||
var imageBytes = await dataPackageView.GetImageAsPngBytesAsync();
|
||||
var input = await dataPackageView.GetTextOrHtmlTextAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(input) && imageBytes == null)
|
||||
{
|
||||
// If we have no text and no image, try to get text via OCR or throw if nothing exists
|
||||
input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken());
|
||||
}
|
||||
|
||||
var result = await _customActionTransformService.TransformAsync(fixedPrompt, input, imageBytes, kernel.GetCancellationToken(), kernel.GetProgress());
|
||||
return DataPackageHelpers.CreateFromText(result?.Content ?? string.Empty);
|
||||
});
|
||||
|
||||
@@ -313,15 +359,22 @@ public abstract class KernelServiceBase(
|
||||
new ActionChainItem(format, Arguments: new() { { PromptParameterName, prompt } }),
|
||||
async dataPackageView =>
|
||||
{
|
||||
var input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken());
|
||||
string output = await GetPromptBasedOutput(format, prompt, input, kernel.GetCancellationToken(), kernel.GetProgress());
|
||||
var imageBytes = await dataPackageView.GetImageAsPngBytesAsync();
|
||||
var input = await dataPackageView.GetTextOrHtmlTextAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(input) && imageBytes == null)
|
||||
{
|
||||
input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken());
|
||||
}
|
||||
|
||||
string output = await GetPromptBasedOutput(format, prompt, input, imageBytes, kernel.GetCancellationToken(), kernel.GetProgress());
|
||||
return DataPackageHelpers.CreateFromText(output);
|
||||
});
|
||||
|
||||
private async Task<string> GetPromptBasedOutput(PasteFormats format, string prompt, string input, CancellationToken cancellationToken, IProgress<double> progress) =>
|
||||
private async Task<string> GetPromptBasedOutput(PasteFormats format, string prompt, string input, byte[] imageBytes, CancellationToken cancellationToken, IProgress<double> progress) =>
|
||||
format switch
|
||||
{
|
||||
PasteFormats.CustomTextTransformation => (await _customActionTransformService.TransformTextAsync(prompt, input, cancellationToken, progress))?.Content ?? string.Empty,
|
||||
PasteFormats.CustomTextTransformation => (await _customActionTransformService.TransformAsync(prompt, input, imageBytes, cancellationToken, progress))?.Content ?? string.Empty,
|
||||
_ => throw new ArgumentException($"Unsupported format {format} for prompt transform", nameof(format)),
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed class PasteFormatExecutor(IKernelService kernelService, ICustomAct
|
||||
pasteFormat.Format switch
|
||||
{
|
||||
PasteFormats.KernelQuery => await _kernelService.TransformClipboardAsync(pasteFormat.Prompt, clipboardData, pasteFormat.IsSavedQuery, cancellationToken, progress),
|
||||
PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText((await _customActionTransformService.TransformTextAsync(pasteFormat.Prompt, await clipboardData.GetClipboardTextOrThrowAsync(cancellationToken), cancellationToken, progress))?.Content ?? string.Empty),
|
||||
PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText((await _customActionTransformService.TransformAsync(pasteFormat.Prompt, await clipboardData.GetTextOrHtmlTextAsync(), await clipboardData.GetImageAsPngBytesAsync(), cancellationToken, progress))?.Content ?? string.Empty),
|
||||
_ => await TransformHelpers.TransformAsync(format, clipboardData, cancellationToken, progress),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace AdvancedPaste.ViewModels
|
||||
private CancellationTokenSource _pasteActionCancellationTokenSource;
|
||||
|
||||
private string _currentClipboardHistoryId;
|
||||
private uint _lastClipboardSequenceNumber;
|
||||
private DateTimeOffset? _currentClipboardTimestamp;
|
||||
private ClipboardFormat _lastClipboardFormats = ClipboardFormat.None;
|
||||
private bool _clipboardHistoryUnavailableLogged;
|
||||
@@ -455,6 +456,7 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
ResetClipboardPreview();
|
||||
_currentClipboardHistoryId = null;
|
||||
_lastClipboardSequenceNumber = 0;
|
||||
_currentClipboardTimestamp = null;
|
||||
_lastClipboardFormats = ClipboardFormat.None;
|
||||
return;
|
||||
@@ -477,6 +479,13 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
bool clipboardChanged = formatsChanged;
|
||||
|
||||
var currentSequenceNumber = NativeMethods.GetClipboardSequenceNumber();
|
||||
if (_lastClipboardSequenceNumber != currentSequenceNumber)
|
||||
{
|
||||
clipboardChanged = true;
|
||||
_lastClipboardSequenceNumber = currentSequenceNumber;
|
||||
}
|
||||
|
||||
if (Clipboard.IsHistoryEnabled())
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,248 +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.
|
||||
|
||||
// <summary>
|
||||
// Encrypt/decrypt implementation.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using MouseWithoutBorders.Core;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal partial class Common
|
||||
{
|
||||
#pragma warning disable SYSLIB0021
|
||||
private static AesCryptoServiceProvider symAl;
|
||||
#pragma warning restore SYSLIB0021
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
|
||||
internal static string myKey;
|
||||
#pragma warning restore SA1307
|
||||
private static uint magicNumber;
|
||||
private static Random ran = new(); // Used for non encryption related functionality.
|
||||
internal const int SymAlBlockSize = 16;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the first encryption block, the following blocks will be combined with the cipher text of the previous block.
|
||||
/// Thus identical blocks in the socket stream would be encrypted to different cipher text blocks.
|
||||
/// The first block is a handshake one containing random data.
|
||||
/// Related Unit Test: TestEncryptDecrypt
|
||||
/// </summary>
|
||||
internal static readonly string InitialIV = ulong.MaxValue.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
internal static Random Ran
|
||||
{
|
||||
get => Common.ran ??= new Random();
|
||||
set => Common.ran = value;
|
||||
}
|
||||
|
||||
internal static uint MagicNumber
|
||||
{
|
||||
get => Common.magicNumber;
|
||||
set => Common.magicNumber = value;
|
||||
}
|
||||
|
||||
internal static string MyKey
|
||||
{
|
||||
get => Common.myKey;
|
||||
|
||||
set
|
||||
{
|
||||
if (Common.myKey != value)
|
||||
{
|
||||
Common.myKey = value;
|
||||
_ = Task.Factory.StartNew(
|
||||
() => Common.GenLegalKey(),
|
||||
System.Threading.CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
TaskScheduler.Default); // Cache the key to improve UX.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static string KeyDisplayedText(string key)
|
||||
{
|
||||
string displayedValue = string.Empty;
|
||||
int i = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int length = Math.Min(4, key.Length - i);
|
||||
displayedValue += string.Concat(key.AsSpan(i, length), " ");
|
||||
i += 4;
|
||||
}
|
||||
while (i < key.Length - 1);
|
||||
|
||||
return displayedValue.Trim();
|
||||
}
|
||||
|
||||
internal static bool GeneratedKey { get; set; }
|
||||
|
||||
internal static bool KeyCorrupted { get; set; }
|
||||
|
||||
internal static void InitEncryption()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (symAl == null)
|
||||
{
|
||||
#pragma warning disable SYSLIB0021 // No proper replacement for now
|
||||
symAl = new AesCryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021
|
||||
symAl.KeySize = 256;
|
||||
symAl.BlockSize = SymAlBlockSize * 8;
|
||||
symAl.Padding = PaddingMode.Zeros;
|
||||
symAl.Mode = CipherMode.CBC;
|
||||
symAl.GenerateIV();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<string, byte[]> LegalKeyDictionary = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
internal static byte[] GenLegalKey()
|
||||
{
|
||||
byte[] rv;
|
||||
string myKey = Common.MyKey;
|
||||
|
||||
if (!LegalKeyDictionary.TryGetValue(myKey, out byte[] value))
|
||||
{
|
||||
Rfc2898DeriveBytes key = new(
|
||||
myKey,
|
||||
Common.GetBytesU(InitialIV),
|
||||
50000,
|
||||
HashAlgorithmName.SHA512);
|
||||
rv = key.GetBytes(32);
|
||||
_ = LegalKeyDictionary.AddOrUpdate(myKey, rv, (k, v) => rv);
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = value;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static byte[] GenLegalIV()
|
||||
{
|
||||
string st = InitialIV;
|
||||
int ivLength = symAl.IV.Length;
|
||||
if (st.Length > ivLength)
|
||||
{
|
||||
st = st[..ivLength];
|
||||
}
|
||||
else if (st.Length < ivLength)
|
||||
{
|
||||
st = st.PadRight(ivLength, ' ');
|
||||
}
|
||||
|
||||
return GetBytes(st);
|
||||
}
|
||||
|
||||
internal static Stream GetEncryptedStream(Stream encryptedStream)
|
||||
{
|
||||
ICryptoTransform encryptor;
|
||||
encryptor = symAl.CreateEncryptor(GenLegalKey(), GenLegalIV());
|
||||
return new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write);
|
||||
}
|
||||
|
||||
internal static Stream GetDecryptedStream(Stream encryptedStream)
|
||||
{
|
||||
ICryptoTransform decryptor;
|
||||
decryptor = symAl.CreateDecryptor(GenLegalKey(), GenLegalIV());
|
||||
return new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);
|
||||
}
|
||||
|
||||
internal static uint Get24BitHash(string st)
|
||||
{
|
||||
if (string.IsNullOrEmpty(st))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[PACKAGE_SIZE];
|
||||
for (int i = 0; i < PACKAGE_SIZE; i++)
|
||||
{
|
||||
if (i < st.Length)
|
||||
{
|
||||
bytes[i] = (byte)st[i];
|
||||
}
|
||||
}
|
||||
|
||||
var hash = SHA512.Create();
|
||||
byte[] hashValue = hash.ComputeHash(bytes);
|
||||
|
||||
for (int i = 0; i < 50000; i++)
|
||||
{
|
||||
hashValue = hash.ComputeHash(hashValue);
|
||||
}
|
||||
|
||||
Logger.LogDebug(string.Format(CultureInfo.CurrentCulture, "magic: {0},{1},{2}", hashValue[0], hashValue[1], hashValue[^1]));
|
||||
hash.Clear();
|
||||
return (uint)((hashValue[0] << 23) + (hashValue[1] << 16) + (hashValue[^1] << 8) + hashValue[2]);
|
||||
}
|
||||
|
||||
internal static string GetDebugInfo(string st)
|
||||
{
|
||||
return string.IsNullOrEmpty(st) ? st : ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static string CreateDefaultKey()
|
||||
{
|
||||
return CreateRandomKey();
|
||||
}
|
||||
|
||||
private const int PW_LENGTH = 16;
|
||||
|
||||
public static string CreateRandomKey()
|
||||
{
|
||||
// Not including characters like "'`O0& since they are confusing to users.
|
||||
string[] chars = new[] { "abcdefghjkmnpqrstuvxyz", "ABCDEFGHJKMNPQRSTUVXYZ", "123456789", "~!@#$%^*()_-+=:;<,>.?/\\|[]" };
|
||||
char[][] charactersUsedForKey = chars.Select(charset => Enumerable.Range(0, charset.Length - 1).Select(i => charset[i]).ToArray()).ToArray();
|
||||
byte[] randomData = new byte[1];
|
||||
string key = string.Empty;
|
||||
|
||||
do
|
||||
{
|
||||
foreach (string set in chars)
|
||||
{
|
||||
randomData = RandomNumberGenerator.GetBytes(1);
|
||||
key += set[randomData[0] % set.Length];
|
||||
|
||||
if (key.Length >= PW_LENGTH)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (key.Length < PW_LENGTH);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
internal static bool IsKeyValid(string key, out string error)
|
||||
{
|
||||
error = string.IsNullOrEmpty(key) || key.Length < 16
|
||||
? "Key must have at least 16 characters in length (spaces are discarded). Key must be auto generated in one of the machines."
|
||||
: null;
|
||||
|
||||
return error == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,262 +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.
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// In X64, we are WOW
|
||||
[module: SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", Scope = "type", Target = "MouseWithoutBorders.DATA", Justification = "Dotnet port with style preservation")]
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal enum PackageType// : int
|
||||
{
|
||||
// Search for PACKAGE_TYPE_RELATED before changing these!
|
||||
Invalid = 0xFF,
|
||||
|
||||
Error = 0xFE,
|
||||
|
||||
Hi = 2,
|
||||
Hello = 3,
|
||||
ByeBye = 4,
|
||||
|
||||
Heartbeat = 20,
|
||||
Awake = 21,
|
||||
HideMouse = 50,
|
||||
Heartbeat_ex = 51,
|
||||
Heartbeat_ex_l2 = 52,
|
||||
Heartbeat_ex_l3 = 53,
|
||||
|
||||
Clipboard = 69,
|
||||
ClipboardDragDrop = 70,
|
||||
ClipboardDragDropEnd = 71,
|
||||
ExplorerDragDrop = 72,
|
||||
ClipboardCapture = 73,
|
||||
CaptureScreenCommand = 74,
|
||||
ClipboardDragDropOperation = 75,
|
||||
ClipboardDataEnd = 76,
|
||||
MachineSwitched = 77,
|
||||
ClipboardAsk = 78,
|
||||
ClipboardPush = 79,
|
||||
|
||||
NextMachine = 121,
|
||||
Keyboard = 122,
|
||||
Mouse = 123,
|
||||
ClipboardText = 124,
|
||||
ClipboardImage = 125,
|
||||
|
||||
Handshake = 126,
|
||||
HandshakeAck = 127,
|
||||
|
||||
Matrix = 128,
|
||||
MatrixSwapFlag = 2,
|
||||
MatrixTwoRowFlag = 4,
|
||||
}
|
||||
|
||||
internal struct PackageMonitor
|
||||
{
|
||||
internal ulong Keyboard;
|
||||
internal ulong Mouse;
|
||||
internal ulong Heartbeat;
|
||||
internal ulong ByeBye;
|
||||
internal ulong Hello;
|
||||
internal ulong Matrix;
|
||||
internal ulong ClipboardText;
|
||||
internal ulong ClipboardImage;
|
||||
internal ulong Clipboard;
|
||||
internal ulong ClipboardDragDrop;
|
||||
internal ulong ClipboardDragDropEnd;
|
||||
internal ulong ClipboardAsk;
|
||||
internal ulong ExplorerDragDrop;
|
||||
internal ulong Nil;
|
||||
|
||||
internal PackageMonitor(ulong value)
|
||||
{
|
||||
ClipboardDragDrop = ClipboardDragDropEnd = ExplorerDragDrop =
|
||||
Keyboard = Mouse = Heartbeat = ByeBye = Hello = Clipboard =
|
||||
Matrix = ClipboardImage = ClipboardText = Nil = ClipboardAsk = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum ID : uint
|
||||
{
|
||||
NONE = 0,
|
||||
ALL = 255,
|
||||
}
|
||||
|
||||
internal enum ClipboardPostAction : uint
|
||||
{
|
||||
Other = 0,
|
||||
Desktop = 1,
|
||||
Mspaint = 2,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct KEYBDDATA
|
||||
{
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
|
||||
internal int wVk;
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
|
||||
internal int dwFlags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MOUSEDATA
|
||||
{
|
||||
internal int X;
|
||||
internal int Y;
|
||||
internal int WheelDelta;
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
|
||||
internal int dwFlags;
|
||||
}
|
||||
|
||||
// The beauty of "union" in C#
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal class DATA
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
internal PackageType Type; // 4 (first byte = package type, 1 = checksum, 2+3 = magic no.)
|
||||
|
||||
[FieldOffset(sizeof(PackageType))]
|
||||
internal int Id; // 4
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + sizeof(uint))]
|
||||
internal ID Src; // 4
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (2 * sizeof(uint)))]
|
||||
internal ID Des; // 4
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal long DateTime;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)) + sizeof(long))]
|
||||
internal KEYBDDATA Kd;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal MOUSEDATA Md;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal ID Machine1;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (4 * sizeof(uint)))]
|
||||
internal ID Machine2;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (5 * sizeof(uint)))]
|
||||
internal ID Machine3;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (6 * sizeof(uint)))]
|
||||
internal ID Machine4;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal ClipboardPostAction PostAction;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)))]
|
||||
private long machineNameP1;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + sizeof(long))]
|
||||
private long machineNameP2;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (2 * sizeof(long)))]
|
||||
private long machineNameP3;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (3 * sizeof(long)))]
|
||||
private long machineNameP4;
|
||||
|
||||
internal string MachineName
|
||||
{
|
||||
get
|
||||
{
|
||||
string name = Common.GetString(BitConverter.GetBytes(machineNameP1))
|
||||
+ Common.GetString(BitConverter.GetBytes(machineNameP2))
|
||||
+ Common.GetString(BitConverter.GetBytes(machineNameP3))
|
||||
+ Common.GetString(BitConverter.GetBytes(machineNameP4));
|
||||
return name.Trim();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
byte[] machineName = Common.GetBytes(value.PadRight(32, ' '));
|
||||
machineNameP1 = BitConverter.ToInt64(machineName, 0);
|
||||
machineNameP2 = BitConverter.ToInt64(machineName, 8);
|
||||
machineNameP3 = BitConverter.ToInt64(machineName, 16);
|
||||
machineNameP4 = BitConverter.ToInt64(machineName, 24);
|
||||
}
|
||||
}
|
||||
|
||||
public DATA()
|
||||
{
|
||||
}
|
||||
|
||||
public DATA(byte[] initialData)
|
||||
{
|
||||
Bytes = initialData;
|
||||
}
|
||||
|
||||
internal byte[] Bytes
|
||||
{
|
||||
get
|
||||
{
|
||||
byte[] buf = new byte[IsBigPackage ? Common.PACKAGE_SIZE_EX : Common.PACKAGE_SIZE];
|
||||
Array.Copy(StructToBytes(this), buf, IsBigPackage ? Common.PACKAGE_SIZE_EX : Common.PACKAGE_SIZE);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
Debug.Assert(value.Length <= Common.PACKAGE_SIZE_EX, "Length > package size");
|
||||
byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
|
||||
Array.Copy(value, buf, value.Length);
|
||||
BytesToStruct(buf, this);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsBigPackage
|
||||
{
|
||||
get => Type == 0
|
||||
? throw new InvalidOperationException("Package type not set.")
|
||||
: Type switch
|
||||
{
|
||||
PackageType.Hello or PackageType.Awake or PackageType.Heartbeat or PackageType.Heartbeat_ex or PackageType.Handshake or PackageType.HandshakeAck or PackageType.ClipboardPush or PackageType.Clipboard or PackageType.ClipboardAsk or PackageType.ClipboardImage or PackageType.ClipboardText or PackageType.ClipboardDataEnd => true,
|
||||
_ => (Type & PackageType.Matrix) == PackageType.Matrix,
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] StructToBytes(object structObject)
|
||||
{
|
||||
byte[] bytes = new byte[Common.PACKAGE_SIZE_EX];
|
||||
GCHandle bHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
|
||||
Marshal.StructureToPtr(structObject, Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0), false);
|
||||
bHandle.Free();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void BytesToStruct(byte[] value, object structObject)
|
||||
{
|
||||
GCHandle bHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
|
||||
Marshal.PtrToStructure(Marshal.UnsafeAddrOfPinnedArrayElement(value, 0), structObject);
|
||||
bHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class Common
|
||||
{
|
||||
internal const byte PACKAGE_SIZE = 32;
|
||||
internal const byte PACKAGE_SIZE_EX = 64;
|
||||
internal const byte WP_PACKAGE_SIZE = 6;
|
||||
internal static PackageMonitor PackageSent;
|
||||
internal static PackageMonitor PackageReceived;
|
||||
internal static int PackageID;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +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 ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using MouseWithoutBorders.Class;
|
||||
|
||||
using Logger = MouseWithoutBorders.Core.Logger;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal class ShutdownWithPowerToys
|
||||
{
|
||||
public static void WaitForPowerToysRunner(ETWTrace etwTrace)
|
||||
{
|
||||
try
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunnerExitFallback(() =>
|
||||
{
|
||||
etwTrace?.Dispose();
|
||||
Common.MainForm.Quit(true, false);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +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.
|
||||
|
||||
// <summary>
|
||||
// Virtual key constants.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using System;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal enum VK : ushort
|
||||
{
|
||||
CAPITAL = 0x14,
|
||||
NUMLOCK = 0x90,
|
||||
SHIFT = 0x10,
|
||||
CONTROL = 0x11,
|
||||
MENU = 0x12,
|
||||
ESCAPE = 0x1B,
|
||||
BACK = 0x08,
|
||||
TAB = 0x09,
|
||||
RETURN = 0x0D,
|
||||
PRIOR = 0x21,
|
||||
NEXT = 0x22,
|
||||
END = 0x23,
|
||||
HOME = 0x24,
|
||||
LEFT = 0x25,
|
||||
UP = 0x26,
|
||||
RIGHT = 0x27,
|
||||
DOWN = 0x28,
|
||||
SELECT = 0x29,
|
||||
PRINT = 0x2A,
|
||||
EXECUTE = 0x2B,
|
||||
SNAPSHOT = 0x2C,
|
||||
INSERT = 0x2D,
|
||||
DELETE = 0x2E,
|
||||
HELP = 0x2F,
|
||||
NUMPAD0 = 0x60,
|
||||
NUMPAD1 = 0x61,
|
||||
NUMPAD2 = 0x62,
|
||||
NUMPAD3 = 0x63,
|
||||
NUMPAD4 = 0x64,
|
||||
NUMPAD5 = 0x65,
|
||||
NUMPAD6 = 0x66,
|
||||
NUMPAD7 = 0x67,
|
||||
NUMPAD8 = 0x68,
|
||||
NUMPAD9 = 0x69,
|
||||
MULTIPLY = 0x6A,
|
||||
ADD = 0x6B,
|
||||
SEPARATOR = 0x6C,
|
||||
SUBTRACT = 0x6D,
|
||||
DECIMAL = 0x6E,
|
||||
DIVIDE = 0x6F,
|
||||
F1 = 0x70,
|
||||
F2 = 0x71,
|
||||
F3 = 0x72,
|
||||
F4 = 0x73,
|
||||
F5 = 0x74,
|
||||
F6 = 0x75,
|
||||
F7 = 0x76,
|
||||
F8 = 0x77,
|
||||
F9 = 0x78,
|
||||
F10 = 0x79,
|
||||
F11 = 0x7A,
|
||||
F12 = 0x7B,
|
||||
OEM_1 = 0xBA,
|
||||
OEM_PLUS = 0xBB,
|
||||
OEM_COMMA = 0xBC,
|
||||
OEM_MINUS = 0xBD,
|
||||
OEM_PERIOD = 0xBE,
|
||||
OEM_2 = 0xBF,
|
||||
OEM_3 = 0xC0,
|
||||
MEDIA_NEXT_TRACK = 0xB0,
|
||||
MEDIA_PREV_TRACK = 0xB1,
|
||||
MEDIA_STOP = 0xB2,
|
||||
MEDIA_PLAY_PAUSE = 0xB3,
|
||||
LWIN = 0x5B,
|
||||
RWIN = 0x5C,
|
||||
LSHIFT = 0xA0,
|
||||
RSHIFT = 0xA1,
|
||||
LCONTROL = 0xA2,
|
||||
RCONTROL = 0xA3,
|
||||
LMENU = 0xA4,
|
||||
RMENU = 0xA5,
|
||||
}
|
||||
|
||||
internal partial class Common
|
||||
{
|
||||
internal const ushort KEYEVENTF_KEYDOWN = 0x0001;
|
||||
internal const ushort KEYEVENTF_KEYUP = 0x0002;
|
||||
|
||||
internal const int WH_MOUSE = 7;
|
||||
internal const int WH_KEYBOARD = 2;
|
||||
internal const int WH_MOUSE_LL = 14;
|
||||
internal const int WH_KEYBOARD_LL = 13;
|
||||
|
||||
internal const int WM_MOUSEMOVE = 0x200;
|
||||
internal const int WM_LBUTTONDOWN = 0x201;
|
||||
internal const int WM_RBUTTONDOWN = 0x204;
|
||||
internal const int WM_MBUTTONDOWN = 0x207;
|
||||
internal const int WM_XBUTTONDOWN = 0x20B;
|
||||
internal const int WM_LBUTTONUP = 0x202;
|
||||
internal const int WM_RBUTTONUP = 0x205;
|
||||
internal const int WM_MBUTTONUP = 0x208;
|
||||
internal const int WM_XBUTTONUP = 0x20C;
|
||||
internal const int WM_LBUTTONDBLCLK = 0x203;
|
||||
internal const int WM_RBUTTONDBLCLK = 0x206;
|
||||
internal const int WM_MBUTTONDBLCLK = 0x209;
|
||||
internal const int WM_MOUSEWHEEL = 0x020A;
|
||||
internal const int WM_MOUSEHWHEEL = 0x020E;
|
||||
|
||||
internal const int WM_KEYDOWN = 0x100;
|
||||
internal const int WM_KEYUP = 0x101;
|
||||
internal const int WM_SYSKEYDOWN = 0x104;
|
||||
internal const int WM_SYSKEYUP = 0x105;
|
||||
|
||||
[Flags]
|
||||
internal enum LLKHF
|
||||
{
|
||||
EXTENDED = 0x01,
|
||||
INJECTED = 0x10,
|
||||
ALTDOWN = 0x20,
|
||||
UP = 0x80,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
// <summary>
|
||||
// Screen/Desktop helper functions.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
// Desktops, and GetScreenConfig routines
|
||||
internal partial class Common
|
||||
{
|
||||
private static MyRectangle newDesktopBounds;
|
||||
private static MyRectangle newPrimaryScreenBounds;
|
||||
private static string activeDesktop;
|
||||
|
||||
internal static string ActiveDesktop => Common.activeDesktop;
|
||||
|
||||
internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
GetScreenConfig();
|
||||
}
|
||||
|
||||
internal static readonly List<Point> SensitivePoints = new();
|
||||
|
||||
private static bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref NativeMethods.RECT lprcMonitor, IntPtr dwData)
|
||||
{
|
||||
// lprcMonitor is wrong!!! => using GetMonitorInfo(...)
|
||||
// Log(String.Format( CultureInfo.CurrentCulture,"MONITOR: l{0}, t{1}, r{2}, b{3}", lprcMonitor.Left, lprcMonitor.Top, lprcMonitor.Right, lprcMonitor.Bottom));
|
||||
NativeMethods.MonitorInfoEx mi = default;
|
||||
mi.cbSize = Marshal.SizeOf(mi);
|
||||
_ = NativeMethods.GetMonitorInfo(hMonitor, ref mi);
|
||||
|
||||
try
|
||||
{
|
||||
// For logging only
|
||||
_ = NativeMethods.GetDpiForMonitor(hMonitor, 0, out uint dpiX, out uint dpiY);
|
||||
Logger.Log(string.Format(CultureInfo.CurrentCulture, "MONITOR: ({0}, {1}, {2}, {3}). DPI: ({4}, {5})", mi.rcMonitor.Left, mi.rcMonitor.Top, mi.rcMonitor.Right, mi.rcMonitor.Bottom, dpiX, dpiY));
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Left == 0 && mi.rcMonitor.Top == 0 && mi.rcMonitor.Right != 0 && mi.rcMonitor.Bottom != 0)
|
||||
{
|
||||
// Primary screen
|
||||
_ = Interlocked.Exchange(ref screenWidth, mi.rcMonitor.Right - mi.rcMonitor.Left);
|
||||
_ = Interlocked.Exchange(ref screenHeight, mi.rcMonitor.Bottom - mi.rcMonitor.Top);
|
||||
|
||||
newPrimaryScreenBounds.Left = mi.rcMonitor.Left;
|
||||
newPrimaryScreenBounds.Top = mi.rcMonitor.Top;
|
||||
newPrimaryScreenBounds.Right = mi.rcMonitor.Right;
|
||||
newPrimaryScreenBounds.Bottom = mi.rcMonitor.Bottom;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mi.rcMonitor.Left < newDesktopBounds.Left)
|
||||
{
|
||||
newDesktopBounds.Left = mi.rcMonitor.Left;
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Top < newDesktopBounds.Top)
|
||||
{
|
||||
newDesktopBounds.Top = mi.rcMonitor.Top;
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Right > newDesktopBounds.Right)
|
||||
{
|
||||
newDesktopBounds.Right = mi.rcMonitor.Right;
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Bottom > newDesktopBounds.Bottom)
|
||||
{
|
||||
newDesktopBounds.Bottom = mi.rcMonitor.Bottom;
|
||||
}
|
||||
}
|
||||
|
||||
lock (SensitivePoints)
|
||||
{
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Top));
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Top));
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Bottom));
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Bottom));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void GetScreenConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("==================== GetScreenConfig started");
|
||||
newDesktopBounds = new MyRectangle();
|
||||
newPrimaryScreenBounds = new MyRectangle();
|
||||
newDesktopBounds.Left = newPrimaryScreenBounds.Left = Screen.PrimaryScreen.Bounds.Left;
|
||||
newDesktopBounds.Top = newPrimaryScreenBounds.Top = Screen.PrimaryScreen.Bounds.Top;
|
||||
newDesktopBounds.Right = newPrimaryScreenBounds.Right = Screen.PrimaryScreen.Bounds.Right;
|
||||
newDesktopBounds.Bottom = newPrimaryScreenBounds.Bottom = Screen.PrimaryScreen.Bounds.Bottom;
|
||||
|
||||
Logger.Log(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
|
||||
Common.RunOnLogonDesktop,
|
||||
Common.newPrimaryScreenBounds.Left,
|
||||
Common.newPrimaryScreenBounds.Top,
|
||||
Common.newPrimaryScreenBounds.Right,
|
||||
Common.newPrimaryScreenBounds.Bottom,
|
||||
Common.newDesktopBounds.Left,
|
||||
Common.newDesktopBounds.Top,
|
||||
Common.newDesktopBounds.Right,
|
||||
Common.newDesktopBounds.Bottom));
|
||||
|
||||
#if USE_MANAGED_ROUTINES
|
||||
// Managed routines do not work well when running on secure desktop:(
|
||||
screenWidth = Screen.PrimaryScreen.Bounds.Width;
|
||||
screenHeight = Screen.PrimaryScreen.Bounds.Height;
|
||||
screenCount = Screen.AllScreens.Length;
|
||||
for (int i = 0; i < Screen.AllScreens.Length; i++)
|
||||
{
|
||||
if (Screen.AllScreens[i].Bounds.Left < desktopBounds.Left) desktopBounds.Left = Screen.AllScreens[i].Bounds.Left;
|
||||
if (Screen.AllScreens[i].Bounds.Top < desktopBounds.Top) desktopBounds.Top = Screen.AllScreens[i].Bounds.Top;
|
||||
if (Screen.AllScreens[i].Bounds.Right > desktopBounds.Right) desktopBounds.Right = Screen.AllScreens[i].Bounds.Right;
|
||||
if (Screen.AllScreens[i].Bounds.Bottom > desktopBounds.Bottom) desktopBounds.Bottom = Screen.AllScreens[i].Bounds.Bottom;
|
||||
}
|
||||
#else
|
||||
lock (SensitivePoints)
|
||||
{
|
||||
SensitivePoints.Clear();
|
||||
}
|
||||
|
||||
NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
|
||||
|
||||
// 1000 calls to EnumDisplayMonitors cost a dozen of milliseconds
|
||||
#endif
|
||||
Interlocked.Exchange(ref MachineStuff.desktopBounds, newDesktopBounds);
|
||||
Interlocked.Exchange(ref MachineStuff.primaryScreenBounds, newPrimaryScreenBounds);
|
||||
|
||||
Logger.Log(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
|
||||
Common.RunOnLogonDesktop,
|
||||
MachineStuff.PrimaryScreenBounds.Left,
|
||||
MachineStuff.PrimaryScreenBounds.Top,
|
||||
MachineStuff.PrimaryScreenBounds.Right,
|
||||
MachineStuff.PrimaryScreenBounds.Bottom,
|
||||
MachineStuff.DesktopBounds.Left,
|
||||
MachineStuff.DesktopBounds.Top,
|
||||
MachineStuff.DesktopBounds.Right,
|
||||
MachineStuff.DesktopBounds.Bottom));
|
||||
|
||||
Logger.Log("==================== GetScreenConfig ended");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
#if USING_SCREEN_SAVER_ROUTINES
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern int PostMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern IntPtr OpenDesktop(string hDesktop, int Flags, bool Inherit, UInt32 DesiredAccess);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool CloseDesktop(IntPtr hDesktop);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool EnumDesktopWindows( IntPtr hDesktop, EnumDesktopWindowsProc callback, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool SystemParametersInfo(int uAction, int uParam, ref int pvParam, int flags);
|
||||
|
||||
private delegate bool EnumDesktopWindowsProc(IntPtr hDesktop, IntPtr lParam);
|
||||
private const int WM_CLOSE = 16;
|
||||
private const int SPI_GETSCREENSAVERRUNNING = 114;
|
||||
|
||||
internal static bool IsScreenSaverRunning()
|
||||
{
|
||||
int isRunning = 0;
|
||||
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0,ref isRunning, 0);
|
||||
return (isRunning != 0);
|
||||
}
|
||||
|
||||
internal static void CloseScreenSaver()
|
||||
{
|
||||
IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
|
||||
if (hDesktop != IntPtr.Zero)
|
||||
{
|
||||
LogDebug("Closing screen saver...");
|
||||
EnumDesktopWindows(hDesktop, new EnumDesktopWindowsProc(CloseScreenSaverFunc), IntPtr.Zero);
|
||||
CloseDesktop(hDesktop);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CloseScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
|
||||
{
|
||||
if (IsWindowVisible(hWnd))
|
||||
{
|
||||
LogDebug("Posting WM_CLOSE to " + hWnd.ToString(CultureInfo.InvariantCulture));
|
||||
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static string GetMyDesktop()
|
||||
{
|
||||
byte[] arThreadDesktop = new byte[256];
|
||||
IntPtr hD = NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId());
|
||||
if (hD != IntPtr.Zero)
|
||||
{
|
||||
_ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arThreadDesktop, arThreadDesktop.Length, out _);
|
||||
return GetString(arThreadDesktop).Replace("\0", string.Empty);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
internal static string GetInputDesktop()
|
||||
{
|
||||
byte[] arInputDesktop = new byte[256];
|
||||
IntPtr hD = NativeMethods.OpenInputDesktop(0, false, NativeMethods.DESKTOP_READOBJECTS);
|
||||
if (hD != IntPtr.Zero)
|
||||
{
|
||||
_ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arInputDesktop, arInputDesktop.Length, out _);
|
||||
return GetString(arInputDesktop).Replace("\0", string.Empty);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
internal static void StartMMService(string desktopToRunMouseWithoutBordersOn)
|
||||
{
|
||||
if (!Common.RunWithNoAdminRight)
|
||||
{
|
||||
Logger.LogDebug("*** Starting on active Desktop: " + desktopToRunMouseWithoutBordersOn);
|
||||
Service.StartMouseWithoutBordersService(desktopToRunMouseWithoutBordersOn);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CheckForDesktopSwitchEvent(bool cleanupIfExit)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
|
||||
{
|
||||
Helper.RunDDHelper(true);
|
||||
int waitCount = 20;
|
||||
|
||||
while (NativeMethods.WTSGetActiveConsoleSessionId() == 0xFFFFFFFF && waitCount > 0)
|
||||
{
|
||||
waitCount--;
|
||||
Logger.LogDebug("The session is detached/attached.");
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
string myDesktop = GetMyDesktop();
|
||||
activeDesktop = GetInputDesktop();
|
||||
|
||||
Logger.LogDebug("*** Active Desktop = " + activeDesktop);
|
||||
Logger.LogDebug("*** My Desktop = " + myDesktop);
|
||||
|
||||
if (myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("*** Active Desktop == My Desktop (TS session)");
|
||||
}
|
||||
|
||||
if (!activeDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
|
||||
!activeDesktop.Equals("default", StringComparison.OrdinalIgnoreCase) &&
|
||||
!activeDesktop.Equals("disconnect", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
StartMMService(activeDesktop);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"{nameof(CheckForDesktopSwitchEvent)}: {e}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Log("*** Active Desktop <> My Desktop");
|
||||
}
|
||||
|
||||
uint sid = NativeMethods.WTSGetActiveConsoleSessionId();
|
||||
|
||||
if (Process.GetProcessesByName(Common.BinaryName).Any(p => (uint)p.SessionId == sid))
|
||||
{
|
||||
Logger.Log("Found MouseWithoutBorders on the active session!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("MouseWithoutBorders not found on the active session!");
|
||||
StartMMService(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!myDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
|
||||
!myDesktop.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("*** Desktop inactive, exiting: " + myDesktop);
|
||||
Setting.Values.LastX = JUST_GOT_BACK_FROM_SCREEN_SAVER;
|
||||
if (cleanupIfExit)
|
||||
{
|
||||
InitAndCleanup.Cleanup();
|
||||
}
|
||||
|
||||
Process.GetCurrentProcess().KillProcess();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Point p;
|
||||
|
||||
internal static bool IsMyDesktopActive()
|
||||
{
|
||||
return NativeMethods.GetCursorPos(ref p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,7 +315,7 @@ namespace MouseWithoutBorders
|
||||
if (!acquireMutex)
|
||||
{
|
||||
Process[] ps = Process.GetProcessesByName(Common.BinaryName);
|
||||
Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {GetMyDesktop()}/{GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning);
|
||||
Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {WinAPI.IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {WinAPI.GetMyDesktop()}/{WinAPI.GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning);
|
||||
}
|
||||
|
||||
Logger.LogDebug("SOCKET MUTEX ENDED.");
|
||||
@@ -358,7 +358,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}, restarting the process.", SeverityLevel.Warning, true);
|
||||
|
||||
string desktop = Common.GetMyDesktop();
|
||||
string desktop = WinAPI.GetMyDesktop();
|
||||
MachineStuff.oneInstanceCheck?.Close();
|
||||
_ = Process.Start(Application.ExecutablePath, desktop);
|
||||
Logger.LogDebug($"Started on desktop {desktop}");
|
||||
@@ -514,7 +514,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void SendHeartBeat(bool initial = false)
|
||||
{
|
||||
SendPackage(ID.ALL, initial && Common.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat);
|
||||
SendPackage(ID.ALL, initial && Encryption.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat);
|
||||
}
|
||||
|
||||
private static long lastSendNextMachine;
|
||||
@@ -550,7 +550,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void SendAwakeBeat()
|
||||
{
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() &&
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() &&
|
||||
Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount)
|
||||
{
|
||||
SendPackage(ID.ALL, PackageType.Awake);
|
||||
@@ -568,7 +568,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
if (lastInputEventCount == Event.InputEventCount)
|
||||
{
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive())
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive())
|
||||
{
|
||||
PokeMyself();
|
||||
}
|
||||
@@ -577,13 +577,13 @@ namespace MouseWithoutBorders
|
||||
lastInputEventCount = Event.InputEventCount;
|
||||
}
|
||||
|
||||
private static void PokeMyself()
|
||||
internal static void PokeMyself()
|
||||
{
|
||||
int x, y = 0;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
x = Ran.Next(-9, 10);
|
||||
x = Encryption.Ran.Next(-9, 10);
|
||||
InputSimulation.MoveMouseRelative(x, y);
|
||||
Thread.Sleep(50);
|
||||
InputSimulation.MoveMouseRelative(-x, -y);
|
||||
@@ -677,7 +677,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
Common.MMSleep(0.2);
|
||||
InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT });
|
||||
InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)Common.LLKHF.UP, wVk = (int)VK.SNAPSHOT });
|
||||
InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)WM.LLKHF.UP, wVk = (int)VK.SNAPSHOT });
|
||||
|
||||
Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated.");
|
||||
|
||||
@@ -710,7 +710,7 @@ namespace MouseWithoutBorders
|
||||
"\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
|
||||
"\"",
|
||||
"\"" + file + "\"",
|
||||
GetInputDesktop(),
|
||||
WinAPI.GetInputDesktop(),
|
||||
1);
|
||||
|
||||
// CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
|
||||
@@ -919,7 +919,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
try
|
||||
{
|
||||
data.Id = Interlocked.Increment(ref PackageID);
|
||||
data.Id = Interlocked.Increment(ref Package.PackageID);
|
||||
|
||||
bool updateClientSockets = false;
|
||||
|
||||
@@ -999,7 +999,7 @@ namespace MouseWithoutBorders
|
||||
}
|
||||
else
|
||||
{
|
||||
PackageSent.Nil++;
|
||||
Package.PackageSent.Nil++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1379,7 +1379,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
if (string.IsNullOrEmpty(machine_Name))
|
||||
{
|
||||
machine_Name = "RANDOM" + Ran.Next().ToString(CultureInfo.CurrentCulture);
|
||||
machine_Name = "RANDOM" + Encryption.Ran.Next().ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1533,13 +1533,13 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void SendOrReceiveARandomDataBlockPerInitialIV(Stream st, bool send = true)
|
||||
{
|
||||
byte[] ranData = new byte[SymAlBlockSize];
|
||||
byte[] ranData = new byte[Encryption.SymAlBlockSize];
|
||||
|
||||
try
|
||||
{
|
||||
if (send)
|
||||
{
|
||||
ranData = RandomNumberGenerator.GetBytes(SymAlBlockSize);
|
||||
ranData = RandomNumberGenerator.GetBytes(Encryption.SymAlBlockSize);
|
||||
st.Write(ranData, 0, ranData.Length);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace MouseWithoutBorders.Class
|
||||
// Install Mouse Hook
|
||||
mouseHookProcedure = new NativeMethods.HookProc(MouseHookProc);
|
||||
hMouseHook = NativeMethods.SetWindowsHookEx(
|
||||
Common.WH_MOUSE_LL,
|
||||
WM.WH_MOUSE_LL,
|
||||
mouseHookProcedure,
|
||||
Marshal.GetHINSTANCE(
|
||||
Assembly.GetExecutingAssembly().GetModules()[0]),
|
||||
@@ -126,7 +126,7 @@ namespace MouseWithoutBorders.Class
|
||||
// Install Keyboard Hook
|
||||
keyboardHookProcedure = new NativeMethods.HookProc(KeyboardHookProc);
|
||||
hKeyboardHook = NativeMethods.SetWindowsHookEx(
|
||||
Common.WH_KEYBOARD_LL,
|
||||
WM.WH_KEYBOARD_LL,
|
||||
keyboardHookProcedure,
|
||||
Marshal.GetHINSTANCE(
|
||||
Assembly.GetExecutingAssembly().GetModules()[0]),
|
||||
@@ -233,7 +233,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
if (nCode >= 0 && MouseEvent != null)
|
||||
{
|
||||
if (wParam == Common.WM_LBUTTONUP && SkipMouseUpCount > 0)
|
||||
if (wParam == WM.WM_LBUTTONUP && SkipMouseUpCount > 0)
|
||||
{
|
||||
Logger.LogDebug($"{nameof(SkipMouseUpCount)}: {SkipMouseUpCount}.");
|
||||
SkipMouseUpCount--;
|
||||
@@ -241,7 +241,7 @@ namespace MouseWithoutBorders.Class
|
||||
return rv;
|
||||
}
|
||||
|
||||
if ((wParam == Common.WM_LBUTTONUP || wParam == Common.WM_LBUTTONDOWN) && SkipMouseUpDown)
|
||||
if ((wParam == WM.WM_LBUTTONUP || wParam == WM.WM_LBUTTONDOWN) && SkipMouseUpDown)
|
||||
{
|
||||
rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam);
|
||||
return rv;
|
||||
@@ -370,7 +370,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
private bool ProcessKeyEx(int vkCode, int flags, KEYBDDATA hookCallbackKeybdData)
|
||||
{
|
||||
if ((flags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
|
||||
if ((flags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
|
||||
{
|
||||
EasyMouseKeyDown = false;
|
||||
|
||||
@@ -553,7 +553,7 @@ namespace MouseWithoutBorders.Class
|
||||
KeyboardEvent(hookCallbackKeybdData);
|
||||
}
|
||||
|
||||
hookCallbackKeybdData.dwFlags |= (int)Common.LLKHF.UP;
|
||||
hookCallbackKeybdData.dwFlags |= (int)WM.LLKHF.UP;
|
||||
|
||||
foreach (var code in codes)
|
||||
{
|
||||
|
||||
@@ -112,12 +112,12 @@ namespace MouseWithoutBorders.Class
|
||||
uint scanCode = 0;
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/ms644967(VS.85).aspx
|
||||
if ((kd.dwFlags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
|
||||
if ((kd.dwFlags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
|
||||
{
|
||||
dwFlags = NativeMethods.KEYEVENTF.KEYUP;
|
||||
}
|
||||
|
||||
if ((kd.dwFlags & (int)Common.LLKHF.EXTENDED) == (int)Common.LLKHF.EXTENDED)
|
||||
if ((kd.dwFlags & (int)WM.LLKHF.EXTENDED) == (int)WM.LLKHF.EXTENDED)
|
||||
{
|
||||
dwFlags |= NativeMethods.KEYEVENTF.EXTENDEDKEY;
|
||||
}
|
||||
@@ -173,44 +173,44 @@ namespace MouseWithoutBorders.Class
|
||||
mouse_input.mi.dy = (int)dy;
|
||||
mouse_input.mi.mouseData = md.WheelDelta;
|
||||
|
||||
if (md.dwFlags != Common.WM_MOUSEMOVE)
|
||||
if (md.dwFlags != WM.WM_MOUSEMOVE)
|
||||
{
|
||||
Logger.LogDebug($"InputSimulation.SendMouse: x = {md.X}, y = {md.Y}, WheelDelta = {md.WheelDelta}, dwFlags = {md.dwFlags}.");
|
||||
}
|
||||
|
||||
switch (md.dwFlags)
|
||||
{
|
||||
case Common.WM_MOUSEMOVE:
|
||||
case WM.WM_MOUSEMOVE:
|
||||
mouse_input.mi.dwFlags |= (int)(NativeMethods.MOUSEEVENTF.MOVE | NativeMethods.MOUSEEVENTF.ABSOLUTE);
|
||||
break;
|
||||
case Common.WM_LBUTTONDOWN:
|
||||
case WM.WM_LBUTTONDOWN:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTDOWN;
|
||||
break;
|
||||
case Common.WM_LBUTTONUP:
|
||||
case WM.WM_LBUTTONUP:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTUP;
|
||||
break;
|
||||
case Common.WM_RBUTTONDOWN:
|
||||
case WM.WM_RBUTTONDOWN:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTDOWN;
|
||||
break;
|
||||
case Common.WM_RBUTTONUP:
|
||||
case WM.WM_RBUTTONUP:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTUP;
|
||||
break;
|
||||
case Common.WM_MBUTTONDOWN:
|
||||
case WM.WM_MBUTTONDOWN:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEDOWN;
|
||||
break;
|
||||
case Common.WM_MBUTTONUP:
|
||||
case WM.WM_MBUTTONUP:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEUP;
|
||||
break;
|
||||
case Common.WM_MOUSEWHEEL:
|
||||
case WM.WM_MOUSEWHEEL:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.WHEEL;
|
||||
break;
|
||||
case Common.WM_MOUSEHWHEEL:
|
||||
case WM.WM_MOUSEHWHEEL:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.HWHEEL;
|
||||
break;
|
||||
case Common.WM_XBUTTONUP:
|
||||
case WM.WM_XBUTTONUP:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XUP;
|
||||
break;
|
||||
case Common.WM_XBUTTONDOWN:
|
||||
case WM.WM_XBUTTONDOWN:
|
||||
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XDOWN;
|
||||
break;
|
||||
|
||||
@@ -373,7 +373,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
eatKey = false;
|
||||
|
||||
if ((flags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
|
||||
if ((flags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
|
||||
{
|
||||
switch ((VK)vkCode)
|
||||
{
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace MouseWithoutBorders.Class
|
||||
return;
|
||||
}
|
||||
|
||||
string myDesktop = Common.GetMyDesktop();
|
||||
string myDesktop = WinAPI.GetMyDesktop();
|
||||
|
||||
if (firstArg.Equals("winlogon", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -305,8 +305,8 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
MachineStuff.ClearComputerMatrix();
|
||||
Setting.Values.MyKey = securityKey;
|
||||
Common.MyKey = securityKey;
|
||||
Common.MagicNumber = Common.Get24BitHash(Common.MyKey);
|
||||
Encryption.MyKey = securityKey;
|
||||
Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
|
||||
MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { pcName.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty };
|
||||
|
||||
string[] machines = MachineStuff.MachineMatrix;
|
||||
@@ -328,8 +328,8 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
Setting.Values.EasyMouse = (int)EasyMouseOption.Enable;
|
||||
MachineStuff.ClearComputerMatrix();
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Common.GeneratedKey = true;
|
||||
Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
|
||||
Encryption.GeneratedKey = true;
|
||||
|
||||
Setting.Values.PauseInstantSaving = false;
|
||||
Setting.Values.SaveSettings();
|
||||
|
||||
@@ -109,9 +109,9 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
var shouldReopenSockets = false;
|
||||
|
||||
if (Common.MyKey != _properties.SecurityKey.Value)
|
||||
if (Encryption.MyKey != _properties.SecurityKey.Value)
|
||||
{
|
||||
Common.MyKey = _properties.SecurityKey.Value;
|
||||
Encryption.MyKey = _properties.SecurityKey.Value;
|
||||
shouldReopenSockets = true;
|
||||
}
|
||||
|
||||
@@ -489,7 +489,7 @@ namespace MouseWithoutBorders.Class
|
||||
}
|
||||
else
|
||||
{
|
||||
string randomKey = Common.CreateDefaultKey();
|
||||
string randomKey = Encryption.CreateDefaultKey();
|
||||
_properties.SecurityKey.Value = randomKey;
|
||||
|
||||
return randomKey;
|
||||
@@ -1055,7 +1055,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
if (machineId == 0)
|
||||
{
|
||||
var newMachineId = Common.Ran.Next();
|
||||
var newMachineId = Encryption.Ran.Next();
|
||||
_properties.MachineID.Value = newMachineId;
|
||||
machineId = newMachineId;
|
||||
if (!PauseInstantSaving)
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
if (encryptedStream == null && BackingSocket.Connected)
|
||||
{
|
||||
encryptedStream = Common.GetEncryptedStream(new NetworkStream(BackingSocket));
|
||||
encryptedStream = Encryption.GetEncryptedStream(new NetworkStream(BackingSocket));
|
||||
Common.SendOrReceiveARandomDataBlockPerInitialIV(encryptedStream);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
if (decryptedStream == null && BackingSocket.Connected)
|
||||
{
|
||||
decryptedStream = Common.GetDecryptedStream(new NetworkStream(BackingSocket));
|
||||
decryptedStream = Encryption.GetDecryptedStream(new NetworkStream(BackingSocket));
|
||||
Common.SendOrReceiveARandomDataBlockPerInitialIV(decryptedStream, false);
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace MouseWithoutBorders.Class
|
||||
Logger.LogDebug("SocketStuff started.");
|
||||
|
||||
bASE_PORT = port;
|
||||
Common.Ran = new Random();
|
||||
Encryption.Ran = new Random();
|
||||
|
||||
Logger.LogDebug("Validating session...");
|
||||
|
||||
@@ -221,11 +221,11 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
if (Setting.Values.IsMyKeyRandom)
|
||||
{
|
||||
Setting.Values.MyKey = Common.MyKey;
|
||||
Setting.Values.MyKey = Encryption.MyKey;
|
||||
}
|
||||
|
||||
Common.MagicNumber = Common.Get24BitHash(Common.MyKey);
|
||||
Common.PackageID = Setting.Values.PackageID;
|
||||
Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
|
||||
Package.PackageID = Setting.Values.PackageID;
|
||||
|
||||
TcpPort = bASE_PORT;
|
||||
|
||||
@@ -242,7 +242,7 @@ namespace MouseWithoutBorders.Class
|
||||
Logger.TelemetryLogTrace($"{nameof(SocketStuff)}: {e.Message}", SeverityLevel.Warning);
|
||||
}
|
||||
|
||||
Common.GetScreenConfig();
|
||||
WinAPI.GetScreenConfig();
|
||||
|
||||
if (firstRun && Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
@@ -305,7 +305,7 @@ namespace MouseWithoutBorders.Class
|
||||
sleepSecs = 10;
|
||||
|
||||
// It is reasonable to give a try on restarting MwB processes in other sessions.
|
||||
if (restartCount++ < 5 && Common.IsMyDesktopActive() && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
if (restartCount++ < 5 && WinAPI.IsMyDesktopActive() && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
Logger.TelemetryLogTrace("Restarting the service dues to WSAEADDRINUSE.", SeverityLevel.Warning);
|
||||
Program.StartService();
|
||||
@@ -361,7 +361,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
Setting.Values.LastX = Common.LastX;
|
||||
Setting.Values.LastY = Common.LastY;
|
||||
Setting.Values.PackageID = Common.PackageID;
|
||||
Setting.Values.PackageID = Package.PackageID;
|
||||
|
||||
// Common.Log("Saving IP: " + Setting.Values.DesMachineID.ToString(CultureInfo.CurrentCulture));
|
||||
Setting.Values.DesMachineID = (uint)Common.DesMachineID;
|
||||
@@ -505,10 +505,10 @@ namespace MouseWithoutBorders.Class
|
||||
throw new ExpectedSocketException(log);
|
||||
}
|
||||
|
||||
bytes[3] = (byte)((Common.MagicNumber >> 24) & 0xFF);
|
||||
bytes[2] = (byte)((Common.MagicNumber >> 16) & 0xFF);
|
||||
bytes[3] = (byte)((Encryption.MagicNumber >> 24) & 0xFF);
|
||||
bytes[2] = (byte)((Encryption.MagicNumber >> 16) & 0xFF);
|
||||
bytes[1] = 0;
|
||||
for (int i = 2; i < Common.PACKAGE_SIZE; i++)
|
||||
for (int i = 2; i < Package.PACKAGE_SIZE; i++)
|
||||
{
|
||||
bytes[1] = (byte)(bytes[1] + bytes[i]);
|
||||
}
|
||||
@@ -535,13 +535,13 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
magic = (buf[3] << 24) + (buf[2] << 16);
|
||||
|
||||
if (magic != (Common.MagicNumber & 0xFFFF0000))
|
||||
if (magic != (Encryption.MagicNumber & 0xFFFF0000))
|
||||
{
|
||||
Logger.Log("Magic number invalid!");
|
||||
buf[0] = (byte)PackageType.Invalid;
|
||||
}
|
||||
|
||||
for (int i = 2; i < Common.PACKAGE_SIZE; i++)
|
||||
for (int i = 2; i < Package.PACKAGE_SIZE; i++)
|
||||
{
|
||||
checksum = (byte)(checksum + buf[i]);
|
||||
}
|
||||
@@ -557,7 +557,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
internal static DATA TcpReceiveData(TcpSk tcp, out int bytesReceived)
|
||||
{
|
||||
byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
|
||||
byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
|
||||
Stream decryptedStream = tcp.DecryptedStream;
|
||||
|
||||
if (tcp.BackingSocket == null || !tcp.BackingSocket.Connected || decryptedStream == null)
|
||||
@@ -571,9 +571,9 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
try
|
||||
{
|
||||
bytesReceived = decryptedStream.ReadEx(buf, 0, Common.PACKAGE_SIZE);
|
||||
bytesReceived = decryptedStream.ReadEx(buf, 0, Package.PACKAGE_SIZE);
|
||||
|
||||
if (bytesReceived != Common.PACKAGE_SIZE)
|
||||
if (bytesReceived != Package.PACKAGE_SIZE)
|
||||
{
|
||||
buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
|
||||
}
|
||||
@@ -586,9 +586,9 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
if (package.IsBigPackage)
|
||||
{
|
||||
bytesReceived = decryptedStream.ReadEx(buf, Common.PACKAGE_SIZE, Common.PACKAGE_SIZE);
|
||||
bytesReceived = decryptedStream.ReadEx(buf, Package.PACKAGE_SIZE, Package.PACKAGE_SIZE);
|
||||
|
||||
if (bytesReceived != Common.PACKAGE_SIZE)
|
||||
if (bytesReceived != Package.PACKAGE_SIZE)
|
||||
{
|
||||
buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
|
||||
}
|
||||
@@ -614,28 +614,28 @@ namespace MouseWithoutBorders.Class
|
||||
switch (type)
|
||||
{
|
||||
case PackageType.Keyboard:
|
||||
Common.PackageSent.Keyboard++;
|
||||
Package.PackageSent.Keyboard++;
|
||||
break;
|
||||
|
||||
case PackageType.Mouse:
|
||||
Common.PackageSent.Mouse++;
|
||||
Package.PackageSent.Mouse++;
|
||||
break;
|
||||
|
||||
case PackageType.Heartbeat:
|
||||
case PackageType.Heartbeat_ex:
|
||||
Common.PackageSent.Heartbeat++;
|
||||
Package.PackageSent.Heartbeat++;
|
||||
break;
|
||||
|
||||
case PackageType.Hello:
|
||||
Common.PackageSent.Hello++;
|
||||
Package.PackageSent.Hello++;
|
||||
break;
|
||||
|
||||
case PackageType.ByeBye:
|
||||
Common.PackageSent.ByeBye++;
|
||||
Package.PackageSent.ByeBye++;
|
||||
break;
|
||||
|
||||
case PackageType.Matrix:
|
||||
Common.PackageSent.Matrix++;
|
||||
Package.PackageSent.Matrix++;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -643,11 +643,11 @@ namespace MouseWithoutBorders.Class
|
||||
switch (subtype)
|
||||
{
|
||||
case (byte)PackageType.ClipboardText:
|
||||
Common.PackageSent.ClipboardText++;
|
||||
Package.PackageSent.ClipboardText++;
|
||||
break;
|
||||
|
||||
case (byte)PackageType.ClipboardImage:
|
||||
Common.PackageSent.ClipboardImage++;
|
||||
Package.PackageSent.ClipboardImage++;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1266,7 +1266,7 @@ namespace MouseWithoutBorders.Class
|
||||
string strIP = string.Empty;
|
||||
ID remoteID = ID.NONE;
|
||||
|
||||
byte[] buf = RandomNumberGenerator.GetBytes(Common.PACKAGE_SIZE_EX);
|
||||
byte[] buf = RandomNumberGenerator.GetBytes(Package.PACKAGE_SIZE_EX);
|
||||
d = new DATA(buf);
|
||||
|
||||
TcpSk currentTcp = tcp;
|
||||
@@ -1280,8 +1280,8 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
try
|
||||
{
|
||||
currentSocket.SendBufferSize = Common.PACKAGE_SIZE * 10000;
|
||||
currentSocket.ReceiveBufferSize = Common.PACKAGE_SIZE * 10000;
|
||||
currentSocket.SendBufferSize = Package.PACKAGE_SIZE * 10000;
|
||||
currentSocket.ReceiveBufferSize = Package.PACKAGE_SIZE * 10000;
|
||||
currentSocket.NoDelay = true; // This is very interesting to know:(
|
||||
currentSocket.SendTimeout = 500;
|
||||
d.MachineName = Common.MachineName;
|
||||
@@ -1829,7 +1829,7 @@ namespace MouseWithoutBorders.Class
|
||||
}
|
||||
while (rv > 0);
|
||||
|
||||
if ((rv = Common.PACKAGE_SIZE - (sentCount % Common.PACKAGE_SIZE)) > 0)
|
||||
if ((rv = Package.PACKAGE_SIZE - (sentCount % Package.PACKAGE_SIZE)) > 0)
|
||||
{
|
||||
Array.Clear(buf, 0, buf.Length);
|
||||
ecStream.Write(buf, 0, rv);
|
||||
@@ -1900,7 +1900,7 @@ namespace MouseWithoutBorders.Class
|
||||
}
|
||||
while (rv > 0);
|
||||
|
||||
if ((rv = sentCount % Common.PACKAGE_SIZE) > 0)
|
||||
if ((rv = sentCount % Package.PACKAGE_SIZE) > 0)
|
||||
{
|
||||
Array.Clear(buf, 0, buf.Length);
|
||||
ecStream.Write(buf, 0, rv);
|
||||
@@ -1984,7 +1984,7 @@ namespace MouseWithoutBorders.Class
|
||||
if (tcp.MachineId == Setting.Values.MachineId)
|
||||
{
|
||||
tcp = null;
|
||||
Setting.Values.MachineId = Common.Ran.Next();
|
||||
Setting.Values.MachineId = Encryption.Ran.Next();
|
||||
InitAndCleanup.UpdateMachineTimeAndID();
|
||||
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace MouseWithoutBorders.Class
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Common.IsMyDesktopActive())
|
||||
if (!WinAPI.IsMyDesktopActive())
|
||||
{
|
||||
// We can just throw the SocketException but to avoid a redundant log entry:
|
||||
throw new ExpectedSocketException($"{nameof(StartServer)}: The desktop is no longer active.");
|
||||
|
||||
@@ -270,15 +270,15 @@ internal static class Clipboard
|
||||
int index = 0;
|
||||
int len;
|
||||
DATA package = new();
|
||||
byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
|
||||
int dataStart = Common.PACKAGE_SIZE_EX - DATA_SIZE;
|
||||
byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
|
||||
int dataStart = Package.PACKAGE_SIZE_EX - DATA_SIZE;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if ((index + DATA_SIZE) > l)
|
||||
{
|
||||
len = l - index;
|
||||
Array.Clear(buf, 0, Common.PACKAGE_SIZE_EX);
|
||||
Array.Clear(buf, 0, Package.PACKAGE_SIZE_EX);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -315,7 +315,7 @@ internal static class Clipboard
|
||||
}
|
||||
|
||||
MemoryStream m = new();
|
||||
int dataStart = Common.PACKAGE_SIZE_EX - DATA_SIZE;
|
||||
int dataStart = Package.PACKAGE_SIZE_EX - DATA_SIZE;
|
||||
m.Write(data.Bytes, dataStart, DATA_SIZE);
|
||||
int unexpectedCount = 0;
|
||||
|
||||
@@ -809,27 +809,27 @@ internal static class Clipboard
|
||||
MachineName = Common.MachineName,
|
||||
};
|
||||
|
||||
byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
|
||||
byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
|
||||
|
||||
NetworkStream ns = new(s);
|
||||
enStream = Common.GetEncryptedStream(ns);
|
||||
enStream = Encryption.GetEncryptedStream(ns);
|
||||
Common.SendOrReceiveARandomDataBlockPerInitialIV(enStream);
|
||||
Logger.LogDebug($"{nameof(ShakeHand)}: Writing header package.");
|
||||
enStream.Write(package.Bytes, 0, Common.PACKAGE_SIZE_EX);
|
||||
enStream.Write(package.Bytes, 0, Package.PACKAGE_SIZE_EX);
|
||||
|
||||
Logger.LogDebug($"{nameof(ShakeHand)}: Sent: clientPush={clientPushData}, postAction={postAction}.");
|
||||
|
||||
deStream = Common.GetDecryptedStream(ns);
|
||||
deStream = Encryption.GetDecryptedStream(ns);
|
||||
Common.SendOrReceiveARandomDataBlockPerInitialIV(deStream, false);
|
||||
|
||||
Logger.LogDebug($"{nameof(ShakeHand)}: Reading header package.");
|
||||
|
||||
int bytesReceived = deStream.ReadEx(buf, 0, Common.PACKAGE_SIZE_EX);
|
||||
int bytesReceived = deStream.ReadEx(buf, 0, Package.PACKAGE_SIZE_EX);
|
||||
package.Bytes = buf;
|
||||
|
||||
string name = "Unknown";
|
||||
|
||||
if (bytesReceived == Common.PACKAGE_SIZE_EX)
|
||||
if (bytesReceived == Package.PACKAGE_SIZE_EX)
|
||||
{
|
||||
if (package.Type is PackageType.Clipboard or PackageType.ClipboardPush)
|
||||
{
|
||||
|
||||
@@ -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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal enum ClipboardPostAction : uint
|
||||
{
|
||||
Other = 0,
|
||||
Desktop = 1,
|
||||
Mspaint = 2,
|
||||
}
|
||||
150
src/modules/MouseWithoutBorders/App/Core/DATA.cs
Normal file
150
src/modules/MouseWithoutBorders/App/Core/DATA.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// In X64, we are WOW
|
||||
[module: SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", Scope = "type", Target = "MouseWithoutBorders.Core.DATA", Justification = "Dotnet port with style preservation")]
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
// The beauty of "union" in C#
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal sealed class DATA
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
internal PackageType Type; // 4 (first byte = package type, 1 = checksum, 2+3 = magic no.)
|
||||
|
||||
[FieldOffset(sizeof(PackageType))]
|
||||
internal int Id; // 4
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + sizeof(uint))]
|
||||
internal ID Src; // 4
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (2 * sizeof(uint)))]
|
||||
internal ID Des; // 4
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal long DateTime;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)) + sizeof(long))]
|
||||
internal KEYBDDATA Kd;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal MOUSEDATA Md;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal ID Machine1;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (4 * sizeof(uint)))]
|
||||
internal ID Machine2;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (5 * sizeof(uint)))]
|
||||
internal ID Machine3;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (6 * sizeof(uint)))]
|
||||
internal ID Machine4;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
|
||||
internal ClipboardPostAction PostAction;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)))]
|
||||
private long machineNameP1;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + sizeof(long))]
|
||||
private long machineNameP2;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (2 * sizeof(long)))]
|
||||
private long machineNameP3;
|
||||
|
||||
[FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (3 * sizeof(long)))]
|
||||
private long machineNameP4;
|
||||
|
||||
internal string MachineName
|
||||
{
|
||||
get
|
||||
{
|
||||
string name = Common.GetString(BitConverter.GetBytes(machineNameP1))
|
||||
+ Common.GetString(BitConverter.GetBytes(machineNameP2))
|
||||
+ Common.GetString(BitConverter.GetBytes(machineNameP3))
|
||||
+ Common.GetString(BitConverter.GetBytes(machineNameP4));
|
||||
return name.Trim();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
byte[] machineName = Common.GetBytes(value.PadRight(32, ' '));
|
||||
machineNameP1 = BitConverter.ToInt64(machineName, 0);
|
||||
machineNameP2 = BitConverter.ToInt64(machineName, 8);
|
||||
machineNameP3 = BitConverter.ToInt64(machineName, 16);
|
||||
machineNameP4 = BitConverter.ToInt64(machineName, 24);
|
||||
}
|
||||
}
|
||||
|
||||
public DATA()
|
||||
{
|
||||
}
|
||||
|
||||
public DATA(byte[] initialData)
|
||||
{
|
||||
Bytes = initialData;
|
||||
}
|
||||
|
||||
internal byte[] Bytes
|
||||
{
|
||||
get
|
||||
{
|
||||
byte[] buf = new byte[IsBigPackage ? Package.PACKAGE_SIZE_EX : Package.PACKAGE_SIZE];
|
||||
Array.Copy(StructToBytes(this), buf, IsBigPackage ? Package.PACKAGE_SIZE_EX : Package.PACKAGE_SIZE);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
Debug.Assert(value.Length <= Package.PACKAGE_SIZE_EX, "Length > package size");
|
||||
byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
|
||||
Array.Copy(value, buf, value.Length);
|
||||
BytesToStruct(buf, this);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsBigPackage
|
||||
{
|
||||
get => Type == 0
|
||||
? throw new InvalidOperationException("Package type not set.")
|
||||
: Type switch
|
||||
{
|
||||
PackageType.Hello or PackageType.Awake or PackageType.Heartbeat or PackageType.Heartbeat_ex or PackageType.Handshake or PackageType.HandshakeAck or PackageType.ClipboardPush or PackageType.Clipboard or PackageType.ClipboardAsk or PackageType.ClipboardImage or PackageType.ClipboardText or PackageType.ClipboardDataEnd => true,
|
||||
_ => (Type & PackageType.Matrix) == PackageType.Matrix,
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] StructToBytes(object structObject)
|
||||
{
|
||||
byte[] bytes = new byte[Package.PACKAGE_SIZE_EX];
|
||||
GCHandle bHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
|
||||
Marshal.StructureToPtr(structObject, Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0), false);
|
||||
bHandle.Free();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void BytesToStruct(byte[] value, object structObject)
|
||||
{
|
||||
GCHandle bHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
|
||||
Marshal.PtrToStructure(Marshal.UnsafeAddrOfPinnedArrayElement(value, 0), structObject);
|
||||
bHandle.Free();
|
||||
}
|
||||
}
|
||||
@@ -67,20 +67,20 @@ internal static class DragDrop
|
||||
return;
|
||||
}
|
||||
|
||||
if (wParam == Common.WM_LBUTTONDOWN)
|
||||
if (wParam == WM.WM_LBUTTONDOWN)
|
||||
{
|
||||
MouseDown = true;
|
||||
DragMachine = MachineStuff.desMachineID;
|
||||
MachineStuff.dropMachineID = ID.NONE;
|
||||
Logger.LogDebug("DragDropStep01: MouseDown");
|
||||
}
|
||||
else if (wParam == Common.WM_LBUTTONUP)
|
||||
else if (wParam == WM.WM_LBUTTONUP)
|
||||
{
|
||||
MouseDown = false;
|
||||
Logger.LogDebug("DragDropStep01: MouseUp");
|
||||
}
|
||||
|
||||
if (wParam == Common.WM_RBUTTONUP && IsDropping)
|
||||
if (wParam == WM.WM_RBUTTONUP && IsDropping)
|
||||
{
|
||||
IsDropping = false;
|
||||
Clipboard.LastIDWithClipboardData = ID.NONE;
|
||||
@@ -252,7 +252,7 @@ internal static class DragDrop
|
||||
|
||||
internal static void DragDropStep09(int wParam)
|
||||
{
|
||||
if (wParam == Common.WM_MOUSEMOVE && IsDropping)
|
||||
if (wParam == WM.WM_MOUSEMOVE && IsDropping)
|
||||
{
|
||||
// Show/Move form
|
||||
Common.DoSomethingInUIThread(() =>
|
||||
@@ -260,7 +260,7 @@ internal static class DragDrop
|
||||
_ = NativeMethods.PostMessage(Common.MainForm.Handle, NativeMethods.WM_SHOW_DRAG_DROP, (IntPtr)0, (IntPtr)0);
|
||||
});
|
||||
}
|
||||
else if (wParam == Common.WM_LBUTTONUP && (IsDropping || IsDragging))
|
||||
else if (wParam == WM.WM_LBUTTONUP && (IsDropping || IsDragging))
|
||||
{
|
||||
if (IsDropping)
|
||||
{
|
||||
|
||||
245
src/modules/MouseWithoutBorders/App/Core/Encryption.cs
Normal file
245
src/modules/MouseWithoutBorders/App/Core/Encryption.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
// 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.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// <summary>
|
||||
// Encrypt/decrypt implementation.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class Encryption
|
||||
{
|
||||
#pragma warning disable SYSLIB0021
|
||||
private static AesCryptoServiceProvider symAl;
|
||||
#pragma warning restore SYSLIB0021
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
|
||||
internal static string myKey;
|
||||
#pragma warning restore SA1307
|
||||
private static uint magicNumber;
|
||||
private static Random ran = new(); // Used for non encryption related functionality.
|
||||
internal const int SymAlBlockSize = 16;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the first encryption block, the following blocks will be combined with the cipher text of the previous block.
|
||||
/// Thus identical blocks in the socket stream would be encrypted to different cipher text blocks.
|
||||
/// The first block is a handshake one containing random data.
|
||||
/// Related Unit Test: TestEncryptDecrypt
|
||||
/// </summary>
|
||||
private static readonly string InitialIV = ulong.MaxValue.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
internal static Random Ran
|
||||
{
|
||||
get => Encryption.ran ??= new Random();
|
||||
set => Encryption.ran = value;
|
||||
}
|
||||
|
||||
internal static uint MagicNumber
|
||||
{
|
||||
get => Encryption.magicNumber;
|
||||
set => Encryption.magicNumber = value;
|
||||
}
|
||||
|
||||
internal static string MyKey
|
||||
{
|
||||
get => Encryption.myKey;
|
||||
|
||||
set
|
||||
{
|
||||
if (Encryption.myKey != value)
|
||||
{
|
||||
Encryption.myKey = value;
|
||||
_ = Task.Factory.StartNew(
|
||||
() => Encryption.GenLegalKey(),
|
||||
System.Threading.CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
TaskScheduler.Default); // Cache the key to improve UX.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string KeyDisplayedText(string key)
|
||||
{
|
||||
string displayedValue = string.Empty;
|
||||
int i = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int length = Math.Min(4, key.Length - i);
|
||||
displayedValue += string.Concat(key.AsSpan(i, length), " ");
|
||||
i += 4;
|
||||
}
|
||||
while (i < key.Length - 1);
|
||||
|
||||
return displayedValue.Trim();
|
||||
}
|
||||
|
||||
internal static bool GeneratedKey { get; set; }
|
||||
|
||||
internal static bool KeyCorrupted { get; set; }
|
||||
|
||||
internal static void InitEncryption()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (symAl == null)
|
||||
{
|
||||
#pragma warning disable SYSLIB0021 // No proper replacement for now
|
||||
symAl = new AesCryptoServiceProvider();
|
||||
#pragma warning restore SYSLIB0021
|
||||
symAl.KeySize = 256;
|
||||
symAl.BlockSize = SymAlBlockSize * 8;
|
||||
symAl.Padding = PaddingMode.Zeros;
|
||||
symAl.Mode = CipherMode.CBC;
|
||||
symAl.GenerateIV();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<string, byte[]> LegalKeyDictionary = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static byte[] GenLegalKey()
|
||||
{
|
||||
byte[] rv;
|
||||
string myKey = Encryption.MyKey;
|
||||
|
||||
if (!LegalKeyDictionary.TryGetValue(myKey, out byte[] value))
|
||||
{
|
||||
Rfc2898DeriveBytes key = new(
|
||||
myKey,
|
||||
Common.GetBytesU(InitialIV),
|
||||
50000,
|
||||
HashAlgorithmName.SHA512);
|
||||
rv = key.GetBytes(32);
|
||||
_ = LegalKeyDictionary.AddOrUpdate(myKey, rv, (k, v) => rv);
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = value;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static byte[] GenLegalIV()
|
||||
{
|
||||
string st = InitialIV;
|
||||
int ivLength = symAl.IV.Length;
|
||||
if (st.Length > ivLength)
|
||||
{
|
||||
st = st[..ivLength];
|
||||
}
|
||||
else if (st.Length < ivLength)
|
||||
{
|
||||
st = st.PadRight(ivLength, ' ');
|
||||
}
|
||||
|
||||
return Common.GetBytes(st);
|
||||
}
|
||||
|
||||
internal static Stream GetEncryptedStream(Stream encryptedStream)
|
||||
{
|
||||
ICryptoTransform encryptor;
|
||||
encryptor = symAl.CreateEncryptor(GenLegalKey(), GenLegalIV());
|
||||
return new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write);
|
||||
}
|
||||
|
||||
internal static Stream GetDecryptedStream(Stream encryptedStream)
|
||||
{
|
||||
ICryptoTransform decryptor;
|
||||
decryptor = symAl.CreateDecryptor(GenLegalKey(), GenLegalIV());
|
||||
return new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);
|
||||
}
|
||||
|
||||
internal static uint Get24BitHash(string st)
|
||||
{
|
||||
if (string.IsNullOrEmpty(st))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[Package.PACKAGE_SIZE];
|
||||
for (int i = 0; i < Package.PACKAGE_SIZE; i++)
|
||||
{
|
||||
if (i < st.Length)
|
||||
{
|
||||
bytes[i] = (byte)st[i];
|
||||
}
|
||||
}
|
||||
|
||||
var hash = SHA512.Create();
|
||||
byte[] hashValue = hash.ComputeHash(bytes);
|
||||
|
||||
for (int i = 0; i < 50000; i++)
|
||||
{
|
||||
hashValue = hash.ComputeHash(hashValue);
|
||||
}
|
||||
|
||||
Logger.LogDebug(string.Format(CultureInfo.CurrentCulture, "magic: {0},{1},{2}", hashValue[0], hashValue[1], hashValue[^1]));
|
||||
hash.Clear();
|
||||
return (uint)((hashValue[0] << 23) + (hashValue[1] << 16) + (hashValue[^1] << 8) + hashValue[2]);
|
||||
}
|
||||
|
||||
internal static string GetDebugInfo(string st)
|
||||
{
|
||||
return string.IsNullOrEmpty(st) ? st : ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static string CreateDefaultKey()
|
||||
{
|
||||
return CreateRandomKey();
|
||||
}
|
||||
|
||||
private const int PW_LENGTH = 16;
|
||||
|
||||
internal static string CreateRandomKey()
|
||||
{
|
||||
// Not including characters like "'`O0& since they are confusing to users.
|
||||
string[] chars = new[] { "abcdefghjkmnpqrstuvxyz", "ABCDEFGHJKMNPQRSTUVXYZ", "123456789", "~!@#$%^*()_-+=:;<,>.?/\\|[]" };
|
||||
char[][] charactersUsedForKey = chars.Select(charset => Enumerable.Range(0, charset.Length - 1).Select(i => charset[i]).ToArray()).ToArray();
|
||||
byte[] randomData = new byte[1];
|
||||
string key = string.Empty;
|
||||
|
||||
do
|
||||
{
|
||||
foreach (string set in chars)
|
||||
{
|
||||
randomData = RandomNumberGenerator.GetBytes(1);
|
||||
key += set[randomData[0] % set.Length];
|
||||
|
||||
if (key.Length >= PW_LENGTH)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (key.Length < PW_LENGTH);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
internal static bool IsKeyValid(string key, out string error)
|
||||
{
|
||||
error = string.IsNullOrEmpty(key) || key.Length < 16
|
||||
? "Key must have at least 16 characters in length (spaces are discarded). Key must be auto generated in one of the machines."
|
||||
: null;
|
||||
|
||||
return error == null;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ internal static class Event
|
||||
// Check if easy mouse setting is enabled.
|
||||
bool isEasyMouseEnabled = IsSwitchingByMouseEnabled();
|
||||
|
||||
if (isEasyMouseEnabled && Common.Sk != null && (Common.DesMachineID == Common.MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == Common.WM_MOUSEMOVE)
|
||||
if (isEasyMouseEnabled && Common.Sk != null && (Common.DesMachineID == Common.MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == WM.WM_MOUSEMOVE)
|
||||
{
|
||||
Point p = MachineStuff.MoveToMyNeighbourIfNeeded(e.X, e.Y, MachineStuff.desMachineID);
|
||||
|
||||
@@ -115,7 +115,7 @@ internal static class Event
|
||||
|
||||
Common.SkSend(MousePackage, null, false);
|
||||
|
||||
if (MousePackage.Md.dwFlags is Common.WM_LBUTTONUP or Common.WM_RBUTTONUP)
|
||||
if (MousePackage.Md.dwFlags is WM.WM_LBUTTONUP or WM.WM_RBUTTONUP)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
@@ -265,7 +265,7 @@ internal static class Event
|
||||
KeybdPackage.Kd = e;
|
||||
KeybdPackage.DateTime = Common.GetTick();
|
||||
Common.SkSend(KeybdPackage, null, false);
|
||||
if (KeybdPackage.Kd.dwFlags is Common.WM_KEYUP or Common.WM_SYSKEYUP)
|
||||
if (KeybdPackage.Kd.dwFlags is WM.WM_KEYUP or WM.WM_SYSKEYUP)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ internal static class Helper
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Common.IsMyDesktopActive())
|
||||
if (!WinAPI.IsMyDesktopActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -314,7 +314,7 @@ internal static class Helper
|
||||
_ = Launch.CreateProcessInInputDesktopSession(
|
||||
$"\"{Path.GetDirectoryName(Application.ExecutablePath)}\\{HelperProcessName}.exe\"",
|
||||
string.Empty,
|
||||
Common.GetInputDesktop(),
|
||||
WinAPI.GetInputDesktop(),
|
||||
0);
|
||||
|
||||
Clipboard.HasSwitchedMachineSinceLastCopy = true;
|
||||
@@ -379,7 +379,7 @@ internal static class Helper
|
||||
log += "=============================================================================================================================\r\n";
|
||||
log += $"{Application.ProductName} version {Application.ProductVersion}\r\n";
|
||||
|
||||
log += $"{Setting.Values.Username}/{Common.GetDebugInfo(Common.MyKey)}\r\n";
|
||||
log += $"{Setting.Values.Username}/{Encryption.GetDebugInfo(Encryption.MyKey)}\r\n";
|
||||
log += $"{Common.MachineName}/{Common.MachineID}/{Common.DesMachineID}\r\n";
|
||||
log += $"Id: {Setting.Values.DeviceId}\r\n";
|
||||
log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n";
|
||||
|
||||
19
src/modules/MouseWithoutBorders/App/Core/ID.cs
Normal file
19
src/modules/MouseWithoutBorders/App/Core/ID.cs
Normal file
@@ -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.
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal enum ID : uint
|
||||
{
|
||||
NONE = 0,
|
||||
ALL = 255,
|
||||
}
|
||||
@@ -89,23 +89,23 @@ internal static class InitAndCleanup
|
||||
internal static void Init()
|
||||
{
|
||||
_ = Helper.GetUserName();
|
||||
Common.GeneratedKey = true;
|
||||
Encryption.GeneratedKey = true;
|
||||
|
||||
try
|
||||
{
|
||||
Common.MyKey = Setting.Values.MyKey;
|
||||
Encryption.MyKey = Setting.Values.MyKey;
|
||||
int tmp = Setting.Values.MyKeyDaysToExpire;
|
||||
}
|
||||
catch (FormatException e)
|
||||
{
|
||||
Common.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Encryption.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
|
||||
Logger.Log(e.Message);
|
||||
}
|
||||
catch (CryptographicException e)
|
||||
{
|
||||
Common.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Encryption.KeyCorrupted = true;
|
||||
Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
|
||||
Logger.Log(e.Message);
|
||||
}
|
||||
|
||||
@@ -127,14 +127,14 @@ internal static class InitAndCleanup
|
||||
bool dummy = Setting.Values.DrawMouseEx;
|
||||
Common.Is64bitOS = IntPtr.Size == 8;
|
||||
Common.tcpPort = Setting.Values.TcpPort;
|
||||
Common.GetScreenConfig();
|
||||
Common.PackageSent = new PackageMonitor(0);
|
||||
Common.PackageReceived = new PackageMonitor(0);
|
||||
WinAPI.GetScreenConfig();
|
||||
Package.PackageSent = new PackageMonitor(0);
|
||||
Package.PackageReceived = new PackageMonitor(0);
|
||||
SetupMachineNameAndID();
|
||||
Common.InitEncryption();
|
||||
Encryption.InitEncryption();
|
||||
CreateHelperThreads();
|
||||
|
||||
SystemEvents.DisplaySettingsChanged += new EventHandler(Common.SystemEvents_DisplaySettingsChanged);
|
||||
SystemEvents.DisplaySettingsChanged += new EventHandler(WinAPI.SystemEvents_DisplaySettingsChanged);
|
||||
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
|
||||
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
|
||||
PleaseReopenSocket = 9;
|
||||
@@ -220,7 +220,7 @@ internal static class InitAndCleanup
|
||||
lastReleaseAllKeysCall = Common.GetTick();
|
||||
|
||||
KEYBDDATA kd;
|
||||
kd.dwFlags = (int)Common.LLKHF.UP;
|
||||
kd.dwFlags = (int)WM.LLKHF.UP;
|
||||
|
||||
VK[] keys = new VK[]
|
||||
{
|
||||
@@ -266,7 +266,7 @@ internal static class InitAndCleanup
|
||||
true);
|
||||
}
|
||||
|
||||
if (!Common.IsMyDesktopActive())
|
||||
if (!WinAPI.IsMyDesktopActive())
|
||||
{
|
||||
PleaseReopenSocket = 0;
|
||||
}
|
||||
|
||||
25
src/modules/MouseWithoutBorders/App/Core/KEYBDDATA.cs
Normal file
25
src/modules/MouseWithoutBorders/App/Core/KEYBDDATA.cs
Normal file
@@ -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.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct KEYBDDATA
|
||||
{
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
|
||||
internal int wVk;
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
|
||||
internal int dwFlags;
|
||||
}
|
||||
@@ -121,52 +121,52 @@ internal static class Logger
|
||||
{
|
||||
string log;
|
||||
|
||||
if (!lastPackageSent.Equals(Common.PackageSent))
|
||||
if (!lastPackageSent.Equals(Package.PackageSent))
|
||||
{
|
||||
log = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"SENT:" + HeaderSENT,
|
||||
Common.PackageSent.Heartbeat,
|
||||
Common.PackageSent.Keyboard,
|
||||
Common.PackageSent.Mouse,
|
||||
Common.PackageSent.Hello,
|
||||
Common.PackageSent.Matrix,
|
||||
Common.PackageSent.ClipboardText,
|
||||
Common.PackageSent.ClipboardImage,
|
||||
Common.PackageSent.ByeBye,
|
||||
Common.PackageSent.Clipboard,
|
||||
Common.PackageSent.ClipboardDragDrop,
|
||||
Common.PackageSent.ClipboardDragDropEnd,
|
||||
Common.PackageSent.ExplorerDragDrop,
|
||||
Package.PackageSent.Heartbeat,
|
||||
Package.PackageSent.Keyboard,
|
||||
Package.PackageSent.Mouse,
|
||||
Package.PackageSent.Hello,
|
||||
Package.PackageSent.Matrix,
|
||||
Package.PackageSent.ClipboardText,
|
||||
Package.PackageSent.ClipboardImage,
|
||||
Package.PackageSent.ByeBye,
|
||||
Package.PackageSent.Clipboard,
|
||||
Package.PackageSent.ClipboardDragDrop,
|
||||
Package.PackageSent.ClipboardDragDropEnd,
|
||||
Package.PackageSent.ExplorerDragDrop,
|
||||
Event.inputEventCount,
|
||||
Common.PackageSent.Nil);
|
||||
Package.PackageSent.Nil);
|
||||
Log(log);
|
||||
lastPackageSent = Common.PackageSent; // Copy data
|
||||
lastPackageSent = Package.PackageSent; // Copy data
|
||||
}
|
||||
|
||||
if (!lastPackageReceived.Equals(Common.PackageReceived))
|
||||
if (!lastPackageReceived.Equals(Package.PackageReceived))
|
||||
{
|
||||
log = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"RECEIVED:" + HeaderRECEIVED,
|
||||
Common.PackageReceived.Heartbeat,
|
||||
Common.PackageReceived.Keyboard,
|
||||
Common.PackageReceived.Mouse,
|
||||
Common.PackageReceived.Hello,
|
||||
Common.PackageReceived.Matrix,
|
||||
Common.PackageReceived.ClipboardText,
|
||||
Common.PackageReceived.ClipboardImage,
|
||||
Common.PackageReceived.ByeBye,
|
||||
Common.PackageReceived.Clipboard,
|
||||
Common.PackageReceived.ClipboardDragDrop,
|
||||
Common.PackageReceived.ClipboardDragDropEnd,
|
||||
Common.PackageReceived.ExplorerDragDrop,
|
||||
Package.PackageReceived.Heartbeat,
|
||||
Package.PackageReceived.Keyboard,
|
||||
Package.PackageReceived.Mouse,
|
||||
Package.PackageReceived.Hello,
|
||||
Package.PackageReceived.Matrix,
|
||||
Package.PackageReceived.ClipboardText,
|
||||
Package.PackageReceived.ClipboardImage,
|
||||
Package.PackageReceived.ByeBye,
|
||||
Package.PackageReceived.Clipboard,
|
||||
Package.PackageReceived.ClipboardDragDrop,
|
||||
Package.PackageReceived.ClipboardDragDropEnd,
|
||||
Package.PackageReceived.ExplorerDragDrop,
|
||||
Event.invalidPackageCount,
|
||||
Common.PackageReceived.Nil,
|
||||
Package.PackageReceived.Nil,
|
||||
Receiver.processedPackageCount,
|
||||
Receiver.skippedPackageCount);
|
||||
Log(log);
|
||||
lastPackageReceived = Common.PackageReceived;
|
||||
lastPackageReceived = Package.PackageReceived;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +209,9 @@ internal static class Logger
|
||||
"Private Mem: " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024).ToString(CultureInfo.CurrentCulture) + "KB",
|
||||
sb.ToString());
|
||||
|
||||
if (!string.IsNullOrEmpty(Common.myKey))
|
||||
if (!string.IsNullOrEmpty(Encryption.myKey))
|
||||
{
|
||||
log = log.Replace(Common.MyKey, Common.GetDebugInfo(Common.MyKey));
|
||||
log = log.Replace(Encryption.MyKey, Encryption.GetDebugInfo(Encryption.MyKey));
|
||||
}
|
||||
|
||||
log += Thread.DumpThreadsStack();
|
||||
@@ -251,14 +251,18 @@ internal static class Logger
|
||||
{
|
||||
typeof(Clipboard),
|
||||
typeof(DragDrop),
|
||||
typeof(Encryption),
|
||||
typeof(Event),
|
||||
typeof(InitAndCleanup),
|
||||
typeof(Helper),
|
||||
typeof(Launch),
|
||||
typeof(Logger),
|
||||
typeof(MachineStuff),
|
||||
typeof(Package),
|
||||
typeof(Receiver),
|
||||
typeof(Service),
|
||||
typeof(WinAPI),
|
||||
typeof(WM),
|
||||
};
|
||||
foreach (var staticType in staticTypes)
|
||||
{
|
||||
@@ -294,7 +298,7 @@ internal static class Logger
|
||||
// strArr[3] = t.FullName;
|
||||
strArr[4] = " = ";
|
||||
strArr[5] = objName.Equals("myKey", StringComparison.OrdinalIgnoreCase)
|
||||
? Common.GetDebugInfo(objString)
|
||||
? Encryption.GetDebugInfo(objString)
|
||||
: objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase)
|
||||
? string.Empty
|
||||
: objString
|
||||
|
||||
26
src/modules/MouseWithoutBorders/App/Core/MOUSEDATA.cs
Normal file
26
src/modules/MouseWithoutBorders/App/Core/MOUSEDATA.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MOUSEDATA
|
||||
{
|
||||
internal int X;
|
||||
internal int Y;
|
||||
internal int WheelDelta;
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
|
||||
internal int dwFlags;
|
||||
}
|
||||
@@ -221,9 +221,9 @@ internal static class MachineStuff
|
||||
|
||||
if (Setting.Values.BlockMouseAtCorners)
|
||||
{
|
||||
lock (Common.SensitivePoints)
|
||||
lock (WinAPI.SensitivePoints)
|
||||
{
|
||||
foreach (Point p in Common.SensitivePoints)
|
||||
foreach (Point p in WinAPI.SensitivePoints)
|
||||
{
|
||||
if (Math.Abs(p.X - x) < 100 && Math.Abs(p.Y - y) < 100)
|
||||
{
|
||||
@@ -793,8 +793,8 @@ internal static class MachineStuff
|
||||
internal static void ShowSetupForm(bool reopenSockets = false)
|
||||
{
|
||||
Logger.LogDebug("========== BEGIN THE SETUP EXPERIENCE ==========", true);
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
Common.GeneratedKey = true;
|
||||
Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
|
||||
Encryption.GeneratedKey = true;
|
||||
|
||||
if (Process.GetCurrentProcess().SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
|
||||
{
|
||||
@@ -1067,7 +1067,7 @@ internal static class MachineStuff
|
||||
|
||||
internal static void AssertOneInstancePerDesktopSession()
|
||||
{
|
||||
string eventName = $"Global\\{Application.ProductName}-{FrmAbout.AssemblyVersion}-{Common.GetMyDesktop()}-{Common.CurrentProcess.SessionId}";
|
||||
string eventName = $"Global\\{Application.ProductName}-{FrmAbout.AssemblyVersion}-{WinAPI.GetMyDesktop()}-{Common.CurrentProcess.SessionId}";
|
||||
oneInstanceCheck = new EventWaitHandle(false, EventResetMode.ManualReset, eventName, out bool created);
|
||||
|
||||
if (!created)
|
||||
|
||||
23
src/modules/MouseWithoutBorders/App/Core/Package.cs
Normal file
23
src/modules/MouseWithoutBorders/App/Core/Package.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class Package
|
||||
{
|
||||
internal const byte PACKAGE_SIZE = 32;
|
||||
internal const byte PACKAGE_SIZE_EX = 64;
|
||||
private const byte WP_PACKAGE_SIZE = 6;
|
||||
internal static PackageMonitor PackageSent;
|
||||
internal static PackageMonitor PackageReceived;
|
||||
internal static int PackageID;
|
||||
}
|
||||
38
src/modules/MouseWithoutBorders/App/Core/PackageMonitor.cs
Normal file
38
src/modules/MouseWithoutBorders/App/Core/PackageMonitor.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal struct PackageMonitor
|
||||
{
|
||||
internal ulong Keyboard;
|
||||
internal ulong Mouse;
|
||||
internal ulong Heartbeat;
|
||||
internal ulong ByeBye;
|
||||
internal ulong Hello;
|
||||
internal ulong Matrix;
|
||||
internal ulong ClipboardText;
|
||||
internal ulong ClipboardImage;
|
||||
internal ulong Clipboard;
|
||||
internal ulong ClipboardDragDrop;
|
||||
internal ulong ClipboardDragDropEnd;
|
||||
internal ulong ClipboardAsk;
|
||||
internal ulong ExplorerDragDrop;
|
||||
internal ulong Nil;
|
||||
|
||||
internal PackageMonitor(ulong value)
|
||||
{
|
||||
ClipboardDragDrop = ClipboardDragDropEnd = ExplorerDragDrop =
|
||||
Keyboard = Mouse = Heartbeat = ByeBye = Hello = Clipboard =
|
||||
Matrix = ClipboardImage = ClipboardText = Nil = ClipboardAsk = value;
|
||||
}
|
||||
}
|
||||
57
src/modules/MouseWithoutBorders/App/Core/PackageType.cs
Normal file
57
src/modules/MouseWithoutBorders/App/Core/PackageType.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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.
|
||||
|
||||
// <summary>
|
||||
// Package format/conversion.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal enum PackageType // : int
|
||||
{
|
||||
// Search for PACKAGE_TYPE_RELATED before changing these!
|
||||
Invalid = 0xFF,
|
||||
|
||||
Error = 0xFE,
|
||||
|
||||
Hi = 2,
|
||||
Hello = 3,
|
||||
ByeBye = 4,
|
||||
|
||||
Heartbeat = 20,
|
||||
Awake = 21,
|
||||
HideMouse = 50,
|
||||
Heartbeat_ex = 51,
|
||||
Heartbeat_ex_l2 = 52,
|
||||
Heartbeat_ex_l3 = 53,
|
||||
|
||||
Clipboard = 69,
|
||||
ClipboardDragDrop = 70,
|
||||
ClipboardDragDropEnd = 71,
|
||||
ExplorerDragDrop = 72,
|
||||
ClipboardCapture = 73,
|
||||
CaptureScreenCommand = 74,
|
||||
ClipboardDragDropOperation = 75,
|
||||
ClipboardDataEnd = 76,
|
||||
MachineSwitched = 77,
|
||||
ClipboardAsk = 78,
|
||||
ClipboardPush = 79,
|
||||
|
||||
NextMachine = 121,
|
||||
Keyboard = 122,
|
||||
Mouse = 123,
|
||||
ClipboardText = 124,
|
||||
ClipboardImage = 125,
|
||||
|
||||
Handshake = 126,
|
||||
HandshakeAck = 127,
|
||||
|
||||
Matrix = 128,
|
||||
MatrixSwapFlag = 2,
|
||||
MatrixTwoRowFlag = 4,
|
||||
}
|
||||
@@ -93,7 +93,7 @@ internal static class Receiver
|
||||
switch (package.Type)
|
||||
{
|
||||
case PackageType.Keyboard:
|
||||
Common.PackageReceived.Keyboard++;
|
||||
Package.PackageReceived.Keyboard++;
|
||||
if (package.Des == Common.MachineID || package.Des == ID.ALL)
|
||||
{
|
||||
JustGotAKey = Common.GetTick();
|
||||
@@ -102,7 +102,7 @@ internal static class Receiver
|
||||
bool nonElevated = Common.RunWithNoAdminRight && false;
|
||||
if (nonElevated && Setting.Values.OneWayControlMode)
|
||||
{
|
||||
if ((package.Kd.dwFlags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
|
||||
if ((package.Kd.dwFlags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
|
||||
{
|
||||
Helper.ShowOneWayModeMessage();
|
||||
}
|
||||
@@ -116,7 +116,7 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.Mouse:
|
||||
Common.PackageReceived.Mouse++;
|
||||
Package.PackageReceived.Mouse++;
|
||||
|
||||
if (package.Des == Common.MachineID || package.Des == ID.ALL)
|
||||
{
|
||||
@@ -127,16 +127,16 @@ internal static class Receiver
|
||||
|
||||
// NOTE(@yuyoyuppe): disabled to drop elevation requirement
|
||||
bool nonElevated = Common.RunWithNoAdminRight && false;
|
||||
if (nonElevated && Setting.Values.OneWayControlMode && package.Md.dwFlags != Common.WM_MOUSEMOVE)
|
||||
if (nonElevated && Setting.Values.OneWayControlMode && package.Md.dwFlags != WM.WM_MOUSEMOVE)
|
||||
{
|
||||
if (!DragDrop.IsDropping)
|
||||
{
|
||||
if (package.Md.dwFlags is Common.WM_LBUTTONDOWN or Common.WM_RBUTTONDOWN)
|
||||
if (package.Md.dwFlags is WM.WM_LBUTTONDOWN or WM.WM_RBUTTONDOWN)
|
||||
{
|
||||
Helper.ShowOneWayModeMessage();
|
||||
}
|
||||
}
|
||||
else if (package.Md.dwFlags is Common.WM_LBUTTONUP or Common.WM_RBUTTONUP)
|
||||
else if (package.Md.dwFlags is WM.WM_LBUTTONUP or WM.WM_RBUTTONUP)
|
||||
{
|
||||
DragDrop.IsDropping = false;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ internal static class Receiver
|
||||
|
||||
if (Math.Abs(package.Md.X) >= Event.MOVE_MOUSE_RELATIVE && Math.Abs(package.Md.Y) >= Event.MOVE_MOUSE_RELATIVE)
|
||||
{
|
||||
if (package.Md.dwFlags == Common.WM_MOUSEMOVE)
|
||||
if (package.Md.dwFlags == WM.WM_MOUSEMOVE)
|
||||
{
|
||||
InputSimulation.MoveMouseRelative(
|
||||
package.Md.X < 0 ? package.Md.X + Event.MOVE_MOUSE_RELATIVE : package.Md.X - Event.MOVE_MOUSE_RELATIVE,
|
||||
@@ -203,19 +203,19 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.ExplorerDragDrop:
|
||||
Common.PackageReceived.ExplorerDragDrop++;
|
||||
Package.PackageReceived.ExplorerDragDrop++;
|
||||
DragDrop.DragDropStep03(package);
|
||||
break;
|
||||
|
||||
case PackageType.Heartbeat:
|
||||
case PackageType.Heartbeat_ex:
|
||||
Common.PackageReceived.Heartbeat++;
|
||||
Package.PackageReceived.Heartbeat++;
|
||||
|
||||
Common.GeneratedKey = Common.GeneratedKey || package.Type == PackageType.Heartbeat_ex;
|
||||
Encryption.GeneratedKey = Encryption.GeneratedKey || package.Type == PackageType.Heartbeat_ex;
|
||||
|
||||
if (Common.GeneratedKey)
|
||||
if (Encryption.GeneratedKey)
|
||||
{
|
||||
Setting.Values.MyKey = Common.MyKey;
|
||||
Setting.Values.MyKey = Encryption.MyKey;
|
||||
Common.SendPackage(ID.ALL, PackageType.Heartbeat_ex_l2);
|
||||
}
|
||||
|
||||
@@ -230,26 +230,26 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.Heartbeat_ex_l2:
|
||||
Common.GeneratedKey = true;
|
||||
Setting.Values.MyKey = Common.MyKey;
|
||||
Encryption.GeneratedKey = true;
|
||||
Setting.Values.MyKey = Encryption.MyKey;
|
||||
Common.SendPackage(ID.ALL, PackageType.Heartbeat_ex_l3);
|
||||
|
||||
break;
|
||||
|
||||
case PackageType.Heartbeat_ex_l3:
|
||||
Common.GeneratedKey = true;
|
||||
Setting.Values.MyKey = Common.MyKey;
|
||||
Encryption.GeneratedKey = true;
|
||||
Setting.Values.MyKey = Encryption.MyKey;
|
||||
|
||||
break;
|
||||
|
||||
case PackageType.Awake:
|
||||
Common.PackageReceived.Heartbeat++;
|
||||
Package.PackageReceived.Heartbeat++;
|
||||
_ = MachineStuff.AddToMachinePool(package);
|
||||
Common.HumanBeingDetected();
|
||||
break;
|
||||
|
||||
case PackageType.Hello:
|
||||
Common.PackageReceived.Hello++;
|
||||
Package.PackageReceived.Hello++;
|
||||
Common.SendHeartBeat();
|
||||
string newMachine = MachineStuff.AddToMachinePool(package);
|
||||
if (Setting.Values.MachineMatrixString == null)
|
||||
@@ -262,16 +262,16 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.Hi:
|
||||
Common.PackageReceived.Hello++;
|
||||
Package.PackageReceived.Hello++;
|
||||
break;
|
||||
|
||||
case PackageType.ByeBye:
|
||||
Common.PackageReceived.ByeBye++;
|
||||
Package.PackageReceived.ByeBye++;
|
||||
Common.ProcessByeByeMessage(package);
|
||||
break;
|
||||
|
||||
case PackageType.Clipboard:
|
||||
Common.PackageReceived.Clipboard++;
|
||||
Package.PackageReceived.Clipboard++;
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
Clipboard.clipboardCopiedTime = Common.GetTick();
|
||||
@@ -291,7 +291,7 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.ClipboardCapture:
|
||||
Common.PackageReceived.Clipboard++;
|
||||
Package.PackageReceived.Clipboard++;
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
if (package.Des == Common.MachineID || package.Des == ID.ALL)
|
||||
@@ -304,7 +304,7 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.CaptureScreenCommand:
|
||||
Common.PackageReceived.Clipboard++;
|
||||
Package.PackageReceived.Clipboard++;
|
||||
if (package.Des == Common.MachineID || package.Des == ID.ALL)
|
||||
{
|
||||
Common.SendImage(package.Src, Common.CaptureScreen());
|
||||
@@ -313,7 +313,7 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.ClipboardAsk:
|
||||
Common.PackageReceived.ClipboardAsk++;
|
||||
Package.PackageReceived.ClipboardAsk++;
|
||||
|
||||
if (package.Des == Common.MachineID)
|
||||
{
|
||||
@@ -344,17 +344,17 @@ internal static class Receiver
|
||||
break;
|
||||
|
||||
case PackageType.ClipboardDragDrop:
|
||||
Common.PackageReceived.ClipboardDragDrop++;
|
||||
Package.PackageReceived.ClipboardDragDrop++;
|
||||
DragDrop.DragDropStep08(package);
|
||||
break;
|
||||
|
||||
case PackageType.ClipboardDragDropOperation:
|
||||
Common.PackageReceived.ClipboardDragDrop++;
|
||||
Package.PackageReceived.ClipboardDragDrop++;
|
||||
DragDrop.DragDropStep08_2(package);
|
||||
break;
|
||||
|
||||
case PackageType.ClipboardDragDropEnd:
|
||||
Common.PackageReceived.ClipboardDragDropEnd++;
|
||||
Package.PackageReceived.ClipboardDragDropEnd++;
|
||||
DragDrop.DragDropStep12();
|
||||
break;
|
||||
|
||||
@@ -363,11 +363,11 @@ internal static class Receiver
|
||||
Clipboard.clipboardCopiedTime = 0;
|
||||
if (package.Type == PackageType.ClipboardImage)
|
||||
{
|
||||
Common.PackageReceived.ClipboardImage++;
|
||||
Package.PackageReceived.ClipboardImage++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.PackageReceived.ClipboardText++;
|
||||
Package.PackageReceived.ClipboardText++;
|
||||
}
|
||||
|
||||
if (tcp != null)
|
||||
@@ -390,7 +390,7 @@ internal static class Receiver
|
||||
default:
|
||||
if ((package.Type & PackageType.Matrix) == PackageType.Matrix)
|
||||
{
|
||||
Common.PackageReceived.Matrix++;
|
||||
Package.PackageReceived.Matrix++;
|
||||
MachineStuff.UpdateMachineMatrix(package);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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 ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class ShutdownWithPowerToys
|
||||
{
|
||||
internal static void WaitForPowerToysRunner(ETWTrace etwTrace)
|
||||
{
|
||||
try
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunnerExitFallback(() =>
|
||||
{
|
||||
etwTrace?.Dispose();
|
||||
Common.MainForm.Quit(true, false);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/modules/MouseWithoutBorders/App/Core/VK.cs
Normal file
88
src/modules/MouseWithoutBorders/App/Core/VK.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
|
||||
// <summary>
|
||||
// Virtual key constants.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal enum VK : ushort
|
||||
{
|
||||
CAPITAL = 0x14,
|
||||
NUMLOCK = 0x90,
|
||||
SHIFT = 0x10,
|
||||
CONTROL = 0x11,
|
||||
MENU = 0x12,
|
||||
ESCAPE = 0x1B,
|
||||
BACK = 0x08,
|
||||
TAB = 0x09,
|
||||
RETURN = 0x0D,
|
||||
PRIOR = 0x21,
|
||||
NEXT = 0x22,
|
||||
END = 0x23,
|
||||
HOME = 0x24,
|
||||
LEFT = 0x25,
|
||||
UP = 0x26,
|
||||
RIGHT = 0x27,
|
||||
DOWN = 0x28,
|
||||
SELECT = 0x29,
|
||||
PRINT = 0x2A,
|
||||
EXECUTE = 0x2B,
|
||||
SNAPSHOT = 0x2C,
|
||||
INSERT = 0x2D,
|
||||
DELETE = 0x2E,
|
||||
HELP = 0x2F,
|
||||
NUMPAD0 = 0x60,
|
||||
NUMPAD1 = 0x61,
|
||||
NUMPAD2 = 0x62,
|
||||
NUMPAD3 = 0x63,
|
||||
NUMPAD4 = 0x64,
|
||||
NUMPAD5 = 0x65,
|
||||
NUMPAD6 = 0x66,
|
||||
NUMPAD7 = 0x67,
|
||||
NUMPAD8 = 0x68,
|
||||
NUMPAD9 = 0x69,
|
||||
MULTIPLY = 0x6A,
|
||||
ADD = 0x6B,
|
||||
SEPARATOR = 0x6C,
|
||||
SUBTRACT = 0x6D,
|
||||
DECIMAL = 0x6E,
|
||||
DIVIDE = 0x6F,
|
||||
F1 = 0x70,
|
||||
F2 = 0x71,
|
||||
F3 = 0x72,
|
||||
F4 = 0x73,
|
||||
F5 = 0x74,
|
||||
F6 = 0x75,
|
||||
F7 = 0x76,
|
||||
F8 = 0x77,
|
||||
F9 = 0x78,
|
||||
F10 = 0x79,
|
||||
F11 = 0x7A,
|
||||
F12 = 0x7B,
|
||||
OEM_1 = 0xBA,
|
||||
OEM_PLUS = 0xBB,
|
||||
OEM_COMMA = 0xBC,
|
||||
OEM_MINUS = 0xBD,
|
||||
OEM_PERIOD = 0xBE,
|
||||
OEM_2 = 0xBF,
|
||||
OEM_3 = 0xC0,
|
||||
MEDIA_NEXT_TRACK = 0xB0,
|
||||
MEDIA_PREV_TRACK = 0xB1,
|
||||
MEDIA_STOP = 0xB2,
|
||||
MEDIA_PLAY_PAUSE = 0xB3,
|
||||
LWIN = 0x5B,
|
||||
RWIN = 0x5C,
|
||||
LSHIFT = 0xA0,
|
||||
RSHIFT = 0xA1,
|
||||
LCONTROL = 0xA2,
|
||||
RCONTROL = 0xA3,
|
||||
LMENU = 0xA4,
|
||||
RMENU = 0xA5,
|
||||
}
|
||||
55
src/modules/MouseWithoutBorders/App/Core/WM.cs
Normal file
55
src/modules/MouseWithoutBorders/App/Core/WM.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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;
|
||||
|
||||
// <summary>
|
||||
// Virtual key constants.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal partial class WM
|
||||
{
|
||||
internal const ushort KEYEVENTF_KEYDOWN = 0x0001;
|
||||
internal const ushort KEYEVENTF_KEYUP = 0x0002;
|
||||
|
||||
internal const int WH_MOUSE = 7;
|
||||
internal const int WH_KEYBOARD = 2;
|
||||
internal const int WH_MOUSE_LL = 14;
|
||||
internal const int WH_KEYBOARD_LL = 13;
|
||||
|
||||
internal const int WM_MOUSEMOVE = 0x200;
|
||||
internal const int WM_LBUTTONDOWN = 0x201;
|
||||
internal const int WM_RBUTTONDOWN = 0x204;
|
||||
internal const int WM_MBUTTONDOWN = 0x207;
|
||||
internal const int WM_XBUTTONDOWN = 0x20B;
|
||||
internal const int WM_LBUTTONUP = 0x202;
|
||||
internal const int WM_RBUTTONUP = 0x205;
|
||||
internal const int WM_MBUTTONUP = 0x208;
|
||||
internal const int WM_XBUTTONUP = 0x20C;
|
||||
internal const int WM_LBUTTONDBLCLK = 0x203;
|
||||
internal const int WM_RBUTTONDBLCLK = 0x206;
|
||||
internal const int WM_MBUTTONDBLCLK = 0x209;
|
||||
internal const int WM_MOUSEWHEEL = 0x020A;
|
||||
internal const int WM_MOUSEHWHEEL = 0x020E;
|
||||
|
||||
internal const int WM_KEYDOWN = 0x100;
|
||||
internal const int WM_KEYUP = 0x101;
|
||||
internal const int WM_SYSKEYDOWN = 0x104;
|
||||
internal const int WM_SYSKEYUP = 0x105;
|
||||
|
||||
[Flags]
|
||||
internal enum LLKHF
|
||||
{
|
||||
EXTENDED = 0x01,
|
||||
INJECTED = 0x10,
|
||||
ALTDOWN = 0x20,
|
||||
UP = 0x80,
|
||||
}
|
||||
}
|
||||
359
src/modules/MouseWithoutBorders/App/Core/WinAPI.cs
Normal file
359
src/modules/MouseWithoutBorders/App/Core/WinAPI.cs
Normal file
@@ -0,0 +1,359 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using MouseWithoutBorders.Class;
|
||||
|
||||
// <summary>
|
||||
// Screen/Desktop helper functions.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
// Desktops, and GetScreenConfig routines
|
||||
internal static class WinAPI
|
||||
{
|
||||
private static MyRectangle newDesktopBounds;
|
||||
private static MyRectangle newPrimaryScreenBounds;
|
||||
private static string activeDesktop;
|
||||
|
||||
private static string ActiveDesktop => WinAPI.activeDesktop;
|
||||
|
||||
internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
GetScreenConfig();
|
||||
}
|
||||
|
||||
internal static readonly List<Point> SensitivePoints = new();
|
||||
|
||||
private static bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref NativeMethods.RECT lprcMonitor, IntPtr dwData)
|
||||
{
|
||||
// lprcMonitor is wrong!!! => using GetMonitorInfo(...)
|
||||
// Log(String.Format( CultureInfo.CurrentCulture,"MONITOR: l{0}, t{1}, r{2}, b{3}", lprcMonitor.Left, lprcMonitor.Top, lprcMonitor.Right, lprcMonitor.Bottom));
|
||||
NativeMethods.MonitorInfoEx mi = default;
|
||||
mi.cbSize = Marshal.SizeOf(mi);
|
||||
_ = NativeMethods.GetMonitorInfo(hMonitor, ref mi);
|
||||
|
||||
try
|
||||
{
|
||||
// For logging only
|
||||
_ = NativeMethods.GetDpiForMonitor(hMonitor, 0, out uint dpiX, out uint dpiY);
|
||||
Logger.Log(string.Format(CultureInfo.CurrentCulture, "MONITOR: ({0}, {1}, {2}, {3}). DPI: ({4}, {5})", mi.rcMonitor.Left, mi.rcMonitor.Top, mi.rcMonitor.Right, mi.rcMonitor.Bottom, dpiX, dpiY));
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Left == 0 && mi.rcMonitor.Top == 0 && mi.rcMonitor.Right != 0 && mi.rcMonitor.Bottom != 0)
|
||||
{
|
||||
// Primary screen
|
||||
_ = Interlocked.Exchange(ref Common.screenWidth, mi.rcMonitor.Right - mi.rcMonitor.Left);
|
||||
_ = Interlocked.Exchange(ref Common.screenHeight, mi.rcMonitor.Bottom - mi.rcMonitor.Top);
|
||||
|
||||
newPrimaryScreenBounds.Left = mi.rcMonitor.Left;
|
||||
newPrimaryScreenBounds.Top = mi.rcMonitor.Top;
|
||||
newPrimaryScreenBounds.Right = mi.rcMonitor.Right;
|
||||
newPrimaryScreenBounds.Bottom = mi.rcMonitor.Bottom;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mi.rcMonitor.Left < newDesktopBounds.Left)
|
||||
{
|
||||
newDesktopBounds.Left = mi.rcMonitor.Left;
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Top < newDesktopBounds.Top)
|
||||
{
|
||||
newDesktopBounds.Top = mi.rcMonitor.Top;
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Right > newDesktopBounds.Right)
|
||||
{
|
||||
newDesktopBounds.Right = mi.rcMonitor.Right;
|
||||
}
|
||||
|
||||
if (mi.rcMonitor.Bottom > newDesktopBounds.Bottom)
|
||||
{
|
||||
newDesktopBounds.Bottom = mi.rcMonitor.Bottom;
|
||||
}
|
||||
}
|
||||
|
||||
lock (SensitivePoints)
|
||||
{
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Top));
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Top));
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Bottom));
|
||||
SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Bottom));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void GetScreenConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("==================== GetScreenConfig started");
|
||||
newDesktopBounds = new MyRectangle();
|
||||
newPrimaryScreenBounds = new MyRectangle();
|
||||
newDesktopBounds.Left = newPrimaryScreenBounds.Left = Screen.PrimaryScreen.Bounds.Left;
|
||||
newDesktopBounds.Top = newPrimaryScreenBounds.Top = Screen.PrimaryScreen.Bounds.Top;
|
||||
newDesktopBounds.Right = newPrimaryScreenBounds.Right = Screen.PrimaryScreen.Bounds.Right;
|
||||
newDesktopBounds.Bottom = newPrimaryScreenBounds.Bottom = Screen.PrimaryScreen.Bounds.Bottom;
|
||||
|
||||
Logger.Log(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
|
||||
Common.RunOnLogonDesktop,
|
||||
WinAPI.newPrimaryScreenBounds.Left,
|
||||
WinAPI.newPrimaryScreenBounds.Top,
|
||||
WinAPI.newPrimaryScreenBounds.Right,
|
||||
WinAPI.newPrimaryScreenBounds.Bottom,
|
||||
WinAPI.newDesktopBounds.Left,
|
||||
WinAPI.newDesktopBounds.Top,
|
||||
WinAPI.newDesktopBounds.Right,
|
||||
WinAPI.newDesktopBounds.Bottom));
|
||||
|
||||
#if USE_MANAGED_ROUTINES
|
||||
// Managed routines do not work well when running on secure desktop:(
|
||||
screenWidth = Screen.PrimaryScreen.Bounds.Width;
|
||||
screenHeight = Screen.PrimaryScreen.Bounds.Height;
|
||||
screenCount = Screen.AllScreens.Length;
|
||||
for (int i = 0; i < Screen.AllScreens.Length; i++)
|
||||
{
|
||||
if (Screen.AllScreens[i].Bounds.Left < desktopBounds.Left) desktopBounds.Left = Screen.AllScreens[i].Bounds.Left;
|
||||
if (Screen.AllScreens[i].Bounds.Top < desktopBounds.Top) desktopBounds.Top = Screen.AllScreens[i].Bounds.Top;
|
||||
if (Screen.AllScreens[i].Bounds.Right > desktopBounds.Right) desktopBounds.Right = Screen.AllScreens[i].Bounds.Right;
|
||||
if (Screen.AllScreens[i].Bounds.Bottom > desktopBounds.Bottom) desktopBounds.Bottom = Screen.AllScreens[i].Bounds.Bottom;
|
||||
}
|
||||
#else
|
||||
lock (SensitivePoints)
|
||||
{
|
||||
SensitivePoints.Clear();
|
||||
}
|
||||
|
||||
NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
|
||||
|
||||
// 1000 calls to EnumDisplayMonitors cost a dozen of milliseconds
|
||||
#endif
|
||||
Interlocked.Exchange(ref MachineStuff.desktopBounds, newDesktopBounds);
|
||||
Interlocked.Exchange(ref MachineStuff.primaryScreenBounds, newPrimaryScreenBounds);
|
||||
|
||||
Logger.Log(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
|
||||
Common.RunOnLogonDesktop,
|
||||
MachineStuff.PrimaryScreenBounds.Left,
|
||||
MachineStuff.PrimaryScreenBounds.Top,
|
||||
MachineStuff.PrimaryScreenBounds.Right,
|
||||
MachineStuff.PrimaryScreenBounds.Bottom,
|
||||
MachineStuff.DesktopBounds.Left,
|
||||
MachineStuff.DesktopBounds.Top,
|
||||
MachineStuff.DesktopBounds.Right,
|
||||
MachineStuff.DesktopBounds.Bottom));
|
||||
|
||||
Logger.Log("==================== GetScreenConfig ended");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
#if USING_SCREEN_SAVER_ROUTINES
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern int PostMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern IntPtr OpenDesktop(string hDesktop, int Flags, bool Inherit, UInt32 DesiredAccess);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool CloseDesktop(IntPtr hDesktop);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool EnumDesktopWindows( IntPtr hDesktop, EnumDesktopWindowsProc callback, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool SystemParametersInfo(int uAction, int uParam, ref int pvParam, int flags);
|
||||
|
||||
private delegate bool EnumDesktopWindowsProc(IntPtr hDesktop, IntPtr lParam);
|
||||
private const int WM_CLOSE = 16;
|
||||
private const int SPI_GETSCREENSAVERRUNNING = 114;
|
||||
|
||||
internal static bool IsScreenSaverRunning()
|
||||
{
|
||||
int isRunning = 0;
|
||||
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0,ref isRunning, 0);
|
||||
return (isRunning != 0);
|
||||
}
|
||||
|
||||
internal static void CloseScreenSaver()
|
||||
{
|
||||
IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
|
||||
if (hDesktop != IntPtr.Zero)
|
||||
{
|
||||
LogDebug("Closing screen saver...");
|
||||
EnumDesktopWindows(hDesktop, new EnumDesktopWindowsProc(CloseScreenSaverFunc), IntPtr.Zero);
|
||||
CloseDesktop(hDesktop);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CloseScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
|
||||
{
|
||||
if (IsWindowVisible(hWnd))
|
||||
{
|
||||
LogDebug("Posting WM_CLOSE to " + hWnd.ToString(CultureInfo.InvariantCulture));
|
||||
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static string GetMyDesktop()
|
||||
{
|
||||
byte[] arThreadDesktop = new byte[256];
|
||||
IntPtr hD = NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId());
|
||||
if (hD != IntPtr.Zero)
|
||||
{
|
||||
_ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arThreadDesktop, arThreadDesktop.Length, out _);
|
||||
return Common.GetString(arThreadDesktop).Replace("\0", string.Empty);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
internal static string GetInputDesktop()
|
||||
{
|
||||
byte[] arInputDesktop = new byte[256];
|
||||
IntPtr hD = NativeMethods.OpenInputDesktop(0, false, NativeMethods.DESKTOP_READOBJECTS);
|
||||
if (hD != IntPtr.Zero)
|
||||
{
|
||||
_ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arInputDesktop, arInputDesktop.Length, out _);
|
||||
return Common.GetString(arInputDesktop).Replace("\0", string.Empty);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static void StartMMService(string desktopToRunMouseWithoutBordersOn)
|
||||
{
|
||||
if (!Common.RunWithNoAdminRight)
|
||||
{
|
||||
Logger.LogDebug("*** Starting on active Desktop: " + desktopToRunMouseWithoutBordersOn);
|
||||
Service.StartMouseWithoutBordersService(desktopToRunMouseWithoutBordersOn);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CheckForDesktopSwitchEvent(bool cleanupIfExit)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
|
||||
{
|
||||
Helper.RunDDHelper(true);
|
||||
int waitCount = 20;
|
||||
|
||||
while (NativeMethods.WTSGetActiveConsoleSessionId() == 0xFFFFFFFF && waitCount > 0)
|
||||
{
|
||||
waitCount--;
|
||||
Logger.LogDebug("The session is detached/attached.");
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
string myDesktop = GetMyDesktop();
|
||||
activeDesktop = GetInputDesktop();
|
||||
|
||||
Logger.LogDebug("*** Active Desktop = " + activeDesktop);
|
||||
Logger.LogDebug("*** My Desktop = " + myDesktop);
|
||||
|
||||
if (myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("*** Active Desktop == My Desktop (TS session)");
|
||||
}
|
||||
|
||||
if (!activeDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
|
||||
!activeDesktop.Equals("default", StringComparison.OrdinalIgnoreCase) &&
|
||||
!activeDesktop.Equals("disconnect", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
StartMMService(activeDesktop);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"{nameof(CheckForDesktopSwitchEvent)}: {e}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Log("*** Active Desktop <> My Desktop");
|
||||
}
|
||||
|
||||
uint sid = NativeMethods.WTSGetActiveConsoleSessionId();
|
||||
|
||||
if (Process.GetProcessesByName(Common.BinaryName).Any(p => (uint)p.SessionId == sid))
|
||||
{
|
||||
Logger.Log("Found MouseWithoutBorders on the active session!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("MouseWithoutBorders not found on the active session!");
|
||||
StartMMService(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!myDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
|
||||
!myDesktop.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("*** Desktop inactive, exiting: " + myDesktop);
|
||||
Setting.Values.LastX = Common.JUST_GOT_BACK_FROM_SCREEN_SAVER;
|
||||
if (cleanupIfExit)
|
||||
{
|
||||
InitAndCleanup.Cleanup();
|
||||
}
|
||||
|
||||
Process.GetCurrentProcess().KillProcess();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Point p;
|
||||
|
||||
internal static bool IsMyDesktopActive()
|
||||
{
|
||||
return NativeMethods.GetCursorPos(ref p);
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
protected string GetSecureKey()
|
||||
{
|
||||
return Common.MyKey;
|
||||
return Encryption.MyKey;
|
||||
}
|
||||
|
||||
private void BackButton_Click(object sender, EventArgs e)
|
||||
|
||||
@@ -89,8 +89,8 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
if (GetSecureKey() != SecurityCodeField.Text)
|
||||
{
|
||||
Common.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty);
|
||||
SecurityCode = Common.MyKey;
|
||||
Encryption.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty);
|
||||
SecurityCode = Encryption.MyKey;
|
||||
}
|
||||
|
||||
MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { ComputerNameField.Text.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty };
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace MouseWithoutBorders
|
||||
internal void UpdateKeyTextBox()
|
||||
{
|
||||
_ = Helper.GetUserName();
|
||||
textBoxEnc.Text = Common.MyKey;
|
||||
textBoxEnc.Text = Encryption.MyKey;
|
||||
}
|
||||
|
||||
private void InitAll()
|
||||
@@ -505,19 +505,19 @@ namespace MouseWithoutBorders
|
||||
|
||||
private bool UpdateKey(string newKey)
|
||||
{
|
||||
if (!Common.IsKeyValid(newKey, out string rv))
|
||||
if (!Encryption.IsKeyValid(newKey, out string rv))
|
||||
{
|
||||
ShowKeyErrorMsg(rv);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newKey.Equals(Common.MyKey, StringComparison.OrdinalIgnoreCase))
|
||||
if (!newKey.Equals(Encryption.MyKey, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Common.MyKey = newKey;
|
||||
Common.GeneratedKey = false;
|
||||
Encryption.MyKey = newKey;
|
||||
Encryption.GeneratedKey = false;
|
||||
}
|
||||
|
||||
Common.MagicNumber = Common.Get24BitHash(Common.MyKey);
|
||||
Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1116,10 +1116,10 @@ namespace MouseWithoutBorders
|
||||
|
||||
if (MessageBox.Show(message, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
|
||||
{
|
||||
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
|
||||
textBoxEnc.Text = Common.MyKey;
|
||||
Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
|
||||
textBoxEnc.Text = Encryption.MyKey;
|
||||
checkBoxShowKey.Checked = true;
|
||||
Common.GeneratedKey = true;
|
||||
Encryption.GeneratedKey = true;
|
||||
ButtonOK_Click(null, null);
|
||||
Common.ShowToolTip("New security key was generated, update other machines to the same key.", 10000, ToolTipIcon.Info, false);
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
try
|
||||
{
|
||||
if (!Common.IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
|
||||
if (!WinAPI.IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
|
||||
{
|
||||
myDesktopNotActive = true;
|
||||
|
||||
@@ -348,7 +348,7 @@ namespace MouseWithoutBorders
|
||||
Common.Hook?.ResetLastSwitchKeys();
|
||||
});
|
||||
|
||||
Common.CheckForDesktopSwitchEvent(true);
|
||||
WinAPI.CheckForDesktopSwitchEvent(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -369,21 +369,21 @@ namespace MouseWithoutBorders
|
||||
if (myDesktopNotActive)
|
||||
{
|
||||
myDesktopNotActive = false;
|
||||
Common.MyKey = Setting.Values.MyKey;
|
||||
Encryption.MyKey = Setting.Values.MyKey;
|
||||
}
|
||||
|
||||
MachineStuff.UpdateMachinePoolStringSetting();
|
||||
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && (Setting.Values.FirstRun || Common.KeyCorrupted))
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && (Setting.Values.FirstRun || Encryption.KeyCorrupted))
|
||||
{
|
||||
if (!shownSetupFormOneTime)
|
||||
{
|
||||
shownSetupFormOneTime = true;
|
||||
MachineStuff.ShowMachineMatrix();
|
||||
|
||||
if (Common.KeyCorrupted && !Setting.Values.FirstRun)
|
||||
if (Encryption.KeyCorrupted && !Setting.Values.FirstRun)
|
||||
{
|
||||
Common.KeyCorrupted = false;
|
||||
Encryption.KeyCorrupted = false;
|
||||
string msg = "The security key is corrupted for some reason, please re-setup.";
|
||||
MessageBox.Show(msg, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
@@ -490,9 +490,9 @@ namespace MouseWithoutBorders
|
||||
|
||||
if (count == 600)
|
||||
{
|
||||
if (!Common.GeneratedKey)
|
||||
if (!Encryption.GeneratedKey)
|
||||
{
|
||||
Common.MyKey = Setting.Values.MyKey;
|
||||
Encryption.MyKey = Setting.Values.MyKey;
|
||||
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
@@ -505,7 +505,7 @@ namespace MouseWithoutBorders
|
||||
Common.ShowToolTip("The security key must be auto generated in one of the machines.", 10000);
|
||||
}
|
||||
}
|
||||
else if (!Common.KeyCorrupted && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && !Setting.Values.FirstRun && Common.AtLeastOneSocketConnected())
|
||||
else if (!Encryption.KeyCorrupted && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && !Setting.Values.FirstRun && Common.AtLeastOneSocketConnected())
|
||||
{
|
||||
int myKeyDaysToExpire = Setting.Values.MyKeyDaysToExpire;
|
||||
|
||||
@@ -531,7 +531,7 @@ namespace MouseWithoutBorders
|
||||
#if SHOW_ON_WINLOGON
|
||||
// if (Common.RunOnLogonDesktop) ShowMouseWithoutBordersUiOnWinLogonDesktop(false);
|
||||
#endif
|
||||
Common.CheckForDesktopSwitchEvent(true);
|
||||
WinAPI.CheckForDesktopSwitchEvent(true);
|
||||
MachineStuff.UpdateClientSockets("helperTimer_Tick"); // Sockets may be closed by the remote host when both machines switch desktop at the same time.
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
int rv = 0;
|
||||
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() && (rv = Helper.SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero)) <= 0)
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() && (rv = Helper.SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero)) <= 0)
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{Helper.HELPER_FORM_TEXT} not found: {rv}", SeverityLevel.Warning);
|
||||
}
|
||||
|
||||
@@ -46,79 +46,6 @@ avgSendTime = 0
|
||||
maxSendTime = 0
|
||||
totalSendCount = 0
|
||||
totalSendTime = 0
|
||||
magicNumber = 0
|
||||
ran = System.Random
|
||||
--_impl = System.Random+XoshiroImpl
|
||||
----_s0 = ????????????
|
||||
----_s1 = ????????????
|
||||
----_s2 = ????????????
|
||||
----_s3 = ????????????
|
||||
--<Shared>k__BackingField = System.Random+ThreadSafeRandom
|
||||
InitialIV = ????????????
|
||||
<GeneratedKey>k__BackingField = False
|
||||
<KeyCorrupted>k__BackingField = False
|
||||
LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte[]]
|
||||
--_tables = Concurrent.ConcurrentDictionary`2+Tables[System.String,System.Byte[]]
|
||||
----_comparer = Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
|
||||
----_buckets = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]
|
||||
------System.Collections.Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][] = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]: N/A
|
||||
----_fastModBucketsMultiplier = 498560650640798693
|
||||
----_locks = O[]
|
||||
------System.Object[] = O[]: N/A
|
||||
----_countPerLock = 32[]
|
||||
------[0] = 0
|
||||
------[1] = 0
|
||||
------[2] = 0
|
||||
------[3] = 0
|
||||
------[4] = 0
|
||||
------[5] = 0
|
||||
------[6] = 0
|
||||
------[7] = 0
|
||||
--_budget = ????????????
|
||||
--_growLockArray = True
|
||||
--_comparerIsDefaultForClasses = False
|
||||
PackageSent = MouseWithoutBorders.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageReceived = MouseWithoutBorders.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageID = 0
|
||||
SensitivePoints = Generic.List`1[Point]
|
||||
--_items = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
--_size = 0
|
||||
--_version = 0
|
||||
--s_emptyArray = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
p = {X=0,Y=0}
|
||||
--x = 0
|
||||
--y = 0
|
||||
--Empty = {X=0,Y=0}
|
||||
<IpcChannelCreated>k__BackingField = False
|
||||
TOGGLE_ICONS_SIZE = 4
|
||||
ICON_ONE = 0
|
||||
@@ -128,34 +55,6 @@ ICON_BIG_CLIPBOARD = 3
|
||||
ICON_ERROR = 4
|
||||
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
|
||||
NETWORK_STREAM_BUF_SIZE = 1048576
|
||||
SymAlBlockSize = 16
|
||||
PW_LENGTH = 16
|
||||
PACKAGE_SIZE = 32
|
||||
PACKAGE_SIZE_EX = 64
|
||||
WP_PACKAGE_SIZE = 6
|
||||
KEYEVENTF_KEYDOWN = 1
|
||||
KEYEVENTF_KEYUP = 2
|
||||
WH_MOUSE = 7
|
||||
WH_KEYBOARD = 2
|
||||
WH_MOUSE_LL = 14
|
||||
WH_KEYBOARD_LL = 13
|
||||
WM_MOUSEMOVE = 512
|
||||
WM_LBUTTONDOWN = 513
|
||||
WM_RBUTTONDOWN = 516
|
||||
WM_MBUTTONDOWN = 519
|
||||
WM_XBUTTONDOWN = 523
|
||||
WM_LBUTTONUP = 514
|
||||
WM_RBUTTONUP = 517
|
||||
WM_MBUTTONUP = 520
|
||||
WM_XBUTTONUP = 524
|
||||
WM_LBUTTONDBLCLK = 515
|
||||
WM_RBUTTONDBLCLK = 518
|
||||
WM_MBUTTONDBLCLK = 521
|
||||
WM_MOUSEWHEEL = 522
|
||||
WM_KEYDOWN = 256
|
||||
WM_KEYUP = 257
|
||||
WM_SYSKEYDOWN = 260
|
||||
WM_SYSKEYUP = 261
|
||||
[Clipboard]
|
||||
===============
|
||||
Comma = System.Char[]
|
||||
@@ -193,16 +92,51 @@ dragDropStep05ExCalledByIpc = 0
|
||||
isDropping = False
|
||||
dragMachine = NONE
|
||||
<MouseDown>k__BackingField = False
|
||||
[Encryption]
|
||||
===============
|
||||
magicNumber = 0
|
||||
ran = System.Random
|
||||
--_impl = System.Random+XoshiroImpl
|
||||
----_s0 = ????????????
|
||||
----_s1 = ????????????
|
||||
----_s2 = ????????????
|
||||
----_s3 = ????????????
|
||||
--<Shared>k__BackingField = System.Random+ThreadSafeRandom
|
||||
InitialIV = ????????????
|
||||
<GeneratedKey>k__BackingField = False
|
||||
<KeyCorrupted>k__BackingField = False
|
||||
LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte[]]
|
||||
--_tables = Concurrent.ConcurrentDictionary`2+Tables[System.String,System.Byte[]]
|
||||
----_comparer = Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
|
||||
----_buckets = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]
|
||||
------System.Collections.Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][] = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]: N/A
|
||||
----_fastModBucketsMultiplier = 498560650640798693
|
||||
----_locks = O[]
|
||||
------System.Object[] = O[]: N/A
|
||||
----_countPerLock = 32[]
|
||||
------[0] = 0
|
||||
------[1] = 0
|
||||
------[2] = 0
|
||||
------[3] = 0
|
||||
------[4] = 0
|
||||
------[5] = 0
|
||||
------[6] = 0
|
||||
------[7] = 0
|
||||
--_budget = ????????????
|
||||
--_growLockArray = True
|
||||
--_comparerIsDefaultForClasses = False
|
||||
SymAlBlockSize = 16
|
||||
PW_LENGTH = 16
|
||||
[Event]
|
||||
===============
|
||||
KeybdPackage = MouseWithoutBorders.DATA
|
||||
KeybdPackage = MouseWithoutBorders.Core.DATA
|
||||
--Type = 0
|
||||
--Id = 0
|
||||
--Src = NONE
|
||||
--Des = NONE
|
||||
--DateTime = 0
|
||||
--Kd = MouseWithoutBorders.KEYBDDATA
|
||||
--Md = MouseWithoutBorders.MOUSEDATA
|
||||
--Kd = MouseWithoutBorders.Core.KEYBDDATA
|
||||
--Md = MouseWithoutBorders.Core.MOUSEDATA
|
||||
--Machine1 = NONE
|
||||
--Machine2 = NONE
|
||||
--Machine3 = NONE
|
||||
@@ -212,14 +146,14 @@ KeybdPackage = MouseWithoutBorders.DATA
|
||||
--machineNameP2 = 0
|
||||
--machineNameP3 = 0
|
||||
--machineNameP4 = 0
|
||||
MousePackage = MouseWithoutBorders.DATA
|
||||
MousePackage = MouseWithoutBorders.Core.DATA
|
||||
--Type = 0
|
||||
--Id = 0
|
||||
--Src = NONE
|
||||
--Des = NONE
|
||||
--DateTime = 0
|
||||
--Kd = MouseWithoutBorders.KEYBDDATA
|
||||
--Md = MouseWithoutBorders.MOUSEDATA
|
||||
--Kd = MouseWithoutBorders.Core.KEYBDDATA
|
||||
--Md = MouseWithoutBorders.Core.MOUSEDATA
|
||||
--Machine1 = NONE
|
||||
--Machine2 = NONE
|
||||
--Machine3 = NONE
|
||||
@@ -296,7 +230,7 @@ LogCounter = Concurrent.ConcurrentDictionary`2[System.String,32]
|
||||
allLogsIndex = 0
|
||||
lastHour = 0
|
||||
exceptionCount = 0
|
||||
lastPackageSent = MouseWithoutBorders.PackageMonitor
|
||||
lastPackageSent = MouseWithoutBorders.Core.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
@@ -311,7 +245,7 @@ lastPackageSent = MouseWithoutBorders.PackageMonitor
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
lastPackageReceived = MouseWithoutBorders.PackageMonitor
|
||||
lastPackageReceived = MouseWithoutBorders.Core.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
@@ -366,6 +300,42 @@ MAX_SOCKET = 8
|
||||
HEARTBEAT_TIMEOUT = 1500000
|
||||
SKIP_PIXELS = 1
|
||||
JUMP_PIXELS = 2
|
||||
[Package]
|
||||
===============
|
||||
PackageSent = MouseWithoutBorders.Core.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageReceived = MouseWithoutBorders.Core.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageID = 0
|
||||
PACKAGE_SIZE = 32
|
||||
PACKAGE_SIZE_EX = 64
|
||||
WP_PACKAGE_SIZE = 6
|
||||
[Receiver]
|
||||
===============
|
||||
QUEUE_SIZE = 50
|
||||
@@ -436,3 +406,41 @@ lastStartServiceTime = ????????????
|
||||
--MinValue = 01/01/0001 00:00:00
|
||||
--MaxValue = 31/12/9999 23:59:59
|
||||
--UnixEpoch = 01/01/1970 00:00:00
|
||||
[WinAPI]
|
||||
===============
|
||||
SensitivePoints = Generic.List`1[Point]
|
||||
--_items = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
--_size = 0
|
||||
--_version = 0
|
||||
--s_emptyArray = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
p = {X=0,Y=0}
|
||||
--x = 0
|
||||
--y = 0
|
||||
--Empty = {X=0,Y=0}
|
||||
[WM]
|
||||
===============
|
||||
KEYEVENTF_KEYDOWN = 1
|
||||
KEYEVENTF_KEYUP = 2
|
||||
WH_MOUSE = 7
|
||||
WH_KEYBOARD = 2
|
||||
WH_MOUSE_LL = 14
|
||||
WH_KEYBOARD_LL = 13
|
||||
WM_MOUSEMOVE = 512
|
||||
WM_LBUTTONDOWN = 513
|
||||
WM_RBUTTONDOWN = 516
|
||||
WM_MBUTTONDOWN = 519
|
||||
WM_XBUTTONDOWN = 523
|
||||
WM_LBUTTONUP = 514
|
||||
WM_RBUTTONUP = 517
|
||||
WM_MBUTTONUP = 520
|
||||
WM_XBUTTONUP = 524
|
||||
WM_LBUTTONDBLCLK = 515
|
||||
WM_RBUTTONDBLCLK = 518
|
||||
WM_MBUTTONDBLCLK = 521
|
||||
WM_MOUSEWHEEL = 522
|
||||
WM_KEYDOWN = 256
|
||||
WM_KEYUP = 257
|
||||
WM_SYSKEYDOWN = 260
|
||||
WM_SYSKEYUP = 261
|
||||
|
||||
@@ -121,7 +121,7 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
|
||||
BEGIN
|
||||
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
|
||||
LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10
|
||||
LTEXT "ZoomIt v9.21",IDC_VERSION,42,7,73,10
|
||||
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
|
||||
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
||||
"SysLink",WS_TABSTOP,42,26,150,9
|
||||
|
||||
@@ -350,7 +350,7 @@ namespace Awake.Core
|
||||
TrayHelper.TimedIcon,
|
||||
TrayIconAction.Update);
|
||||
},
|
||||
_ => HandleTimerCompletion("timed"),
|
||||
() => HandleTimerCompletion("timed"),
|
||||
_tokenSource.Token);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IGalleryGridLayout> _model;
|
||||
|
||||
public bool ShowTitle { get; private set; }
|
||||
|
||||
public bool ShowSubtitle { get; private set; }
|
||||
|
||||
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
_model = new(galleryGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public bool ShowSubtitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
|
||||
@@ -6,5 +6,9 @@ namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public interface IGridPropertiesViewModel
|
||||
{
|
||||
bool ShowTitle { get; }
|
||||
|
||||
bool ShowSubtitle { get; }
|
||||
|
||||
void InitializeProperties();
|
||||
}
|
||||
|
||||
@@ -10,10 +10,9 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
|
||||
: CommandItemViewModel(new(model), context)
|
||||
public partial class ListItemViewModel : CommandItemViewModel
|
||||
{
|
||||
public new ExtensionObject<IListItem> Model { get; } = new(model);
|
||||
public new ExtensionObject<IListItem> Model { get; }
|
||||
|
||||
public List<TagViewModel>? Tags { get; set; }
|
||||
|
||||
@@ -32,6 +31,40 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
|
||||
public string AccessibleName { get; private set; } = string.Empty;
|
||||
|
||||
public bool ShowTitle { get; private set; }
|
||||
|
||||
public bool ShowSubtitle { get; private set; }
|
||||
|
||||
public bool LayoutShowsTitle
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref field, value))
|
||||
{
|
||||
UpdateShowsTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool LayoutShowsSubtitle
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref field, value))
|
||||
{
|
||||
UpdateShowsSubtitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
|
||||
: base(new(model), context)
|
||||
{
|
||||
Model = new ExtensionObject<IListItem>(model);
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
if (IsInitialized)
|
||||
@@ -93,16 +126,18 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(Tags):
|
||||
case nameof(model.Tags):
|
||||
UpdateTags(model.Tags);
|
||||
break;
|
||||
case nameof(TextToSuggest):
|
||||
this.TextToSuggest = model.TextToSuggest ?? string.Empty;
|
||||
case nameof(model.TextToSuggest):
|
||||
TextToSuggest = model.TextToSuggest ?? string.Empty;
|
||||
UpdateProperty(nameof(TextToSuggest));
|
||||
break;
|
||||
case nameof(Section):
|
||||
this.Section = model.Section ?? string.Empty;
|
||||
case nameof(model.Section):
|
||||
Section = model.Section ?? string.Empty;
|
||||
UpdateProperty(nameof(Section));
|
||||
break;
|
||||
case nameof(Details):
|
||||
case nameof(model.Details):
|
||||
var extensionDetails = model.Details;
|
||||
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
|
||||
Details?.InitializeProperties();
|
||||
@@ -110,16 +145,24 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
UpdateShowDetailsCommand();
|
||||
break;
|
||||
case nameof(MoreCommands):
|
||||
case nameof(model.MoreCommands):
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
AddShowDetailsCommands();
|
||||
break;
|
||||
case nameof(Title):
|
||||
case nameof(Subtitle):
|
||||
case nameof(model.Title):
|
||||
UpdateProperty(nameof(Title));
|
||||
UpdateShowsTitle();
|
||||
UpdateAccessibleName();
|
||||
break;
|
||||
case nameof(model.Subtitle):
|
||||
UpdateProperty(nameof(Subtitle));
|
||||
UpdateShowsSubtitle();
|
||||
UpdateAccessibleName();
|
||||
break;
|
||||
default:
|
||||
UpdateProperty(propertyName);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
|
||||
// TODO: Do we want filters to match descriptions and other properties? Tags, etc... Yes?
|
||||
@@ -206,11 +249,32 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
// many COM exception issues.
|
||||
Tags = [.. newTags];
|
||||
|
||||
UpdateProperty(nameof(Tags));
|
||||
UpdateProperty(nameof(HasTags));
|
||||
// We're already in UI thread, so just raise the events
|
||||
OnPropertyChanged(nameof(Tags));
|
||||
OnPropertyChanged(nameof(HasTags));
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateShowsTitle()
|
||||
{
|
||||
var oldShowTitle = ShowTitle;
|
||||
ShowTitle = LayoutShowsTitle;
|
||||
if (oldShowTitle != ShowTitle)
|
||||
{
|
||||
UpdateProperty(nameof(ShowTitle));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateShowsSubtitle()
|
||||
{
|
||||
var oldShowSubtitle = ShowSubtitle;
|
||||
ShowSubtitle = LayoutShowsSubtitle && !string.IsNullOrWhiteSpace(Subtitle);
|
||||
if (oldShowSubtitle != ShowSubtitle)
|
||||
{
|
||||
UpdateProperty(nameof(ShowSubtitle));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UnsafeCleanup()
|
||||
{
|
||||
base.UnsafeCleanup();
|
||||
|
||||
@@ -24,8 +24,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
// Observable from MVVM Toolkit will auto create public properties that use INotifyPropertyChange change
|
||||
// https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/observablegroupedcollections for grouping support
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
|
||||
public ObservableCollection<ListItemViewModel> FilteredItems { get; } = [];
|
||||
|
||||
public FiltersViewModel? Filters { get; set; }
|
||||
|
||||
@@ -224,6 +223,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// TODO we can probably further optimize this by also keeping a
|
||||
// HashSet of every ExtensionObject we currently have, and only
|
||||
// building new viewmodels for the ones we haven't already built.
|
||||
var showsTitle = GridProperties?.ShowTitle ?? true;
|
||||
var showsSubtitle = GridProperties?.ShowSubtitle ?? true;
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
// Check for cancellation during item processing
|
||||
@@ -237,6 +238,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// If an item fails to load, silently ignore it.
|
||||
if (viewModel.SafeFastInit())
|
||||
{
|
||||
viewModel.LayoutShowsTitle = showsTitle;
|
||||
viewModel.LayoutShowsSubtitle = showsSubtitle;
|
||||
newViewModels.Add(viewModel);
|
||||
}
|
||||
}
|
||||
@@ -583,6 +586,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(GridProperties));
|
||||
ApplyLayoutToItems();
|
||||
|
||||
ShowDetails = model.ShowDetails;
|
||||
UpdateProperty(nameof(ShowDetails));
|
||||
@@ -608,22 +612,15 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
}
|
||||
|
||||
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
private static IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
{
|
||||
if (gridProperties is IMediumGridLayout mediumGridLayout)
|
||||
return gridProperties switch
|
||||
{
|
||||
return new MediumGridPropertiesViewModel(mediumGridLayout);
|
||||
}
|
||||
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
return new GalleryGridPropertiesViewModel(galleryGridLayout);
|
||||
}
|
||||
else if (gridProperties is ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
return new SmallGridPropertiesViewModel(smallGridLayout);
|
||||
}
|
||||
|
||||
return null;
|
||||
IMediumGridLayout mediumGridLayout => new MediumGridPropertiesViewModel(mediumGridLayout),
|
||||
IGalleryGridLayout galleryGridLayout => new GalleryGridPropertiesViewModel(galleryGridLayout),
|
||||
ISmallGridLayout smallGridLayout => new SmallGridPropertiesViewModel(smallGridLayout),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public void LoadMoreIfNeeded()
|
||||
@@ -685,6 +682,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
ApplyLayoutToItems();
|
||||
break;
|
||||
case nameof(ShowDetails):
|
||||
ShowDetails = model.ShowDetails;
|
||||
@@ -730,6 +728,21 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
private void ApplyLayoutToItems()
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
var showsTitle = GridProperties?.ShowTitle ?? true;
|
||||
var showsSubtitle = GridProperties?.ShowSubtitle ?? true;
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
item.LayoutShowsTitle = showsTitle;
|
||||
item.LayoutShowsSubtitle = showsSubtitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -11,13 +11,15 @@ public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IMediumGridLayout> _model;
|
||||
|
||||
public bool ShowTitle { get; private set; }
|
||||
|
||||
public bool ShowSubtitle => false;
|
||||
|
||||
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
_model = new(mediumGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
|
||||
@@ -11,6 +11,10 @@ public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<ISmallGridLayout> _model;
|
||||
|
||||
public bool ShowTitle => false;
|
||||
|
||||
public bool ShowSubtitle => false;
|
||||
|
||||
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
_model = new(smallGridLayout);
|
||||
|
||||
@@ -36,6 +36,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
"com.microsoft.cmdpal.builtin.websearch",
|
||||
"com.microsoft.cmdpal.builtin.windowssettings",
|
||||
"com.microsoft.cmdpal.builtin.datetime",
|
||||
"com.microsoft.cmdpal.builtin.remotedesktop",
|
||||
];
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
@@ -160,7 +161,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
{
|
||||
return _tlcManager
|
||||
.TopLevelCommands
|
||||
.Where(tlc => !string.IsNullOrEmpty(tlc.Title))
|
||||
.Where(tlc => !tlc.IsFallback && !string.IsNullOrEmpty(tlc.Title))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
@@ -15,6 +17,8 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class SettingsModel : ObservableObject
|
||||
{
|
||||
private const string DeprecatedHotkeyGoesHomeKey = "HotkeyGoesHome";
|
||||
|
||||
[JsonIgnore]
|
||||
public static readonly string FilePath;
|
||||
|
||||
@@ -30,8 +34,6 @@ public partial class SettingsModel : ObservableObject
|
||||
|
||||
public bool ShowAppDetails { get; set; }
|
||||
|
||||
public bool HotkeyGoesHome { get; set; }
|
||||
|
||||
public bool BackspaceGoesBack { get; set; }
|
||||
|
||||
public bool SingleClickActivates { get; set; }
|
||||
@@ -56,6 +58,8 @@ public partial class SettingsModel : ObservableObject
|
||||
|
||||
public WindowPosition? LastWindowPosition { get; set; }
|
||||
|
||||
public TimeSpan AutoGoHomeInterval { get; set; } = Timeout.InfiniteTimeSpan;
|
||||
|
||||
// END SETTINGS
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -98,12 +102,29 @@ public partial class SettingsModel : ObservableObject
|
||||
{
|
||||
// Read the JSON content from the file
|
||||
var jsonContent = File.ReadAllText(FilePath);
|
||||
var loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, JsonSerializationContext.Default.SettingsModel) ?? new();
|
||||
|
||||
var loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, JsonSerializationContext.Default.SettingsModel);
|
||||
var migratedAny = false;
|
||||
try
|
||||
{
|
||||
if (JsonNode.Parse(jsonContent) is JsonObject root)
|
||||
{
|
||||
migratedAny |= ApplyMigrations(root, loaded);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Migration check failed: {ex}");
|
||||
}
|
||||
|
||||
Debug.WriteLine(loaded is not null ? "Loaded settings file" : "Failed to parse");
|
||||
Debug.WriteLine("Loaded settings file");
|
||||
|
||||
return loaded ?? new();
|
||||
if (migratedAny)
|
||||
{
|
||||
SaveSettings(loaded);
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -113,6 +134,51 @@ public partial class SettingsModel : ObservableObject
|
||||
return new();
|
||||
}
|
||||
|
||||
private static bool ApplyMigrations(JsonObject root, SettingsModel model)
|
||||
{
|
||||
var migrated = false;
|
||||
|
||||
// Migration #1: HotkeyGoesHome (bool) -> AutoGoHomeInterval (TimeSpan)
|
||||
// The old 'HotkeyGoesHome' boolean indicated whether the "go home" action should happen immediately (true) or never (false).
|
||||
// The new 'AutoGoHomeInterval' uses a TimeSpan: 'TimeSpan.Zero' means immediate, 'Timeout.InfiniteTimeSpan' means never.
|
||||
migrated |= TryMigrate(
|
||||
"Migration #1: HotkeyGoesHome (bool) -> AutoGoHomeInterval (TimeSpan)",
|
||||
root,
|
||||
model,
|
||||
nameof(AutoGoHomeInterval),
|
||||
DeprecatedHotkeyGoesHomeKey,
|
||||
(settingsModel, goesHome) => settingsModel.AutoGoHomeInterval = goesHome ? TimeSpan.Zero : Timeout.InfiniteTimeSpan,
|
||||
JsonSerializationContext.Default.Boolean);
|
||||
|
||||
return migrated;
|
||||
}
|
||||
|
||||
private static bool TryMigrate<T>(string migrationName, JsonObject root, SettingsModel model, string newKey, string oldKey, Action<SettingsModel, T> apply, JsonTypeInfo<T> jsonTypeInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If new key already present, skip migration
|
||||
if (root.ContainsKey(newKey) && root[newKey] is not null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If old key present, try to deserialize and apply
|
||||
if (root.TryGetPropertyValue(oldKey, out var oldNode) && oldNode is not null)
|
||||
{
|
||||
var value = oldNode.Deserialize<T>(jsonTypeInfo);
|
||||
apply(model, value!);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error during migration {migrationName}.", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SaveSettings(SettingsModel model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(FilePath))
|
||||
@@ -139,6 +205,9 @@ public partial class SettingsModel : ObservableObject
|
||||
savedSettings[item.Key] = item.Value?.DeepClone();
|
||||
}
|
||||
|
||||
// Remove deprecated keys
|
||||
savedSettings.Remove(DeprecatedHotkeyGoesHomeKey);
|
||||
|
||||
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options);
|
||||
File.WriteAllText(FilePath, serialized);
|
||||
|
||||
|
||||
@@ -11,6 +11,19 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private static readonly List<TimeSpan> AutoGoHomeIntervals =
|
||||
[
|
||||
Timeout.InfiniteTimeSpan,
|
||||
TimeSpan.Zero,
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(20),
|
||||
TimeSpan.FromSeconds(30),
|
||||
TimeSpan.FromSeconds(60),
|
||||
TimeSpan.FromSeconds(90),
|
||||
TimeSpan.FromSeconds(120),
|
||||
TimeSpan.FromSeconds(180),
|
||||
];
|
||||
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
@@ -58,16 +71,6 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
public bool HotkeyGoesHome
|
||||
{
|
||||
get => _settings.HotkeyGoesHome;
|
||||
set
|
||||
{
|
||||
_settings.HotkeyGoesHome = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public bool BackspaceGoesBack
|
||||
{
|
||||
get => _settings.BackspaceGoesBack;
|
||||
@@ -138,6 +141,25 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
public int AutoGoBackIntervalIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var index = AutoGoHomeIntervals.IndexOf(_settings.AutoGoHomeInterval);
|
||||
return index >= 0 ? index : 0;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value >= 0 && value < AutoGoHomeIntervals.Count)
|
||||
{
|
||||
_settings.AutoGoHomeInterval = AutoGoHomeIntervals[value];
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<ProviderSettingsViewModel> CommandProviders { get; } = [];
|
||||
|
||||
public SettingsExtensionsViewModel Extensions { get; }
|
||||
|
||||
@@ -2,21 +2,52 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public sealed class WindowPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets left position in device pixels.
|
||||
/// </summary>
|
||||
public int X { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets top position in device pixels.
|
||||
/// </summary>
|
||||
public int Y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets width in device pixels.
|
||||
/// </summary>
|
||||
public int Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets height in device pixels.
|
||||
/// </summary>
|
||||
public int Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets width of the screen in device pixels where the window is located.
|
||||
/// </summary>
|
||||
public int ScreenWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets height of the screen in device pixels where the window is located.
|
||||
/// </summary>
|
||||
public int ScreenHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets DPI (dots per inch) of the display where the window is located.
|
||||
/// </summary>
|
||||
public int Dpi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the window position properties to a <see cref="RectInt32"/> structure representing the physical window rectangle.
|
||||
/// </summary>
|
||||
public RectInt32 ToPhysicalWindowRectangle()
|
||||
{
|
||||
return new RectInt32(X, Y, Width, Height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using Microsoft.CmdPal.Ext.Calc;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
using Microsoft.CmdPal.Ext.Indexer;
|
||||
using Microsoft.CmdPal.Ext.Registry;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop;
|
||||
using Microsoft.CmdPal.Ext.Shell;
|
||||
using Microsoft.CmdPal.Ext.System;
|
||||
using Microsoft.CmdPal.Ext.TimeDate;
|
||||
@@ -151,6 +152,7 @@ public partial class App : Application
|
||||
services.AddSingleton<ICommandProvider, BuiltInsCommandProvider>();
|
||||
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
|
||||
services.AddSingleton<ICommandProvider, RemoteDesktopCommandProvider>();
|
||||
|
||||
// Models
|
||||
services.AddSingleton<TopLevelCommandManager>();
|
||||
|
||||
257
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/DevRibbon.xaml
Normal file
257
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/DevRibbon.xaml
Normal file
@@ -0,0 +1,257 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Controls.DevRibbon"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<DataTemplate x:Key="LogEntryTemplate" x:DataType="viewModels:LogEntryViewModel">
|
||||
<controls:SettingsExpander Description="{x:Bind Description}" Header="{x:Bind Header}">
|
||||
<controls:SettingsExpander.HeaderIcon>
|
||||
<FontIcon Glyph="{x:Bind SeverityGlyph}" />
|
||||
</controls:SettingsExpander.HeaderIcon>
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Vertical">
|
||||
<ScrollViewer
|
||||
MaxWidth="1160"
|
||||
HorizontalScrollMode="Auto"
|
||||
VerticalScrollMode="Auto">
|
||||
<TextBlock
|
||||
FontFamily="Consolas"
|
||||
FontSize="11"
|
||||
Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Details}"
|
||||
TextWrapping="NoWrap" />
|
||||
</ScrollViewer>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
</DataTemplate>
|
||||
|
||||
<converters:BoolToVisibilityConverter
|
||||
x:Key="InvertedBoolToVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Border
|
||||
x:Name="RootBorder"
|
||||
Height="26"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource SettingsCardBackground}"
|
||||
BorderBrush="{ThemeResource SurfaceStrokeColorFlyoutBrush}"
|
||||
BorderThickness="1,0,1,1"
|
||||
CornerRadius="0,0,8,8"
|
||||
Opacity="0.3">
|
||||
<Button
|
||||
Padding="0"
|
||||
CornerRadius="0,0,8,8"
|
||||
FontSize="11"
|
||||
PointerEntered="DevRibbonButton_PointerEntered"
|
||||
PointerExited="DevRibbonButton_PointerExited">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel
|
||||
Padding="8,4"
|
||||
VerticalAlignment="Center"
|
||||
Background="DarkOrange"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{x:Bind VisibleIfGreaterThanZero(ViewModel.WarningCount), Mode=OneWay}">
|
||||
<FontIcon
|
||||
Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Glyph="" />
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<Run Text="{x:Bind ViewModel.WarningCount, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Padding="8,4"
|
||||
VerticalAlignment="Center"
|
||||
Background="Maroon"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{x:Bind VisibleIfGreaterThanZero(ViewModel.ErrorCount), Mode=OneWay}">
|
||||
<FontIcon
|
||||
Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Glyph="" />
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<Run Text="{x:Bind ViewModel.ErrorCount, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Padding="8,4">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{x:Bind ViewModel.TagColor}" />
|
||||
</Border.Background>
|
||||
<TextBlock Padding="4" VerticalAlignment="Center">
|
||||
<Run Text="{x:Bind ViewModel.Tag}" />
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<Button.Flyout>
|
||||
<Flyout
|
||||
Placement="Bottom"
|
||||
ShouldConstrainToRootBounds="False"
|
||||
SystemBackdrop="{ThemeResource AcrylicBackgroundFillColorDefaultBackdrop}">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
|
||||
<Setter Property="MinWidth" Value="600" />
|
||||
<Setter Property="MaxWidth" Value="1200" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
|
||||
<Grid x:Name="FlyoutContent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Padding="16" Spacing="8">
|
||||
|
||||
<!-- Logs section -->
|
||||
<TextBlock
|
||||
Margin="1,0,0,6"
|
||||
Style="{ThemeResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Text="Logs" />
|
||||
<ItemsControl ItemTemplate="{StaticResource LogEntryTemplate}" ItemsSource="{x:Bind ViewModel.LatestLogs, Mode=OneWay}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Command="{x:Bind ViewModel.OpenLogFileCommand}" Content="Open Log File" />
|
||||
<Button Command="{x:Bind ViewModel.OpenLogFolderCommand}" Content="Open Log Folder" />
|
||||
<Button Command="{x:Bind ViewModel.ResetErrorCountersCommand}" Content="Clear Counters" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Build info section -->
|
||||
<TextBlock Style="{ThemeResource SettingsSectionHeaderTextBlockStyle}" Text="Build Info" />
|
||||
<Border
|
||||
Padding="16"
|
||||
Background="{ThemeResource SettingsCardBackground}"
|
||||
BorderBrush="{ThemeResource SettingsCardBorderBrush}"
|
||||
BorderThickness="1">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.Resources>
|
||||
<Style
|
||||
x:Key="KeyTextBlockStyle"
|
||||
BasedOn="{StaticResource CaptionTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ValueTextBlockStyle"
|
||||
BasedOn="{StaticResource CaptionTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource KeyTextBlockStyle}"
|
||||
Text="Configuration:" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ValueTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.BuildConfiguration, Mode=OneWay}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource KeyTextBlockStyle}"
|
||||
Text="AOT:" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ValueTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.IsAot, Mode=OneWay}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource KeyTextBlockStyle}"
|
||||
Text="Trimmed:" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ValueTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.IsPublishTrimmed, Mode=OneWay}" />
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Padding="16"
|
||||
Background="{ThemeResource SettingsCardBackground}"
|
||||
BorderBrush="{ThemeResource SettingsCardBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
Visibility="{x:Bind ViewModel.IsAotReleaseConfiguration, Mode=OneWay, Converter={StaticResource InvertedBoolToVisibilityConverter}}">
|
||||
<TextBlock Text="Warning: Test in Release/AOT configuration to verify everything works." TextWrapping="Wrap" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Border>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="RootBorder"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1.0"
|
||||
Duration="0:0:0.1" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="SeverityStates">
|
||||
<VisualState x:Name="NoLog" />
|
||||
<VisualState x:Name="WarningLog">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SeverityIcon" Storyboard.TargetProperty="Glyph">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="ErrorLog">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SeverityIcon" Storyboard.TargetProperty="Glyph">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
internal sealed partial class DevRibbon : UserControl
|
||||
{
|
||||
public ViewModels.DevRibbonViewModel ViewModel { get; }
|
||||
|
||||
public DevRibbon()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = new ViewModels.DevRibbonViewModel();
|
||||
|
||||
if (FlyoutContent != null)
|
||||
{
|
||||
FlyoutContent.DataContext = ViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
private void DevRibbonButton_PointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "PointerOver", true);
|
||||
}
|
||||
|
||||
private void DevRibbonButton_PointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Normal", true);
|
||||
}
|
||||
|
||||
private Visibility VisibleIfGreaterThanZero(int value)
|
||||
{
|
||||
return value > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
@@ -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 Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
internal sealed partial class GridItemContainerStyleSelector : StyleSelector
|
||||
{
|
||||
public IGridPropertiesViewModel? GridProperties { get; set; }
|
||||
|
||||
public Style? Small { get; set; }
|
||||
|
||||
public Style? Medium { get; set; }
|
||||
|
||||
public Style? Gallery { get; set; }
|
||||
|
||||
protected override Style? SelectStyleCore(object item, DependencyObject container)
|
||||
{
|
||||
return GridProperties switch
|
||||
{
|
||||
SmallGridPropertiesViewModel => Small,
|
||||
MediumGridPropertiesViewModel => Medium,
|
||||
GalleryGridPropertiesViewModel => Gallery,
|
||||
_ => Medium,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -20,21 +20,12 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
|
||||
{
|
||||
DataTemplate? dataTemplate = Medium;
|
||||
|
||||
if (GridProperties is SmallGridPropertiesViewModel)
|
||||
return GridProperties switch
|
||||
{
|
||||
dataTemplate = Small;
|
||||
}
|
||||
else if (GridProperties is MediumGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Medium;
|
||||
}
|
||||
else if (GridProperties is GalleryGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Gallery;
|
||||
}
|
||||
|
||||
return dataTemplate;
|
||||
SmallGridPropertiesViewModel => Small,
|
||||
MediumGridPropertiesViewModel => Medium,
|
||||
GalleryGridPropertiesViewModel => Gallery,
|
||||
_ => Medium,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,33 +5,151 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
|
||||
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
x:Name="PageRoot"
|
||||
Background="Transparent"
|
||||
DataContext="{x:Bind ViewModel, Mode=OneWay}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<!-- TODO: Figure out what we want to do here for filtering/grouping and where -->
|
||||
<!-- https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.data.collectionviewsource -->
|
||||
<!--<CollectionViewSource
|
||||
x:Name="ItemsCVS"
|
||||
IsSourceGrouped="True"
|
||||
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
<!--
|
||||
GridViewItemCornerRadius is the corner radius defined in GridView template; make
|
||||
it bigger to match the radii of the gallery
|
||||
-->
|
||||
<CornerRadius x:Key="GalleryGridViewItemContainerCornerRadius">6</CornerRadius>
|
||||
<CornerRadius x:Key="IconGridViewItemContainerCornerRadius">4</CornerRadius>
|
||||
<CornerRadius x:Key="GalleryGridViewItemRadius">4</CornerRadius>
|
||||
<CornerRadius x:Key="SmallGridViewItemCornerRadius">8</CornerRadius>
|
||||
<CornerRadius x:Key="MediumGridViewItemCornerRadius">8</CornerRadius>
|
||||
|
||||
<Style x:Key="IconGridViewItemStyle" TargetType="GridViewItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GridViewItem">
|
||||
<ListViewItemPresenter
|
||||
x:Name="Root"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
CheckBoxBorderBrush="{ThemeResource GridViewItemCheckBoxBorderBrush}"
|
||||
CheckBoxBrush="{ThemeResource GridViewItemCheckBoxBrush}"
|
||||
CheckBoxCornerRadius="{ThemeResource GridViewItemCheckBoxCornerRadius}"
|
||||
CheckBoxDisabledBorderBrush="{ThemeResource GridViewItemCheckBoxDisabledBorderBrush}"
|
||||
CheckBoxDisabledBrush="{ThemeResource GridViewItemCheckBoxDisabledBrush}"
|
||||
CheckBoxPointerOverBorderBrush="{ThemeResource GridViewItemCheckBoxPointerOverBorderBrush}"
|
||||
CheckBoxPointerOverBrush="{ThemeResource GridViewItemCheckBoxPointerOverBrush}"
|
||||
CheckBoxPressedBorderBrush="{ThemeResource GridViewItemCheckBoxPressedBorderBrush}"
|
||||
CheckBoxPressedBrush="{ThemeResource GridViewItemCheckBoxPressedBrush}"
|
||||
CheckBoxSelectedBrush="{ThemeResource GridViewItemCheckBoxSelectedBrush}"
|
||||
CheckBoxSelectedDisabledBrush="{ThemeResource GridViewItemCheckBoxSelectedDisabledBrush}"
|
||||
CheckBoxSelectedPointerOverBrush="{ThemeResource GridViewItemCheckBoxSelectedPointerOverBrush}"
|
||||
CheckBoxSelectedPressedBrush="{ThemeResource GridViewItemCheckBoxSelectedPressedBrush}"
|
||||
CheckBrush="{ThemeResource GridViewItemCheckBrush}"
|
||||
CheckDisabledBrush="{ThemeResource GridViewItemCheckDisabledBrush}"
|
||||
CheckMode="{ThemeResource GridViewItemCheckMode}"
|
||||
CheckPressedBrush="{ThemeResource GridViewItemCheckPressedBrush}"
|
||||
ContentMargin="{TemplateBinding Padding}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
CornerRadius="{StaticResource IconGridViewItemContainerCornerRadius}"
|
||||
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
|
||||
DragBackground="{ThemeResource GridViewItemDragBackground}"
|
||||
DragForeground="{ThemeResource GridViewItemDragForeground}"
|
||||
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
|
||||
FocusBorderBrush="{ThemeResource GridViewItemFocusBorderBrush}"
|
||||
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
|
||||
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
|
||||
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
|
||||
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
|
||||
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
|
||||
PlaceholderBackground="{ThemeResource GridViewItemPlaceholderBackground}"
|
||||
PointerOverBackground="{ThemeResource GridViewItemBackgroundPointerOver}"
|
||||
PointerOverBorderBrush="{ThemeResource GridViewItemPointerOverBorderBrush}"
|
||||
PointerOverForeground="{ThemeResource GridViewItemForegroundPointerOver}"
|
||||
PressedBackground="{ThemeResource GridViewItemBackgroundPressed}"
|
||||
ReorderHintOffset="{ThemeResource GridViewItemReorderHintThemeOffset}"
|
||||
SelectedBackground="{ThemeResource GridViewItemBackgroundSelected}"
|
||||
SelectedBorderBrush="{ThemeResource GridViewItemSelectedBorderBrush}"
|
||||
SelectedBorderThickness="{ThemeResource GridViewItemSelectedBorderThickness}"
|
||||
SelectedDisabledBackground="{ThemeResource GridViewItemBackgroundSelectedDisabled}"
|
||||
SelectedDisabledBorderBrush="{ThemeResource GridViewItemSelectedDisabledBorderBrush}"
|
||||
SelectedForeground="{ThemeResource GridViewItemForegroundSelected}"
|
||||
SelectedInnerBorderBrush="{ThemeResource GridViewItemSelectedInnerBorderBrush}"
|
||||
SelectedPointerOverBackground="{ThemeResource GridViewItemBackgroundSelectedPointerOver}"
|
||||
SelectedPointerOverBorderBrush="{ThemeResource GridViewItemSelectedPointerOverBorderBrush}"
|
||||
SelectedPressedBackground="{ThemeResource GridViewItemBackgroundSelectedPressed}"
|
||||
SelectedPressedBorderBrush="{ThemeResource GridViewItemSelectedPressedBorderBrush}"
|
||||
SelectionCheckMarkVisualEnabled="{ThemeResource GridViewItemSelectionCheckMarkVisualEnabled}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GalleryGridViewItemStyle" TargetType="GridViewItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GridViewItem">
|
||||
<ListViewItemPresenter
|
||||
x:Name="Root"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
CheckBoxBorderBrush="{ThemeResource GridViewItemCheckBoxBorderBrush}"
|
||||
CheckBoxBrush="{ThemeResource GridViewItemCheckBoxBrush}"
|
||||
CheckBoxCornerRadius="{ThemeResource GridViewItemCheckBoxCornerRadius}"
|
||||
CheckBoxDisabledBorderBrush="{ThemeResource GridViewItemCheckBoxDisabledBorderBrush}"
|
||||
CheckBoxDisabledBrush="{ThemeResource GridViewItemCheckBoxDisabledBrush}"
|
||||
CheckBoxPointerOverBorderBrush="{ThemeResource GridViewItemCheckBoxPointerOverBorderBrush}"
|
||||
CheckBoxPointerOverBrush="{ThemeResource GridViewItemCheckBoxPointerOverBrush}"
|
||||
CheckBoxPressedBorderBrush="{ThemeResource GridViewItemCheckBoxPressedBorderBrush}"
|
||||
CheckBoxPressedBrush="{ThemeResource GridViewItemCheckBoxPressedBrush}"
|
||||
CheckBoxSelectedBrush="{ThemeResource GridViewItemCheckBoxSelectedBrush}"
|
||||
CheckBoxSelectedDisabledBrush="{ThemeResource GridViewItemCheckBoxSelectedDisabledBrush}"
|
||||
CheckBoxSelectedPointerOverBrush="{ThemeResource GridViewItemCheckBoxSelectedPointerOverBrush}"
|
||||
CheckBoxSelectedPressedBrush="{ThemeResource GridViewItemCheckBoxSelectedPressedBrush}"
|
||||
CheckBrush="{ThemeResource GridViewItemCheckBrush}"
|
||||
CheckDisabledBrush="{ThemeResource GridViewItemCheckDisabledBrush}"
|
||||
CheckMode="{ThemeResource GridViewItemCheckMode}"
|
||||
CheckPressedBrush="{ThemeResource GridViewItemCheckPressedBrush}"
|
||||
ContentMargin="{TemplateBinding Padding}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
CornerRadius="{StaticResource GalleryGridViewItemContainerCornerRadius}"
|
||||
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
|
||||
DragBackground="{ThemeResource GridViewItemDragBackground}"
|
||||
DragForeground="{ThemeResource GridViewItemDragForeground}"
|
||||
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
|
||||
FocusBorderBrush="{ThemeResource GridViewItemFocusBorderBrush}"
|
||||
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
|
||||
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
|
||||
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
|
||||
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
|
||||
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
|
||||
PlaceholderBackground="{ThemeResource GridViewItemPlaceholderBackground}"
|
||||
PointerOverBackground="{ThemeResource GridViewItemBackgroundPointerOver}"
|
||||
PointerOverBorderBrush="{ThemeResource GridViewItemPointerOverBorderBrush}"
|
||||
PointerOverForeground="{ThemeResource GridViewItemForegroundPointerOver}"
|
||||
PressedBackground="{ThemeResource GridViewItemBackgroundPressed}"
|
||||
ReorderHintOffset="{ThemeResource GridViewItemReorderHintThemeOffset}"
|
||||
SelectedBackground="{ThemeResource GridViewItemBackgroundSelected}"
|
||||
SelectedBorderBrush="{ThemeResource GridViewItemSelectedBorderBrush}"
|
||||
SelectedBorderThickness="{ThemeResource GridViewItemSelectedBorderThickness}"
|
||||
SelectedDisabledBackground="{ThemeResource GridViewItemBackgroundSelectedDisabled}"
|
||||
SelectedDisabledBorderBrush="{ThemeResource GridViewItemSelectedDisabledBorderBrush}"
|
||||
SelectedForeground="{ThemeResource GridViewItemForegroundSelected}"
|
||||
SelectedInnerBorderBrush="{ThemeResource GridViewItemSelectedInnerBorderBrush}"
|
||||
SelectedPointerOverBackground="{ThemeResource GridViewItemBackgroundSelectedPointerOver}"
|
||||
SelectedPointerOverBorderBrush="{ThemeResource GridViewItemSelectedPointerOverBorderBrush}"
|
||||
SelectedPressedBackground="{ThemeResource GridViewItemBackgroundSelectedPressed}"
|
||||
SelectedPressedBorderBrush="{ThemeResource GridViewItemSelectedPressedBorderBrush}"
|
||||
SelectionCheckMarkVisualEnabled="{ThemeResource GridViewItemSelectionCheckMarkVisualEnabled}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="TagTemplate" x:DataType="coreViewModels:TagViewModel">
|
||||
<cpcontrols:Tag
|
||||
@@ -48,10 +166,17 @@
|
||||
x:Key="GridItemTemplateSelector"
|
||||
x:DataType="coreViewModels:ListItemViewModel"
|
||||
Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
|
||||
Medium="{StaticResource MediumGridItemViewModelTemplate}"
|
||||
Small="{StaticResource SmallGridItemViewModelTemplate}" />
|
||||
|
||||
<cmdpalUI:GridItemContainerStyleSelector
|
||||
x:Key="GridItemContainerStyleSelector"
|
||||
Gallery="{StaticResource GalleryGridViewItemStyle}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
|
||||
Medium="{StaticResource IconGridViewItemStyle}"
|
||||
Small="{StaticResource IconGridViewItemStyle}" />
|
||||
|
||||
<!-- https://learn.microsoft.com/windows/apps/design/controls/itemsview#specify-the-look-of-the-items -->
|
||||
<DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<Grid
|
||||
@@ -94,7 +219,7 @@
|
||||
Text="{x:Bind Subtitle, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind Subtitle, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}" />
|
||||
Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<ItemsControl
|
||||
@@ -124,11 +249,11 @@
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
CornerRadius="{StaticResource SmallGridViewItemCornerRadius}"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
|
||||
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
@@ -145,23 +270,22 @@
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
<Grid
|
||||
Width="100"
|
||||
Height="100"
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
Padding="8"
|
||||
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
|
||||
CornerRadius="{StaticResource MediumGridViewItemCornerRadius}"
|
||||
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
Grid.Row="0"
|
||||
Width="36"
|
||||
Height="36"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
@@ -169,21 +293,20 @@
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxHeight="40"
|
||||
Margin="0,8,0,4"
|
||||
Grid.Row="1"
|
||||
Height="32"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="12"
|
||||
Text="{x:Bind Title}"
|
||||
Text="{x:Bind Title, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding ElementName=PageRoot, Path=DataContext.GridProperties.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
Visibility="{x:Bind LayoutShowsTitle, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
@@ -193,11 +316,11 @@
|
||||
Padding="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="4"
|
||||
CornerRadius="{StaticResource GalleryGridViewItemRadius}"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
|
||||
|
||||
<Grid
|
||||
Width="160"
|
||||
@@ -205,12 +328,8 @@
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
CornerRadius="{StaticResource GalleryGridViewItemRadius}">
|
||||
<Viewbox
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Stretch="UniformToFill"
|
||||
StretchDirection="Both">
|
||||
@@ -222,35 +341,39 @@
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Padding="4" Orientation="Vertical">
|
||||
<StackPanel
|
||||
Padding="4"
|
||||
Orientation="Vertical"
|
||||
Spacing="4"
|
||||
Visibility="{x:Bind help:BindTransformers.VisibleWhenAny(ShowTitle, ShowSubtitle)}">
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
Text="{x:Bind Title}"
|
||||
Text="{x:Bind Title, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind ShowTitle, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
x:Name="SubTitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="11"
|
||||
FontSize="11"
|
||||
Foreground="{ThemeResource TextFillColorTertiary}"
|
||||
Text="{x:Bind Subtitle}"
|
||||
Text="{x:Bind Subtitle, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
@@ -295,6 +418,7 @@
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ItemContainerStyleSelector="{StaticResource GridItemContainerStyleSelector}"
|
||||
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
@@ -302,6 +426,7 @@
|
||||
<GridView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</GridView.ItemContainerTransitions>
|
||||
<GridView.ItemContainerStyle />
|
||||
</GridView>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
|
||||
@@ -15,4 +15,7 @@ internal static class BindTransformers
|
||||
|
||||
public static Visibility EmptyOrWhitespaceToCollapsed(string? input)
|
||||
=> string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
public static Visibility VisibleWhenAny(bool value1, bool value2)
|
||||
=> (value1 || value2) ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
36
src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/BuildInfo.cs
Normal file
36
src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/BuildInfo.cs
Normal 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.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
internal static class BuildInfo
|
||||
{
|
||||
#if DEBUG
|
||||
public const string Configuration = "Debug";
|
||||
#else
|
||||
public const string Configuration = "Release";
|
||||
#endif
|
||||
|
||||
// Runtime AOT detection
|
||||
public static bool IsNativeAot => !RuntimeFeature.IsDynamicCodeSupported;
|
||||
|
||||
// From assembly metadata (build-time values)
|
||||
public static bool PublishTrimmed => GetBoolMetadata("PublishTrimmed", false);
|
||||
|
||||
// From assembly metadata (build-time values)
|
||||
public static bool PublishAot => GetBoolMetadata("PublishAot", false);
|
||||
|
||||
public static bool IsCiBuild => GetBoolMetadata("CIBuild", false);
|
||||
|
||||
private static string? GetMetadata(string key) =>
|
||||
Assembly.GetExecutingAssembly()
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.FirstOrDefault(a => a.Key == key)?.Value;
|
||||
|
||||
private static bool GetBoolMetadata(string key, bool defaultValue) =>
|
||||
bool.TryParse(GetMetadata(key), out var result) ? result : defaultValue;
|
||||
}
|
||||
@@ -14,5 +14,7 @@
|
||||
Activated="MainWindow_Activated"
|
||||
Closed="MainWindow_Closed"
|
||||
mc:Ignorable="d">
|
||||
<pages:ShellPage x:Name="RootShellPage" />
|
||||
<Grid x:Name="RootElement">
|
||||
<pages:ShellPage />
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
|
||||
@@ -11,6 +11,7 @@ using Microsoft.CmdPal.Core.Common.Helpers;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
|
||||
using Microsoft.CmdPal.UI.Controls;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
@@ -18,6 +19,7 @@ using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Input;
|
||||
@@ -33,6 +35,8 @@ using Windows.UI.WindowManagement;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Dwm;
|
||||
using Windows.Win32.Graphics.Gdi;
|
||||
using Windows.Win32.UI.HiDpi;
|
||||
using Windows.Win32.UI.Input.KeyboardAndMouse;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using WinRT;
|
||||
@@ -48,10 +52,14 @@ public sealed partial class MainWindow : WindowEx,
|
||||
IRecipient<QuitMessage>,
|
||||
IDisposable
|
||||
{
|
||||
private const int DefaultWidth = 800;
|
||||
private const int DefaultHeight = 480;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_")]
|
||||
private readonly uint WM_TASKBAR_RESTART;
|
||||
private readonly HWND _hwnd;
|
||||
private readonly DispatcherTimer _autoGoHomeTimer;
|
||||
private readonly WNDPROC? _hotkeyWndProc;
|
||||
private readonly WNDPROC? _originalWndProc;
|
||||
private readonly List<TopLevelHotkey> _hotkeys = [];
|
||||
@@ -62,6 +70,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
private SystemBackdropConfiguration? _configurationSource;
|
||||
private TimeSpan _autoGoHomeInterval = Timeout.InfiniteTimeSpan;
|
||||
|
||||
private WindowPosition _currentWindowPosition = new();
|
||||
|
||||
@@ -69,6 +78,9 @@ public sealed partial class MainWindow : WindowEx,
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_autoGoHomeTimer = new DispatcherTimer();
|
||||
_autoGoHomeTimer.Tick += OnAutoGoHomeTimerOnTick;
|
||||
|
||||
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
|
||||
|
||||
unsafe
|
||||
@@ -102,7 +114,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
SizeChanged += WindowSizeChanged;
|
||||
RootShellPage.Loaded += RootShellPage_Loaded;
|
||||
RootElement.Loaded += RootElementLoaded;
|
||||
|
||||
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated");
|
||||
|
||||
@@ -119,7 +131,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler;
|
||||
|
||||
// Make sure that we update the acrylic theme when the OS theme changes
|
||||
RootShellPage.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
|
||||
RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
|
||||
|
||||
// Hardcoding event name to avoid bringing in the PowerToys.interop dependency. Event name must match CMDPAL_SHOW_EVENT from shared_constants.h
|
||||
NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () =>
|
||||
@@ -135,6 +147,15 @@ public sealed partial class MainWindow : WindowEx,
|
||||
HideWindow();
|
||||
}
|
||||
|
||||
private void OnAutoGoHomeTimerOnTick(object? s, object e)
|
||||
{
|
||||
_autoGoHomeTimer.Stop();
|
||||
|
||||
// BEAR LOADING: Focus Search must be suppressed here; otherwise it may steal focus (for example, from the system tray icon)
|
||||
// and prevent the user from opening its context menu.
|
||||
WeakReferenceMessenger.Default.Send(new GoHomeMessage(WithAnimation: false, FocusSearch: false));
|
||||
}
|
||||
|
||||
private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.GoBack)
|
||||
@@ -145,11 +166,18 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings();
|
||||
|
||||
private void RootShellPage_Loaded(object sender, RoutedEventArgs e) =>
|
||||
|
||||
private void RootElementLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Now that our content has loaded, we can update our draggable regions
|
||||
UpdateRegionsForCustomTitleBar();
|
||||
|
||||
// Add dev ribbon if enabled
|
||||
if (!BuildInfo.IsCiBuild)
|
||||
{
|
||||
RootElement.Children.Add(new DevRibbon { Margin = new Thickness(-1, -1, 120, -1) });
|
||||
}
|
||||
}
|
||||
|
||||
private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs args) => UpdateRegionsForCustomTitleBar();
|
||||
|
||||
private void PositionCentered()
|
||||
@@ -173,22 +201,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
return;
|
||||
}
|
||||
|
||||
AppWindow.Resize(new SizeInt32 { Width = savedPosition.Width, Height = savedPosition.Height });
|
||||
|
||||
var savedRect = new RectInt32(savedPosition.X, savedPosition.Y, savedPosition.Width, savedPosition.Height);
|
||||
var displayArea = DisplayArea.GetFromRect(savedRect, DisplayAreaFallback.Nearest);
|
||||
var workArea = displayArea.WorkArea;
|
||||
|
||||
var maxX = workArea.X + Math.Max(0, workArea.Width - savedPosition.Width);
|
||||
var maxY = workArea.Y + Math.Max(0, workArea.Height - savedPosition.Height);
|
||||
|
||||
var targetPoint = new PointInt32
|
||||
{
|
||||
X = Math.Clamp(savedPosition.X, workArea.X, maxX),
|
||||
Y = Math.Clamp(savedPosition.Y, workArea.Y, maxY),
|
||||
};
|
||||
|
||||
AppWindow.Move(targetPoint);
|
||||
var newRect = EnsureWindowIsVisible(savedPosition.ToPhysicalWindowRectangle(), new SizeInt32(savedPosition.ScreenWidth, savedPosition.ScreenHeight), savedPosition.Dpi);
|
||||
AppWindow.MoveAndResize(newRect);
|
||||
}
|
||||
|
||||
private void PositionCentered(DisplayArea displayArea)
|
||||
@@ -207,12 +221,16 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private void UpdateWindowPositionInMemory()
|
||||
{
|
||||
var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest) ?? DisplayArea.Primary;
|
||||
_currentWindowPosition = new WindowPosition
|
||||
{
|
||||
X = AppWindow.Position.X,
|
||||
Y = AppWindow.Position.Y,
|
||||
Width = AppWindow.Size.Width,
|
||||
Height = AppWindow.Size.Height,
|
||||
Dpi = (int)this.GetDpiForWindow(),
|
||||
ScreenWidth = displayArea.WorkArea.Width,
|
||||
ScreenHeight = displayArea.WorkArea.Height,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -224,6 +242,9 @@ public sealed partial class MainWindow : WindowEx,
|
||||
App.Current.Services.GetService<TrayIconService>()!.SetupTrayIcon(settings.ShowSystemTrayIcon);
|
||||
|
||||
_ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen;
|
||||
|
||||
_autoGoHomeInterval = settings.AutoGoHomeInterval;
|
||||
_autoGoHomeTimer.Interval = _autoGoHomeInterval;
|
||||
}
|
||||
|
||||
// We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
|
||||
@@ -283,6 +304,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target)
|
||||
{
|
||||
StopAutoGoHome();
|
||||
|
||||
var hwnd = new HWND(hwndValue != 0 ? hwndValue : _hwnd);
|
||||
|
||||
// Remember, IsIconic == "minimized", which is entirely different state
|
||||
@@ -300,8 +323,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
if (target == MonitorBehavior.ToLast)
|
||||
{
|
||||
AppWindow.Resize(new SizeInt32 { Width = _currentWindowPosition.Width, Height = _currentWindowPosition.Height });
|
||||
AppWindow.Move(new PointInt32 { X = _currentWindowPosition.X, Y = _currentWindowPosition.Y });
|
||||
var newRect = EnsureWindowIsVisible(_currentWindowPosition.ToPhysicalWindowRectangle(), new SizeInt32(_currentWindowPosition.ScreenWidth, _currentWindowPosition.ScreenHeight), _currentWindowPosition.Dpi);
|
||||
AppWindow.MoveAndResize(newRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -330,6 +353,114 @@ public sealed partial class MainWindow : WindowEx,
|
||||
PInvoke.SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the window rectangle is visible on-screen.
|
||||
/// </summary>
|
||||
/// <param name="windowRect">The window rectangle in physical pixels.</param>
|
||||
/// <param name="originalScreen">The desktop area the window was positioned on.</param>
|
||||
/// <param name="originalDpi">The window's original DPI.</param>
|
||||
/// <returns>
|
||||
/// A window rectangle in physical pixels, moved to the nearest display and resized
|
||||
/// if the DPI has changed.
|
||||
/// </returns>
|
||||
private static RectInt32 EnsureWindowIsVisible(RectInt32 windowRect, SizeInt32 originalScreen, int originalDpi)
|
||||
{
|
||||
var displayArea = DisplayArea.GetFromRect(windowRect, DisplayAreaFallback.Nearest);
|
||||
if (displayArea is null)
|
||||
{
|
||||
return windowRect;
|
||||
}
|
||||
|
||||
var workArea = displayArea.WorkArea;
|
||||
if (workArea.Width <= 0 || workArea.Height <= 0)
|
||||
{
|
||||
// Fallback, nothing reasonable to do
|
||||
return windowRect;
|
||||
}
|
||||
|
||||
var effectiveDpi = GetEffectiveDpiFromDisplayId(displayArea);
|
||||
if (originalDpi <= 0)
|
||||
{
|
||||
originalDpi = effectiveDpi; // use current DPI as baseline (no scaling adjustment needed)
|
||||
}
|
||||
|
||||
var hasInvalidSize = windowRect.Width <= 0 || windowRect.Height <= 0;
|
||||
if (hasInvalidSize)
|
||||
{
|
||||
windowRect = new RectInt32(windowRect.X, windowRect.Y, DefaultWidth, DefaultHeight);
|
||||
}
|
||||
|
||||
// If we have a DPI change, scale the window rectangle accordingly
|
||||
if (effectiveDpi != originalDpi)
|
||||
{
|
||||
var scalingFactor = effectiveDpi / (double)originalDpi;
|
||||
windowRect = new RectInt32(
|
||||
(int)Math.Round(windowRect.X * scalingFactor),
|
||||
(int)Math.Round(windowRect.Y * scalingFactor),
|
||||
(int)Math.Round(windowRect.Width * scalingFactor),
|
||||
(int)Math.Round(windowRect.Height * scalingFactor));
|
||||
}
|
||||
|
||||
var targetWidth = Math.Min(windowRect.Width, workArea.Width);
|
||||
var targetHeight = Math.Min(windowRect.Height, workArea.Height);
|
||||
|
||||
// Ensure at least some minimum visible area (e.g., 100 pixels)
|
||||
// This helps prevent the window from being entirely offscreen, regardless of display scaling.
|
||||
const int minimumVisibleSize = 100;
|
||||
var isOffscreen =
|
||||
windowRect.X + minimumVisibleSize > workArea.X + workArea.Width ||
|
||||
windowRect.X + windowRect.Width - minimumVisibleSize < workArea.X ||
|
||||
windowRect.Y + minimumVisibleSize > workArea.Y + workArea.Height ||
|
||||
windowRect.Y + windowRect.Height - minimumVisibleSize < workArea.Y;
|
||||
|
||||
// if the work area size has changed, re-center the window
|
||||
var workAreaSizeChanged =
|
||||
originalScreen.Width != workArea.Width ||
|
||||
originalScreen.Height != workArea.Height;
|
||||
|
||||
int targetX;
|
||||
int targetY;
|
||||
var recenter = isOffscreen || workAreaSizeChanged || hasInvalidSize;
|
||||
if (recenter)
|
||||
{
|
||||
targetX = workArea.X + ((workArea.Width - targetWidth) / 2);
|
||||
targetY = workArea.Y + ((workArea.Height - targetHeight) / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetX = windowRect.X;
|
||||
targetY = windowRect.Y;
|
||||
}
|
||||
|
||||
return new RectInt32(targetX, targetY, targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
private static int GetEffectiveDpiFromDisplayId(DisplayArea displayArea)
|
||||
{
|
||||
var effectiveDpi = 96;
|
||||
|
||||
var hMonitor = (HMONITOR)Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);
|
||||
if (!hMonitor.IsNull)
|
||||
{
|
||||
var hr = PInvoke.GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _);
|
||||
if (hr == 0)
|
||||
{
|
||||
effectiveDpi = (int)dpiX;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"GetDpiForMonitor failed with HRESULT: 0x{hr.Value:X8} on display {displayArea.DisplayId}");
|
||||
}
|
||||
}
|
||||
|
||||
if (effectiveDpi <= 0)
|
||||
{
|
||||
effectiveDpi = 96;
|
||||
}
|
||||
|
||||
return effectiveDpi;
|
||||
}
|
||||
|
||||
private DisplayArea GetScreen(HWND currentHwnd, MonitorBehavior target)
|
||||
{
|
||||
// Leaving a note here, in case we ever need it:
|
||||
@@ -429,6 +560,25 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// If the window was not cloaked, then leave it hidden.
|
||||
// Sure, it's not ideal, but at least it's not visible.
|
||||
}
|
||||
|
||||
// Start auto-go-home timer
|
||||
RestartAutoGoHome();
|
||||
}
|
||||
|
||||
private void StopAutoGoHome()
|
||||
{
|
||||
_autoGoHomeTimer.Stop();
|
||||
}
|
||||
|
||||
private void RestartAutoGoHome()
|
||||
{
|
||||
if (_autoGoHomeInterval == Timeout.InfiniteTimeSpan)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_autoGoHomeTimer.Stop();
|
||||
_autoGoHomeTimer.Start();
|
||||
}
|
||||
|
||||
private bool Cloak()
|
||||
@@ -479,6 +629,9 @@ public sealed partial class MainWindow : WindowEx,
|
||||
Y = _currentWindowPosition.Y,
|
||||
Width = _currentWindowPosition.Width,
|
||||
Height = _currentWindowPosition.Height,
|
||||
Dpi = _currentWindowPosition.Dpi,
|
||||
ScreenWidth = _currentWindowPosition.ScreenWidth,
|
||||
ScreenHeight = _currentWindowPosition.ScreenHeight,
|
||||
};
|
||||
|
||||
SettingsModel.SaveSettings(settings);
|
||||
@@ -513,28 +666,28 @@ public sealed partial class MainWindow : WindowEx,
|
||||
private void UpdateRegionsForCustomTitleBar()
|
||||
{
|
||||
// Specify the interactive regions of the title bar.
|
||||
var scaleAdjustment = RootShellPage.XamlRoot.RasterizationScale;
|
||||
var scaleAdjustment = RootElement.XamlRoot.RasterizationScale;
|
||||
|
||||
// Get the rectangle around our XAML content. We're going to mark this
|
||||
// rectangle as "Passthrough", so that the normal window operations
|
||||
// (resizing, dragging) don't apply in this space.
|
||||
var transform = RootShellPage.TransformToVisual(null);
|
||||
var transform = RootElement.TransformToVisual(null);
|
||||
|
||||
// Reserve 16px of space at the top for dragging.
|
||||
var topHeight = 16;
|
||||
var bounds = transform.TransformBounds(new Rect(
|
||||
0,
|
||||
topHeight,
|
||||
RootShellPage.ActualWidth,
|
||||
RootShellPage.ActualHeight));
|
||||
RootElement.ActualWidth,
|
||||
RootElement.ActualHeight));
|
||||
var contentRect = GetRect(bounds, scaleAdjustment);
|
||||
var rectArray = new RectInt32[] { contentRect };
|
||||
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
|
||||
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
|
||||
|
||||
// Add a drag-able region on top
|
||||
var w = RootShellPage.ActualWidth;
|
||||
_ = RootShellPage.ActualHeight;
|
||||
var w = RootElement.ActualWidth;
|
||||
_ = RootElement.ActualHeight;
|
||||
var dragSides = new RectInt32[]
|
||||
{
|
||||
GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<LangVersion>preview</LangVersion>
|
||||
|
||||
<Version>$(CmdPalVersion)</Version>
|
||||
|
||||
@@ -25,10 +26,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
|
||||
<!-- <PropertyGroup>
|
||||
<!--<PropertyGroup>
|
||||
<EnableCmdPalAOT>true</EnableCmdPalAOT>
|
||||
<CIBuild>true</CIBuild>
|
||||
</PropertyGroup> -->
|
||||
<GeneratePackageLocally>true</GeneratePackageLocally>
|
||||
</PropertyGroup>-->
|
||||
|
||||
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
|
||||
<SelfContained>true</SelfContained>
|
||||
@@ -37,7 +38,7 @@
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
||||
<PropertyGroup Condition="'$(CIBuild)' == 'true' or '$(GeneratePackageLocally)' == 'true'">
|
||||
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
|
||||
<AppxBundle>Never</AppxBundle>
|
||||
<AppxPackageTestDir>$(OutputPath)\AppPackages\Microsoft.CmdPal.UI_$(Version)_Test\</AppxPackageTestDir>
|
||||
@@ -66,6 +67,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\ActionBar.xaml" />
|
||||
<None Remove="Controls\DevRibbon.xaml" />
|
||||
<None Remove="Controls\KeyVisual\KeyCharPresenter.xaml" />
|
||||
<None Remove="Controls\SearchBar.xaml" />
|
||||
<None Remove="IsEnabledTextBlock.xaml" />
|
||||
@@ -118,6 +120,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.System\Microsoft.CmdPal.Ext.System.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WebSearch\Microsoft.CmdPal.Ext.WebSearch.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
|
||||
@@ -167,6 +170,9 @@
|
||||
<Page Update="Controls\SearchBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Controls\DevRibbon.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Styles\TextBox.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
@@ -234,4 +240,24 @@
|
||||
</ItemGroup>
|
||||
<!-- </AdaptiveCardsWorkaround> -->
|
||||
|
||||
<!-- Metadata for build information -->
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>PublishTrimmed</_Parameter1>
|
||||
<_Parameter2>$(PublishTrimmed)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>PublishAot</_Parameter1>
|
||||
<_Parameter2>$(PublishAot)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>CIBuild</_Parameter1>
|
||||
<_Parameter2>$(CIBuild)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>CommandPaletteBranding</_Parameter1>
|
||||
<_Parameter2>$(CommandPaletteBranding)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -345,7 +345,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
// Depending on the settings, either
|
||||
// * Go home, or
|
||||
// * Select the search text (if we should remain open on this page)
|
||||
if (settings.HotkeyGoesHome)
|
||||
if (settings.AutoGoHomeInterval == TimeSpan.Zero)
|
||||
{
|
||||
GoHome(false);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
"nativeDebugging": false,
|
||||
"doNotLaunchApp": false
|
||||
},
|
||||
"Microsoft.CmdPal.UI (Package) + Native debugging": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": true,
|
||||
"doNotLaunchApp": false
|
||||
},
|
||||
"Microsoft.CmdPal.UI (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
|
||||
@@ -51,8 +51,18 @@
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_GoHome_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.HotkeyGoesHome, Mode=TwoWay}" />
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_AutoGoHome_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind viewModel.AutoGoBackIntervalIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_Never" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_Immediately" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_After10Seconds" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_After20Seconds" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_After30Seconds" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_After60Seconds" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_After90Seconds" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_After120Seconds" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AutoGoHome_Item_After180Seconds" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_HighlightSearch_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.HighlightSearchOnActivate, Mode=TwoWay}" />
|
||||
|
||||
@@ -20,13 +20,15 @@
|
||||
<winuiex:WindowEx.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</winuiex:WindowEx.SystemBackdrop>
|
||||
<Grid>
|
||||
<Grid x:Name="RootElement">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar
|
||||
x:Name="AppTitleBar"
|
||||
BackRequested="TitleBar_BackRequested"
|
||||
IsBackButtonVisible="{x:Bind NavFrame.CanGoBack, Mode=OneWay}"
|
||||
IsTabStop="False"
|
||||
PaneToggleRequested="AppTitleBar_PaneToggleRequested">
|
||||
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
|
||||
@@ -56,10 +58,12 @@
|
||||
</NavigationView.Resources>
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="GeneralPageNavItem"
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_General"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="General" />
|
||||
<NavigationViewItem
|
||||
x:Name="ExtensionPageNavItem"
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_Extensions"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="Extensions" />
|
||||
@@ -90,7 +94,10 @@
|
||||
</BreadcrumbBar.Resources>
|
||||
</BreadcrumbBar>
|
||||
</Grid>
|
||||
<Frame x:Name="NavFrame" Grid.Row="1" />
|
||||
<Frame
|
||||
x:Name="NavFrame"
|
||||
Grid.Row="1"
|
||||
Navigated="NavFrame_OnNavigated" />
|
||||
</Grid>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
|
||||
@@ -4,14 +4,19 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.System;
|
||||
using Windows.UI.Core;
|
||||
using WinUIEx;
|
||||
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
|
||||
using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
|
||||
@@ -19,9 +24,12 @@ using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
|
||||
namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
public sealed partial class SettingsWindow : WindowEx,
|
||||
IDisposable,
|
||||
IRecipient<NavigateToExtensionSettingsMessage>,
|
||||
IRecipient<QuitMessage>
|
||||
{
|
||||
private readonly LocalKeyboardListener _localKeyboardListener;
|
||||
|
||||
public ObservableCollection<Crumb> BreadCrumbs { get; } = [];
|
||||
|
||||
// Gets or sets optional action invoked after NavigationView is loaded.
|
||||
@@ -40,6 +48,17 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
|
||||
WeakReferenceMessenger.Default.Register<NavigateToExtensionSettingsMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
|
||||
_localKeyboardListener = new LocalKeyboardListener();
|
||||
_localKeyboardListener.KeyPressed += LocalKeyboardListener_OnKeyPressed;
|
||||
_localKeyboardListener.Start();
|
||||
Closed += SettingsWindow_Closed;
|
||||
RootElement.AddHandler(UIElement.PointerPressedEvent, new PointerEventHandler(RootElement_OnPointerPressed), true);
|
||||
}
|
||||
|
||||
private void SettingsWindow_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
// Handles NavigationView loaded event.
|
||||
@@ -85,10 +104,9 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
"Extensions" => typeof(ExtensionsPage),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (pageType is not null)
|
||||
{
|
||||
BreadCrumbs.Clear();
|
||||
BreadCrumbs.Add(new(page, page));
|
||||
NavFrame.Navigate(pageType);
|
||||
}
|
||||
}
|
||||
@@ -96,7 +114,6 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
private void Navigate(ProviderSettingsViewModel extension)
|
||||
{
|
||||
NavFrame.Navigate(typeof(ExtensionPage), extension);
|
||||
BreadCrumbs.Add(new(extension.DisplayName, string.Empty));
|
||||
}
|
||||
|
||||
private void PositionCentered()
|
||||
@@ -127,9 +144,9 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Activated(object sender, WindowActivatedEventArgs args)
|
||||
private void Window_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<WindowActivatedEventArgs>(args);
|
||||
WeakReferenceMessenger.Default.Send<Microsoft.UI.Xaml.WindowActivatedEventArgs>(args);
|
||||
}
|
||||
|
||||
private void Window_Closed(object sender, WindowEventArgs args)
|
||||
@@ -141,7 +158,7 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
|
||||
private void NavView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
|
||||
{
|
||||
if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
|
||||
if (args.DisplayMode is NavigationViewDisplayMode.Compact or NavigationViewDisplayMode.Minimal)
|
||||
{
|
||||
AppTitleBar.IsPaneToggleButtonVisible = true;
|
||||
WorkAroundIcon.Margin = new Thickness(8, 0, 16, 0); // Required for workaround, see XAML comment
|
||||
@@ -149,7 +166,7 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
else
|
||||
{
|
||||
AppTitleBar.IsPaneToggleButtonVisible = false;
|
||||
WorkAroundIcon.Margin = new Thickness(16, 0, 0, 0); // Required for workaround, see XAML comment
|
||||
WorkAroundIcon.Margin = new Thickness(16, 0, 8, 0); // Required for workaround, see XAML comment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +180,93 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
{
|
||||
NavView.IsPaneOpen = !NavView.IsPaneOpen;
|
||||
}
|
||||
|
||||
private void TryGoBack()
|
||||
{
|
||||
if (NavFrame.CanGoBack)
|
||||
{
|
||||
NavFrame.GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
private void TitleBar_BackRequested(TitleBar sender, object args)
|
||||
{
|
||||
TryGoBack();
|
||||
}
|
||||
|
||||
private void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case VirtualKey.GoBack:
|
||||
case VirtualKey.XButton1:
|
||||
TryGoBack();
|
||||
break;
|
||||
|
||||
case VirtualKey.Left:
|
||||
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
|
||||
if (altPressed)
|
||||
{
|
||||
TryGoBack();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RootElement_OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse)
|
||||
{
|
||||
var ptrPt = e.GetCurrentPoint(RootElement);
|
||||
if (ptrPt.Properties.IsXButton1Pressed)
|
||||
{
|
||||
TryGoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error handling mouse button press event", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_localKeyboardListener?.Dispose();
|
||||
}
|
||||
|
||||
private void NavFrame_OnNavigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
BreadCrumbs.Clear();
|
||||
|
||||
if (e.SourcePageType == typeof(GeneralPage))
|
||||
{
|
||||
NavView.SelectedItem = GeneralPageNavItem;
|
||||
var pageType = RS_.GetString("Settings_PageTitles_GeneralPage");
|
||||
BreadCrumbs.Add(new(pageType, pageType));
|
||||
}
|
||||
else if (e.SourcePageType == typeof(ExtensionsPage))
|
||||
{
|
||||
NavView.SelectedItem = ExtensionPageNavItem;
|
||||
var pageType = RS_.GetString("Settings_PageTitles_ExtensionsPage");
|
||||
BreadCrumbs.Add(new(pageType, pageType));
|
||||
}
|
||||
else if (e.SourcePageType == typeof(ExtensionPage) && e.Parameter is ProviderSettingsViewModel vm)
|
||||
{
|
||||
NavView.SelectedItem = ExtensionPageNavItem;
|
||||
var extensionsPageType = RS_.GetString("Settings_PageTitles_ExtensionsPage");
|
||||
BreadCrumbs.Add(new(extensionsPageType, extensionsPageType));
|
||||
BreadCrumbs.Add(new(vm.DisplayName, vm));
|
||||
}
|
||||
else
|
||||
{
|
||||
BreadCrumbs.Add(new($"[{e.SourcePageType?.Name}]", string.Empty));
|
||||
Logger.LogError($"Unknown breadcrumb for page type '{e.SourcePageType}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Crumb
|
||||
|
||||
@@ -344,12 +344,6 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Settings_GeneralPage_IgnoreShortcutWhenFullscreen_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Preventing disruption of the program running in fullscreen by unintentional activation of shortcut</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_GoHome_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Go home when activated</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_GoHome_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Automatically opens the home page upon activation</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_HighlightSearch_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Highlight search on activate</value>
|
||||
</data>
|
||||
@@ -523,4 +517,43 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="GlobalErrorHandler_CrashMessageBox_Caption" xml:space="preserve">
|
||||
<value>Command Palette - Fatal error</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_Never.Content" xml:space="preserve">
|
||||
<value>Never</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_Immediately.Content" xml:space="preserve">
|
||||
<value>Immediately</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_After10Seconds.Content" xml:space="preserve">
|
||||
<value>10 seconds</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_After20Seconds.Content" xml:space="preserve">
|
||||
<value>20 seconds</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_After30Seconds.Content" xml:space="preserve">
|
||||
<value>30 seconds</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_After60Seconds.Content" xml:space="preserve">
|
||||
<value>60 seconds</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_After90Seconds.Content" xml:space="preserve">
|
||||
<value>90 seconds</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_After120Seconds.Content" xml:space="preserve">
|
||||
<value>2 minutes</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_Item_After180Seconds.Content" xml:space="preserve">
|
||||
<value>3 minutes</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Automatically return home</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AutoGoHome_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Automatically returns to home page after a period of inactivity when Command Palette is closed</value>
|
||||
</data>
|
||||
<data name="Settings_PageTitles_GeneralPage" xml:space="preserve">
|
||||
<value>General</value>
|
||||
</data>
|
||||
<data name="Settings_PageTitles_ExtensionsPage" xml:space="preserve">
|
||||
<value>Extensions</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.UI;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
internal sealed partial class DevRibbonViewModel : ObservableObject
|
||||
{
|
||||
private const int MaxLogEntries = 2;
|
||||
private const string Release = "Release";
|
||||
private const string Debug = "Debug";
|
||||
|
||||
private static readonly Color ReleaseAotColor = ColorHelper.FromArgb(255, 124, 58, 237);
|
||||
private static readonly Color ReleaseColor = ColorHelper.FromArgb(255, 51, 65, 85);
|
||||
private static readonly Color DebugAotColor = ColorHelper.FromArgb(255, 99, 102, 241);
|
||||
private static readonly Color DebugColor = ColorHelper.FromArgb(255, 107, 114, 128);
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
public DevRibbonViewModel()
|
||||
{
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
Trace.Listeners.Add(new DevRibbonTraceListener(this));
|
||||
|
||||
var configLabel = BuildConfiguration == Release ? "RLS" : "DBG"; /* #no-spell-check-line */
|
||||
var aotLabel = BuildInfo.IsNativeAot ? "⚡AOT" : "NO AOT";
|
||||
Tag = $"{configLabel} | {aotLabel}";
|
||||
|
||||
TagColor = (BuildConfiguration, BuildInfo.IsNativeAot) switch
|
||||
{
|
||||
(Release, true) => ReleaseAotColor,
|
||||
(Release, false) => ReleaseColor,
|
||||
(Debug, true) => DebugAotColor,
|
||||
(Debug, false) => DebugColor,
|
||||
_ => Colors.Fuchsia,
|
||||
};
|
||||
}
|
||||
|
||||
public string BuildConfiguration => BuildInfo.Configuration;
|
||||
|
||||
public bool IsAotReleaseConfiguration => BuildConfiguration == Release && BuildInfo.IsNativeAot;
|
||||
|
||||
public bool IsAot => BuildInfo.IsNativeAot;
|
||||
|
||||
public bool IsPublishTrimmed => BuildInfo.PublishTrimmed;
|
||||
|
||||
public ObservableCollection<LogEntryViewModel> LatestLogs { get; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int WarningCount { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int ErrorCount { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Tag { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Color TagColor { get; private set; }
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenLogFileAsync()
|
||||
{
|
||||
var logPath = Logger.CurrentLogFile;
|
||||
if (File.Exists(logPath))
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri(logPath));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenLogFolderAsync()
|
||||
{
|
||||
var logFolderPath = Logger.CurrentVersionLogDirectoryPath;
|
||||
if (Directory.Exists(logFolderPath))
|
||||
{
|
||||
await Launcher.LaunchFolderPathAsync(logFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ResetErrorCounters()
|
||||
{
|
||||
WarningCount = 0;
|
||||
ErrorCount = 0;
|
||||
LatestLogs.Clear();
|
||||
}
|
||||
|
||||
private sealed partial class DevRibbonTraceListener(DevRibbonViewModel viewModel) : TraceListener
|
||||
{
|
||||
private const string TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff";
|
||||
|
||||
[GeneratedRegex(@"^\[(?<timestamp>.*?)\] \[(?<severity>.*?)\] (?<message>.*)")]
|
||||
private static partial Regex LogRegex();
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
private LogEntryViewModel? _latestLogEntry;
|
||||
|
||||
public override void Write(string? message)
|
||||
{
|
||||
// Not required for this scenario.
|
||||
}
|
||||
|
||||
public override void WriteLine(string? message)
|
||||
{
|
||||
if (message is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var match = LogRegex().Match(message);
|
||||
if (match.Success)
|
||||
{
|
||||
var severity = match.Groups["severity"].Value;
|
||||
var isWarning = severity.Equals("Warning", StringComparison.OrdinalIgnoreCase);
|
||||
var isError = severity.Equals("Error", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isWarning || isError)
|
||||
{
|
||||
var timestampStr = match.Groups["timestamp"].Value;
|
||||
var timestamp = DateTimeOffset.TryParseExact(
|
||||
timestampStr,
|
||||
TimestampFormat,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeLocal,
|
||||
out var parsed)
|
||||
? parsed
|
||||
: DateTimeOffset.Now;
|
||||
|
||||
var logEntry = new LogEntryViewModel(
|
||||
timestamp,
|
||||
severity,
|
||||
match.Groups["message"].Value,
|
||||
string.Empty);
|
||||
|
||||
_latestLogEntry = logEntry;
|
||||
|
||||
viewModel._dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (isWarning)
|
||||
{
|
||||
viewModel.WarningCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
viewModel.ErrorCount++;
|
||||
}
|
||||
|
||||
viewModel.LatestLogs.Insert(0, logEntry);
|
||||
|
||||
while (viewModel.LatestLogs.Count > MaxLogEntries)
|
||||
{
|
||||
viewModel.LatestLogs.RemoveAt(viewModel.LatestLogs.Count - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_latestLogEntry = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IndentLevel > 0 && _latestLogEntry is { } latest)
|
||||
{
|
||||
viewModel._dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
latest.AppendDetails(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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.Globalization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
internal sealed partial class LogEntryViewModel : ObservableObject
|
||||
{
|
||||
private const int HeaderMaxLength = 80;
|
||||
private const string WarningGlyph = "\uE7BA";
|
||||
private const string ErrorGlyph = "\uEA39";
|
||||
private const string TimestampFormat = "HH:mm:ss";
|
||||
|
||||
private DateTimeOffset Timestamp { get; }
|
||||
|
||||
private string Severity { get; }
|
||||
|
||||
private string Message { get; }
|
||||
|
||||
private string FormattedTimestamp { get; }
|
||||
|
||||
public string SeverityGlyph { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Header { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Description { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Details { get; private set; }
|
||||
|
||||
public LogEntryViewModel(DateTimeOffset timestamp, string severity, string message, string details)
|
||||
{
|
||||
Timestamp = timestamp;
|
||||
Severity = severity;
|
||||
Message = message;
|
||||
Details = details;
|
||||
|
||||
SeverityGlyph = severity.ToUpperInvariant() switch
|
||||
{
|
||||
"WARNING" => WarningGlyph,
|
||||
"ERROR" => ErrorGlyph,
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
FormattedTimestamp = timestamp.ToString(TimestampFormat, CultureInfo.CurrentCulture);
|
||||
Description = $"{FormattedTimestamp} • {Message}";
|
||||
Header = Message;
|
||||
}
|
||||
|
||||
public void AppendDetails(string? message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Details += Environment.NewLine + message;
|
||||
|
||||
// Make header the second line of details (because that's actually the message itself):
|
||||
var detailsLines = Details.Split([Environment.NewLine], StringSplitOptions.None);
|
||||
if (detailsLines.Length < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Header = detailsLines[1].Trim();
|
||||
if (Header.Length > HeaderMaxLength)
|
||||
{
|
||||
Header = Header[..(HeaderMaxLength - 1)] + "…";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// 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.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class FallbackRemoteDesktopItemTests
|
||||
{
|
||||
private static readonly CompositeFormat OpenHostCompositeFormat = CompositeFormat.Parse(Resources.remotedesktop_open_host);
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenMatchingConnectionExists_UsesConnectionName()
|
||||
{
|
||||
var connectionName = "my-rdp-server";
|
||||
|
||||
// Arrange
|
||||
var setup = CreateFallback(connectionName);
|
||||
var fallback = setup.Fallback;
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery("my-rdp-server");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(connectionName, fallback.Title);
|
||||
var expectedSubtitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, connectionName);
|
||||
Assert.AreEqual(expectedSubtitle, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNotNull(command);
|
||||
Assert.AreEqual(Resources.remotedesktop_command_connect, command.Name);
|
||||
Assert.AreEqual(connectionName, GetCommandHost(command));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenQueryIsValidHostWithoutExistingConnection_UsesQuery()
|
||||
{
|
||||
// Arrange
|
||||
var setup = CreateFallback();
|
||||
var fallback = setup.Fallback;
|
||||
const string hostname = "test.corp";
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery(hostname);
|
||||
|
||||
// Assert
|
||||
var expectedTitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, hostname);
|
||||
Assert.AreEqual(expectedTitle, fallback.Title);
|
||||
Assert.AreEqual(Resources.remotedesktop_title, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNotNull(command);
|
||||
Assert.AreEqual(Resources.remotedesktop_command_connect, command.Name);
|
||||
Assert.AreEqual(hostname, GetCommandHost(command));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenQueryIsWhitespace_ResetsCommand()
|
||||
{
|
||||
// Arrange
|
||||
var setup = CreateFallback("rdp-server-two");
|
||||
var fallback = setup.Fallback;
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery(" ");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(string.Empty, fallback.Title);
|
||||
Assert.AreEqual(string.Empty, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNull(command);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenQueryIsInvalidHost_ClearsCommand()
|
||||
{
|
||||
// Arrange
|
||||
var setup = CreateFallback("rdp-server-three");
|
||||
var fallback = setup.Fallback;
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery("not a valid host");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(string.Empty, fallback.Title);
|
||||
Assert.AreEqual(string.Empty, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNull(command);
|
||||
}
|
||||
|
||||
private static string GetCommandHost(OpenRemoteDesktopCommand command)
|
||||
{
|
||||
var field = typeof(OpenRemoteDesktopCommand).GetField("_rdpHost", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return field.GetValue(command) as string ?? string.Empty;
|
||||
}
|
||||
|
||||
private static (FallbackRemoteDesktopItem Fallback, IRdpConnectionsManager Manager) CreateFallback(params string[] connectionNames)
|
||||
{
|
||||
var settingsManager = new MockSettingsManager(connectionNames);
|
||||
var connectionsManager = new MockRdpConnectionsManager(settingsManager);
|
||||
|
||||
var fallback = new FallbackRemoteDesktopItem(connectionsManager);
|
||||
|
||||
return (fallback, connectionsManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user