Merge remote-tracking branch 'origin/main' into dev/vanzue/zoomit-hotkey

This commit is contained in:
vanzue
2025-12-03 18:00:13 +08:00
190 changed files with 6502 additions and 5449 deletions

View File

@@ -2,8 +2,8 @@ AAAAs
abcdefghjkmnpqrstuvxyz abcdefghjkmnpqrstuvxyz
abgr abgr
ABlocked ABlocked
ABOUTBOX
ABORTIFHUNG ABORTIFHUNG
ABOUTBOX
Abug Abug
Acceleratorkeys Acceleratorkeys
ACCEPTFILES ACCEPTFILES
@@ -56,6 +56,7 @@ ANull
AOC AOC
aocfnapldcnfbofgmbbllojgocaelgdd aocfnapldcnfbofgmbbllojgocaelgdd
AOklab AOklab
aot
APARTMENTTHREADED APARTMENTTHREADED
APeriod APeriod
apicontract apicontract
@@ -97,8 +98,8 @@ ASSOCSTR
ASYNCWINDOWPLACEMENT ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS ASYNCWINDOWPOS
atl atl
ATX
ATRIOX ATRIOX
ATX
aumid aumid
authenticode authenticode
AUTOBUDDY AUTOBUDDY
@@ -117,10 +118,10 @@ azman
azureaiinference azureaiinference
azureinference azureinference
azureopenai azureopenai
backticks
bbwe bbwe
BCIE BCIE
bck bck
backticks
BESTEFFORT BESTEFFORT
bezelled bezelled
bhid bhid
@@ -148,8 +149,8 @@ bmi
BNumber BNumber
BODGY BODGY
BOklab BOklab
Bootstrappers
BOOTSTRAPPERINSTALLFOLDER BOOTSTRAPPERINSTALLFOLDER
Bootstrappers
BOTTOMALIGN BOTTOMALIGN
boxmodel boxmodel
BPBF BPBF
@@ -176,17 +177,16 @@ BYPOSITION
CALCRECT CALCRECT
CALG CALG
callbackptr callbackptr
cabstr
calpwstr calpwstr
caub
Cangjie Cangjie
CANRENAME CANRENAME
Carlseibert
Canvascustomlayout Canvascustomlayout
CAPTUREBLT CAPTUREBLT
CAPTURECHANGED CAPTURECHANGED
CARETBLINKING CARETBLINKING
Carlseibert
CAtl CAtl
caub
CBN CBN
cch cch
CCHDEVICENAME CCHDEVICENAME
@@ -206,11 +206,9 @@ changecursor
CHILDACTIVATE CHILDACTIVATE
CHILDWINDOW CHILDWINDOW
CHOOSEFONT CHOOSEFONT
CIBUILD
cidl cidl
CIELCh CIELCh
cim cim
claude
CImage CImage
cla cla
CLASSDC CLASSDC
@@ -264,7 +262,6 @@ CONFIGW
CONFLICTINGMODIFIERKEY CONFLICTINGMODIFIERKEY
CONFLICTINGMODIFIERSHORTCUT CONFLICTINGMODIFIERSHORTCUT
CONOUT CONOUT
coreclr
constexpr constexpr
contentdialog contentdialog
contentfiles contentfiles
@@ -276,6 +273,7 @@ copiedcolorrepresentation
coppied coppied
copyable copyable
COPYPEN COPYPEN
coreclr
COREWINDOW COREWINDOW
Corpor Corpor
cotaskmem cotaskmem
@@ -284,18 +282,18 @@ countof
covrun covrun
cpcontrols cpcontrols
cph cph
cppcoreguidelines
cplusplus cplusplus
CPower CPower
cppcoreguidelines
cpptools cpptools
cppvsdbg cppvsdbg
cppwinrt cppwinrt
createdump createdump
creativecommons
CREATEPROCESS CREATEPROCESS
CREATESCHEDULEDTASK CREATESCHEDULEDTASK
CREATESTRUCT CREATESTRUCT
CREATEWINDOWFAILED CREATEWINDOWFAILED
creativecommons
CRECT CRECT
CRH CRH
critsec critsec
@@ -331,7 +329,6 @@ CYSCREEN
CYSMICON CYSMICON
CYVIRTUALSCREEN CYVIRTUALSCREEN
Czechia Czechia
cziplib
Dac Dac
dacl dacl
DAffine DAffine
@@ -355,9 +352,7 @@ Deact
debugbreak debugbreak
decryptor decryptor
Dedup Dedup
dfx
Deduplicator Deduplicator
Deeplink
DEFAULTBOOTSTRAPPERINSTALLFOLDER DEFAULTBOOTSTRAPPERINSTALLFOLDER
DEFAULTCOLOR DEFAULTCOLOR
DEFAULTFLAGS DEFAULTFLAGS
@@ -404,7 +399,6 @@ DISPLAYFREQUENCY
displayname displayname
DISPLAYORIENTATION DISPLAYORIENTATION
divyan divyan
djwsxzxb
Dlg Dlg
DLGFRAME DLGFRAME
DLGMODALFRAME DLGMODALFRAME
@@ -417,7 +411,6 @@ DONTVALIDATEPATH
dotnet dotnet
downsampled downsampled
downsampling downsampling
Downsampled
downscale downscale
DPICHANGED DPICHANGED
DPIs DPIs
@@ -531,7 +524,6 @@ EXTRINSICPROPERTIES
eyetracker eyetracker
FANCYZONESDRAWLAYOUTTEST FANCYZONESDRAWLAYOUTTEST
FANCYZONESEDITOR FANCYZONESEDITOR
FNumber
FARPROC FARPROC
fdx fdx
fesf fesf
@@ -563,8 +555,8 @@ FIXEDSYS
flac flac
flyouts flyouts
FMask FMask
foundrylocal
fmtid fmtid
FNumber
FOF FOF
FOFX FOFX
FOLDERID FOLDERID
@@ -575,6 +567,7 @@ FORCEMINIMIZE
FORMATDLGORD FORMATDLGORD
formatetc formatetc
FORPARSING FORPARSING
foundrylocal
FRAMECHANGED FRAMECHANGED
frm frm
FROMTOUCH FROMTOUCH
@@ -593,13 +586,13 @@ gdi
gdiplus gdiplus
GDIPVER GDIPVER
GDISCALED GDISCALED
geolocator
GETCLIENTAREAANIMATION GETCLIENTAREAANIMATION
GETCURSEL GETCURSEL
GETDESKWALLPAPER GETDESKWALLPAPER
GETDLGCODE GETDLGCODE
GETDPISCALEDSIZE GETDPISCALEDSIZE
getfilesiginforedist getfilesiginforedist
geolocator
GETHOTKEY GETHOTKEY
GETICON GETICON
GETLBTEXT GETLBTEXT
@@ -610,11 +603,12 @@ GETSCREENSAVERRUNNING
GETSECKEY GETSECKEY
GETSTICKYKEYS GETSTICKYKEYS
GETTEXTLENGTH GETTEXTLENGTH
GIFs
gitmodules
GHND GHND
gitmodules
GMEM GMEM
GNumber GNumber
googleai
googlegemini
gpedit gpedit
gpo gpo
GPOCA GPOCA
@@ -631,8 +625,6 @@ GValue
gwl gwl
GWLP GWLP
GWLSTYLE GWLSTYLE
googleai
googlegemini
hangeul hangeul
Hanzi Hanzi
Hardlines Hardlines
@@ -743,9 +735,7 @@ IDCANCEL
IDD IDD
idk idk
idl idl
IIM
idlist idlist
ifd
IDOK IDOK
IDOn IDOn
IDR IDR
@@ -754,15 +744,16 @@ ietf
IEXPLORE IEXPLORE
IFACEMETHOD IFACEMETHOD
IFACEMETHODIMP IFACEMETHODIMP
ifd
IGNOREUNKNOWN IGNOREUNKNOWN
IGo IGo
iid iid
IIM
Iindex Iindex
Ijwhost Ijwhost
ILD ILD
IMAGEHLP IMAGEHLP
IMAGERESIZERCONTEXTMENU IMAGERESIZERCONTEXTMENU
IPTC
IMAGERESIZEREXT IMAGERESIZEREXT
imageresizerinput imageresizerinput
imageresizersettings imageresizersettings
@@ -798,7 +789,6 @@ INSTALLFOLDERTOPREVIOUSINSTALLFOLDER
INSTALLLOCATION INSTALLLOCATION
INSTALLMESSAGE INSTALLMESSAGE
INSTALLPROPERTY INSTALLPROPERTY
installscopeperuser
INSTALLSTARTMENUSHORTCUT INSTALLSTARTMENUSHORTCUT
INSTALLSTATE INSTALLSTATE
Inste Inste
@@ -811,6 +801,7 @@ invokecommand
ipcmanager ipcmanager
IPREVIEW IPREVIEW
ipreviewhandlervisualssetfont ipreviewhandlervisualssetfont
IPTC
irow irow
irprops irprops
isbi isbi
@@ -854,15 +845,14 @@ keyvault
KILLFOCUS KILLFOCUS
killrunner killrunner
kmph kmph
ksa
kvp kvp
Kybd Kybd
LARGEICON LARGEICON
lastcodeanalysissucceeded lastcodeanalysissucceeded
LASTEXITCODE LASTEXITCODE
LAYOUTRTL LAYOUTRTL
LCh
lbl lbl
LCh
lcid lcid
LCIDTo LCIDTo
lcl lcl
@@ -878,10 +868,10 @@ LExit
lhwnd lhwnd
LIBFUZZER LIBFUZZER
LIBID LIBID
lightswitch
LIMITSIZE LIMITSIZE
LIMITTEXT LIMITTEXT
lindex lindex
lightswitch
linkid linkid
LINKOVERLAY LINKOVERLAY
LINQTo LINQTo
@@ -892,6 +882,7 @@ LLKH
llkhf llkhf
LMEM LMEM
LMENU LMENU
lng
LOADFROMFILE LOADFROMFILE
LOBYTE LOBYTE
localappdata localappdata
@@ -901,17 +892,14 @@ LOCATIONCHANGE
LOCKTYPE LOCKTYPE
LOGFONT LOGFONT
LOGFONTW LOGFONTW
logon
lon
LOGMSG LOGMSG
logon
LOGPIXELSX LOGPIXELSX
LOGPIXELSY LOGPIXELSY
lng
lon lon
longdate longdate
LONGNAMES LONGNAMES
lowlevel lowlevel
lquadrant
LOWORD LOWORD
lparam lparam
LPBITMAPINFOHEADER LPBITMAPINFOHEADER
@@ -945,6 +933,7 @@ lpv
LPW LPW
lpwcx lpwcx
lpwndpl lpwndpl
lquadrant
LReader LReader
LRESULT LRESULT
LSTATUS LSTATUS
@@ -971,6 +960,7 @@ MAKELONG
MAKELPARAM MAKELPARAM
makepri makepri
MAKEWPARAM MAKEWPARAM
Malware
manifestdependency manifestdependency
MAPPEDTOSAMEKEY MAPPEDTOSAMEKEY
MAPTOSAMESHORTCUT MAPTOSAMESHORTCUT
@@ -993,8 +983,8 @@ MENUITEMINFO
MENUITEMINFOW MENUITEMINFOW
MERGECOPY MERGECOPY
MERGEPAINT MERGEPAINT
Metadatas
metadatamatters metadatamatters
Metadatas
metafile metafile
mfc mfc
Mgmt Mgmt
@@ -1040,9 +1030,6 @@ mousepointer
mouseutils mouseutils
MOVESIZEEND MOVESIZEEND
MOVESIZESTART MOVESIZESTART
muxx
muxxc
muxxh
MRM MRM
MRT MRT
mru mru
@@ -1071,10 +1058,14 @@ msrc
msstore msstore
msvcp msvcp
MT MT
mstsc
MTND MTND
MULTIPLEUSE MULTIPLEUSE
multizone multizone
muxc muxc
muxx
muxxc
muxxh
MVPs MVPs
mvvm mvvm
MVVMTK MVVMTK
@@ -1157,7 +1148,6 @@ nonstd
NOOWNERZORDER NOOWNERZORDER
NOPARENTNOTIFY NOPARENTNOTIFY
NOPREFIX NOPREFIX
NPU
NOREDIRECTIONBITMAP NOREDIRECTIONBITMAP
NOREDRAW NOREDRAW
NOREMOVE NOREMOVE
@@ -1186,6 +1176,7 @@ nowarn
NOZORDER NOZORDER
NPH NPH
npmjs npmjs
NPU
NResize NResize
NTAPI NTAPI
ntdll ntdll
@@ -1210,15 +1201,17 @@ oldpath
oldtheme oldtheme
oleaut oleaut
OLECHAR OLECHAR
ollama
onebranch onebranch
onnx
OOBEUI OOBEUI
openas openas
opencode opencode
OPENFILENAME OPENFILENAME
openrdp
opensource opensource
openxmlformats openxmlformats
ollama ollama
Olllama
onnx onnx
OPTIMIZEFORINVOKE OPTIMIZEFORINVOKE
ORPHANEDDIALOGTITLE ORPHANEDDIALOGTITLE
@@ -1292,6 +1285,7 @@ pguid
phbm phbm
phbmp phbmp
phicon phicon
Photoshop
phwnd phwnd
pici pici
pidl pidl
@@ -1314,7 +1308,6 @@ pnid
PNMLINK PNMLINK
Poc Poc
Podcasts Podcasts
Photoshop
POINTERID POINTERID
POINTERUPDATE POINTERUPDATE
Pokedex Pokedex
@@ -1409,10 +1402,9 @@ pwsz
pwtd pwtd
QDC QDC
qit qit
QNN
Qualcomm
QITAB QITAB
QITABENT QITABENT
QNN
qoi qoi
Quarternary Quarternary
QUERYENDSESSION QUERYENDSESSION
@@ -1422,8 +1414,8 @@ quickaccent
QUNS QUNS
RAII RAII
RAlt RAlt
RAquadrant
randi randi
RAquadrant
rasterization rasterization
Rasterize Rasterize
RAWINPUTDEVICE RAWINPUTDEVICE
@@ -1433,6 +1425,8 @@ RAWPATH
rbhid rbhid
rclsid rclsid
RCZOOMIT RCZOOMIT
remotedesktop
rdp
RDW RDW
READMODE READMODE
READOBJECTS READOBJECTS
@@ -1450,9 +1444,7 @@ regfile
REGISTERCLASSFAILED REGISTERCLASSFAILED
REGISTRYHEADER REGISTRYHEADER
REGISTRYPREVIEWEXT REGISTRYPREVIEWEXT
registryroot
regkey regkey
regroot
regsvr regsvr
REINSTALLMODE REINSTALLMODE
releaseblog releaseblog
@@ -1505,7 +1497,6 @@ rstringalpha
rstringdigit rstringdigit
rtb rtb
RTLREADING RTLREADING
rtm
runas runas
rundll rundll
rungameid rungameid
@@ -1562,8 +1553,8 @@ SETRULES
SETSCREENSAVEACTIVE SETSCREENSAVEACTIVE
SETSTICKYKEYS SETSTICKYKEYS
SETTEXT SETTEXT
settingscard
SETTINGCHANGE SETTINGCHANGE
settingscard
SETTINGSCHANGED SETTINGSCHANGED
settingsheader settingsheader
settingshotkeycontrol settingshotkeycontrol
@@ -1638,6 +1629,7 @@ SKIPOWNPROCESS
sku sku
SLGP SLGP
sln sln
slnx
SMALLICON SMALLICON
smartphone smartphone
smileys smileys
@@ -1708,6 +1700,7 @@ stringtable
stringval stringval
Strm Strm
strret strret
STRSAFE
stscanf stscanf
sttngs sttngs
Stubless Stubless
@@ -1719,7 +1712,6 @@ sublang
SUBMODULEUPDATE SUBMODULEUPDATE
subresource subresource
Superbar Superbar
suntimes
sut sut
svchost svchost
SVGIn SVGIn
@@ -1753,7 +1745,6 @@ SYSTEMMODAL
SYSTEMTIME SYSTEMTIME
TARG TARG
TARGETAPPHEADER TARGETAPPHEADER
TARGETDIR
targetentrypoint targetentrypoint
TARGETHEADER TARGETHEADER
targetver targetver
@@ -1783,10 +1774,10 @@ textextractor
TEXTINCLUDE TEXTINCLUDE
tfopen tfopen
tgz tgz
THEMECHANGED
themeresources themeresources
THH THH
THICKFRAME THICKFRAME
THEMECHANGED
THISCOMPONENT THISCOMPONENT
throughs throughs
TILEDWINDOW TILEDWINDOW
@@ -1883,7 +1874,6 @@ USEINSTALLERFORTEST
USESHOWWINDOW USESHOWWINDOW
USESTDHANDLES USESTDHANDLES
USRDLL USRDLL
utm
UType UType
uuidv uuidv
uwp uwp
@@ -1956,11 +1946,11 @@ Wca
WCE WCE
wcex wcex
WClass WClass
WCRAPI
wcsicmp wcsicmp
wcsncpy wcsncpy
wcsnicmp wcsnicmp
WCT WCT
WCRAPI
WDA WDA
wdm wdm
wdp wdp
@@ -1988,6 +1978,7 @@ WINDOWPLACEMENT
WINDOWPOSCHANGED WINDOWPOSCHANGED
WINDOWPOSCHANGING WINDOWPOSCHANGING
WINDOWSBUILDNUMBER WINDOWSBUILDNUMBER
windowsml
windowssearch windowssearch
windowssettings windowssettings
WINDOWSTYLES WINDOWSTYLES
@@ -2003,9 +1994,8 @@ Winhook
WINL WINL
winlogon winlogon
winmd winmd
WINNT
windowsml
winml winml
WINNT
winres winres
winrt winrt
winsdk winsdk
@@ -2067,20 +2057,21 @@ WTSAT
Wubi Wubi
WUX WUX
Wwanpp Wwanpp
xap
XAxis XAxis
XButton XButton
xclip xclip
xcopy xcopy
xap
XDeployment XDeployment
XDimension
xdf xdf
XDimension
XDocument XDocument
XElement XElement
xfd xfd
XFile XFile
XIncrement XIncrement
XLoc XLoc
xmp
XNamespace XNamespace
Xoshiro Xoshiro
XPels XPels
@@ -2091,23 +2082,22 @@ xsi
XSpeed XSpeed
XStr XStr
xstyler xstyler
xmp
XTimer XTimer
XUP XUP
XVIRTUALSCREEN XVIRTUALSCREEN
xxxxxx xxxxxx
YAxis YAxis
ycombinator ycombinator
YIncrement
YDimension YDimension
YIncrement
yinle yinle
yinyue yinyue
YPels YPels
YPos YPos
YResolution YResolution
YSpeed YSpeed
YTimer
YStr YStr
YTimer
YVIRTUALSCREEN YVIRTUALSCREEN
ZEROINIT ZEROINIT
zonability zonability

View File

@@ -21,6 +21,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@v4 uses: actions/dependency-review-action@v4

View File

@@ -27,7 +27,7 @@ jobs:
issue: ${{ fromJson(github.event.inputs.issue_numbers) }} issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v6
- name: Run GenAI Issue Deduplicator - name: Run GenAI Issue Deduplicator
uses: pelikhan/action-genai-issue-dedup@v0 uses: pelikhan/action-genai-issue-dedup@v0

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Microsoft.PowerToys.Telemetry" version="2.0.3" /> <package id="Microsoft.PowerToys.Telemetry" version="2.0.4" />
</packages> </packages>

View File

@@ -192,14 +192,14 @@ jobs:
displayName: Verify XAML formatting displayName: Verify XAML formatting
- pwsh: |- - pwsh: |-
& '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln' & '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
displayName: Verify Nuget package versions for PowerToys.sln displayName: Verify Nuget package versions for PowerToys.slnx
- pwsh: |- - pwsh: |-
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln' & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\BugReportTool\BugReportTool.sln' & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\BugReportTool\BugReportTool.sln'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\StylesReportTool\StylesReportTool.sln' & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\StylesReportTool\StylesReportTool.sln'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.sln' & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.slnx'
displayName: Verify ARM64 configurations displayName: Verify ARM64 configurations
- ${{ if eq(parameters.enablePackageCaching, true) }}: - ${{ if eq(parameters.enablePackageCaching, true) }}:
@@ -252,7 +252,7 @@ jobs:
${{ else }}: ${{ else }}:
displayName: Build PowerToys main project displayName: Build PowerToys main project
inputs: inputs:
solution: 'PowerToys.sln' solution: 'PowerToys.slnx'
vsVersion: 17.0 vsVersion: 17.0
msbuildArgs: >- msbuildArgs: >-
-restore -graph -restore -graph
@@ -275,7 +275,7 @@ jobs:
displayName: Generate DSC artifacts for ARM64 displayName: Generate DSC artifacts for ARM64
condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64')) condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
inputs: inputs:
solution: PowerToys.sln solution: PowerToys.slnx
vsVersion: 17.0 vsVersion: 17.0
msbuildArgs: >- msbuildArgs: >-
-restore -restore

View File

@@ -74,7 +74,7 @@ jobs:
command: restore command: restore
feedsToUse: config feedsToUse: config
configPath: nuget.config configPath: nuget.config
restoreSolution: PowerToys.sln restoreSolution: PowerToys.slnx
restoreDirectory: '$(Build.SourcesDirectory)\packages' restoreDirectory: '$(Build.SourcesDirectory)\packages'
# Build all UI test projects if no specific modules are specified # Build all UI test projects if no specific modules are specified
@@ -129,4 +129,4 @@ jobs:
- publish: $(JobOutputDirectory) - publish: $(JobOutputDirectory)
artifact: $(JobOutputArtifactName) artifact: $(JobOutputArtifactName)
displayName: Publish UI Test artifacts displayName: Publish UI Test artifacts
condition: always() condition: always()

View File

@@ -35,7 +35,7 @@ steps:
- task: VSBuild@1 - task: VSBuild@1
displayName: Build Shared Support DLLs displayName: Build Shared Support DLLs
inputs: inputs:
solution: "**/installer/PowerToysSetup.sln" solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0 vsVersion: 17.0
msbuildArgs: >- msbuildArgs: >-
/t:PowerToysSetupCustomActionsVNext;SilentFilesInUseBAFunction /t:PowerToysSetupCustomActionsVNext;SilentFilesInUseBAFunction
@@ -74,7 +74,7 @@ steps:
- task: VSBuild@1 - task: VSBuild@1
displayName: 💻 Build VNext MSI displayName: 💻 Build VNext MSI
inputs: inputs:
solution: "**/installer/PowerToysSetup.sln" solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0 vsVersion: 17.0
msbuildArgs: >- msbuildArgs: >-
-restore -restore
@@ -91,7 +91,7 @@ steps:
- task: VSBuild@1 - task: VSBuild@1
displayName: 👤 Build VNext MSI displayName: 👤 Build VNext MSI
inputs: inputs:
solution: "**/installer/PowerToysSetup.sln" solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0 vsVersion: 17.0
msbuildArgs: >- msbuildArgs: >-
/t:PowerToysInstallerVNext /t:PowerToysInstallerVNext
@@ -142,7 +142,7 @@ steps:
- task: VSBuild@1 - task: VSBuild@1
displayName: 💻 Build VNext Bootstrapper displayName: 💻 Build VNext Bootstrapper
inputs: inputs:
solution: "**/installer/PowerToysSetup.sln" solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0 vsVersion: 17.0
msbuildArgs: >- msbuildArgs: >-
-restore -restore
@@ -159,7 +159,7 @@ steps:
- task: VSBuild@1 - task: VSBuild@1
displayName: 👤 Build VNext Bootstrapper displayName: 👤 Build VNext Bootstrapper
inputs: inputs:
solution: "**/installer/PowerToysSetup.sln" solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0 vsVersion: 17.0
msbuildArgs: >- msbuildArgs: >-
/t:PowerToysBootstrapperVNext /t:PowerToysBootstrapperVNext

View File

@@ -54,4 +54,13 @@ steps:
feedsToUse: 'config' feedsToUse: 'config'
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config' nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
restoreSolution: '$(build.sourcesdirectory)\**\*.sln' restoreSolution: '$(build.sourcesdirectory)\**\*.sln'
includeNuGetOrg: false includeNuGetOrg: false
- task: NuGetCommand@2
displayName: 'Restore NuGet packages (slnx)'
inputs:
command: 'restore'
feedsToUse: 'config'
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
restoreSolution: '$(build.sourcesdirectory)\**\*.slnx'
includeNuGetOrg: false

View File

@@ -243,6 +243,10 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Event Name</th> <th>Event Name</th>
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFound_EnableCmdNotFound</td>
<td>Triggered when Command Not Found is enabled or disabled.</td>
</tr>
<tr> <tr>
<td>Microsoft.PowerToys.CmdNotFoundInstallEvent</td> <td>Microsoft.PowerToys.CmdNotFoundInstallEvent</td>
<td>Triggered when a Command Not Found is installed.</td> <td>Triggered when a Command Not Found is installed.</td>
@@ -257,6 +261,62 @@ _If you want to find diagnostic data events in the source code, these two links
</tr> </tr>
</table> </table>
### Command Palette
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_BeginInvoke</td>
<td>Triggered when the Command Palette is launched by the user.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_ColdLaunch</td>
<td>Occurs when Command Palette starts for the first time (cold start).</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_OpenPage</td>
<td>Triggered when a page is opened within the Command Palette, tracking navigation depth.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_OpenUri</td>
<td>Occurs when a URI is opened through the Command Palette, including whether it's a web URL.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_ReactivateInstance</td>
<td>Triggered when an existing Command Palette instance is reactivated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_RunCommand</td>
<td>Logs when a command is executed through the Command Palette, including admin elevation status.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_RunQuery</td>
<td>Triggered when a search query is performed, including result count and duration.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalDismissedOnEsc</td>
<td>Occurs when the Command Palette is dismissed by pressing the Escape key.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalDismissedOnLostFocus</td>
<td>Triggered when the Command Palette is dismissed due to losing focus.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalHotkeySummoned</td>
<td>Logs when the Command Palette is summoned via hotkey, distinguishing between global and context-specific hotkeys.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalInvokeResult</td>
<td>Records the result type of a Command Palette invocation.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalProcessStarted</td>
<td>Triggered when the Command Palette process is started.</td>
</tr>
</table>
### Crop And Lock ### Crop And Lock
<table style="width:100%"> <table style="width:100%">
<tr> <tr>
@@ -735,6 +795,10 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Event Name</th> <th>Event Name</th>
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_ChangedTemplateLocation</td>
<td>Triggered when the template folder location is changed.</td>
</tr>
<tr> <tr>
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplate</td> <td>Microsoft.PowerToys.NewPlus_EventCopyTemplate</td>
<td>Triggered when an item from New+ is created (copied to the current directory).</td> <td>Triggered when an item from New+ is created (copied to the current directory).</td>
@@ -743,6 +807,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplateResult</td> <td>Microsoft.PowerToys.NewPlus_EventCopyTemplateResult</td>
<td>Logs the success of item creation (copying).</td> <td>Logs the success of item creation (copying).</td>
</tr> </tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_EventOpenTemplates</td>
<td>Triggered when the templates folder is opened.</td>
</tr>
<tr> <tr>
<td>Microsoft.PowerToys.NewPlus_EventShowTemplateItems</td> <td>Microsoft.PowerToys.NewPlus_EventShowTemplateItems</td>
<td>Triggered when the New+ context menu flyout is displayed.</td> <td>Triggered when the New+ context menu flyout is displayed.</td>
@@ -928,12 +996,8 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr> <tr>
<td>Microsoft.PowerToys.ShortcutGuide_EnableGuide</td> <td>Microsoft.PowerToys.ShortcutGuide_GuideSession</td>
<td>Triggered when Shortcut Guide is enabled.</td> <td>Logs a Shortcut Guide session including duration and how it was closed.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutGuide_HideGuide</td>
<td>Occurs when Shortcut Guide is hidden from view.</td>
</tr> </tr>
<tr> <tr>
<td>Microsoft.PowerToys.ShortcutGuide_Settings</td> <td>Microsoft.PowerToys.ShortcutGuide_Settings</td>

View File

@@ -69,7 +69,7 @@
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail. 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.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" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" /> <PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" /> <PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />

File diff suppressed because it is too large Load Diff

1041
PowerToys.slnx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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 --> <!-- 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-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 [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 [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.0/PowerToysUserSetup-0.96.0-arm64.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.0/PowerToysSetup-0.96.0-x64.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.0/PowerToysSetup-0.96.0-arm64.exe [ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysSetup-0.96.1-arm64.exe
| Description | Filename | | Description | Filename |
|----------------|----------| |----------------|----------|
| Per user - x64 | [PowerToysUserSetup-0.96.0-x64.exe][ptUserX64] | | Per user - x64 | [PowerToysUserSetup-0.96.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.96.0-arm64.exe][ptUserArm64] | | Per user - ARM64 | [PowerToysUserSetup-0.96.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.96.0-x64.exe][ptMachineX64] | | Machine wide - x64 | [PowerToysSetup-0.96.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.96.0-arm64.exe][ptMachineArm64] | | Machine wide - ARM64 | [PowerToysSetup-0.96.1-arm64.exe][ptMachineArm64] |
</details> </details>

View File

@@ -134,7 +134,7 @@ If you prefer, you can alternatively build prerequisite projects for the install
#### Locally compiling the installer #### Locally compiling the installer
1. Open `installer\PowerToysSetup.sln` 1. Open `installer\PowerToysSetup.slnx`
1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release` 1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release`
1. From the `Build` menu choose `Build Solution`. 1. From the `Build` menu choose `Build Solution`.
@@ -144,9 +144,9 @@ To build the installer from the command line, run `Developer Command Prompt for
``` ```
git clean -xfd -e *exe -- .\installer\ git clean -xfd -e *exe -- .\installer\
MSBuild -t:restore .\installer\PowerToysSetup.sln -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release MSBuild -t:restore .\installer\PowerToysSetup.slnx -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64" MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64" MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
``` ```
### Supported arguments for the .EXE Bootstrapper installer ### Supported arguments for the .EXE Bootstrapper installer

View File

@@ -19,7 +19,7 @@ You can build the entire solution from the command line, which is sometimes fast
2. Navigate to the repository root directory 2. Navigate to the repository root directory
3. Run the following command(don't forget to set the correct platform): 3. Run the following command(don't forget to set the correct platform):
```pwsh ```pwsh
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln /tl /p:NuGetInteractive="true" msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx /tl /p:NuGetInteractive="true"
``` ```
4. This process should complete in approximately 13-14 minutes for a full build 4. This process should complete in approximately 13-14 minutes for a full build

View File

@@ -42,10 +42,10 @@ Or reach out to "tools\build\BUILD-GUIDELINES.md"
### Sample plain msbuild command ### Sample plain msbuild command
```powershell ```powershell
# Restore: # Restore:
msbuild powertoys.sln -t:restore -p:configuration=debug -p:platform=x64 -m msbuild powertoys.slnx -t:restore -p:configuration=debug -p:platform=x64 -m
# Build powertoys sln # Build powertoys slnx
msbuild powertoys.sln -p:configuration=debug -p:platform=x64 -m msbuild powertoys.slnx -p:configuration=debug -p:platform=x64 -m
# dotnet project # dotnet project
msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m
@@ -122,7 +122,7 @@ Similar for attach to managed code.
| Task | Command / Action | Notes | | Task | Command / Action | Notes |
|------|------------------|-------| |------|------------------|-------|
| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.sln` | Deep clean removes packages & build outputs | | Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.slnx` | Deep clean removes packages & build outputs |
| Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution | | Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution |
| Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug | | Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug |
| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets | | Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |

View File

@@ -12,7 +12,7 @@
- Exit PowerToys if it's running. - Exit PowerToys if it's running.
- Open `PowerToys.sln` in Visual Studio and build the solution. - Open `PowerToys.slnx` in Visual Studio and build the solution.
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`). - Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).

View File

@@ -86,7 +86,7 @@ The module provides a user interface for configuring settings in the PowerToys S
### Building and Testing ### Building and Testing
1. Clone the repository: `git clone https://github.com/microsoft/PowerToys.git` 1. Clone the repository: `git clone https://github.com/microsoft/PowerToys.git`
2. Open PowerToys.sln in Visual Studio 2. Open PowerToys.slnx in Visual Studio
3. Select the Release configuration and build the solution 3. Select the Release configuration and build the solution
4. Run PowerToys.exe from the output directory to test the module 4. Run PowerToys.exe from the output directory to test the module

View File

@@ -161,7 +161,7 @@ FancyZones is divided into several projects:
``` ```
git clone https://github.com/microsoft/PowerToys.git git clone https://github.com/microsoft/PowerToys.git
``` ```
2. Open `PowerToys.sln` in Visual Studio 2. Open `PowerToys.slnx` in Visual Studio
3. Select the Release configuration and build the solution 3. Select the Release configuration and build the solution
4. If you encounter build errors, try deleting the x64 output folder and rebuild 4. If you encounter build errors, try deleting the x64 output folder and rebuild
@@ -244,7 +244,7 @@ UI tests are implemented using [Windows Application Driver](https://github.com/m
- Exit PowerToys if it's running - Exit PowerToys if it's running
- Run WinAppDriver.exe from the installation directory. Skip this step if installed in the default directory (`C:\Program Files (x86)\Windows Application Driver`); in this case, it'll be launched automatically during tests. - Run WinAppDriver.exe from the installation directory. Skip this step if installed in the default directory (`C:\Program Files (x86)\Windows Application Driver`); in this case, it'll be launched automatically during tests.
- Open `PowerToys.sln` in Visual Studio and build the solution. - Open `PowerToys.slnx` in Visual Studio and build the solution.
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`). - Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
>Note: notifications or other application windows, that are shown above the window under test, can disrupt the testing process. >Note: notifications or other application windows, that are shown above the window under test, can disrupt the testing process.

View File

@@ -11,7 +11,7 @@ Keyboard Manager consists of two main components:
## Development Environment Setup ## Development Environment Setup
1. Clone the PowerToys repository 1. Clone the PowerToys repository
2. Open `PowerToys.sln` in Visual Studio 2. Open `PowerToys.slnx` in Visual Studio
3. Ensure all NuGet packages are restored 3. Ensure all NuGet packages are restored
4. Build the entire solution in Debug configuration 4. Build the entire solution in Debug configuration
@@ -91,4 +91,4 @@ If you encounter issues with multiple instances, check the mutex logic in `Keybo
To debug both the Editor and Engine: To debug both the Editor and Engine:
1. Launch the Engine first in debug mode 1. Launch the Engine first in debug mode
2. Attach the debugger to the Editor process when it starts 2. Attach the debugger to the Editor process when it starts

View File

@@ -92,7 +92,7 @@ The modules settings are exposed in the PowerToys Settings UI. Options includ
3. Build the solution: 3. Build the solution:
```sh ```sh
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx
``` ```
> Note: This may take some time. > Note: This may take some time.

View File

@@ -53,7 +53,7 @@ The Screen Ruler module consists of several components:
### Building ### Building
1. Open PowerToys.sln in Visual Studio 1. Open PowerToys.slnx in Visual Studio
2. In the Solutions Configuration drop-down menu, select Release or Debug 2. In the Solutions Configuration drop-down menu, select Release or Debug
3. From the Build menu, choose Build Solution 3. From the Build menu, choose Build Solution
4. The executable app for Screen Ruler is named PowerToys.MeasureToolUI.exe 4. The executable app for Screen Ruler is named PowerToys.MeasureToolUI.exe

View File

@@ -19,7 +19,7 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor
## Build and Debug Instructions ## Build and Debug Instructions
### Build ### Build
1. Open PowerToys.sln in Visual Studio 1. Open PowerToys.slnx in Visual Studio
2. Select Release or Debug in the Solutions Configuration drop-down menu 2. Select Release or Debug in the Solutions Configuration drop-down menu
3. From the Build menu, choose Build Solution 3. From the Build menu, choose Build Solution
4. The executable is named PowerToys.ShortcutGuide.exe 4. The executable is named PowerToys.ShortcutGuide.exe

View File

@@ -80,7 +80,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
### Install Visual Studio dependencies ### Install Visual Studio dependencies
1. Open the `PowerToys.sln` file. 1. Open the `PowerToys.slnx` file.
1. If you see a dialog that says `install extra components` in the solution explorer pane, click `install` 1. If you see a dialog that says `install extra components` in the solution explorer pane, click `install`
### Get Submodules to compile ### Get Submodules to compile
@@ -93,7 +93,7 @@ We have submodules that need to be initialized before you can compile most parts
### Compiling Source Code ### Compiling Source Code
- Open `PowerToys.sln` in Visual Studio. - Open `PowerToys.slnx` in Visual Studio.
- In the `Solutions Configuration` drop-down menu select `Release` or `Debug`. - In the `Solutions Configuration` drop-down menu select `Release` or `Debug`.
- From the `Build` menu choose `Build Solution`, or press <kbd>Control</kbd>+<kbd>Shift</kbd>+<kbd>b</kbd> on your keyboard. - From the `Build` menu choose `Build Solution`, or press <kbd>Control</kbd>+<kbd>Shift</kbd>+<kbd>b</kbd> on your keyboard.
- The build process may take several minutes depending on your computer's performance. Once it completes, the PowerToys binaries will be in your repo under `x64\Release\`. - The build process may take several minutes depending on your computer's performance. Once it completes, the PowerToys binaries will be in your repo under `x64\Release\`.
@@ -107,10 +107,10 @@ Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains
The installer can only be compiled in `Release` mode; steps 1 and 2 must be performed before the MSI can be compiled. The installer can only be compiled in `Release` mode; steps 1 and 2 must be performed before the MSI can be compiled.
1. Compile `PowerToys.sln`. Instructions are listed above. 1. Compile `PowerToys.slnx`. Instructions are listed above.
1. Compile `BugReportTool.sln` tool. Path from root: `tools\BugReportTool\BugReportTool.sln` (details listed below) 1. Compile `BugReportTool.sln` tool. Path from root: `tools\BugReportTool\BugReportTool.sln` (details listed below)
1. Compile `StylesReportTool.sln` tool. Path from root: `tools\StylesReportTool\StylesReportTool.sln` (details listed below) 1. Compile `StylesReportTool.sln` tool. Path from root: `tools\StylesReportTool\StylesReportTool.sln` (details listed below)
1. Compile `PowerToysSetup.sln` Path from root: `installer\PowerToysSetup.sln` (details listed below) 1. Compile `PowerToysSetup.slnx` Path from root: `installer\PowerToysSetup.slnx` (details listed below)
See [Installer](core/installer.md) for more details on building and debugging the installer. See [Installer](core/installer.md) for more details on building and debugging the installer.

View File

@@ -1,96 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32414.318
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "..\src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "..\src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "..\src\common\Telemetry\EtwTrace\EtwTrace.vcxproj", "{8F021B46-362B-485C-BFBA-CCF83E820CBD}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysInstallerVNext", "PowerToysSetupVNext\PowerToysInstallerVNext.wixproj", "{B6E94700-DF38-41F6-A3FD-18B69674AB1E}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysBootstrapperVNext", "PowerToysSetupVNext\PowerToysBootstrapperVNext.wixproj", "{DA4E9744-80BE-424C-B0F5-AFD8757DB575}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActionsVNext", "PowerToysSetupCustomActionsVNext\PowerToysSetupCustomActionsVNext.vcxproj", "{B3A354B0-1E54-4B55-A962-FB5AF9330C19}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SilentFilesInUseBAFunction", "PowerToysSetupVNext\SilentFilesInUseBA\SilentFilesInUseBAFunction.vcxproj", "{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|ARM64.ActiveCfg = Debug|ARM64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.ActiveCfg = Release|ARM64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.Build.0 = Release|ARM64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.ActiveCfg = Release|ARM64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.Build.0 = Release|ARM64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64
{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.Build.0 = Debug|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.ActiveCfg = Debug|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.Build.0 = Debug|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.ActiveCfg = Release|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.Build.0 = Release|ARM64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.ActiveCfg = Release|x64
{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.Build.0 = Release|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.Build.0 = Debug|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.ActiveCfg = Debug|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.Build.0 = Debug|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.ActiveCfg = Release|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.Build.0 = Release|ARM64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.ActiveCfg = Release|x64
{8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.Build.0 = Release|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.Build.0 = Debug|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.ActiveCfg = Debug|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.Build.0 = Debug|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.ActiveCfg = Release|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.Build.0 = Release|ARM64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.ActiveCfg = Release|x64
{B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.Build.0 = Release|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.ActiveCfg = Debug|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.Build.0 = Debug|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.ActiveCfg = Debug|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.Build.0 = Debug|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.ActiveCfg = Release|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.Build.0 = Release|ARM64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.ActiveCfg = Release|x64
{DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.Build.0 = Release|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.ActiveCfg = Debug|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.Build.0 = Debug|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.ActiveCfg = Release|ARM64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.Build.0 = Release|ARM64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.ActiveCfg = Release|x64
{B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.Build.0 = Release|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.ActiveCfg = Debug|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.Build.0 = Debug|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.ActiveCfg = Release|ARM64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.Build.0 = Release|ARM64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.ActiveCfg = Release|x64
{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B7A3DA30-D443-40FF-AC51-988AD41E3962}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,22 @@
<Solution>
<Configurations>
<Platform Name="ARM64" />
<Platform Name="x64" />
</Configurations>
<Project Path="../src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="../src/common/Telemetry/EtwTrace/EtwTrace.vcxproj" Id="8f021b46-362b-485c-bfba-ccf83e820cbd" />
<Project Path="../src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
<Project Path="../src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj" Id="b3a354b0-1e54-4b55-a962-fb5af9330c19">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="PowerToysSetupVNext/PowerToysBootstrapperVNext.wixproj" Type="b7dd6f7e-def8-4e67-b5b7-07ef123db6f0" />
<Project Path="PowerToysSetupVNext/PowerToysInstallerVNext.wixproj" Type="b7dd6f7e-def8-4e67-b5b7-07ef123db6f0" />
<Project Path="PowerToysSetupVNext/SilentFilesInUseBA/SilentFilesInUseBAFunction.vcxproj" Id="f8b9f842-f5c3-4a2d-8c85-7f8b9e2b4f1d">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
</Solution>

View File

@@ -31,6 +31,11 @@ namespace ManagedCommon
/// </summary> /// </summary>
public static string CurrentVersionLogDirectoryPath { get; private set; } 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> /// <summary>
/// Gets the path to the log directory for the app. /// Gets the path to the log directory for the app.
/// </summary> /// </summary>
@@ -55,7 +60,9 @@ namespace ManagedCommon
AppLogDirectoryPath = basePath; AppLogDirectoryPath = basePath;
CurrentVersionLogDirectoryPath = versionedPath; 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)); Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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,
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -315,7 +315,7 @@ namespace MouseWithoutBorders
if (!acquireMutex) if (!acquireMutex)
{ {
Process[] ps = Process.GetProcessesByName(Common.BinaryName); 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."); 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); 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(); MachineStuff.oneInstanceCheck?.Close();
_ = Process.Start(Application.ExecutablePath, desktop); _ = Process.Start(Application.ExecutablePath, desktop);
Logger.LogDebug($"Started on desktop {desktop}"); Logger.LogDebug($"Started on desktop {desktop}");
@@ -514,7 +514,7 @@ namespace MouseWithoutBorders
internal static void SendHeartBeat(bool initial = false) 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; private static long lastSendNextMachine;
@@ -550,7 +550,7 @@ namespace MouseWithoutBorders
internal static void SendAwakeBeat() internal static void SendAwakeBeat()
{ {
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() && if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() &&
Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount) Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount)
{ {
SendPackage(ID.ALL, PackageType.Awake); SendPackage(ID.ALL, PackageType.Awake);
@@ -568,7 +568,7 @@ namespace MouseWithoutBorders
{ {
if (lastInputEventCount == Event.InputEventCount) if (lastInputEventCount == Event.InputEventCount)
{ {
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive()) if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive())
{ {
PokeMyself(); PokeMyself();
} }
@@ -577,13 +577,13 @@ namespace MouseWithoutBorders
lastInputEventCount = Event.InputEventCount; lastInputEventCount = Event.InputEventCount;
} }
private static void PokeMyself() internal static void PokeMyself()
{ {
int x, y = 0; int x, y = 0;
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
x = Ran.Next(-9, 10); x = Encryption.Ran.Next(-9, 10);
InputSimulation.MoveMouseRelative(x, y); InputSimulation.MoveMouseRelative(x, y);
Thread.Sleep(50); Thread.Sleep(50);
InputSimulation.MoveMouseRelative(-x, -y); InputSimulation.MoveMouseRelative(-x, -y);
@@ -677,7 +677,7 @@ namespace MouseWithoutBorders
{ {
Common.MMSleep(0.2); Common.MMSleep(0.2);
InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT }); 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."); Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated.");
@@ -710,7 +710,7 @@ namespace MouseWithoutBorders
"\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") + "\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
"\"", "\"",
"\"" + file + "\"", "\"" + file + "\"",
GetInputDesktop(), WinAPI.GetInputDesktop(),
1); 1);
// CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") + // CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
@@ -919,7 +919,7 @@ namespace MouseWithoutBorders
try try
{ {
data.Id = Interlocked.Increment(ref PackageID); data.Id = Interlocked.Increment(ref Package.PackageID);
bool updateClientSockets = false; bool updateClientSockets = false;
@@ -999,7 +999,7 @@ namespace MouseWithoutBorders
} }
else else
{ {
PackageSent.Nil++; Package.PackageSent.Nil++;
} }
} }
@@ -1379,7 +1379,7 @@ namespace MouseWithoutBorders
if (string.IsNullOrEmpty(machine_Name)) 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) internal static void SendOrReceiveARandomDataBlockPerInitialIV(Stream st, bool send = true)
{ {
byte[] ranData = new byte[SymAlBlockSize]; byte[] ranData = new byte[Encryption.SymAlBlockSize];
try try
{ {
if (send) if (send)
{ {
ranData = RandomNumberGenerator.GetBytes(SymAlBlockSize); ranData = RandomNumberGenerator.GetBytes(Encryption.SymAlBlockSize);
st.Write(ranData, 0, ranData.Length); st.Write(ranData, 0, ranData.Length);
} }
else else

View File

@@ -109,7 +109,7 @@ namespace MouseWithoutBorders.Class
// Install Mouse Hook // Install Mouse Hook
mouseHookProcedure = new NativeMethods.HookProc(MouseHookProc); mouseHookProcedure = new NativeMethods.HookProc(MouseHookProc);
hMouseHook = NativeMethods.SetWindowsHookEx( hMouseHook = NativeMethods.SetWindowsHookEx(
Common.WH_MOUSE_LL, WM.WH_MOUSE_LL,
mouseHookProcedure, mouseHookProcedure,
Marshal.GetHINSTANCE( Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[0]), Assembly.GetExecutingAssembly().GetModules()[0]),
@@ -126,7 +126,7 @@ namespace MouseWithoutBorders.Class
// Install Keyboard Hook // Install Keyboard Hook
keyboardHookProcedure = new NativeMethods.HookProc(KeyboardHookProc); keyboardHookProcedure = new NativeMethods.HookProc(KeyboardHookProc);
hKeyboardHook = NativeMethods.SetWindowsHookEx( hKeyboardHook = NativeMethods.SetWindowsHookEx(
Common.WH_KEYBOARD_LL, WM.WH_KEYBOARD_LL,
keyboardHookProcedure, keyboardHookProcedure,
Marshal.GetHINSTANCE( Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[0]), Assembly.GetExecutingAssembly().GetModules()[0]),
@@ -233,7 +233,7 @@ namespace MouseWithoutBorders.Class
if (nCode >= 0 && MouseEvent != null) if (nCode >= 0 && MouseEvent != null)
{ {
if (wParam == Common.WM_LBUTTONUP && SkipMouseUpCount > 0) if (wParam == WM.WM_LBUTTONUP && SkipMouseUpCount > 0)
{ {
Logger.LogDebug($"{nameof(SkipMouseUpCount)}: {SkipMouseUpCount}."); Logger.LogDebug($"{nameof(SkipMouseUpCount)}: {SkipMouseUpCount}.");
SkipMouseUpCount--; SkipMouseUpCount--;
@@ -241,7 +241,7 @@ namespace MouseWithoutBorders.Class
return rv; 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); rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam);
return rv; return rv;
@@ -370,7 +370,7 @@ namespace MouseWithoutBorders.Class
private bool ProcessKeyEx(int vkCode, int flags, KEYBDDATA hookCallbackKeybdData) 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; EasyMouseKeyDown = false;
@@ -553,7 +553,7 @@ namespace MouseWithoutBorders.Class
KeyboardEvent(hookCallbackKeybdData); KeyboardEvent(hookCallbackKeybdData);
} }
hookCallbackKeybdData.dwFlags |= (int)Common.LLKHF.UP; hookCallbackKeybdData.dwFlags |= (int)WM.LLKHF.UP;
foreach (var code in codes) foreach (var code in codes)
{ {

View File

@@ -112,12 +112,12 @@ namespace MouseWithoutBorders.Class
uint scanCode = 0; uint scanCode = 0;
// http://msdn.microsoft.com/en-us/library/ms644967(VS.85).aspx // 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; 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; dwFlags |= NativeMethods.KEYEVENTF.EXTENDEDKEY;
} }
@@ -173,44 +173,44 @@ namespace MouseWithoutBorders.Class
mouse_input.mi.dy = (int)dy; mouse_input.mi.dy = (int)dy;
mouse_input.mi.mouseData = md.WheelDelta; 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}."); Logger.LogDebug($"InputSimulation.SendMouse: x = {md.X}, y = {md.Y}, WheelDelta = {md.WheelDelta}, dwFlags = {md.dwFlags}.");
} }
switch (md.dwFlags) switch (md.dwFlags)
{ {
case Common.WM_MOUSEMOVE: case WM.WM_MOUSEMOVE:
mouse_input.mi.dwFlags |= (int)(NativeMethods.MOUSEEVENTF.MOVE | NativeMethods.MOUSEEVENTF.ABSOLUTE); mouse_input.mi.dwFlags |= (int)(NativeMethods.MOUSEEVENTF.MOVE | NativeMethods.MOUSEEVENTF.ABSOLUTE);
break; break;
case Common.WM_LBUTTONDOWN: case WM.WM_LBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTDOWN; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTDOWN;
break; break;
case Common.WM_LBUTTONUP: case WM.WM_LBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTUP; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTUP;
break; break;
case Common.WM_RBUTTONDOWN: case WM.WM_RBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTDOWN; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTDOWN;
break; break;
case Common.WM_RBUTTONUP: case WM.WM_RBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTUP; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTUP;
break; break;
case Common.WM_MBUTTONDOWN: case WM.WM_MBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEDOWN; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEDOWN;
break; break;
case Common.WM_MBUTTONUP: case WM.WM_MBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEUP; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEUP;
break; break;
case Common.WM_MOUSEWHEEL: case WM.WM_MOUSEWHEEL:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.WHEEL; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.WHEEL;
break; break;
case Common.WM_MOUSEHWHEEL: case WM.WM_MOUSEHWHEEL:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.HWHEEL; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.HWHEEL;
break; break;
case Common.WM_XBUTTONUP: case WM.WM_XBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XUP; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XUP;
break; break;
case Common.WM_XBUTTONDOWN: case WM.WM_XBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XDOWN; mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XDOWN;
break; break;
@@ -373,7 +373,7 @@ namespace MouseWithoutBorders.Class
{ {
eatKey = false; 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) switch ((VK)vkCode)
{ {

View File

@@ -143,7 +143,7 @@ namespace MouseWithoutBorders.Class
return; return;
} }
string myDesktop = Common.GetMyDesktop(); string myDesktop = WinAPI.GetMyDesktop();
if (firstArg.Equals("winlogon", StringComparison.OrdinalIgnoreCase)) if (firstArg.Equals("winlogon", StringComparison.OrdinalIgnoreCase))
{ {
@@ -305,8 +305,8 @@ namespace MouseWithoutBorders.Class
MachineStuff.ClearComputerMatrix(); MachineStuff.ClearComputerMatrix();
Setting.Values.MyKey = securityKey; Setting.Values.MyKey = securityKey;
Common.MyKey = securityKey; Encryption.MyKey = securityKey;
Common.MagicNumber = Common.Get24BitHash(Common.MyKey); 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 }; MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { pcName.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty };
string[] machines = MachineStuff.MachineMatrix; string[] machines = MachineStuff.MachineMatrix;
@@ -328,8 +328,8 @@ namespace MouseWithoutBorders.Class
Setting.Values.EasyMouse = (int)EasyMouseOption.Enable; Setting.Values.EasyMouse = (int)EasyMouseOption.Enable;
MachineStuff.ClearComputerMatrix(); MachineStuff.ClearComputerMatrix();
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
Common.GeneratedKey = true; Encryption.GeneratedKey = true;
Setting.Values.PauseInstantSaving = false; Setting.Values.PauseInstantSaving = false;
Setting.Values.SaveSettings(); Setting.Values.SaveSettings();

View File

@@ -109,9 +109,9 @@ namespace MouseWithoutBorders.Class
var shouldReopenSockets = false; 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; shouldReopenSockets = true;
} }
@@ -489,7 +489,7 @@ namespace MouseWithoutBorders.Class
} }
else else
{ {
string randomKey = Common.CreateDefaultKey(); string randomKey = Encryption.CreateDefaultKey();
_properties.SecurityKey.Value = randomKey; _properties.SecurityKey.Value = randomKey;
return randomKey; return randomKey;
@@ -1055,7 +1055,7 @@ namespace MouseWithoutBorders.Class
if (machineId == 0) if (machineId == 0)
{ {
var newMachineId = Common.Ran.Next(); var newMachineId = Encryption.Ran.Next();
_properties.MachineID.Value = newMachineId; _properties.MachineID.Value = newMachineId;
machineId = newMachineId; machineId = newMachineId;
if (!PauseInstantSaving) if (!PauseInstantSaving)

View File

@@ -101,7 +101,7 @@ namespace MouseWithoutBorders.Class
{ {
if (encryptedStream == null && BackingSocket.Connected) if (encryptedStream == null && BackingSocket.Connected)
{ {
encryptedStream = Common.GetEncryptedStream(new NetworkStream(BackingSocket)); encryptedStream = Encryption.GetEncryptedStream(new NetworkStream(BackingSocket));
Common.SendOrReceiveARandomDataBlockPerInitialIV(encryptedStream); Common.SendOrReceiveARandomDataBlockPerInitialIV(encryptedStream);
} }
@@ -115,7 +115,7 @@ namespace MouseWithoutBorders.Class
{ {
if (decryptedStream == null && BackingSocket.Connected) if (decryptedStream == null && BackingSocket.Connected)
{ {
decryptedStream = Common.GetDecryptedStream(new NetworkStream(BackingSocket)); decryptedStream = Encryption.GetDecryptedStream(new NetworkStream(BackingSocket));
Common.SendOrReceiveARandomDataBlockPerInitialIV(decryptedStream, false); Common.SendOrReceiveARandomDataBlockPerInitialIV(decryptedStream, false);
} }
@@ -181,7 +181,7 @@ namespace MouseWithoutBorders.Class
Logger.LogDebug("SocketStuff started."); Logger.LogDebug("SocketStuff started.");
bASE_PORT = port; bASE_PORT = port;
Common.Ran = new Random(); Encryption.Ran = new Random();
Logger.LogDebug("Validating session..."); Logger.LogDebug("Validating session...");
@@ -221,11 +221,11 @@ namespace MouseWithoutBorders.Class
if (Setting.Values.IsMyKeyRandom) if (Setting.Values.IsMyKeyRandom)
{ {
Setting.Values.MyKey = Common.MyKey; Setting.Values.MyKey = Encryption.MyKey;
} }
Common.MagicNumber = Common.Get24BitHash(Common.MyKey); Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
Common.PackageID = Setting.Values.PackageID; Package.PackageID = Setting.Values.PackageID;
TcpPort = bASE_PORT; TcpPort = bASE_PORT;
@@ -242,7 +242,7 @@ namespace MouseWithoutBorders.Class
Logger.TelemetryLogTrace($"{nameof(SocketStuff)}: {e.Message}", SeverityLevel.Warning); Logger.TelemetryLogTrace($"{nameof(SocketStuff)}: {e.Message}", SeverityLevel.Warning);
} }
Common.GetScreenConfig(); WinAPI.GetScreenConfig();
if (firstRun && Common.RunOnScrSaverDesktop) if (firstRun && Common.RunOnScrSaverDesktop)
{ {
@@ -305,7 +305,7 @@ namespace MouseWithoutBorders.Class
sleepSecs = 10; sleepSecs = 10;
// It is reasonable to give a try on restarting MwB processes in other sessions. // 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); Logger.TelemetryLogTrace("Restarting the service dues to WSAEADDRINUSE.", SeverityLevel.Warning);
Program.StartService(); Program.StartService();
@@ -361,7 +361,7 @@ namespace MouseWithoutBorders.Class
{ {
Setting.Values.LastX = Common.LastX; Setting.Values.LastX = Common.LastX;
Setting.Values.LastY = Common.LastY; 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)); // Common.Log("Saving IP: " + Setting.Values.DesMachineID.ToString(CultureInfo.CurrentCulture));
Setting.Values.DesMachineID = (uint)Common.DesMachineID; Setting.Values.DesMachineID = (uint)Common.DesMachineID;
@@ -505,10 +505,10 @@ namespace MouseWithoutBorders.Class
throw new ExpectedSocketException(log); throw new ExpectedSocketException(log);
} }
bytes[3] = (byte)((Common.MagicNumber >> 24) & 0xFF); bytes[3] = (byte)((Encryption.MagicNumber >> 24) & 0xFF);
bytes[2] = (byte)((Common.MagicNumber >> 16) & 0xFF); bytes[2] = (byte)((Encryption.MagicNumber >> 16) & 0xFF);
bytes[1] = 0; 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]); bytes[1] = (byte)(bytes[1] + bytes[i]);
} }
@@ -535,13 +535,13 @@ namespace MouseWithoutBorders.Class
magic = (buf[3] << 24) + (buf[2] << 16); magic = (buf[3] << 24) + (buf[2] << 16);
if (magic != (Common.MagicNumber & 0xFFFF0000)) if (magic != (Encryption.MagicNumber & 0xFFFF0000))
{ {
Logger.Log("Magic number invalid!"); Logger.Log("Magic number invalid!");
buf[0] = (byte)PackageType.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]); checksum = (byte)(checksum + buf[i]);
} }
@@ -557,7 +557,7 @@ namespace MouseWithoutBorders.Class
internal static DATA TcpReceiveData(TcpSk tcp, out int bytesReceived) 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; Stream decryptedStream = tcp.DecryptedStream;
if (tcp.BackingSocket == null || !tcp.BackingSocket.Connected || decryptedStream == null) if (tcp.BackingSocket == null || !tcp.BackingSocket.Connected || decryptedStream == null)
@@ -571,9 +571,9 @@ namespace MouseWithoutBorders.Class
try 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; buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
} }
@@ -586,9 +586,9 @@ namespace MouseWithoutBorders.Class
if (package.IsBigPackage) 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; buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
} }
@@ -614,28 +614,28 @@ namespace MouseWithoutBorders.Class
switch (type) switch (type)
{ {
case PackageType.Keyboard: case PackageType.Keyboard:
Common.PackageSent.Keyboard++; Package.PackageSent.Keyboard++;
break; break;
case PackageType.Mouse: case PackageType.Mouse:
Common.PackageSent.Mouse++; Package.PackageSent.Mouse++;
break; break;
case PackageType.Heartbeat: case PackageType.Heartbeat:
case PackageType.Heartbeat_ex: case PackageType.Heartbeat_ex:
Common.PackageSent.Heartbeat++; Package.PackageSent.Heartbeat++;
break; break;
case PackageType.Hello: case PackageType.Hello:
Common.PackageSent.Hello++; Package.PackageSent.Hello++;
break; break;
case PackageType.ByeBye: case PackageType.ByeBye:
Common.PackageSent.ByeBye++; Package.PackageSent.ByeBye++;
break; break;
case PackageType.Matrix: case PackageType.Matrix:
Common.PackageSent.Matrix++; Package.PackageSent.Matrix++;
break; break;
default: default:
@@ -643,11 +643,11 @@ namespace MouseWithoutBorders.Class
switch (subtype) switch (subtype)
{ {
case (byte)PackageType.ClipboardText: case (byte)PackageType.ClipboardText:
Common.PackageSent.ClipboardText++; Package.PackageSent.ClipboardText++;
break; break;
case (byte)PackageType.ClipboardImage: case (byte)PackageType.ClipboardImage:
Common.PackageSent.ClipboardImage++; Package.PackageSent.ClipboardImage++;
break; break;
default: default:
@@ -1266,7 +1266,7 @@ namespace MouseWithoutBorders.Class
string strIP = string.Empty; string strIP = string.Empty;
ID remoteID = ID.NONE; ID remoteID = ID.NONE;
byte[] buf = RandomNumberGenerator.GetBytes(Common.PACKAGE_SIZE_EX); byte[] buf = RandomNumberGenerator.GetBytes(Package.PACKAGE_SIZE_EX);
d = new DATA(buf); d = new DATA(buf);
TcpSk currentTcp = tcp; TcpSk currentTcp = tcp;
@@ -1280,8 +1280,8 @@ namespace MouseWithoutBorders.Class
try try
{ {
currentSocket.SendBufferSize = Common.PACKAGE_SIZE * 10000; currentSocket.SendBufferSize = Package.PACKAGE_SIZE * 10000;
currentSocket.ReceiveBufferSize = Common.PACKAGE_SIZE * 10000; currentSocket.ReceiveBufferSize = Package.PACKAGE_SIZE * 10000;
currentSocket.NoDelay = true; // This is very interesting to know:( currentSocket.NoDelay = true; // This is very interesting to know:(
currentSocket.SendTimeout = 500; currentSocket.SendTimeout = 500;
d.MachineName = Common.MachineName; d.MachineName = Common.MachineName;
@@ -1829,7 +1829,7 @@ namespace MouseWithoutBorders.Class
} }
while (rv > 0); 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); Array.Clear(buf, 0, buf.Length);
ecStream.Write(buf, 0, rv); ecStream.Write(buf, 0, rv);
@@ -1900,7 +1900,7 @@ namespace MouseWithoutBorders.Class
} }
while (rv > 0); while (rv > 0);
if ((rv = sentCount % Common.PACKAGE_SIZE) > 0) if ((rv = sentCount % Package.PACKAGE_SIZE) > 0)
{ {
Array.Clear(buf, 0, buf.Length); Array.Clear(buf, 0, buf.Length);
ecStream.Write(buf, 0, rv); ecStream.Write(buf, 0, rv);
@@ -1984,7 +1984,7 @@ namespace MouseWithoutBorders.Class
if (tcp.MachineId == Setting.Values.MachineId) if (tcp.MachineId == Setting.Values.MachineId)
{ {
tcp = null; tcp = null;
Setting.Values.MachineId = Common.Ran.Next(); Setting.Values.MachineId = Encryption.Ran.Next();
InitAndCleanup.UpdateMachineTimeAndID(); InitAndCleanup.UpdateMachineTimeAndID();
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY; InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY;

View File

@@ -70,7 +70,7 @@ namespace MouseWithoutBorders.Class
continue; continue;
} }
if (!Common.IsMyDesktopActive()) if (!WinAPI.IsMyDesktopActive())
{ {
// We can just throw the SocketException but to avoid a redundant log entry: // We can just throw the SocketException but to avoid a redundant log entry:
throw new ExpectedSocketException($"{nameof(StartServer)}: The desktop is no longer active."); throw new ExpectedSocketException($"{nameof(StartServer)}: The desktop is no longer active.");

View File

@@ -270,15 +270,15 @@ internal static class Clipboard
int index = 0; int index = 0;
int len; int len;
DATA package = new(); DATA package = new();
byte[] buf = new byte[Common.PACKAGE_SIZE_EX]; byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
int dataStart = Common.PACKAGE_SIZE_EX - DATA_SIZE; int dataStart = Package.PACKAGE_SIZE_EX - DATA_SIZE;
while (true) while (true)
{ {
if ((index + DATA_SIZE) > l) if ((index + DATA_SIZE) > l)
{ {
len = l - index; len = l - index;
Array.Clear(buf, 0, Common.PACKAGE_SIZE_EX); Array.Clear(buf, 0, Package.PACKAGE_SIZE_EX);
} }
else else
{ {
@@ -315,7 +315,7 @@ internal static class Clipboard
} }
MemoryStream m = new(); 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); m.Write(data.Bytes, dataStart, DATA_SIZE);
int unexpectedCount = 0; int unexpectedCount = 0;
@@ -809,27 +809,27 @@ internal static class Clipboard
MachineName = Common.MachineName, MachineName = Common.MachineName,
}; };
byte[] buf = new byte[Common.PACKAGE_SIZE_EX]; byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
NetworkStream ns = new(s); NetworkStream ns = new(s);
enStream = Common.GetEncryptedStream(ns); enStream = Encryption.GetEncryptedStream(ns);
Common.SendOrReceiveARandomDataBlockPerInitialIV(enStream); Common.SendOrReceiveARandomDataBlockPerInitialIV(enStream);
Logger.LogDebug($"{nameof(ShakeHand)}: Writing header package."); 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}."); Logger.LogDebug($"{nameof(ShakeHand)}: Sent: clientPush={clientPushData}, postAction={postAction}.");
deStream = Common.GetDecryptedStream(ns); deStream = Encryption.GetDecryptedStream(ns);
Common.SendOrReceiveARandomDataBlockPerInitialIV(deStream, false); Common.SendOrReceiveARandomDataBlockPerInitialIV(deStream, false);
Logger.LogDebug($"{nameof(ShakeHand)}: Reading header package."); 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; package.Bytes = buf;
string name = "Unknown"; string name = "Unknown";
if (bytesReceived == Common.PACKAGE_SIZE_EX) if (bytesReceived == Package.PACKAGE_SIZE_EX)
{ {
if (package.Type is PackageType.Clipboard or PackageType.ClipboardPush) if (package.Type is PackageType.Clipboard or PackageType.ClipboardPush)
{ {

View 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;
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,
}

View 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();
}
}

View File

@@ -67,20 +67,20 @@ internal static class DragDrop
return; return;
} }
if (wParam == Common.WM_LBUTTONDOWN) if (wParam == WM.WM_LBUTTONDOWN)
{ {
MouseDown = true; MouseDown = true;
DragMachine = MachineStuff.desMachineID; DragMachine = MachineStuff.desMachineID;
MachineStuff.dropMachineID = ID.NONE; MachineStuff.dropMachineID = ID.NONE;
Logger.LogDebug("DragDropStep01: MouseDown"); Logger.LogDebug("DragDropStep01: MouseDown");
} }
else if (wParam == Common.WM_LBUTTONUP) else if (wParam == WM.WM_LBUTTONUP)
{ {
MouseDown = false; MouseDown = false;
Logger.LogDebug("DragDropStep01: MouseUp"); Logger.LogDebug("DragDropStep01: MouseUp");
} }
if (wParam == Common.WM_RBUTTONUP && IsDropping) if (wParam == WM.WM_RBUTTONUP && IsDropping)
{ {
IsDropping = false; IsDropping = false;
Clipboard.LastIDWithClipboardData = ID.NONE; Clipboard.LastIDWithClipboardData = ID.NONE;
@@ -252,7 +252,7 @@ internal static class DragDrop
internal static void DragDropStep09(int wParam) internal static void DragDropStep09(int wParam)
{ {
if (wParam == Common.WM_MOUSEMOVE && IsDropping) if (wParam == WM.WM_MOUSEMOVE && IsDropping)
{ {
// Show/Move form // Show/Move form
Common.DoSomethingInUIThread(() => Common.DoSomethingInUIThread(() =>
@@ -260,7 +260,7 @@ internal static class DragDrop
_ = NativeMethods.PostMessage(Common.MainForm.Handle, NativeMethods.WM_SHOW_DRAG_DROP, (IntPtr)0, (IntPtr)0); _ = 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) if (IsDropping)
{ {

View 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;
}
}

View File

@@ -70,7 +70,7 @@ internal static class Event
// Check if easy mouse setting is enabled. // Check if easy mouse setting is enabled.
bool isEasyMouseEnabled = IsSwitchingByMouseEnabled(); 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); Point p = MachineStuff.MoveToMyNeighbourIfNeeded(e.X, e.Y, MachineStuff.desMachineID);
@@ -115,7 +115,7 @@ internal static class Event
Common.SkSend(MousePackage, null, false); 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); Thread.Sleep(10);
} }
@@ -265,7 +265,7 @@ internal static class Event
KeybdPackage.Kd = e; KeybdPackage.Kd = e;
KeybdPackage.DateTime = Common.GetTick(); KeybdPackage.DateTime = Common.GetTick();
Common.SkSend(KeybdPackage, null, false); 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); Thread.Sleep(10);
} }

View File

@@ -290,7 +290,7 @@ internal static class Helper
return; return;
} }
if (!Common.IsMyDesktopActive()) if (!WinAPI.IsMyDesktopActive())
{ {
return; return;
} }
@@ -314,7 +314,7 @@ internal static class Helper
_ = Launch.CreateProcessInInputDesktopSession( _ = Launch.CreateProcessInInputDesktopSession(
$"\"{Path.GetDirectoryName(Application.ExecutablePath)}\\{HelperProcessName}.exe\"", $"\"{Path.GetDirectoryName(Application.ExecutablePath)}\\{HelperProcessName}.exe\"",
string.Empty, string.Empty,
Common.GetInputDesktop(), WinAPI.GetInputDesktop(),
0); 0);
Clipboard.HasSwitchedMachineSinceLastCopy = true; Clipboard.HasSwitchedMachineSinceLastCopy = true;
@@ -379,7 +379,7 @@ internal static class Helper
log += "=============================================================================================================================\r\n"; log += "=============================================================================================================================\r\n";
log += $"{Application.ProductName} version {Application.ProductVersion}\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 += $"{Common.MachineName}/{Common.MachineID}/{Common.DesMachineID}\r\n";
log += $"Id: {Setting.Values.DeviceId}\r\n"; log += $"Id: {Setting.Values.DeviceId}\r\n";
log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n"; log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n";

View 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,
}

View File

@@ -89,23 +89,23 @@ internal static class InitAndCleanup
internal static void Init() internal static void Init()
{ {
_ = Helper.GetUserName(); _ = Helper.GetUserName();
Common.GeneratedKey = true; Encryption.GeneratedKey = true;
try try
{ {
Common.MyKey = Setting.Values.MyKey; Encryption.MyKey = Setting.Values.MyKey;
int tmp = Setting.Values.MyKeyDaysToExpire; int tmp = Setting.Values.MyKeyDaysToExpire;
} }
catch (FormatException e) catch (FormatException e)
{ {
Common.KeyCorrupted = true; Encryption.KeyCorrupted = true;
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
Logger.Log(e.Message); Logger.Log(e.Message);
} }
catch (CryptographicException e) catch (CryptographicException e)
{ {
Common.KeyCorrupted = true; Encryption.KeyCorrupted = true;
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
Logger.Log(e.Message); Logger.Log(e.Message);
} }
@@ -127,14 +127,14 @@ internal static class InitAndCleanup
bool dummy = Setting.Values.DrawMouseEx; bool dummy = Setting.Values.DrawMouseEx;
Common.Is64bitOS = IntPtr.Size == 8; Common.Is64bitOS = IntPtr.Size == 8;
Common.tcpPort = Setting.Values.TcpPort; Common.tcpPort = Setting.Values.TcpPort;
Common.GetScreenConfig(); WinAPI.GetScreenConfig();
Common.PackageSent = new PackageMonitor(0); Package.PackageSent = new PackageMonitor(0);
Common.PackageReceived = new PackageMonitor(0); Package.PackageReceived = new PackageMonitor(0);
SetupMachineNameAndID(); SetupMachineNameAndID();
Common.InitEncryption(); Encryption.InitEncryption();
CreateHelperThreads(); CreateHelperThreads();
SystemEvents.DisplaySettingsChanged += new EventHandler(Common.SystemEvents_DisplaySettingsChanged); SystemEvents.DisplaySettingsChanged += new EventHandler(WinAPI.SystemEvents_DisplaySettingsChanged);
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged); NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged); SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
PleaseReopenSocket = 9; PleaseReopenSocket = 9;
@@ -220,7 +220,7 @@ internal static class InitAndCleanup
lastReleaseAllKeysCall = Common.GetTick(); lastReleaseAllKeysCall = Common.GetTick();
KEYBDDATA kd; KEYBDDATA kd;
kd.dwFlags = (int)Common.LLKHF.UP; kd.dwFlags = (int)WM.LLKHF.UP;
VK[] keys = new VK[] VK[] keys = new VK[]
{ {
@@ -266,7 +266,7 @@ internal static class InitAndCleanup
true); true);
} }
if (!Common.IsMyDesktopActive()) if (!WinAPI.IsMyDesktopActive())
{ {
PleaseReopenSocket = 0; PleaseReopenSocket = 0;
} }

View 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;
}

View File

@@ -121,52 +121,52 @@ internal static class Logger
{ {
string log; string log;
if (!lastPackageSent.Equals(Common.PackageSent)) if (!lastPackageSent.Equals(Package.PackageSent))
{ {
log = string.Format( log = string.Format(
CultureInfo.CurrentCulture, CultureInfo.CurrentCulture,
"SENT:" + HeaderSENT, "SENT:" + HeaderSENT,
Common.PackageSent.Heartbeat, Package.PackageSent.Heartbeat,
Common.PackageSent.Keyboard, Package.PackageSent.Keyboard,
Common.PackageSent.Mouse, Package.PackageSent.Mouse,
Common.PackageSent.Hello, Package.PackageSent.Hello,
Common.PackageSent.Matrix, Package.PackageSent.Matrix,
Common.PackageSent.ClipboardText, Package.PackageSent.ClipboardText,
Common.PackageSent.ClipboardImage, Package.PackageSent.ClipboardImage,
Common.PackageSent.ByeBye, Package.PackageSent.ByeBye,
Common.PackageSent.Clipboard, Package.PackageSent.Clipboard,
Common.PackageSent.ClipboardDragDrop, Package.PackageSent.ClipboardDragDrop,
Common.PackageSent.ClipboardDragDropEnd, Package.PackageSent.ClipboardDragDropEnd,
Common.PackageSent.ExplorerDragDrop, Package.PackageSent.ExplorerDragDrop,
Event.inputEventCount, Event.inputEventCount,
Common.PackageSent.Nil); Package.PackageSent.Nil);
Log(log); 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( log = string.Format(
CultureInfo.CurrentCulture, CultureInfo.CurrentCulture,
"RECEIVED:" + HeaderRECEIVED, "RECEIVED:" + HeaderRECEIVED,
Common.PackageReceived.Heartbeat, Package.PackageReceived.Heartbeat,
Common.PackageReceived.Keyboard, Package.PackageReceived.Keyboard,
Common.PackageReceived.Mouse, Package.PackageReceived.Mouse,
Common.PackageReceived.Hello, Package.PackageReceived.Hello,
Common.PackageReceived.Matrix, Package.PackageReceived.Matrix,
Common.PackageReceived.ClipboardText, Package.PackageReceived.ClipboardText,
Common.PackageReceived.ClipboardImage, Package.PackageReceived.ClipboardImage,
Common.PackageReceived.ByeBye, Package.PackageReceived.ByeBye,
Common.PackageReceived.Clipboard, Package.PackageReceived.Clipboard,
Common.PackageReceived.ClipboardDragDrop, Package.PackageReceived.ClipboardDragDrop,
Common.PackageReceived.ClipboardDragDropEnd, Package.PackageReceived.ClipboardDragDropEnd,
Common.PackageReceived.ExplorerDragDrop, Package.PackageReceived.ExplorerDragDrop,
Event.invalidPackageCount, Event.invalidPackageCount,
Common.PackageReceived.Nil, Package.PackageReceived.Nil,
Receiver.processedPackageCount, Receiver.processedPackageCount,
Receiver.skippedPackageCount); Receiver.skippedPackageCount);
Log(log); 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", "Private Mem: " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024).ToString(CultureInfo.CurrentCulture) + "KB",
sb.ToString()); 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(); log += Thread.DumpThreadsStack();
@@ -251,14 +251,18 @@ internal static class Logger
{ {
typeof(Clipboard), typeof(Clipboard),
typeof(DragDrop), typeof(DragDrop),
typeof(Encryption),
typeof(Event), typeof(Event),
typeof(InitAndCleanup), typeof(InitAndCleanup),
typeof(Helper), typeof(Helper),
typeof(Launch), typeof(Launch),
typeof(Logger), typeof(Logger),
typeof(MachineStuff), typeof(MachineStuff),
typeof(Package),
typeof(Receiver), typeof(Receiver),
typeof(Service), typeof(Service),
typeof(WinAPI),
typeof(WM),
}; };
foreach (var staticType in staticTypes) foreach (var staticType in staticTypes)
{ {
@@ -294,7 +298,7 @@ internal static class Logger
// strArr[3] = t.FullName; // strArr[3] = t.FullName;
strArr[4] = " = "; strArr[4] = " = ";
strArr[5] = objName.Equals("myKey", StringComparison.OrdinalIgnoreCase) strArr[5] = objName.Equals("myKey", StringComparison.OrdinalIgnoreCase)
? Common.GetDebugInfo(objString) ? Encryption.GetDebugInfo(objString)
: objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase) : objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase)
? string.Empty ? string.Empty
: objString : objString

View 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;
}

View File

@@ -221,9 +221,9 @@ internal static class MachineStuff
if (Setting.Values.BlockMouseAtCorners) 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) 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) internal static void ShowSetupForm(bool reopenSockets = false)
{ {
Logger.LogDebug("========== BEGIN THE SETUP EXPERIENCE ==========", true); Logger.LogDebug("========== BEGIN THE SETUP EXPERIENCE ==========", true);
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
Common.GeneratedKey = true; Encryption.GeneratedKey = true;
if (Process.GetCurrentProcess().SessionId != NativeMethods.WTSGetActiveConsoleSessionId()) if (Process.GetCurrentProcess().SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
{ {
@@ -1067,7 +1067,7 @@ internal static class MachineStuff
internal static void AssertOneInstancePerDesktopSession() 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); oneInstanceCheck = new EventWaitHandle(false, EventResetMode.ManualReset, eventName, out bool created);
if (!created) if (!created)

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// <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;
}

View 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;
}
}

View 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,
}

View File

@@ -93,7 +93,7 @@ internal static class Receiver
switch (package.Type) switch (package.Type)
{ {
case PackageType.Keyboard: case PackageType.Keyboard:
Common.PackageReceived.Keyboard++; Package.PackageReceived.Keyboard++;
if (package.Des == Common.MachineID || package.Des == ID.ALL) if (package.Des == Common.MachineID || package.Des == ID.ALL)
{ {
JustGotAKey = Common.GetTick(); JustGotAKey = Common.GetTick();
@@ -102,7 +102,7 @@ internal static class Receiver
bool nonElevated = Common.RunWithNoAdminRight && false; bool nonElevated = Common.RunWithNoAdminRight && false;
if (nonElevated && Setting.Values.OneWayControlMode) 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(); Helper.ShowOneWayModeMessage();
} }
@@ -116,7 +116,7 @@ internal static class Receiver
break; break;
case PackageType.Mouse: case PackageType.Mouse:
Common.PackageReceived.Mouse++; Package.PackageReceived.Mouse++;
if (package.Des == Common.MachineID || package.Des == ID.ALL) if (package.Des == Common.MachineID || package.Des == ID.ALL)
{ {
@@ -127,16 +127,16 @@ internal static class Receiver
// NOTE(@yuyoyuppe): disabled to drop elevation requirement // NOTE(@yuyoyuppe): disabled to drop elevation requirement
bool nonElevated = Common.RunWithNoAdminRight && false; 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 (!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(); 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; 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 (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( InputSimulation.MoveMouseRelative(
package.Md.X < 0 ? package.Md.X + Event.MOVE_MOUSE_RELATIVE : package.Md.X - Event.MOVE_MOUSE_RELATIVE, 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; break;
case PackageType.ExplorerDragDrop: case PackageType.ExplorerDragDrop:
Common.PackageReceived.ExplorerDragDrop++; Package.PackageReceived.ExplorerDragDrop++;
DragDrop.DragDropStep03(package); DragDrop.DragDropStep03(package);
break; break;
case PackageType.Heartbeat: case PackageType.Heartbeat:
case PackageType.Heartbeat_ex: 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); Common.SendPackage(ID.ALL, PackageType.Heartbeat_ex_l2);
} }
@@ -230,26 +230,26 @@ internal static class Receiver
break; break;
case PackageType.Heartbeat_ex_l2: case PackageType.Heartbeat_ex_l2:
Common.GeneratedKey = true; Encryption.GeneratedKey = true;
Setting.Values.MyKey = Common.MyKey; Setting.Values.MyKey = Encryption.MyKey;
Common.SendPackage(ID.ALL, PackageType.Heartbeat_ex_l3); Common.SendPackage(ID.ALL, PackageType.Heartbeat_ex_l3);
break; break;
case PackageType.Heartbeat_ex_l3: case PackageType.Heartbeat_ex_l3:
Common.GeneratedKey = true; Encryption.GeneratedKey = true;
Setting.Values.MyKey = Common.MyKey; Setting.Values.MyKey = Encryption.MyKey;
break; break;
case PackageType.Awake: case PackageType.Awake:
Common.PackageReceived.Heartbeat++; Package.PackageReceived.Heartbeat++;
_ = MachineStuff.AddToMachinePool(package); _ = MachineStuff.AddToMachinePool(package);
Common.HumanBeingDetected(); Common.HumanBeingDetected();
break; break;
case PackageType.Hello: case PackageType.Hello:
Common.PackageReceived.Hello++; Package.PackageReceived.Hello++;
Common.SendHeartBeat(); Common.SendHeartBeat();
string newMachine = MachineStuff.AddToMachinePool(package); string newMachine = MachineStuff.AddToMachinePool(package);
if (Setting.Values.MachineMatrixString == null) if (Setting.Values.MachineMatrixString == null)
@@ -262,16 +262,16 @@ internal static class Receiver
break; break;
case PackageType.Hi: case PackageType.Hi:
Common.PackageReceived.Hello++; Package.PackageReceived.Hello++;
break; break;
case PackageType.ByeBye: case PackageType.ByeBye:
Common.PackageReceived.ByeBye++; Package.PackageReceived.ByeBye++;
Common.ProcessByeByeMessage(package); Common.ProcessByeByeMessage(package);
break; break;
case PackageType.Clipboard: case PackageType.Clipboard:
Common.PackageReceived.Clipboard++; Package.PackageReceived.Clipboard++;
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
{ {
Clipboard.clipboardCopiedTime = Common.GetTick(); Clipboard.clipboardCopiedTime = Common.GetTick();
@@ -291,7 +291,7 @@ internal static class Receiver
break; break;
case PackageType.ClipboardCapture: case PackageType.ClipboardCapture:
Common.PackageReceived.Clipboard++; Package.PackageReceived.Clipboard++;
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
{ {
if (package.Des == Common.MachineID || package.Des == ID.ALL) if (package.Des == Common.MachineID || package.Des == ID.ALL)
@@ -304,7 +304,7 @@ internal static class Receiver
break; break;
case PackageType.CaptureScreenCommand: case PackageType.CaptureScreenCommand:
Common.PackageReceived.Clipboard++; Package.PackageReceived.Clipboard++;
if (package.Des == Common.MachineID || package.Des == ID.ALL) if (package.Des == Common.MachineID || package.Des == ID.ALL)
{ {
Common.SendImage(package.Src, Common.CaptureScreen()); Common.SendImage(package.Src, Common.CaptureScreen());
@@ -313,7 +313,7 @@ internal static class Receiver
break; break;
case PackageType.ClipboardAsk: case PackageType.ClipboardAsk:
Common.PackageReceived.ClipboardAsk++; Package.PackageReceived.ClipboardAsk++;
if (package.Des == Common.MachineID) if (package.Des == Common.MachineID)
{ {
@@ -344,17 +344,17 @@ internal static class Receiver
break; break;
case PackageType.ClipboardDragDrop: case PackageType.ClipboardDragDrop:
Common.PackageReceived.ClipboardDragDrop++; Package.PackageReceived.ClipboardDragDrop++;
DragDrop.DragDropStep08(package); DragDrop.DragDropStep08(package);
break; break;
case PackageType.ClipboardDragDropOperation: case PackageType.ClipboardDragDropOperation:
Common.PackageReceived.ClipboardDragDrop++; Package.PackageReceived.ClipboardDragDrop++;
DragDrop.DragDropStep08_2(package); DragDrop.DragDropStep08_2(package);
break; break;
case PackageType.ClipboardDragDropEnd: case PackageType.ClipboardDragDropEnd:
Common.PackageReceived.ClipboardDragDropEnd++; Package.PackageReceived.ClipboardDragDropEnd++;
DragDrop.DragDropStep12(); DragDrop.DragDropStep12();
break; break;
@@ -363,11 +363,11 @@ internal static class Receiver
Clipboard.clipboardCopiedTime = 0; Clipboard.clipboardCopiedTime = 0;
if (package.Type == PackageType.ClipboardImage) if (package.Type == PackageType.ClipboardImage)
{ {
Common.PackageReceived.ClipboardImage++; Package.PackageReceived.ClipboardImage++;
} }
else else
{ {
Common.PackageReceived.ClipboardText++; Package.PackageReceived.ClipboardText++;
} }
if (tcp != null) if (tcp != null)
@@ -390,7 +390,7 @@ internal static class Receiver
default: default:
if ((package.Type & PackageType.Matrix) == PackageType.Matrix) if ((package.Type & PackageType.Matrix) == PackageType.Matrix)
{ {
Common.PackageReceived.Matrix++; Package.PackageReceived.Matrix++;
MachineStuff.UpdateMachineMatrix(package); MachineStuff.UpdateMachineMatrix(package);
break; break;
} }

View File

@@ -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);
}
}
}

View 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,
}

View 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,
}
}

View 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);
}
}

View File

@@ -42,7 +42,7 @@ namespace MouseWithoutBorders
protected string GetSecureKey() protected string GetSecureKey()
{ {
return Common.MyKey; return Encryption.MyKey;
} }
private void BackButton_Click(object sender, EventArgs e) private void BackButton_Click(object sender, EventArgs e)

View File

@@ -89,8 +89,8 @@ namespace MouseWithoutBorders
{ {
if (GetSecureKey() != SecurityCodeField.Text) if (GetSecureKey() != SecurityCodeField.Text)
{ {
Common.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty); Encryption.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty);
SecurityCode = Common.MyKey; SecurityCode = Encryption.MyKey;
} }
MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { ComputerNameField.Text.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty }; MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { ComputerNameField.Text.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty };

View File

@@ -135,7 +135,7 @@ namespace MouseWithoutBorders
internal void UpdateKeyTextBox() internal void UpdateKeyTextBox()
{ {
_ = Helper.GetUserName(); _ = Helper.GetUserName();
textBoxEnc.Text = Common.MyKey; textBoxEnc.Text = Encryption.MyKey;
} }
private void InitAll() private void InitAll()
@@ -505,19 +505,19 @@ namespace MouseWithoutBorders
private bool UpdateKey(string newKey) private bool UpdateKey(string newKey)
{ {
if (!Common.IsKeyValid(newKey, out string rv)) if (!Encryption.IsKeyValid(newKey, out string rv))
{ {
ShowKeyErrorMsg(rv); ShowKeyErrorMsg(rv);
return false; return false;
} }
if (!newKey.Equals(Common.MyKey, StringComparison.OrdinalIgnoreCase)) if (!newKey.Equals(Encryption.MyKey, StringComparison.OrdinalIgnoreCase))
{ {
Common.MyKey = newKey; Encryption.MyKey = newKey;
Common.GeneratedKey = false; Encryption.GeneratedKey = false;
} }
Common.MagicNumber = Common.Get24BitHash(Common.MyKey); Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
return true; return true;
} }
@@ -1116,10 +1116,10 @@ namespace MouseWithoutBorders
if (MessageBox.Show(message, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes) if (MessageBox.Show(message, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
{ {
Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
textBoxEnc.Text = Common.MyKey; textBoxEnc.Text = Encryption.MyKey;
checkBoxShowKey.Checked = true; checkBoxShowKey.Checked = true;
Common.GeneratedKey = true; Encryption.GeneratedKey = true;
ButtonOK_Click(null, null); ButtonOK_Click(null, null);
Common.ShowToolTip("New security key was generated, update other machines to the same key.", 10000, ToolTipIcon.Info, false); Common.ShowToolTip("New security key was generated, update other machines to the same key.", 10000, ToolTipIcon.Info, false);
} }

View File

@@ -318,7 +318,7 @@ namespace MouseWithoutBorders
try try
{ {
if (!Common.IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId()) if (!WinAPI.IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
{ {
myDesktopNotActive = true; myDesktopNotActive = true;
@@ -348,7 +348,7 @@ namespace MouseWithoutBorders
Common.Hook?.ResetLastSwitchKeys(); Common.Hook?.ResetLastSwitchKeys();
}); });
Common.CheckForDesktopSwitchEvent(true); WinAPI.CheckForDesktopSwitchEvent(true);
} }
} }
else else
@@ -369,21 +369,21 @@ namespace MouseWithoutBorders
if (myDesktopNotActive) if (myDesktopNotActive)
{ {
myDesktopNotActive = false; myDesktopNotActive = false;
Common.MyKey = Setting.Values.MyKey; Encryption.MyKey = Setting.Values.MyKey;
} }
MachineStuff.UpdateMachinePoolStringSetting(); MachineStuff.UpdateMachinePoolStringSetting();
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && (Setting.Values.FirstRun || Common.KeyCorrupted)) if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && (Setting.Values.FirstRun || Encryption.KeyCorrupted))
{ {
if (!shownSetupFormOneTime) if (!shownSetupFormOneTime)
{ {
shownSetupFormOneTime = true; shownSetupFormOneTime = true;
MachineStuff.ShowMachineMatrix(); 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."; string msg = "The security key is corrupted for some reason, please re-setup.";
MessageBox.Show(msg, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBox.Show(msg, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
} }
@@ -490,9 +490,9 @@ namespace MouseWithoutBorders
if (count == 600) if (count == 600)
{ {
if (!Common.GeneratedKey) if (!Encryption.GeneratedKey)
{ {
Common.MyKey = Setting.Values.MyKey; Encryption.MyKey = Setting.Values.MyKey;
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) 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); 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; int myKeyDaysToExpire = Setting.Values.MyKeyDaysToExpire;
@@ -531,7 +531,7 @@ namespace MouseWithoutBorders
#if SHOW_ON_WINLOGON #if SHOW_ON_WINLOGON
// if (Common.RunOnLogonDesktop) ShowMouseWithoutBordersUiOnWinLogonDesktop(false); // if (Common.RunOnLogonDesktop) ShowMouseWithoutBordersUiOnWinLogonDesktop(false);
#endif #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. 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; 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); Logger.TelemetryLogTrace($"{Helper.HELPER_FORM_TEXT} not found: {rv}", SeverityLevel.Warning);
} }

View File

@@ -46,79 +46,6 @@ avgSendTime = 0
maxSendTime = 0 maxSendTime = 0
totalSendCount = 0 totalSendCount = 0
totalSendTime = 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 <IpcChannelCreated>k__BackingField = False
TOGGLE_ICONS_SIZE = 4 TOGGLE_ICONS_SIZE = 4
ICON_ONE = 0 ICON_ONE = 0
@@ -128,34 +55,6 @@ ICON_BIG_CLIPBOARD = 3
ICON_ERROR = 4 ICON_ERROR = 4
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999 JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
NETWORK_STREAM_BUF_SIZE = 1048576 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] [Clipboard]
=============== ===============
Comma = System.Char[] Comma = System.Char[]
@@ -193,16 +92,51 @@ dragDropStep05ExCalledByIpc = 0
isDropping = False isDropping = False
dragMachine = NONE dragMachine = NONE
<MouseDown>k__BackingField = False <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] [Event]
=============== ===============
KeybdPackage = MouseWithoutBorders.DATA KeybdPackage = MouseWithoutBorders.Core.DATA
--Type = 0 --Type = 0
--Id = 0 --Id = 0
--Src = NONE --Src = NONE
--Des = NONE --Des = NONE
--DateTime = 0 --DateTime = 0
--Kd = MouseWithoutBorders.KEYBDDATA --Kd = MouseWithoutBorders.Core.KEYBDDATA
--Md = MouseWithoutBorders.MOUSEDATA --Md = MouseWithoutBorders.Core.MOUSEDATA
--Machine1 = NONE --Machine1 = NONE
--Machine2 = NONE --Machine2 = NONE
--Machine3 = NONE --Machine3 = NONE
@@ -212,14 +146,14 @@ KeybdPackage = MouseWithoutBorders.DATA
--machineNameP2 = 0 --machineNameP2 = 0
--machineNameP3 = 0 --machineNameP3 = 0
--machineNameP4 = 0 --machineNameP4 = 0
MousePackage = MouseWithoutBorders.DATA MousePackage = MouseWithoutBorders.Core.DATA
--Type = 0 --Type = 0
--Id = 0 --Id = 0
--Src = NONE --Src = NONE
--Des = NONE --Des = NONE
--DateTime = 0 --DateTime = 0
--Kd = MouseWithoutBorders.KEYBDDATA --Kd = MouseWithoutBorders.Core.KEYBDDATA
--Md = MouseWithoutBorders.MOUSEDATA --Md = MouseWithoutBorders.Core.MOUSEDATA
--Machine1 = NONE --Machine1 = NONE
--Machine2 = NONE --Machine2 = NONE
--Machine3 = NONE --Machine3 = NONE
@@ -296,7 +230,7 @@ LogCounter = Concurrent.ConcurrentDictionary`2[System.String,32]
allLogsIndex = 0 allLogsIndex = 0
lastHour = 0 lastHour = 0
exceptionCount = 0 exceptionCount = 0
lastPackageSent = MouseWithoutBorders.PackageMonitor lastPackageSent = MouseWithoutBorders.Core.PackageMonitor
--Keyboard = 0 --Keyboard = 0
--Mouse = 0 --Mouse = 0
--Heartbeat = 0 --Heartbeat = 0
@@ -311,7 +245,7 @@ lastPackageSent = MouseWithoutBorders.PackageMonitor
--ClipboardAsk = 0 --ClipboardAsk = 0
--ExplorerDragDrop = 0 --ExplorerDragDrop = 0
--Nil = 0 --Nil = 0
lastPackageReceived = MouseWithoutBorders.PackageMonitor lastPackageReceived = MouseWithoutBorders.Core.PackageMonitor
--Keyboard = 0 --Keyboard = 0
--Mouse = 0 --Mouse = 0
--Heartbeat = 0 --Heartbeat = 0
@@ -366,6 +300,42 @@ MAX_SOCKET = 8
HEARTBEAT_TIMEOUT = 1500000 HEARTBEAT_TIMEOUT = 1500000
SKIP_PIXELS = 1 SKIP_PIXELS = 1
JUMP_PIXELS = 2 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] [Receiver]
=============== ===============
QUEUE_SIZE = 50 QUEUE_SIZE = 50
@@ -436,3 +406,41 @@ lastStartServiceTime = ????????????
--MinValue = 01/01/0001 00:00:00 --MinValue = 01/01/0001 00:00:00
--MaxValue = 31/12/9999 23:59:59 --MaxValue = 31/12/9999 23:59:59
--UnixEpoch = 01/01/1970 00:00:00 --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

View File

@@ -11,15 +11,15 @@ public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
{ {
private readonly ExtensionObject<IGalleryGridLayout> _model; private readonly ExtensionObject<IGalleryGridLayout> _model;
public bool ShowTitle { get; private set; }
public bool ShowSubtitle { get; private set; }
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout) public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
{ {
_model = new(galleryGridLayout); _model = new(galleryGridLayout);
} }
public bool ShowTitle { get; set; }
public bool ShowSubtitle { get; set; }
public void InitializeProperties() public void InitializeProperties()
{ {
var model = _model.Unsafe; var model = _model.Unsafe;

View File

@@ -6,5 +6,9 @@ namespace Microsoft.CmdPal.Core.ViewModels;
public interface IGridPropertiesViewModel public interface IGridPropertiesViewModel
{ {
bool ShowTitle { get; }
bool ShowSubtitle { get; }
void InitializeProperties(); void InitializeProperties();
} }

View File

@@ -10,10 +10,9 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels; namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ListItemViewModel(IListItem model, WeakReference<IPageContext> context) public partial class ListItemViewModel : CommandItemViewModel
: CommandItemViewModel(new(model), context)
{ {
public new ExtensionObject<IListItem> Model { get; } = new(model); public new ExtensionObject<IListItem> Model { get; }
public List<TagViewModel>? Tags { get; set; } 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 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() public override void InitializeProperties()
{ {
if (IsInitialized) if (IsInitialized)
@@ -93,16 +126,18 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
switch (propertyName) switch (propertyName)
{ {
case nameof(Tags): case nameof(model.Tags):
UpdateTags(model.Tags); UpdateTags(model.Tags);
break; break;
case nameof(TextToSuggest): case nameof(model.TextToSuggest):
this.TextToSuggest = model.TextToSuggest ?? string.Empty; TextToSuggest = model.TextToSuggest ?? string.Empty;
UpdateProperty(nameof(TextToSuggest));
break; break;
case nameof(Section): case nameof(model.Section):
this.Section = model.Section ?? string.Empty; Section = model.Section ?? string.Empty;
UpdateProperty(nameof(Section));
break; break;
case nameof(Details): case nameof(model.Details):
var extensionDetails = model.Details; var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null; Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
Details?.InitializeProperties(); Details?.InitializeProperties();
@@ -110,16 +145,24 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
UpdateProperty(nameof(HasDetails)); UpdateProperty(nameof(HasDetails));
UpdateShowDetailsCommand(); UpdateShowDetailsCommand();
break; break;
case nameof(MoreCommands): case nameof(model.MoreCommands):
UpdateProperty(nameof(MoreCommands));
AddShowDetailsCommands(); AddShowDetailsCommands();
break; break;
case nameof(Title): case nameof(model.Title):
case nameof(Subtitle): UpdateProperty(nameof(Title));
UpdateShowsTitle();
UpdateAccessibleName(); UpdateAccessibleName();
break; 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? // 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. // many COM exception issues.
Tags = [.. newTags]; Tags = [.. newTags];
UpdateProperty(nameof(Tags)); // We're already in UI thread, so just raise the events
UpdateProperty(nameof(HasTags)); 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() protected override void UnsafeCleanup()
{ {
base.UnsafeCleanup(); base.UnsafeCleanup();

View File

@@ -24,8 +24,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
// Observable from MVVM Toolkit will auto create public properties that use INotifyPropertyChange change // Observable from MVVM Toolkit will auto create public properties that use INotifyPropertyChange change
// https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/observablegroupedcollections for grouping support // https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/observablegroupedcollections for grouping support
[ObservableProperty] public ObservableCollection<ListItemViewModel> FilteredItems { get; } = [];
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
public FiltersViewModel? Filters { get; set; } 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 // TODO we can probably further optimize this by also keeping a
// HashSet of every ExtensionObject we currently have, and only // HashSet of every ExtensionObject we currently have, and only
// building new viewmodels for the ones we haven't already built. // 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) foreach (var item in newItems)
{ {
// Check for cancellation during item processing // 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 an item fails to load, silently ignore it.
if (viewModel.SafeFastInit()) if (viewModel.SafeFastInit())
{ {
viewModel.LayoutShowsTitle = showsTitle;
viewModel.LayoutShowsSubtitle = showsSubtitle;
newViewModels.Add(viewModel); newViewModels.Add(viewModel);
} }
} }
@@ -583,6 +586,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
GridProperties = LoadGridPropertiesViewModel(model.GridProperties); GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
GridProperties?.InitializeProperties(); GridProperties?.InitializeProperties();
UpdateProperty(nameof(GridProperties)); UpdateProperty(nameof(GridProperties));
ApplyLayoutToItems();
ShowDetails = model.ShowDetails; ShowDetails = model.ShowDetails;
UpdateProperty(nameof(ShowDetails)); UpdateProperty(nameof(ShowDetails));
@@ -608,22 +612,15 @@ public partial class ListViewModel : PageViewModel, IDisposable
model.ItemsChanged += Model_ItemsChanged; 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); IMediumGridLayout mediumGridLayout => new MediumGridPropertiesViewModel(mediumGridLayout),
} IGalleryGridLayout galleryGridLayout => new GalleryGridPropertiesViewModel(galleryGridLayout),
else if (gridProperties is IGalleryGridLayout galleryGridLayout) ISmallGridLayout smallGridLayout => new SmallGridPropertiesViewModel(smallGridLayout),
{ _ => null,
return new GalleryGridPropertiesViewModel(galleryGridLayout); };
}
else if (gridProperties is ISmallGridLayout smallGridLayout)
{
return new SmallGridPropertiesViewModel(smallGridLayout);
}
return null;
} }
public void LoadMoreIfNeeded() public void LoadMoreIfNeeded()
@@ -685,6 +682,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
GridProperties = LoadGridPropertiesViewModel(model.GridProperties); GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
GridProperties?.InitializeProperties(); GridProperties?.InitializeProperties();
UpdateProperty(nameof(IsGridView)); UpdateProperty(nameof(IsGridView));
ApplyLayoutToItems();
break; break;
case nameof(ShowDetails): case nameof(ShowDetails):
ShowDetails = model.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() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View File

@@ -11,13 +11,15 @@ public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
{ {
private readonly ExtensionObject<IMediumGridLayout> _model; private readonly ExtensionObject<IMediumGridLayout> _model;
public bool ShowTitle { get; private set; }
public bool ShowSubtitle => false;
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout) public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
{ {
_model = new(mediumGridLayout); _model = new(mediumGridLayout);
} }
public bool ShowTitle { get; set; }
public void InitializeProperties() public void InitializeProperties()
{ {
var model = _model.Unsafe; var model = _model.Unsafe;

View File

@@ -11,6 +11,10 @@ public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
{ {
private readonly ExtensionObject<ISmallGridLayout> _model; private readonly ExtensionObject<ISmallGridLayout> _model;
public bool ShowTitle => false;
public bool ShowSubtitle => false;
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout) public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
{ {
_model = new(smallGridLayout); _model = new(smallGridLayout);

View File

@@ -36,6 +36,7 @@ public partial class MainListPage : DynamicListPage,
"com.microsoft.cmdpal.builtin.websearch", "com.microsoft.cmdpal.builtin.websearch",
"com.microsoft.cmdpal.builtin.windowssettings", "com.microsoft.cmdpal.builtin.windowssettings",
"com.microsoft.cmdpal.builtin.datetime", "com.microsoft.cmdpal.builtin.datetime",
"com.microsoft.cmdpal.builtin.remotedesktop",
]; ];
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
@@ -160,7 +161,7 @@ public partial class MainListPage : DynamicListPage,
{ {
return _tlcManager return _tlcManager
.TopLevelCommands .TopLevelCommands
.Where(tlc => !string.IsNullOrEmpty(tlc.Title)) .Where(tlc => !tlc.IsFallback && !string.IsNullOrEmpty(tlc.Title))
.ToArray(); .ToArray();
} }
} }

View File

@@ -6,7 +6,9 @@ using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Settings; using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation; using Windows.Foundation;
@@ -15,6 +17,8 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class SettingsModel : ObservableObject public partial class SettingsModel : ObservableObject
{ {
private const string DeprecatedHotkeyGoesHomeKey = "HotkeyGoesHome";
[JsonIgnore] [JsonIgnore]
public static readonly string FilePath; public static readonly string FilePath;
@@ -30,8 +34,6 @@ public partial class SettingsModel : ObservableObject
public bool ShowAppDetails { get; set; } public bool ShowAppDetails { get; set; }
public bool HotkeyGoesHome { get; set; }
public bool BackspaceGoesBack { get; set; } public bool BackspaceGoesBack { get; set; }
public bool SingleClickActivates { get; set; } public bool SingleClickActivates { get; set; }
@@ -56,6 +58,8 @@ public partial class SettingsModel : ObservableObject
public WindowPosition? LastWindowPosition { get; set; } public WindowPosition? LastWindowPosition { get; set; }
public TimeSpan AutoGoHomeInterval { get; set; } = Timeout.InfiniteTimeSpan;
// END SETTINGS // END SETTINGS
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@@ -98,12 +102,29 @@ public partial class SettingsModel : ObservableObject
{ {
// Read the JSON content from the file // Read the JSON content from the file
var jsonContent = File.ReadAllText(FilePath); 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) catch (Exception ex)
{ {
@@ -113,6 +134,51 @@ public partial class SettingsModel : ObservableObject
return new(); 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) public static void SaveSettings(SettingsModel model)
{ {
if (string.IsNullOrEmpty(FilePath)) if (string.IsNullOrEmpty(FilePath))
@@ -139,6 +205,9 @@ public partial class SettingsModel : ObservableObject
savedSettings[item.Key] = item.Value?.DeepClone(); savedSettings[item.Key] = item.Value?.DeepClone();
} }
// Remove deprecated keys
savedSettings.Remove(DeprecatedHotkeyGoesHomeKey);
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options); var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options);
File.WriteAllText(FilePath, serialized); File.WriteAllText(FilePath, serialized);

View File

@@ -11,6 +11,19 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class SettingsViewModel : INotifyPropertyChanged 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 SettingsModel _settings;
private readonly IServiceProvider _serviceProvider; 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 public bool BackspaceGoesBack
{ {
get => _settings.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 ObservableCollection<ProviderSettingsViewModel> CommandProviders { get; } = [];
public SettingsExtensionsViewModel Extensions { get; } public SettingsExtensionsViewModel Extensions { get; }

View File

@@ -13,6 +13,7 @@ using Microsoft.CmdPal.Ext.Calc;
using Microsoft.CmdPal.Ext.ClipboardHistory; using Microsoft.CmdPal.Ext.ClipboardHistory;
using Microsoft.CmdPal.Ext.Indexer; using Microsoft.CmdPal.Ext.Indexer;
using Microsoft.CmdPal.Ext.Registry; using Microsoft.CmdPal.Ext.Registry;
using Microsoft.CmdPal.Ext.RemoteDesktop;
using Microsoft.CmdPal.Ext.Shell; using Microsoft.CmdPal.Ext.Shell;
using Microsoft.CmdPal.Ext.System; using Microsoft.CmdPal.Ext.System;
using Microsoft.CmdPal.Ext.TimeDate; using Microsoft.CmdPal.Ext.TimeDate;
@@ -151,6 +152,7 @@ public partial class App : Application
services.AddSingleton<ICommandProvider, BuiltInsCommandProvider>(); services.AddSingleton<ICommandProvider, BuiltInsCommandProvider>();
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>(); services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>(); services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
services.AddSingleton<ICommandProvider, RemoteDesktopCommandProvider>();
// Models // Models
services.AddSingleton<TopLevelCommandManager>(); services.AddSingleton<TopLevelCommandManager>();

View 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="&#xE7BA;" />
<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="&#xEA39;" />
<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="&#xE7BA;" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="ErrorLog">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SeverityIcon" Storyboard.TargetProperty="Glyph">
<DiscreteObjectKeyFrame KeyTime="0" Value="&#xEA39;" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>

View File

@@ -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;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using 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,
};
}
}

View File

@@ -20,21 +20,12 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject) protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
{ {
DataTemplate? dataTemplate = Medium; return GridProperties switch
if (GridProperties is SmallGridPropertiesViewModel)
{ {
dataTemplate = Small; SmallGridPropertiesViewModel => Small,
} MediumGridPropertiesViewModel => Medium,
else if (GridProperties is MediumGridPropertiesViewModel) GalleryGridPropertiesViewModel => Gallery,
{ _ => Medium,
dataTemplate = Medium; };
}
else if (GridProperties is GalleryGridPropertiesViewModel)
{
dataTemplate = Gallery;
}
return dataTemplate;
} }
} }

View File

@@ -5,33 +5,151 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI" xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels" xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls" xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:help="using:Microsoft.CmdPal.UI.Helpers" xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:local="using:Microsoft.CmdPal.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
x:Name="PageRoot" x:Name="PageRoot"
Background="Transparent" Background="Transparent"
DataContext="{x:Bind ViewModel, Mode=OneWay}" DataContext="{x:Bind ViewModel, Mode=OneWay}"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources> <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 GridViewItemCornerRadius is the corner radius defined in GridView template; make
x:Key="StringVisibilityConverter" it bigger to match the radii of the gallery
EmptyValue="Collapsed" -->
NotEmptyValue="Visible" /> <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"> <DataTemplate x:Key="TagTemplate" x:DataType="coreViewModels:TagViewModel">
<cpcontrols:Tag <cpcontrols:Tag
@@ -48,10 +166,17 @@
x:Key="GridItemTemplateSelector" x:Key="GridItemTemplateSelector"
x:DataType="coreViewModels:ListItemViewModel" x:DataType="coreViewModels:ListItemViewModel"
Gallery="{StaticResource GalleryGridItemViewModelTemplate}" Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
GridProperties="{x:Bind ViewModel.GridProperties}" GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
Medium="{StaticResource MediumGridItemViewModelTemplate}" Medium="{StaticResource MediumGridItemViewModelTemplate}"
Small="{StaticResource SmallGridItemViewModelTemplate}" /> 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 --> <!-- https://learn.microsoft.com/windows/apps/design/controls/itemsview#specify-the-look-of-the-items -->
<DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel"> <DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<Grid <Grid
@@ -94,7 +219,7 @@
Text="{x:Bind Subtitle, Mode=OneWay}" Text="{x:Bind Subtitle, Mode=OneWay}"
TextTrimming="CharacterEllipsis" TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" TextWrapping="NoWrap"
Visibility="{x:Bind Subtitle, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}" /> Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
</StackPanel> </StackPanel>
<ItemsControl <ItemsControl
@@ -124,11 +249,11 @@
Padding="8,16" Padding="8,16"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title}" AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
BorderThickness="0" BorderThickness="0"
CornerRadius="8" CornerRadius="{StaticResource SmallGridViewItemCornerRadius}"
Orientation="Vertical" Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title}"> ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
<cpcontrols:IconBox <cpcontrols:IconBox
x:Name="GridIconBorder" x:Name="GridIconBorder"
@@ -145,23 +270,22 @@
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel"> <DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<StackPanel <Grid
Width="100" Width="100"
Height="100" Height="100"
Padding="8,16" Padding="8"
HorizontalAlignment="Center" AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
VerticalAlignment="Center" CornerRadius="{StaticResource MediumGridViewItemCornerRadius}"
AutomationProperties.Name="{x:Bind Title}" ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
BorderThickness="0" <Grid.RowDefinitions>
CornerRadius="8" <RowDefinition Height="*" />
Orientation="Vertical" <RowDefinition Height="Auto" />
ToolTipService.ToolTip="{x:Bind Title}"> </Grid.RowDefinitions>
<cpcontrols:IconBox <cpcontrols:IconBox
x:Name="GridIconBorder" x:Name="GridIconBorder"
Grid.Row="0"
Width="36" Width="36"
Height="36" Height="36"
Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
CharacterSpacing="12" CharacterSpacing="12"
@@ -169,21 +293,20 @@
Foreground="{ThemeResource TextFillColorPrimary}" Foreground="{ThemeResource TextFillColorPrimary}"
SourceKey="{x:Bind Icon, Mode=OneWay}" SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" /> SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock <TextBlock
x:Name="TitleTextBlock" x:Name="TitleTextBlock"
MaxHeight="40" Grid.Row="1"
Margin="0,8,0,4" Height="32"
Margin="0,8,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center"
CharacterSpacing="12" CharacterSpacing="12"
FontSize="12" FontSize="12"
Text="{x:Bind Title}" Text="{x:Bind Title, Mode=OneWay}"
TextAlignment="Center" TextAlignment="Center"
TextTrimming="WordEllipsis" TextTrimming="WordEllipsis"
TextWrapping="Wrap" TextWrapping="Wrap"
Visibility="{Binding ElementName=PageRoot, Path=DataContext.GridProperties.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{x:Bind LayoutShowsTitle, Mode=OneWay}" />
</StackPanel> </Grid>
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel"> <DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
@@ -193,11 +316,11 @@
Padding="0" Padding="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title}" AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
BorderThickness="0" BorderThickness="0"
CornerRadius="4" CornerRadius="{StaticResource GalleryGridViewItemRadius}"
Orientation="Vertical" Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title}"> ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
<Grid <Grid
Width="160" Width="160"
@@ -205,12 +328,8 @@
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
CornerRadius="4"> CornerRadius="{StaticResource GalleryGridViewItemRadius}">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Viewbox <Viewbox
Grid.Row="1"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Stretch="UniformToFill" Stretch="UniformToFill"
StretchDirection="Both"> StretchDirection="Both">
@@ -222,35 +341,39 @@
</Viewbox> </Viewbox>
</Grid> </Grid>
<StackPanel Padding="4" Orientation="Vertical"> <StackPanel
Padding="4"
Orientation="Vertical"
Spacing="4"
Visibility="{x:Bind help:BindTransformers.VisibleWhenAny(ShowTitle, ShowSubtitle)}">
<TextBlock <TextBlock
x:Name="TitleTextBlock" x:Name="TitleTextBlock"
MaxWidth="152" MaxWidth="152"
MaxHeight="40" MaxHeight="40"
Margin="0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
CharacterSpacing="12" CharacterSpacing="12"
FontSize="14" FontSize="14"
Foreground="{ThemeResource TextFillColorPrimary}" Foreground="{ThemeResource TextFillColorPrimary}"
Text="{x:Bind Title}" Text="{x:Bind Title, Mode=OneWay}"
TextAlignment="Center" TextAlignment="Center"
TextTrimming="WordEllipsis" TextTrimming="WordEllipsis"
TextWrapping="NoWrap" /> TextWrapping="NoWrap"
Visibility="{x:Bind ShowTitle, Mode=OneWay}" />
<TextBlock <TextBlock
x:Name="SubTitleTextBlock" x:Name="SubTitleTextBlock"
MaxWidth="152" MaxWidth="152"
MaxHeight="40" MaxHeight="40"
Margin="0,4,0,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
CharacterSpacing="11" CharacterSpacing="11"
FontSize="11" FontSize="11"
Foreground="{ThemeResource TextFillColorTertiary}" Foreground="{ThemeResource TextFillColorTertiary}"
Text="{x:Bind Subtitle}" Text="{x:Bind Subtitle, Mode=OneWay}"
TextAlignment="Center" TextAlignment="Center"
TextTrimming="WordEllipsis" TextTrimming="WordEllipsis"
TextWrapping="NoWrap" /> TextWrapping="NoWrap"
Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
@@ -295,6 +418,7 @@
IsDoubleTapEnabled="True" IsDoubleTapEnabled="True"
IsItemClickEnabled="True" IsItemClickEnabled="True"
ItemClick="Items_ItemClick" ItemClick="Items_ItemClick"
ItemContainerStyleSelector="{StaticResource GridItemContainerStyleSelector}"
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}" ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="Items_RightTapped" RightTapped="Items_RightTapped"
@@ -302,6 +426,7 @@
<GridView.ItemContainerTransitions> <GridView.ItemContainerTransitions>
<TransitionCollection /> <TransitionCollection />
</GridView.ItemContainerTransitions> </GridView.ItemContainerTransitions>
<GridView.ItemContainerStyle />
</GridView> </GridView>
</controls:Case> </controls:Case>
</controls:SwitchPresenter> </controls:SwitchPresenter>

View File

@@ -15,4 +15,7 @@ internal static class BindTransformers
public static Visibility EmptyOrWhitespaceToCollapsed(string? input) public static Visibility EmptyOrWhitespaceToCollapsed(string? input)
=> string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible; => string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible;
public static Visibility VisibleWhenAny(bool value1, bool value2)
=> (value1 || value2) ? Visibility.Visible : Visibility.Collapsed;
} }

View 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;
}

View File

@@ -14,5 +14,7 @@
Activated="MainWindow_Activated" Activated="MainWindow_Activated"
Closed="MainWindow_Closed" Closed="MainWindow_Closed"
mc:Ignorable="d"> mc:Ignorable="d">
<pages:ShellPage x:Name="RootShellPage" /> <Grid x:Name="RootElement">
<pages:ShellPage />
</Grid>
</winuiex:WindowEx> </winuiex:WindowEx>

View File

@@ -11,6 +11,7 @@ using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Services; using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Ext.ClipboardHistory.Messages; using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
using Microsoft.CmdPal.UI.Controls;
using Microsoft.CmdPal.UI.Events; using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Helpers; using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages; using Microsoft.CmdPal.UI.Messages;
@@ -58,6 +59,7 @@ public sealed partial class MainWindow : WindowEx,
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", 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 uint WM_TASKBAR_RESTART;
private readonly HWND _hwnd; private readonly HWND _hwnd;
private readonly DispatcherTimer _autoGoHomeTimer;
private readonly WNDPROC? _hotkeyWndProc; private readonly WNDPROC? _hotkeyWndProc;
private readonly WNDPROC? _originalWndProc; private readonly WNDPROC? _originalWndProc;
private readonly List<TopLevelHotkey> _hotkeys = []; private readonly List<TopLevelHotkey> _hotkeys = [];
@@ -68,6 +70,7 @@ public sealed partial class MainWindow : WindowEx,
private DesktopAcrylicController? _acrylicController; private DesktopAcrylicController? _acrylicController;
private SystemBackdropConfiguration? _configurationSource; private SystemBackdropConfiguration? _configurationSource;
private TimeSpan _autoGoHomeInterval = Timeout.InfiniteTimeSpan;
private WindowPosition _currentWindowPosition = new(); private WindowPosition _currentWindowPosition = new();
@@ -75,6 +78,9 @@ public sealed partial class MainWindow : WindowEx,
{ {
InitializeComponent(); InitializeComponent();
_autoGoHomeTimer = new DispatcherTimer();
_autoGoHomeTimer.Tick += OnAutoGoHomeTimerOnTick;
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32()); _hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
unsafe unsafe
@@ -108,7 +114,7 @@ public sealed partial class MainWindow : WindowEx,
ExtendsContentIntoTitleBar = true; ExtendsContentIntoTitleBar = true;
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed; AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
SizeChanged += WindowSizeChanged; SizeChanged += WindowSizeChanged;
RootShellPage.Loaded += RootShellPage_Loaded; RootElement.Loaded += RootElementLoaded;
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated"); WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated");
@@ -125,7 +131,7 @@ public sealed partial class MainWindow : WindowEx,
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler; App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler;
// Make sure that we update the acrylic theme when the OS theme changes // 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 // 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", () => NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () =>
@@ -141,6 +147,15 @@ public sealed partial class MainWindow : WindowEx,
HideWindow(); 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) private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
{ {
if (e.Key == VirtualKey.GoBack) if (e.Key == VirtualKey.GoBack)
@@ -151,11 +166,18 @@ public sealed partial class MainWindow : WindowEx,
private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings(); 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 // Now that our content has loaded, we can update our draggable regions
UpdateRegionsForCustomTitleBar(); 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 WindowSizeChanged(object sender, WindowSizeChangedEventArgs args) => UpdateRegionsForCustomTitleBar();
private void PositionCentered() private void PositionCentered()
@@ -220,6 +242,9 @@ public sealed partial class MainWindow : WindowEx,
App.Current.Services.GetService<TrayIconService>()!.SetupTrayIcon(settings.ShowSystemTrayIcon); App.Current.Services.GetService<TrayIconService>()!.SetupTrayIcon(settings.ShowSystemTrayIcon);
_ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen; _ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen;
_autoGoHomeInterval = settings.AutoGoHomeInterval;
_autoGoHomeTimer.Interval = _autoGoHomeInterval;
} }
// We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material // We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
@@ -279,6 +304,8 @@ public sealed partial class MainWindow : WindowEx,
private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target) private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target)
{ {
StopAutoGoHome();
var hwnd = new HWND(hwndValue != 0 ? hwndValue : _hwnd); var hwnd = new HWND(hwndValue != 0 ? hwndValue : _hwnd);
// Remember, IsIconic == "minimized", which is entirely different state // Remember, IsIconic == "minimized", which is entirely different state
@@ -533,6 +560,25 @@ public sealed partial class MainWindow : WindowEx,
// If the window was not cloaked, then leave it hidden. // If the window was not cloaked, then leave it hidden.
// Sure, it's not ideal, but at least it's not visible. // 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() private bool Cloak()
@@ -620,28 +666,28 @@ public sealed partial class MainWindow : WindowEx,
private void UpdateRegionsForCustomTitleBar() private void UpdateRegionsForCustomTitleBar()
{ {
// Specify the interactive regions of the title bar. // 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 // Get the rectangle around our XAML content. We're going to mark this
// rectangle as "Passthrough", so that the normal window operations // rectangle as "Passthrough", so that the normal window operations
// (resizing, dragging) don't apply in this space. // (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. // Reserve 16px of space at the top for dragging.
var topHeight = 16; var topHeight = 16;
var bounds = transform.TransformBounds(new Rect( var bounds = transform.TransformBounds(new Rect(
0, 0,
topHeight, topHeight,
RootShellPage.ActualWidth, RootElement.ActualWidth,
RootShellPage.ActualHeight)); RootElement.ActualHeight));
var contentRect = GetRect(bounds, scaleAdjustment); var contentRect = GetRect(bounds, scaleAdjustment);
var rectArray = new RectInt32[] { contentRect }; var rectArray = new RectInt32[] { contentRect };
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id); var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray); nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
// Add a drag-able region on top // Add a drag-able region on top
var w = RootShellPage.ActualWidth; var w = RootElement.ActualWidth;
_ = RootShellPage.ActualHeight; _ = RootElement.ActualHeight;
var dragSides = new RectInt32[] var dragSides = new RectInt32[]
{ {
GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall

View File

@@ -15,6 +15,7 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<LangVersion>preview</LangVersion>
<Version>$(CmdPalVersion)</Version> <Version>$(CmdPalVersion)</Version>
@@ -25,10 +26,10 @@
</PropertyGroup> </PropertyGroup>
<!-- For debugging purposes, uncomment this block to enable AOT builds --> <!-- For debugging purposes, uncomment this block to enable AOT builds -->
<!-- <PropertyGroup> <!--<PropertyGroup>
<EnableCmdPalAOT>true</EnableCmdPalAOT> <EnableCmdPalAOT>true</EnableCmdPalAOT>
<CIBuild>true</CIBuild> <GeneratePackageLocally>true</GeneratePackageLocally>
</PropertyGroup> --> </PropertyGroup>-->
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'"> <PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
@@ -37,7 +38,7 @@
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(CIBuild)'=='true'"> <PropertyGroup Condition="'$(CIBuild)' == 'true' or '$(GeneratePackageLocally)' == 'true'">
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild> <GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
<AppxBundle>Never</AppxBundle> <AppxBundle>Never</AppxBundle>
<AppxPackageTestDir>$(OutputPath)\AppPackages\Microsoft.CmdPal.UI_$(Version)_Test\</AppxPackageTestDir> <AppxPackageTestDir>$(OutputPath)\AppPackages\Microsoft.CmdPal.UI_$(Version)_Test\</AppxPackageTestDir>
@@ -66,6 +67,7 @@
<ItemGroup> <ItemGroup>
<None Remove="Controls\ActionBar.xaml" /> <None Remove="Controls\ActionBar.xaml" />
<None Remove="Controls\DevRibbon.xaml" />
<None Remove="Controls\KeyVisual\KeyCharPresenter.xaml" /> <None Remove="Controls\KeyVisual\KeyCharPresenter.xaml" />
<None Remove="Controls\SearchBar.xaml" /> <None Remove="Controls\SearchBar.xaml" />
<None Remove="IsEnabledTextBlock.xaml" /> <None Remove="IsEnabledTextBlock.xaml" />
@@ -118,6 +120,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" /> <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.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.System\Microsoft.CmdPal.Ext.System.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WebSearch\Microsoft.CmdPal.Ext.WebSearch.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" /> <ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
@@ -167,6 +170,9 @@
<Page Update="Controls\SearchBar.xaml"> <Page Update="Controls\SearchBar.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Update="Controls\DevRibbon.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\TextBox.xaml"> <Page Update="Styles\TextBox.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime> <XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page> </Page>
@@ -234,4 +240,24 @@
</ItemGroup> </ItemGroup>
<!-- </AdaptiveCardsWorkaround> --> <!-- </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> </Project>

View File

@@ -345,7 +345,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// Depending on the settings, either // Depending on the settings, either
// * Go home, or // * Go home, or
// * Select the search text (if we should remain open on this page) // * Select the search text (if we should remain open on this page)
if (settings.HotkeyGoesHome) if (settings.AutoGoHomeInterval == TimeSpan.Zero)
{ {
GoHome(false); GoHome(false);
} }

View File

@@ -51,8 +51,18 @@
</controls:SettingsCard> </controls:SettingsCard>
</controls:SettingsExpander.Items> </controls:SettingsExpander.Items>
</controls:SettingsExpander> </controls:SettingsExpander>
<controls:SettingsCard x:Uid="Settings_GeneralPage_GoHome_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE80F;}"> <controls:SettingsCard x:Uid="Settings_GeneralPage_AutoGoHome_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE80F;}">
<ToggleSwitch IsOn="{x:Bind viewModel.HotkeyGoesHome, Mode=TwoWay}" /> <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>
<controls:SettingsCard x:Uid="Settings_GeneralPage_HighlightSearch_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE933;}"> <controls:SettingsCard x:Uid="Settings_GeneralPage_HighlightSearch_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE933;}">
<ToggleSwitch IsOn="{x:Bind viewModel.HighlightSearchOnActivate, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind viewModel.HighlightSearchOnActivate, Mode=TwoWay}" />

View File

@@ -20,13 +20,15 @@
<winuiex:WindowEx.SystemBackdrop> <winuiex:WindowEx.SystemBackdrop>
<MicaBackdrop /> <MicaBackdrop />
</winuiex:WindowEx.SystemBackdrop> </winuiex:WindowEx.SystemBackdrop>
<Grid> <Grid x:Name="RootElement">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TitleBar <TitleBar
x:Name="AppTitleBar" x:Name="AppTitleBar"
BackRequested="TitleBar_BackRequested"
IsBackButtonVisible="{x:Bind NavFrame.CanGoBack, Mode=OneWay}"
IsTabStop="False" IsTabStop="False"
PaneToggleRequested="AppTitleBar_PaneToggleRequested"> 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 --> <!-- 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.Resources>
<NavigationView.MenuItems> <NavigationView.MenuItems>
<NavigationViewItem <NavigationViewItem
x:Name="GeneralPageNavItem"
x:Uid="Settings_GeneralPage_NavigationViewItem_General" x:Uid="Settings_GeneralPage_NavigationViewItem_General"
Icon="{ui:FontIcon Glyph=&#xE80F;}" Icon="{ui:FontIcon Glyph=&#xE80F;}"
Tag="General" /> Tag="General" />
<NavigationViewItem <NavigationViewItem
x:Name="ExtensionPageNavItem"
x:Uid="Settings_GeneralPage_NavigationViewItem_Extensions" x:Uid="Settings_GeneralPage_NavigationViewItem_Extensions"
Icon="{ui:FontIcon Glyph=&#xEA86;}" Icon="{ui:FontIcon Glyph=&#xEA86;}"
Tag="Extensions" /> Tag="Extensions" />
@@ -90,7 +94,10 @@
</BreadcrumbBar.Resources> </BreadcrumbBar.Resources>
</BreadcrumbBar> </BreadcrumbBar>
</Grid> </Grid>
<Frame x:Name="NavFrame" Grid.Row="1" /> <Frame
x:Name="NavFrame"
Grid.Row="1"
Navigated="NavFrame_OnNavigated" />
</Grid> </Grid>
</NavigationView> </NavigationView>
</Grid> </Grid>

View File

@@ -4,14 +4,19 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.UI.Helpers; using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages; using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Controls; 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 WinUIEx;
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance; using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar; using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
@@ -19,9 +24,12 @@ using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
namespace Microsoft.CmdPal.UI.Settings; namespace Microsoft.CmdPal.UI.Settings;
public sealed partial class SettingsWindow : WindowEx, public sealed partial class SettingsWindow : WindowEx,
IDisposable,
IRecipient<NavigateToExtensionSettingsMessage>, IRecipient<NavigateToExtensionSettingsMessage>,
IRecipient<QuitMessage> IRecipient<QuitMessage>
{ {
private readonly LocalKeyboardListener _localKeyboardListener;
public ObservableCollection<Crumb> BreadCrumbs { get; } = []; public ObservableCollection<Crumb> BreadCrumbs { get; } = [];
// Gets or sets optional action invoked after NavigationView is loaded. // 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<NavigateToExtensionSettingsMessage>(this);
WeakReferenceMessenger.Default.Register<QuitMessage>(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. // Handles NavigationView loaded event.
@@ -85,10 +104,9 @@ public sealed partial class SettingsWindow : WindowEx,
"Extensions" => typeof(ExtensionsPage), "Extensions" => typeof(ExtensionsPage),
_ => null, _ => null,
}; };
if (pageType is not null) if (pageType is not null)
{ {
BreadCrumbs.Clear();
BreadCrumbs.Add(new(page, page));
NavFrame.Navigate(pageType); NavFrame.Navigate(pageType);
} }
} }
@@ -96,7 +114,6 @@ public sealed partial class SettingsWindow : WindowEx,
private void Navigate(ProviderSettingsViewModel extension) private void Navigate(ProviderSettingsViewModel extension)
{ {
NavFrame.Navigate(typeof(ExtensionPage), extension); NavFrame.Navigate(typeof(ExtensionPage), extension);
BreadCrumbs.Add(new(extension.DisplayName, string.Empty));
} }
private void PositionCentered() 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) 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) 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; AppTitleBar.IsPaneToggleButtonVisible = true;
WorkAroundIcon.Margin = new Thickness(8, 0, 16, 0); // Required for workaround, see XAML comment 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 else
{ {
AppTitleBar.IsPaneToggleButtonVisible = false; 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; 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 public readonly struct Crumb

View File

@@ -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"> <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> <value>Preventing disruption of the program running in fullscreen by unintentional activation of shortcut</value>
</data> </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"> <data name="Settings_GeneralPage_HighlightSearch_SettingsCard.Header" xml:space="preserve">
<value>Highlight search on activate</value> <value>Highlight search on activate</value>
</data> </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"> <data name="GlobalErrorHandler_CrashMessageBox_Caption" xml:space="preserve">
<value>Command Palette - Fatal error</value> <value>Command Palette - Fatal error</value>
</data> </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> </root>

View File

@@ -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);
});
}
}
}
}
}

View File

@@ -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)] + "…";
}
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<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>

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.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));
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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" };
}

View File

@@ -18,6 +18,8 @@ public class MockSettingsInterface : ISettingsInterface
public int HistoryItemCount { get; set; } public int HistoryItemCount { get; set; }
public string CustomSearchUri { get; }
public IReadOnlyList<HistoryItem> HistoryItems => _historyItems; public IReadOnlyList<HistoryItem> HistoryItems => _historyItems;
public MockSettingsInterface(int historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null) public MockSettingsInterface(int historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)

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