mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-02 02:16:35 +01:00
Compare commits
29 Commits
dev/mjolle
...
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 |
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
|
||||
|
||||
@@ -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}
|
||||
|
||||
16
README.md
16
README.md
@@ -53,17 +53,17 @@ Go to the [PowerToys GitHub releases][github-release-link], click Assets to reve
|
||||
<!-- 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.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.0/PowerToysUserSetup-0.96.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.0/PowerToysUserSetup-0.96.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.0/PowerToysSetup-0.96.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.0/PowerToysSetup-0.96.0-arm64.exe
|
||||
[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.96.0-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.96.0-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.96.0-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.96.0-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>
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -64,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();
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
internal sealed class MockRdpConnectionsManager : IRdpConnectionsManager
|
||||
{
|
||||
private readonly List<ConnectionListItem> _connections = new();
|
||||
|
||||
public IReadOnlyCollection<ConnectionListItem> Connections => _connections.AsReadOnly();
|
||||
|
||||
public MockRdpConnectionsManager(ISettingsInterface settingsManager)
|
||||
{
|
||||
_connections.AddRange(settingsManager.PredefinedConnections.Select(ConnectionHelpers.MapToResult));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
using ToolkitSettings = Microsoft.CommandPalette.Extensions.Toolkit.Settings;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
internal sealed class MockSettingsManager : ISettingsInterface
|
||||
{
|
||||
private readonly List<string> _connections;
|
||||
|
||||
public IReadOnlyCollection<string> PredefinedConnections => _connections;
|
||||
|
||||
public ToolkitSettings Settings { get; } = new();
|
||||
|
||||
public MockSettingsManager(params string[] predefinedConnections)
|
||||
{
|
||||
_connections = new(predefinedConnections);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class RdpConnectionsManagerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Constructor_AddsOpenCommandItem()
|
||||
{
|
||||
// Act
|
||||
var manager = new RdpConnectionsManager(new MockSettingsManager(["test.local"]));
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(manager.Connections.Any(item => string.IsNullOrEmpty(item.ConnectionName)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FindConnection_ReturnsExactMatch()
|
||||
{
|
||||
// Arrange
|
||||
var connectionName = "rdp-test";
|
||||
var connection = new ConnectionListItem(connectionName);
|
||||
|
||||
// Act
|
||||
var result = ConnectionHelpers.FindConnection(connectionName, new[] { connection });
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(connectionName, result.ConnectionName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FindConnection_ReturnsNullForWhitespaceQuery()
|
||||
{
|
||||
// Arrange
|
||||
var connection = new ConnectionListItem("rdp-test");
|
||||
|
||||
// Act
|
||||
var result = ConnectionHelpers.FindConnection(" ", new[] { connection });
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Pages;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class RemoteDesktopCommandProviderTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ProviderHasCorrectId()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("com.microsoft.cmdpal.builtin.remotedesktop", provider.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProviderHasDisplayName()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(provider.DisplayName);
|
||||
Assert.IsTrue(provider.DisplayName.Length > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProviderHasIcon()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(provider.Icon);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TopLevelCommandsNotEmpty()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(commands);
|
||||
Assert.IsTrue(commands.Length > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FallbackCommandsNotEmpty()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.FallbackCommands();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(commands);
|
||||
Assert.IsTrue(commands.Length > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TopLevelCommandsContainListPageCommand()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, commands.Length);
|
||||
Assert.IsInstanceOfType(commands.Single().Command, typeof(RemoteDesktopListPage));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FallbackCommandsContainFallbackItem()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.FallbackCommands();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, commands.Length);
|
||||
Assert.IsInstanceOfType(commands.Single(), typeof(FallbackRemoteDesktopItem));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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.Ext.WebSearch.Helpers.Browser;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
|
||||
|
||||
public class MockBrowserInfoService : IBrowserInfoService
|
||||
{
|
||||
public BrowserInfo GetDefaultBrowser() => new() { Name = "mocked browser", Path = "C:\\mockery\\mock.exe" };
|
||||
}
|
||||
@@ -18,6 +18,8 @@ public class MockSettingsInterface : ISettingsInterface
|
||||
|
||||
public int HistoryItemCount { get; set; }
|
||||
|
||||
public string CustomSearchUri { get; }
|
||||
|
||||
public IReadOnlyList<HistoryItem> HistoryItems => _historyItems;
|
||||
|
||||
public MockSettingsInterface(int historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
|
||||
|
||||
@@ -26,8 +26,9 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
// Setup
|
||||
var settings = new MockSettingsInterface();
|
||||
var browserInfoService = new MockBrowserInfoService();
|
||||
|
||||
var page = new WebSearchListPage(settings);
|
||||
var page = new WebSearchListPage(settings, browserInfoService);
|
||||
|
||||
// Act
|
||||
page.UpdateSearchText(string.Empty, query);
|
||||
@@ -55,8 +56,9 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
};
|
||||
|
||||
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, historyItemCount: 5);
|
||||
var browserInfoService = new MockBrowserInfoService();
|
||||
|
||||
var page = new WebSearchListPage(settings);
|
||||
var page = new WebSearchListPage(settings, browserInfoService);
|
||||
|
||||
// Act
|
||||
page.UpdateSearchText("abcdef", string.Empty);
|
||||
@@ -90,8 +92,9 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
};
|
||||
|
||||
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, historyItemCount: 5);
|
||||
var browserInfoService = new MockBrowserInfoService();
|
||||
|
||||
var page = new WebSearchListPage(settings);
|
||||
var page = new WebSearchListPage(settings, browserInfoService);
|
||||
|
||||
mockHistoryItems.Add(new HistoryItem("another search5", DateTime.Parse("2024-01-06 13:00:00", CultureInfo.CurrentCulture)));
|
||||
|
||||
@@ -123,8 +126,9 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
};
|
||||
|
||||
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, historyItemCount: 0);
|
||||
var browserInfoService = new MockBrowserInfoService();
|
||||
|
||||
var page = new WebSearchListPage(settings);
|
||||
var page = new WebSearchListPage(settings, browserInfoService);
|
||||
|
||||
// Act
|
||||
page.UpdateSearchText("abcdef", string.Empty);
|
||||
|
||||
@@ -20,7 +20,9 @@ public class SettingsManagerTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
// Setup
|
||||
var settings = new MockSettingsInterface(historyItemCount: 5);
|
||||
var page = new WebSearchListPage(settings);
|
||||
var browserInfoService = new MockBrowserInfoService();
|
||||
|
||||
var page = new WebSearchListPage(settings, browserInfoService);
|
||||
|
||||
var eventRaised = false;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user