diff --git a/.config/configuration.vsEnterprise.winget b/.config/configuration.vsEnterprise.winget index 4d19d37137..08c9983562 100644 --- a/.config/configuration.vsEnterprise.winget +++ b/.config/configuration.vsEnterprise.winget @@ -2,14 +2,14 @@ # Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys properties: resources: - - resource: Microsoft.Windows.Developer/DeveloperMode + - resource: Microsoft.Windows.Settings/WindowsSettings directives: description: Enable Developer Mode allowPrerelease: true # Requires elevation for the set operation securityContext: elevated settings: - Ensure: Present + DeveloperMode: true - resource: Microsoft.WinGet.DSC/WinGetPackage id: vsPackage directives: diff --git a/.config/configuration.vsProfessional.winget b/.config/configuration.vsProfessional.winget index 78fb1c13d5..4d7e4a31c3 100644 --- a/.config/configuration.vsProfessional.winget +++ b/.config/configuration.vsProfessional.winget @@ -2,14 +2,14 @@ # Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys properties: resources: - - resource: Microsoft.Windows.Developer/DeveloperMode + - resource: Microsoft.Windows.Settings/WindowsSettings directives: description: Enable Developer Mode allowPrerelease: true # Requires elevation for the set operation securityContext: elevated settings: - Ensure: Present + DeveloperMode: true - resource: Microsoft.WinGet.DSC/WinGetPackage id: vsPackage directives: diff --git a/.config/configuration.winget b/.config/configuration.winget index 456eca47f2..2016dcdc33 100644 --- a/.config/configuration.winget +++ b/.config/configuration.winget @@ -2,14 +2,14 @@ # Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys properties: resources: - - resource: Microsoft.Windows.Developer/DeveloperMode + - resource: Microsoft.Windows.Settings/WindowsSettings directives: description: Enable Developer Mode allowPrerelease: true # Requires elevation for the set operation securityContext: elevated settings: - Ensure: Present + DeveloperMode: true - resource: Microsoft.WinGet.DSC/WinGetPackage id: vsPackage directives: diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e55d081c91..5e5ea03e7e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -12,7 +12,7 @@ body: attributes: label: Microsoft PowerToys version placeholder: X.XX.X - description: Hover over system tray icon or look at Settings + description: Hover over the system tray icon or look at Settings validations: required: true @@ -20,7 +20,7 @@ body: type: dropdown attributes: label: Installation method - description: How / Where was PowerToys installed from? + description: How / where was PowerToys installed from? multiple: true options: - GitHub diff --git a/.github/ISSUE_TEMPLATE/documentation-issue.yml b/.github/ISSUE_TEMPLATE/documentation-issue.yml index 151fc5a1f7..583cf54811 100644 --- a/.github/ISSUE_TEMPLATE/documentation-issue.yml +++ b/.github/ISSUE_TEMPLATE/documentation-issue.yml @@ -6,7 +6,7 @@ labels: body: - type: textarea attributes: - label: Provide a description of requested docs changes + label: Describe the requested doc changes placeholder: Briefly describe which document needs to be corrected and why. validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index d7d092dbca..a2c7db9cc5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -13,7 +13,7 @@ body: - type: textarea attributes: label: Scenario when this would be used? - placeholder: What is the scenario this would be used? Why is this important to your workflow as a power user? + placeholder: What is the scenario this would be used in? Why is this important to your workflow as a power user? validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/translation_issue.yml b/.github/ISSUE_TEMPLATE/translation_issue.yml index ffddacb9aa..1fdefbff8b 100644 --- a/.github/ISSUE_TEMPLATE/translation_issue.yml +++ b/.github/ISSUE_TEMPLATE/translation_issue.yml @@ -14,7 +14,7 @@ body: attributes: label: Microsoft PowerToys version placeholder: 0.70.0 - description: Hover over system tray icon or look at Settings + description: Hover over the system tray icon or look at Settings validations: required: true - type: dropdown @@ -65,7 +65,7 @@ body: - type: textarea attributes: label: ❌ Actual phrase(s) - placeholder: What is there? Please include a screenshot as that is extremely helpful. + placeholder: What is there? Please include a screenshot, as that is extremely helpful. validations: required: true - type: textarea diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index 78ddf93b32..847e329ffa 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -273,6 +273,11 @@ mengyuanchen # DllName testhost +Testably #Tools -OIP \ No newline at end of file +OIP +xef +xes +PACKAGEVERSIONNUMBER +APPXMANIFESTVERSION diff --git a/.github/actions/spell-check/allow/names.txt b/.github/actions/spell-check/allow/names.txt index eeda5b9ae1..f570a231af 100644 --- a/.github/actions/spell-check/allow/names.txt +++ b/.github/actions/spell-check/allow/names.txt @@ -46,8 +46,8 @@ betsegaw bricelam bsky CCcat -chenmy chemwolf +chenmy Chinh chrdavis Chrzan @@ -65,8 +65,8 @@ Deondre DHowett ductdo Essey -Feng ethanfangg +Feng ferraridavide foxmsft frankychen @@ -77,6 +77,7 @@ Galaxi Garside Gershaft Giordani +Gleb Gokce gordon Griese @@ -90,12 +91,15 @@ Hemmerlein hlaueriksson Horvalds Howett +hotkidfamily htcfreek Huynh Ionut jamrobot Jaswal +Jaylyn jefflord +Jeremic Jordi jyuwono kai @@ -105,6 +109,7 @@ Kantarci Karthick kaylacinnamon kevinguo +Khmyznikov Krigun Lambson Laute @@ -127,6 +132,8 @@ Naro nathancartlidge Nemeth nielslaute +Noraa +noraajunker oldnewthing onegreatworld palenshus @@ -146,11 +153,13 @@ ricardosantos riri ritchielawrence robmikh +ruslanlap Russinovich Rutkas ryanbodrug saahmedm sachaple +Sameerjs Santossio Schoen Sekan @@ -166,9 +175,11 @@ Tadele talynone Taras TBM +Teutsch tilovell Triet urnotdfs +vednig waaverecords wang Whuihuan @@ -187,9 +198,6 @@ zhaopy zhaoqpcn Zoltan Zykova -Sameerjs -ruslanlap -vednig # OTHERS @@ -216,6 +224,7 @@ openai Quickime regedit roslyn +Skia Spotify Vanara wangyi diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index 21d7da66b8..f5710e72ce 100644 --- a/.github/actions/spell-check/excludes.txt +++ b/.github/actions/spell-check/excludes.txt @@ -92,6 +92,7 @@ ^\.github/actions/spell-check/ ^\.gitmodules$ ^\Q.github/workflows/spelling2.yml\E$ +^\Q.pipelines/272MSSharedLibSN2048.snk\E$ ^\Q.pipelines/ESRPSigning_core.json\E$ ^\Qdoc/devdocs/localization.md\E$ ^\Qsrc/common/ManagedCommon/ColorFormatHelper.cs\E$ @@ -120,7 +121,9 @@ ^src/modules/MouseWithoutBorders/App/Form/.*\.resx$ ^src/modules/MouseWithoutBorders/App/Helper/.*\.resx$ ^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag\.txt$ +^src/modules/ZoomIt/ZoomIt/ZoomIt\.idc$ ^src/Monaco/ ^src/common/sysinternals/Eula/ ^tools/Verification scripts/Check preview handler registration\.ps1$ ignore$ +^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$ diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index bdae0338be..a081d3fcd1 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -14,6 +14,7 @@ AColumn acrt ACTIVATEAPP activationaction +ACTIVATEOPTIONS ACVS adaptivecards ADate @@ -68,6 +69,7 @@ appwiz APSTUDIO AQS ARandom +Arash ARCHITEW ARemapped ARPINSTALLLOCATION @@ -154,6 +156,7 @@ builttoroam BVal BValue byapp +BYCOMMAND BYPOSITION CALCRECT CALG @@ -189,6 +192,7 @@ CImage cla CLASSDC CLASSNOTAVAILABLE +cleanmgr clickable clickonce CLIENTEDGE @@ -200,6 +204,7 @@ CLIPSIBLINGS closesocket clp CLSCTX +CLSCTXLOCALSERVER clsids Clusion cmder @@ -273,6 +278,7 @@ currentculture CURRENTDIR CURSORINFO cursorpos +CURSORSHOWING customaction CUSTOMACTIONTEST CUSTOMFORMATPLACEHOLDER @@ -327,7 +333,6 @@ DEFAULTTONULL DEFAULTTOPRIMARY DEFERERASE DEFPUSHBUTTON -DEFT deinitialization DELA DELETEDKEYIMAGE @@ -341,7 +346,9 @@ DESELECTOTHERS DESIGNINFO DESKTOPABSOLUTEEDITING DESKTOPABSOLUTEPARSING +DESKTOPHORZRES desktopshorcutinstalled +DESKTOPVERTRES devblogs devdocs devmgmt @@ -352,6 +359,7 @@ DFX DIALOGEX digicert dimm +DINORMAL DISABLEASACTIONKEY DISABLENOSCROLL diskmgmt @@ -395,6 +403,7 @@ DVASPECTINFO DVD dvr DVTARGETDEVICE +dwflags dwl dwm dwmapi @@ -469,6 +478,7 @@ EXPCMDFLAGS EXPCMDSTATE explr exppowertoys +exprtk exptas exsb exstyle @@ -482,6 +492,7 @@ FANCYZONESDRAWLAYOUTTEST FANCYZONESEDITOR FARPROC fff +FFFF FILEEXPLORER FILEFLAGS FILEFLAGSMASK @@ -600,6 +611,7 @@ helptext HGFE hglobal hhk +HHmmssfff hhx Hiber Hiberboot @@ -608,6 +620,7 @@ hicon HIDEREADONLY HIDEWINDOW Hif +hightlight HIMAGELIST himl hinst @@ -669,7 +682,6 @@ IBeam icf ICONERROR ICONLOCATION -idc IDCANCEL IDD idk @@ -680,7 +692,6 @@ IDR IDXGI ietf IEXPLORE -iextn IFACEMETHOD IFACEMETHODIMP IFile @@ -719,7 +730,6 @@ INPUTMOUSE INPUTSINK INPUTTYPE INSTALLDESKTOPSHORTCUT -INSTALLDIR installdir INSTALLFOLDER INSTALLFOLDERTOBOOTSTRAPPERINSTALLFOLDER @@ -745,6 +755,7 @@ isocpp iss issecret ISSEPARATOR +istep ith ITHUMBNAIL IUI @@ -755,6 +766,7 @@ iwr jfif jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi jjw +JLO jobject jpe jpnime @@ -788,6 +800,7 @@ LCIDTo Lclean Ldone Ldr +LEFTALIGN LEFTSCROLLBAR LEFTTEXT LError @@ -810,11 +823,11 @@ LMENU lnks LOADFROMFILE LOBYTE -localappdata LOCALDISPLAY localpackage LOCALSYSTEM LOCATIONCHANGE +LOCKTYPE LOGFONT LOGFONTW logon @@ -910,6 +923,7 @@ metafile mfc Mgmt Microwaved +middleclickaction midl mii mindaro @@ -975,7 +989,6 @@ msrc msstore mst msvcp -msvsmon MTND MULTIPLEUSE multizone @@ -1153,6 +1166,7 @@ PARTIALCONFIRMATIONDIALOGTITLE PATCOPY PATHMUSTEXIST PATINVERT +partow PATPAINT pbc pbi @@ -1251,6 +1265,7 @@ prg prgh prgms pri +primaryclickaction PRINTCLIENT printmanagement prm @@ -1272,6 +1287,7 @@ prvpane psapi pscid PSECURITY +psexec psfgao psfi PSMODULEPATH @@ -1282,7 +1298,6 @@ pstm PStr pstream pstrm -pswd PSYSTEM psz ptb @@ -1427,6 +1442,7 @@ SDKDDK sdns searchterm SEARCHUI +secondaryclickaction SECONDARYDISPLAY secpol securestring @@ -1474,16 +1490,12 @@ SHELLDLL shellex SHELLEXECUTEINFO SHELLEXECUTEINFOW -SHELLEXTENSION SHELLICONSIZE -SHELLNEWVALUE SHFILEINFO SHFILEOPSTRUCT SHGDN SHGDNF SHGFI -SHGFIICON -SHGFILARGEICON SHIL shinfo shlwapi @@ -1515,9 +1527,9 @@ SICHINT SIDs siex sigdn +Signedness SIGNINGSCENARIO signtool -Signtool SINGLEKEY sipolicy SIZEBOX @@ -1533,8 +1545,10 @@ SLGP sln SMALLICON smartphone +smileys SMTO SNAPPROCESS +snk snwprintf softline SOURCECLIENTAREAONLY @@ -1587,6 +1601,7 @@ steamapps STGC STGM STGMEDIUM +STGMREAD STICKYKEYS sticpl storelogo @@ -1663,6 +1678,7 @@ TDefault TDevice telephon templatenamespace +TESTONLY testprocess TEXCOORD TEXTBOXNEWLINE @@ -1715,6 +1731,7 @@ trx tsa TSender TServer +tskill tstoi TStr tweakme @@ -1778,7 +1795,6 @@ uxtheme vabdq validmodulename valuegenerator -VARENUM variantassignment vcamp VCENTER @@ -1874,6 +1890,7 @@ WINDOWPOSCHANGING WINDOWSBUILDNUMBER windowssearch windowssettings +windowsterminal WINDOWSTYLES WINDOWSTYLESICON winerror @@ -1948,6 +1965,7 @@ Wwanpp XAxis xclip xcopy +XDeployment XDocument XElement xfd @@ -1983,6 +2001,7 @@ Zoneszonabletester Zoomin zoomit ZOOMITX +Zorder ZXk ZXNs zzz @@ -1994,3 +2013,10 @@ culori Evercoder LCh CIELCh +CLSCTXINPROCALL +IIDI +irow +lcid +ppwsz +rguid +VARTYPE diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt index 5a6f4785b1..6bd99fb83e 100644 --- a/.github/actions/spell-check/patterns.txt +++ b/.github/actions/spell-check/patterns.txt @@ -243,4 +243,4 @@ Process Process # ZoomIt menu items with accelerator keys E&xit -St&yle \ No newline at end of file +St&yle diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0cff106acb..2db246d63b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,9 +5,9 @@ ## PR Checklist - [ ] **Closes:** #xxx -- [ ] **Communication:** I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected +- [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass -- [ ] **Localization:** All end user facing strings can be localized +- [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries @@ -16,7 +16,7 @@ - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx - + ## Detailed Description of the Pull Request / Additional comments diff --git a/.github/workflows/package-submissions.yml b/.github/workflows/package-submissions.yml index b03c18b78b..a2d401faa4 100644 --- a/.github/workflows/package-submissions.yml +++ b/.github/workflows/package-submissions.yml @@ -1,5 +1,4 @@ name: WinGet submission on release -# based off of https://github.com/nushell/nushell/blob/main/.github/workflows/winget-submission.yml on: workflow_dispatch: @@ -9,23 +8,31 @@ on: jobs: winget: name: Publish winget package + + # winget-create is only supported on Windows runs-on: windows-latest + + # winget-create will read the following environment variable to access the GitHub token needed for submitting a PR + # See https://aka.ms/winget-create-token + env: + WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.PT_WINGET }} + + # Only submit stable releases + if: ${{ !github.event.release.prerelease }} steps: - name: Submit Microsoft.PowerToys package to Windows Package Manager Community Repository run: | + # Get installer info from GitHub release event + $assets = '${{ toJSON(github.event.release.assets) }}' | ConvertFrom-Json + $x64UserInstallerUrl = $assets | Where-Object -Property name -match 'PowerToysUserSetup.*x64' | Select -ExpandProperty browser_download_url + $x64MachineInstallerUrl = $assets | Where-Object -Property name -match 'PowerToysSetup.*x64' | Select -ExpandProperty browser_download_url + $arm64UserInstallerUrl = $assets | Where-Object -Property name -match 'PowerToysUserSetup.*arm64' | Select -ExpandProperty browser_download_url + $arm64MachineInstallerUrl = $assets | Where-Object -Property name -match 'PowerToysSetup.*arm64' | Select -ExpandProperty browser_download_url + $packageVersion = (${{ toJSON(github.event.release.tag_name) }}).Trim('v') - $wingetPackage = "Microsoft.PowerToys" - $gitToken = "${{ secrets.PT_WINGET }}" - - $github = Invoke-RestMethod -uri "https://api.github.com/repos/Microsoft/PowerToys/releases" - - $targetRelease = $github | Where-Object -Property name -match 'Release'| Select -First 1 - $installerUserX64Url = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'PowerToysUserSetup.*x64' | Select -ExpandProperty browser_download_url - $installerMachineX64Url = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'PowerToysSetup.*x64' | Select -ExpandProperty browser_download_url - $installerUserArmUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'PowerToysUserSetup.*arm64' | Select -ExpandProperty browser_download_url - $installerMachineArmUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'PowerToysSetup.*arm64' | Select -ExpandProperty browser_download_url - $ver = $targetRelease.tag_name -ireplace '^v' - - # getting latest wingetcreate file - iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe - .\wingetcreate.exe update $wingetPackage -s -v $ver -u "$installerUserX64Url|user" "$installerMachineX64Url|machine" "$installerUserArmUrl|user" "$installerMachineArmUrl|machine" -t $gitToken + # Update package using wingetcreate + curl.exe -JLO https://aka.ms/wingetcreate/latest + .\wingetcreate.exe update Microsoft.PowerToys ` + --version $packageVersion ` + --urls "$x64UserInstallerUrl|user" "$x64MachineInstallerUrl|machine" "$arm64UserInstallerUrl|user" "$arm64MachineInstallerUrl|machine" ` + --submit diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index 03bb66fff6..cd2b6d5b0f 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -93,7 +93,7 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@67debf50669c7fc76fc8f5d7f996384535a72b77 # v0.0.24 + uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25 with: config: .github/actions/spell-check suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }} @@ -156,7 +156,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' steps: - name: comment - uses: check-spelling/check-spelling@67debf50669c7fc76fc8f5d7f996384535a72b77 # v0.0.24 + uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25 with: config: .github/actions/spell-check checkout: true @@ -175,7 +175,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@67debf50669c7fc76fc8f5d7f996384535a72b77 # v0.0.24 + uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25 with: config: .github/actions/spell-check checkout: true @@ -202,7 +202,7 @@ jobs: cancel-in-progress: false steps: - name: apply spelling updates - uses: check-spelling/check-spelling@67debf50669c7fc76fc8f5d7f996384535a72b77 # v0.0.24 + uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25 with: experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} checkout: true diff --git a/.pipelines/272MSSharedLibSN2048.snk b/.pipelines/272MSSharedLibSN2048.snk new file mode 100644 index 0000000000..bd766f84a2 Binary files /dev/null and b/.pipelines/272MSSharedLibSN2048.snk differ diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 615b5633bf..944bbda8b7 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -3,226 +3,228 @@ "UseMinimatch": false, "SignBatches": [ { - "MatchedPath": [ - "*.resources.dll", - - "WinUI3Apps\\Assets\\Settings\\Scripts\\*.ps1", + "MatchedPath": [ + "*.resources.dll", - "PowerToys.ActionRunner.exe", - "PowerToys.Update.exe", - "PowerToys.BackgroundActivatorDLL.dll", - "Notifications.dll", - "os-detection.dll", - "PowerToys.exe", - "PowerToys.FilePreviewCommon.dll", - "PowerToys.Interop.dll", - "Tools\\PowerToys.BugReportTool.exe", - "StylesReportTool\\PowerToys.StylesReportTool.exe", - "Telemetry.dll", - "PowerToys.ManagedTelemetry.dll", - "PowerToys.ManagedCommon.dll", - "PowerToys.Common.UI.dll", - "PowerToys.Settings.UI.Lib.dll", - "PowerToys.GPOWrapper.dll", - "PowerToys.GPOWrapperProjection.dll", - "PowerToys.AllExperiments.dll", + "WinUI3Apps\\Assets\\Settings\\Scripts\\*.ps1", - "PowerToys.AlwaysOnTop.exe", - "PowerToys.AlwaysOnTopModuleInterface.dll", + "PowerToys.ActionRunner.exe", + "PowerToys.Update.exe", + "PowerToys.BackgroundActivatorDLL.dll", + "Notifications.dll", + "os-detection.dll", + "PowerToys.exe", + "PowerToys.FilePreviewCommon.dll", + "PowerToys.Interop.dll", + "Tools\\PowerToys.BugReportTool.exe", + "StylesReportTool\\PowerToys.StylesReportTool.exe", + "Telemetry.dll", + "CalculatorEngineCommon.dll", + "PowerToys.ManagedTelemetry.dll", + "PowerToys.ManagedCommon.dll", + "PowerToys.ManagedCsWin32.dll", + "PowerToys.Common.UI.dll", + "PowerToys.Settings.UI.Lib.dll", + "PowerToys.GPOWrapper.dll", + "PowerToys.GPOWrapperProjection.dll", + "PowerToys.AllExperiments.dll", - "PowerToys.CmdNotFoundModuleInterface.dll", - "PowerToys.CmdNotFound.dll", + "PowerToys.AlwaysOnTop.exe", + "PowerToys.AlwaysOnTopModuleInterface.dll", - "PowerToys.ColorPicker.dll", - "PowerToys.ColorPickerUI.dll", - "PowerToys.ColorPickerUI.exe", + "PowerToys.CmdNotFoundModuleInterface.dll", + "PowerToys.CmdNotFound.dll", - "PowerToys.CropAndLockModuleInterface.dll", - "PowerToys.CropAndLock.exe", + "PowerToys.ColorPicker.dll", + "PowerToys.ColorPickerUI.dll", + "PowerToys.ColorPickerUI.exe", - "PowerToys.PowerOCRModuleInterface.dll", - "PowerToys.PowerOCR.dll", - "PowerToys.PowerOCR.exe", + "PowerToys.CropAndLockModuleInterface.dll", + "PowerToys.CropAndLock.exe", - "PowerToys.AdvancedPasteModuleInterface.dll", - "WinUI3Apps\\PowerToys.AdvancedPaste.exe", - "WinUI3Apps\\PowerToys.AdvancedPaste.dll", + "PowerToys.PowerOCRModuleInterface.dll", + "PowerToys.PowerOCR.dll", + "PowerToys.PowerOCR.exe", - "PowerToys.AwakeModuleInterface.dll", - "PowerToys.Awake.exe", - "PowerToys.Awake.dll", + "PowerToys.AdvancedPasteModuleInterface.dll", + "WinUI3Apps\\PowerToys.AdvancedPaste.exe", + "WinUI3Apps\\PowerToys.AdvancedPaste.dll", - "fancyzones.dll", - "PowerToys.FancyZonesEditor.exe", - "PowerToys.FancyZonesEditor.dll", - "PowerToys.FancyZonesEditorCommon.dll", - "PowerToys.FancyZonesModuleInterface.dll", - "PowerToys.FancyZones.exe", + "PowerToys.AwakeModuleInterface.dll", + "PowerToys.Awake.exe", + "PowerToys.Awake.dll", - "PowerToys.GcodePreviewHandler.dll", - "PowerToys.GcodePreviewHandler.exe", - "PowerToys.GcodePreviewHandlerCpp.dll", - "PowerToys.GcodeThumbnailProvider.dll", - "PowerToys.GcodeThumbnailProvider.exe", - "PowerToys.GcodeThumbnailProviderCpp.dll", - "PowerToys.ManagedTelemetry.dll", - "PowerToys.MarkdownPreviewHandler.dll", - "PowerToys.MarkdownPreviewHandler.exe", - "PowerToys.MarkdownPreviewHandlerCpp.dll", - "PowerToys.MonacoPreviewHandler.dll", - "PowerToys.MonacoPreviewHandler.exe", - "PowerToys.MonacoPreviewHandlerCpp.dll", - "PowerToys.PdfPreviewHandler.dll", - "PowerToys.PdfPreviewHandler.exe", - "PowerToys.PdfPreviewHandlerCpp.dll", - "PowerToys.PdfThumbnailProvider.dll", - "PowerToys.PdfThumbnailProvider.exe", - "PowerToys.PdfThumbnailProviderCpp.dll", - "PowerToys.powerpreview.dll", - "PowerToys.PreviewHandlerCommon.dll", - "PowerToys.QoiPreviewHandler.dll", - "PowerToys.QoiPreviewHandler.exe", - "PowerToys.QoiPreviewHandlerCpp.dll", - "PowerToys.QoiThumbnailProvider.dll", - "PowerToys.QoiThumbnailProvider.exe", - "PowerToys.QoiThumbnailProviderCpp.dll", - "PowerToys.StlThumbnailProvider.dll", - "PowerToys.StlThumbnailProvider.exe", - "PowerToys.StlThumbnailProviderCpp.dll", - "PowerToys.SvgPreviewHandler.dll", - "PowerToys.SvgPreviewHandler.exe", - "PowerToys.SvgPreviewHandlerCpp.dll", - "PowerToys.SvgThumbnailProvider.dll", - "PowerToys.SvgThumbnailProvider.exe", - "PowerToys.SvgThumbnailProviderCpp.dll", + "fancyzones.dll", + "PowerToys.FancyZonesEditor.exe", + "PowerToys.FancyZonesEditor.dll", + "PowerToys.FancyZonesEditorCommon.dll", + "PowerToys.FancyZonesModuleInterface.dll", + "PowerToys.FancyZones.exe", - "WinUI3Apps\\PowerToys.HostsModuleInterface.dll", - "WinUI3Apps\\PowerToys.HostsUILib.dll", - "WinUI3Apps\\PowerToys.Hosts.dll", - "WinUI3Apps\\PowerToys.Hosts.exe", + "PowerToys.GcodePreviewHandler.dll", + "PowerToys.GcodePreviewHandler.exe", + "PowerToys.GcodePreviewHandlerCpp.dll", + "PowerToys.GcodeThumbnailProvider.dll", + "PowerToys.GcodeThumbnailProvider.exe", + "PowerToys.GcodeThumbnailProviderCpp.dll", + "PowerToys.ManagedTelemetry.dll", + "PowerToys.MarkdownPreviewHandler.dll", + "PowerToys.MarkdownPreviewHandler.exe", + "PowerToys.MarkdownPreviewHandlerCpp.dll", + "PowerToys.MonacoPreviewHandler.dll", + "PowerToys.MonacoPreviewHandler.exe", + "PowerToys.MonacoPreviewHandlerCpp.dll", + "PowerToys.PdfPreviewHandler.dll", + "PowerToys.PdfPreviewHandler.exe", + "PowerToys.PdfPreviewHandlerCpp.dll", + "PowerToys.PdfThumbnailProvider.dll", + "PowerToys.PdfThumbnailProvider.exe", + "PowerToys.PdfThumbnailProviderCpp.dll", + "PowerToys.powerpreview.dll", + "PowerToys.PreviewHandlerCommon.dll", + "PowerToys.QoiPreviewHandler.dll", + "PowerToys.QoiPreviewHandler.exe", + "PowerToys.QoiPreviewHandlerCpp.dll", + "PowerToys.QoiThumbnailProvider.dll", + "PowerToys.QoiThumbnailProvider.exe", + "PowerToys.QoiThumbnailProviderCpp.dll", + "PowerToys.StlThumbnailProvider.dll", + "PowerToys.StlThumbnailProvider.exe", + "PowerToys.StlThumbnailProviderCpp.dll", + "PowerToys.SvgPreviewHandler.dll", + "PowerToys.SvgPreviewHandler.exe", + "PowerToys.SvgPreviewHandlerCpp.dll", + "PowerToys.SvgThumbnailProvider.dll", + "PowerToys.SvgThumbnailProvider.exe", + "PowerToys.SvgThumbnailProviderCpp.dll", - "WinUI3Apps\\PowerToys.FileLocksmithLib.Interop.dll", - "WinUI3Apps\\PowerToys.FileLocksmithExt.dll", - "WinUI3Apps\\PowerToys.FileLocksmithUI.exe", - "WinUI3Apps\\PowerToys.FileLocksmithUI.dll", - "WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll", - "FileLocksmithContextMenuPackage.msix", + "WinUI3Apps\\PowerToys.HostsModuleInterface.dll", + "WinUI3Apps\\PowerToys.HostsUILib.dll", + "WinUI3Apps\\PowerToys.Hosts.dll", + "WinUI3Apps\\PowerToys.Hosts.exe", - "WinUI3Apps\\Peek.Common.dll", - "WinUI3Apps\\Peek.FilePreviewer.dll", - "WinUI3Apps\\Powertoys.Peek.UI.dll", - "WinUI3Apps\\Powertoys.Peek.UI.exe", - "WinUI3Apps\\Powertoys.Peek.dll", + "WinUI3Apps\\PowerToys.FileLocksmithLib.Interop.dll", + "WinUI3Apps\\PowerToys.FileLocksmithExt.dll", + "WinUI3Apps\\PowerToys.FileLocksmithUI.exe", + "WinUI3Apps\\PowerToys.FileLocksmithUI.dll", + "WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll", + "FileLocksmithContextMenuPackage.msix", - "WinUI3Apps\\PowerToys.EnvironmentVariablesModuleInterface.dll", - "WinUI3Apps\\PowerToys.EnvironmentVariablesUILib.dll", - "WinUI3Apps\\PowerToys.EnvironmentVariables.dll", - "WinUI3Apps\\PowerToys.EnvironmentVariables.exe", + "WinUI3Apps\\Peek.Common.dll", + "WinUI3Apps\\Peek.FilePreviewer.dll", + "WinUI3Apps\\Powertoys.Peek.UI.dll", + "WinUI3Apps\\Powertoys.Peek.UI.exe", + "WinUI3Apps\\Powertoys.Peek.dll", - "PowerToys.ImageResizer.exe", - "PowerToys.ImageResizer.dll", - "PowerToys.ImageResizerExt.dll", - "PowerToys.ImageResizerContextMenu.dll", - "ImageResizerContextMenuPackage.msix", + "WinUI3Apps\\PowerToys.EnvironmentVariablesModuleInterface.dll", + "WinUI3Apps\\PowerToys.EnvironmentVariablesUILib.dll", + "WinUI3Apps\\PowerToys.EnvironmentVariables.dll", + "WinUI3Apps\\PowerToys.EnvironmentVariables.exe", - "PowerToys.KeyboardManager.dll", - "KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe", - "KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe", - "PowerToys.KeyboardManagerEditorLibraryWrapper.dll", + "PowerToys.ImageResizer.exe", + "PowerToys.ImageResizer.dll", + "PowerToys.ImageResizerExt.dll", + "PowerToys.ImageResizerContextMenu.dll", + "ImageResizerContextMenuPackage.msix", - "PowerToys.Launcher.dll", - "PowerToys.PowerLauncher.dll", - "PowerToys.PowerLauncher.exe", - "PowerToys.PowerLauncher.Telemetry.dll", - "Wox.dll", - "Wox.Infrastructure.dll", - "Wox.Plugin.dll", - "RunPlugins\\Calculator\\Microsoft.PowerToys.Run.Plugin.Calculator.dll", - "RunPlugins\\Folder\\Microsoft.Plugin.Folder.dll", - "RunPlugins\\Indexer\\Microsoft.Plugin.Indexer.dll", - "RunPlugins\\OneNote\\Microsoft.PowerToys.Run.Plugin.OneNote.dll", - "RunPlugins\\History\\Microsoft.PowerToys.Run.Plugin.History.dll", - "RunPlugins\\PowerToys\\Microsoft.PowerToys.Run.Plugin.PowerToys.dll", - "RunPlugins\\Program\\Microsoft.Plugin.Program.dll", - "RunPlugins\\Registry\\Microsoft.PowerToys.Run.Plugin.Registry.dll", - "RunPlugins\\WindowsSettings\\Microsoft.PowerToys.Run.Plugin.WindowsSettings.dll", - "RunPlugins\\Shell\\Microsoft.Plugin.Shell.dll", - "RunPlugins\\Uri\\Microsoft.Plugin.Uri.dll", - "RunPlugins\\WindowWalker\\Microsoft.Plugin.WindowWalker.dll", - "RunPlugins\\UnitConverter\\Community.PowerToys.Run.Plugin.UnitConverter.dll", - "RunPlugins\\VSCodeWorkspaces\\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll", - "RunPlugins\\Service\\Microsoft.PowerToys.Run.Plugin.Service.dll", - "RunPlugins\\System\\Microsoft.PowerToys.Run.Plugin.System.dll", - "RunPlugins\\TimeDate\\Microsoft.PowerToys.Run.Plugin.TimeDate.dll", - "RunPlugins\\ValueGenerator\\Community.PowerToys.Run.Plugin.ValueGenerator.dll", - "RunPlugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll", - "RunPlugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll", - - "WinUI3Apps\\PowerToys.MeasureToolModuleInterface.dll", - "WinUI3Apps\\PowerToys.MeasureToolCore.dll", - "WinUI3Apps\\PowerToys.MeasureToolUI.dll", - "WinUI3Apps\\PowerToys.MeasureToolUI.exe", + "PowerToys.KeyboardManager.dll", + "KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe", + "KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe", + "PowerToys.KeyboardManagerEditorLibraryWrapper.dll", - "PowerToys.FindMyMouse.dll", - "PowerToys.MouseHighlighter.dll", - "PowerToys.MouseJump.dll", - "PowerToys.MouseJump.Common.dll", - "PowerToys.MousePointerCrosshairs.dll", - "PowerToys.MouseJumpUI.dll", - "PowerToys.MouseJumpUI.exe", + "PowerToys.Launcher.dll", + "PowerToys.PowerLauncher.dll", + "PowerToys.PowerLauncher.exe", + "PowerToys.PowerLauncher.Telemetry.dll", + "Wox.dll", + "Wox.Infrastructure.dll", + "Wox.Plugin.dll", + "RunPlugins\\Calculator\\Microsoft.PowerToys.Run.Plugin.Calculator.dll", + "RunPlugins\\Folder\\Microsoft.Plugin.Folder.dll", + "RunPlugins\\Indexer\\Microsoft.Plugin.Indexer.dll", + "RunPlugins\\OneNote\\Microsoft.PowerToys.Run.Plugin.OneNote.dll", + "RunPlugins\\History\\Microsoft.PowerToys.Run.Plugin.History.dll", + "RunPlugins\\PowerToys\\Microsoft.PowerToys.Run.Plugin.PowerToys.dll", + "RunPlugins\\Program\\Microsoft.Plugin.Program.dll", + "RunPlugins\\Registry\\Microsoft.PowerToys.Run.Plugin.Registry.dll", + "RunPlugins\\WindowsSettings\\Microsoft.PowerToys.Run.Plugin.WindowsSettings.dll", + "RunPlugins\\Shell\\Microsoft.Plugin.Shell.dll", + "RunPlugins\\Uri\\Microsoft.Plugin.Uri.dll", + "RunPlugins\\WindowWalker\\Microsoft.Plugin.WindowWalker.dll", + "RunPlugins\\UnitConverter\\Community.PowerToys.Run.Plugin.UnitConverter.dll", + "RunPlugins\\VSCodeWorkspaces\\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll", + "RunPlugins\\Service\\Microsoft.PowerToys.Run.Plugin.Service.dll", + "RunPlugins\\System\\Microsoft.PowerToys.Run.Plugin.System.dll", + "RunPlugins\\TimeDate\\Microsoft.PowerToys.Run.Plugin.TimeDate.dll", + "RunPlugins\\ValueGenerator\\Community.PowerToys.Run.Plugin.ValueGenerator.dll", + "RunPlugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll", + "RunPlugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll", - "PowerToys.MouseWithoutBorders.dll", - "PowerToys.MouseWithoutBorders.exe", - "PowerToys.MouseWithoutBordersModuleInterface.dll", - "PowerToys.MouseWithoutBordersService.dll", - "PowerToys.MouseWithoutBordersService.exe", - "PowerToys.MouseWithoutBordersHelper.dll", - "PowerToys.MouseWithoutBordersHelper.exe", + "WinUI3Apps\\PowerToys.MeasureToolModuleInterface.dll", + "WinUI3Apps\\PowerToys.MeasureToolCore.dll", + "WinUI3Apps\\PowerToys.MeasureToolUI.dll", + "WinUI3Apps\\PowerToys.MeasureToolUI.exe", - "WinUI3Apps\\PowerToys.NewPlus.ShellExtension.dll", - "WinUI3Apps\\NewPlusPackage.msix", - "WinUI3Apps\\PowerToys.NewPlus.ShellExtension.win10.dll", + "PowerToys.FindMyMouse.dll", + "PowerToys.MouseHighlighter.dll", + "PowerToys.MouseJump.dll", + "PowerToys.MouseJump.Common.dll", + "PowerToys.MousePointerCrosshairs.dll", + "PowerToys.MouseJumpUI.dll", + "PowerToys.MouseJumpUI.exe", - "PowerAccent.Core.dll", - "PowerToys.PowerAccent.dll", - "PowerToys.PowerAccent.exe", - "PowerToys.PowerAccentModuleInterface.dll", - "PowerToys.PowerAccentKeyboardService.dll", + "PowerToys.MouseWithoutBorders.dll", + "PowerToys.MouseWithoutBorders.exe", + "PowerToys.MouseWithoutBordersModuleInterface.dll", + "PowerToys.MouseWithoutBordersService.dll", + "PowerToys.MouseWithoutBordersService.exe", + "PowerToys.MouseWithoutBordersHelper.dll", + "PowerToys.MouseWithoutBordersHelper.exe", - "WinUI3Apps\\PowerToys.PowerRenameExt.dll", - "WinUI3Apps\\PowerToys.PowerRename.exe", - "WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll", - "WinUI3Apps\\PowerRenameContextMenuPackage.msix", + "WinUI3Apps\\PowerToys.NewPlus.ShellExtension.dll", + "WinUI3Apps\\NewPlusPackage.msix", + "WinUI3Apps\\PowerToys.NewPlus.ShellExtension.win10.dll", - "PowerToys.WorkspacesSnapshotTool.exe", - "PowerToys.WorkspacesLauncher.exe", - "PowerToys.WorkspacesWindowArranger.exe", - "PowerToys.WorkspacesEditor.exe", - "PowerToys.WorkspacesEditor.dll", - "PowerToys.WorkspacesLauncherUI.exe", - "PowerToys.WorkspacesLauncherUI.dll", - "PowerToys.WorkspacesModuleInterface.dll", - "PowerToys.WorkspacesCsharpLibrary.dll", + "PowerAccent.Core.dll", + "PowerToys.PowerAccent.dll", + "PowerToys.PowerAccent.exe", + "PowerToys.PowerAccentModuleInterface.dll", + "PowerToys.PowerAccentKeyboardService.dll", - "WinUI3Apps\\PowerToys.RegistryPreviewExt.dll", - "WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll", - "WinUI3Apps\\PowerToys.RegistryPreview.dll", - "WinUI3Apps\\PowerToys.RegistryPreview.exe", + "WinUI3Apps\\PowerToys.PowerRenameExt.dll", + "WinUI3Apps\\PowerToys.PowerRename.exe", + "WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll", + "WinUI3Apps\\PowerRenameContextMenuPackage.msix", - "PowerToys.ShortcutGuide.exe", - "PowerToys.ShortcutGuideModuleInterface.dll", + "PowerToys.WorkspacesSnapshotTool.exe", + "PowerToys.WorkspacesLauncher.exe", + "PowerToys.WorkspacesWindowArranger.exe", + "PowerToys.WorkspacesEditor.exe", + "PowerToys.WorkspacesEditor.dll", + "PowerToys.WorkspacesLauncherUI.exe", + "PowerToys.WorkspacesLauncherUI.dll", + "PowerToys.WorkspacesModuleInterface.dll", + "PowerToys.WorkspacesCsharpLibrary.dll", - "PowerToys.ZoomIt.exe", - "PowerToys.ZoomItModuleInterface.dll", - "PowerToys.ZoomItSettingsInterop.dll", + "WinUI3Apps\\PowerToys.RegistryPreviewExt.dll", + "WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll", + "WinUI3Apps\\PowerToys.RegistryPreview.dll", + "WinUI3Apps\\PowerToys.RegistryPreview.exe", - "WinUI3Apps\\PowerToys.Settings.dll", - "WinUI3Apps\\PowerToys.Settings.exe", + "PowerToys.ShortcutGuide.exe", + "PowerToys.ShortcutGuideModuleInterface.dll", - "PowerToys.CmdPalModuleInterface.dll", - "CmdPalKeyboardService.dll", - "*Microsoft.CmdPal.UI_*.msix" - ], + "PowerToys.ZoomIt.exe", + "PowerToys.ZoomItModuleInterface.dll", + "PowerToys.ZoomItSettingsInterop.dll", + + "WinUI3Apps\\PowerToys.Settings.dll", + "WinUI3Apps\\PowerToys.Settings.exe", + + "PowerToys.CmdPalModuleInterface.dll", + "CmdPalKeyboardService.dll", + "*Microsoft.CmdPal.UI_*.msix" + ], "SigningInfo": { "Operations": [ { diff --git a/.pipelines/ESRPSigning_installer.json b/.pipelines/ESRPSigning_installer.json index 4f908aa89a..953644a546 100644 --- a/.pipelines/ESRPSigning_installer.json +++ b/.pipelines/ESRPSigning_installer.json @@ -5,6 +5,7 @@ { "MatchedPath": [ "PowerToysSetupCustomActions.dll", + "PowerToysSetupCustomActionsVNext.dll", "PowerToys*Setup-*.exe", "PowerToys*Setup-*.msi", "PowerToys*Setup-*.msi", diff --git a/.pipelines/ESRPSigning_sdk.json b/.pipelines/ESRPSigning_sdk.json index 066acf9e4e..e2e2db7701 100644 --- a/.pipelines/ESRPSigning_sdk.json +++ b/.pipelines/ESRPSigning_sdk.json @@ -4,9 +4,66 @@ "SignBatches": [ { "MatchedPath": [ - "Microsoft.CommandPalette.Extensions.dll", "Microsoft.CommandPalette.Extensions.Toolkit.dll" ], + "SigningInfo": { + "Operations": [ + { + "KeyCode": "CP-233904-SN", + "OperationSetCode": "StrongNameSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": [] + }, + { + "KeyCode": "CP-233904-SN", + "OperationSetCode": "StrongNameVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": [] + }, + { + "KeyCode": "CP-230012", + "OperationSetCode": "SigntoolSign", + "Parameters": [ + { + "parameterName": "OpusName", + "parameterValue": "Microsoft" + }, + { + "parameterName": "OpusInfo", + "parameterValue": "http://www.microsoft.com" + }, + { + "parameterName": "FileDigest", + "parameterValue": "/fd \"SHA256\"" + }, + { + "parameterName": "PageHash", + "parameterValue": "/NPH" + }, + { + "parameterName": "TimeStamp", + "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + ], + "ToolName": "sign", + "ToolVersion": "1.0" + }, + { + "KeyCode": "CP-230012", + "OperationSetCode": "SigntoolVerify", + "Parameters": [], + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + } + }, + { + "MatchedPath": [ + "Microsoft.CommandPalette.Extensions.dll" + ], "SigningInfo": { "Operations": [ { diff --git a/.pipelines/UpdateVersions.ps1 b/.pipelines/UpdateVersions.ps1 index a1bc5bef9a..0be3e3d30b 100644 --- a/.pipelines/UpdateVersions.ps1 +++ b/.pipelines/UpdateVersions.ps1 @@ -126,16 +126,15 @@ Get-ChildItem -Path $rootPath -Recurse packages.config | ForEach-Object { } # Update Directory.Packages.props file -$propsFile = [System.IO.Path]::Combine($rootPath,"Directory.Packages.props") -if (Test-Path $propsFile) { - $file = Read-FileWithEncoding -Path $propsFile +Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Object { + $file = Read-FileWithEncoding -Path $_.FullName $content = $file.Content if ($content -match '' $oldVersionString = '' $content = $content -replace $oldVersionString, $newVersionString - Write-FileWithEncoding -Path $propsFile -Content $content -Encoding $file.encoding - Write-Host "Modified " $propsFile + Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding + Write-Host "Modified " $_.FullName } } @@ -144,8 +143,8 @@ Get-ChildItem -Path $rootPath -Recurse *.vcxproj | ForEach-Object { $file = Read-FileWithEncoding -Path $_.FullName $content = $file.Content if ($content -match '\\Microsoft.WindowsAppSDK.') { - $newVersionString = '\Microsoft.WindowsAppSDK.' + $WinAppSDKVersion + '\' - $oldVersionString = '\\Microsoft.WindowsAppSDK.[-.0-9a-zA-Z]*\\' + $newVersionString = '\Microsoft.WindowsAppSDK.' + $WinAppSDKVersion + $oldVersionString = '\\Microsoft.WindowsAppSDK.(?=[-.0-9a-zA-Z]*\d)[-.0-9a-zA-Z]*' #positive lookahead for at least a digit $content = $content -replace $oldVersionString, $newVersionString Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding Write-Host "Modified " $_.FullName diff --git a/.pipelines/applyXamlStyling.ps1 b/.pipelines/applyXamlStyling.ps1 index 7cb7b4a4b0..1facedc569 100644 --- a/.pipelines/applyXamlStyling.ps1 +++ b/.pipelines/applyXamlStyling.ps1 @@ -41,6 +41,9 @@ Write-Output "" Write-Output "Restoring dotnet tools..." dotnet tool restore --disable-parallel --no-cache +# Use Regex syntax +$PathExcludes = "(\\obj\\)|(\\bin\\)|(\\x64\\)|(\\Generated Files\\PowerRenameXAML\\)|(\\RegistryPreviewUILib\\Controls\\HexBox\\)" + if (-not $Passive) { # Look for unstaged changed files by default @@ -87,7 +90,7 @@ if (-not $Passive) } Write-Output "Running Git Diff: $gitDiffCommand" - $files = Invoke-Expression $gitDiffCommand | Select-String -Pattern "\.xaml$" + $files = Invoke-Expression $gitDiffCommand | Select-String -Pattern "\.xaml$" | Where-Object { $_ -notmatch $PathExcludes } if (-not $Passive -and -not $Main -and -not $Unstaged -and -not $Staged -and -not $LastCommit) { @@ -107,7 +110,7 @@ if (-not $Passive) else { Write-Output "Checking all files (passively)" - $files = Get-ChildItem -Path "$PSScriptRoot\..\src\*.xaml" -Recurse | Select-Object -ExpandProperty FullName | Where-Object { $_ -notmatch "(\\obj\\)|(\\bin\\)|(\\x64\\)|(\\Generated Files\\PowerRenameXAML\\)" } + $files = Get-ChildItem -Path "$PSScriptRoot\..\src\*.xaml" -Recurse | Select-Object -ExpandProperty FullName | Where-Object { $_ -notmatch $PathExcludes } if ($files.count -gt 0) { diff --git a/.pipelines/loc/loc.yml b/.pipelines/loc/loc.yml index 8d582c4830..cc4512c92e 100644 --- a/.pipelines/loc/loc.yml +++ b/.pipelines/loc/loc.yml @@ -25,7 +25,7 @@ steps: fetchDepth: 1 # Don't need a deep checkout for loc files! persistCredentials: true -- task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@3 +- task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@5 displayName: 'Touchdown Build - 37400, PRODEXT' inputs: teamId: 37400 diff --git a/.pipelines/packages.config b/.pipelines/packages.config index 43fa34c91c..c4bca409f9 100644 --- a/.pipelines/packages.config +++ b/.pipelines/packages.config @@ -1,4 +1,4 @@ - + diff --git a/.pipelines/v2/release.yml b/.pipelines/v2/release.yml index bd825355dd..227dd1cf71 100644 --- a/.pipelines/v2/release.yml +++ b/.pipelines/v2/release.yml @@ -20,16 +20,6 @@ parameters: type: string default: '0.0.1' - - name: cmdPalVersionNumber - displayName: "Command Palette Version Number" - type: string - default: '0.0.1' - - - name: cmdPalSdkVersionNumber - displayName: "Command Palette SDK Version Number" - type: string - default: '0.0.1' - - name: buildConfigurations displayName: "Build Configurations" type: object @@ -50,6 +40,9 @@ parameters: name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) +variables: + - template: templates/variables-nuget-package-version.yml + extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: @@ -88,8 +81,8 @@ extends: buildPlatforms: ${{ parameters.buildPlatforms }} buildConfigurations: ${{ parameters.buildConfigurations }} versionNumber: ${{ parameters.versionNumber }} - cmdPalVersionNumber: ${{ parameters.cmdPalVersionNumber }} publishArtifacts: false # 1ES PT handles publication for us. + official: true codeSign: true runTests: false signingIdentity: @@ -106,7 +99,7 @@ extends: beforeBuildSteps: # Sets versions for all PowerToy created DLLs - pwsh: |- - .pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment '' -cmdPalVersionNumber '${{ parameters.cmdPalVersionNumber }}' + .pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment '' displayName: Prepare versioning # Prepare the localizations and telemetry config before the release build @@ -130,8 +123,8 @@ extends: name: SHINE-INT-L image: SHINE-VS17-Latest os: windows + official: true codeSign: true - sdkVersionNumber: ${{ parameters.cmdPalSdkVersionNumber }} signingIdentity: serviceName: $(SigningServiceName) appId: $(SigningAppId) diff --git a/.pipelines/v2/templates/job-build-project.yml b/.pipelines/v2/templates/job-build-project.yml index 2e21a1c26c..51adb1a009 100644 --- a/.pipelines/v2/templates/job-build-project.yml +++ b/.pipelines/v2/templates/job-build-project.yml @@ -11,6 +11,9 @@ parameters: default: - x64 - arm64 + - name: official + type: boolean + default: false - name: codeSign type: boolean default: false @@ -50,15 +53,15 @@ parameters: - name: runTests type: boolean default: true + - name: buildTests + type: boolean + default: true - name: useVSPreview type: boolean default: false - name: versionNumber type: string default: '0.0.1' - - name: cmdPalVersionNumber - type: string - default: '0.0.1' - name: useLatestWinAppSDK type: boolean default: false @@ -99,7 +102,7 @@ jobs: ${{ else }}: OutputBuildPlatform: ${{ platform }} variables: - MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\MakeAppx.exe' + MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\MakeAppx.exe' # Azure DevOps abhors a vacuum # If these are blank, expansion will fail later on... which will result in direct substitution of the variable *names* # later on. We'll just... set them to a single space and if we need to, check IsNullOrWhiteSpace. @@ -110,7 +113,7 @@ jobs: JobOutputArtifactName: build-$(BuildPlatform)-$(BuildConfiguration)${{ parameters.artifactStem }} NUGET_RESTORE_MSBUILD_ARGS: /p:Platform=$(BuildPlatform) # Required for nuget to work due to self contained NODE_OPTIONS: --max_old_space_size=16384 - ${{ if eq(parameters.runTests, true) }}: + ${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}: MSBuildMainBuildTargets: Build;Test ${{ else }}: MSBuildMainBuildTargets: Build @@ -215,14 +218,15 @@ jobs: env: VCWhereExtraVersionTarget: '-prerelease' + - ${{ if eq(parameters.official, true) }}: + - template: .\steps-setup-versioning.yml + parameters: + directory: $(build.sourcesdirectory)\src\modules\cmdpal + - pwsh: |- & "$(build.sourcesdirectory)\.pipelines\installWiX.ps1" displayName: Download and install WiX 3.14 development build - - pwsh: |- - & "$(build.sourcesdirectory)\.pipelines\installWiXVNext.ps1" - displayName: Download and install WiX 4/5 development build - - ${{ parameters.beforeBuildSteps }} - task: VSBuild@1 @@ -331,6 +335,7 @@ jobs: /p:VCRTForwarders-IncludeDebugCRT=false /p:PowerToysRoot=$(Build.SourcesDirectory) /p:PublishProfile=InstallationPublishProfile.pubxml + /p:TargetFramework=net9.0-windows10.0.26100.0 /bl:$(LogOutputDirectory)\publish-${{ join('_',split(project, '/')) }}.binlog $(RestoreAdditionalProjectSourcesArg) platform: $(BuildPlatform) @@ -393,18 +398,16 @@ jobs: testAssemblyVer2: | **\KeyboardManagerEngineTest.dll **\KeyboardManagerEditorTest.dll - **\UnitTests-CommonLib.dll - **\PowerRenameUnitTests.dll - **\UnitTests-FancyZones.dll + **\*UnitTest*.dll !**\obj\** - - ${{ if eq(parameters.codeSign, true) }}: - - pwsh: |- - $Package = (Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix" | Select -First 1) - $PackageFilename = $Package.FullName - Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}" - displayName: Locate the MSIX + - pwsh: |- + $Package = (Get-ChildItem -Recurse -Filter "Microsoft.CmdPal.UI_*.msix" | Select -First 1) + $PackageFilename = $Package.FullName + Write-Host "##vso[task.setvariable variable=CmdPalPackagePath]${PackageFilename}" + displayName: Locate the CmdPal MSIX + - ${{ if eq(parameters.codeSign, true) }}: - pwsh: |- & "$(MakeAppxPath)" unpack /p "$(CmdPalPackagePath)" /d "$(JobOutputDirectory)/CmdPalPackageContents" displayName: Unpack the MSIX for signing @@ -424,6 +427,8 @@ jobs: $PackageFilename = Join-Path $outDir.FullName (Split-Path -Leaf "$(CmdPalPackagePath)") & "$(MakeAppxPath)" pack /h SHA256 /o /p $PackageFilename /d "$(JobOutputDirectory)/CmdPalPackageContents" Copy-Item -Force $PackageFilename "$(CmdPalPackagePath)" + Remove-Item -Force -Recurse "$(JobOutputDirectory)/CmdPalPackageContents" -ErrorAction:Ignore + Remove-Item -Force -Recurse "$(JobOutputDirectory)/_appx" -ErrorAction:Ignore displayName: Re-pack the new CmdPal package after signing - template: steps-esrp-signing.yml @@ -446,6 +451,10 @@ jobs: batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_DSC.json' ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml' + - pwsh: |- + Copy-Item -Verbose -Force "$(CmdPalPackagePath)" "$(JobOutputDirectory)" + displayName: Stage the final CmdPal package + - template: steps-build-installer.yml parameters: codeSign: ${{ parameters.codeSign }} @@ -567,7 +576,7 @@ jobs: displayName: Stage GPO files # Running the tests may result in future jobs consuming artifacts out of this build - - ${{ if eq(parameters.runTests, true) }}: + - ${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}: - task: CopyFiles@2 displayName: Stage entire build output inputs: diff --git a/.pipelines/v2/templates/job-build-sdk.yml b/.pipelines/v2/templates/job-build-sdk.yml index f8aa5dca3e..f8cb9c930a 100644 --- a/.pipelines/v2/templates/job-build-sdk.yml +++ b/.pipelines/v2/templates/job-build-sdk.yml @@ -3,6 +3,9 @@ parameters: type: object default: - Release + - name: official + type: boolean + default: false - name: codeSign type: boolean default: false @@ -12,9 +15,6 @@ parameters: - name: signingIdentity type: object default: {} - - name: sdkVersionNumber - type: string - default: '0.0.1' jobs: - job: "BuildSDK" @@ -36,8 +36,17 @@ jobs: fetchTags: false fetchDepth: 1 + - template: .\steps-ensure-nuget-version.yml + + - task: NuGetAuthenticate@1 + + - ${{ if eq(parameters.official, true) }}: + - template: .\steps-setup-versioning.yml + parameters: + directory: $(build.sourcesdirectory)\src\modules\cmdpal + - pwsh: |- - & "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -VersionOfSDK ${{ parameters.sdkVersionNumber }} -BuildStep "build" -IsAzurePipelineBuild + & "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -BuildStep "build" -IsAzurePipelineBuild displayName: Build SDK - ${{ if eq(parameters.codeSign, true) }}: @@ -52,7 +61,7 @@ jobs: ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml' - pwsh: |- - & "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -VersionOfSDK ${{ parameters.sdkVersionNumber }} -BuildStep "pack" -IsAzurePipelineBuild + & "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -BuildStep "pack" -IsAzurePipelineBuild displayName: Pack SDK - task: CopyFiles@2 diff --git a/.pipelines/v2/templates/job-publish-symbols-using-symbolrequestprod-api.yml b/.pipelines/v2/templates/job-publish-symbols-using-symbolrequestprod-api.yml index 967b7ba4eb..6b214be612 100644 --- a/.pipelines/v2/templates/job-publish-symbols-using-symbolrequestprod-api.yml +++ b/.pipelines/v2/templates/job-publish-symbols-using-symbolrequestprod-api.yml @@ -68,7 +68,7 @@ jobs: pwsh: true ScriptType: InlineScript Inline: |- - $AzToken = (Get-AzAccessToken -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token + $AzToken = (Get-AzAccessToken -AsSecureString -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token | ConvertFrom-SecureString -AsPlainText Write-Host "##vso[task.setvariable variable=SymbolAccessToken;issecret=true]$AzToken" diff --git a/.pipelines/v2/templates/job-test-project.yml b/.pipelines/v2/templates/job-test-project.yml index 234f477fb6..ab682cd5a5 100644 --- a/.pipelines/v2/templates/job-test-project.yml +++ b/.pipelines/v2/templates/job-test-project.yml @@ -15,20 +15,29 @@ parameters: jobs: - job: Test${{ parameters.platform }}${{ parameters.configuration }} displayName: Test ${{ parameters.platform }} ${{ parameters.configuration }} + timeoutInMinutes: 300 variables: - BuildPlatform: ${{ parameters.platform }} + ${{ if or(eq(parameters.platform, 'x64Win10'), eq(parameters.platform, 'x64Win11')) }}: + BuildPlatform: x64 + ${{ else }}: + BuildPlatform: ${{ parameters.platform }} + TestPlatform: ${{ parameters.platform }} BuildConfiguration: ${{ parameters.configuration }} SrcPath: $(Build.Repository.LocalPath) - TestArtifactsName: build-${{ parameters.platform }}-${{ parameters.configuration }}${{ parameters.inputArtifactStem }} + TestArtifactsName: build-${{ variables.BuildPlatform }}-${{ parameters.configuration }}${{ parameters.inputArtifactStem }} pool: ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}: ${{ if ne(parameters.platform, 'ARM64') }}: name: SHINE-INT-Testing-x64 + ${{ if eq(parameters.platform, 'x64Win11') }}: + demands: ImageOverride -equals SHINE-W11-Testing ${{ else }}: name: SHINE-INT-Testing-arm64 ${{ else }}: ${{ if ne(parameters.platform, 'ARM64') }}: name: SHINE-OSS-Testing-x64 + ${{ if eq(parameters.platform, 'x64Win11') }}: + demands: ImageOverride -equals SHINE-W11-Testing ${{ else }}: name: SHINE-OSS-Testing-arm64 steps: @@ -101,8 +110,13 @@ jobs: vsTestVersion: 'toolsInstaller' uiTests: true rerunFailedTests: true + # Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs testAssemblyVer2: | - **\UITests-FancyZones.dll - **\UITests-FancyZonesEditor.dll + **\*UITest*.dll !**\obj\** - !**\ref\** \ No newline at end of file + !**\ref\** + !**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll + + + env: + platform: '$(TestPlatform)' diff --git a/.pipelines/v2/templates/pipeline-ci-build.yml b/.pipelines/v2/templates/pipeline-ci-build.yml index 37e06ae4f2..cc7a67e613 100644 --- a/.pipelines/v2/templates/pipeline-ci-build.yml +++ b/.pipelines/v2/templates/pipeline-ci-build.yml @@ -60,16 +60,4 @@ stages: useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }} ${{ if eq(parameters.useLatestWinAppSDK, true) }}: winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }} - useExperimentalVersion: ${{ parameters.useExperimentalVersion }} - - - ${{ if and(eq(parameters.runTests, true), not(and(eq(platform, 'arm64'), eq(variables['System.PullRequest.IsFork'], true)))) }}: - - stage: Test_${{ platform }} - displayName: Test ${{ platform }} - dependsOn: - - Build_${{platform}} - jobs: - - template: job-test-project.yml - parameters: - platform: ${{ platform }} - configuration: Release - useLatestWebView2: ${{ parameters.useLatestWebView2 }} + useExperimentalVersion: ${{ parameters.useExperimentalVersion }} \ No newline at end of file diff --git a/.pipelines/v2/templates/pipeline-ui-tests-automation.yml b/.pipelines/v2/templates/pipeline-ui-tests-automation.yml new file mode 100644 index 0000000000..4db1005f3b --- /dev/null +++ b/.pipelines/v2/templates/pipeline-ui-tests-automation.yml @@ -0,0 +1,84 @@ +variables: + - name: runCodesignValidationInjectionBG + value: false + - name: EnablePipelineCache + value: true + - ${{ if eq(parameters.enableMsBuildCaching, true) }}: + - name: EnablePipelineCache + value: true + +parameters: + - name: buildPlatforms + type: object + default: + - x64 + - arm64 + - name: enableMsBuildCaching + type: boolean + default: false + - name: useVSPreview + type: boolean + default: false + - name: useLatestWebView2 + type: boolean + default: false + +stages: + - ${{ each platform in parameters.buildPlatforms }}: + - stage: Build_${{ platform }} + displayName: Build ${{ platform }} + dependsOn: [] + jobs: + - template: job-build-project.yml + parameters: + pool: + ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}: + name: SHINE-INT-L + ${{ else }}: + name: SHINE-OSS-L + ${{ if eq(parameters.useVSPreview, true) }}: + demands: ImageOverride -equals SHINE-VS17-Preview + buildPlatforms: + - ${{ platform }} + buildConfigurations: [Release] + enablePackageCaching: true + enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }} + runTests: false + buildTests: true + useVSPreview: ${{ parameters.useVSPreview }} + + - ${{ if eq(platform, 'x64') }}: + - stage: Test_x64Win10 + displayName: Test x64Win10 + dependsOn: + - Build_${{platform}} + jobs: + - template: job-test-project.yml + parameters: + platform: x64Win10 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + + - ${{ if eq(platform, 'x64') }}: + - stage: Test_x64Win11 + displayName: Test x64Win11 + dependsOn: + - Build_${{platform}} + jobs: + - template: job-test-project.yml + parameters: + platform: x64Win11 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + + - ${{ if ne(platform, 'x64') }}: + - stage: Test_${{ platform }} + displayName: Test ${{ platform }} + dependsOn: + - Build_${{platform}} + jobs: + - template: job-test-project.yml + parameters: + platform: ${{ platform }} + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} diff --git a/.pipelines/v2/templates/steps-build-installer-vnext.yml b/.pipelines/v2/templates/steps-build-installer-vnext.yml index ba286f2b9d..a877e783d3 100644 --- a/.pipelines/v2/templates/steps-build-installer-vnext.yml +++ b/.pipelines/v2/templates/steps-build-installer-vnext.yml @@ -36,12 +36,12 @@ steps: # This dll needs to be built and signed before building the MSI. - task: VSBuild@1 - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build PowerToysSetupCustomActions + displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build PowerToysSetupCustomActionsVNext inputs: solution: "**/installer/PowerToysSetup.sln" vsVersion: 17.0 msbuildArgs: >- - /t:PowerToysSetupCustomActions + /t:PowerToysSetupCustomActionsVNext /p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true -restore -graph /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-actions.binlog @@ -55,10 +55,10 @@ steps: - ${{ if eq(parameters.codeSign, true) }}: - template: steps-esrp-signing.yml parameters: - displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign PowerToysSetupCustomActions + displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign PowerToysSetupCustomActionsVNext signingIdentity: ${{ parameters.signingIdentity }} inputs: - FolderPath: 'installer/PowerToysSetupCustomActions/$(InstallerRelativePath)' + FolderPath: 'installer/PowerToysSetupCustomActionsVNext/$(InstallerRelativePath)' signType: batchSigning batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json' ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml' @@ -118,6 +118,7 @@ steps: solution: "**/installer/PowerToysSetup.sln" vsVersion: 17.0 msbuildArgs: >- + -restore /t:PowerToysBootstrapperVNext /p:PerUser=${{parameters.buildUserInstaller}};CIBuild=true /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-bootstrapper.binlog diff --git a/.pipelines/v2/templates/steps-fetch-and-prepare-localizations.yml b/.pipelines/v2/templates/steps-fetch-and-prepare-localizations.yml index 30cf2b6f67..44f8c4b6dc 100644 --- a/.pipelines/v2/templates/steps-fetch-and-prepare-localizations.yml +++ b/.pipelines/v2/templates/steps-fetch-and-prepare-localizations.yml @@ -4,7 +4,7 @@ parameters: default: false steps: - - task: TouchdownBuildTask@3 + - task: TouchdownBuildTask@5 displayName: 'Download Localization Files -- PowerToys 37400' inputs: teamId: 37400 diff --git a/.pipelines/v2/templates/steps-setup-versioning.yml b/.pipelines/v2/templates/steps-setup-versioning.yml new file mode 100644 index 0000000000..6dc0e3ef92 --- /dev/null +++ b/.pipelines/v2/templates/steps-setup-versioning.yml @@ -0,0 +1,11 @@ +parameters: + - name: directory + type: string + default: $(Build.SourcesDirectory) + +steps: + - pwsh: |- + nuget install Microsoft.Windows.Terminal.Versioning -ConfigFile "$(Build.SourcesDirectory)\.pipelines\release-nuget.config" -OutputDirectory _versioning + $VersionRoot = (Get-Item _versioning\Microsoft.Windows.*).FullName + & "$VersionRoot\build\Setup.ps1" -ProjectDirectory "${{ parameters.directory }}" -Verbose + displayName: Set up versioning for ${{ parameters.directory }} via M.W.T.V diff --git a/.pipelines/v2/templates/variables-nuget-package-version.yml b/.pipelines/v2/templates/variables-nuget-package-version.yml new file mode 100644 index 0000000000..460b7ceee0 --- /dev/null +++ b/.pipelines/v2/templates/variables-nuget-package-version.yml @@ -0,0 +1,17 @@ +variables: + # If we are building a branch called "stable*", hide the NuGet suffix. + # If we don't do that, XES will set the suffix to "stable". + # main is special, however. XES ignores main. Since we never produce actual + # shipping builds from main, we want to force it to have a beta label. + # + # In effect: + # BRANCH / BRANDING | Version | + # ------------------|----------------------------| + # stable | 0.2.250512001 | + # main | 0.2.250512001-experimental | + # all others | 0.2.250512001-branch | + ${{ if startsWith(variables['Build.SourceBranchName'], 'stable') }}: + NoNuGetPackBetaVersion: true + ${{ elseif eq(variables['Build.SourceBranchName'], 'main') }}: + NuGetPackBetaVersion: experimental + diff --git a/.pipelines/verifyAndSetLatestVCToolsVersion.ps1 b/.pipelines/verifyAndSetLatestVCToolsVersion.ps1 index a47e6a2292..4ebf3a3eec 100644 --- a/.pipelines/verifyAndSetLatestVCToolsVersion.ps1 +++ b/.pipelines/verifyAndSetLatestVCToolsVersion.ps1 @@ -1,7 +1,28 @@ $VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -include packages -format xml)) $VSPackages = $VSInstances.instances.instance.packages.package -$LatestVCPackage = ($VSInstances.instances.instance.packages.package | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" }) +$LatestVCPackage = ($VSPackages | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" }) $LatestVCToolsVersion = $LatestVCPackage.version; + +$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property 'resolvedInstallationPath') +$VCToolsRoot = Join-Path $VSRoot "VC\Tools\MSVC" + +# We have observed a few instances where the VC tools package version actually +# differs from the version on the files themselves. We might as well check +# whether the version we just found _actually exists_ before we use it. +# We'll use whichever highest version exists. +$PackageVCToolPath = Join-Path $VCToolsRoot $LatestVCToolsVersion +If ($Null -Eq (Get-Item $PackageVCToolPath -ErrorAction:Ignore)) { + $VCToolsVersions = Get-ChildItem $VCToolsRoot | ForEach-Object { + [Version]$_.Name + } | Sort -Descending + $LatestActualVCToolsVersion = $VCToolsVersions | Select -First 1 + + If ([Version]$LatestVCToolsVersion -Ne $LatestActualVCToolsVersion) { + Write-Output "VC Tools Mismatch: Directory = $LatestActualVCToolsVersion, Package = $LatestVCToolsVersion" + $LatestVCToolsVersion = $LatestActualVCToolsVersion.ToString(3) + } +} + Write-Output "Latest VCToolsVersion: $LatestVCToolsVersion" Write-Output "Updating VCToolsVersion environment variable for job" Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion" diff --git a/.pipelines/verifyDepsJsonLibraryVersions.ps1 b/.pipelines/verifyDepsJsonLibraryVersions.ps1 index e85ca1d991..085e1e439a 100644 --- a/.pipelines/verifyDepsJsonLibraryVersions.ps1 +++ b/.pipelines/verifyDepsJsonLibraryVersions.ps1 @@ -92,4 +92,3 @@ if ($totalFailures -gt 0) { Write-Host -ForegroundColor Green "All " $referencedFileVersionsPerDll.keys.Count " libraries are mentioned with the same version across the dependencies.`r`n" exit 0 - diff --git a/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1 b/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1 index ebef8412a7..e3120836c8 100644 --- a/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1 +++ b/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1 @@ -72,9 +72,57 @@ $returnList = [System.Collections.Generic.HashSet[string]]($totalList) -join "`r Write-Host $returnList +# Extract the current package list from NOTICE.md +$noticePattern = "## NuGet Packages used by PowerToys\s*((?:\r?\n- .+)+)" +$noticeMatch = [regex]::Match($noticeFile, $noticePattern) + +if ($noticeMatch.Success) { + $currentNoticePackageList = $noticeMatch.Groups[1].Value.Trim() +} else { + Write-Warning "Warning: Could not find 'NuGet Packages used by PowerToys' section in NOTICE.md" + $currentNoticePackageList = "" +} + if (!$noticeFile.Trim().EndsWith($returnList.Trim())) { Write-Host -ForegroundColor Red "Notice.md does not match NuGet list." + + # Show detailed differences + $generatedPackages = $returnList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | Sort-Object + $noticePackages = $currentNoticePackageList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | ForEach-Object { $_.Trim() } | Sort-Object + + Write-Host "" + Write-Host -ForegroundColor Cyan "=== DETAILED DIFFERENCE ANALYSIS ===" + Write-Host "" + + # Find packages in proj file list but not in NOTICE.md + $missingFromNotice = $generatedPackages | Where-Object { $noticePackages -notcontains $_ } + if ($missingFromNotice.Count -gt 0) { + Write-Host -ForegroundColor Red "MissingFromNotice:" + foreach ($pkg in $missingFromNotice) { + Write-Host -ForegroundColor Red " $pkg" + } + Write-Host "" + } + + # Find packages in NOTICE.md but not in proj file list + $extraInNotice = $noticePackages | Where-Object { $generatedPackages -notcontains $_ } + if ($extraInNotice.Count -gt 0) { + Write-Host -ForegroundColor Yellow "ExtraInNotice:" + foreach ($pkg in $extraInNotice) { + Write-Host -ForegroundColor Yellow " $pkg" + } + Write-Host "" + } + + # Show counts for summary + Write-Host -ForegroundColor Cyan "Summary:" + Write-Host " Proj file list has $($generatedPackages.Count) packages" + Write-Host " NOTICE.md has $($noticePackages.Count) packages" + Write-Host " MissingFromNotice: $($missingFromNotice.Count) packages" + Write-Host " ExtraInNotice: $($extraInNotice.Count) packages" + Write-Host "" + exit 1 } diff --git a/.pipelines/versionAndSignCheck.ps1 b/.pipelines/versionAndSignCheck.ps1 index 89cfbeea1c..1bb271300d 100644 --- a/.pipelines/versionAndSignCheck.ps1 +++ b/.pipelines/versionAndSignCheck.ps1 @@ -9,6 +9,7 @@ Param( $DirPath = $targetDir; #this file is in pipeline, we need root. $items = Get-ChildItem -Path $DirPath -File -Include *.exe, *.dll, *.ttf, PTCustomActions -Recurse -Force -ErrorAction SilentlyContinue $versionExceptions = @( + "AdaptiveCards.Templating.dll", "Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll", "Microsoft.Windows.ApplicationModel.Resources.Projection.dll", "Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll", @@ -28,6 +29,8 @@ $versionExceptions = @( "ObjectModelCsProjection.dll", "RendererCsProjection.dll") -join '|'; $nullVersionExceptions = @( + "SkiaSharp.Views.WinUI.Native.dll", + "libSkiaSharp.dll", "codicon.ttf", "e_sqlite3.dll", "getfilesiginforedist.dll", diff --git a/.pipelines/versionSetting.ps1 b/.pipelines/versionSetting.ps1 index bda3c47cc2..cf2d2595af 100644 --- a/.pipelines/versionSetting.ps1 +++ b/.pipelines/versionSetting.ps1 @@ -5,10 +5,7 @@ Param( [Parameter(Mandatory=$True,Position=2)] [AllowEmptyString()] - [string]$DevEnvironment = "Local", - - [Parameter(Mandatory=$True,Position=3)] - [string]$cmdPalVersionNumber = "0.0.1" + [string]$DevEnvironment = "Local" ) Write-Host $PSScriptRoot @@ -49,7 +46,6 @@ $verProps.Save($verPropWriteFileLocation); $verPropWriteFileLocation = $PSScriptRoot + '/../src/CmdPalVersion.props'; $verPropReadFileLocation = $verPropWriteFileLocation; [XML]$verProps = Get-Content $verPropReadFileLocation -$verProps.Project.PropertyGroup.CmdPalVersion = $cmdPalVersionNumber; $verProps.Project.PropertyGroup.DevEnvironment = $DevEnvironment; Write-Host "xml" $verProps.Project.PropertyGroup.Version $verProps.Save($verPropWriteFileLocation); @@ -90,12 +86,3 @@ $newPlusContextMenuAppManifestReadFileLocation = $newPlusContextMenuAppManifestW $newPlusContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0' Write-Host "NewPlusContextMenu version" $newPlusContextMenuAppManifest.Package.Identity.Version $newPlusContextMenuAppManifest.Save($newPlusContextMenuAppManifestWriteFileLocation); - -# Set package version in Package.appxmanifest -$cmdPalAppManifestWriteFileLocation = $PSScriptRoot + '/../src/modules/cmdpal/Microsoft.CmdPal.UI/Package.appxmanifest'; -$cmdPalAppManifestReadFileLocation = $cmdPalAppManifestWriteFileLocation; - -[XML]$cmdPalAppManifest = Get-Content $cmdPalAppManifestReadFileLocation -$cmdPalAppManifest.Package.Identity.Version = $cmdPalVersionNumber + '.0' -Write-Host "CmdPal Package version: " $cmdPalAppManifest.Package.Identity.Version -$cmdPalAppManifest.Save($cmdPalAppManifestWriteFileLocation); diff --git a/.vsconfig b/.vsconfig index 77ec8b0ffd..90abacd81c 100644 --- a/.vsconfig +++ b/.vsconfig @@ -9,6 +9,7 @@ "Microsoft.VisualStudio.Component.Windows10SDK.19041", "Microsoft.VisualStudio.Component.Windows10SDK.20348", "Microsoft.VisualStudio.Component.Windows10SDK.22621", + "Microsoft.VisualStudio.Component.Windows10SDK.26100", "Microsoft.VisualStudio.ComponentGroup.UWP.VC", "Microsoft.VisualStudio.Component.UWP.VC.ARM64", "Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre", diff --git a/COMMUNITY.md b/COMMUNITY.md index ccfa61decf..5f9a0a57ea 100644 --- a/COMMUNITY.md +++ b/COMMUNITY.md @@ -6,8 +6,8 @@ Names are in alphabetical order based on first name. ## High impact community members -### [@Aaron-Junker](https://github.com/Aaron-Junker) - [Aaron Junker](https://aaron-junker.github.io) -Aaron has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Aaron was the primary person for helping build the File Explorer preview pane handler for developer files. +### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch) +Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files. ### [@cgaarden](https://github.com/cgaarden) - [Christian Gaarden Gaardmark](https://www.onegreatworld.com) Christian contributed New+ utility @@ -21,7 +21,7 @@ Connor was the creator of Workspaces and helped create Command Palette (PowerToy ### [@damienleroy](https://github.com/damienleroy) - [Damien Leroy](https://www.linkedin.com/in/Damien-Leroy-b2734416a/) Damien has helped out by developing and contributing the Quick Accent utility. -### [@daverayment ](https://github.com/daverayment) - [David Rayment](https://www.linkedin.com/in/david-rayment-168b5251/) +### [@daverayment](https://github.com/daverayment) - [David Rayment](https://www.linkedin.com/in/david-rayment-168b5251/) Dave has helped improve the experience inside of Peek by adding in new features and fixing bugs. ### [@davidegiacometti](https://github.com/davidegiacometti) - [Davide Giacometti](https://www.linkedin.com/in/davidegiacometti/) @@ -117,10 +117,6 @@ PowerRename is from Chris's SmartRename and icon rendering for SVGs in File Expl PowerToys Awake is a tool to keep your computer awake. -### [@Niels9001](https://github.com/niels9001/) - [Niels Laute](https://nielslaute.com/) - -Niels has helped drive large sums of our update toward a new [consistent and modern UX](https://github.com/microsoft/PowerToys/issues/891). This includes the [launcher work](https://github.com/microsoft/PowerToys/issues/44), color picker UX update and [icon design](https://github.com/microsoft/PowerToys/issues/1118). - ### [@randyrants](https://github.com/randyrants) - [Randy Santossio](https://www.randyrants.com) Randy contributed Registry Preview and some very early conversations about keyboard remapping. @@ -184,11 +180,9 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter ## PowerToys core team -- [@crutkas](https://github.com/crutkas/) - Clint Rutkas - Lead - [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon - Lead -- [@nguyen-dows](https://github.com/nguyen-dows) - Christopher Nguyen - Product Manager - [@craigloewen-msft](https://github.com/craigloewen-msft) - Craig Loewen - Product Manager -- [@zhiwei-ms](https://github.com/zhiwei-ms) - Zhiwei Yu - Product Manager +- [@niels9001](https://github.com/niels9001/) - Niels Laute - Product Manager - [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev lead - [@yeelam-gordon](https://github.com/yeelam-gordon) - Gordon Lam - Dev lead - [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev lead @@ -204,6 +198,13 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter - [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev - [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev - [@vanzue](https://github.com/vanzue) - Kai Tao - Dev +- [@zadjii-msft](https://github.com/zadjii-msft) - Mike Griese - Dev +- [@khmyznikov](https://github.com/khmyznikov) - Gleb Khmyznikov - Dev +- [@chatasweetie](https://github.com/chatasweetie) - Jessica Earley-Cha - Dev +- [@MichaelJolley](https://github.com/MichaelJolley) - Michael Jolley - Dev +- [@Jaylyn-Barbee](https://github.com/Jaylyn-Barbee) - Jaylyn Barbee - Dev +- [@zateutsch](https://github.com/zateutsch) - Zach Teutsch - Dev +- [@crutkas](https://github.com/crutkas/) - Clint Rutkas - Overhead ## Former PowerToys core team members @@ -211,6 +212,7 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter - [@ethanfangg](https://github.com/ethanfangg) - Ethan Fang - Product Manager - [@plante-msft](https://github.com/plante-msft) - Connor Plante - Product Manager - [@joadoumie](https://github.com/joadoumie) - Jordi Adoumie - Product Manager +- [@nguyen-dows](https://github.com/nguyen-dows) - Christopher Nguyen - Product Manager - [@enricogior](https://github.com/enricogior) - Enrico Giordani - Dev Lead - [@bzoz](https://github.com/bzoz) - Bartosz Sosnowski - Dev - [@ivan100sic](https://github.com/ivan100sic) - Ivan Stošić - Dev diff --git a/Cpp.Build.props b/Cpp.Build.props index 06c22b45bf..5a4538f940 100644 --- a/Cpp.Build.props +++ b/Cpp.Build.props @@ -96,8 +96,8 @@ - 10.0.22621.0 - 10.0.22621.0 + 10.0.26100.0 + 10.0.26100.0 10.0.19041.0 diff --git a/Directory.Packages.props b/Directory.Packages.props index 36b715c403..6a0bba7415 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,8 @@ - + + @@ -31,31 +32,31 @@ - + - + - - - - - + + + + + - + - - + + - - + + @@ -69,31 +70,33 @@ + + - + - - - + + + - + - + - - + + - + - - - - + + + + @@ -101,12 +104,13 @@ - - - - - - + + + + + + + diff --git a/NOTICE.md b/NOTICE.md index d604be7d7f..bb03f9ade1 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -79,6 +79,43 @@ For more information, please refer to ### Calculator +#### exprtk + +We use the exprtk library (exprtk.hpp) to evaluate mathematical expressions. + +**Source**: [https://github.com/ArashPartow/exprtk](https://github.com/ArashPartow/exprtk) + +``` +MIT License + +Copyright (c) 1999-2024 Arash Partow + +https://www.partow.net/programming/exprtk/index.html + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +## Utility: PowerToys Run Built-in Extensions + +### Calculator + #### Mages We use the Mages NuGet package for calculating the result of expression. @@ -807,30 +844,25 @@ DEALINGS IN THE SOFTWARE. **Source**: https://github.com/kuba--/zip -This is free and unencumbered software released into the public domain. +All Rights Reserved. -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ## Utility: Measure tool @@ -1395,6 +1427,37 @@ EXHIBIT A -Mozilla Public License. ## Utility: Registry Preview +### HexBox.WinUI + +We use HexBox.WinUI to show a preview of binary values. + +**Source**: https://github.com/hotkidfamily/HexBox.WinUI + +``` +MIT License + +Copyright (c) 2019 Filip Jeremic +Copyright (c) 2024~2025 hotkidfamily@gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + ### Monaco Editor **Source**: https://github.com/Microsoft/monaco-editor @@ -1425,11 +1488,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` + ## NuGet Packages used by PowerToys - AdaptiveCards.ObjectModel.WinUI3 2.0.0-beta - AdaptiveCards.Rendering.WinUI3 2.1.0-beta -- AdaptiveCards.Templating 2.0.2 +- AdaptiveCards.Templating 2.0.5 - Appium.WebDriver 4.4.5 - Azure.AI.OpenAI 1.0.0-beta.17 - CommunityToolkit.Common 8.4.0 @@ -1453,26 +1517,27 @@ SOFTWARE. - Mages 3.0.0 - Markdig.Signed 0.34.0 - MessagePack 3.1.3 -- Microsoft.Bcl.AsyncInterfaces 9.0.4 +- Microsoft.Bcl.AsyncInterfaces 9.0.6 +- Microsoft.Bot.AdaptiveExpressions.Core 4.23.0 - Microsoft.CodeAnalysis.NetAnalyzers 9.0.0 -- Microsoft.Data.Sqlite 9.0.4 +- Microsoft.Data.Sqlite 9.0.6 - Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16 - Microsoft.DotNet.ILCompiler (A) -- Microsoft.Extensions.DependencyInjection 9.0.4 -- Microsoft.Extensions.Hosting 9.0.4 -- Microsoft.Extensions.Hosting.WindowsServices 9.0.4 -- Microsoft.Extensions.Logging 9.0.4 -- Microsoft.Extensions.Logging.Abstractions 9.0.4 +- Microsoft.Extensions.DependencyInjection 9.0.6 +- Microsoft.Extensions.Hosting 9.0.6 +- Microsoft.Extensions.Hosting.WindowsServices 9.0.6 +- Microsoft.Extensions.Logging 9.0.6 +- Microsoft.Extensions.Logging.Abstractions 9.0.6 - Microsoft.NET.ILLink.Tasks (A) - Microsoft.SemanticKernel 1.15.0 - Microsoft.Toolkit.Uwp.Notifications 7.1.2 - Microsoft.Web.WebView2 1.0.2903.40 -- Microsoft.Win32.SystemEvents 9.0.4 -- Microsoft.Windows.Compatibility 9.0.4 -- Microsoft.Windows.CsWin32 0.2.46-beta +- Microsoft.Win32.SystemEvents 9.0.6 +- Microsoft.Windows.Compatibility 9.0.6 +- Microsoft.Windows.CsWin32 0.3.183 - Microsoft.Windows.CsWinRT 2.2.0 -- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428 -- Microsoft.WindowsAppSDK 1.7.250401001 +- Microsoft.Windows.SDK.BuildTools 10.0.26100.4188 +- Microsoft.WindowsAppSDK 1.7.250513003 - Microsoft.WindowsPackageManager.ComInterop 1.10.340 - Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9 - Microsoft.Xaml.Behaviors.Wpf 1.1.39 @@ -1485,27 +1550,28 @@ SOFTWARE. - ReverseMarkdown 4.1.0 - ScipBe.Common.Office.OneNote 3.0.1 - SharpCompress 0.37.2 +- SkiaSharp.Views.WinUI 2.88.9 - StreamJsonRpc 2.21.69 - StyleCop.Analyzers 1.2.0-beta.556 -- System.CodeDom 9.0.4 +- System.CodeDom 9.0.6 - System.CommandLine 2.0.0-beta4.22272.1 -- System.ComponentModel.Composition 9.0.4 -- System.Configuration.ConfigurationManager 9.0.4 -- System.Data.OleDb 9.0.4 -- System.Data.SqlClient 4.8.6 -- System.Diagnostics.EventLog 9.0.4 -- System.Diagnostics.PerformanceCounter 9.0.4 -- System.Drawing.Common 9.0.4 +- System.ComponentModel.Composition 9.0.6 +- System.Configuration.ConfigurationManager 9.0.6 +- System.Data.OleDb 9.0.6 +- System.Data.SqlClient 4.9.0 +- System.Diagnostics.EventLog 9.0.6 +- System.Diagnostics.PerformanceCounter 9.0.6 +- System.Drawing.Common 9.0.6 - System.IO.Abstractions 22.0.13 - System.IO.Abstractions.TestingHelpers 22.0.13 -- System.Management 9.0.4 +- System.Management 9.0.6 - System.Net.Http 4.3.4 - System.Private.Uri 4.3.2 - System.Reactive 6.0.1 -- System.Runtime.Caching 9.0.4 -- System.ServiceProcess.ServiceController 9.0.4 -- System.Text.Encoding.CodePages 9.0.4 -- System.Text.Json 9.0.4 +- System.Runtime.Caching 9.0.6 +- System.ServiceProcess.ServiceController 9.0.6 +- System.Text.Encoding.CodePages 9.0.6 +- System.Text.Json 9.0.6 - System.Text.RegularExpressions 4.3.1 - UnicodeInformation 2.6.0 - UnitsNet 5.56.0 diff --git a/PowerToys.sln b/PowerToys.sln index 201c0fbefe..f81a222668 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -604,6 +604,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowProperties", "WindowP EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLib", "src\modules\Workspaces\WorkspacesLib\WorkspacesLib.vcxproj", "{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLibUnitTests", "src\modules\Workspaces\WorkspacesLib.UnitTests\WorkspacesLibUnitTests.vcxproj", "{A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}" + ProjectSection(ProjectDependencies) = postProject + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} = {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} + EndProjectSection +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesLauncherUI", "src\modules\Workspaces\WorkspacesLauncherUI\WorkspacesLauncherUI.csproj", "{9C53CC25-0623-4569-95BC-B05410675EE3}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesModuleInterface", "src\modules\Workspaces\WorkspacesModuleInterface\WorkspacesModuleInterface.vcxproj", "{45285DF2-9742-4ECA-9AC9-58951FC26489}" @@ -706,10 +711,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegistryPreview.FuzzTests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.System", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.System\Microsoft.CmdPal.Ext.System.csproj", "{64B88F02-CD88-4ED8-9624-989A800230F9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZones.FuzzTests", "src\modules\fancyzones\FancyZones.FuzzTests\FancyZones.FuzzTests.csproj", "{0217E86E-3476-9946-DE8E-9D200CEBD47A}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "src\modules\cmdpal\CmdPalKeyboardService\CmdPalKeyboardService.vcxproj", "{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MouseUtils.UITests", "src\modules\MouseUtils\MouseUtils.UITests\MouseUtils.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesEditorUITest", "src\modules\Workspaces\WorkspacesEditorUITest\WorkspacesEditorUITest.csproj", "{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalculatorEngineCommon", "src\common\CalculatorEngineCommon\CalculatorEngineCommon.vcxproj", "{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCsWin32", "src\common\ManagedCsWin32\ManagedCsWin32.csproj", "{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2198,6 +2213,14 @@ Global {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.Build.0 = Release|ARM64 {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.ActiveCfg = Release|x64 {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.Build.0 = Release|x64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|ARM64.Build.0 = Debug|ARM64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|x64.ActiveCfg = Debug|x64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|x64.Build.0 = Debug|x64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|ARM64.ActiveCfg = Release|ARM64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|ARM64.Build.0 = Release|ARM64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|x64.ActiveCfg = Release|x64 + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|x64.Build.0 = Release|x64 {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.ActiveCfg = Debug|ARM64 {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.Build.0 = Debug|ARM64 {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.ActiveCfg = Debug|x64 @@ -2582,6 +2605,30 @@ Global {64B88F02-CD88-4ED8-9624-989A800230F9}.Release|ARM64.Build.0 = Release|ARM64 {64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.ActiveCfg = Release|x64 {64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.Build.0 = Release|x64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64 + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64 + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.Build.0 = Debug|x64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.ActiveCfg = Release|ARM64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.Build.0 = Release|ARM64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.ActiveCfg = Release|x64 + {0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.Build.0 = Release|x64 {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.ActiveCfg = Debug|ARM64 {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.Build.0 = Debug|ARM64 {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|x64.ActiveCfg = Debug|x64 @@ -2596,6 +2643,22 @@ Global {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|ARM64.ActiveCfg = Release|ARM64 {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.ActiveCfg = Release|x64 {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.Build.0 = Release|x64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.Build.0 = Debug|ARM64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.ActiveCfg = Debug|x64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.Build.0 = Debug|x64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|ARM64.ActiveCfg = Release|ARM64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|ARM64.Build.0 = Release|ARM64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|x64.ActiveCfg = Release|x64 + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|x64.Build.0 = Release|x64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|ARM64.Build.0 = Debug|ARM64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|x64.ActiveCfg = Debug|x64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|x64.Build.0 = Debug|x64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|ARM64.ActiveCfg = Release|ARM64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|ARM64.Build.0 = Release|ARM64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.ActiveCfg = Release|x64 + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2735,7 +2798,7 @@ Global {25C91A4E-BA4E-467A-85CD-8B62545BF674} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5} {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5} {212AD910-8488-4036-BE20-326931B75FB2} = {4AFC9975-2456-4C70-94A4-84073C1CED93} - {7AC943C9-52E8-44CF-9083-744D8049667B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {7AC943C9-52E8-44CF-9083-744D8049667B} = {322566EF-20DC-43A6-B9F8-616AF942579A} {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A} = {7AC943C9-52E8-44CF-9083-744D8049667B} {92C39820-9F84-4529-BC7D-22AAE514D63B} = {7AC943C9-52E8-44CF-9083-744D8049667B} {515554D1-D004-4F7F-A107-2211FC0F6B2C} = {7AC943C9-52E8-44CF-9083-744D8049667B} @@ -2815,6 +2878,7 @@ Global {BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {9C53CC25-0623-4569-95BC-B05410675EE3} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {45285DF2-9742-4ECA-9AC9-58951FC26489} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} @@ -2866,8 +2930,13 @@ Global {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} {5702B3CC-8575-48D5-83D8-15BB42269CD3} = {929C1324-22E8-4412-A9A8-80E85F3985A5} {64B88F02-CD88-4ED8-9624-989A800230F9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2} + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {322566EF-20DC-43A6-B9F8-616AF942579A} + {43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {0217E86E-3476-9946-DE8E-9D200CEBD47A} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79} {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} + {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482} + {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/README.md b/README.md index 1a0d80dc00..232cf6fd82 100644 --- a/README.md +++ b/README.md @@ -35,28 +35,28 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user. -[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.91%22 -[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.90%22 -[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysUserSetup-0.90.0-x64.exe -[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysUserSetup-0.90.0-arm64.exe -[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysSetup-0.90.0-x64.exe -[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.90.0/PowerToysSetup-0.90.0-arm64.exe +[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.92%22 +[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.91%22 +[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.1/PowerToysUserSetup-0.91.1-x64.exe +[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.1/PowerToysUserSetup-0.91.1-arm64.exe +[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.1/PowerToysSetup-0.91.1-x64.exe +[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.91.1/PowerToysSetup-0.91.1-arm64.exe | Description | Filename | sha256 hash | |----------------|----------|-------------| -| Per user - x64 | [PowerToysUserSetup-0.90.0-x64.exe][ptUserX64] | 2A6036F5B2D454084E55816C306E1E57EF1D14C916691CBDA42B469797605CE0 | -| Per user - ARM64 | [PowerToysUserSetup-0.90.0-arm64.exe][ptUserArm64] | AB2E4DC87A9D764BE897C5170E2890E174C89CA912A1916FA3AE1E427536EA4A | -| Machine wide - x64 | [PowerToysSetup-0.90.0-x64.exe][ptMachineX64] | 12801C44F43D0CC61E90DF1EFDC40E4F3C88341E0199D5B20791042D9B173DCF | -| Machine wide - ARM64 | [PowerToysSetup-0.90.0-arm64.exe][ptMachineArm64] | 2998007C8FCD7BD2770767C6502AAA2CC75B85EC30DE62986EC7005EB0014EDB | +| Per user - x64 | [PowerToysUserSetup-0.91.1-x64.exe][ptUserX64] | 42EA4A3E8C79A5456476D19E72B3E2AB9393A89C4DC7442EB7EE5A1E3490D38A | +| Per user - ARM64 | [PowerToysUserSetup-0.91.1-arm64.exe][ptUserArm64] | F3F433FE04049F9197411D792AADEBF34E3BE7FE16327BD8B73D2A046ED8BAF6 | +| Machine wide - x64 | [PowerToysSetup-0.91.1-x64.exe][ptMachineX64] | EC4BC3A8625775866B0ED4577CCF83E6EC7B1A0AD267379DDBAF4FE49C7B5BDD | +| Machine wide - ARM64 | [PowerToysSetup-0.91.1-arm64.exe][ptMachineArm64] | 9CB8911008420D0E446AE3D5CE365E447FA4DF9DCF9337F3A80F933C00FC3689 | This is our preferred method. ### Via Microsoft Store -Install from the [Microsoft Store's PowerToys page][microsoft-store-link]. You must be using the [new Microsoft Store](https://blogs.windows.com/windowsExperience/2021/06/24/building-a-new-open-microsoft-store-on-windows-11/) which is available for both Windows 11 and Windows 10. +Install from the [Microsoft Store's PowerToys page][microsoft-store-link]. You must be using the [new Microsoft Store](https://blogs.windows.com/windowsExperience/2021/06/24/building-a-new-open-microsoft-store-on-windows-11/), which is available for both Windows 11 and Windows 10. ### Via WinGet -Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell: +Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell: #### User scope installer [default] ```powershell @@ -79,7 +79,7 @@ There is a collection of [third-party plugins](./doc/thirdPartyRunPlugins.md) cr ## Contributing -This project welcomes contributions of all types. Besides coding features / bug fixes, other ways to assist include spec writing, design, documentation, and finding bugs. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows. +This project welcomes contributions of all types. Besides coding features / bug fixes, other ways to assist include spec writing, design, documentation, and finding bugs. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows. We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](CONTRIBUTING.md). We would be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort. @@ -93,96 +93,168 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/ Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on. -### 0.90 - March 2025 Update +### 0.91 - May 2025 Update In this release, we focused on new features, stability, and automation. **✨Highlights** -![Gif for Command Palette](doc/images/overview/CmdPal_Hero.gif) + - We focused on greatly improving the Command Palette's performance and fixing a large number of bugs. Some new features we've added are: + - Added the ability for Command Palette to search any file using a fallback command. + - Added the ability to make the Command Palette global hotkey a low-level keyboard hook. + - Added open URL fallback command for the WebSearch extension, enabling users to directly open URLs in the browser from Command Palette. + - You can now define custom formats in the Date and Time plugins of PT Run and Command Palette. Thanks [@htcfreek](https://github.com/htcfreek)! -- New module: Command Palette ("CmdPal") - Created as the evolution of PowerToys Run with extensibility at the forefront, Command Palette is a quick launcher with a richer display and additional capabilities without sacrificing performance, allowing you to start anything with the shortcut **Win+Alt+Space**! Thanks [@zadjii-msft](https://github.com/zadjii-msft), [@niels9001](https://github.com/niels9001), [@michael-hawker](https://github.com/michael-hawker), [@joadoumie](https://github.com/joadoumie), [@plante-msft](https://github.com/plante-msft), [@ethanfangg](https://github.com/ethanfangg) and [@krschau](https://github.com/krschau)! - - Enhanced the Color Picker by switching from WPF UI to .NET WPF, resulting in improved themes and visual consistency across different modes. Thanks [@mantaionut](https://github.com/mantaionut)! Thanks [@Jay-o-Way](https://github.com/Jay-o-Way) and [@niels9001](https://github.com/niels9001) for helping with the review! - - Added the ability to delete files directly from Peek, enhancing file management efficiency. Thanks [@daverayment](https://github.com/daverayment) and thanks [@htcfreek](https://github.com/htcfreek) for the review! - - Added support for variables in template filenames, enabling dynamic elements like date components and environment variables for enhanced customization in New+. Thanks [@cgaarden](https://github.com/cgaarden)! +### Advanced Paste + + - Fixed an issue where Advanced Paste failed to create the OCR engine for certain English language tags (e.g., en-CA) by initializing the OCR engine with the user profile language. Thanks [@cryolithic](https://github.com/cryolithic)! ### Color Picker - - Replaced WPF UI with .NET WPF for the Color Picker, enhancing compatibility and improving theme support. Thanks [@mantaionut](https://github.com/mantaionut)! Thanks [@Jay-o-Way](https://github.com/Jay-o-Way) and [@niels9001](https://github.com/niels9001) for helping with the review! + - Fixed an issue where a resource leak caused hangs or crashes by properly disposing of the Graphics object. Thanks [@dcog989](https://github.com/dcog989)! + - Fixed an issue where Color Picker exited on Backspace keypress by ensuring it only closes when focused and aligning Escape/Backspace behavior. Thanks [@PesBandi](https://github.com/PesBandi)! + - Added support for Oklab and Oklch color formats in Color Picker. Thanks [@lemonyte](https://github.com/lemonyte)! + +### Command Not Found + + - Updated the WinGet Command Not Found script to only enable the experimental features if they exist. ### Command Palette -- Introduced the Windows Command Palette ("CmdPal"), the next iteration of PowerToys Run, designed with extensibility at its core. CmdPal includes features such as searching for installed apps, shell commands, files and WinGet package installation. This module aims to provide a more powerful and flexible launcher experience. Thanks [@zadjii-msft](https://github.com/zadjii-msft), [@niels9001](https://github.com/niels9001), [@michael-hawker](https://github.com/michael-hawker), [@joadoumie](https://github.com/joadoumie), [@plante-msft](https://github.com/plante-msft), and the whole team! - -### FancyZones - - - Fixed a bug where deleting a layout resulted in incorrect data being written to the JSON file. - - Fixed a bug where layout hotkeys were displayed incorrectly, ensuring the hotkey list does not include invalid entries. - - Fixed an issue where the "None" option was missing in the editor layout. + - Updated bug template to include Command Palette module. + - Fixed an issue where the toast window was not scaled for DPI, causing layout issues under display scaling. + - Fixed an issue where Up/Down keyboard navigation didn't move selection when the caret was at position 0, and added continuous navigation like PT Run v1. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Updated the Time and Date extension code to simplify it and improve clarity. + - Fixed an issue where capitalization in the command causes failure when trying to go to the mouse pointer, resolved by adjusting the command to lowercase. + - Added open URL fallback command for the WebSearch extension, enabling users to directly open URLs in the browser from the Command Palette. Thanks [@htcfreek](https://github.com/htcfreek)! + - Added setting to enable/disable system tray icon in CmdPal and aligned terminology with Windows 11. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed an alias update issue by removing the old alias when a new one is set. + - Resolved GitHub casing conflict by migrating Exts and exts into a new ext directory, ensuring consistent structure across platforms and preventing path fragmentation. + - Fix an issue where the 'Create New Extension' command generated empty file names. + - Added the ability to make the global hotkey a low-level keyboard hook. + - Added support for JUMBO thumbnails, enabling access to high-resolution icons. + - Fixed crashes when CmdPal auto-hid itself while an MSAL dialog was opened, by preventing CmdPal from hiding if it's disabled. + - Added support for immediately selecting search text when a page is loaded. + - Fixed a bug where extension settings pages failed to reload on reopen by updating the settings form when extension settings are saved. + - Fixed an issue where the Command Palette failed to launch from the runner. + - Refactored and ported the PowerToys Run v1 calculator logic into Command Palette, added settings support, and improved fallback behavior. + - Re-added support for list item keyboard shortcuts. + - Enhanced accessibility in Command Palette by adding proper labels, refining animations, improving localization, and fixed a11y related issues. + - Ported custom format support to the Time and Date plugin, reordered and cleaned up settings, improved error messaging, and fixed edge-case crashes for more robust and user-friendly behavior. Thanks [@htcfreek](https://github.com/htcfreek)! + - Added fallback item for system command. + - Fixed a bug in Windows System Command where the key prompt incorrectly displayed "Empty" for the "Open Recycle Bin" action. Thanks [@jironemo](https://github.com/jironemo)! + - Fixed an issue where the 'more commands' list showed commands that shouldn't be visible. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed an issue where the details view in Command Palette displayed an oversized icon and misaligned text, aligning it with Windows Search behavior. + - Fixed a bug where empty screen content and command bar commands were cut off when using long labels, ensuring proper layout and visibility. + - Improved CmdPal’s WinGet integration by fixing version display for installed packages, enabling updates with icons, and migrating the preview winget API to a stable version. + - Fixed a bug where commands for ContentPage didn't update until after exit, by ensuring context menus are fully initialized when they change. + - Added fallback support to the TimeDate extension, enabling direct date/time queries without pre-selecting the command. + - Added import of Common.Dotnet.AotCompatibility.props across multiple CmdPal project files to enhance AOT compilation support. + - Fixed a crash in CmdPal settings caused by a null HotKey when settings.json is missing or lacks a defined hotkey. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Added support for filterable, nested context menus in CmdPal, including a search box to maintain focus behavior. + - Refactored CmdPal classes to improve JSON serialization and introduced new serialization contexts for better performance and maintainability. + - Added support for ahead-of-time (AoT) compilation. + - Added retry mechanism for CmdPal launch. + - Removed some unused files from CmdPal.Common to simplify the codebase and facilitate marking it as AoT-compatible. + - Fixed a bug where a race condition in the update of SearchText caused the cursor in the input box to automatically jump to the end of the line, ensuring SearchText is only updated after it has actually been changed. + - Added support for searching any file in the fallback command. + - Cleaned up AoT-related code to prevent duplicate operations during testing. + - Reduced CmdPal load time by parallelizing extension startup and adding timeouts to prevent misbehaving extensions from blocking others. + - Enhanced UI behavior by dismissing the details pane when the list gets emptied, avoiding inconsistent visual states. + - Added support to unset the fallback command in CmdPal when no matching command is found, ensuring cleaner reload behavior. + - Fixed a leak in the CmdPal extension template by addressing improper ComServer use. + - Prevented CmdPal window from maximizing on title bar double-click to maintain intended window behavior. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed an issue where the Settings UI launched too small by making window dimensions DPI-aware and enforcing minimum width and height using WinUIEx. + - Fixed white flash and one-time animation issues in CmdPal by cloaking the window instead of hiding it. + - Fixed a bug where all extension settings were fetched on startup by lazy-loading extension settings, reducing initialization overhead. + - Added support for protecting CmdPal from crashes on Adaptive Card parse failure. + - Replaced shell:AppsFolder with URI activation in CmdPal to improve reliability. + - Added ability to open CmdPal settings from PowerToys Settings. + - Added ability for CmdPal to observe and dynamically update extension details by tracking property changes on the selected item. + - Bumped the toolkit version used in the CmdPal extension template to 0.2.0. ### Image Resizer - - Fixed warnings in ImageResizer regarding the use of variables "shellItem" and "itemName" without being initialized. + - Fixed an issue where deleting an Image Resizer preset removed the wrong preset. -### Mouse Without Borders +### Keyboard Manager - - Enhanced the logger to properly track the file path for easier debugging. - - Refactored the "Common" class into distinct individual classes to enhance maintainability, and updated all references and unit tests to reflect these changes. Thanks [@mikeclayton](https://github.com/mikeclayton) for this! + - Fixed an issue where a modifier key, when set without specifying left or right, would get stuck due to incorrect key handling by tracking the pressed keys and sending the correct key accordingly. Thanks [@mantaionut](https://github.com/mantaionut)! -### New+ +### PowerRename - - Added support for variables in template filenames, including date/time components, parent folder name, and environment variables. Thanks [@cgaarden](https://github.com/cgaarden)! - -### Peek - - - Added the ability to delete the file currently being previewed in Peek, including navigation updates and handling for deleted items. Thanks [@daverayment](https://github.com/daverayment) and thanks [@htcfreek](https://github.com/htcfreek) for your help reviewing this! + - Enhanced PowerRename's time formatting capabilities by adding 12-hour time format patterns with AM/PM support. Thanks [@bitmap4](https://github.com/bitmap4)! ### PowerToys Run - - Fixed an issue where duplicated applications were shown by ensuring the shell link helper opens .ink files non-exclusively and correctly retrieves the "FullPath". Thanks [@htcfreek](https://github.com/htcfreek) and [@davidegiacometti](https://github.com/davidegiacometti) for review! - - Fixed an issue where applying round corners on Windows 11 build 22000 caused crashes. - - Async the OnRename method to unblock the thread. Thanks [@davidegiacometti](https://github.com/davidegiacometti) for review! - - Added support for using `sq` instead of `^2` in the Unit Converter. Thanks [@PesBandi](https://github.com/PesBandi)! + - Added support for custom formats in the "Time and Date" plugin and improved error messages for invalid input formats. Thanks [@htcfreek](https://github.com/htcfreek)! + - Fix two crashes: one for WFT on very early dates and another for calculating the week of the month on very late dates (e.g., 31.12.9999), and reorder UI settings. Thanks [@htcfreek](https://github.com/htcfreek)! + - Fix an issue where capitalization in the command causes failure when trying to go to the mouse pointer, resolved by adjusting the command to lowercase. + - Added version details to plugin error messages for 'Loading error' and 'Init error'. Thanks [@htcfreek](https://github.com/htcfreek)! + - Enhanced result model by adding support for preventing usage-based ordering, giving plugin developers greater control over sorting behavior. Thanks [@CoreyHayward](https://github.com/CoreyHayward) and [@htcfreek](https://github.com/htcfreek)! + +### Quick Accent + + - Updated the letter mapping in GetDefaultLetterKeyEPO, replacing "ǔ" with "ŭ" for the VK_U key to accurately reflect Esperanto phonetics. Thanks [@OlegKharchevkin](https://github.com/OlegKharchevkin)! + - Fixed an issue where Quick Accent did not work properly when using the on-screen keyboard. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + +### Registry Preview + + - Enhanced Registry Preview to support pasting registry keys and values without manually writing the file header, and added a new button for resetting the app. Thanks [@htcfreek](https://github.com/htcfreek)! ### Settings - - Disabled the spell check feature in the text boxes of plugin settings for PowerToys Run. Thanks [@htcfreek](https://github.com/htcfreek)! - - Fixed an issue where InfoBars for release notes errors were not displayed properly, and added a retry button. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fix an issue where the Settings app randomly showed a blank icon in the taskbar by deferring icon assignment until the window is activated. + - Added the ability to maximize the "What's New" window for a more comfortable reading experience. ### Workspaces - - Fixed an issue where some minimized packaged apps (e.g., Microsoft ToDo, Settings) were not snapshotted. + - Fixed bugs where Steam games were not captured or launched correctly by updating window filtering and integrating Steam URL protocol handling. ### Documentation - - Added the FirefoxBookmark plugin to the list of Third-Party plugins for PowerToys Run. Thanks [@8LWXpg](https://github.com/8LWXpg)! - - Added the SVGL third-party plugin to PowerToys Run, enabling users to search, browse, and copy SVG logos. Thanks [@SameerJS6](https://github.com/SameerJS6)! - - Added Monaco usage for the Registry Preview. + - Added QuickNotes to the third-party plugins documentation for PowerToys Run. Thanks [@ruslanlap](https://github.com/ruslanlap)! + - Added Weather and Pomodoro plugins to the PowerToys Run third-party plugin documentation. Thanks [@ruslanlap](https://github.com/ruslanlap)! + - Added the Linear plugin to PowerToys Run's third-party plugin documentation. Thanks [@vednig](https://github.com/vednig)! + - Fixed formatting issues in documentation files and updated contributor and team member information. Thanks [@DanielEScherzer](https://github.com/DanielEScherzer) and [@RokyZevon](https://github.com/RokyZevon)! ### Development - - Updated WinGet configuration file location and extension. Thanks [@mdanish-kh](https://github.com/mdanish-kh)! - - Removed the Markdown file bypass to ensure CI runs for commits that only update Markdown files. - - Fixed an issue where the default generated file path exceeded the length limit of 260 characters for EnvironmentVariablesUILib.csproj, causing build failures. - - Upgraded WindowsAppSDK to 1.6.250205002 and CsWinRT to 2.2.0. Thanks [@htcfreek](https://github.com/htcfreek) for review! - - Upgraded XamlStyler to 3.2501.8 and dotnet-consolidate to 4.2.0. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Updated .NET Packages from 9.0.2 to 9.0.3. - - Optimized the UI Test Automation Framework and added UI test cases for the Hosts File Editor module. - - Added fuzz testing for RegistryPreview. - - Added new UI tests for the FancyZones editor, including tests for creating, duplicating, editing, and deleting layouts. - - Added telemetry code to measure the module editor open time and evaluate the benefits of applying AOT. + - Updated GitHub Action to install .NET 9 for MSStore release support. + - Updated version placeholder in bug_report.yml to prevent incorrect v0.70.0 versioning in issue reports. + - Updated GitHub Action to upgrade actions/setup-dotnet from version 3 to version 4 for MSStore release. + - Added securityContext to WinGet configuration files, allowing invocation from user context and prompting a single UAC for elevated resources in a separate process. Thanks [@mdanish-kh](https://github.com/mdanish-kh)! + - Changed log file extensions from .txt to .log to support proper file associations and tooling compatibility, and added logs for Workspace. Thanks [@benwa](https://github.com/benwa)! + - Upgraded testing framework dependencies and aligned package versions across components. + - Upgraded dependencies to fix vulnerabilities. + - Enhanced repository security by pinning GitHub Actions and Docker tags to immutable full-length commits and integrating automated dependency vulnerability scanning via Dependency Review Workflow. Thanks [@Nick2bad4u](https://github.com/Nick2bad4u)! + - Upgraded Boost dependencies to a newer version. + - Upgraded toolkit to the latest version, suppressed AoT-related warnings. + - Fixed an issue where missing signing for newly added files caused build failures. + - Update release pipeline to prevent publishing private symbols for 100 years. + - Introduced fuzzing for PowerRename to improve reliability and added setup guidance for extending fuzzing to other C++ modules. + - Added centralized pre-creation of generated folders for all .csproj projects to prevent build failures. + - Updated WinAppSDK to the latest 1.7 version. + - Upgraded Boost dependencies to the latest version for the PowerRename Fuzzing project. + - Updated the ADO area path in tsa.json to resolve TSA pipeline errors caused by a deprecated path. + - Initiated AoT support for CmdPal with foundational work in progress. + +### Tool/General + - Added support for automating bug report creation by generating a pre-filled GitHub issue URL with system and diagnostic information. Thanks [@donlaci](https://github.com/donlaci)! + - Added scripts to locally build the installer, ensuring the CmdPal can also be launched in a local environment. + - Removed export PFX logic to eliminate hardcoded password usage and resolve PSScriptAnalyzer security warning. + - Added PowerShell script and CI integration to enforce consistent use of Common.Dotnet.CsWinRT.props across all C# projects under the src folder. + +### What is being planned for version 0.92 - -### What is being planned for version 0.91 +For [v0.92][github-next-release-work], we'll work on the items below: -For [v0.91][github-next-release-work], we'll work on the items below: - - - New module: File Actions Menu + - Continued Command Palette polish - New UI Automation tests - Working on installer upgrades - Upgrading Keyboard Manager's editor UI - - Stability / bug fixes + - Stability, bug fixes ## PowerToys Community @@ -194,7 +266,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct][oss-conduct ## Privacy Statement -The application logs basic diagnostic data (telemetry). For more information on privacy and what we collect, see our [PowerToys Data and Privacy documentation](https://aka.ms/powertoys-data-and-privacy-documentation). +The application logs basic diagnostic data (telemetry). For more privacy information and what we collect, see our [PowerToys Data and Privacy documentation](https://aka.ms/powertoys-data-and-privacy-documentation). [oss-CLA]: https://cla.opensource.microsoft.com [oss-conduct-code]: CODE_OF_CONDUCT.md diff --git a/deps/cziplib b/deps/cziplib index 7a57414261..81314fff0a 160000 --- a/deps/cziplib +++ b/deps/cziplib @@ -1 +1 @@ -Subproject commit 7a57414261361ca991ff8053881343eb6bb6f205 +Subproject commit 81314fff0a882b72a9ad321e7a3311660125b56e diff --git a/doc/devdocs/UITests.md b/doc/devdocs/UITests.md index b1b91a632c..d02eeb6993 100644 --- a/doc/devdocs/UITests.md +++ b/doc/devdocs/UITests.md @@ -21,67 +21,74 @@ - Create a new project and add the following references to the project file. Change the OutputPath to your own module's path. ``` - - Library - - false - - - - ..\..\..\..\$(Platform)\$(Configuration)\tests\KeyboardManagerUITests\ - - - - - - - + + + + + + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} + PowerToys.Hosts.UITests + PowerToys.Hosts.UITests + false + true + enable + Library + + + false + + + $(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\ + + + + + + + + ``` - Inherit your test class from UITestBase. >Set Scope: The default scope starts from the PowerToys settings UI. If you want to start from your own module, set the constructor as shown below: >Specify Scope: ``` - [TestClass] - public class RunFancyZonesTest : UITestBase - { - public RunFancyZonesTest() - : base(PowerToysModule.FancyZone) - { - } - } + [TestClass] + public class HostModuleTests : UITestBase + { + public HostModuleTests() + : base(PowerToysModule.Hosts, WindowSize.Small_Vertical) + { + } + } ``` -- Then you can start using session to perform the UI operations. +- Then you can start performing the UI operations. **Example** ``` -using Microsoft.PowerToys.UITest; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace UITests_KeyboardManager +[TestMethod("Hosts.Basic.EmptyViewShouldWork")] +[TestCategory("Hosts File Editor #4")] +public void TestEmptyView() { - [TestClass] - public class RunKeyboardManagerUITests : UITestBase - { - [TestMethod] - public void OpenKeyboardManagerEditor() - { - // Open KeyboardManagerEditor - this.Session.Find + + + + + + + + + (new(commandViewModel.Model)); + } + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml index dc297df841..ab221daaa9 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml @@ -1,4 +1,4 @@ - + - + + IsOn="{x:Bind IsDirectAlias, Mode=TwoWay}" /> diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw index 6c63eeff16..11f1106250 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw @@ -412,4 +412,16 @@ Right-click to remove the key combination, thereby deactivating the shortcut. More + + Settings + + + Exit + + + Direct + + + Indirect + \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/TextBlock.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/TextBlock.xaml new file mode 100644 index 0000000000..6160585127 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Styles/TextBlock.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/app.manifest b/src/modules/cmdpal/Microsoft.CmdPal.UI/app.manifest index 0cd29eccd5..f5dd7ff036 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/app.manifest +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/app.manifest @@ -13,6 +13,7 @@ + true/PM PerMonitorV2 diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/rd.xml b/src/modules/cmdpal/Microsoft.CmdPal.UI/rd.xml new file mode 100644 index 0000000000..f8dc5641af --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/rd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj index 69e3c64635..4f13f17ed6 100644 --- a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj +++ b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj @@ -1,9 +1,9 @@ - + ..\..\..\..\ - $(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250401001 + $(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250513003 @@ -19,13 +19,10 @@ false Windows Store 10.0 - 10.0.22621.0 + 10.0.26100.0 10.0.19041.0 - - - Debug @@ -156,7 +153,6 @@ IconPathConverter.idl - ResourceString.idl @@ -205,9 +201,4 @@ - - - {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} - - - \ No newline at end of file + diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/resource.h b/src/modules/cmdpal/Microsoft.Terminal.UI/resource.h deleted file mode 100644 index 85ae1a0e9b..0000000000 --- a/src/modules/cmdpal/Microsoft.Terminal.UI/resource.h +++ /dev/null @@ -1,13 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by AlwaysOnTopModuleInterface.rc - -////////////////////////////// -// Non-localizable - -#define FILE_DESCRIPTION "PowerToys Command Palette Terminal UI" -#define INTERNAL_NAME "Microsoft.Terminal.UI" -#define ORIGINAL_FILENAME "Microsoft.Terminal.UI.dll" - -// Non-localizable -////////////////////////////// diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/version.rc b/src/modules/cmdpal/Microsoft.Terminal.UI/version.rc deleted file mode 100644 index 0bcdeca2ef..0000000000 --- a/src/modules/cmdpal/Microsoft.Terminal.UI/version.rc +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include "resource.h" -#include "../../../../common/version/version.h" - -#define APSTUDIO_READONLY_SYMBOLS -#include "winres.h" -#undef APSTUDIO_READONLY_SYMBOLS - -1 VERSIONINFO -FILEVERSION FILE_VERSION -PRODUCTVERSION PRODUCT_VERSION -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG -FILEFLAGS VS_FF_DEBUG -#else -FILEFLAGS 0x0L -#endif -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset - BEGIN - VALUE "CompanyName", COMPANY_NAME - VALUE "FileDescription", FILE_DESCRIPTION - VALUE "FileVersion", FILE_VERSION_STRING - VALUE "InternalName", INTERNAL_NAME - VALUE "LegalCopyright", COPYRIGHT_NOTE - VALUE "OriginalFilename", ORIGINAL_FILENAME - VALUE "ProductName", PRODUCT_NAME - VALUE "ProductVersion", PRODUCT_VERSION_STRING - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset - END -END diff --git a/src/modules/cmdpal/README.md b/src/modules/cmdpal/README.md index 518a54b78f..b9e0a42f61 100644 --- a/src/modules/cmdpal/README.md +++ b/src/modules/cmdpal/README.md @@ -1,6 +1,6 @@ # ![cmdpal logo](./Microsoft.CmdPal.UI/Assets/Stable/StoreLogo.scale-100.png) Command Palette -Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at it's core, the Command Palette is your one-stop launcher to start _anything_. +Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at its core, the Command Palette is your one-stop launcher to start _anything_. By default, CmdPal is bound to Win+Alt+Space. @@ -39,8 +39,8 @@ Projects of interest are: [Initial SDK Spec]: ./doc/initial-sdk-spec/initial-sdk-spec.md -[generic samples]: ./Exts/SamplePagesExtension -[real samples]: ./Exts/ProcessMonitorExtension +[generic samples]: ./ext/SamplePagesExtension +[real samples]: ./ext/ProcessMonitorExtension [real extensions that we've "shipped" already]: https://github.com/zadjii/CmdPalExtensions/blob/main/src/extensions diff --git a/src/modules/cmdpal/custom.props b/src/modules/cmdpal/custom.props new file mode 100644 index 0000000000..27145d9722 --- /dev/null +++ b/src/modules/cmdpal/custom.props @@ -0,0 +1,11 @@ + + + + + true + 2025 + 0 + 3 + Microsoft Command Palette + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsSettings.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsSettings.cs index 91d5cd5736..63a40614aa 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsSettings.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AllAppsSettings.cs @@ -71,7 +71,7 @@ public class AllAppsSettings : JsonSettingsManager internal static string SettingsJsonPath() { - string directory = Utilities.BaseSettingsPath("Microsoft.CmdPal"); + var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal"); Directory.CreateDirectory(directory); // now, the state is just next to the exe diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppCommand.cs index cb57086cb3..d2fe194830 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppCommand.cs @@ -8,7 +8,12 @@ using System.Threading.Tasks; using ManagedCommon; using Microsoft.CmdPal.Ext.Apps.Programs; using Microsoft.CmdPal.Ext.Apps.Properties; +using Microsoft.CmdPal.Ext.Apps.Utils; using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.Services.Maps; +using Windows.Win32; +using Windows.Win32.System.Com; +using Windows.Win32.UI.Shell; using WyHash; namespace Microsoft.CmdPal.Ext.Apps; @@ -27,26 +32,31 @@ internal sealed partial class AppCommand : InvokableCommand internal static async Task StartApp(string aumid) { - var appManager = new ApplicationActivationManager(); - const ActivateOptions noFlags = ActivateOptions.None; await Task.Run(() => { - try + unsafe { - appManager.ActivateApplication(aumid, /*queryArguments*/ string.Empty, noFlags, out var unusedPid); - } - catch (System.Exception ex) - { - Logger.LogError(ex.Message); + IApplicationActivationManager* appManager = null; + try + { + PInvoke.CoCreateInstance(typeof(ApplicationActivationManager).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out appManager).ThrowOnFailure(); + using var handle = new SafeComHandle((IntPtr)appManager); + appManager->ActivateApplication( + aumid, + string.Empty, + ACTIVATEOPTIONS.AO_NONE, + out var unusedPid); + } + catch (System.Exception ex) + { + Logger.LogError(ex.Message); + } } }).ConfigureAwait(false); } internal static async Task StartExe(string path) { - var appManager = new ApplicationActivationManager(); - - // const ActivateOptions noFlags = ActivateOptions.None; await Task.Run(() => { try diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Commands/CopyPathCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Commands/CopyPathCommand.cs new file mode 100644 index 0000000000..30ad044f37 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Commands/CopyPathCommand.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.Text; +using ManagedCommon; +using Microsoft.CmdPal.Ext.Apps.Properties; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.Apps.Commands; + +internal sealed partial class CopyPathCommand : InvokableCommand +{ + private static readonly IconInfo TheIcon = new("\ue8c8"); + + private readonly string _target; + + public CopyPathCommand(string target) + { + Name = Resources.copy_path; + Icon = TheIcon; + + _target = target; + } + + private static readonly CompositeFormat CopyFailedFormat = CompositeFormat.Parse(Resources.copy_failed); + + public override CommandResult Invoke() + { + try + { + ClipboardHelper.SetText(_target); + } + catch (Exception ex) + { + Logger.LogError("Copy failed: " + ex.Message); + return CommandResult.ShowToast( + new ToastArgs + { + Message = string.Format(CultureInfo.CurrentCulture, CopyFailedFormat, ex.Message), + Result = CommandResult.KeepOpen(), + }); + } + + return CommandResult.ShowToast(Resources.copied_to_clipboard); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj index 24782335ed..9ead1da687 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj @@ -1,11 +1,13 @@  + Microsoft.CmdPal.Ext.Apps enable $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal false false + true @@ -18,7 +20,7 @@ - + @@ -49,4 +51,9 @@ Resources.Designer.cs + + + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/NativeMethods.json b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/NativeMethods.json new file mode 100644 index 0000000000..b1156c41b7 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/NativeMethods.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "allowMarshaling": false, + "comInterop": { + "preserveSigMethods": [ "*" ] + } +} \ No newline at end of file diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/NativeMethods.txt b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/NativeMethods.txt index c0c94348c5..017871d42f 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/NativeMethods.txt +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/NativeMethods.txt @@ -1,19 +1,20 @@ -GetPhysicallyInstalledSystemMemory -GlobalMemoryStatusEx -GetSystemInfo +IStream CoCreateInstance -SetForegroundWindow -IsIconic -RegisterHotKey -SetWindowLongPtr -CallWindowProc -ShowWindow -SetForegroundWindow -SetFocus -SetActiveWindow -MonitorFromWindow -GetMonitorInfo +IApplicationActivationManager +ApplicationActivationManager SHCreateStreamOnFileEx -CoAllowSetForegroundWindow -SHCreateStreamOnFileEx -SHLoadIndirectString \ No newline at end of file +SHCreateItemFromParsingName +IShellItem +ISequentialStream +SHLoadIndirectString +IAppxFactory +AppxFactory +IAppxManifestReader +IAppxManifestApplicationsEnumerator +IAppxManifestApplication +IAppxManifestProperties +IShellLinkW +ShellLink +IPersistFile +CoTaskMemFree +IUnknown diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/AppxFactory.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/AppxFactory.cs deleted file mode 100644 index 420128ef29..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/AppxFactory.cs +++ /dev/null @@ -1,14 +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.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Apps.Programs; - -// Reference : https://stackoverflow.com/questions/32122679/getting-icon-of-modern-windows-app-from-a-desktop-application -[Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781")] -[ComImport] -public class AppxFactory -{ -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/AppxPackageHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/AppxPackageHelper.cs index 83a9fbb146..ef81410898 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/AppxPackageHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/AppxPackageHelper.cs @@ -2,42 +2,75 @@ // 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.Runtime.InteropServices; +using ManagedCommon; +using Microsoft.CmdPal.Ext.Apps.Utils; +using Microsoft.UI.Xaml.Controls; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Storage.Packaging.Appx; using Windows.Win32.System.Com; -using static Microsoft.CmdPal.Ext.Apps.Utils.Native; namespace Microsoft.CmdPal.Ext.Apps.Programs; public static class AppxPackageHelper { - private static readonly IAppxFactory AppxFactory = (IAppxFactory)new AppxFactory(); - - // This function returns a list of attributes of applications - internal static IEnumerable GetAppsFromManifest(IStream stream) + internal static unsafe List GetAppsFromManifest(IStream* stream) { - var reader = AppxFactory.CreateManifestReader(stream); - var manifestApps = reader.GetApplications(); + PInvoke.CoCreateInstance(typeof(AppxFactory).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out IAppxFactory* appxFactory).ThrowOnFailure(); + using var handle = new SafeComHandle((IntPtr)appxFactory); - while (manifestApps.GetHasCurrent()) + IAppxManifestReader* reader = null; + IAppxManifestApplicationsEnumerator* manifestApps = null; + var result = new List(); + + appxFactory->CreateManifestReader(stream, &reader); + using var readerHandle = new SafeComHandle((IntPtr)reader); + reader->GetApplications(&manifestApps); + using var manifestAppsHandle = new SafeComHandle((IntPtr)manifestApps); + + while (true) { - var manifestApp = manifestApps.GetCurrent(); - var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry); - _ = CheckHRAndReturnOrThrow(hr, appListEntry); - if (appListEntry != "none") + manifestApps->GetHasCurrent(out var hasCurrent); + if (hasCurrent == false) { - yield return manifestApp; + break; } - manifestApps.MoveNext(); - } - } + IAppxManifestApplication* manifestApp = null; - internal static T CheckHRAndReturnOrThrow(HRESULT hr, T result) - { - if (hr != HRESULT.S_OK) - { - Marshal.ThrowExceptionForHR((int)hr); + try + { + manifestApps->GetCurrent(&manifestApp).ThrowOnFailure(); + + var hr = manifestApp->GetStringValue("AppListEntry", out var appListEntryPtr); + var appListEntry = ComFreeHelper.GetStringAndFree(hr, appListEntryPtr); + + if (appListEntry != "none") + { + result.Add((IntPtr)manifestApp); + } + else if (manifestApp != null) + { + manifestApp->Release(); + } + } + catch (Exception ex) + { + if (manifestApp != null) + { + manifestApp->Release(); + } + + Logger.LogError($"Failed to get current application from manifest: {ex.Message}"); + } + + manifestApps->MoveNext(out var hasNext); + if (hasNext == false) + { + break; + } } return result; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IApplicationActivationManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IApplicationActivationManager.cs deleted file mode 100644 index 32fb3f2890..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IApplicationActivationManager.cs +++ /dev/null @@ -1,47 +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.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Apps.Programs; - -// Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs -[Flags] -public enum ActivateOptions -{ - None = 0x00000000, - DesignMode = 0x00000001, - NoErrorUI = 0x00000002, - NoSplashScreen = 0x00000004, -} - -// ApplicationActivationManager -[ComImport] -[Guid("2e941141-7f97-4756-ba1d-9decde894a3d")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IApplicationActivationManager -{ - IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); - - IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); - - IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); -} - -// Application Activation Manager Class -[ComImport] -[Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")] -public class ApplicationActivationManager : IApplicationActivationManager -{ - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/] - public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxFactory.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxFactory.cs deleted file mode 100644 index 7af82b74ab..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxFactory.cs +++ /dev/null @@ -1,20 +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.Runtime.InteropServices; -using Windows.Win32.System.Com; - -namespace Microsoft.CmdPal.Ext.Apps.Programs; - -[Guid("BEB94909-E451-438B-B5A7-D79E767B75D8")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IAppxFactory -{ - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")] - void _VtblGap0_2(); // skip 2 methods - - internal IAppxManifestReader CreateManifestReader(IStream inputStream); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestApplication.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestApplication.cs deleted file mode 100644 index 1ca12d3c29..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestApplication.cs +++ /dev/null @@ -1,19 +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.Runtime.InteropServices; -using static Microsoft.CmdPal.Ext.Apps.Utils.Native; - -namespace Microsoft.CmdPal.Ext.Apps.Programs; - -[Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IAppxManifestApplication -{ - [PreserveSig] - HRESULT GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value); - - [PreserveSig] - HRESULT GetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] out string value); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestApplicationsEnumerator.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestApplicationsEnumerator.cs deleted file mode 100644 index f7152a0813..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestApplicationsEnumerator.cs +++ /dev/null @@ -1,19 +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.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Apps.Programs; - -[Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IAppxManifestApplicationsEnumerator -{ - IAppxManifestApplication GetCurrent(); - - bool GetHasCurrent(); - - bool MoveNext(); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestProperties.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestProperties.cs deleted file mode 100644 index 4c61e6f069..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestProperties.cs +++ /dev/null @@ -1,19 +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.Runtime.InteropServices; -using static Microsoft.CmdPal.Ext.Apps.Utils.Native; - -namespace Microsoft.CmdPal.Ext.Apps.Programs; - -[Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IAppxManifestProperties -{ - [PreserveSig] - HRESULT GetBoolValue([MarshalAs(UnmanagedType.LPWStr)] string name, out bool value); - - [PreserveSig] - HRESULT GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestReader.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestReader.cs deleted file mode 100644 index 20c7fb62f6..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/IAppxManifestReader.cs +++ /dev/null @@ -1,27 +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.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Apps.Programs; - -[Guid("4E1BD148-55A0-4480-A3D1-15544710637C")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IAppxManifestReader -{ - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")] - void _VtblGap0_1(); // skip 1 method - - IAppxManifestProperties GetProperties(); - - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")] - void _VtblGap1_5(); // skip 5 methods - - IAppxManifestApplicationsEnumerator GetApplications(); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/ReparsePoint.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/ReparsePoint.cs index 1e3463f77c..bdbce1bfc3 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/ReparsePoint.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/ReparsePoint.cs @@ -3,20 +3,19 @@ // See the LICENSE file in the project root for more information. using System; -using System.ComponentModel; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; - +using ManagedCsWin32; using Microsoft.Win32.SafeHandles; -using Windows.Storage.Streams; namespace Microsoft.CmdPal.Ext.Apps.Programs; /// /// Provides access to NTFS reparse points in .Net. /// -public static class ReparsePoint +public static partial class ReparsePoint { #pragma warning disable SA1310 // Field names should not contain underscore @@ -35,118 +34,6 @@ public static class ReparsePoint private const int E_INVALID_PROTOCOL_FORMAT = unchecked((int)0x83760002); #pragma warning restore SA1310 // Field names should not contain underscore - [Flags] - private enum FileAccessType : uint - { - DELETE = 0x00010000, - READ_CONTROL = 0x00020000, - WRITE_DAC = 0x00040000, - WRITE_OWNER = 0x00080000, - SYNCHRONIZE = 0x00100000, - - STANDARD_RIGHTS_REQUIRED = 0x000F0000, - - STANDARD_RIGHTS_READ = READ_CONTROL, - STANDARD_RIGHTS_WRITE = READ_CONTROL, - STANDARD_RIGHTS_EXECUTE = READ_CONTROL, - - STANDARD_RIGHTS_ALL = 0x001F0000, - - SPECIFIC_RIGHTS_ALL = 0x0000FFFF, - - ACCESS_SYSTEM_SECURITY = 0x01000000, - - MAXIMUM_ALLOWED = 0x02000000, - - GENERIC_READ = 0x80000000, - GENERIC_WRITE = 0x40000000, - GENERIC_EXECUTE = 0x20000000, - GENERIC_ALL = 0x10000000, - - FILE_READ_DATA = 0x0001, - FILE_WRITE_DATA = 0x0002, - FILE_APPEND_DATA = 0x0004, - FILE_READ_EA = 0x0008, - FILE_WRITE_EA = 0x0010, - FILE_EXECUTE = 0x0020, - FILE_READ_ATTRIBUTES = 0x0080, - FILE_WRITE_ATTRIBUTES = 0x0100, - - FILE_ALL_ACCESS = - STANDARD_RIGHTS_REQUIRED | - SYNCHRONIZE - | 0x1FF, - - FILE_GENERIC_READ = - STANDARD_RIGHTS_READ | - FILE_READ_DATA | - FILE_READ_ATTRIBUTES | - FILE_READ_EA | - SYNCHRONIZE, - - FILE_GENERIC_WRITE = - STANDARD_RIGHTS_WRITE | - FILE_WRITE_DATA | - FILE_WRITE_ATTRIBUTES | - FILE_WRITE_EA | - FILE_APPEND_DATA | - SYNCHRONIZE, - - FILE_GENERIC_EXECUTE = - STANDARD_RIGHTS_EXECUTE | - FILE_READ_ATTRIBUTES | - FILE_EXECUTE | - SYNCHRONIZE, - } - - [Flags] - private enum FileShareType : uint - { - None = 0x00000000, - Read = 0x00000001, - Write = 0x00000002, - Delete = 0x00000004, - } - - private enum CreationDisposition : uint - { - New = 1, - CreateAlways = 2, - OpenExisting = 3, - OpenAlways = 4, - TruncateExisting = 5, - } - - [Flags] - private enum FileAttributes : uint - { - Readonly = 0x00000001, - Hidden = 0x00000002, - System = 0x00000004, - Directory = 0x00000010, - Archive = 0x00000020, - Device = 0x00000040, - Normal = 0x00000080, - Temporary = 0x00000100, - SparseFile = 0x00000200, - ReparsePoint = 0x00000400, - Compressed = 0x00000800, - Offline = 0x00001000, - NotContentIndexed = 0x00002000, - Encrypted = 0x00004000, - Write_Through = 0x80000000, - Overlapped = 0x40000000, - NoBuffering = 0x20000000, - RandomAccess = 0x10000000, - SequentialScan = 0x08000000, - DeleteOnClose = 0x04000000, - BackupSemantics = 0x02000000, - PosixSemantics = 0x01000000, - OpenReparsePoint = 0x00200000, - OpenNoRecall = 0x00100000, - FirstPipeInstance = 0x00080000, - } - private enum AppExecutionAliasReparseTagBufferLayoutVersion : uint { Invalid = 0, @@ -195,27 +82,6 @@ public static class ReparsePoint public AppExecutionAliasReparseTagBufferLayoutVersion Version; } - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern bool DeviceIoControl( - IntPtr hDevice, - uint dwIoControlCode, - IntPtr inBuffer, - int nInBufferSize, - IntPtr outBuffer, - int nOutBufferSize, - out int pBytesReturned, - IntPtr lpOverlapped); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern IntPtr CreateFile( - string lpFileName, - FileAccessType dwDesiredAccess, - FileShareType dwShareMode, - IntPtr lpSecurityAttributes, - CreationDisposition dwCreationDisposition, - FileAttributes dwFlagsAndAttributes, - IntPtr hTemplateFile); - /// /// Gets the target of the specified reparse point. /// @@ -229,13 +95,13 @@ public static class ReparsePoint public static string? GetTarget(string reparsePoint) { using (SafeFileHandle reparsePointHandle = new SafeFileHandle( - CreateFile( + Kernel32.CreateFile( reparsePoint, FileAccessType.FILE_READ_ATTRIBUTES | FileAccessType.FILE_READ_EA, FileShareType.Delete | FileShareType.Read | FileShareType.Write, IntPtr.Zero, CreationDisposition.OpenExisting, - FileAttributes.OpenReparsePoint, + ManagedCsWin32.FileAttributes.OpenReparsePoint, IntPtr.Zero), true)) { @@ -253,7 +119,7 @@ public static class ReparsePoint for (var i = 0; i < 2; ++i) { int bytesReturned; - var result = DeviceIoControl( + var result = Kernel32.DeviceIoControl( reparsePointHandle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, IntPtr.Zero, @@ -284,15 +150,18 @@ public static class ReparsePoint ThrowLastWin32Error("Unable to get information about reparse point."); } - AppExecutionAliasReparseTagHeader aliasReparseHeader = Marshal.PtrToStructure(outBuffer); - - if (aliasReparseHeader.ReparseTag == IO_REPARSE_TAG_APPEXECLINK) + unsafe { - var metadata = AppExecutionAliasMetadata.FromPersistedRepresentationIntPtr( - outBuffer, - aliasReparseHeader.Version); + var aliasReparseHeader = Unsafe.Read((void*)outBuffer); - return metadata.ExePath; + if (aliasReparseHeader.ReparseTag == IO_REPARSE_TAG_APPEXECLINK) + { + var metadata = AppExecutionAliasMetadata.FromPersistedRepresentationIntPtr( + outBuffer, + aliasReparseHeader.Version); + + return metadata.ExePath; + } } return null; @@ -319,61 +188,65 @@ public static class ReparsePoint public static AppExecutionAliasMetadata FromPersistedRepresentationIntPtr(IntPtr reparseDataBufferPtr, AppExecutionAliasReparseTagBufferLayoutVersion version) { - var dataOffset = Marshal.SizeOf(); - var dataBufferPtr = reparseDataBufferPtr + dataOffset; - - string? packageFullName = null; - string? packageFamilyName = null; - string? aumid = null; - string? exePath = null; - - VerifyVersion(version); - - switch (version) + unsafe { - case AppExecutionAliasReparseTagBufferLayoutVersion.Initial: - packageFullName = Marshal.PtrToStringUni(dataBufferPtr); - if (packageFullName is not null) - { - dataBufferPtr += Encoding.Unicode.GetByteCount(packageFullName) + Encoding.Unicode.GetByteCount("\0"); - aumid = Marshal.PtrToStringUni(dataBufferPtr); + var dataOffset = Unsafe.SizeOf(); - if (aumid is not null) + var dataBufferPtr = reparseDataBufferPtr + dataOffset; + + string? packageFullName = null; + string? packageFamilyName = null; + string? aumid = null; + string? exePath = null; + + VerifyVersion(version); + + switch (version) + { + case AppExecutionAliasReparseTagBufferLayoutVersion.Initial: + packageFullName = Marshal.PtrToStringUni(dataBufferPtr); + if (packageFullName is not null) { - dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0"); - exePath = Marshal.PtrToStringUni(dataBufferPtr); + dataBufferPtr += Encoding.Unicode.GetByteCount(packageFullName) + Encoding.Unicode.GetByteCount("\0"); + aumid = Marshal.PtrToStringUni(dataBufferPtr); + + if (aumid is not null) + { + dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0"); + exePath = Marshal.PtrToStringUni(dataBufferPtr); + } } - } - break; + break; - case AppExecutionAliasReparseTagBufferLayoutVersion.PackageFamilyName: - case AppExecutionAliasReparseTagBufferLayoutVersion.MultiAppTypeSupport: - packageFamilyName = Marshal.PtrToStringUni(dataBufferPtr); + case AppExecutionAliasReparseTagBufferLayoutVersion.PackageFamilyName: + case AppExecutionAliasReparseTagBufferLayoutVersion.MultiAppTypeSupport: + packageFamilyName = Marshal.PtrToStringUni(dataBufferPtr); - if (packageFamilyName is not null) - { - dataBufferPtr += Encoding.Unicode.GetByteCount(packageFamilyName) + Encoding.Unicode.GetByteCount("\0"); - aumid = Marshal.PtrToStringUni(dataBufferPtr); - - if (aumid is not null) + if (packageFamilyName is not null) { - dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0"); + dataBufferPtr += Encoding.Unicode.GetByteCount(packageFamilyName) + Encoding.Unicode.GetByteCount("\0"); + aumid = Marshal.PtrToStringUni(dataBufferPtr); - exePath = Marshal.PtrToStringUni(dataBufferPtr); + if (aumid is not null) + { + dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0"); + + exePath = Marshal.PtrToStringUni(dataBufferPtr); + } } - } - break; + break; + } + + return new AppExecutionAliasMetadata + { + PackageFullName = packageFullName ?? string.Empty, + PackageFamilyName = packageFamilyName ?? string.Empty, + Aumid = aumid ?? string.Empty, + ExePath = exePath ?? string.Empty, + }; } - - return new AppExecutionAliasMetadata - { - PackageFullName = packageFullName ?? string.Empty, - PackageFamilyName = packageFamilyName ?? string.Empty, - Aumid = aumid ?? string.Empty, - ExePath = exePath ?? string.Empty, - }; } private static void VerifyVersion(AppExecutionAliasReparseTagBufferLayoutVersion version) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWP.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWP.cs index e115911001..15d7c079db 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWP.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWP.cs @@ -3,17 +3,19 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; +using System.Threading.Tasks; using System.Xml.Linq; using ManagedCommon; using Microsoft.CmdPal.Ext.Apps.Utils; using Microsoft.CommandPalette.Extensions.Toolkit; using Windows.Win32; using Windows.Win32.Foundation; +using Windows.Win32.Storage.Packaging.Appx; using Windows.Win32.System.Com; -using static Microsoft.CmdPal.Ext.Apps.Utils.Native; namespace Microsoft.CmdPal.Ext.Apps.Programs; @@ -55,7 +57,7 @@ public partial class UWP FamilyName = package.FamilyName; } - public void InitializeAppInfo(string installedLocation) + public unsafe void InitializeAppInfo(string installedLocation) { Location = installedLocation; LocationLocalized = ShellLocalization.Instance.GetLocalizedPath(installedLocation); @@ -65,26 +67,31 @@ public partial class UWP InitPackageVersion(namespaces); const uint noAttribute = 0x80; - - var access = (uint)STGM.READ; - var hResult = PInvoke.SHCreateStreamOnFileEx(path, access, noAttribute, false, null, out IStream stream); - - // S_OK - if (hResult == 0) + const uint STGMREAD = 0x00000000; + try { - Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest => new UWPApplication(appInManifest, this)).Where(a => + IStream* stream = null; + PInvoke.SHCreateStreamOnFileEx(path, STGMREAD, noAttribute, false, null, &stream).ThrowOnFailure(); + using var streamHandle = new SafeComHandle((IntPtr)stream); + + Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest => + { + using var appHandle = new SafeComHandle(appInManifest); + return new UWPApplication((IAppxManifestApplication*)appInManifest, this); + }).Where(a => { var valid = - !string.IsNullOrEmpty(a.UserModelId) && - !string.IsNullOrEmpty(a.DisplayName) && - a.AppListEntry != "none"; - + !string.IsNullOrEmpty(a.UserModelId) && + !string.IsNullOrEmpty(a.DisplayName) && + a.AppListEntry != "none"; return valid; }).ToList(); } - else + catch (Exception ex) { Apps = Array.Empty(); + Logger.LogError($"Failed to initialize UWP app info for {Name} ({FullName}): {ex.Message}"); + return; } } @@ -123,35 +130,36 @@ public partial class UWP { var windows10 = new Version(10, 0); var support = Environment.OSVersion.Version.Major >= windows10.Major; - if (support) - { - var applications = CurrentUserPackages().AsParallel().SelectMany(p => - { - UWP u; - try - { - u = new UWP(p); - u.InitializeAppInfo(p.InstalledLocation); - } - catch (Exception ex) - { - Logger.LogError(ex.Message); - return Array.Empty(); - } - return u.Apps; - }); - - var updatedListWithoutDisabledApps = applications - .Where(t1 => AllAppsSettings.Instance.DisabledProgramSources.All(x => x.UniqueIdentifier != t1.UniqueIdentifier)) - .Select(x => x); - - return updatedListWithoutDisabledApps.ToArray(); - } - else + if (!support) { return Array.Empty(); } + + var appsBag = new ConcurrentBag(); + + Parallel.ForEach(CurrentUserPackages(), p => + { + try + { + var u = new UWP(p); + u.InitializeAppInfo(p.InstalledLocation); + + foreach (var app in u.Apps) + { + if (AllAppsSettings.Instance.DisabledProgramSources.All(x => x.UniqueIdentifier != app.UniqueIdentifier)) + { + appsBag.Add(app); + } + } + } + catch (Exception ex) + { + Logger.LogError(ex.Message); + } + }); + + return appsBag.ToArray(); } private static IEnumerable CurrentUserPackages() diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs index c38c05d7b5..5ea2e42657 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Xml; using ManagedCommon; @@ -13,7 +14,9 @@ using Microsoft.CmdPal.Ext.Apps.Commands; using Microsoft.CmdPal.Ext.Apps.Properties; using Microsoft.CmdPal.Ext.Apps.Utils; using Microsoft.CommandPalette.Extensions.Toolkit; -using static Microsoft.CmdPal.Ext.Apps.Utils.Native; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Storage.Packaging.Appx; using PackageVersion = Microsoft.CmdPal.Ext.Apps.Programs.UWP.PackageVersion; using Theme = Microsoft.CmdPal.Ext.Apps.Utils.Theme; @@ -82,6 +85,10 @@ public class UWPApplication : IProgram // We don't add context menu to 'run as different user', because UWP applications normally installed per user and not for all users. } + commands.Add( + new CommandContextItem( + new CopyPathCommand(Location))); + commands.Add( new CommandContextItem( new OpenPathCommand(Location) @@ -97,27 +104,27 @@ public class UWPApplication : IProgram return commands; } - public UWPApplication(IAppxManifestApplication manifestApp, UWP package) + internal unsafe UWPApplication(IAppxManifestApplication* manifestApp, UWP package) { ArgumentNullException.ThrowIfNull(manifestApp); - var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId); - UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId); + var hr = manifestApp->GetAppUserModelId(out var tmpUserModelIdPtr); + UserModelId = ComFreeHelper.GetStringAndFree(hr, tmpUserModelIdPtr); - hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier); - UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier); + manifestApp->GetAppUserModelId(out var tmpUniqueIdentifierPtr); + UniqueIdentifier = ComFreeHelper.GetStringAndFree(hr, tmpUniqueIdentifierPtr); - hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName); - DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName); + manifestApp->GetStringValue("DisplayName", out var tmpDisplayNamePtr); + DisplayName = ComFreeHelper.GetStringAndFree(hr, tmpDisplayNamePtr); - hr = manifestApp.GetStringValue("Description", out var tmpDescription); - Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription); + manifestApp->GetStringValue("Description", out var tmpDescriptionPtr); + Description = ComFreeHelper.GetStringAndFree(hr, tmpDescriptionPtr); - hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor); - BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor); + manifestApp->GetStringValue("BackgroundColor", out var tmpBackgroundColorPtr); + BackgroundColor = ComFreeHelper.GetStringAndFree(hr, tmpBackgroundColorPtr); - hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint); - EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint); + manifestApp->GetStringValue("EntryPoint", out var tmpEntryPointPtr); + EntryPoint = ComFreeHelper.GetStringAndFree(hr, tmpEntryPointPtr); Package = package ?? throw new ArgumentNullException(nameof(package)); @@ -166,7 +173,26 @@ public class UWPApplication : IProgram return false; } - internal string ResourceFromPri(string packageFullName, string resourceReference) + private static string TryLoadIndirectString(string source, Span buffer, string errorContext) + { + try + { + PInvoke.SHLoadIndirectString(source, buffer).ThrowOnFailure(); + + var len = buffer.IndexOf('\0'); + var loaded = len >= 0 + ? buffer[..len].ToString() + : buffer.ToString(); + return string.IsNullOrEmpty(loaded) ? string.Empty : loaded; + } + catch (Exception ex) + { + Logger.LogError($"Unable to load resource {source} : {errorContext} : {ex.Message}"); + return string.Empty; + } + } + + internal unsafe string ResourceFromPri(string packageFullName, string resourceReference) { const string prefix = "ms-resource:"; @@ -200,30 +226,8 @@ public class UWPApplication : IProgram parsedFallback = prefix + "///" + key; } - var outBuffer = new StringBuilder(128); - var source = $"@{{{packageFullName}? {parsed}}}"; - var capacity = (uint)outBuffer.Capacity; - var hResult = SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero); - if (hResult != HRESULT.S_OK) + if (string.IsNullOrEmpty(parsedFallback)) { - if (!string.IsNullOrEmpty(parsedFallback)) - { - var sourceFallback = $"@{{{packageFullName}? {parsedFallback}}}"; - hResult = SHLoadIndirectString(sourceFallback, outBuffer, capacity, IntPtr.Zero); - if (hResult == HRESULT.S_OK) - { - var loaded = outBuffer.ToString(); - if (!string.IsNullOrEmpty(loaded)) - { - return loaded; - } - else - { - return string.Empty; - } - } - } - // https://github.com/Wox-launcher/Wox/issues/964 // known hresult 2147942522: // 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'. @@ -232,18 +236,19 @@ public class UWPApplication : IProgram // Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription return string.Empty; } - else + + Span outBuffer = stackalloc char[1024]; + var source = $"@{{{packageFullName}? {parsed}}}"; + + var loaded = TryLoadIndirectString(source, outBuffer, resourceReference); + + if (!string.IsNullOrEmpty(loaded)) { - var loaded = outBuffer.ToString(); - if (!string.IsNullOrEmpty(loaded)) - { - return loaded; - } - else - { - return string.Empty; - } + return loaded; } + + var sourceFallback = $"@{{{packageFullName}?{parsedFallback}}}"; + return TryLoadIndirectString(sourceFallback, outBuffer, $"{resourceReference} (fallback)"); } else { @@ -258,13 +263,12 @@ public class UWPApplication : IProgram { PackageVersion.Windows8, "SmallLogo" }, }; - internal string LogoUriFromManifest(IAppxManifestApplication app) + internal unsafe string LogoUriFromManifest(IAppxManifestApplication* app) { if (_logoKeyFromVersion.TryGetValue(Package.Version, out var key)) { - var hr = app.GetStringValue(key, out var logoUriFromApp); - _ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUriFromApp); - return logoUriFromApp; + var hr = app->GetStringValue(key, out var logoUriFromAppPtr); + return ComFreeHelper.GetStringAndFree(hr, logoUriFromAppPtr); } else { @@ -349,7 +353,7 @@ public class UWPApplication : IProgram var prefix = path.Substring(0, end); var paths = new List { }; const int appIconSize = 36; - var targetSizes = new List { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel(); + var targetSizes = new List { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }; var pathFactorPairs = new Dictionary(); foreach (var factor in targetSizes) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs index 11c9be6be5..1ccf797d35 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.Design; using System.Diagnostics; @@ -197,6 +198,9 @@ public class Win32Program : IProgram new RunAsUserCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory))); } + commands.Add(new CommandContextItem( + new CopyPathCommand(FullPath))); + commands.Add(new CommandContextItem( new OpenPathCommand(ParentDirectory))); @@ -841,18 +845,69 @@ public class Win32Program : IProgram var disabledProgramsList = settings.DisabledProgramSources; // Get all paths but exclude all normal .Executables - paths.UnionWith(sources - .AsParallel() - .SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty()) - .Where(programPath => disabledProgramsList.All(x => x.UniqueIdentifier != programPath)) - .Where(path => !ExecutableApplicationExtensions.Contains(Extension(path)))); - runCommandPaths.UnionWith(runCommandSources - .AsParallel() - .SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty()) - .Where(programPath => disabledProgramsList.All(x => x.UniqueIdentifier != programPath))); + var pathBag = new ConcurrentBag(); - var programs = paths.AsParallel().Select(source => GetProgramFromPath(source)); - var runCommandPrograms = runCommandPaths.AsParallel().Select(source => GetRunCommandProgramFromPath(source)); + Parallel.ForEach(sources, source => + { + if (!source.IsEnabled) + { + return; + } + + foreach (var path in source.GetPaths()) + { + if (disabledProgramsList.All(x => x.UniqueIdentifier != path) && + !ExecutableApplicationExtensions.Contains(Extension(path))) + { + pathBag.Add(path); + } + } + }); + + paths.UnionWith(pathBag); + + var runCommandPathBag = new ConcurrentBag(); + + Parallel.ForEach(runCommandSources, source => + { + if (!source.IsEnabled) + { + return; + } + + foreach (var path in source.GetPaths()) + { + if (disabledProgramsList.All(x => x.UniqueIdentifier != path)) + { + runCommandPathBag.Add(path); + } + } + }); + + runCommandPaths.UnionWith(runCommandPathBag); + + var programsList = new ConcurrentBag(); + Parallel.ForEach(paths, source => + { + var program = GetProgramFromPath(source); + if (program != null) + { + programsList.Add(program); + } + }); + + var runCommandProgramsList = new ConcurrentBag(); + Parallel.ForEach(runCommandPaths, source => + { + var program = GetRunCommandProgramFromPath(source); + if (program != null) + { + runCommandProgramsList.Add(program); + } + }); + + var programs = programsList.ToList(); + var runCommandPrograms = runCommandProgramsList.ToList(); return DeduplicatePrograms(programs.Concat(runCommandPrograms).Where(program => program?.Valid == true)); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.Designer.cs index 33531ba62f..cc07ac86b4 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.Designer.cs @@ -78,6 +78,33 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties { } } + /// + /// Looks up a localized string similar to Copied to clipboard!. + /// + internal static string copied_to_clipboard { + get { + return ResourceManager.GetString("copied_to_clipboard", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy failed ({0}). Please try again.. + /// + internal static string copy_failed { + get { + return ResourceManager.GetString("copy_failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy path. + /// + internal static string copy_path { + get { + return ResourceManager.GetString("copy_path", resourceCulture); + } + } + /// /// Looks up a localized string similar to Include apps found on the desktop. /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.resx index 98212f1066..ce4fb79689 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Properties/Resources.resx @@ -163,12 +163,22 @@ Open location + + Copy path + Run as administrator Run as different user + + Copy failed ({0}). Please try again. + {0} is the error message + + + Copied to clipboard! + Include apps found in the Start Menu diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/ListRepository`1.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/ListRepository`1.cs index 653663b7e1..74877bf841 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/ListRepository`1.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/ListRepository`1.cs @@ -69,11 +69,6 @@ public class ListRepository : IRepository, IEnumerable } } - public ParallelQuery AsParallel() - { - return _items.Values.AsParallel(); - } - public bool Contains(T item) { if (item is not null) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/Win32ProgramRepository.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/Win32ProgramRepository.cs index 9592599b17..b8e2064048 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/Win32ProgramRepository.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Storage/Win32ProgramRepository.cs @@ -6,8 +6,8 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.IO.Abstractions; using System.Threading.Tasks; using ManagedCommon; using Microsoft.CmdPal.Ext.Apps.Programs; @@ -15,11 +15,9 @@ using Win32Program = Microsoft.CmdPal.Ext.Apps.Programs.Win32Program; namespace Microsoft.CmdPal.Ext.Apps.Storage; +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] internal sealed partial class Win32ProgramRepository : ListRepository, IProgramRepository { - private static readonly IFileSystem FileSystem = new FileSystem(); - private static readonly IPath Path = FileSystem.Path; - private const string LnkExtension = ".lnk"; private const string UrlExtension = ".url"; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ComFreeHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ComFreeHelper.cs new file mode 100644 index 0000000000..5f11987f76 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ComFreeHelper.cs @@ -0,0 +1,35 @@ +// 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 Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com; + +namespace Microsoft.CmdPal.Ext.Apps.Utils; + +public static class ComFreeHelper +{ + internal static unsafe string GetStringAndFree(HRESULT hr, PWSTR ptr) + { + hr.ThrowOnFailure(); + try + { + return ptr.ToString(); + } + finally + { + PInvoke.CoTaskMemFree(ptr); + } + } + + public static unsafe void ComObjectRelease(T* comPtr) + where T : unmanaged + { + if (comPtr != null) + { + ((IUnknown*)comPtr)->Release(); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/Native.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/Native.cs deleted file mode 100644 index 37918160fe..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/Native.cs +++ /dev/null @@ -1,157 +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.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Text; - -namespace Microsoft.CmdPal.Ext.Apps.Utils; - -[SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "We want plugins to share this NativeMethods class, instead of each one creating its own.")] -public sealed class Native -{ - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, nint ppvReserved); - - [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, nint pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); - - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - public static extern HRESULT SHCreateStreamOnFileEx(string fileName, STGM grfMode, uint attributes, bool create, System.Runtime.InteropServices.ComTypes.IStream reserved, out System.Runtime.InteropServices.ComTypes.IStream stream); - - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - public static extern HRESULT SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, nint ppvReserved); - - public enum HRESULT : uint - { - /// - /// Operation successful. - /// - S_OK = 0x00000000, - - /// - /// Operation successful. (negative condition/no operation) - /// - S_FALSE = 0x00000001, - - /// - /// Not implemented. - /// - E_NOTIMPL = 0x80004001, - - /// - /// No such interface supported. - /// - E_NOINTERFACE = 0x80004002, - - /// - /// Pointer that is not valid. - /// - E_POINTER = 0x80004003, - - /// - /// Operation aborted. - /// - E_ABORT = 0x80004004, - - /// - /// Unspecified failure. - /// - E_FAIL = 0x80004005, - - /// - /// Unexpected failure. - /// - E_UNEXPECTED = 0x8000FFFF, - - /// - /// General access denied error. - /// - E_ACCESSDENIED = 0x80070005, - - /// - /// Handle that is not valid. - /// - E_HANDLE = 0x80070006, - - /// - /// Failed to allocate necessary memory. - /// - E_OUTOFMEMORY = 0x8007000E, - - /// - /// One or more arguments are not valid. - /// - E_INVALIDARG = 0x80070057, - - /// - /// The operation was canceled by the user. (Error source 7 means Win32.) - /// - /// - /// - E_CANCELLED = 0x800704C7, - } - - public static class ShellItemTypeConstants - { - /// - /// Guid for type IShellItem. - /// - public static readonly Guid ShellItemGuid = new("43826d1e-e718-42ee-bc55-a1e261c37bfe"); - - /// - /// Guid for type IShellItem2. - /// - public static readonly Guid ShellItem2Guid = new("7E9FB0D3-919F-4307-AB2E-9B1860310C93"); - } - - /// - /// The following are ShellItem DisplayName types. - /// - [Flags] - public enum SIGDN : uint - { - NORMALDISPLAY = 0, - PARENTRELATIVEPARSING = 0x80018001, - PARENTRELATIVEFORADDRESSBAR = 0x8001c001, - DESKTOPABSOLUTEPARSING = 0x80028000, - PARENTRELATIVEEDITING = 0x80031001, - DESKTOPABSOLUTEEDITING = 0x8004c000, - FILESYSPATH = 0x80058000, - URL = 0x80068000, - } - - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] - public interface IShellItem - { - void BindToHandler( - nint pbc, - [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out nint ppv); - - void GetParent(out IShellItem ppsi); - - void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); - - void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); - - void Compare(IShellItem psi, uint hint, out int piOrder); - } - - /// - /// see all STGM values - /// - [Flags] - public enum STGM : long - { - READ = 0x00000000L, - WRITE = 0x00000001L, - READWRITE = 0x00000002L, - CREATE = 0x00001000L, - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/SafeComHandle.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/SafeComHandle.cs new file mode 100644 index 0000000000..86db1caf33 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/SafeComHandle.cs @@ -0,0 +1,30 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.CmdPal.Ext.Apps.Utils; + +public partial class SafeComHandle : SafeHandle +{ + public SafeComHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + public SafeComHandle(IntPtr handle) + : base(IntPtr.Zero, ownsHandle: true) + { + SetHandle(handle); + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + var count = Marshal.Release(handle); + return true; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLinkHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLinkHelper.cs index 1bb0e10580..543abf5dcf 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLinkHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLinkHelper.cs @@ -3,124 +3,17 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using System.Text; using ManagedCommon; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com; +using Windows.Win32.UI.Shell; namespace Microsoft.CmdPal.Ext.Apps.Utils; public class ShellLinkHelper : IShellLinkHelper { - [Flags] - private enum SLGP_FLAGS - { - SLGP_SHORTPATH = 0x1, - SLGP_UNCPRIORITY = 0x2, - SLGP_RAWPATH = 0x4, - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")] - private struct WIN32_FIND_DATAW - { - public uint dwFileAttributes; - public long ftCreationTime; - public long ftLastAccessTime; - public long ftLastWriteTime; - public uint nFileSizeHigh; - public uint nFileSizeLow; - public uint dwReserved0; - public uint dwReserved1; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] - public string cFileName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] - public string cAlternateFileName; - } - - [Flags] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")] - public enum SLR_FLAGS - { - SLR_NO_UI = 0x1, - SLR_ANY_MATCH = 0x2, - SLR_UPDATE = 0x4, - SLR_NOUPDATE = 0x8, - SLR_NOSEARCH = 0x10, - SLR_NOTRACK = 0x20, - SLR_NOLINKINFO = 0x40, - SLR_INVOKE_MSI = 0x80, - } - - // Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW - - // The IShellLink interface allows Shell links to be created, modified, and resolved - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("000214F9-0000-0000-C000-000000000046")] - private interface IShellLinkW - { - /// Retrieves the path and file name of a Shell link object - void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags); - - /// Retrieves the list of item identifiers for a Shell link object - void GetIDList(out nint ppidl); - - /// Sets the pointer to an item identifier list (PIDL) for a Shell link object. - void SetIDList(nint pidl); - - /// Retrieves the description string for a Shell link object - void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); - - /// Sets the description for a Shell link object. The description can be any application-defined string - void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); - - /// Retrieves the name of the working directory for a Shell link object - void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); - - /// Sets the name of the working directory for a Shell link object - void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); - - /// Retrieves the command-line arguments associated with a Shell link object - void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); - - /// Sets the command-line arguments for a Shell link object - void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); - - /// Retrieves the hot key for a Shell link object - void GetHotkey(out short pwHotkey); - - /// Sets a hot key for a Shell link object - void SetHotkey(short wHotkey); - - /// Retrieves the show command for a Shell link object - void GetShowCmd(out int piShowCmd); - - /// Sets the show command for a Shell link object. The show command sets the initial show state of the window. - void SetShowCmd(int iShowCmd); - - /// Retrieves the location (path and index) of the icon for a Shell link object - void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon); - - /// Sets the location (path and index) of the icon for a Shell link object - void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); - - /// Sets the relative path to the Shell link object - void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); - - /// Attempts to find the target of a Shell link, even if it has been moved or renamed - void Resolve(ref nint hwnd, SLR_FLAGS fFlags); - - /// Sets the path and file name of a Shell link object - void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); - } - - [ComImport] - [Guid("00021401-0000-0000-C000-000000000046")] - private class ShellLink - { - } - // Contains the description of the app public string Description { get; set; } = string.Empty; @@ -130,60 +23,63 @@ public class ShellLinkHelper : IShellLinkHelper public bool HasArguments { get; set; } // Retrieve the target path using Shell Link - public string RetrieveTargetPath(string path) + public unsafe string RetrieveTargetPath(string path) { - var link = new ShellLink(); - const int STGM_READ = 0; - - try - { - ((IPersistFile)link).Load(path, STGM_READ); - } - catch (System.IO.FileNotFoundException ex) - { - Logger.LogError(ex.Message); - return string.Empty; - } - - var hwnd = default(nint); - ((IShellLinkW)link).Resolve(ref hwnd, 0); - + var target = string.Empty; const int MAX_PATH = 260; - var buffer = new StringBuilder(MAX_PATH); + IShellLinkW* link = null; - var data = default(WIN32_FIND_DATAW); - ((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH); - var target = buffer.ToString(); + PInvoke.CoCreateInstance(typeof(ShellLink).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out link).ThrowOnFailure(); + using var linkHandle = new SafeComHandle((IntPtr)link); + + const int STGMREAD = 0; + + IPersistFile* persistFile = null; + Guid iid = typeof(IPersistFile).GUID; + ((IUnknown*)link)->QueryInterface(&iid, (void**)&persistFile); + if (persistFile != null) + { + using var persistFileHandle = new SafeComHandle((IntPtr)persistFile); + try + { + persistFile->Load(path, STGMREAD); + } + catch (System.IO.FileNotFoundException) + { + // Log.Exception($"Failed to load {path}, {e.Message}", e, GetType()); + return string.Empty; + } + } + + var hwnd = HWND.Null; + const uint SLR_NO_UI = 0x1; + link->Resolve(hwnd, SLR_NO_UI); + + var buffer = stackalloc char[MAX_PATH]; + + var hr = link->GetPath((PWSTR)buffer, MAX_PATH, null, 0x1); + + target = hr.Succeeded ? new string(buffer) : string.Empty; // To set the app description if (!string.IsNullOrEmpty(target)) { - buffer = new StringBuilder(MAX_PATH); - try - { - ((IShellLinkW)link).GetDescription(buffer, MAX_PATH); - Description = buffer.ToString(); - } - catch (Exception ex) - { - Logger.LogError(ex.Message); - Description = string.Empty; - } + var descBuffer = stackalloc char[MAX_PATH]; + var desHr = link->GetDescription(descBuffer, MAX_PATH); + Description = desHr.Succeeded ? new string(descBuffer) : string.Empty; - var argumentBuffer = new StringBuilder(MAX_PATH); - ((IShellLinkW)link).GetArguments(argumentBuffer, argumentBuffer.Capacity); - Arguments = argumentBuffer.ToString(); + var argsBuffer = stackalloc char[MAX_PATH]; + var argHr = link->GetArguments(argsBuffer, MAX_PATH); + + Arguments = argHr.Succeeded ? new string(argsBuffer) : string.Empty; // Set variable to true if the program takes in any arguments - if (argumentBuffer.Length != 0) + if (Arguments.Length != 0) { HasArguments = true; } } - // To release unmanaged memory - Marshal.ReleaseComObject(link); - return target; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLocalization.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLocalization.cs index 0a3337e0a8..87e152b7e0 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLocalization.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Utils/ShellLocalization.cs @@ -4,7 +4,8 @@ using System; using System.Collections.Concurrent; using System.IO; -using static Microsoft.CmdPal.Ext.Apps.Utils.Native; +using Windows.Win32; +using Windows.Win32.UI.Shell; namespace Microsoft.CmdPal.Ext.Apps.Utils; @@ -23,7 +24,7 @@ public class ShellLocalization /// /// Path to the shell item (e. g. shortcut 'File Explorer.lnk'). /// The localized name as string or . - public string GetLocalizedName(string path) + public unsafe string GetLocalizedName(string path) { var lowerInvariantPath = path.ToLowerInvariant(); @@ -33,18 +34,29 @@ public class ShellLocalization return value; } - var shellItemType = ShellItemTypeConstants.ShellItemGuid; - var retCode = SHCreateItemFromParsingName(path, nint.Zero, ref shellItemType, out var shellItem); - if (retCode != 0) + void* shellItemPtrVoid = null; + try + { + var retCode = PInvoke.SHCreateItemFromParsingName(path, null, typeof(IShellItem).GUID, out shellItemPtrVoid).ThrowOnFailure(); + using var shellItemHandle = new SafeComHandle((IntPtr)shellItemPtrVoid); + IShellItem* shellItemPtr = (IShellItem*)shellItemPtrVoid; + + var hr = shellItemPtr->GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY, out var filenamePtr); + + var filename = ComFreeHelper.GetStringAndFree(hr, filenamePtr); + + if (filename == null) + { + return string.Empty; + } + + _ = _localizationCache.TryAdd(lowerInvariantPath, filename); + return filename; + } + catch (Exception) { return string.Empty; } - - shellItem.GetDisplayName(SIGDN.NORMALDISPLAY, out var filename); - - _ = _localizationCache.TryAdd(lowerInvariantPath, filename); - - return filename; } /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs index 0d6f9536db..f5e35b05cb 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs @@ -6,20 +6,20 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Text.RegularExpressions; - -using Mages.Core; +using CalculatorEngineCommon; +using Windows.Foundation.Collections; namespace Microsoft.CmdPal.Ext.Calc.Helper; public static class CalculateEngine { - private static readonly Engine _magesEngine = new Engine(new Configuration + private static readonly PropertySet _constants = new() { - Scope = new Dictionary - { - { "e", Math.E }, // e is not contained in the default mages engine - }, - }); + { "pi", Math.PI }, + { "e", Math.E }, + }; + + private static readonly Calculator _calculator = new Calculator(_constants); public const int RoundingDigits = 10; @@ -68,29 +68,23 @@ public static class CalculateEngine // Expand conversions between trig units input = CalculateHelper.ExpandTrigConversions(input, trigMode); - var result = _magesEngine.Interpret(input); + var result = _calculator.EvaluateExpression(input); // This could happen for some incorrect queries, like pi(2) - if (result == null) + if (result == "NaN") { error = Properties.Resources.calculator_expression_not_complete; return default; } - result = TransformResult(result); - if (result is string) - { - error = result as string; - return default; - } - - if (string.IsNullOrEmpty(result?.ToString())) + if (string.IsNullOrEmpty(result)) { return default; } var decimalResult = Convert.ToDecimal(result, cultureInfo); - var roundedResult = Round(decimalResult); + + var roundedResult = FormatMax15Digits(decimalResult, cultureInfo); return new CalculateResult() { @@ -104,24 +98,27 @@ public static class CalculateEngine return Math.Round(value, RoundingDigits, MidpointRounding.AwayFromZero); } - private static dynamic TransformResult(object result) + /// + /// Format a decimal so that the output contains **at most 15 total digits** + /// (integer + fraction, not counting the decimal point or minus sign). + /// Any extra fractional digits are rounded using “away-from-zero” rounding. + /// Trailing zeros in the fractional part—and a dangling decimal point—are removed. + /// Examples + /// 1.9999999999 → "1.9999999999" + /// 100000.9999999999 → "100001" + /// 1234567890123.45 → "1234567890123.45" + /// + private static decimal FormatMax15Digits(decimal value, CultureInfo cultureInfo) { - if (result.ToString() == "NaN") - { - return Properties.Resources.calculator_not_a_number; - } + var absValue = Math.Abs(value); + var integerDigits = absValue >= 1 ? (int)Math.Floor(Math.Log10((double)absValue)) + 1 : 1; - if (result is Function) - { - return Properties.Resources.calculator_expression_not_complete; - } + var maxDecimalDigits = Math.Max(0, 15 - integerDigits); - if (result is double[,]) - { - // '[10,10]' is interpreted as array by mages engine - return Properties.Resources.calculator_double_array_returned; - } + var rounded = Math.Round(value, maxDecimalDigits, MidpointRounding.AwayFromZero); - return result; + var formatted = rounded.ToString("G29", cultureInfo); + + return Convert.ToDecimal(formatted, cultureInfo); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs index acab3d7b96..deb441cfe2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs @@ -35,7 +35,7 @@ public static class CalculateHelper { if (string.IsNullOrWhiteSpace(input)) { - throw new ArgumentNullException(paramName: nameof(input)); + return false; } if (!RegValidExpressChar.IsMatch(input)) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs index 8de77ebdae..6930e2fa23 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Globalization; using System.Text; using System.Text.RegularExpressions; @@ -63,46 +64,61 @@ public class NumberTranslator return Translate(input, targetCulture, sourceCulture, splitRegexForTarget); } + private static string ConvertBaseLiteral(string token, CultureInfo cultureTo) + { + var prefixes = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "0x", 16 }, + { "0b", 2 }, + { "0o", 8 }, + }; + + foreach (var (prefix, numberBase) in prefixes) + { + if (token.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + try + { + var num = Convert.ToInt64(token.Substring(prefix.Length), numberBase); + return num.ToString(cultureTo); + } + catch + { + return null; // fallback + } + } + } + + return null; + } + private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex) { var outputBuilder = new StringBuilder(); - var hexRegex = new Regex(@"(?:(0x[\da-fA-F]+))"); - var hexTokens = hexRegex.Split(input); + // Match numbers in hexadecimal (0x..), binary (0b..), or octal (0o..) format, + // and convert them to decimal form for compatibility with ExprTk (which only supports decimal input). + var baseNumberRegex = new Regex(@"(0[xX][\da-fA-F]+|0[bB][0-9]+|0[oO][0-9]+)"); - foreach (var hexToken in hexTokens) + var tokens = baseNumberRegex.Split(input); + + foreach (var token in tokens) { - if (hexToken.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) - { - // Mages engine has issues processing large hex number (larger than 7 hex digits + 0x prefix = 9 characters). So we convert it to decimal and pass it to the engine. - if (hexToken.Length > 9) - { - try - { - var num = Convert.ToInt64(hexToken, 16); - var numStr = num.ToString(cultureFrom); - outputBuilder.Append(numStr); - } - catch (Exception) - { - outputBuilder.Append(hexToken); - } - } - else - { - outputBuilder.Append(hexToken); - } + // Currently, we only convert base literals (hexadecimal, binary, octal) to decimal. + var converted = ConvertBaseLiteral(token, cultureTo); + if (converted != null) + { + outputBuilder.Append(converted); continue; } - var tokens = splitRegex.Split(hexToken); - foreach (var token in tokens) + foreach (var inner in splitRegex.Split(token)) { var leadingZeroCount = 0; // Count leading zero characters. - foreach (var c in token) + foreach (var c in inner) { if (c != '0') { @@ -113,7 +129,7 @@ public class NumberTranslator } // number is all zero characters. no need to add zero characters at the end. - if (token.Length == leadingZeroCount) + if (inner.Length == leadingZeroCount) { leadingZeroCount = 0; } @@ -121,9 +137,9 @@ public class NumberTranslator decimal number; outputBuilder.Append( - decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number) + decimal.TryParse(inner, NumberStyles.Number, cultureFrom, out number) ? (new string('0', leadingZeroCount) + number.ToString(cultureTo)) - : token.Replace(cultureFrom.TextInfo.ListSeparator, cultureTo.TextInfo.ListSeparator)); + : inner.Replace(cultureFrom.TextInfo.ListSeparator, cultureTo.TextInfo.ListSeparator)); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs index 6b1e62ae03..df6a8a153d 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/QueryHelper.cs @@ -23,6 +23,9 @@ public static partial class QueryHelper CultureInfo inputCulture = settings.InputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture; CultureInfo outputCulture = settings.OutputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture; + // In case the user pastes a query with a leading = + query = query.TrimStart('='); + // Happens if the user has only typed the action key so far if (string.IsNullOrEmpty(query)) { @@ -32,6 +35,11 @@ public static partial class QueryHelper NumberTranslator translator = NumberTranslator.Create(inputCulture, new CultureInfo("en-US")); var input = translator.Translate(query.Normalize(NormalizationForm.FormKC)); + if (string.IsNullOrWhiteSpace(input)) + { + return ErrorHandler.OnError(isFallbackSearch, query, Properties.Resources.calculator_expression_empty); + } + if (!CalculateHelper.InputValid(input)) { return null; @@ -57,11 +65,6 @@ public static partial class QueryHelper return ResultHelper.CreateResult(result.RoundedResult, inputCulture, outputCulture, query, handleSave); } - catch (Mages.Core.ParseException) - { - // Invalid input - return ErrorHandler.OnError(isFallbackSearch, query, Properties.Resources.calculator_expression_not_complete); - } catch (OverflowException) { // Result to big to convert to decimal diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Microsoft.CmdPal.Ext.Calc.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Microsoft.CmdPal.Ext.Calc.csproj index 59f5ccbc70..fc84428a76 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Microsoft.CmdPal.Ext.Calc.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Microsoft.CmdPal.Ext.Calc.csproj @@ -9,15 +9,25 @@ Microsoft.CmdPal.Ext.Calc.pri + + CalculatorEngineCommon + $(OutDir) + + + - - + + + PreserveNewest + + + PreserveNewest + - Resources.resx diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.Designer.cs index 8cd385be32..8759c1209f 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.Designer.cs @@ -132,6 +132,15 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties { } } + /// + /// Looks up a localized string similar to Please enter an expression. + /// + public static string calculator_expression_empty { + get { + return ResourceManager.GetString("calculator_expression_empty", resourceCulture); + } + } + /// /// Looks up a localized string similar to Expression wrong or incomplete. /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.resx index 3c50d3a1c5..dba2ba2067 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Properties/Resources.resx @@ -199,4 +199,7 @@ Copy binary + + Please enter an expression + \ No newline at end of file diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/ClipboardHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/ClipboardHelper.cs index bbfec3c491..100d23e9f0 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/ClipboardHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/ClipboardHelper.cs @@ -30,6 +30,8 @@ internal static class ClipboardHelper (StandardDataFormats.Bitmap, ClipboardFormat.Image), ]; + private static readonly ClipboardThreadQueue ClipboardThreadQueue = new ClipboardThreadQueue(); + internal static async Task GetAvailableClipboardFormatsAsync(DataPackageView clipboardData) { var availableClipboardFormats = DataFormats.Aggregate( @@ -58,9 +60,12 @@ internal static class ClipboardHelper try { // Clipboard.SetContentWithOptions(output, null); - Clipboard.SetContent(output); - Flush(); - ExtensionHost.LogMessage(new LogMessage() { Message = "Copied text to clipboard" }); + ClipboardThreadQueue.EnqueueTask(() => + { + Clipboard.SetContent(output); + Flush(); + ExtensionHost.LogMessage(new LogMessage() { Message = "Copied text to clipboard" }); + }); } catch (COMException ex) { @@ -74,27 +79,32 @@ internal static class ClipboardHelper // TODO(stefan): For some reason Flush() fails from time to time when directly activated via hotkey. // Calling inside a loop makes it work. // Exception is: The operation is not permitted because the calling application is not the owner of the data on the clipboard. - const int maxAttempts = 5; - for (var i = 1; i <= maxAttempts; i++) + ClipboardThreadQueue.EnqueueTask(() => { - try + const int maxAttempts = 5; + + for (var i = 1; i <= maxAttempts; i++) { - Task.Run(Clipboard.Flush).Wait(); - return true; - } - catch (Exception ex) - { - if (i == maxAttempts) + try { - ExtensionHost.LogMessage(new LogMessage() + Task.Run(Clipboard.Flush).Wait(); + return; + } + catch (Exception ex) + { + if (i == maxAttempts) { - Message = $"{nameof(Clipboard)}.{nameof(Flush)}() failed: {ex}", - }); + ExtensionHost.LogMessage(new LogMessage() + { + Message = $"{nameof(Clipboard)}.{nameof(Flush)}() failed: {ex}", + }); + } } } - } + }); - return false; + // We cannot get the real result of the Flush() call here, as it is executed in a different thread. + return true; } private static async Task FlushAsync() => await Task.Run(Flush); @@ -105,7 +115,7 @@ internal static class ClipboardHelper DataPackage output = new(); output.SetStorageItems([storageFile]); - Clipboard.SetContent(output); + ClipboardThreadQueue.EnqueueTask(() => Clipboard.SetContent(output)); await FlushAsync(); } @@ -118,7 +128,7 @@ internal static class ClipboardHelper { DataPackage output = new(); output.SetBitmap(image); - Clipboard.SetContentWithOptions(output, null); + ClipboardThreadQueue.EnqueueTask(() => Clipboard.SetContentWithOptions(output, null)); Flush(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/ClipboardThreadScheduler.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/ClipboardThreadScheduler.cs new file mode 100644 index 0000000000..0f36f66453 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/ClipboardThreadScheduler.cs @@ -0,0 +1,74 @@ +// 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.Threading; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers; + +public partial class ClipboardThreadQueue : IDisposable +{ + private readonly Thread _thread; + private readonly ConcurrentQueue _taskQueue = new ConcurrentQueue(); + private readonly AutoResetEvent _taskAvailable = new AutoResetEvent(false); + private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); + + public ClipboardThreadQueue() + { + _thread = new Thread(() => + { + var hr = NativeMethods.CoInitialize(IntPtr.Zero); + if (hr != 0) + { + ExtensionHost.LogMessage($"CoInitialize failed with HRESULT: {hr}"); + } + + while (true) + { + _taskAvailable.WaitOne(); + + if (cancellationToken.IsCancellationRequested) + { + break; + } + + while (_taskQueue.TryDequeue(out var task)) + { + try + { + task(); + } + catch (Exception ex) + { + ExtensionHost.LogMessage($"Error executing task in ClipboardThreadQueue: {ex.Message}"); + } + } + } + + NativeMethods.CoUninitialize(); + }); + + _thread.SetApartmentState(ApartmentState.STA); + _thread.IsBackground = true; + _thread.Start(); + } + + public void EnqueueTask(Action task) + { + _taskQueue.Enqueue(task); + _taskAvailable.Set(); + } + + public void Dispose() + { + cancellationToken.Cancel(); + _taskAvailable.Set(); + _thread.Join(); // Wait for the thread to finish processing tasks + + _taskAvailable.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/NativeMethods.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/NativeMethods.cs index f4b6089229..1e0a46f030 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/NativeMethods.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/NativeMethods.cs @@ -8,7 +8,7 @@ using Windows.Foundation; namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers; -internal static class NativeMethods +public static partial class NativeMethods { [StructLayout(LayoutKind.Sequential)] [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")] @@ -98,4 +98,10 @@ internal static class NativeMethods [DllImport("user32.dll")] internal static extern bool GetCursorPos(out PointInter lpPoint); + + [LibraryImport("ole32.dll")] + internal static partial int CoInitialize(IntPtr pvReserved); + + [LibraryImport("ole32.dll")] + internal static partial void CoUninitialize(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeFactory.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeFactory.cs new file mode 100644 index 0000000000..6fd708f265 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeFactory.cs @@ -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. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using ManagedCsWin32; +using WinRT; + +namespace Microsoft.CmdPal.Ext.Indexer.Data; + +internal static class ActionRuntimeFactory +{ + private const string ActionRuntimeClsidStr = "C36FEF7E-35F3-4192-9F2C-AF1FD425FB85"; + + // typeof(Windows.AI.Actions.IActionRuntime).GUID + private static readonly Guid IActionRuntimeIID = Guid.Parse("206EFA2C-C909-508A-B4B0-9482BE96DB9C"); + + public static unsafe global::Windows.AI.Actions.ActionRuntime CreateActionRuntime() + { + IntPtr abiPtr = default; + try + { + Guid classId = Guid.Parse(ActionRuntimeClsidStr); + Guid iid = IActionRuntimeIID; + + var hresult = Ole32.CoCreateInstance(ref Unsafe.AsRef(in classId), IntPtr.Zero, CLSCTX.LocalServer, ref iid, out abiPtr); + Marshal.ThrowExceptionForHR((int)hresult); + + return MarshalInterface.FromAbi(abiPtr); + } + finally + { + MarshalInspectable.DisposeAbi(abiPtr); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Assets/Actions.png b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Assets/Actions.png new file mode 100644 index 0000000000..6aaddfda85 Binary files /dev/null and b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Assets/Actions.png differ diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/ExecuteActionCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/ExecuteActionCommand.cs new file mode 100644 index 0000000000..2a3c895e9b --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/ExecuteActionCommand.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.AI.Actions.Hosting; + +namespace Microsoft.CmdPal.Ext.Indexer.Commands; + +internal sealed partial class ExecuteActionCommand : InvokableCommand +{ + private readonly ActionInstance actionInstance; + + internal ExecuteActionCommand(ActionInstance actionInstance) + { + this.actionInstance = actionInstance; + this.Name = actionInstance.DisplayInfo.Description; + this.Icon = new IconInfo(actionInstance.Definition.IconFullPath); + } + + public override CommandResult Invoke() + { + var task = Task.Run(InvokeAsync); + task.Wait(); + + return task.Result; + } + + private async Task InvokeAsync() + { + try + { + await actionInstance.InvokeAsync(); + return CommandResult.GoHome(); + } + catch (Exception ex) + { + return CommandResult.ShowToast("Failed to invoke action " + actionInstance.Definition.Id + ": " + ex.Message); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenPropertiesCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenPropertiesCommand.cs index a6611cf6b3..d07bbdca80 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenPropertiesCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenPropertiesCommand.cs @@ -5,13 +5,11 @@ using System; using System.Runtime.InteropServices; using ManagedCommon; +using ManagedCsWin32; using Microsoft.CmdPal.Ext.Indexer.Data; -using Microsoft.CmdPal.Ext.Indexer.Native; +using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils; using Microsoft.CmdPal.Ext.Indexer.Properties; using Microsoft.CommandPalette.Extensions.Toolkit; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.UI.Shell; using Windows.Win32.UI.WindowsAndMessaging; namespace Microsoft.CmdPal.Ext.Indexer.Commands; @@ -27,19 +25,16 @@ internal sealed partial class OpenPropertiesCommand : InvokableCommand try { - var filenamePCWSTR = new PCWSTR((char*)filenamePtr); - var propertiesPCWSTR = new PCWSTR((char*)propertiesPtr); - - var info = new SHELLEXECUTEINFOW + var info = new Shell32.SHELLEXECUTEINFOW { - cbSize = (uint)Marshal.SizeOf(), - lpVerb = propertiesPCWSTR, - lpFile = filenamePCWSTR, - nShow = (int)SHOW_WINDOW_CMD.SW_SHOW, - fMask = NativeHelpers.SEEMASKINVOKEIDLIST, + CbSize = (uint)sizeof(Shell32.SHELLEXECUTEINFOW), + LpVerb = propertiesPtr, + LpFile = filenamePtr, + Show = (int)SHOW_WINDOW_CMD.SW_SHOW, + FMask = NativeHelpers.SEEMASKINVOKEIDLIST, }; - return PInvoke.ShellExecuteEx(ref info); + return Shell32.ShellExecuteEx(ref info); } finally { diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenWithCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenWithCommand.cs index a9c431d32c..2c1875d3d7 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenWithCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/OpenWithCommand.cs @@ -3,13 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; +using ManagedCsWin32; using Microsoft.CmdPal.Ext.Indexer.Data; -using Microsoft.CmdPal.Ext.Indexer.Native; +using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils; using Microsoft.CmdPal.Ext.Indexer.Properties; using Microsoft.CommandPalette.Extensions.Toolkit; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.UI.Shell; using Windows.Win32.UI.WindowsAndMessaging; namespace Microsoft.CmdPal.Ext.Indexer.Commands; @@ -25,19 +23,16 @@ internal sealed partial class OpenWithCommand : InvokableCommand try { - var filenamePCWSTR = new PCWSTR((char*)filenamePtr); - var verbPCWSTR = new PCWSTR((char*)verbPtr); - - var info = new SHELLEXECUTEINFOW + var info = new Shell32.SHELLEXECUTEINFOW { - cbSize = (uint)Marshal.SizeOf(), - lpVerb = verbPCWSTR, - lpFile = filenamePCWSTR, - nShow = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL, - fMask = NativeHelpers.SEEMASKINVOKEIDLIST, + CbSize = (uint)sizeof(Shell32.SHELLEXECUTEINFOW), + LpVerb = verbPtr, + LpFile = filenamePtr, + Show = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL, + FMask = NativeHelpers.SEEMASKINVOKEIDLIST, }; - return PInvoke.ShellExecuteEx(ref info); + return Shell32.ShellExecuteEx(ref info); } finally { diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs index 57d399b668..f2bfbc1c1a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs @@ -4,8 +4,11 @@ using System.Collections.Generic; using Microsoft.CmdPal.Ext.Indexer.Commands; +using Microsoft.CmdPal.Ext.Indexer.Pages; using Microsoft.CmdPal.Ext.Indexer.Properties; +using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.Foundation.Metadata; namespace Microsoft.CmdPal.Ext.Indexer.Data; @@ -38,9 +41,24 @@ internal sealed partial class IndexerListItem : ListItem } } - MoreCommands = [ + IContextItem[] moreCommands = [ ..context, - new CommandContextItem(new OpenWithCommand(indexerItem)), + new CommandContextItem(new OpenWithCommand(indexerItem))]; + + if (ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4)) + { + var actionsListContextItem = new ActionsListContextItem(indexerItem.FullPath); + if (actionsListContextItem.AnyActions()) + { + moreCommands = [ + .. moreCommands, + actionsListContextItem + ]; + } + } + + MoreCommands = [ + .. moreCommands, new CommandContextItem(new ShowFileInFolderCommand(indexerItem.FullPath) { Name = Resources.Indexer_Command_ShowInFolder }), new CommandContextItem(new CopyPathCommand(indexerItem)), new CommandContextItem(new OpenInConsoleCommand(indexerItem)), diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/DataSourceManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/DataSourceManager.cs index e186251825..7b9f9bd45b 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/DataSourceManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/DataSourceManager.cs @@ -3,17 +3,15 @@ // See the LICENSE file in the project root for more information. using System; +using System.Runtime.CompilerServices; using ManagedCommon; -using Windows.Win32; -using Windows.Win32.System.Com; -using Windows.Win32.System.Search; +using ManagedCsWin32; +using Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; namespace Microsoft.CmdPal.Ext.Indexer.Indexer; internal static class DataSourceManager { - private static readonly Guid CLSIDCollatorDataSource = new("9E175B8B-F52A-11D8-B9A5-505054503030"); - private static IDBInitialize _dataSource; public static IDBInitialize GetDataSource() @@ -28,20 +26,18 @@ internal static class DataSourceManager private static bool InitializeDataSource() { - var hr = PInvoke.CoCreateInstance(CLSIDCollatorDataSource, null, CLSCTX.CLSCTX_INPROC_SERVER, typeof(IDBInitialize).GUID, out var dataSourceObj); - if (hr != 0) + var riid = typeof(IDBInitialize).GUID; + + try { - Logger.LogError("CoCreateInstance failed: " + hr); + _dataSource = ComHelper.CreateComInstance(ref Unsafe.AsRef(in CLSID.CollatorDataSource), CLSCTX.InProcServer); + } + catch (Exception e) + { + Logger.LogError($"Failed to create datasource. ex: {e.Message}"); return false; } - if (dataSourceObj == null) - { - Logger.LogError("CoCreateInstance failed: dataSourceObj is null"); - return false; - } - - _dataSource = (IDBInitialize)dataSourceObj; _dataSource.Initialize(); return true; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/DBPROP.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/DBPROP.cs index d4be1b967f..054e3d504c 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/DBPROP.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/DBPROP.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; +using Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; using Windows.Win32.Storage.IndexServer; -using Windows.Win32.System.Com.StructuredStorage; namespace Microsoft.CmdPal.Ext.Indexer.Indexer.OleDB; @@ -16,6 +16,6 @@ internal struct DBPROP public uint dwOptions; public uint dwStatus; public DBID colid; - public PROPVARIANT vValue; + public PropVariant vValue; #pragma warning restore SA1307 // Accessible fields should begin with upper-case letter } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowset.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowset.cs index 3127e2e563..75502f56c2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowset.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowset.cs @@ -4,40 +4,38 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.CmdPal.Ext.Indexer.Indexer.OleDB; -[ComImport] [Guid("0c733a7c-2a1c-11ce-ade5-00aa0044773d")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IRowset +[GeneratedComInterface] +public partial interface IRowset { - [PreserveSig] - int AddRefRows( + void AddRefRows( uint cRows, - [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] rghRows, - [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] uint[] rgRefCounts, - [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] rgRowStatus); + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] rghRows, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] uint[] rgRefCounts, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] rgRowStatus); - [PreserveSig] - int GetData( + void GetData( IntPtr hRow, IntPtr hAccessor, IntPtr pData); - [PreserveSig] - int GetNextRows( + void GetNextRows( IntPtr hReserved, long lRowsOffset, long cRows, out uint pcRowsObtained, out IntPtr prghRows); - [PreserveSig] - int ReleaseRows( + void ReleaseRows( uint cRows, - [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] rghRows, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] rghRows, IntPtr rgRowOptions, - [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] uint[] rgRefCounts, - [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] rgRowStatus); + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] uint[] rgRefCounts, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] rgRowStatus); + + void RestartPosition(nuint hReserved); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowsetInfo.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowsetInfo.cs index 5c891c8036..bf1406179b 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowsetInfo.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/OleDB/IRowsetInfo.cs @@ -4,29 +4,29 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.CmdPal.Ext.Indexer.Indexer.OleDB; -[ComImport] [Guid("0C733A55-2A1C-11CE-ADE5-00AA0044773D")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IRowsetInfo +[GeneratedComInterface] +public partial interface IRowsetInfo { [PreserveSig] int GetProperties( uint cPropertyIDSets, - [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] DBPROPIDSET[] rgPropertyIDSets, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] DBPROPIDSET[] rgPropertyIDSets, out ulong pcPropertySets, out IntPtr prgPropertySets); [PreserveSig] int GetReferencedRowset( uint iOrdinal, - [In] ref Guid riid, - [Out, MarshalAs(UnmanagedType.Interface)] out object ppReferencedRowset); + ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out object ppReferencedRowset); [PreserveSig] int GetSpecification( - [In] ref Guid riid, - [Out, MarshalAs(UnmanagedType.Interface)] out object ppSpecification); + ref Guid riid, + [MarshalAs(UnmanagedType.Interface)] out object ppSpecification); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchQuery.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchQuery.cs index abe12396ea..8fa972f302 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchQuery.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchQuery.cs @@ -4,16 +4,15 @@ using System; using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using ManagedCommon; +using ManagedCsWin32; using Microsoft.CmdPal.Ext.Indexer.Indexer.OleDB; +using Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils; -using Microsoft.CmdPal.Ext.Indexer.Native; -using Windows.Win32; -using Windows.Win32.System.Com; -using Windows.Win32.System.Search; -using Windows.Win32.UI.Shell.PropertiesSystem; +using static Microsoft.CmdPal.Ext.Indexer.Indexer.Utils.NativeHelpers; namespace Microsoft.CmdPal.Ext.Indexer.Indexer; @@ -120,11 +119,6 @@ internal sealed partial class SearchQuery : IDisposable // We need to generate a search query string with the search text the user entered above if (currentRowset != null) { - if (reuseRowset != null) - { - Marshal.ReleaseComObject(reuseRowset); - } - // We have a previous rowset, this means the user is typing and we should store this // recapture the where ID from this so the next ExecuteSync call will be faster reuseRowset = currentRowset; @@ -148,13 +142,10 @@ internal sealed partial class SearchQuery : IDisposable private bool HandleRow(IGetRow getRow, nuint rowHandle) { - object propertyStorePtr = null; - try { - getRow.GetRowFromHROW(null, rowHandle, typeof(IPropertyStore).GUID, out propertyStorePtr); + getRow.GetRowFromHROW(null, rowHandle, ref Unsafe.AsRef(in IID.IPropertyStore), out var propertyStore); - var propertyStore = (IPropertyStore)propertyStorePtr; if (propertyStore == null) { Logger.LogError("Failed to get IPropertyStore interface"); @@ -176,14 +167,6 @@ internal sealed partial class SearchQuery : IDisposable Logger.LogError("Error handling row", ex); return false; } - finally - { - // Ensure the COM object is released if not returned - if (propertyStorePtr != null) - { - Marshal.ReleaseComObject(propertyStorePtr); - } - } } public bool FetchRows(int offset, int limit) @@ -194,16 +177,17 @@ internal sealed partial class SearchQuery : IDisposable return false; } - if (currentRowset is not IGetRow) + IGetRow getRow = null; + + try + { + getRow = (IGetRow)currentRowset; + } + catch (Exception) { Logger.LogInfo("Reset the current rowset"); ExecuteSyncInternal(); - } - - if (currentRowset is not IGetRow getRow) - { - Logger.LogError("Rowset does not support IGetRow interface"); - return false; + getRow = (IGetRow)currentRowset; } uint rowCountReturned; @@ -211,12 +195,7 @@ internal sealed partial class SearchQuery : IDisposable try { - var res = currentRowset.GetNextRows(IntPtr.Zero, offset, limit, out rowCountReturned, out prghRows); - if (res < 0) - { - Logger.LogError($"Error fetching rows: {res}"); - return false; - } + currentRowset.GetNextRows(IntPtr.Zero, offset, limit, out rowCountReturned, out prghRows); if (rowCountReturned == 0) { @@ -237,11 +216,7 @@ internal sealed partial class SearchQuery : IDisposable } } - res = currentRowset.ReleaseRows(rowCountReturned, rowHandles, IntPtr.Zero, null, null); - if (res != 0) - { - Logger.LogError($"Error releasing rows: {res}"); - } + currentRowset.ReleaseRows(rowCountReturned, rowHandles, IntPtr.Zero, null, null); Marshal.FreeCoTaskMem(prghRows); prghRows = IntPtr.Zero; @@ -268,11 +243,6 @@ internal sealed partial class SearchQuery : IDisposable var rowset = ExecuteCommand(queryStr); if (rowset != null) { - if (reuseRowset != null) - { - Marshal.ReleaseComObject(reuseRowset); - } - reuseRowset = rowset; reuseWhereID = GetReuseWhereId(reuseRowset); } @@ -280,110 +250,50 @@ internal sealed partial class SearchQuery : IDisposable private unsafe IRowset ExecuteCommand(string queryStr) { - object sessionPtr = null; - object commandPtr = null; + if (string.IsNullOrEmpty(queryStr)) + { + return null; + } try { var session = (IDBCreateSession)DataSourceManager.GetDataSource(); - session.CreateSession(null, typeof(IDBCreateCommand).GUID, out sessionPtr); - if (sessionPtr == null) + var guid = typeof(IDBCreateCommand).GUID; + session.CreateSession(IntPtr.Zero, ref guid, out var ppDBSession); + + if (ppDBSession == null) { Logger.LogError("CreateSession failed"); return null; } - var createCommand = (IDBCreateCommand)sessionPtr; - createCommand.CreateCommand(null, typeof(ICommandText).GUID, out commandPtr); - if (commandPtr == null) - { - Logger.LogError("CreateCommand failed"); - return null; - } + var createCommand = (IDBCreateCommand)ppDBSession; + guid = typeof(ICommandText).GUID; + createCommand.CreateCommand(IntPtr.Zero, ref guid, out ICommandText commandText); - var commandText = (ICommandText)commandPtr; if (commandText == null) { Logger.LogError("Failed to get ICommandText interface"); return null; } - commandText.SetCommandText(in NativeHelpers.OleDb.DbGuidDefault, queryStr); - commandText.Execute(null, typeof(IRowset).GUID, null, null, out var rowsetPointer); + var riid = NativeHelpers.OleDb.DbGuidDefault; - return rowsetPointer as IRowset; + var irowSetRiid = typeof(IRowset).GUID; + + commandText.SetCommandText(ref riid, queryStr); + commandText.Execute(null, ref irowSetRiid, null, out var pcRowsAffected, out var rowsetPointer); + + return rowsetPointer; } catch (Exception ex) { Logger.LogError("Unexpected error.", ex); return null; } - finally - { - // Release the command pointer - if (commandPtr != null) - { - Marshal.ReleaseComObject(commandPtr); - } - - // Release the session pointer - if (sessionPtr != null) - { - Marshal.ReleaseComObject(sessionPtr); - } - } } - private IRowsetInfo GetRowsetInfo(IRowset rowset) - { - if (rowset == null) - { - return null; - } - - var rowsetPtr = IntPtr.Zero; - var rowsetInfoPtr = IntPtr.Zero; - - try - { - // Get the IUnknown pointer for the IRowset object - rowsetPtr = Marshal.GetIUnknownForObject(rowset); - - // Query for IRowsetInfo interface - var rowsetInfoGuid = typeof(IRowsetInfo).GUID; - var res = Marshal.QueryInterface(rowsetPtr, in rowsetInfoGuid, out rowsetInfoPtr); - if (res != 0) - { - Logger.LogError($"Error getting IRowsetInfo interface: {res}"); - return null; - } - - // Marshal the interface pointer to the actual IRowsetInfo object - var rowsetInfo = (IRowsetInfo)Marshal.GetObjectForIUnknown(rowsetInfoPtr); - return rowsetInfo; - } - catch (Exception ex) - { - Logger.LogError($"Exception occurred while getting IRowsetInfo. ", ex); - return null; - } - finally - { - // Release the IRowsetInfo pointer if it was obtained - if (rowsetInfoPtr != IntPtr.Zero) - { - Marshal.Release(rowsetInfoPtr); // Release the IRowsetInfo pointer - } - - // Release the IUnknown pointer for the IRowset object - if (rowsetPtr != IntPtr.Zero) - { - Marshal.Release(rowsetPtr); - } - } - } - - private DBPROP? GetPropset(IRowsetInfo rowsetInfo) + private unsafe DBPROP? GetPropset(IRowsetInfo rowsetInfo) { var prgPropSetsPtr = IntPtr.Zero; @@ -403,16 +313,15 @@ internal sealed partial class SearchQuery : IDisposable return null; } - var firstPropSetPtr = new IntPtr(prgPropSetsPtr.ToInt64()); - var propSet = Marshal.PtrToStructure(firstPropSetPtr); + var firstPropSetPtr = (DBPROPSET*)prgPropSetsPtr.ToInt64(); + var propSet = *firstPropSetPtr; if (propSet.cProperties == 0 || propSet.rgProperties == IntPtr.Zero) { return null; } - var propPtr = new IntPtr(propSet.rgProperties.ToInt64()); - var prop = Marshal.PtrToStructure(propPtr); - return prop; + var propPtr = (DBPROP*)propSet.rgProperties.ToInt64(); + return *propPtr; } catch (Exception ex) { @@ -431,7 +340,8 @@ internal sealed partial class SearchQuery : IDisposable private uint GetReuseWhereId(IRowset rowset) { - var rowsetInfo = GetRowsetInfo(rowset); + var rowsetInfo = (IRowsetInfo)rowset; + if (rowsetInfo == null) { return 0; @@ -443,9 +353,9 @@ internal sealed partial class SearchQuery : IDisposable return 0; } - if (prop?.vValue.Anonymous.Anonymous.vt == VARENUM.VT_UI4) + if (prop?.vValue.VarType == VarEnum.VT_UI4) { - var value = prop?.vValue.Anonymous.Anonymous.Anonymous.ulVal; + var value = prop?.vValue._ulong; return (uint)value; } @@ -462,18 +372,6 @@ internal sealed partial class SearchQuery : IDisposable Marshal.FreeCoTaskMem(dbPropIdSet.rgPropertyIDs); } - if (reuseRowset != null) - { - Marshal.ReleaseComObject(reuseRowset); - reuseRowset = null; - } - - if (currentRowset != null) - { - Marshal.ReleaseComObject(currentRowset); - currentRowset = null; - } - queryCompletedEvent?.Dispose(); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchResult.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchResult.cs index 1840338b73..b44e9ab11b 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchResult.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchResult.cs @@ -3,12 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Runtime.InteropServices; using ManagedCommon; +using Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils; -using Microsoft.CmdPal.Ext.Indexer.Native; -using Windows.Win32.System.Com; -using Windows.Win32.System.Com.StructuredStorage; -using Windows.Win32.UI.Shell.PropertiesSystem; namespace Microsoft.CmdPal.Ext.Indexer.Indexer; @@ -39,14 +37,9 @@ internal sealed class SearchResult { try { - var key = NativeHelpers.PropertyKeys.PKEYItemNameDisplay; - propStore.GetValue(&key, out var itemNameDisplay); - - key = NativeHelpers.PropertyKeys.PKEYItemUrl; - propStore.GetValue(&key, out var itemUrl); - - key = NativeHelpers.PropertyKeys.PKEYKindText; - propStore.GetValue(&key, out var kindText); + propStore.GetValue(NativeHelpers.PropertyKeys.PKEYItemNameDisplay, out var itemNameDisplay); + propStore.GetValue(NativeHelpers.PropertyKeys.PKEYItemUrl, out var itemUrl); + propStore.GetValue(NativeHelpers.PropertyKeys.PKEYKindText, out var kindText); var filePath = GetFilePath(ref itemUrl); var isFolder = IsFoder(ref kindText); @@ -67,28 +60,34 @@ internal sealed class SearchResult } } - private static bool IsFoder(ref PROPVARIANT kindText) + private static bool IsFoder(ref PropVariant kindText) { var kindString = GetStringFromPropVariant(ref kindText); return string.Equals(kindString, "Folder", StringComparison.OrdinalIgnoreCase); } - private static string GetFilePath(ref PROPVARIANT itemUrl) + private static string GetFilePath(ref PropVariant itemUrl) { var filePath = GetStringFromPropVariant(ref itemUrl); filePath = UrlToFilePathConverter.Convert(filePath); return filePath; } - private static string GetStringFromPropVariant(ref PROPVARIANT propVariant) + private static string GetStringFromPropVariant(ref PropVariant propVariant) { - if (propVariant.Anonymous.Anonymous.vt == VARENUM.VT_LPWSTR) + if (propVariant.VarType == System.Runtime.InteropServices.VarEnum.VT_LPWSTR) { - var pwszVal = propVariant.Anonymous.Anonymous.Anonymous.pwszVal; - if (pwszVal != null) + var pwszVal = propVariant._ptr; + + if (pwszVal == IntPtr.Zero) { - return pwszVal.ToString(); + return string.Empty; } + + // convert to string + var str = Marshal.PtrToStringUni(pwszVal); + + return str; } return string.Empty; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchCatalogManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchCatalogManager.cs deleted file mode 100644 index bc2b855844..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchCatalogManager.cs +++ /dev/null @@ -1,18 +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.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; - -[CoClass(typeof(CSearchCatalogManagerClass))] -[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF50")] -[ComImport] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1715:Identifiers should have correct prefix", Justification = "Using original name from type lib")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:Interface names should begin with I", Justification = "Using original name from type lib")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Using original name from type lib")] -public interface CSearchCatalogManager : ISearchCatalogManager -{ -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchCatalogManagerClass.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchCatalogManagerClass.cs deleted file mode 100644 index 6f64e518a0..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchCatalogManagerClass.cs +++ /dev/null @@ -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. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; - -[ComConversionLoss] -[Guid("AAB49DD5-AD0B-40AE-B654-AE8976BF6BD2")] -[ClassInterface((short)0)] -[ComImport] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")] -public class CSearchCatalogManagerClass : ISearchCatalogManager, CSearchCatalogManager -{ - [DispId(1610678272)] - public virtual extern string Name - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void GetCatalogStatus( - out object pStatus, - out object pPausedReason); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void Reset(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void Reindex(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void ReindexMatchingURLs([MarshalAs(UnmanagedType.LPWStr), In] string pszPattern); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void ReindexSearchRoot([MarshalAs(UnmanagedType.LPWStr), In] string pszRoot); - - [DispId(1610678280)] - public virtual extern uint ConnectTimeout - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [DispId(1610678282)] - public virtual extern uint DataTimeout - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern int NumberOfItems(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void NumberOfItemsToIndex( - out int plIncrementalCount, - out int plNotificationQueue, - out int plHighPriorityQueue); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - public virtual extern string URLBeingIndexed(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern uint GetURLIndexingState([MarshalAs(UnmanagedType.LPWStr), In] string psz); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - public virtual extern object GetPersistentItemsChangedSink(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void RegisterViewForNotification( - [MarshalAs(UnmanagedType.LPWStr), In] string pszView, - [MarshalAs(UnmanagedType.Interface), In] object pViewChangedSink, - out uint pdwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void GetItemsChangedSink( - [MarshalAs(UnmanagedType.Interface), In] object pISearchNotifyInlineSite, - [In] ref Guid riid, - out IntPtr ppv, - out Guid pGUIDCatalogResetSignature, - out Guid pGUIDCheckPointSignature, - out uint pdwLastCheckPointNumber); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void UnregisterViewForNotification([In] uint dwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void SetExtensionClusion([MarshalAs(UnmanagedType.LPWStr), In] string pszExtension, [In] int fExclude); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - public virtual extern object EnumerateExcludedExtensions(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - public virtual extern CSearchQueryHelper GetQueryHelper(); - - [DispId(1610678295)] - public virtual extern int DiacriticSensitivity - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - public virtual extern object GetCrawlScopeManager(); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchManager.cs deleted file mode 100644 index 210bb3437e..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchManager.cs +++ /dev/null @@ -1,18 +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.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; - -[CoClass(typeof(CSearchManagerClass))] -[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF69")] -[ComImport] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1715:Identifiers should have correct prefix", Justification = "Using original name from type lib")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:Interface names should begin with I", Justification = "Using original name from type lib")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Using original name from type lib")] -public interface CSearchManager : ISearchManager -{ -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchManagerClass.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchManagerClass.cs deleted file mode 100644 index 19a8067bb7..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchManagerClass.cs +++ /dev/null @@ -1,90 +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.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; - -[Guid("7D096C5F-AC08-4F1F-BEB7-5C22C517CE39")] -[TypeLibType(2)] -[ClassInterface((short)0)] -[ComConversionLoss] -[ComImport] -public class CSearchManagerClass : ISearchManager, CSearchManager -{ - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void GetIndexerVersionStr([MarshalAs(UnmanagedType.LPWStr)] out string ppszVersionString); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void GetIndexerVersion(out uint pdwMajor, out uint pdwMinor); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue); - - [DispId(1610678276)] - public virtual extern string ProxyName - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [DispId(1610678277)] - public virtual extern string BypassList - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void SetProxy( - [In] object sUseProxy, - [In] int fLocalByPassProxy, - [In] uint dwPortNumber, - [MarshalAs(UnmanagedType.LPWStr), In] string pszProxyName, - [MarshalAs(UnmanagedType.LPWStr), In] string pszByPassList); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - public virtual extern CSearchCatalogManager GetCatalog([MarshalAs(UnmanagedType.LPWStr), In] string pszCatalog); - - [DispId(1610678280)] - public virtual extern string UserAgent - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - } - - [DispId(1610678282)] - public virtual extern object UseProxy - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [DispId(1610678283)] - public virtual extern int LocalBypass - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [DispId(1610678284)] - public virtual extern uint PortNumber - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchQueryHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchQueryHelper.cs deleted file mode 100644 index c4a08859ff..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchQueryHelper.cs +++ /dev/null @@ -1,18 +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.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; - -[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF63")] -[CoClass(typeof(CSearchQueryHelperClass))] -[ComImport] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1715:Identifiers should have correct prefix", Justification = "Using original name from type lib")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:Interface names should begin with I", Justification = "Using original name from type lib")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Using original name from type lib")] -public interface CSearchQueryHelper : ISearchQueryHelper -{ -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchQueryHelperClass.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchQueryHelperClass.cs deleted file mode 100644 index f89de6050b..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/CSearchQueryHelperClass.cs +++ /dev/null @@ -1,134 +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.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; - -[ClassInterface((short)0)] -[Guid("B271E955-09E1-42E1-9B95-5994A534B613")] -[ComImport] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")] -public class CSearchQueryHelperClass : ISearchQueryHelper, CSearchQueryHelper -{ - [DispId(1610678272)] - public virtual extern string ConnectionString - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [DispId(1610678273)] - public virtual extern uint QueryContentLocale - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [DispId(1610678275)] - public virtual extern uint QueryKeywordLocale - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [DispId(1610678277)] - public virtual extern object QueryTermExpansion - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [DispId(1610678279)] - public virtual extern object QuerySyntax - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [DispId(1610678281)] - public virtual extern string QueryContentProperties - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [DispId(1610678283)] - public virtual extern string QuerySelectColumns - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [DispId(1610678285)] - public virtual extern string QueryWhereRestrictions - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [DispId(1610678287)] - public virtual extern string QuerySorting - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - public virtual extern string GenerateSQLFromUserQuery([MarshalAs(UnmanagedType.LPWStr), In] string pszQuery); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public virtual extern void WriteProperties( - [In] int itemID, - [In] uint dwNumberOfColumns, - [In] ref object pColumns, - [In] ref object pValues, - [In] ref object pftGatherModifiedTime); - - [DispId(1610678291)] - public virtual extern int QueryMaxResults - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ICommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ICommand.cs new file mode 100644 index 0000000000..e04da52d1c --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ICommand.cs @@ -0,0 +1,21 @@ +// 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.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using Microsoft.CmdPal.Ext.Indexer.Indexer.OleDB; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[Guid("0C733A63-2A1C-11CE-ADE5-00AA0044773D")] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +public partial interface ICommand +{ + void Cancel(); + + void Execute([MarshalAs(UnmanagedType.Interface)] object pUnkOuter, ref Guid riid, [Optional][MarshalAs(UnmanagedType.Interface)] object pParams, [Optional]out int pcRowsAffected, out IRowset ppRowset); + + void GetDBSession(ref Guid riid, out IntPtr ppSession); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ICommandText.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ICommandText.cs new file mode 100644 index 0000000000..f01aa48810 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ICommandText.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CmdPal.Ext.Indexer.Indexer.OleDB; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[Guid("0C733A27-2A1C-11CE-ADE5-00AA0044773D")] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +public partial interface ICommandText : ICommand +{ + void GetCommandText([Optional] ref Guid pguidDialect, out IntPtr ppwszCommand); + + void SetCommandText(ref Guid rguidDialect, string pwszCommand); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBCreateCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBCreateCommand.cs new file mode 100644 index 0000000000..25bd1c1c4e --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBCreateCommand.cs @@ -0,0 +1,16 @@ +// 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.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[Guid("0C733A1D-2A1C-11CE-ADE5-00AA0044773D")] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +public partial interface IDBCreateCommand +{ + void CreateCommand(IntPtr pUnkOuter, ref Guid riid, out ICommandText ppCommand); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBCreateSession.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBCreateSession.cs new file mode 100644 index 0000000000..6d2f0cafef --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBCreateSession.cs @@ -0,0 +1,20 @@ +// 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.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[Guid("0C733A5D-2A1C-11CE-ADE5-00AA0044773D")] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +public partial interface IDBCreateSession +{ + void CreateSession(IntPtr pUnkOuter, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppDBSession); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBInitialize.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBInitialize.cs new file mode 100644 index 0000000000..40f8532427 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IDBInitialize.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[Guid("0C733A8B-2A1C-11CE-ADE5-00AA0044773D")] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +public partial interface IDBInitialize +{ + void Initialize(); + + void Uninitialize(); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IGetRow.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IGetRow.cs new file mode 100644 index 0000000000..581ee9e8ec --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IGetRow.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[Guid("0C733AAF-2A1C-11CE-ADE5-00AA0044773D")] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +public partial interface IGetRow +{ + unsafe void GetRowFromHROW([MarshalAs(UnmanagedType.Interface)] object pUnkOuter, nuint hRow, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IPropertyStore ppUnk); + + unsafe string GetURLFromHROW(nuint hRow); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IPropertyStore.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IPropertyStore.cs new file mode 100644 index 0000000000..1bd733f7b9 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/IPropertyStore.cs @@ -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; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling] + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99")] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +public partial interface IPropertyStore +{ + uint GetCount(); + + PropertyKey GetAt(uint iProp); + + void GetValue(in PropertyKey pkey, out PropVariant pv); + + void SetValue(in PropertyKey pkey, in PropVariant pv); + + void Commit(); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchCatalogManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchCatalogManager.cs index 04f6f76be0..f89c882669 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchCatalogManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchCatalogManager.cs @@ -5,125 +5,68 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; [Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF50")] -[ComConversionLoss] -[InterfaceType(1)] -[ComImport] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")] -public interface ISearchCatalogManager +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Please do not change the function name")] +public partial interface ISearchCatalogManager { - [DispId(1610678272)] - string Name - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + string get_Name(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName); + void GetParameter(string pszName, out IntPtr pValue); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue); + void SetParameter(string pszName, ref IntPtr pValue); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetCatalogStatus(out object pStatus, out object pPausedReason); + void GetCatalogStatus(out uint pdwStatus, out uint pdwPausedReason); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] void Reset(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] void Reindex(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void ReindexMatchingURLs([MarshalAs(UnmanagedType.LPWStr), In] string pszPattern); + void ReindexMatchingURLs(string pszPattern); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void ReindexSearchRoot([MarshalAs(UnmanagedType.LPWStr), In] string pszRoot); + void ReindexSearchRoot(string pszRoot); - [DispId(1610678280)] - uint ConnectTimeout - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + uint get_ConnectTimeout(); - [DispId(1610678282)] - uint DataTimeout - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + void put_ConnectTimeout(uint dwTimeout); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - int NumberOfItems(); + uint get_DataTimeout(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void NumberOfItemsToIndex( - out int plIncrementalCount, - out int plNotificationQueue, - out int plHighPriorityQueue); + void put_DataTimeout(uint dwTimeout); + + uint NumberOfItems(); + + uint NumberOfItemsToIndex(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [return: MarshalAs(UnmanagedType.LPWStr)] string URLBeingIndexed(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetURLIndexingState([MarshalAs(UnmanagedType.LPWStr), In] string psz); + void GetURLIndexingState(string pszURL, out uint pdwState); + + IntPtr GetPersistentItemsChangedSink(); + + void RegisterViewForNotification(string pszView, IntPtr pViewNotify, out uint pdwCookie); + + IntPtr GetItemsChangedSink(); + + void UnregisterViewForNotification(uint dwCookie); + + void SetExtensionClusion(string pszExtension, [MarshalAs(UnmanagedType.Bool)] bool fExclude); + + void EnumerateExcludedExtensions(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [return: MarshalAs(UnmanagedType.Interface)] - object GetPersistentItemsChangedSink(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void RegisterViewForNotification( - [MarshalAs(UnmanagedType.LPWStr), In] string pszView, - [MarshalAs(UnmanagedType.Interface), In] object pViewChangedSink, - out uint pdwCookie); + ISearchQueryHelper GetQueryHelper(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetItemsChangedSink( - [MarshalAs(UnmanagedType.Interface), In] object pISearchNotifyInlineSite, - [In] ref Guid riid, - out IntPtr ppv, - out Guid pGUIDCatalogResetSignature, - out Guid pGUIDCheckPointSignature, - out uint pdwLastCheckPointNumber); + [return: MarshalAs(UnmanagedType.Bool)] + bool get_DiacriticSensitivity(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void UnregisterViewForNotification([In] uint dwCookie); + void put_DiacriticSensitivity([MarshalAs(UnmanagedType.Bool)] bool fDiacriticSensitive); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetExtensionClusion([MarshalAs(UnmanagedType.LPWStr), In] string pszExtension, [In] int fExclude); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - object EnumerateExcludedExtensions(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - CSearchQueryHelper GetQueryHelper(); - - [DispId(1610678295)] - int DiacriticSensitivity - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.Interface)] - object GetCrawlScopeManager(); + IntPtr GetCrawlScopeManager(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchManager.cs index 042e0f7660..c8e7a37544 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchManager.cs @@ -3,87 +3,46 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; -[ComConversionLoss] [Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF69")] -[InterfaceType(1)] -[ComImport] -public interface ISearchManager +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Please do not change the function name")] +public partial interface ISearchManager { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetIndexerVersionStr([MarshalAs(UnmanagedType.LPWStr)] out string ppszVersionString); + string GetIndexerVersionStr(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] void GetIndexerVersion(out uint pdwMajor, out uint pdwMinor); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName); + void GetParameter(string pszName, [MarshalAs(UnmanagedType.Interface)] out object pValue); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue); + void SetParameter(string pszName, [MarshalAs(UnmanagedType.Interface)] ref object pValue); - [DispId(1610678276)] - string ProxyName - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + [return: MarshalAs(UnmanagedType.Bool)] + bool get_UseProxy(); - [DispId(1610678277)] - string BypassList - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + string get_BypassList(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] void SetProxy( - [In] object sUseProxy, - [In] int fLocalByPassProxy, - [In] uint dwPortNumber, - [MarshalAs(UnmanagedType.LPWStr), In] string pszProxyName, - [MarshalAs(UnmanagedType.LPWStr), In] string pszByPassList); + string pszProxyName, + [MarshalAs(UnmanagedType.Bool)] bool fLocalBypass, + string pszBypassList, + uint dwPortNumber); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [return: MarshalAs(UnmanagedType.Interface)] - CSearchCatalogManager GetCatalog([MarshalAs(UnmanagedType.LPWStr), In] string pszCatalog); + ISearchCatalogManager GetCatalog(string pszCatalog); - [DispId(1610678280)] - string UserAgent - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - } + string get_UserAgent(); - [DispId(1610678282)] - object UseProxy - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + void put_UserAgent(string pszUserAgent); - [DispId(1610678283)] - int LocalBypass - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + string get_ProxyName(); - [DispId(1610678284)] - uint PortNumber - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + [return: MarshalAs(UnmanagedType.Bool)] + bool get_LocalBypass(); + + uint get_PortNumber(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchQueryHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchQueryHelper.cs index e560935639..d1a49e83bd 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchQueryHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/ISearchQueryHelper.cs @@ -5,130 +5,74 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; [Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF63")] -[InterfaceType(1)] -[ComImport] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")] -public interface ISearchQueryHelper +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "I don't want to change the name")] +public partial interface ISearchQueryHelper { - [DispId(1610678272)] - string ConnectionString - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + string GetConnectionString(); - [DispId(1610678273)] - uint QueryContentLocale - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + void SetQueryContentLocale(int lcid); - [DispId(1610678275)] - uint QueryKeywordLocale - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + uint GetQueryContentLocale(); - [DispId(1610678277)] - object QueryTermExpansion - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + void SetQueryKeywordLocale(int lcid); - [DispId(1610678279)] - object QuerySyntax - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + uint GetQueryKeywordLocale(); - [DispId(1610678281)] - string QueryContentProperties - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + void SetQueryTermExpansion(SEARCH_TERM_EXPANSION expandTerms); - [DispId(1610678283)] - string QuerySelectColumns - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + void GetQueryTermExpansion(out SEARCH_TERM_EXPANSION pExpandTerms); - [DispId(1610678285)] - string QueryWhereRestrictions - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + void SetQuerySyntax(SEARCH_QUERY_SYNTAX querySyntax); - [DispId(1610678287)] - string QuerySorting - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: MarshalAs(UnmanagedType.LPWStr)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - get; - } + [return: MarshalAs(UnmanagedType.Interface)] + object GetQuerySyntax(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [return: MarshalAs(UnmanagedType.LPWStr)] - string GenerateSQLFromUserQuery([MarshalAs(UnmanagedType.LPWStr), In] string pszQuery); + void SetQueryContentProperties(string pszContentProperties); + + string GetQueryContentProperties(); + + void SetQuerySelectColumns(string pszColumns); + + string GetQuerySelectColumns(); + + void SetQueryWhereRestrictions(string pszRestrictions); + + string GetQueryWhereRestrictions(); + + void SetQuerySorting(string pszSorting); + + string GetQuerySorting(); + + string GenerateSQLFromUserQuery(string pszQuery); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] void WriteProperties( - [In] int itemID, - [In] uint dwNumberOfColumns, - [In] ref object pColumns, - [In] ref object pValues, - [In] ref object pftGatherModifiedTime); + int itemID, + uint dwNumberOfColumns, + [MarshalAs(UnmanagedType.Interface)] ref object pColumns, + [MarshalAs(UnmanagedType.Interface)] ref object pValues, + [MarshalAs(UnmanagedType.Interface)] ref object pftGatherModifiedTime); - [DispId(1610678291)] - int QueryMaxResults - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [param: In] - set; - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - get; - } + void SetQueryMaxResults(int lMaxResults); + + int GetQueryMaxResults(); +} + +public enum SEARCH_TERM_EXPANSION +{ + SEARCH_TERM_NO_EXPANSION, + SEARCH_TERM_PREFIX_ALL, + SEARCH_TERM_STEM_ALL, +} + +public enum SEARCH_QUERY_SYNTAX +{ + SEARCH_NO_QUERY_SYNTAX, + SEARCH_ADVANCED_QUERY_SYNTAX, + SEARCH_NATURAL_QUERY_SYNTAX, } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/PropVariant.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/PropVariant.cs new file mode 100644 index 0000000000..0a48a42ee5 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/PropVariant.cs @@ -0,0 +1,70 @@ +// 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.CodeAnalysis; +using System.Runtime.InteropServices; +using VARTYPE = System.Runtime.InteropServices.VarEnum; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter +[StructLayout(LayoutKind.Explicit, Pack = 8)] +public struct PropVariant +{ + /// Value type tag. + [FieldOffset(0)] + public ushort vt; + + [FieldOffset(2)] + public ushort wReserved1; + + /// Reserved for future use. + [FieldOffset(4)] + public ushort wReserved2; + + /// Reserved for future use. + [FieldOffset(6)] + public ushort wReserved3; + + /// The decimal value when VT_DECIMAL. + [FieldOffset(0)] + internal decimal _decimal; + + /// The raw data pointer. + [FieldOffset(8)] + internal IntPtr _ptr; + + /// The FILETIME when VT_FILETIME. + [FieldOffset(8)] + internal System.Runtime.InteropServices.ComTypes.FILETIME _ft; + + [FieldOffset(8)] + internal BLOB _blob; + + /// The value when a numeric value less than 8 bytes. + [FieldOffset(8)] + internal ulong _ulong; + + public PropVariant(string value) + { + ArgumentNullException.ThrowIfNull(value); + vt = (ushort)VarEnum.VT_LPWSTR; + _ptr = Marshal.StringToCoTaskMemUni(value); + } + + public VarEnum VarType { get => (VarEnum)vt; set => vt = (ushort)(VARTYPE)value; } +} + +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public struct BLOB +{ + /// The count of bytes + public uint cbSize; + + /// A pointer to the allocated array of bytes. + public IntPtr pBlobData; +} + +#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/PropertyKey.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/PropertyKey.cs new file mode 100644 index 0000000000..e37177d759 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SystemSearch/PropertyKey.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +[StructLayout(LayoutKind.Sequential)] +public struct PropertyKey +{ + public Guid FmtID; + + public uint PID; + + public PropertyKey(Guid fmtid, uint pid) + { + this.FmtID = fmtid; + this.PID = pid; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/Utils/NativeHelpers.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/Utils/NativeHelpers.cs new file mode 100644 index 0000000000..ec7604a7b3 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/Utils/NativeHelpers.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; +using Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer.Utils; + +public sealed partial class NativeHelpers +{ + public const uint SEEMASKINVOKEIDLIST = 12; + + public struct PropertyKeys + { + public static readonly PropertyKey PKEYItemNameDisplay = new() { FmtID = new Guid("B725F130-47EF-101A-A5F1-02608C9EEBAC"), PID = 10 }; + public static readonly PropertyKey PKEYItemUrl = new() { FmtID = new Guid("49691C90-7E17-101A-A91C-08002B2ECDA9"), PID = 9 }; + public static readonly PropertyKey PKEYKindText = new() { FmtID = new Guid("F04BEF95-C585-4197-A2B7-DF46FDC9EE6D"), PID = 100 }; + } + + public static class OleDb + { + public static readonly Guid DbGuidDefault = new("C8B521FB-5CF3-11CE-ADE5-00AA0044773D"); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/Utils/QueryStringBuilder.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/Utils/QueryStringBuilder.cs index f835acafaa..997d364b4d 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/Utils/QueryStringBuilder.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/Utils/QueryStringBuilder.cs @@ -2,12 +2,18 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using ManagedCommon; +using ManagedCsWin32; using Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch; namespace Microsoft.CmdPal.Ext.Indexer.Indexer.Utils; -internal sealed class QueryStringBuilder +internal sealed partial class QueryStringBuilder { private const string Properties = "System.ItemUrl, System.ItemNameDisplay, path, System.Search.EntryID, System.Kind, System.KindText"; private const string SystemIndex = "SystemIndex"; @@ -24,16 +30,36 @@ internal sealed class QueryStringBuilder { if (queryHelper == null) { - var searchManager = new CSearchManager(); - ISearchCatalogManager catalogManager = searchManager.GetCatalog(SystemIndex); - queryHelper = catalogManager.GetQueryHelper(); + ISearchManager searchManager; - queryHelper.QuerySelectColumns = Properties; - queryHelper.QueryContentProperties = "System.FileName"; - queryHelper.QuerySorting = OrderConditions; + try + { + searchManager = ComHelper.CreateComInstance(ref Unsafe.AsRef(in CLSID.SearchManager), CLSCTX.LocalServer); + } + catch (Exception ex) + { + Logger.LogError($"Failed to create searchManager. ex: {ex.Message}"); + throw; + } + + ISearchCatalogManager catalogManager = searchManager.GetCatalog(SystemIndex); + if (catalogManager == null) + { + throw new ArgumentException($"Failed to get catalog manager for {SystemIndex}"); + } + + queryHelper = catalogManager.GetQueryHelper(); + if (queryHelper == null) + { + throw new ArgumentException("Failed to get query helper from catalog manager"); + } + + queryHelper.SetQuerySelectColumns(Properties); + queryHelper.SetQueryContentProperties("System.FileName"); + queryHelper.SetQuerySorting(OrderConditions); } - queryHelper.QueryWhereRestrictions = "AND " + ScopeFileConditions + "AND ReuseWhere(" + whereId.ToString(CultureInfo.InvariantCulture) + ")"; + queryHelper.SetQueryWhereRestrictions("AND " + ScopeFileConditions + "AND ReuseWhere(" + whereId.ToString(CultureInfo.InvariantCulture) + ")"); return queryHelper.GenerateSQLFromUserQuery(searchText); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj index 70e6a10c5a..af8fcff41a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj @@ -1,12 +1,12 @@ - + + Microsoft.CmdPal.Ext.Indexer - $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal false false - + true @@ -15,8 +15,9 @@ runtime; build; native; contentfiles; analyzers + - + @@ -34,6 +35,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -46,4 +50,9 @@ + + + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Native/NativeHelpers.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Native/NativeHelpers.cs deleted file mode 100644 index 9851573843..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Native/NativeHelpers.cs +++ /dev/null @@ -1,25 +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 Windows.Win32.UI.Shell.PropertiesSystem; - -namespace Microsoft.CmdPal.Ext.Indexer.Native; - -internal sealed class NativeHelpers -{ - public const uint SEEMASKINVOKEIDLIST = 12; - - internal static class PropertyKeys - { - public static readonly PROPERTYKEY PKEYItemNameDisplay = new() { fmtid = new System.Guid("B725F130-47EF-101A-A5F1-02608C9EEBAC"), pid = 10 }; - public static readonly PROPERTYKEY PKEYItemUrl = new() { fmtid = new System.Guid("49691C90-7E17-101A-A91C-08002B2ECDA9"), pid = 9 }; - public static readonly PROPERTYKEY PKEYKindText = new() { fmtid = new System.Guid("F04BEF95-C585-4197-A2B7-DF46FDC9EE6D"), pid = 100 }; - } - - internal static class OleDb - { - public static readonly Guid DbGuidDefault = new("C8B521FB-5CF3-11CE-ADE5-00AA0044773D"); - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/NativeMethods.json b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/NativeMethods.json new file mode 100644 index 0000000000..02fff599f2 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/NativeMethods.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "allowMarshaling": false, + "comInterop": { + "preserveSigMethods": [ "*" ] + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/NativeMethods.txt b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/NativeMethods.txt index f0702ba24c..029b6fe6c2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/NativeMethods.txt +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/NativeMethods.txt @@ -1,11 +1,2 @@ DBID -SHOW_WINDOW_CMD -CoCreateInstance -GetErrorInfo -ICommandText -IDBCreateCommand -IDBCreateSession -IDBInitialize -IGetRow -IPropertyStore -ShellExecuteEx \ No newline at end of file +SHOW_WINDOW_CMD \ No newline at end of file diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/ActionsListContextItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/ActionsListContextItem.cs new file mode 100644 index 0000000000..d191a5a08c --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/ActionsListContextItem.cs @@ -0,0 +1,93 @@ +// 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.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CmdPal.Ext.Indexer.Commands; +using Microsoft.CmdPal.Ext.Indexer.Data; +using Microsoft.CmdPal.Ext.Indexer.Properties; +using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.AI.Actions; +using Windows.System; + +namespace Microsoft.CmdPal.Ext.Indexer.Pages; + +internal sealed partial class ActionsListContextItem : CommandContextItem +{ + private readonly string fullPath; + private readonly List actions = []; + private static readonly Lock UpdateMoreCommandsLock = new(); + private static ActionRuntime actionRuntime; + + public ActionsListContextItem(string fullPath) + : base(new NoOpCommand()) + { + Title = Resources.Indexer_Command_Actions; + Icon = Icons.Actions; + RequestedShortcut = KeyChordHelpers.FromModifiers(alt: true, vkey: VirtualKey.A); + this.fullPath = fullPath; + UpdateMoreCommands(); + } + + public bool AnyActions() => actions.Count != 0; + + private void ActionCatalog_Changed(global::Windows.AI.Actions.Hosting.ActionCatalog sender, object args) + { + UpdateMoreCommands(); + } + + private void UpdateMoreCommands() + { + try + { + lock (UpdateMoreCommandsLock) + { + if (actionRuntime == null) + { + actionRuntime = ActionRuntimeFactory.CreateActionRuntime(); + Task.Delay(500).Wait(); + } + + actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed; + actionRuntime.ActionCatalog.Changed += ActionCatalog_Changed; + } + + var extension = System.IO.Path.GetExtension(fullPath).ToLower(CultureInfo.InvariantCulture); + ActionEntity entity = null; + if (extension != null) + { + if (extension == ".jpg" || extension == ".jpeg" || extension == ".png") + { + entity = actionRuntime.EntityFactory.CreatePhotoEntity(fullPath); + } + else if (extension == ".docx" || extension == ".doc" || extension == ".pdf" || extension == ".txt") + { + entity = actionRuntime.EntityFactory.CreateDocumentEntity(fullPath); + } + } + + if (entity == null) + { + entity = actionRuntime.EntityFactory.CreateFileEntity(fullPath); + } + + lock (actions) + { + actions.Clear(); + foreach (var actionInstance in actionRuntime.ActionCatalog.GetActionsForInputs([entity])) + { + actions.Add(new CommandContextItem(new ExecuteActionCommand(actionInstance))); + } + + MoreCommands = [.. actions]; + } + } + catch + { + actionRuntime = null; + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/Icons.cs index a55cbe506e..a07bce2016 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/Icons.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/Icons.cs @@ -12,6 +12,8 @@ internal sealed class Icons internal static IconInfo FileExplorer { get; } = IconHelpers.FromRelativePath("Assets\\FileExplorer.png"); + internal static IconInfo Actions { get; } = IconHelpers.FromRelativePath("Assets\\Actions.png"); + internal static IconInfo OpenFile { get; } = new("\uE8E5"); // OpenFile internal static IconInfo Document { get; } = new("\uE8A5"); // Document diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs index 37a53c1cef..f5d1ba2d61 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs @@ -60,6 +60,15 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties { } } + /// + /// Looks up a localized string similar to Actions. + /// + internal static string Indexer_Command_Actions { + get { + return ResourceManager.GetString("Indexer_Command_Actions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Browse. /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx index 8e4f70bc1a..61d51998b2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx @@ -138,6 +138,9 @@ Open with + + Actions... + Show in folder diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs index d1bdb0e7be..d3bc979419 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs @@ -12,10 +12,12 @@ namespace Microsoft.CmdPal.Ext.Shell; internal sealed partial class FallbackExecuteItem : FallbackCommandItem { private readonly ExecuteItem _executeItem; + private readonly SettingsManager _settings; public FallbackExecuteItem(SettingsManager settings) : base(new ExecuteItem(string.Empty, settings), Resources.shell_command_display_title) { + _settings = settings; _executeItem = (ExecuteItem)this.Command!; Title = string.Empty; _executeItem.Name = string.Empty; @@ -28,5 +30,9 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem _executeItem.Cmd = query; _executeItem.Name = string.IsNullOrEmpty(query) ? string.Empty : Properties.Resources.generic_run_command; Title = query; + MoreCommands = [ + new CommandContextItem(new ExecuteItem(query, _settings, RunAsType.Administrator)), + new CommandContextItem(new ExecuteItem(query, _settings, RunAsType.OtherUser)), + ]; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Commands.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Commands.cs index a981b2ed00..70c1b7f8c6 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Commands.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Commands.cs @@ -110,6 +110,13 @@ internal static class Commands }); } + results.Add(new ListItem(new ExecuteCommandConfirmation(Resources.Microsoft_plugin_sys_RestartShell_name!, confirmCommands, Resources.Microsoft_plugin_sys_RestartShell_confirmation!, static () => OpenInShellHelper.OpenInShell("cmd", "/C tskill explorer && start explorer", runWithHiddenWindow: true))) + { + Title = Resources.Microsoft_plugin_sys_RestartShell!, + Subtitle = Resources.Microsoft_plugin_sys_RestartShell_description!, + Icon = Icons.RestartShellIcon, + }); + // UEFI command/result. It is only available on systems booted in UEFI mode. if (isUefi) { diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Icons.cs index be64ddb181..313ba773f8 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Icons.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Icons.cs @@ -20,6 +20,8 @@ public static partial class Icons public static IconInfo RestartIcon { get; } = new IconInfo("\uE777"); + public static IconInfo RestartShellIcon { get; } = new IconInfo("\uEC50"); + public static IconInfo ShutdownIcon { get; } = new IconInfo("\uE7E8"); public static IconInfo SleepIcon { get; } = new IconInfo("\uE708"); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Native.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Native.cs deleted file mode 100644 index 3daf2a4be4..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/Native.cs +++ /dev/null @@ -1,144 +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.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.System.Helpers; - -[SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "We want plugins to share this NativeMethods class, instead of each one creating its own.")] -public sealed class Native -{ - public enum HRESULT : uint - { - /// - /// Operation successful. - /// - S_OK = 0x00000000, - - /// - /// Operation successful. (negative condition/no operation) - /// - S_FALSE = 0x00000001, - - /// - /// Not implemented. - /// - E_NOTIMPL = 0x80004001, - - /// - /// No such interface supported. - /// - E_NOINTERFACE = 0x80004002, - - /// - /// Pointer that is not valid. - /// - E_POINTER = 0x80004003, - - /// - /// Operation aborted. - /// - E_ABORT = 0x80004004, - - /// - /// Unspecified failure. - /// - E_FAIL = 0x80004005, - - /// - /// Unexpected failure. - /// - E_UNEXPECTED = 0x8000FFFF, - - /// - /// General access denied error. - /// - E_ACCESSDENIED = 0x80070005, - - /// - /// Handle that is not valid. - /// - E_HANDLE = 0x80070006, - - /// - /// Failed to allocate necessary memory. - /// - E_OUTOFMEMORY = 0x8007000E, - - /// - /// One or more arguments are not valid. - /// - E_INVALIDARG = 0x80070057, - - /// - /// The operation was canceled by the user. (Error source 7 means Win32.) - /// - /// - /// - E_CANCELLED = 0x800704C7, - } - - public static class ShellItemTypeConstants - { - /// - /// Guid for type IShellItem. - /// - public static readonly Guid ShellItemGuid = new("43826d1e-e718-42ee-bc55-a1e261c37bfe"); - - /// - /// Guid for type IShellItem2. - /// - public static readonly Guid ShellItem2Guid = new("7E9FB0D3-919F-4307-AB2E-9B1860310C93"); - } - - /// - /// The following are ShellItem DisplayName types. - /// - [Flags] - public enum SIGDN : uint - { - NORMALDISPLAY = 0, - PARENTRELATIVEPARSING = 0x80018001, - PARENTRELATIVEFORADDRESSBAR = 0x8001c001, - DESKTOPABSOLUTEPARSING = 0x80028000, - PARENTRELATIVEEDITING = 0x80031001, - DESKTOPABSOLUTEEDITING = 0x8004c000, - FILESYSPATH = 0x80058000, - URL = 0x80068000, - } - - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] - public interface IShellItem - { - void BindToHandler( - nint pbc, - [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out nint ppv); - - void GetParent(out IShellItem ppsi); - - void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); - - void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); - - void Compare(IShellItem psi, uint hint, out int piOrder); - } - - /// - /// see all STGM values - /// - [Flags] - public enum STGM : long - { - READ = 0x00000000L, - WRITE = 0x00000001L, - READWRITE = 0x00000002L, - CREATE = 0x00001000L, - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.Designer.cs index 1cfa1a6353..f2ec8c1218 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.Designer.cs @@ -645,6 +645,42 @@ namespace Microsoft.CmdPal.Ext.System { } } + /// + /// Looks up a localized string similar to Restart Windows Explorer. + /// + public static string Microsoft_plugin_sys_RestartShell { + get { + return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You are about to restart Windows Explorer, are you sure?. + /// + public static string Microsoft_plugin_sys_RestartShell_confirmation { + get { + return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_confirmation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to End and restart the Windows Explorer shell process. + /// + public static string Microsoft_plugin_sys_RestartShell_description { + get { + return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Restart. + /// + public static string Microsoft_plugin_sys_RestartShell_name { + get { + return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_name", resourceCulture); + } + } + /// /// Looks up a localized string similar to ip; mac; address. /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.resx index 23d27a86a2..bf7ab04da0 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Properties/Resources.resx @@ -417,4 +417,16 @@ Open System Command + + Restart Windows Explorer + + + End and restart the Windows Explorer shell process + + + Restart + + + You are about to restart Windows Explorer, are you sure? + \ No newline at end of file diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/FallbackTimeDateItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/FallbackTimeDateItem.cs index 267ab43989..4ecc469f82 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/FallbackTimeDateItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/FallbackTimeDateItem.cs @@ -68,6 +68,7 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem Title = result.Title; Subtitle = result.Subtitle; Icon = result.Icon; + Command = result.Command; } else { diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Microsoft.CmdPal.Ext.TimeDate.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Microsoft.CmdPal.Ext.TimeDate.csproj index 038629d266..44ebab07ae 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Microsoft.CmdPal.Ext.TimeDate.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Microsoft.CmdPal.Ext.TimeDate.csproj @@ -4,6 +4,7 @@ Microsoft.CmdPal.Ext.TimeDate + $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal false false @@ -32,8 +33,10 @@ - + + + PreserveNewest diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs index 024a559087..8dce2128ce 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.TimeDate/Pages/TimeDateExtensionPage.cs @@ -18,8 +18,6 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage private IList _results = new List(); - private bool initialized; - private SettingsManager _settingsManager; public TimeDateExtensionPage(SettingsManager settingsManager) @@ -35,12 +33,9 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage public override IListItem[] GetItems() { - if (!initialized) - { - DoExecuteSearch(string.Empty); - } + DoExecuteSearch(string.Empty); - lock (_resultsLock) + lock (_resultsLock) { ListItem[] results = _results.ToArray(); return results; @@ -49,11 +44,6 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage public override void UpdateSearchText(string oldSearch, string newSearch) { - if (newSearch == oldSearch) - { - return; - } - DoExecuteSearch(newSearch); } @@ -84,7 +74,6 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage { lock (_resultsLock) { - initialized = true; this._results = result; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs index b3df3fc492..87b87c7ff5 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/DefaultBrowserInfo.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using ManagedCommon; @@ -78,8 +79,11 @@ public static class DefaultBrowserInfo try { var progId = GetRegistryValue( - @"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice", - "ProgId"); + @"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoiceLatest\ProgId", + "ProgId") + ?? GetRegistryValue( + @"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice", + "ProgId"); var appName = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\Application", "ApplicationName") ?? GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}", "FriendlyTypeName"); @@ -188,17 +192,20 @@ public static class DefaultBrowserInfo { var buffer = stackalloc char[128]; var capacity = 128; - void* reserved = null; + var firstChar = str[0]; + var strPtr = &firstChar; // S_OK == 0 - if (global::Windows.Win32.PInvoke.SHLoadIndirectString( - str, + fixed (char* pszSourceLocal = str) + { + if (global::Windows.Win32.PInvoke.SHLoadIndirectString( + pszSourceLocal, buffer, (uint)capacity, - ref reserved) - == 0) - { - return new string(buffer); + default) == 0) + { + return new string(buffer); + } } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Microsoft.CmdPal.Ext.WinGet.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Microsoft.CmdPal.Ext.WinGet.csproj index 7c5c6d2dd3..2f9da761d2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Microsoft.CmdPal.Ext.WinGet.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Microsoft.CmdPal.Ext.WinGet.csproj @@ -32,6 +32,11 @@ + + + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/NativeMethods.json b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/NativeMethods.json new file mode 100644 index 0000000000..02fff599f2 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/NativeMethods.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "allowMarshaling": false, + "comInterop": { + "preserveSigMethods": [ "*" ] + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs index 5c556ec3cb..8156095d85 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageListItem.cs @@ -156,7 +156,22 @@ public partial class InstallPackageListItem : ListItem private async void UpdatedInstalledStatus() { - var status = await _package.CheckInstalledStatusAsync(); + try + { + var status = await _package.CheckInstalledStatusAsync(); + } + catch (OperationCanceledException) + { + // DO NOTHING HERE + return; + } + catch (Exception ex) + { + // Handle other exceptions + ExtensionHost.LogMessage($"[WinGet] UpdatedInstalledStatus throw exception: {ex.Message}"); + return; + } + var isInstalled = _package.InstalledVersion != null; var installedState = isInstalled ? diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs index a645ff9e8e..000e2a9bba 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs @@ -57,7 +57,7 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable { // emptySearchForTag === // we don't have results yet, we haven't typed anything, and we're searching for a tag - bool emptySearchForTag = _results == null && + var emptySearchForTag = _results == null && string.IsNullOrEmpty(SearchText) && HasTag; @@ -116,8 +116,22 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable IsLoading = true; - // Save the latest search task - _currentSearchTask = DoSearchAsync(newSearch, cancellationToken); + try + { + // Save the latest search task + _currentSearchTask = DoSearchAsync(newSearch, cancellationToken); + } + catch (OperationCanceledException) + { + // DO NOTHING HERE + return; + } + catch (Exception ex) + { + // Handle other exceptions + ExtensionHost.LogMessage($"[WinGet] DoUpdateSearchText throw exception: {ex.Message}"); + return; + } // Await the task to ensure only the latest one gets processed _ = ProcessSearchResultsAsync(_currentSearchTask, newSearch); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WindowsPackageManager.Interop/WindowsPackageManagerStandardFactory.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WindowsPackageManager.Interop/WindowsPackageManagerStandardFactory.cs index d8b6064a86..677f688ac6 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WindowsPackageManager.Interop/WindowsPackageManagerStandardFactory.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/WindowsPackageManager.Interop/WindowsPackageManagerStandardFactory.cs @@ -20,20 +20,25 @@ public class WindowsPackageManagerStandardFactory : WindowsPackageManagerFactory protected override T CreateInstance(Guid clsid, Guid iid) { var pUnknown = IntPtr.Zero; - try + unsafe { - var hr = PInvoke.CoCreateInstance(clsid, null, CLSCTX.CLSCTX_ALL, iid, out var result); - Marshal.ThrowExceptionForHR(hr); - pUnknown = Marshal.GetIUnknownForObject(result); - return MarshalGeneric.FromAbi(pUnknown); - } - finally - { - // CoCreateInstance and FromAbi both AddRef on the native object. - // Release once to prevent memory leak. - if (pUnknown != IntPtr.Zero) + try { - Marshal.Release(pUnknown); + var hr = PInvoke.CoCreateInstance(clsid, null, CLSCTX.CLSCTX_ALL, iid, out var result); + Marshal.ThrowExceptionForHR(hr); + + pUnknown = new IntPtr(result); + + return MarshalGeneric.FromAbi(pUnknown); + } + finally + { + // CoCreateInstance and FromAbi both AddRef on the native object. + // Release once to prevent memory leak. + if (pUnknown != IntPtr.Zero) + { + Marshal.Release(pUnknown); + } } } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/app.manifest b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/app.manifest index bcafb9bc5b..16492c3b79 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/app.manifest +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/app.manifest @@ -13,6 +13,7 @@ + true/PM PerMonitorV2 diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ContextMenuHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ContextMenuHelper.cs index cbadadc699..a2064b0369 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ContextMenuHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/ContextMenuHelper.cs @@ -36,6 +36,7 @@ internal sealed class ContextMenuHelper contextMenu.Add(new CommandContextItem(new KillProcessCommand(windowData)) { RequestedShortcut = KeyChordHelpers.FromModifiers(true, false, false, false, (int)VirtualKey.Delete, 0), + IsCritical = true, }); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/IVirtualDesktopManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/IVirtualDesktopManager.cs index 070e24cc26..75170a56cc 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/IVirtualDesktopManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/IVirtualDesktopManager.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers; @@ -11,18 +12,16 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers; /// Interface for accessing Virtual Desktop Manager. /// Code used from /// -[ComImport] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +[GeneratedComInterface] [Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")] -[System.Security.SuppressUnmanagedCodeSecurity] -internal interface IVirtualDesktopManager +public partial interface IVirtualDesktopManager { [PreserveSig] - int IsWindowOnCurrentVirtualDesktop([In] IntPtr hTopLevelWindow, [Out] out int onCurrentDesktop); + int IsWindowOnCurrentVirtualDesktop(IntPtr hTopLevelWindow, out int onCurrentDesktop); [PreserveSig] - int GetWindowDesktopId([In] IntPtr hTopLevelWindow, [Out] out Guid desktop); + int GetWindowDesktopId(IntPtr hTopLevelWindow, out Guid desktop); [PreserveSig] - int MoveWindowToDesktop([In] IntPtr hTopLevelWindow, [MarshalAs(UnmanagedType.LPStruct)][In] Guid desktop); + int MoveWindowToDesktop(IntPtr hTopLevelWindow, ref Guid desktop); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs index e60cb262fe..99d29a9949 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/NativeMethods.cs @@ -13,7 +13,7 @@ using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessage namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers; [SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "We want plugins to share this NativeMethods class, instead of each one creating its own.")] -public static class NativeMethods +public static partial class NativeMethods { [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int EnumWindows(EnumWindowsProc callPtr, IntPtr lParam); @@ -98,32 +98,6 @@ public static class NativeMethods [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetFirmwareType(ref FirmwareType FirmwareType); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool ExitWindowsEx(uint uFlags, uint dwReason); - - [DllImport("user32")] - public static extern void LockWorkStation(); - - [DllImport("Powrprof.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent); - - [DllImport("Shell32.dll", CharSet = CharSet.Unicode)] - public static extern uint SHEmptyRecycleBin(IntPtr hWnd, uint dwFlags); - - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - public static extern HRESULT SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); - - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - public static extern HRESULT SHCreateStreamOnFileEx(string fileName, STGM grfMode, uint attributes, bool create, System.Runtime.InteropServices.ComTypes.IStream reserved, out System.Runtime.InteropServices.ComTypes.IStream stream); - - [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); - - [DllImport("rpcrt4.dll")] - public static extern int UuidCreateSequential(out GUIDDATA Uuid); } [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "These are the names used by win32.")] @@ -161,19 +135,6 @@ public static class Win32Constants public const int RPC_S_UUID_LOCAL_ONLY = 0x720; } -public static class ShellItemTypeConstants -{ - /// - /// Guid for type IShellItem. - /// - public static readonly Guid ShellItemGuid = new("43826d1e-e718-42ee-bc55-a1e261c37bfe"); - - /// - /// Guid for type IShellItem2. - /// - public static readonly Guid ShellItem2Guid = new("7E9FB0D3-919F-4307-AB2E-9B1860310C93"); -} - public enum HRESULT : uint { /// @@ -1124,26 +1085,6 @@ public enum ExtendedWindowStyles : uint WS_EX_NOACTIVATE = 0x8000000, } -[ComImport] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] -public interface IShellItem -{ - void BindToHandler( - IntPtr pbc, - [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out IntPtr ppv); - - void GetParent(out IShellItem ppsi); - - void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); - - void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); - - void Compare(IShellItem psi, uint hint, out int piOrder); -} - /// /// The following are ShellItem DisplayName types. /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs index 023bd8ed33..1cdc9f82b9 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/VirtualDesktopHelper.cs @@ -5,9 +5,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using System.Text; - +using ManagedCsWin32; using Microsoft.CmdPal.Ext.WindowWalker.Properties; using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.Win32; @@ -61,9 +63,11 @@ public class VirtualDesktopHelper /// Setting to configure if the list of available desktops should update automatically or only when calling . Per default this is set to manual update (false) to have less registry queries. public VirtualDesktopHelper(bool desktopListUpdate = false) { + var cw = new StrategyBasedComWrappers(); + try { - _virtualDesktopManager = (IVirtualDesktopManager)new CVirtualDesktopManager(); + _virtualDesktopManager = ComHelper.CreateComInstance(ref Unsafe.AsRef(in CLSID.VirtualDesktopManager), CLSCTX.InProcServer); } catch (COMException ex) { @@ -409,7 +413,7 @@ public class VirtualDesktopHelper /// Handle of the top level window. /// Guid of the target desktop. /// on success and on failure. - public bool MoveWindowToDesktop(IntPtr hWindow, in Guid desktopId) + public bool MoveWindowToDesktop(IntPtr hWindow, ref Guid desktopId) { if (_virtualDesktopManager == null) { @@ -417,7 +421,7 @@ public class VirtualDesktopHelper return false; } - var hr = _virtualDesktopManager.MoveWindowToDesktop(hWindow, desktopId); + var hr = _virtualDesktopManager.MoveWindowToDesktop(hWindow, ref desktopId); if (hr != (int)HRESULT.S_OK) { ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.MoveWindowToDesktop() failed: An exception was thrown when moving the window ({hWindow}) to another desktop ({desktopId})." }); @@ -455,7 +459,7 @@ public class VirtualDesktopHelper } Guid newDesktop = _availableDesktops[windowDesktopNumber - 1]; - return MoveWindowToDesktop(hWindow, newDesktop); + return MoveWindowToDesktop(hWindow, ref newDesktop); } /// @@ -486,7 +490,7 @@ public class VirtualDesktopHelper } Guid newDesktop = _availableDesktops[windowDesktopNumber + 1]; - return MoveWindowToDesktop(hWindow, newDesktop); + return MoveWindowToDesktop(hWindow, ref newDesktop); } /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj index f53237e632..4da6458258 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj @@ -15,6 +15,7 @@ + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs index 994db10445..3f5a6c6217 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs @@ -31,57 +31,81 @@ internal sealed partial class WindowsSettingsListPage : DynamicListPage } var filteredList = _windowsSettings.Settings - .Where(Predicate) - .OrderBy(found => found.Name); + .Select(SearchScoringPredicate) + .Where(scoredSetting => scoredSetting.Score > 0) + .OrderByDescending(scoredSetting => scoredSetting.Score) + .Select(scoredSetting => scoredSetting.Setting); var newList = ResultHelper.GetResultList(filteredList, query); return newList; - bool Predicate(WindowsSetting found) + // Rank settings by how they matched the search query. Order is: + // 1. Exact Name (10 points) + // 2. Name Starts With (8 points) + // 3. Name (5 points) + // 4. Area (4 points) + // 5. AltName (2 points) + // 6. Settings path (1 point) + (WindowsSetting Setting, int Score) SearchScoringPredicate(WindowsSetting setting) { if (string.IsNullOrWhiteSpace(query)) { // If no search string is entered skip query comparison. - return true; + return (setting, 0); } - if (found.Name.Contains(query, StringComparison.CurrentCultureIgnoreCase)) + if (string.Equals(setting.Name, query, StringComparison.OrdinalIgnoreCase)) { - return true; + return (setting, 10); } - if (!(found.Areas is null)) + if (setting.Name.StartsWith(query, StringComparison.CurrentCultureIgnoreCase)) { - foreach (var area in found.Areas) + return (setting, 8); + } + + if (setting.Name.Contains(query, StringComparison.CurrentCultureIgnoreCase)) + { + return (setting, 5); + } + + if (!(setting.Areas is null)) + { + foreach (var area in setting.Areas) { // Search for areas on normal queries. if (area.Contains(query, StringComparison.CurrentCultureIgnoreCase)) { - return true; + return (setting, 4); } // Search for Area only on queries with action char. if (area.Contains(query.Replace(":", string.Empty), StringComparison.CurrentCultureIgnoreCase) && query.EndsWith(":", StringComparison.CurrentCultureIgnoreCase)) { - return true; + return (setting, 4); } } } - if (!(found.AltNames is null)) + if (!(setting.AltNames is null)) { - foreach (var altName in found.AltNames) + foreach (var altName in setting.AltNames) { if (altName.Contains(query, StringComparison.CurrentCultureIgnoreCase)) { - return true; + return (setting, 2); } } } // Search by key char '>' for app name and settings path - return query.Contains('>') ? ResultHelper.FilterBySettingsPath(found, query) : false; + if (query.Contains('>') && ResultHelper.FilterBySettingsPath(setting, query)) + { + return (setting, 1); + } + + return (setting, 0); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs index 39155115ca..1d1fcac873 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs @@ -3,18 +3,14 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Resources; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using ManagedCommon; +using ManagedCsWin32; using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers; using Microsoft.CmdPal.Ext.WindowsTerminal.Properties; -using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; -using Windows.UI; namespace Microsoft.CmdPal.Ext.WindowsTerminal.Commands; @@ -68,7 +64,18 @@ internal sealed partial class LaunchProfileAsAdminCommand : InvokableCommand private void Launch(string id, string profile) { - var appManager = new ApplicationActivationManager(); + IApplicationActivationManager appManager; + + try + { + appManager = ComHelper.CreateComInstance(ref Unsafe.AsRef(in CLSID.ApplicationActivationManager), CLSCTX.InProcServer); + } + catch (Exception e) + { + Logger.LogError($"Failed to create IApplicationActivationManager instance. ex: {e.Message}"); + throw; + } + const ActivateOptions noFlags = ActivateOptions.None; var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab, _openQuake); try diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs index a879dc2410..9951ad3d68 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs @@ -3,18 +3,14 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Resources; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using ManagedCommon; +using ManagedCsWin32; using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers; using Microsoft.CmdPal.Ext.WindowsTerminal.Properties; -using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; -using Windows.UI; namespace Microsoft.CmdPal.Ext.WindowsTerminal.Commands; @@ -38,7 +34,18 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand private void Launch(string id, string profile) { - var appManager = new ApplicationActivationManager(); + IApplicationActivationManager appManager; + + try + { + appManager = ComHelper.CreateComInstance(ref Unsafe.AsRef(in CLSID.ApplicationActivationManager), CLSCTX.InProcServer); + } + catch (Exception e) + { + Logger.LogError($"Failed to create IApplicationActivationManager instance. ex: {e.Message}"); + throw; + } + const ActivateOptions noFlags = ActivateOptions.None; var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab, _openQuake); try diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/ApplicationActivationManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/ApplicationActivationManager.cs deleted file mode 100644 index a074d9c86d..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/ApplicationActivationManager.cs +++ /dev/null @@ -1,24 +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.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers; - -// Application Activation Manager Class -[ComImport] -[Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")] -public class ApplicationActivationManager : IApplicationActivationManager -{ - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/] - public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/IApplicationActivationManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/IApplicationActivationManager.cs index e332eee6fd..c023323d7f 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/IApplicationActivationManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/IApplicationActivationManager.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers; @@ -18,14 +19,13 @@ public enum ActivateOptions } // ApplicationActivationManager -[ComImport] +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] [Guid("2e941141-7f97-4756-ba1d-9decde894a3d")] -[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] -public interface IApplicationActivationManager +public partial interface IApplicationActivationManager { - IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); + void ActivateApplication(string appUserModelId, string arguments, ActivateOptions options, out uint processId); - IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); + void ActivateForFile(string appUserModelId, IntPtr /*IShellItemArray* */ itemArray, string verb, out uint processId); - IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); + void ActivateForProtocol(string appUserModelId, IntPtr /* IShellItemArray* */itemArray, out uint processId); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Microsoft.CmdPal.Ext.WindowsTerminal.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Microsoft.CmdPal.Ext.WindowsTerminal.csproj index e3ba9e6d46..bbd0a9530d 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Microsoft.CmdPal.Ext.WindowsTerminal.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Microsoft.CmdPal.Ext.WindowsTerminal.csproj @@ -17,6 +17,7 @@ + diff --git a/src/modules/cmdpal/ext/ProcessMonitorExtension/app.manifest b/src/modules/cmdpal/ext/ProcessMonitorExtension/app.manifest index a620209321..cf7490b1cb 100644 --- a/src/modules/cmdpal/ext/ProcessMonitorExtension/app.manifest +++ b/src/modules/cmdpal/ext/ProcessMonitorExtension/app.manifest @@ -13,6 +13,7 @@ + true/PM PerMonitorV2 diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs index 9495a7f6bf..018c11720c 100644 --- a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs +++ b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleListPageWithDetails.cs @@ -5,6 +5,7 @@ using System; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; +using Microsoft.UI.Xaml; namespace SamplePagesExtension; @@ -129,6 +130,25 @@ internal sealed partial class SampleListPageWithDetails : ListPage ], }, }, + new DetailsElement() + { + Key = "Commands", + Data = new DetailsCommands() + { + Commands = [ + new ToastCommand("Hey! You clicked it!", MessageState.Success) + { + Name = "Do something amazing", + Icon = new("\uE945"), + }, + new ToastCommand("I warned you!", MessageState.Error) + { + Name = "Don't click me", + Icon = new("\uEA39"), + }, + ], + }, + }, ], }, } diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/app.manifest b/src/modules/cmdpal/ext/SamplePagesExtension/app.manifest index 6a7c7d68a7..13894ddf39 100644 --- a/src/modules/cmdpal/ext/SamplePagesExtension/app.manifest +++ b/src/modules/cmdpal/ext/SamplePagesExtension/app.manifest @@ -13,6 +13,7 @@ + true/PM PerMonitorV2 diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/DetailsCommand.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/DetailsCommands.cs similarity index 70% rename from src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/DetailsCommand.cs rename to src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/DetailsCommands.cs index 0d08458ebf..32b11faad4 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/DetailsCommand.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/DetailsCommands.cs @@ -4,7 +4,7 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit; -public partial class DetailsCommand : IDetailsCommand +public partial class DetailsCommands : IDetailsCommands { - public ICommand? Command { get; set; } + public ICommand[]? Commands { get; set; } } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs index 6d92cdc146..6a0dde88cc 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs @@ -16,6 +16,6 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit; [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "Dictionary")] [JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)] -internal partial class JsonSerializationContext : JsonSerializerContext +internal sealed partial class JsonSerializationContext : JsonSerializerContext { } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchOption.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchOption.cs index 5f56c5a5b0..9f740e4ade 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchOption.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchOption.cs @@ -5,8 +5,6 @@ using System; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Plugin.Program.UnitTests")] - namespace Microsoft.CommandPalette.Extensions.Toolkit; public partial class MatchOption diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchResult.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchResult.cs index ad61733f9e..3848065f25 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchResult.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/MatchResult.cs @@ -4,8 +4,6 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Plugin.Program.UnitTests")] - namespace Microsoft.CommandPalette.Extensions.Toolkit; public partial class MatchResult diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj index 95dc53b444..4ceae15953 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj @@ -15,6 +15,12 @@ None + + true + true + $(MSBuildThisFileDirectory)..\..\..\..\..\.pipelines\272MSSharedLibSN2048.snk + + Microsoft.CommandPalette.Extensions $(OutDir) @@ -51,6 +57,6 @@ True - IL2081 + IL2081;$(WarningsNotAsErrors) diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs index cf7c5c2073..e06dbfcb22 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs @@ -123,7 +123,7 @@ public class ThumbnailHelper private static nint GetLargestIcon(string path) { var shinfo = default(NativeMethods.SHFILEINFO); - NativeMethods.SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SYSICONINDEX); + NativeMethods.SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX); var hIcon = IntPtr.Zero; var iID_IImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950"); diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Utilities.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Utilities.cs index ca88c11c0f..50e56bdefb 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Utilities.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Utilities.cs @@ -37,7 +37,7 @@ public class Utilities var FOLDERID_LocalAppData = new Guid("F1B32785-6FBA-4FCF-9D55-7B8E7F157091"); var hr = PInvoke.SHGetKnownFolderPath( FOLDERID_LocalAppData, - (uint)KNOWN_FOLDER_FLAG.KF_FLAG_FORCE_APP_DATA_REDIRECTION, + KNOWN_FOLDER_FLAG.KF_FLAG_FORCE_APP_DATA_REDIRECTION, null, out var localAppDataFolder); diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl index df4b442cdf..3105b0647d 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl @@ -182,8 +182,8 @@ namespace Microsoft.CommandPalette.Extensions String Text { get; }; } [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)] - interface IDetailsCommand requires IDetailsData { - ICommand Command { get; }; + interface IDetailsCommands requires IDetailsData { + ICommand[] Commands { get; }; } [uuid("58070392-02bb-4e89-9beb-47ceb8c3d741")] [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)] diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj index 0f87df5625..e620524c77 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj @@ -1,10 +1,10 @@ - + ..\..\..\..\..\ - $(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250401001 + $(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250513003 $(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5 - $(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428 + $(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188 $(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2903.40 @@ -24,7 +24,7 @@ Windows Store 10.0 10.0.19041.0 - 10.0.22621.0 + 10.0.26100.0 @@ -180,12 +180,4 @@ - - - - - - {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} - - diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config index 608661db25..fc2bc5a5df 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config @@ -2,6 +2,6 @@ - - + + \ No newline at end of file diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/version.rc b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/version.rc deleted file mode 100644 index 67e50b2cbb..0000000000 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/version.rc +++ /dev/null @@ -1,34 +0,0 @@ -#include "winres.h" - -#include "../../../../common/version/version.h" - -VS_VERSION_INFO VERSIONINFO - FILEVERSION FILE_VERSION - PRODUCTVERSION PRODUCT_VERSION - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x0L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", "Microsoft Corporation" - VALUE "FileDescription", "Microsoft.CommandPalette.Extensions.Toolkit" - VALUE "FileVersion", FILE_VERSION_STRING - VALUE "ProductName", "Microsoft.CommandPalette.Extensions.Toolkit" - VALUE "ProductVersion", PRODUCT_VERSION_STRING - VALUE "LegalCopyright", "Copyright (c) Microsoft Corporation" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END diff --git a/src/modules/cmdpal/extensionsdk/README.md b/src/modules/cmdpal/extensionsdk/README.md index 2b2732cc14..c2e9e1fe59 100644 --- a/src/modules/cmdpal/extensionsdk/README.md +++ b/src/modules/cmdpal/extensionsdk/README.md @@ -12,4 +12,4 @@ To view the full docs, you can head over to [our docs site](https://go.microsoft There are samples of just about everything you can do in [the samples project]. Head over there to see basic usage of the APIs. -[the samples project]: https://github.com/microsoft/PowerToys/tree/main/src/modules/cmdpal/Exts/SamplePagesExtension +[the samples project]: https://github.com/microsoft/PowerToys/tree/main/src/modules/cmdpal/ext/SamplePagesExtension diff --git a/src/modules/cmdpal/extensionsdk/nuget/BuildSDKHelper.ps1 b/src/modules/cmdpal/extensionsdk/nuget/BuildSDKHelper.ps1 index 179c4bfa3f..8270e7bb4a 100644 --- a/src/modules/cmdpal/extensionsdk/nuget/BuildSDKHelper.ps1 +++ b/src/modules/cmdpal/extensionsdk/nuget/BuildSDKHelper.ps1 @@ -1,11 +1,19 @@ Param( [string]$Configuration = "release", - [string]$VersionOfSDK = "0.0.0", + [string]$VersionOfSDK = "", [string]$BuildStep = "all", [switch]$IsAzurePipelineBuild = $false, [switch]$Help = $false ) +If ([String]::IsNullOrEmpty($VersionOfSDK)) { + $VersionOfSDK = $Env:XES_PACKAGEVERSIONNUMBER +} + +If ([String]::IsNullOrEmpty($VersionOfSDK)) { + $VersionOfSDK = "0.0.0" +} + $StartTime = Get-Date if ($Help) { @@ -61,6 +69,10 @@ if (($BuildStep -ieq "all") -Or ($BuildStep -ieq "build")) { ("/p:VersionNumber="+$VersionOfSDK) ) + if ($IsAzurePipelineBuild) { + $msbuildArgs += "/p:CIBuild=true" + } + & $msbuildPath $msbuildArgs } } diff --git a/src/modules/colorPicker/ColorPickerUI/App.manifest b/src/modules/colorPicker/ColorPickerUI/App.manifest index 94b792d3fe..c99f120d43 100644 --- a/src/modules/colorPicker/ColorPickerUI/App.manifest +++ b/src/modules/colorPicker/ColorPickerUI/App.manifest @@ -52,10 +52,8 @@ - true - - PerMonitor - + true/PM + PerMonitorV2 diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/AppStateHandler.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/AppStateHandler.cs index b3350702c7..705324cb93 100644 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/AppStateHandler.cs +++ b/src/modules/colorPicker/ColorPickerUI/Helpers/AppStateHandler.cs @@ -107,21 +107,14 @@ namespace ColorPicker.Helpers } } - public void OnColorPickerMouseDown() + public void OpenColorEditor() { - if (_userSettings.ActivationAction.Value == ColorPickerActivationAction.OpenColorPickerAndThenEditor || _userSettings.ActivationAction.Value == ColorPickerActivationAction.OpenEditor) + lock (_colorPickerVisibilityLock) { - lock (_colorPickerVisibilityLock) - { - HideColorPicker(); - } + HideColorPicker(); + } - ShowColorPickerEditor(); - } - else - { - EndUserSession(); - } + ShowColorPickerEditor(); } public static void SetTopMost() diff --git a/src/modules/colorPicker/ColorPickerUI/Mouse/IMouseInfoProvider.cs b/src/modules/colorPicker/ColorPickerUI/Mouse/IMouseInfoProvider.cs index 3361b95cb4..51bb35ac51 100644 --- a/src/modules/colorPicker/ColorPickerUI/Mouse/IMouseInfoProvider.cs +++ b/src/modules/colorPicker/ColorPickerUI/Mouse/IMouseInfoProvider.cs @@ -16,10 +16,12 @@ namespace ColorPicker.Mouse // position and bool indicating zoom in or zoom out event EventHandler> OnMouseWheel; - event MouseUpEventHandler OnMouseDown; + event PrimaryMouseDownEventHandler OnPrimaryMouseDown; event SecondaryMouseUpEventHandler OnSecondaryMouseUp; + event MiddleMouseDownEventHandler OnMiddleMouseDown; + System.Windows.Point CurrentPosition { get; } Color CurrentColor { get; } diff --git a/src/modules/colorPicker/ColorPickerUI/Mouse/MouseHook.cs b/src/modules/colorPicker/ColorPickerUI/Mouse/MouseHook.cs index 8476398c99..72fffd1445 100644 --- a/src/modules/colorPicker/ColorPickerUI/Mouse/MouseHook.cs +++ b/src/modules/colorPicker/ColorPickerUI/Mouse/MouseHook.cs @@ -7,17 +7,18 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Input; -using ColorPicker.Helpers; using ManagedCommon; using static ColorPicker.NativeMethods; namespace ColorPicker.Mouse { - public delegate void MouseUpEventHandler(object sender, System.Drawing.Point p); + public delegate void PrimaryMouseDownEventHandler(object sender, IntPtr wParam); public delegate void SecondaryMouseUpEventHandler(object sender, IntPtr wParam); + public delegate void MiddleMouseDownEventHandler(object sender, IntPtr wParam); + internal class MouseHook { [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop object")] @@ -30,23 +31,25 @@ namespace ColorPicker.Mouse private const int WM_RBUTTONUP = 0x0205; [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop object")] private const int WM_RBUTTONDOWN = 0x0204; + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop object")] + private const int WM_MBUTTONDOWN = 0x0207; private IntPtr _mouseHookHandle; private HookProc _mouseDelegate; - private event MouseUpEventHandler MouseDown; + private event PrimaryMouseDownEventHandler PrimaryMouseDown; - public event MouseUpEventHandler OnMouseDown + public event PrimaryMouseDownEventHandler OnPrimaryMouseDown { add { Subscribe(); - MouseDown += value; + PrimaryMouseDown += value; } remove { - MouseDown -= value; + PrimaryMouseDown -= value; Unsubscribe(); } } @@ -68,6 +71,23 @@ namespace ColorPicker.Mouse } } + private event MiddleMouseDownEventHandler MiddleMouseDown; + + public event MiddleMouseDownEventHandler OnMiddleMouseDown + { + add + { + Subscribe(); + MiddleMouseDown += value; + } + + remove + { + MiddleMouseDown -= value; + Unsubscribe(); + } + } + private event MouseWheelEventHandler MouseWheel; public event MouseWheelEventHandler OnMouseWheel @@ -126,9 +146,9 @@ namespace ColorPicker.Mouse MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); if (wParam.ToInt32() == WM_LBUTTONDOWN) { - if (MouseDown != null) + if (PrimaryMouseDown != null) { - MouseDown.Invoke(null, new System.Drawing.Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y)); + PrimaryMouseDown.Invoke(null, wParam); } return new IntPtr(-1); @@ -150,6 +170,16 @@ namespace ColorPicker.Mouse return new IntPtr(-1); } + if (wParam.ToInt32() == WM_MBUTTONDOWN) + { + if (MiddleMouseDown != null) + { + MiddleMouseDown.Invoke(null, wParam); + } + + return new IntPtr(-1); + } + if (wParam.ToInt32() == WM_MOUSEWHEEL) { if (MouseWheel != null) diff --git a/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs b/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs index c28dcc1ae6..4d6596bc3f 100644 --- a/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs +++ b/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs @@ -56,10 +56,12 @@ namespace ColorPicker.Mouse public event EventHandler> OnMouseWheel; - public event MouseUpEventHandler OnMouseDown; + public event PrimaryMouseDownEventHandler OnPrimaryMouseDown; public event SecondaryMouseUpEventHandler OnSecondaryMouseUp; + public event MiddleMouseDownEventHandler OnMiddleMouseDown; + public System.Windows.Point CurrentPosition { get @@ -148,9 +150,10 @@ namespace ColorPicker.Mouse _timer.Start(); } - _mouseHook.OnMouseDown += MouseHook_OnMouseDown; + _mouseHook.OnPrimaryMouseDown += MouseHook_OnPrimaryMouseDown; _mouseHook.OnMouseWheel += MouseHook_OnMouseWheel; _mouseHook.OnSecondaryMouseUp += MouseHook_OnSecondaryMouseUp; + _mouseHook.OnMiddleMouseDown += MouseHook_OnMiddleMouseDown; if (_userSettings.ChangeCursor.Value) { @@ -169,10 +172,10 @@ namespace ColorPicker.Mouse OnMouseWheel?.Invoke(this, new Tuple(_previousMousePosition, zoomIn)); } - private void MouseHook_OnMouseDown(object sender, Point p) + private void MouseHook_OnPrimaryMouseDown(object sender, IntPtr wParam) { DisposeHook(); - OnMouseDown?.Invoke(this, p); + OnPrimaryMouseDown?.Invoke(this, wParam); } private void MouseHook_OnSecondaryMouseUp(object sender, IntPtr wParam) @@ -181,6 +184,12 @@ namespace ColorPicker.Mouse OnSecondaryMouseUp?.Invoke(this, wParam); } + private void MouseHook_OnMiddleMouseDown(object sender, IntPtr wParam) + { + DisposeHook(); + OnMiddleMouseDown?.Invoke(this, wParam); + } + private void CopiedColorRepresentation_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { _colorFormatChanged = true; @@ -194,9 +203,10 @@ namespace ColorPicker.Mouse } _previousMousePosition = new System.Windows.Point(-1, 1); - _mouseHook.OnMouseDown -= MouseHook_OnMouseDown; + _mouseHook.OnPrimaryMouseDown -= MouseHook_OnPrimaryMouseDown; _mouseHook.OnMouseWheel -= MouseHook_OnMouseWheel; _mouseHook.OnSecondaryMouseUp -= MouseHook_OnSecondaryMouseUp; + _mouseHook.OnMiddleMouseDown -= MouseHook_OnMiddleMouseDown; if (_userSettings.ChangeCursor.Value) { diff --git a/src/modules/colorPicker/ColorPickerUI/Settings/IUserSettings.cs b/src/modules/colorPicker/ColorPickerUI/Settings/IUserSettings.cs index ab92e64081..170b58c944 100644 --- a/src/modules/colorPicker/ColorPickerUI/Settings/IUserSettings.cs +++ b/src/modules/colorPicker/ColorPickerUI/Settings/IUserSettings.cs @@ -21,6 +21,12 @@ namespace ColorPicker.Settings SettingItem ActivationAction { get; } + SettingItem PrimaryClickAction { get; } + + SettingItem MiddleClickAction { get; } + + SettingItem SecondaryClickAction { get; } + RangeObservableCollection ColorHistory { get; } SettingItem ColorHistoryLimit { get; } diff --git a/src/modules/colorPicker/ColorPickerUI/Settings/UserSettings.cs b/src/modules/colorPicker/ColorPickerUI/Settings/UserSettings.cs index eb6a9db7aa..e15a792259 100644 --- a/src/modules/colorPicker/ColorPickerUI/Settings/UserSettings.cs +++ b/src/modules/colorPicker/ColorPickerUI/Settings/UserSettings.cs @@ -49,7 +49,10 @@ namespace ColorPicker.Settings ChangeCursor = new SettingItem(true); ActivationShortcut = new SettingItem(DefaultActivationShortcut); CopiedColorRepresentation = new SettingItem(ColorRepresentationType.HEX.ToString()); - ActivationAction = new SettingItem(ColorPickerActivationAction.OpenEditor); + ActivationAction = new SettingItem(ColorPickerActivationAction.OpenColorPicker); + PrimaryClickAction = new SettingItem(ColorPickerClickAction.PickColorThenEditor); + MiddleClickAction = new SettingItem(ColorPickerClickAction.PickColorAndClose); + SecondaryClickAction = new SettingItem(ColorPickerClickAction.Close); ColorHistoryLimit = new SettingItem(20); ColorHistory.CollectionChanged += ColorHistory_CollectionChanged; ShowColorName = new SettingItem(false); @@ -78,6 +81,12 @@ namespace ColorPicker.Settings public SettingItem ActivationAction { get; private set; } + public SettingItem PrimaryClickAction { get; private set; } + + public SettingItem MiddleClickAction { get; private set; } + + public SettingItem SecondaryClickAction { get; private set; } + public RangeObservableCollection ColorHistory { get; private set; } = new RangeObservableCollection(); public SettingItem ColorHistoryLimit { get; } @@ -121,6 +130,9 @@ namespace ColorPicker.Settings CopiedColorRepresentation.Value = settings.Properties.CopiedColorRepresentation; CopiedColorRepresentationFormat = new SettingItem(string.Empty); ActivationAction.Value = settings.Properties.ActivationAction; + PrimaryClickAction.Value = settings.Properties.PrimaryClickAction; + MiddleClickAction.Value = settings.Properties.MiddleClickAction; + SecondaryClickAction.Value = settings.Properties.SecondaryClickAction; ColorHistoryLimit.Value = settings.Properties.ColorHistoryLimit; ShowColorName.Value = settings.Properties.ShowColorName; diff --git a/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs b/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs index a39bdbc8d9..377a1a5348 100644 --- a/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs +++ b/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace ColorPicker.Telemetry { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class ColorPickerSession : EventBase, IEvent { public ColorPickerSession() diff --git a/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSettings.cs b/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSettings.cs index 0bab133002..4fc265426c 100644 --- a/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSettings.cs +++ b/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSettings.cs @@ -3,14 +3,15 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace ColorPicker.Telemetry { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class ColorPickerSettings : EventBase, IEvent { public ColorPickerSettings(IDictionary> editorFormats) diff --git a/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs b/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs index 518f253456..e4d0a54b44 100644 --- a/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs +++ b/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs @@ -16,6 +16,7 @@ using ColorPicker.Settings; using ColorPicker.ViewModelContracts; using Common.UI; using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library.Enumerations; using PowerToys.Interop; namespace ColorPicker.ViewModels @@ -79,9 +80,10 @@ namespace ColorPicker.ViewModels { SetColorDetails(mouseInfoProvider.CurrentColor); mouseInfoProvider.MouseColorChanged += Mouse_ColorChanged; - mouseInfoProvider.OnMouseDown += MouseInfoProvider_OnMouseDown; + mouseInfoProvider.OnPrimaryMouseDown += MouseInfoProvider_OnPrimaryMouseDown; mouseInfoProvider.OnMouseWheel += MouseInfoProvider_OnMouseWheel; mouseInfoProvider.OnSecondaryMouseUp += MouseInfoProvider_OnSecondaryMouseUp; + mouseInfoProvider.OnMiddleMouseDown += MouseInfoProvider_OnMiddleMouseDown; } _userSettings.ShowColorName.PropertyChanged += (s, e) => { OnPropertyChanged(nameof(ShowColorName)); }; @@ -113,7 +115,7 @@ namespace ColorPicker.ViewModels private void AppStateHandler_EnterPressed(object sender, EventArgs e) { - MouseInfoProvider_OnMouseDown(null, default(System.Drawing.Point)); + MouseInfoProvider_OnPrimaryMouseDown(null, default); } /// @@ -167,18 +169,50 @@ namespace ColorPicker.ViewModels SetColorDetails(color); } - /// - /// Tell the color picker that the user have press a mouse button (after release the button) - /// - /// The sender of this event - /// The current of the mouse cursor - private void MouseInfoProvider_OnMouseDown(object sender, System.Drawing.Point p) + private void MouseInfoProvider_OnPrimaryMouseDown(object sender, IntPtr wParam) { - ClipboardHelper.CopyToClipboard(ColorText); + HandleMouseClickAction(_userSettings.PrimaryClickAction.Value); + } - var color = GetColorString(); + private void MouseInfoProvider_OnMiddleMouseDown(object sender, IntPtr wParam) + { + HandleMouseClickAction(_userSettings.MiddleClickAction.Value); + } - var oldIndex = _userSettings.ColorHistory.IndexOf(color); + private void MouseInfoProvider_OnSecondaryMouseUp(object sender, IntPtr wParam) + { + HandleMouseClickAction(_userSettings.SecondaryClickAction.Value); + } + + private void HandleMouseClickAction(ColorPickerClickAction action) + { + switch (action) + { + case ColorPickerClickAction.PickColorThenEditor: + ClipboardHelper.CopyToClipboard(ColorText); + UpdateColorHistory(GetColorString()); + + _appStateHandler.OpenColorEditor(); + + break; + + case ColorPickerClickAction.PickColorAndClose: + ClipboardHelper.CopyToClipboard(ColorText); + UpdateColorHistory(GetColorString()); + + _appStateHandler.EndUserSession(); + + break; + + case ColorPickerClickAction.Close: + _appStateHandler.EndUserSession(); + break; + } + } + + private void UpdateColorHistory(string color) + { + int oldIndex = _userSettings.ColorHistory.IndexOf(color); if (oldIndex != -1) { _userSettings.ColorHistory.Move(oldIndex, 0); @@ -192,13 +226,6 @@ namespace ColorPicker.ViewModels { _userSettings.ColorHistory.RemoveAt(_userSettings.ColorHistory.Count - 1); } - - _appStateHandler.OnColorPickerMouseDown(); - } - - private void MouseInfoProvider_OnSecondaryMouseUp(object sender, IntPtr wParam) - { - _appStateHandler.EndUserSession(); } private string GetColorString() diff --git a/src/modules/fancyzones/FancyZones.FuzzTests/FancyZones.FuzzTests.csproj b/src/modules/fancyzones/FancyZones.FuzzTests/FancyZones.FuzzTests.csproj new file mode 100644 index 0000000000..9890b67217 --- /dev/null +++ b/src/modules/fancyzones/FancyZones.FuzzTests/FancyZones.FuzzTests.csproj @@ -0,0 +1,36 @@ + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\tests\FancyZones.FuzzTests\ + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/modules/fancyzones/FancyZones.FuzzTests/FuzzTests.cs b/src/modules/fancyzones/FancyZones.FuzzTests/FuzzTests.cs new file mode 100644 index 0000000000..3bc846089f --- /dev/null +++ b/src/modules/fancyzones/FancyZones.FuzzTests/FuzzTests.cs @@ -0,0 +1,86 @@ +// 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.Text.Json; +using FancyZonesEditorCommon.Data; +using FancyZonesEditorCommon.Utils; +using static FancyZonesEditorCommon.Data.CustomLayouts; + +namespace FancyZones.FuzzTests +{ + public class FuzzTests + { + public static void FuzzGridFromJsonElement(ReadOnlySpan input) + { + if (input.Length < 4) + { + return; + } + + int inputData = BitConverter.ToInt32(input.Slice(0, 4)); + + // mock user input for custom-layouts.json + string mockCustomLayouts = $@"{{""custom-layouts"": [{{ + ""uuid"": ""{{B8C275E-A7BC-485F-A35C-67B69164F51F}}"", + ""name"": ""Custom layout 1"", + ""type"": ""grid"", + ""info"": {{ + ""rows"": {inputData}, + ""columns"": {inputData}, + ""rows-percentage"": [ {inputData} ], + ""columns-percentage"": [ {inputData}, {inputData}, {inputData} ], + ""cell-child-map"": [ [{inputData}, {inputData}, {inputData}] ], + ""show-spacing"": true, + ""spacing"": {inputData}, + ""sensitivity-radius"": {inputData} + }} + }}]}}"; + + CustomLayoutListWrapper wrapper; + try + { + wrapper = JsonSerializer.Deserialize(mockCustomLayouts, JsonOptions); + } + catch (JsonException) + { + return; + } + + List customLayouts = wrapper.CustomLayouts; + + if (customLayouts == null) + { + return; + } + + // Get Layout Info from mockCustomLayouts + foreach (var zoneSet in customLayouts) + { + if (zoneSet.Uuid == null || zoneSet.Uuid.Length == 0) + { + return; + } + + CustomLayouts deserializer = new CustomLayouts(); + + // Fuzzing the deserializer + _ = deserializer.GridFromJsonElement(zoneSet.Info.GetRawText()); + } + } + + private static JsonSerializerOptions JsonOptions + { + get + { + return new JsonSerializerOptions + { + PropertyNamingPolicy = new DashCaseNamingPolicy(), + WriteIndented = true, + }; + } + } + } +} diff --git a/src/modules/fancyzones/FancyZones.FuzzTests/MSTestSettings.cs b/src/modules/fancyzones/FancyZones.FuzzTests/MSTestSettings.cs new file mode 100644 index 0000000000..5b05c0b86e --- /dev/null +++ b/src/modules/fancyzones/FancyZones.FuzzTests/MSTestSettings.cs @@ -0,0 +1,5 @@ +// 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. + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/src/modules/fancyzones/FancyZones.FuzzTests/OneFuzzConfig.json b/src/modules/fancyzones/FancyZones.FuzzTests/OneFuzzConfig.json new file mode 100644 index 0000000000..e35a2167b6 --- /dev/null +++ b/src/modules/fancyzones/FancyZones.FuzzTests/OneFuzzConfig.json @@ -0,0 +1,51 @@ +{ + "configVersion": 3, + "entries": [ + { + "fuzzer": { + "$type": "libfuzzerDotNet", + "dll": "FancyZones.FuzzTests.dll", + "class": "FancyZones.FuzzTests.FuzzTests", + "method": "FuzzGridFromJsonElement", + "FuzzingTargetBinaries": [ + "PowerToys.FancyZones.dll" + ] + }, + "adoTemplate": { + // supply the values appropriate to your + // project, where bugs will be filed + "org": "microsoft", + "project": "OS", + "AssignedTo": "PowerToys@microsoft.com", + "AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT", + "IterationPath": "OS\\Future" + }, + "jobNotificationEmail": "PowerToys@microsoft.com", + "skip": false, + "rebootAfterSetup": false, + "oneFuzzJobs": [ + // at least one job is required + { + "projectName": "FancyZones", + "targetName": "FancyZones-dotnet-fuzzer-FuzzGridFromJsonElement" + } + ], + "jobDependencies": [ + // this should contain, at minimum, + // the DLL and PDB files + // you will need to add any other files required + // (globs are supported) + "FancyZones.FuzzTests.dll", + "FancyZones.FuzzTests.pdb", + "Microsoft.Windows.SDK.NET.dll", + "Newtonsoft.Json.dll", + "System.IO.Abstractions.dll", + "Testably.Abstractions.FileSystem.Interface.dll", + "TestableIO.System.IO.Abstractions.dll", + "TestableIO.System.IO.Abstractions.Wrappers.dll", + "WinRT.Runtime.dll" + ], + "SdlWorkItemId": 49911822 + } + ] +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 638776c25a..a2cae59ca8 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -23,6 +23,7 @@ namespace NonLocalizable { const wchar_t ToolWindowClassName[] = L"FancyZones_ZonesOverlay"; + const wchar_t ToolWindowName[] = L"FancyZones_ZonesOverlay"; } using namespace FancyZonesUtils; @@ -59,7 +60,7 @@ namespace HWND windowFromPool = ExtractWindow(); if (windowFromPool == NULL) { - HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner); + HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, NonLocalizable::ToolWindowName, WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner); Logger::info("Creating new ZonesOverlay window, hWnd = {}", (void*)window); FancyZonesWindowUtils::MakeWindowTransparent(window); diff --git a/src/modules/fancyzones/UITests-FancyZones/DragWindowTests.cs b/src/modules/fancyzones/UITests-FancyZones/DragWindowTests.cs new file mode 100644 index 0000000000..61d88e2121 --- /dev/null +++ b/src/modules/fancyzones/UITests-FancyZones/DragWindowTests.cs @@ -0,0 +1,673 @@ +// 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.Threading.Tasks; +using System.Windows.Forms; +using FancyZonesEditor.Models; +using FancyZonesEditorCommon.Data; +using Microsoft.FancyZones.UITests.Utils; +using Microsoft.FancyZonesEditor.UITests.Utils; +using Microsoft.FancyZonesEditor.UnitTests.Utils; +using Microsoft.PowerToys.UITest; +using Microsoft.VisualBasic.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.Windows; +using static FancyZonesEditorCommon.Data.CustomLayouts; + +namespace UITests_FancyZones +{ + [TestClass] + public class DragWindowTests : UITestBase + { + private static readonly IOTestHelper AppZoneHistory = new FancyZonesEditorFiles().AppZoneHistoryIOHelper; + private static string nonPrimaryMouseButton = "Right"; + + private static string highlightColor = "#008CFF"; // set highlight color + private static string inactivateColor = "#AACDFF"; // set inactivate zone color + + // set screen margin + private static int screenMarginTop; + private static int screenMarginLeft; + private static int screenMarginRight; + private static int screenMarginBottom; + + // set 1/4 margin + private static int quarterX; + private static int quarterY; + + private static string powertoysWindowName = "PowerToys Settings"; // set powertoys settings window name + + public DragWindowTests() + : base(PowerToysModule.PowerToysSettings, WindowSize.Medium) + { + } + + [TestInitialize] + public void TestInitialize() + { + // ClearOpenWindows + ClearOpenWindows(); + + // kill all processes related to FancyZones Editor to ensure a clean state + Session.KillAllProcessesByName("PowerToys.FancyZonesEditor"); + + AppZoneHistory.DeleteFile(); + this.RestartScopeExe(); + FancyZonesEditorHelper.Files.Restore(); + + // Set a custom layout with 1 subzones and clear app zone history + SetupCustomLayouts(); + + // Get the current mouse button setting + nonPrimaryMouseButton = SystemInformation.MouseButtonsSwapped ? "Left" : "Right"; + + // get PowerToys window Name + powertoysWindowName = ZoneSwitchHelper.GetActiveWindowTitle(); + + // Ensure FancyZones settings page is visible and enable FancyZones + LaunchFancyZones(); + } + + /// + /// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings + /// + /// + /// Verifies that holding Shift while dragging shows all zones as expected. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")] + [TestCategory("FancyZones_Dragging #1")] + public void TestShowZonesOnShiftDuringDrag() + { + string testCaseName = nameof(TestShowZonesOnShiftDuringDrag); + Pane dragElement = Find(By.Name("Non Client Input Sink Window")); // element to drag + var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + + var (initialColor, withShiftColor) = RunDragInteractions( + preAction: () => + { + dragElement.DragAndHold(offSet.Dx, offSet.Dy); + }, + postAction: () => + { + Session.PressKey(Key.Shift); + Task.Delay(500).Wait(); + }, + releaseAction: () => + { + Session.ReleaseKey(Key.Shift); + Task.Delay(5000).Wait(); // Optional: Wait for a moment to ensure window switch + }, + testCaseName: testCaseName); + + string zoneColorWithoutShift = GetOutWindowPixelColor(30); + + Assert.AreNotEqual(initialColor, withShiftColor, $"[{testCaseName}] Zone display failed."); + Assert.IsTrue( + withShiftColor == inactivateColor || withShiftColor == highlightColor, + $"[{testCaseName}] Zone display failed: withShiftColor was {withShiftColor}, expected {inactivateColor} or {highlightColor}."); + Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] Zone display failed."); + + Assert.AreEqual(zoneColorWithoutShift, initialColor, $"[{testCaseName}] Zone deactivated failed."); + dragElement.ReleaseDrag(); + + Clean(); + } + + /// + /// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings + /// + /// + /// Verifies that dragging activates zones as expected. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestShowZonesOnDragDuringShift")] + [TestCategory("FancyZones_Dragging #2")] + public void TestShowZonesOnDragDuringShift() + { + string testCaseName = nameof(TestShowZonesOnDragDuringShift); + + var dragElement = Find(By.Name("Non Client Input Sink Window")); + var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + + var (initialColor, withDragColor) = RunDragInteractions( + preAction: () => + { + dragElement.Drag(offSet.Dx, offSet.Dy); + Session.PressKey(Key.Shift); + }, + postAction: () => + { + dragElement.DragAndHold(0, 0); + Task.Delay(5000).Wait(); + }, + releaseAction: () => + { + dragElement.ReleaseDrag(); + Session.ReleaseKey(Key.Shift); + }, + testCaseName: testCaseName); + + Assert.AreNotEqual(initialColor, withDragColor, $"[{testCaseName}] Zone color did not change; zone activation failed."); + Assert.AreEqual(highlightColor, withDragColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed."); + + // double check by app-zone-history.json + string appZoneHistoryJson = AppZoneHistory.GetData(); + string? zoneNumber = ZoneSwitchHelper.GetZoneIndexSetByAppName(powertoysWindowName, appZoneHistoryJson); + Assert.IsNull(zoneNumber, $"[{testCaseName}] AppZoneHistory layout was unexpectedly set."); + + Clean(); + } + + /// + /// Test toggling zones using a non-primary mouse click during window dragging. + /// + /// + /// Verifies that clicking a non-primary mouse button deactivates zones while dragging a window. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestToggleZonesWithNonPrimaryMouseClick")] + [TestCategory("FancyZones_Dragging #3")] + public void TestToggleZonesWithNonPrimaryMouseClick() + { + string testCaseName = nameof(TestToggleZonesWithNonPrimaryMouseClick); + var dragElement = Find(By.Name("Non Client Input Sink Window")); + var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + + var (initialColor, withMouseColor) = RunDragInteractions( + preAction: () => + { + // activate zone + dragElement.DragAndHold(offSet.Dx, offSet.Dy); + }, + postAction: () => + { + // press non-primary mouse button to toggle zones + Session.PerformMouseAction( + nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick); + }, + releaseAction: () => + { + dragElement.ReleaseDrag(); + }, + testCaseName: testCaseName); + + // check the zone color is deactivated + Assert.AreNotEqual(highlightColor, withMouseColor, $"[{testCaseName}] Zone deactivation failed."); + + // check the zone color is activated + Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed."); + + Clean(); + } + + /// + /// Test both use Shift and non primary mouse off settings. + /// + /// + /// Verifies that pressing the Shift key deactivates zones during a window drag-and-hold action. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestShowZonesWhenShiftAndMouseOff")] + [TestCategory("FancyZones_Dragging #4")] + public void TestShowZonesWhenShiftAndMouseOff() + { + string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOff); + Pane dragElement = Find(By.Name("Non Client Input Sink Window")); + var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + + var (initialColor, withShiftColor) = RunDragInteractions( + preAction: () => + { + // activate zone + dragElement.DragAndHold(offSet.Dx, offSet.Dy); + }, + postAction: () => + { + // press Shift Key to deactivate zones + Session.PressKey(Key.Shift); + Task.Delay(500).Wait(); + }, + releaseAction: () => + { + dragElement.ReleaseDrag(); + Session.ReleaseKey(Key.Shift); + }, + testCaseName: testCaseName); + + Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed."); + Assert.AreNotEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone deactivation failed."); + + Clean(); + } + + /// + /// Test zone visibility when both Shift key and mouse settings are involved. + /// + /// + /// Verifies that zones are activated when Shift is pressed during drag, and deactivated by a non-primary mouse click. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestShowZonesWhenShiftAndMouseOn")] + [TestCategory("FancyZones_Dragging #5")] + public void TestShowZonesWhenShiftAndMouseOn() + { + string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOn); + + var dragElement = Find(By.Name("Non Client Input Sink Window")); + var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + var (initialColor, withShiftColor) = RunDragInteractions( + preAction: () => + { + dragElement.DragAndHold(offSet.Dx, offSet.Dy); + }, + postAction: () => + { + Session.PressKey(Key.Shift); + }, + releaseAction: () => + { + }, + testCaseName: testCaseName); + + Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] show zone failed."); + + Session.PerformMouseAction( + nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick); + + string zoneColorWithMouse = GetOutWindowPixelColor(30); + Assert.AreEqual(initialColor, zoneColorWithMouse, $"[{nameof(TestShowZonesWhenShiftAndMouseOff)}] Zone deactivate failed."); + + Session.ReleaseKey(Key.Shift); + dragElement.ReleaseDrag(); + + Clean(); + } + + /// + /// Test that a window becomes transparent during dragging when the transparent window setting is enabled. + /// + /// + /// Verifies that the window appears transparent while being dragged. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestMakeDraggedWindowTransparentOn")] + [TestCategory("FancyZones_Dragging #8")] + public void TestMakeDraggedWindowTransparentOn() + { + var pixel = GetPixelWhenMakeDraggedWindow(); + Assert.AreNotEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOn)}] Window transparency failed."); + + Clean(); + } + + /// + /// Test that a window remains opaque during dragging when the transparent window setting is disabled. + /// + /// + /// Verifies that the window is not transparent while being dragged. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestMakeDraggedWindowTransparentOff")] + [TestCategory("FancyZones_Dragging #8")] + public void TestMakeDraggedWindowTransparentOff() + { + var pixel = GetPixelWhenMakeDraggedWindow(); + Assert.AreEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOff)}] Window without transparency failed."); + + Clean(); + } + + private void Clean() + { + // clean app zone history file + AppZoneHistory.DeleteFile(); + } + + // Helper method to ensure the desktop has no open windows by clicking the "Show Desktop" button + private void ClearOpenWindows() + { + string desktopButtonName; + + // Check for both possible button names (Win10/Win11) + if (this.FindAll("Show Desktop", 5000, true).Count == 0) + { + // win10 + desktopButtonName = "Show desktop"; + } + else + { + // win11 + desktopButtonName = "Show Desktop"; + } + + this.Find(By.Name(desktopButtonName), 5000, true).Click(false, 500, 2000); + } + + // Setup custom layout with 1 subzones + private void SetupCustomLayouts() + { + var customLayouts = new CustomLayouts(); + var customLayoutListWrapper = CustomLayoutsList; + + if (TestContext.TestName == "TestMakeDraggedWindowTransparentOff") + { + customLayoutListWrapper = CustomLayoutsListWithTwo; + } + + FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper)); + } + + // launch FancyZones settings page + private void LaunchFancyZones() + { + if (this.FindAll("FancyZones").Count == 0) + { + this.Find("Windowing & Layouts").Click(); + } + + this.Find("FancyZones").Click(); + this.Find("Enable FancyZones").Toggle(true); + + this.Session.SetMainWindowSize(WindowSize.Large); + Find(By.AccessibilityId("HeaderPresenter")).Click(); + this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible + ZoneBehaviourSettings(TestContext.TestName); + + this.Find("Launch layout editor").Click(false, 500, 10000); + this.Session.Attach(PowerToysModule.FancyZone); + + // pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required. + // Console.WriteLine($"after launch, Custom layout data: {customLayoutData}"); + try + { + this.Find("Maximize").Click(); + + // Set the FancyZones layout to a custom layout + this.Find(By.Name("Custom Column")).Click(); + } + catch (Exception) + { + // Console.WriteLine($"[Exception] Failed to attach to FancyZones window. Retrying...{ex.Message}"); + this.Find("Close").Click(); + this.Session.Attach(PowerToysModule.PowerToysSettings); + SetupCustomLayouts(); + this.Find("Launch layout editor").Click(false, 5000, 5000); + this.Session.Attach(PowerToysModule.FancyZone); + this.Find("Maximize").Click(); + + // customLayoutData = FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.GetData(); + // Console.WriteLine($"after retry, Custom layout data: {customLayoutData}"); + + // Set the FancyZones layout to a custom layout + this.Find(By.Name("Custom Column")).Click(); + } + + // Get screen margins for positioning checks + GetScreenMargins(); + + // Close layout editor window + SendKeys(Key.Alt, Key.F4); + + // make window small to detect zone easily + Session.Attach(powertoysWindowName, WindowSize.Small); + } + + // Get the screen margins to calculate the dragged window position + private void GetScreenMargins() + { + var rect = Session.GetMainWindowRect(); + screenMarginTop = rect.Top; + screenMarginLeft = rect.Left; + screenMarginRight = rect.Right; + screenMarginBottom = rect.Bottom; + (quarterX, quarterY) = ZoneSwitchHelper.GetScreenMargins(rect, 4); + } + + // Get the mouse color of the pixel when make dragged window + private (string PixelInWindow, string TransPixel) GetPixelWhenMakeDraggedWindow() + { + var dragElement = Find(By.Name("Non Client Input Sink Window")); + + // maximize the window to make sure get pixel color more accurate + dragElement.DoubleClick(); + + var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + Session.PressKey(Key.Shift); + dragElement.DragAndHold(offSet.Dx, offSet.Dy); + Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position + Tuple pos = GetMousePosition(); + string pixelInWindow = this.GetPixelColorString(pos.Item1, pos.Item2); + Session.ReleaseKey(Key.Shift); + Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position + string transPixel = this.GetPixelColorString(pos.Item1, pos.Item2); + dragElement.ReleaseDrag(); + + return (pixelInWindow, transPixel); + } + + /// + /// Gets the color of a pixel located just outside the application's window. + /// + /// + /// The minimum spacing (in pixels) required between the window edge and screen margin + /// to determine a safe pixel sampling area outside the window. + /// + /// + /// A string representing the color of the pixel at the computed location outside the window, + /// + private string GetOutWindowPixelColor(int spacing) + { + var rect = Session.GetMainWindowRect(); + int checkX, checkY; + + if ((rect.Top - screenMarginTop) >= spacing) + { + checkX = rect.Left; + checkY = screenMarginTop + (spacing / 2); + } + else if ((screenMarginBottom - rect.Bottom) >= spacing) + { + checkX = rect.Left; + checkY = rect.Bottom + (spacing / 2); + } + else if ((rect.Left - screenMarginLeft) >= spacing) + { + checkX = rect.Left - (spacing / 2); + checkY = rect.Top; + } + else if ((screenMarginRight - rect.Right) >= spacing) + { + checkX = rect.Right + (spacing / 2); + checkY = rect.Top; + } + else + { + throw new ArgumentOutOfRangeException(nameof(spacing), "No sufficient margin to sample outside the window."); + } + + Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the mouse is in position + string zoneColor = this.GetPixelColorString(checkX, checkY); + return zoneColor; + } + + /// + /// Runs drag interactions during a FancyZones test and returns the initial and final zone highlight colors. + /// + /// An optional action to execute before the drag starts (e.g., setup or key press). + /// An optional action to execute after the drag is initiated but before it's released. + /// An optional action to execute when releasing the dragged window (e.g., mouse up). + /// The name of the test case for logging or diagnostics. + /// + /// A tuple containing: + /// + /// InitialZoneColor: The zone highlight color before interaction completes. + /// FinalZoneColor: The zone highlight color after interaction completes. + /// + /// + private (string InitialZoneColor, string FinalZoneColor) RunDragInteractions( + Action? preAction, + Action? postAction, + Action? releaseAction, + string testCaseName) + { + // Invoke the pre-action + preAction?.Invoke(); + + // Capture initial window state and zone color + var initialWindowRect = Session.GetMainWindowRect(); + string initialZoneColor = GetOutWindowPixelColor(30); + + // Invoke the post-action + postAction?.Invoke(); + + // Capture final zone color after the interaction + string finalZoneColor = GetOutWindowPixelColor(30); + + releaseAction?.Invoke(); + + // Return initial and final zone colors + return (initialZoneColor, finalZoneColor); + } + + // set the custom layout + private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper + { + CustomLayouts = new List + { + new CustomLayouts.CustomLayoutWrapper + { + Uuid = "{63F09977-D327-4DAC-98F4-0C886CAE9517}", + Type = CustomLayout.Grid.TypeToString(), + Name = "Custom Column", + Info = new CustomLayouts().ToJsonElement(new CustomLayouts.GridInfoWrapper + { + Rows = 1, + Columns = 1, + RowsPercentage = new List { 10000 }, + ColumnsPercentage = new List { 10000 }, + CellChildMap = new int[][] { [0] }, + SensitivityRadius = 20, + ShowSpacing = true, + Spacing = 10, // set spacing to 0 make sure the zone is full of the screen + }), + }, + }, + }; + + // set the custom layout with 1 subzones + private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsListWithTwo = new CustomLayouts.CustomLayoutListWrapper + { + CustomLayouts = new List + { + new CustomLayouts.CustomLayoutWrapper + { + Uuid = "{63F09977-D327-4DAC-98F4-0C886CAE9517}", + Type = CustomLayout.Grid.TypeToString(), + Name = "Custom Column", + Info = new CustomLayouts().ToJsonElement(new CustomLayouts.GridInfoWrapper + { + Rows = 1, + Columns = 2, + RowsPercentage = new List { 10000 }, + ColumnsPercentage = new List { 5000, 5000 }, + CellChildMap = new int[][] { [0, 1] }, + SensitivityRadius = 20, + ShowSpacing = true, + Spacing = 10, + }), + }, + }, + }; + + private string GetZoneColor(string color) + { + // Click on the "Highlight color" group + Find(color).Click(); + + // Optional: Ensure the hex textbox is found (to wait until the UI loads) + var hexBox = Find(By.AccessibilityId("HexTextBox")); + Task.Delay(500).Wait(); // Optional: Wait for the UI to update + + // Get and return the RGB hex value text + var hexColorElement = Find("RGB hex"); + + // return mouse to color set position + Find(color).Click(); + + return hexColorElement.Text; + } + + // set the zone behaviour settings + private void ZoneBehaviourSettings(string? testName) + { + // test settings + Microsoft.PowerToys.UITest.CheckBox useShiftCheckBox = this.Find("Hold Shift key to activate zones while dragging a window"); + Microsoft.PowerToys.UITest.CheckBox useNonPrimaryMouseCheckBox = this.Find("Use a non-primary mouse button to toggle zone activation"); + Microsoft.PowerToys.UITest.CheckBox makeDraggedWindowTransparent = this.Find("Make dragged window transparent"); + + Find("Show zone number").SetCheck(false, 100); + Find("Opacity (%)").QuickSetValue(100); // make highlight color visible with opacity 100 + + // Get the highlight and inactivate color from appearance settings + Find("Zone appearance").Click(); + Find("Custom colors").Click(); + + // get the highlight (activated) and inactivate zone color + highlightColor = GetZoneColor("Highlight color"); + inactivateColor = GetZoneColor("Inactive color"); + + this.Scroll(2, "Down"); + makeDraggedWindowTransparent.SetCheck(false, 500); // set make dragged window transparent to false or will influence the color comparison + this.Scroll(6, "Up"); + + switch (testName) + { + case "TestShowZonesOnShiftDuringDrag": + useShiftCheckBox.SetCheck(true, 500); + useNonPrimaryMouseCheckBox.SetCheck(false, 500); + break; + case "TestShowZonesOnDragDuringShift": + useShiftCheckBox.SetCheck(true, 500); + useNonPrimaryMouseCheckBox.SetCheck(false, 500); + break; + case "TestToggleZonesWithNonPrimaryMouseClick": + useShiftCheckBox.SetCheck(false, 500); + useNonPrimaryMouseCheckBox.SetCheck(true, 500); + break; + case "TestShowZonesWhenShiftAndMouseOff": + useShiftCheckBox.SetCheck(false, 500); + useNonPrimaryMouseCheckBox.SetCheck(false, 500); + break; + case "TestShowZonesWhenShiftAndMouseOn": + useShiftCheckBox.SetCheck(true, 500); + useNonPrimaryMouseCheckBox.SetCheck(true, 500); + break; + case "TestMakeDraggedWindowTransparentOff": + useShiftCheckBox.SetCheck(true, 500); + useNonPrimaryMouseCheckBox.SetCheck(false, 500); + break; // Added break to prevent fall-through + case "TestMakeDraggedWindowTransparentOn": + useNonPrimaryMouseCheckBox.SetCheck(false, 500); + useShiftCheckBox.SetCheck(true, 500); + this.Scroll(5, "Down"); // Pull the settings page up to make sure the settings are visible + makeDraggedWindowTransparent.SetCheck(true, 500); + this.Scroll(5, "Up"); + break; // Added break to prevent fall-through + default: + throw new ArgumentException("Unsupported Test Case.", testName); + } + } + } +} diff --git a/src/modules/fancyzones/UITests-FancyZones/LayoutApplyHotKeyTests.cs b/src/modules/fancyzones/UITests-FancyZones/LayoutApplyHotKeyTests.cs new file mode 100644 index 0000000000..8956032a0b --- /dev/null +++ b/src/modules/fancyzones/UITests-FancyZones/LayoutApplyHotKeyTests.cs @@ -0,0 +1,673 @@ +// 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.Threading; +using System.Threading.Tasks; +using System.Windows.Automation; +using FancyZonesEditor.Models; +using FancyZonesEditorCommon.Data; +using Microsoft.FancyZonesEditor.UITests; +using Microsoft.FancyZonesEditor.UnitTests.Utils; +using Microsoft.PowerToys.UITest; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static FancyZonesEditorCommon.Data.CustomLayouts; +using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper; + +namespace UITests_FancyZones +{ + [TestClass] + public class LayoutApplyHotKeyTests : UITestBase + { + public LayoutApplyHotKeyTests() + : base(PowerToysModule.PowerToysSettings, WindowSize.Large) + { + } + + private static readonly EditorParameters.ParamsWrapper Parameters = new EditorParameters.ParamsWrapper + { + ProcessId = 1, + SpanZonesAcrossMonitors = false, + Monitors = new List + { + new EditorParameters.NativeMonitorDataWrapper + { + Monitor = "monitor-1", + MonitorInstanceId = "instance-id-1", + MonitorSerialNumber = "serial-number-1", + MonitorNumber = 1, + VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}", + Dpi = 96, + LeftCoordinate = 0, + TopCoordinate = 0, + WorkAreaHeight = 1040, + WorkAreaWidth = 1920, + MonitorHeight = 1080, + MonitorWidth = 1920, + IsSelected = true, + }, + new EditorParameters.NativeMonitorDataWrapper + { + Monitor = "monitor-2", + MonitorInstanceId = "instance-id-2", + MonitorSerialNumber = "serial-number-2", + MonitorNumber = 2, + VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}", + Dpi = 96, + LeftCoordinate = 1920, + TopCoordinate = 0, + WorkAreaHeight = 1040, + WorkAreaWidth = 1920, + MonitorHeight = 1080, + MonitorWidth = 1920, + IsSelected = false, + }, + }, + }; + + private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper + { + CustomLayouts = new List + { + new CustomLayoutWrapper + { + Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}", + Type = CustomLayout.Grid.TypeToString(), + Name = "Grid custom layout", + Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper + { + Rows = 2, + Columns = 2, + RowsPercentage = new List { 5000, 5000 }, + ColumnsPercentage = new List { 5000, 5000 }, + CellChildMap = new int[][] { [0, 1], [2, 3] }, + SensitivityRadius = 30, + Spacing = 26, + ShowSpacing = false, + }), + }, + new CustomLayoutWrapper + { + Uuid = "{0EB9BF3E-010E-46D7-8681-1879D1E111E1}", + Type = CustomLayout.Grid.TypeToString(), + Name = "Grid-9", + Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper + { + Rows = 3, + Columns = 3, + RowsPercentage = new List { 2333, 3333, 4334 }, + ColumnsPercentage = new List { 2333, 3333, 4334 }, + CellChildMap = new int[][] { [0, 1, 2], [3, 4, 5], [6, 7, 8] }, + SensitivityRadius = 20, + Spacing = 3, + ShowSpacing = false, + }), + }, + new CustomLayoutWrapper + { + Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}", + Type = CustomLayout.Canvas.TypeToString(), + Name = "Canvas custom layout", + Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper + { + RefHeight = 1040, + RefWidth = 1920, + SensitivityRadius = 10, + Zones = new List + { + new CanvasInfoWrapper.CanvasZoneWrapper + { + X = 0, + Y = 0, + Width = 500, + Height = 250, + }, + new CanvasInfoWrapper.CanvasZoneWrapper + { + X = 500, + Y = 0, + Width = 1420, + Height = 500, + }, + new CanvasInfoWrapper.CanvasZoneWrapper + { + X = 0, + Y = 250, + Width = 1920, + Height = 500, + }, + }, + }), + }, + }, + }; + + private static readonly LayoutHotkeys.LayoutHotkeysWrapper LayoutHotkeysList = new LayoutHotkeys.LayoutHotkeysWrapper + { + LayoutHotkeys = new List + { + new LayoutHotkeys.LayoutHotkeyWrapper + { + Key = 0, + LayoutId = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}", + }, + new LayoutHotkeys.LayoutHotkeyWrapper + { + Key = 1, + LayoutId = "{0EB9BF3E-010E-46D7-8681-1879D1E111E1}", + }, + new LayoutHotkeys.LayoutHotkeyWrapper + { + Key = 2, + LayoutId = "{E7807D0D-6223-4883-B15B-1F3883944C09}", + }, + }, + }; + + private static readonly LayoutTemplates.TemplateLayoutsListWrapper TemplateLayoutsList = new LayoutTemplates.TemplateLayoutsListWrapper + { + LayoutTemplates = new List + { + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Blank.TypeToString(), + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Focus.TypeToString(), + ZoneCount = 10, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Rows.TypeToString(), + ZoneCount = 2, + ShowSpacing = true, + Spacing = 10, + SensitivityRadius = 10, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Columns.TypeToString(), + ZoneCount = 2, + ShowSpacing = true, + Spacing = 20, + SensitivityRadius = 20, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Grid.TypeToString(), + ZoneCount = 4, + ShowSpacing = false, + Spacing = 10, + SensitivityRadius = 30, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.PriorityGrid.TypeToString(), + ZoneCount = 3, + ShowSpacing = true, + Spacing = 1, + SensitivityRadius = 40, + }, + }, + }; + + [TestInitialize] + public void TestInitialize() + { + FancyZonesEditorHelper.Files.Restore(); + EditorParameters editorParameters = new EditorParameters(); + FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters)); + + LayoutTemplates layoutTemplates = new LayoutTemplates(); + FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(TemplateLayoutsList)); + + CustomLayouts customLayouts = new CustomLayouts(); + FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayoutsList)); + + DefaultLayouts defaultLayouts = new DefaultLayouts(); + DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper + { + DefaultLayouts = new List + { + new DefaultLayouts.DefaultLayoutWrapper + { + MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(), + Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper + { + Type = LayoutType.Focus.TypeToString(), + ZoneCount = 4, + ShowSpacing = true, + Spacing = 5, + SensitivityRadius = 20, + }, + }, + new DefaultLayouts.DefaultLayoutWrapper + { + MonitorConfiguration = MonitorConfigurationType.Vertical.TypeToString(), + Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper + { + Type = LayoutType.Custom.TypeToString(), + Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}", + ZoneCount = 0, + ShowSpacing = false, + Spacing = 0, + SensitivityRadius = 0, + }, + }, + }, + }; + FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper)); + + LayoutHotkeys layoutHotkeys = new LayoutHotkeys(); + FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(LayoutHotkeysList)); + + AppliedLayouts appliedLayouts = new AppliedLayouts(); + AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper + { + AppliedLayouts = new List { }, + }; + FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper)); + + this.RestartScopeExe(); + } + + [TestMethod("FancyZones.Settings.TestApplyHotKey")] + [TestCategory("FancyZones #1")] + public void TestApplyHotKey() + { + this.OpenFancyZonesPanel(); + this.ControlQuickLayoutSwitch(true); + + // Set Hotkey + this.AttachFancyZonesEditor(); + var layout = "Grid custom layout"; + Session.Find(layout).Find [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class LauncherQueryEvent : EventBase, IEvent { public double QueryTimeMs { get; set; } diff --git a/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherResultActionEvent.cs b/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherResultActionEvent.cs index e4cc9b20ce..fb5ee7caa1 100644 --- a/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherResultActionEvent.cs +++ b/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherResultActionEvent.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerLauncher.Telemetry /// ETW event for when a result is actioned. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class LauncherResultActionEvent : EventBase, IEvent { public enum TriggerType diff --git a/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherShowEvent.cs b/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherShowEvent.cs index 8882c5f0ec..f56ff92b44 100644 --- a/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherShowEvent.cs +++ b/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherShowEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace Microsoft.PowerLauncher.Telemetry { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class LauncherShowEvent : EventBase, IEvent { public LauncherShowEvent(string hotkey) diff --git a/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherWarmStateHotkeyEvent.cs b/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherWarmStateHotkeyEvent.cs index 5dbeba4865..c828b37c88 100644 --- a/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherWarmStateHotkeyEvent.cs +++ b/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherWarmStateHotkeyEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace Microsoft.PowerLauncher.Telemetry { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class LauncherWarmStateHotkeyEvent : EventBase, IEvent { public double HotkeyToVisibleTimeMs { get; set; } diff --git a/src/modules/launcher/PowerLauncher.Telemetry/Events/RunPluginsSettingsEvent.cs b/src/modules/launcher/PowerLauncher.Telemetry/Events/RunPluginsSettingsEvent.cs index 94eaa90089..f92219671a 100644 --- a/src/modules/launcher/PowerLauncher.Telemetry/Events/RunPluginsSettingsEvent.cs +++ b/src/modules/launcher/PowerLauncher.Telemetry/Events/RunPluginsSettingsEvent.cs @@ -3,14 +3,15 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace PowerLauncher.Telemetry.Events { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class RunPluginsSettingsEvent : EventBase, IEvent { public RunPluginsSettingsEvent(IDictionary pluginManager) diff --git a/src/modules/launcher/PowerLauncher/Helper/ErrorReporting.cs b/src/modules/launcher/PowerLauncher/Helper/ErrorReporting.cs index 3eb6252a24..5003a02cae 100644 --- a/src/modules/launcher/PowerLauncher/Helper/ErrorReporting.cs +++ b/src/modules/launcher/PowerLauncher/Helper/ErrorReporting.cs @@ -14,25 +14,7 @@ namespace PowerLauncher.Helper { public static class ErrorReporting { - private static void Report(Exception e, bool waitForClose) - { - if (e != null) - { - var logger = LogManager.GetLogger("UnHandledException"); - logger.Fatal(ExceptionFormatter.FormatException(e)); - - var reportWindow = new ReportWindow(e); - - if (waitForClose) - { - reportWindow.ShowDialog(); - } - else - { - reportWindow.Show(); - } - } - } + private const string LoggerName = "UnHandledException"; public static void ShowMessageBox(string title, string message) { @@ -47,17 +29,20 @@ namespace PowerLauncher.Helper // handle non-ui thread exceptions System.Windows.Application.Current.Dispatcher.Invoke(() => { - Report((Exception)e?.ExceptionObject, true); + HandleException(e?.ExceptionObject as Exception, true); }); } public static void DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { - // handle ui thread exceptions - Report(e?.Exception, false); + if (e != null) + { + // handle ui thread exceptions + HandleException(e.Exception, false); - // prevent application exist, so the user can copy prompted error info - e.Handled = true; + // prevent application exist, so the user can copy prompted error info + e.Handled = true; + } } public static string RuntimeInfo() @@ -68,5 +53,60 @@ namespace PowerLauncher.Helper $"\nx64: {Environment.Is64BitOperatingSystem}"; return info; } + + private static void HandleException(Exception e, bool isNotUIThread) + { + // The crash occurs in PresentationFramework.dll, not necessarily when the Runner UI is visible, originating from this line: + // https://github.com/dotnet/wpf/blob/3439f20fb8c685af6d9247e8fd2978cac42e74ac/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Shell/WindowChromeWorker.cs#L1005 + // Many bug reports because users see the "Report problem UI" after "the" crash with System.Runtime.InteropServices.COMException 0xD0000701 or 0x80263001. + // However, displaying this "Report problem UI" during WPF crashes, especially when DWM composition is changing, is not ideal; some users reported it hangs for up to a minute before the "Report problem UI" appears. + // This change modifies the behavior to log the exception instead of showing the "Report problem UI". + if (IsDwmCompositionException(e as System.Runtime.InteropServices.COMException)) + { + var logger = LogManager.GetLogger(LoggerName); + logger.Error($"From {(isNotUIThread ? "non" : string.Empty)} UI thread's exception: {ExceptionFormatter.FormatException(e)}"); + } + else + { + Report(e, isNotUIThread); + } + } + + private static void Report(Exception e, bool waitForClose) + { + if (e != null) + { + var logger = LogManager.GetLogger(LoggerName); + logger.Fatal($"From {(waitForClose ? "non" : string.Empty)} UI thread's exception: {ExceptionFormatter.FormatException(e)}"); + + var reportWindow = new ReportWindow(e); + + if (waitForClose) + { + reportWindow.ShowDialog(); + } + else + { + reportWindow.Show(); + } + } + } + + private static bool IsDwmCompositionException(System.Runtime.InteropServices.COMException comException) + { + if (comException == null) + { + return false; + } + + var stackTrace = comException.StackTrace; + if (string.IsNullOrEmpty(stackTrace)) + { + return false; + } + + // Check for common DWM composition changed patterns in the stack trace + return stackTrace.Contains("DwmCompositionChanged"); + } } } diff --git a/src/modules/launcher/PowerLauncher/app.manifest b/src/modules/launcher/PowerLauncher/app.manifest index fb9b15e291..88e30f9f32 100644 --- a/src/modules/launcher/PowerLauncher/app.manifest +++ b/src/modules/launcher/PowerLauncher/app.manifest @@ -52,8 +52,8 @@ - true - PerMonitorV2, PerMonitor + true/PM + PerMonitorV2 diff --git a/src/modules/launcher/Wox.Plugin/Common/DefaultBrowserInfo.cs b/src/modules/launcher/Wox.Plugin/Common/DefaultBrowserInfo.cs index 64493e23e2..a5796efeb1 100644 --- a/src/modules/launcher/Wox.Plugin/Common/DefaultBrowserInfo.cs +++ b/src/modules/launcher/Wox.Plugin/Common/DefaultBrowserInfo.cs @@ -80,8 +80,11 @@ namespace Wox.Plugin.Common try { string progId = GetRegistryValue( - @"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice", - "ProgId"); + @"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoiceLatest\ProgId", + "ProgId") + ?? GetRegistryValue( + @"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice", + "ProgId"); string appName = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\Application", "ApplicationName") ?? GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}", "FriendlyTypeName"); diff --git a/src/modules/peek/Peek.Common/Telemetry/Events/ClosedEvent.cs b/src/modules/peek/Peek.Common/Telemetry/Events/ClosedEvent.cs index d50f49ac0d..d71629ad46 100644 --- a/src/modules/peek/Peek.Common/Telemetry/Events/ClosedEvent.cs +++ b/src/modules/peek/Peek.Common/Telemetry/Events/ClosedEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace Peek.UI.Telemetry.Events { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class ClosedEvent : EventBase, IEvent { public ClosedEvent() diff --git a/src/modules/peek/Peek.Common/Telemetry/Events/ErrorEvent.cs b/src/modules/peek/Peek.Common/Telemetry/Events/ErrorEvent.cs index d308308291..2ec0d2fa93 100644 --- a/src/modules/peek/Peek.Common/Telemetry/Events/ErrorEvent.cs +++ b/src/modules/peek/Peek.Common/Telemetry/Events/ErrorEvent.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; using Peek.Common.Models; @@ -11,6 +11,7 @@ using Peek.Common.Models; namespace Peek.UI.Telemetry.Events { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class ErrorEvent : EventBase, IEvent { public class FailureType diff --git a/src/modules/peek/Peek.Common/Telemetry/Events/OpenWithEvent.cs b/src/modules/peek/Peek.Common/Telemetry/Events/OpenWithEvent.cs index 0abb563588..46f9fc2fb5 100644 --- a/src/modules/peek/Peek.Common/Telemetry/Events/OpenWithEvent.cs +++ b/src/modules/peek/Peek.Common/Telemetry/Events/OpenWithEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace Peek.UI.Telemetry.Events { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class OpenWithEvent : EventBase, IEvent { public OpenWithEvent() diff --git a/src/modules/peek/Peek.Common/Telemetry/Events/OpenedEvent.cs b/src/modules/peek/Peek.Common/Telemetry/Events/OpenedEvent.cs index 13990ae88f..b83c8c88aa 100644 --- a/src/modules/peek/Peek.Common/Telemetry/Events/OpenedEvent.cs +++ b/src/modules/peek/Peek.Common/Telemetry/Events/OpenedEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace Peek.UI.Telemetry.Events { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class OpenedEvent : EventBase, IEvent { public OpenedEvent() diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml.cs b/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml.cs index c02582db5d..b7e2652335 100644 --- a/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml.cs +++ b/src/modules/peek/Peek.FilePreviewer/Controls/ShellPreviewHandlerControl.xaml.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; - using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI; using Microsoft.UI.Xaml; @@ -24,8 +23,8 @@ namespace Peek.FilePreviewer.Controls private static readonly COLORREF LightThemeBgColor = new(0x00f3f3f3); private static readonly COLORREF DarkThemeBgColor = new(0x00202020); - private static readonly HBRUSH LightThemeBgBrush = PInvoke.CreateSolidBrush(LightThemeBgColor); - private static readonly HBRUSH DarkThemeBgBrush = PInvoke.CreateSolidBrush(DarkThemeBgColor); + private static readonly HBRUSH LightThemeBgBrush = PInvoke_FilePreviewer.CreateSolidBrush(LightThemeBgColor); + private static readonly HBRUSH DarkThemeBgBrush = PInvoke_FilePreviewer.CreateSolidBrush(DarkThemeBgColor); [ObservableProperty] private IPreviewHandler? source; @@ -88,19 +87,19 @@ namespace Peek.FilePreviewer.Controls if (HandlerVisibility == Visibility.Visible) { - PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_SHOW); + PInvoke_FilePreviewer.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_SHOW); IsEnabled = true; // Clears the background from the last previewer // The brush can only be drawn here because flashes will occur during resize - PInvoke.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, containerBgBrush); - PInvoke.UpdateWindow(containerHwnd); - PInvoke.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, IntPtr.Zero); - PInvoke.InvalidateRect(containerHwnd, (RECT*)null, true); + PInvoke_FilePreviewer.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, containerBgBrush); + PInvoke_FilePreviewer.UpdateWindow(containerHwnd); + PInvoke_FilePreviewer.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, IntPtr.Zero); + PInvoke_FilePreviewer.InvalidateRect(containerHwnd, (RECT*)null, true); } else { - PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_HIDE); + PInvoke_FilePreviewer.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_HIDE); IsEnabled = false; } } @@ -132,14 +131,14 @@ namespace Peek.FilePreviewer.Controls visuals.SetTextColor(fgColor); // Changing the previewer colors might not always redraw itself - PInvoke.InvalidateRect(containerHwnd, (RECT*)null, true); + PInvoke_FilePreviewer.InvalidateRect(containerHwnd, (RECT*)null, true); } } private LRESULT ContainerWndProc(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam) { // Here for future use :) - return PInvoke.DefWindowProc(hWnd, msg, wParam, lParam); + return PInvoke_FilePreviewer.DefWindowProc(hWnd, msg, wParam, lParam); } private void EnsureContainerHwndCreated() @@ -157,14 +156,14 @@ namespace Peek.FilePreviewer.Controls fixed (char* pContainerClassName = "PeekShellPreviewHandlerContainer") { - PInvoke.RegisterClass(new WNDCLASSW() + PInvoke_FilePreviewer.RegisterClass(new WNDCLASSW() { lpfnWndProc = containerWndProc, lpszClassName = pContainerClassName, }); // Create the container window to host the preview handler - containerHwnd = PInvoke.CreateWindowEx( + containerHwnd = PInvoke_FilePreviewer.CreateWindowEx( WINDOW_EX_STYLE.WS_EX_LAYERED, pContainerClassName, null, @@ -178,7 +177,7 @@ namespace Peek.FilePreviewer.Controls HINSTANCE.Null); // Allows the preview handlers to display properly - PInvoke.SetLayeredWindowAttributes(containerHwnd, default, byte.MaxValue, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); + PInvoke_FilePreviewer.SetLayeredWindowAttributes(containerHwnd, default, byte.MaxValue, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); } } @@ -186,12 +185,12 @@ namespace Peek.FilePreviewer.Controls { EnsureContainerHwndCreated(); - var dpi = (float)PInvoke.GetDpiForWindow(containerHwnd) / 96; + var dpi = (float)PInvoke_FilePreviewer.GetDpiForWindow(containerHwnd) / 96; // Resize the container window - PInvoke.SetWindowPos( + PInvoke_FilePreviewer.SetWindowPos( containerHwnd, - (HWND)0, // HWND_TOP + (HWND)(nint)0, // HWND_TOP (int)(Math.Abs(args.EffectiveViewport.X) * dpi), (int)(Math.Abs(args.EffectiveViewport.Y) * dpi), (int)(ActualWidth * dpi), @@ -210,7 +209,7 @@ namespace Peek.FilePreviewer.Controls } // Resizing the previewer might not always redraw itself - PInvoke.InvalidateRect(containerHwnd, (RECT*)null, false); + PInvoke_FilePreviewer.InvalidateRect(containerHwnd, (RECT*)null, false); } private void UserControl_GotFocus(object sender, RoutedEventArgs e) diff --git a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml index c3bbe50c61..e7f6db1652 100644 --- a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml +++ b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml @@ -60,6 +60,21 @@ + + + + + + + + + + GetMissingCodecAsync(StorageFile? file) + { + try + { + var profile = await MediaEncodingProfile.CreateFromFileAsync(file); + + if (profile.Video != null) + { + var codecQuery = new CodecQuery(); + var decoders = await codecQuery.FindAllAsync( + CodecKind.Video, + CodecCategory.Decoder, + profile.Video.Subtype); + + return decoders.Count > 0 ? string.Empty : profile.Video.Subtype; + } + else + { + Logger.LogWarning($"No video profile found for file {file?.Path}. Cannot determine codec support."); + } + } + catch (Exception ex) + { + Logger.LogError($"Error checking codec support for file {file?.Path}: {ex.Message}"); + } + + return string.Empty; + } + private Task LoadVideoAsync(CancellationToken cancellationToken) { return TaskExtension.RunSafe(async () => @@ -98,10 +134,17 @@ namespace Peek.FilePreviewer.Previewers var storageFile = await Item.GetStorageItemAsync() as StorageFile; + var missingCodecName = await GetMissingCodecAsync(storageFile); + await Dispatcher.RunOnUiThread(() => { cancellationToken.ThrowIfCancellationRequested(); + if (!string.IsNullOrEmpty(missingCodecName)) + { + MissingCodecName = missingCodecName; + } + Preview = MediaSource.CreateFromStorageFile(storageFile); }); }); diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/Helpers/IStreamWrapper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/Helpers/IStreamWrapper.cs index 91783781e3..acf54e9f91 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/Helpers/IStreamWrapper.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/Helpers/IStreamWrapper.cs @@ -53,9 +53,9 @@ namespace Peek.FilePreviewer.Previewers.Helpers } } - public void Seek(long dlibMove, STREAM_SEEK dwOrigin, [Optional] ulong* plibNewPosition) + public void Seek(long dlibMove, SeekOrigin dwOrigin, [Optional] ulong* plibNewPosition) { - long position = Stream.Seek(dlibMove, (SeekOrigin)dwOrigin); + long position = Stream.Seek(dlibMove, dwOrigin); if (plibNewPosition != null) { *plibNewPosition = (ulong)position; @@ -82,7 +82,7 @@ namespace Peek.FilePreviewer.Previewers.Helpers throw new NotSupportedException(); } - public void LockRegion(ulong libOffset, ulong cb, uint dwLockType) + public void LockRegion(ulong libOffset, ulong cb, LOCKTYPE dwLockType) { throw new NotSupportedException(); } @@ -92,7 +92,7 @@ namespace Peek.FilePreviewer.Previewers.Helpers throw new NotSupportedException(); } - public void Stat(STATSTG* pstatstg, uint grfStatFlag) + public void Stat(STATSTG* pstatstg, STATFLAG grfStatFlag) { throw new NotSupportedException(); } diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/ShellPreviewHandlerPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/ShellPreviewHandlerPreviewer.cs index 248a18ec2c..608ce78f3f 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/ShellPreviewHandlerPreviewer.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/ShellPreviewHandlerPreviewer/ShellPreviewHandlerPreviewer.cs @@ -93,12 +93,12 @@ namespace Peek.FilePreviewer.Previewers // TODO: Figure out how to get it to run in a low integrity level if (!HandlerFactories.TryGetValue(clsid, out var factory)) { - var hr = PInvoke.CoGetClassObject(clsid, CLSCTX.CLSCTX_LOCAL_SERVER, null, typeof(IClassFactory).GUID, out var pFactory); + var hr = PInvoke_FilePreviewer.CoGetClassObject(clsid, CLSCTX.CLSCTX_LOCAL_SERVER, null, typeof(IClassFactory).GUID, out object pFactory); Marshal.ThrowExceptionForHR(hr); // Storing the factory in memory helps makes the handlers load faster // TODO: Maybe free them after some inactivity or when Peek quits? - factory = (IClassFactory)Marshal.GetObjectForIUnknown((IntPtr)pFactory); + factory = (IClassFactory)pFactory; factory.LockServer(true); HandlerFactories.AddOrUpdate(clsid, factory, (_, _) => factory); } @@ -149,7 +149,7 @@ namespace Peek.FilePreviewer.Previewers } else if (previewHandler is IInitializeWithItem initWithItem) { - var hr = PInvoke.SHCreateItemFromParsingName(FileItem.Path, null, typeof(IShellItem).GUID, out var item); + var hr = PInvoke_FilePreviewer.SHCreateItemFromParsingName(FileItem.Path, null, typeof(IShellItem).GUID, out var item); Marshal.ThrowExceptionForHR(hr); initWithItem.Initialize((IShellItem)item, STGM_READ); diff --git a/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs b/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs index 2a29aadb70..54df9dab21 100644 --- a/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs +++ b/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs @@ -48,21 +48,21 @@ namespace Peek.UI.Extensions internal static HWND FindChildWindow(this HWND windowHandle, string className) { - return PInvoke.FindWindowEx(windowHandle, HWND.Null, className, null); + return PInvoke_PeekUI.FindWindowEx(windowHandle, HWND.Null, className, null); } internal static Size GetMonitorSize(this HWND hwnd) { - var monitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); + var monitor = PInvoke_PeekUI.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); MONITORINFO info = default(MONITORINFO); info.cbSize = 40; - PInvoke.GetMonitorInfo(monitor, ref info); + PInvoke_PeekUI.GetMonitorInfo(monitor, ref info); return new Size(info.rcMonitor.Size.Width, info.rcMonitor.Size.Height); } internal static double GetMonitorScale(this HWND hwnd) { - var dpi = PInvoke.GetDpiForWindow(hwnd); + var dpi = PInvoke_PeekUI.GetDpiForWindow(hwnd); var scalingFactor = dpi / 96d; return scalingFactor; } diff --git a/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs b/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs index 0112a3877c..393f07c20b 100644 --- a/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs +++ b/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs @@ -28,12 +28,12 @@ namespace Peek.UI.Extensions // If the window is maximized, restore to normal state before change its size var placement = default(WINDOWPLACEMENT); - if (PInvoke.GetWindowPlacement(hwndToCenter, ref placement)) + if (PInvoke_PeekUI.GetWindowPlacement(hwndToCenter, ref placement)) { if (placement.showCmd == SHOW_WINDOW_CMD.SW_MAXIMIZE) { placement.showCmd = SHOW_WINDOW_CMD.SW_SHOWNORMAL; - if (!PInvoke.SetWindowPlacement(hwndToCenter, in placement)) + if (!PInvoke_PeekUI.SetWindowPlacement(hwndToCenter, in placement)) { Logger.LogError($"SetWindowPlacement failed with error {Marshal.GetLastWin32Error()}"); } @@ -44,12 +44,12 @@ namespace Peek.UI.Extensions Logger.LogError($"GetWindowPlacement failed with error {Marshal.GetLastWin32Error()}"); } - var monitor = PInvoke.MonitorFromWindow(hwndDesktop, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); + var monitor = PInvoke_PeekUI.MonitorFromWindow(hwndDesktop, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); MONITORINFO info = default(MONITORINFO); info.cbSize = 40; - PInvoke.GetMonitorInfo(monitor, ref info); - var dpi = PInvoke.GetDpiForWindow(new HWND(hwndDesktop)); - PInvoke.GetWindowRect(hwndToCenter, out RECT windowRect); + PInvoke_PeekUI.GetMonitorInfo(monitor, ref info); + var dpi = PInvoke_PeekUI.GetDpiForWindow(new HWND((nint)hwndDesktop)); + PInvoke_PeekUI.GetWindowRect(hwndToCenter, out RECT windowRect); var scalingFactor = dpi / 96d; var w = width.HasValue ? (int)(width * scalingFactor) : windowRect.right - windowRect.left; var h = height.HasValue ? (int)(height * scalingFactor) : windowRect.bottom - windowRect.top; @@ -63,7 +63,7 @@ namespace Peek.UI.Extensions private static void SetWindowPosOrThrow(HWND hWnd, HWND hWndInsertAfter, int x, int y, int cx, int cy, SET_WINDOW_POS_FLAGS uFlags) { - bool result = PInvoke.SetWindowPos(hWnd, hWndInsertAfter, x, y, cx, cy, uFlags); + bool result = PInvoke_PeekUI.SetWindowPos(hWnd, hWndInsertAfter, x, y, cx, cy, uFlags); if (!result) { Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); diff --git a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs index 466ae85028..fb57e80047 100644 --- a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs +++ b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs @@ -58,7 +58,7 @@ namespace Peek.UI.Helpers object? oNull2 = null; var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref oNull1, ref oNull2, SWC_DESKTOP, out int pHWND, SWFO_NEEDDISPATCH); - var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke.SID_STopLevelBrowser, typeof(IShellBrowser).GUID); + var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke_PeekUI.SID_STopLevelBrowser, typeof(IShellBrowser).GUID); IShellItemArray? shellItemArray = GetShellItemArray(shellBrowser, onlySelectedFiles); return shellItemArray; @@ -81,7 +81,7 @@ namespace Peek.UI.Helpers if (webBrowserApp.HWND == foregroundWindowHandle) { var serviceProvider = (IServiceProvider)webBrowserApp; - var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke.SID_STopLevelBrowser, typeof(IShellBrowser).GUID); + var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke_PeekUI.SID_STopLevelBrowser, typeof(IShellBrowser).GUID); shellBrowser.GetWindow(out IntPtr shellBrowserHandle); if (activeTab == shellBrowserHandle) @@ -122,7 +122,7 @@ namespace Peek.UI.Helpers GUITHREADINFO guiThreadInfo = new() { cbSize = (uint)Marshal.SizeOf() }; // Get information for the foreground thread - if (PInvoke.GetGUIThreadInfo(0, ref guiThreadInfo)) + if (PInvoke_PeekUI.GetGUIThreadInfo(0, ref guiThreadInfo)) { return guiThreadInfo.hwndActive == hwnd && (guiThreadInfo.flags & GUITHREADINFO_FLAGS.GUI_CARETBLINKING) != 0; } diff --git a/src/modules/peek/Peek.UI/NativeMethods.json b/src/modules/peek/Peek.UI/NativeMethods.json new file mode 100644 index 0000000000..ff8e941bec --- /dev/null +++ b/src/modules/peek/Peek.UI/NativeMethods.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "public": false, + "className": "PInvoke_PeekUI" +} \ No newline at end of file diff --git a/src/modules/peek/Peek.UI/Peek.UI.csproj b/src/modules/peek/Peek.UI/Peek.UI.csproj index 673ff5c9e7..070d9d2ef2 100644 --- a/src/modules/peek/Peek.UI/Peek.UI.csproj +++ b/src/modules/peek/Peek.UI/Peek.UI.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs index a433b00e43..9b753b3224 100644 --- a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs +++ b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs @@ -128,7 +128,7 @@ namespace Peek.UI { // Need to read the foreground HWND before activating Peek to avoid focus stealing // Foreground HWND must always be Explorer or Desktop - var foregroundWindowHandle = Windows.Win32.PInvoke.GetForegroundWindow(); + var foregroundWindowHandle = Windows.Win32.PInvoke_PeekUI.GetForegroundWindow(); bool firstActivation = false; diff --git a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs index 03e48c5ceb..3ce9a82bf9 100644 --- a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs +++ b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs @@ -213,7 +213,7 @@ namespace Peek.UI /// PreviewSizeChangedArgs private void FilePreviewer_PreviewSizeChanged(object sender, PreviewSizeChangedArgs e) { - var foregroundWindowHandle = Windows.Win32.PInvoke.GetForegroundWindow(); + var foregroundWindowHandle = Windows.Win32.PInvoke_PeekUI.GetForegroundWindow(); var monitorSize = foregroundWindowHandle.GetMonitorSize(); var monitorScale = foregroundWindowHandle.GetMonitorScale(); diff --git a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw index 4ffec5f685..f45f961268 100644 --- a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw +++ b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw @@ -372,4 +372,10 @@ Don't show this warning again + + Your device doesn't support the {0} format. To play this file, install a codec that supports this format. + + + Search in Microsoft Store + \ No newline at end of file diff --git a/src/modules/peek/Peek.UI/app.manifest b/src/modules/peek/Peek.UI/app.manifest index 122bedce24..e45d3c1982 100644 --- a/src/modules/peek/Peek.UI/app.manifest +++ b/src/modules/peek/Peek.UI/app.manifest @@ -19,7 +19,7 @@ 2) System < Windows 10 Anniversary Update --> true/PM - PerMonitorV2, PerMonitor + PerMonitorV2 true diff --git a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs index 5d32f1d565..c2f0698f25 100644 --- a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs +++ b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs @@ -62,11 +62,11 @@ public partial class PowerAccent : IDisposable private void SetEvents() { - _keyboardListener.SetShowToolbarEvent(new PowerToys.PowerAccentKeyboardService.ShowToolbar((LetterKey letterKey, TriggerKey trigger ) => + _keyboardListener.SetShowToolbarEvent(new PowerToys.PowerAccentKeyboardService.ShowToolbar((LetterKey letterKey) => { System.Windows.Application.Current.Dispatcher.Invoke(() => { - ShowToolbar(letterKey, trigger); + ShowToolbar(letterKey); }); })); @@ -92,15 +92,23 @@ public partial class PowerAccent : IDisposable })); } - private void ShowToolbar(LetterKey letterKey, TriggerKey trigger) + private void ShowToolbar(LetterKey letterKey) { _visible = true; _characters = GetCharacters(letterKey); _characterDescriptions = GetCharacterDescriptions(_characters); _showUnicodeDescription = _settingService.ShowUnicodeDescription; - OnChangeDisplay?.Invoke(true, _characters); - ProcessNextChar(trigger, false); + + Task.Delay(_settingService.InputTime).ContinueWith( + t => + { + if (_visible) + { + OnChangeDisplay?.Invoke(true, _characters); + } + }, + TaskScheduler.FromCurrentSynchronizationContext()); } private string[] GetCharacters(LetterKey letterKey) diff --git a/src/modules/poweraccent/PowerAccent.Core/Telemetry/PowerAccentShowAccentMenuEvent.cs b/src/modules/poweraccent/PowerAccent.Core/Telemetry/PowerAccentShowAccentMenuEvent.cs index 99c6fcdb8b..2fe32e293c 100644 --- a/src/modules/poweraccent/PowerAccent.Core/Telemetry/PowerAccentShowAccentMenuEvent.cs +++ b/src/modules/poweraccent/PowerAccent.Core/Telemetry/PowerAccentShowAccentMenuEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace PowerAccent.Core.Telemetry { [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class PowerAccentShowAccentMenuEvent : EventBase, IEvent { public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; diff --git a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj index 291a2a8c17..217b437e95 100644 --- a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj +++ b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj @@ -9,6 +9,7 @@ true True icon.ico + app.manifest PowerToys.PowerAccent True PowerAccent.UI.Program diff --git a/src/modules/poweraccent/PowerAccent.UI/app.manifest b/src/modules/poweraccent/PowerAccent.UI/app.manifest new file mode 100644 index 0000000000..4747d3bd23 --- /dev/null +++ b/src/modules/poweraccent/PowerAccent.UI/app.manifest @@ -0,0 +1,9 @@ + + + + + true/PM + PerMonitorV2 + + + \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.cpp b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.cpp index 3d38d20b91..93fb5c0230 100644 --- a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.cpp +++ b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.cpp @@ -13,7 +13,7 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation { KeyboardListener::KeyboardListener() : - m_toolbarVisible(false), m_activationKeyHold(false), m_triggeredWithSpace(false), m_leftShiftPressed(false), m_rightShiftPressed(false), m_triggeredWithLeftArrow(false), m_triggeredWithRightArrow(false) + m_toolbarVisible(false), m_triggeredWithSpace(false), m_leftShiftPressed(false), m_rightShiftPressed(false), m_triggeredWithLeftArrow(false), m_triggeredWithRightArrow(false) { s_instance = this; LoggerHelpers::init_logger(L"PowerAccent", L"PowerAccentKeyboardService", "PowerAccent"); @@ -53,8 +53,8 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation void KeyboardListener::SetShowToolbarEvent(ShowToolbar showToolbarEvent) { - m_showToolbarCb = [trigger = std::move(showToolbarEvent)](LetterKey key, TriggerKey triggerKey) { - trigger(key, triggerKey); + m_showToolbarCb = [trigger = std::move(showToolbarEvent)](LetterKey key) { + trigger(key); }; } @@ -152,17 +152,6 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation return false; } - void KeyboardListener::BeginShowToolbar(std::chrono::milliseconds delay, LetterKey key, TriggerKey trigger) - { - std::unique_lock lock(toolbarMutex); - auto result = toolbarCV.wait_for(lock, delay); - if (result == std::cv_status::timeout) - { - m_toolbarVisible = true; - m_showToolbarCb(key, trigger); - } - } - bool KeyboardListener::OnKeyDown(KBDLLHOOKSTRUCT info) noexcept { auto letterKey = static_cast(info.vkCode); @@ -210,7 +199,7 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation } } - if (!m_toolbarVisible && !m_activationKeyHold && letterPressed != LetterKey::None && triggerPressed && !IsSuppressedByGameMode() && !IsForegroundAppExcluded()) + if (!m_toolbarVisible && letterPressed != LetterKey::None && triggerPressed && !IsSuppressedByGameMode() && !IsForegroundAppExcluded()) { Logger::debug(L"Show toolbar. Letter: {}, Trigger: {}", letterPressed, triggerPressed); @@ -218,21 +207,11 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation m_triggeredWithSpace = triggerPressed == VK_SPACE; m_triggeredWithLeftArrow = triggerPressed == VK_LEFT; m_triggeredWithRightArrow = triggerPressed == VK_RIGHT; - m_activationKeyHold = true; - m_bothKeysPressed = true; - if (toolbarThread != nullptr) - { - toolbarCV.notify_all(); - toolbarThread->join(); - } - toolbarThread = std::make_unique(std::bind(&KeyboardListener::BeginShowToolbar, this, m_settings.inputTime, letterPressed,static_cast(triggerPressed))); + m_toolbarVisible = true; + m_showToolbarCb(letterPressed); } - if (m_activationKeyHold && triggerPressed && !m_toolbarVisible) - { - return true; - } - else if (m_toolbarVisible && triggerPressed) + if (m_toolbarVisible && triggerPressed) { if (triggerPressed == VK_LEFT) { @@ -272,9 +251,8 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation { letterPressed = LetterKey::None; - if (m_toolbarVisible || m_bothKeysPressed) + if (m_toolbarVisible) { - m_bothKeysPressed = false; if (m_stopwatch.elapsed() < m_settings.inputTime) { Logger::debug(L"Activation too fast. Do nothing."); @@ -302,18 +280,11 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation Logger::debug(L"Hide toolbar event and input char"); m_hideToolbarCb(InputType::Char); + m_toolbarVisible = false; } } - auto triggerPressed = info.vkCode; - - if (m_activationKeyHold && (letterPressed == LetterKey::None || (triggerPressed == VK_SPACE || triggerPressed == VK_LEFT || triggerPressed == VK_RIGHT))) - { - m_activationKeyHold = false; - toolbarCV.notify_all(); - } - return false; } diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.h b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.h index 6ec1118775..61c28e1866 100644 --- a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.h +++ b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.h @@ -2,7 +2,6 @@ #include "KeyboardListener.g.h" #include -#include #include namespace winrt::PowerToys::PowerAccentKeyboardService::implementation @@ -45,7 +44,6 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); private: - void BeginShowToolbar(std::chrono::milliseconds delay, LetterKey key, TriggerKey trigger); bool OnKeyDown(KBDLLHOOKSTRUCT info) noexcept; bool OnKeyUp(KBDLLHOOKSTRUCT info) noexcept; bool IsSuppressedByGameMode(); @@ -53,14 +51,9 @@ namespace winrt::PowerToys::PowerAccentKeyboardService::implementation static inline KeyboardListener* s_instance; HHOOK s_llKeyboardHook = nullptr; - std::atomic m_toolbarVisible; - bool m_activationKeyHold; - bool m_bothKeysPressed = false; - std::unique_ptr toolbarThread; - std::mutex toolbarMutex; - std::condition_variable toolbarCV; + bool m_toolbarVisible; PowerAccentSettings m_settings; - std::function m_showToolbarCb; + std::function m_showToolbarCb; std::function m_hideToolbarCb; std::function m_nextCharCb; std::function m_isLanguageLetterCb; diff --git a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.idl b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.idl index 9fc0f42e42..9bc8448c22 100644 --- a/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.idl +++ b/src/modules/poweraccent/PowerAccentKeyboardService/KeyboardListener.idl @@ -67,7 +67,7 @@ namespace PowerToys Char }; - [version(1.0), uuid(37197089-5438-4479-af57-30ab3f3c8be4)] delegate void ShowToolbar(LetterKey key, TriggerKey trigger); + [version(1.0), uuid(37197089-5438-4479-af57-30ab3f3c8be4)] delegate void ShowToolbar(LetterKey key); [version(1.0), uuid(8eb79d6b-1826-424f-9fbc-af21ae19725e)] delegate void HideToolbar(InputType inputType); [version(1.0), uuid(db72d45c-a5a2-446f-bdc1-506e9121764a)] delegate void NextChar(TriggerKey inputSpace, boolean shiftPressed); [version(1.0), uuid(20be2919-2b91-4313-b6e0-4c3484fe91ef)] delegate void IsLanguageLetter(LetterKey key, [out] boolean* result); diff --git a/src/modules/powerrename/PowerRename.FuzzingTest/OneFuzzConfig.json b/src/modules/powerrename/PowerRename.FuzzingTest/OneFuzzConfig.json index 41ceb9414a..502c128c19 100644 --- a/src/modules/powerrename/PowerRename.FuzzingTest/OneFuzzConfig.json +++ b/src/modules/powerrename/PowerRename.FuzzingTest/OneFuzzConfig.json @@ -11,8 +11,8 @@ // project, where bugs will be filed "org": "microsoft", "project": "OS", - "AssignedTo": "leilzh@microsoft.com", - "AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\DIVE\\SALT", + "AssignedTo": "PowerToys@microsoft.com", + "AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT", "IterationPath": "OS\\Future" }, "jobNotificationEmail": "PowerToys@microsoft.com", diff --git a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj index 6dc9ddcdb2..a40c363194 100644 --- a/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj +++ b/src/modules/powerrename/PowerRename.FuzzingTest/PowerRename.FuzzingTest.vcxproj @@ -6,7 +6,7 @@ Win32Proj {2694e2fb-dcd5-4bff-a418-b6c3c7ce3b8e} Test - 10.0.22621.0 + 10.0.26100.0 PowerRename.FuzzingTest @@ -72,7 +72,6 @@ Console - $(OutDir)\..\..\WinUI3Apps\PowerRenameLib.lib;comctl32.lib;pathcch.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;Pathcch.lib;%(AdditionalDependencies) @@ -85,6 +84,9 @@ {51920f1f-c28c-4adf-8660-4238766796c2} + + {51920f1f-c28c-4adf-8660-4238766796c2} + {6955446d-23f7-4023-9bb3-8657f904af99} diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj index 806da3904b..9587b0dac0 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj @@ -1,7 +1,7 @@  - - + + true @@ -205,9 +205,9 @@ - + - + @@ -218,11 +218,11 @@ - - + + - - + + diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml index d049a0d067..7126a63604 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml @@ -184,6 +184,7 @@ + + + + + + + + + + + + Random string features + + Time used for replacement + + + Creation Time + + + Modification Time + + + Access Time + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUILib/app.manifest b/src/modules/powerrename/PowerRenameUILib/app.manifest index 8554f867e0..a5c3bbd209 100644 --- a/src/modules/powerrename/PowerRenameUILib/app.manifest +++ b/src/modules/powerrename/PowerRenameUILib/app.manifest @@ -9,7 +9,7 @@ 2) System < Windows 10 Anniversary Update --> true/PM - PerMonitorV2, PerMonitor + PerMonitorV2 diff --git a/src/modules/powerrename/PowerRenameUILib/packages.config b/src/modules/powerrename/PowerRenameUILib/packages.config index 77b75fad7e..4a91705aea 100644 --- a/src/modules/powerrename/PowerRenameUILib/packages.config +++ b/src/modules/powerrename/PowerRenameUILib/packages.config @@ -5,6 +5,6 @@ - - + + \ No newline at end of file diff --git a/src/modules/powerrename/lib/PowerRenameInterfaces.h b/src/modules/powerrename/lib/PowerRenameInterfaces.h index c545e9dc00..ea761d156a 100644 --- a/src/modules/powerrename/lib/PowerRenameInterfaces.h +++ b/src/modules/powerrename/lib/PowerRenameInterfaces.h @@ -18,7 +18,10 @@ enum PowerRenameFlags Lowercase = 0x400, Titlecase = 0x800, Capitalized = 0x1000, - RandomizeItems = 0x2000 + RandomizeItems = 0x2000, + CreationTime = 0x4000, + ModificationTime = 0x8000, + AccessTime = 0x10000, }; enum PowerRenameFilters @@ -67,7 +70,7 @@ interface __declspec(uuid("C7F59201-4DE1-4855-A3A2-26FC3279C8A5")) IPowerRenameI public: IFACEMETHOD(PutPath)(_In_opt_ PCWSTR newPath) = 0; IFACEMETHOD(GetPath)(_Outptr_ PWSTR * path) = 0; - IFACEMETHOD(GetTime)(_Outptr_ SYSTEMTIME* time) = 0; + IFACEMETHOD(GetTime)(_In_ DWORD flags, _Outptr_ SYSTEMTIME * time) = 0; IFACEMETHOD(GetShellItem)(_Outptr_ IShellItem** ppsi) = 0; IFACEMETHOD(GetOriginalName)(_Outptr_ PWSTR * originalName) = 0; IFACEMETHOD(PutOriginalName)(_In_opt_ PCWSTR originalName) = 0; diff --git a/src/modules/powerrename/lib/PowerRenameItem.cpp b/src/modules/powerrename/lib/PowerRenameItem.cpp index bd8fa48285..9a0652c451 100644 --- a/src/modules/powerrename/lib/PowerRenameItem.cpp +++ b/src/modules/powerrename/lib/PowerRenameItem.cpp @@ -56,12 +56,28 @@ IFACEMETHODIMP CPowerRenameItem::GetPath(_Outptr_ PWSTR* path) return hr; } -IFACEMETHODIMP CPowerRenameItem::GetTime(_Outptr_ SYSTEMTIME* time) +IFACEMETHODIMP CPowerRenameItem::GetTime(_In_ DWORD flags, _Outptr_ SYSTEMTIME* time) { CSRWSharedAutoLock lock(&m_lock); HRESULT hr = E_FAIL; + PowerRenameFlags parsedTimeType; - if (m_isTimeParsed) + // Get Time by PowerRenameFlags + if (flags & PowerRenameFlags::ModificationTime) + { + parsedTimeType = PowerRenameFlags::ModificationTime; + } + else if (flags & PowerRenameFlags::AccessTime) + { + parsedTimeType = PowerRenameFlags::AccessTime; + } + else + { + // Default to modification time if no specific flag is set + parsedTimeType = PowerRenameFlags::CreationTime; + } + + if (m_isTimeParsed && parsedTimeType == m_parsedTimeType) { hr = S_OK; } @@ -70,21 +86,43 @@ IFACEMETHODIMP CPowerRenameItem::GetTime(_Outptr_ SYSTEMTIME* time) HANDLE hFile = CreateFileW(m_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFile != INVALID_HANDLE_VALUE) { - FILETIME CreationTime; - if (GetFileTime(hFile, &CreationTime, NULL, NULL)) + FILETIME FileTime; + bool success = false; + + // Get Time by PowerRenameFlags + switch (parsedTimeType) + { + case PowerRenameFlags::CreationTime: + success = GetFileTime(hFile, &FileTime, NULL, NULL); + break; + case PowerRenameFlags::ModificationTime: + success = GetFileTime(hFile, NULL, NULL, &FileTime); + break; + case PowerRenameFlags::AccessTime: + success = GetFileTime(hFile, NULL, &FileTime, NULL); + break; + default: + // Default to modification time if no specific flag is set + success = GetFileTime(hFile, NULL, NULL, &FileTime); + break; + } + + if (success) { SYSTEMTIME SystemTime, LocalTime; - if (FileTimeToSystemTime(&CreationTime, &SystemTime)) + if (FileTimeToSystemTime(&FileTime, &SystemTime)) { if (SystemTimeToTzSpecificLocalTime(NULL, &SystemTime, &LocalTime)) { m_time = LocalTime; m_isTimeParsed = true; + m_parsedTimeType = parsedTimeType; hr = S_OK; } } } } + CloseHandle(hFile); } *time = m_time; diff --git a/src/modules/powerrename/lib/PowerRenameItem.h b/src/modules/powerrename/lib/PowerRenameItem.h index 83817b0699..6ced0a3ada 100644 --- a/src/modules/powerrename/lib/PowerRenameItem.h +++ b/src/modules/powerrename/lib/PowerRenameItem.h @@ -16,7 +16,7 @@ public: // IPowerRenameItem IFACEMETHODIMP PutPath(_In_opt_ PCWSTR newPath); IFACEMETHODIMP GetPath(_Outptr_ PWSTR* path); - IFACEMETHODIMP GetTime(_Outptr_ SYSTEMTIME* time); + IFACEMETHODIMP GetTime(_In_ DWORD flags, _Outptr_ SYSTEMTIME* time); IFACEMETHODIMP GetShellItem(_Outptr_ IShellItem** ppsi); IFACEMETHODIMP PutOriginalName(_In_opt_ PCWSTR originalName); IFACEMETHODIMP GetOriginalName(_Outptr_ PWSTR* originalName); @@ -54,6 +54,7 @@ protected: bool m_selected = true; bool m_isFolder = false; bool m_isTimeParsed = false; + PowerRenameFlags m_parsedTimeType = PowerRenameFlags::CreationTime; bool m_canRename = true; int m_id = -1; int m_iconIndex = -1; diff --git a/src/modules/powerrename/lib/Renaming.cpp b/src/modules/powerrename/lib/Renaming.cpp index bf27500529..aa21666783 100644 --- a/src/modules/powerrename/lib/Renaming.cpp +++ b/src/modules/powerrename/lib/Renaming.cpp @@ -78,7 +78,7 @@ bool DoRename(CComPtr& spRenameRegEx, unsigned long& itemEnum if (useFileTime) { - winrt::check_hresult(spItem->GetTime(&fileTime)); + winrt::check_hresult(spItem->GetTime(flags, &fileTime)); winrt::check_hresult(spRenameRegEx->PutFileTime(fileTime)); } diff --git a/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFileHandlerLoaded.cs b/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFileHandlerLoaded.cs index c1d1fba40a..81d0ab352e 100644 --- a/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFileHandlerLoaded.cs +++ b/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFileHandlerLoaded.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events /// A telemetry event to be raised when a svg file has been viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class GcodeFileHandlerLoaded : EventBase, IEvent { /// diff --git a/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewError.cs b/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewError.cs index 9e9a232e04..64e81ebcbd 100644 --- a/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewError.cs +++ b/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewError.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events /// A telemetry event to be raised when an error has occurred in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class GcodeFilePreviewError : EventBase, IEvent { /// diff --git a/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewed.cs b/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewed.cs index 693e314e40..69bb66e5b4 100644 --- a/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewed.cs +++ b/src/modules/previewpane/GcodePreviewHandler/Telemetry/Events/GcodeFilePreviewed.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events /// A telemetry event to be raised when a svg file has been viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class GcodeFilePreviewed : EventBase, IEvent { /// diff --git a/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFileHandlerLoaded.cs b/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFileHandlerLoaded.cs index dec9852dec..36139dbaea 100644 --- a/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFileHandlerLoaded.cs +++ b/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFileHandlerLoaded.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown.Telemetry.Events /// A telemetry event that is triggered when a markdown file is viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class MarkdownFileHandlerLoaded : EventBase, IEvent { /// diff --git a/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewError.cs b/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewError.cs index 95268b1619..d665c1b0f6 100644 --- a/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewError.cs +++ b/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewError.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -10,6 +11,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown.Telemetry.Events /// /// A telemetry event that is triggered when an error occurs while attempting to view a markdown file in the preview pane. /// + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class MarkdownFilePreviewError : EventBase, IEvent { /// diff --git a/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewed.cs b/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewed.cs index e38d2d38ca..f8b869a300 100644 --- a/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewed.cs +++ b/src/modules/previewpane/MarkdownPreviewHandler/Telemetry/Events/MarkdownFilePreviewed.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown.Telemetry.Events /// A telemetry event that is triggered when a markdown file is viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class MarkdownFilePreviewed : EventBase, IEvent { /// diff --git a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs index 68dde66e3c..c1c72d61aa 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs +++ b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs @@ -389,7 +389,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco } fileReader.Close(); - _base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent)); + _base64FileCode = Convert.ToBase64String(encodingToUse.GetBytes(fileContent)); Logger.LogInfo("Reading requested file ended"); } diff --git a/src/modules/previewpane/PdfPreviewHandler/PdfPreviewHandlerControl.cs b/src/modules/previewpane/PdfPreviewHandler/PdfPreviewHandlerControl.cs index 4068567985..9de84327ef 100644 --- a/src/modules/previewpane/PdfPreviewHandler/PdfPreviewHandlerControl.cs +++ b/src/modules/previewpane/PdfPreviewHandler/PdfPreviewHandlerControl.cs @@ -228,6 +228,8 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf DestinationWidth = (uint)this.ClientSize.Width, }).GetAwaiter().GetResult(); + stream.Seek(0); // Reset the stream position to the beginning before reading. + imageOfPage = Image.FromStream(stream.AsStream()); } diff --git a/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFileHandlerLoaded.cs b/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFileHandlerLoaded.cs index fc2ac2881b..0748b2cbb6 100644 --- a/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFileHandlerLoaded.cs +++ b/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFileHandlerLoaded.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf.Telemetry.Events /// A telemetry event that is triggered when a pdf file is viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class PdfFileHandlerLoaded : EventBase, IEvent { /// diff --git a/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewError.cs b/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewError.cs index 73dec91265..77917c8cff 100644 --- a/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewError.cs +++ b/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewError.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -10,6 +11,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf.Telemetry.Events /// /// A telemetry event that is triggered when an error occurs while attempting to view a markdown file in the preview pane. /// + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class PdfFilePreviewError : EventBase, IEvent { /// diff --git a/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewed.cs b/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewed.cs index 0a223a24b2..a5f4f1bd35 100644 --- a/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewed.cs +++ b/src/modules/previewpane/PdfPreviewHandler/Telemetry/Events/PdfFilePreviewed.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf.Telemetry.Events /// A telemetry event that is triggered when a markdown file is viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class PdfFilePreviewed : EventBase, IEvent { /// diff --git a/src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.cs b/src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.cs index e82607b299..e6b40f0ea8 100644 --- a/src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.cs +++ b/src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.cs @@ -1,6 +1,7 @@ -// Copyright (c) Microsoft Corporation +// 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.Drawing; using Windows.Data.Pdf; using Windows.Storage; using Windows.Storage.Streams; @@ -95,6 +96,8 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Pdf DestinationHeight = height, }).GetAwaiter().GetResult(); + stream.Seek(0); + imageOfPage = Image.FromStream(stream.AsStream()); return imageOfPage; diff --git a/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs index cdc4516fd9..6a3f4629e5 100644 --- a/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs +++ b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events /// A telemetry event to be raised when an error has occurred in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class QoiFilePreviewError : EventBase, IEvent { /// diff --git a/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs index 273ec8caf0..bba4b7972c 100644 --- a/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs +++ b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events /// A telemetry event to be raised when a Qoi file has been viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class QoiFilePreviewed : EventBase, IEvent { /// diff --git a/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFileHandlerLoaded.cs b/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFileHandlerLoaded.cs index aaea992ba9..bda66bceb5 100644 --- a/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFileHandlerLoaded.cs +++ b/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFileHandlerLoaded.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg.Telemetry.Events /// A telemetry event to be raised when a svg file has been viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class SvgFileHandlerLoaded : EventBase, IEvent { /// diff --git a/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewError.cs b/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewError.cs index daed435502..526a75af3e 100644 --- a/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewError.cs +++ b/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewError.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg.Telemetry.Events /// A telemetry event to be raised when an error has occurred in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class SvgFilePreviewError : EventBase, IEvent { /// diff --git a/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewed.cs b/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewed.cs index 5348bcd466..6e1e16a328 100644 --- a/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewed.cs +++ b/src/modules/previewpane/SvgPreviewHandler/Telemetry/Events/SvgFilePreviewed.cs @@ -2,8 +2,8 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg.Telemetry.Events /// A telemetry event to be raised when a svg file has been viewed in the preview pane. /// [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class SvgFilePreviewed : EventBase, IEvent { /// diff --git a/src/modules/registrypreview/RegistryPreview.FuzzTests/OneFuzzConfig.json b/src/modules/registrypreview/RegistryPreview.FuzzTests/OneFuzzConfig.json index db58123701..deaf72e946 100644 --- a/src/modules/registrypreview/RegistryPreview.FuzzTests/OneFuzzConfig.json +++ b/src/modules/registrypreview/RegistryPreview.FuzzTests/OneFuzzConfig.json @@ -19,11 +19,11 @@ // project, where bugs will be filed "org": "microsoft", "project": "OS", - "AssignedTo": "mengyuanchen@microsoft.com", - "AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\DIVE\\SALT", + "AssignedTo": "PowerToys@microsoft.com", + "AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT", "IterationPath": "OS\\Future" }, - "jobNotificationEmail": "mengyuanchen@microsoft.com", + "jobNotificationEmail": "PowerToys@microsoft.com", "skip": false, "rebootAfterSetup": false, "oneFuzzJobs": [ @@ -60,11 +60,11 @@ // project, where bugs will be filed "org": "microsoft", "project": "OS", - "AssignedTo": "mengyuanchen@microsoft.com", - "AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\DIVE\\SALT", + "AssignedTo": "PowerToys@microsoft.com", + "AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT", "IterationPath": "OS\\Future" }, - "jobNotificationEmail": "mengyuanchen@microsoft.com", + "jobNotificationEmail": "PowerToys@microsoft.com", "skip": false, "rebootAfterSetup": false, "oneFuzzJobs": [ diff --git a/src/modules/registrypreview/RegistryPreview/RegistryPreview.csproj b/src/modules/registrypreview/RegistryPreview/RegistryPreview.csproj index 36bd1f4f4d..8ca723808f 100644 --- a/src/modules/registrypreview/RegistryPreview/RegistryPreview.csproj +++ b/src/modules/registrypreview/RegistryPreview/RegistryPreview.csproj @@ -31,7 +31,7 @@ - + diff --git a/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml b/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml index 7c2836890c..400d0c71fd 100644 --- a/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml +++ b/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml @@ -10,7 +10,31 @@ - + + + + + + + + + 1 + 1,1,1,2 + + + + + + + + + + + + 1 + 2 + + diff --git a/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartEvent.cs b/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartEvent.cs index 6ac5fb51a8..aa511bcd12 100644 --- a/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartEvent.cs +++ b/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace RegistryPreview.Telemetry; [EventData] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class RegistryPreviewEditorStartEvent() : EventBase, IEvent { public long TimeStamp { get; set; } diff --git a/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartFinishEvent.cs b/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartFinishEvent.cs index 98067f26ce..6a5edc5343 100644 --- a/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartFinishEvent.cs +++ b/src/modules/registrypreview/RegistryPreview/Telemetry/RegistryPreviewEditorStartFinishEvent.cs @@ -2,14 +2,15 @@ // 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.Diagnostics.Tracing; - using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; namespace RegistryPreview.Telemetry; [EventData] +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class RegistryPreviewEditorStartFinishEvent() : EventBase, IEvent { public long TimeStamp { get; set; } diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/AddressFormat.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/AddressFormat.cs new file mode 100644 index 0000000000..8219ed8b51 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/AddressFormat.cs @@ -0,0 +1,43 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox +{ + /// + /// Enumerates the address column formatting options. + /// + public enum AddressFormat + { + /// + /// 16 bit HEX address "0000". + /// + Address16, + + /// + /// 24 bit HEX address "00:0000". + /// + Address24, + + /// + /// 32 bit HEX address "0000:0000". + /// + Address32, + + /// + /// 48 bit HEX address "0000:00000000". + /// + Address48, + + /// + /// 64 bit HEX address "00000000:00000000". + /// + Address64, + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/CanvasCommands.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/CanvasCommands.cs new file mode 100644 index 0000000000..dca1f95725 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/CanvasCommands.cs @@ -0,0 +1,43 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// +using System; +using System.Windows.Input; + +namespace RegistryPreviewUILib.HexBox +{ + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute; + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) + { + return _canExecute == null || _canExecute(parameter); + } + + public void Execute(object parameter) + { + _execute(parameter); + } + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataFormat.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataFormat.cs new file mode 100644 index 0000000000..31929f8b88 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataFormat.cs @@ -0,0 +1,28 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox +{ + /// + /// Enumerates the format to display integral data in. + /// + public enum DataFormat + { + /// + /// Display the data in decimal format. + /// + Decimal, + + /// + /// Display the data in hexadecimal format. + /// + Hexadecimal, + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataSignedness.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataSignedness.cs new file mode 100644 index 0000000000..4f5d95bc93 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataSignedness.cs @@ -0,0 +1,28 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox +{ + /// + /// Enumerates the signedness of the data to display. + /// + public enum DataSignedness + { + /// + /// Display the data as signed values. + /// + Signed, + + /// + /// Display the data as unsigned values. + /// + Unsigned, + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataType.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataType.cs new file mode 100644 index 0000000000..c9619bfc6b --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/DataType.cs @@ -0,0 +1,43 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox +{ + /// + /// Enumerates how the data (bytes read from the buffer) is to be interpreted when displayed. + /// + public enum DataType + { + /// + /// Display the data as integral (integer) values. + /// + Int_1 = 1, + /// + /// Display the data as integral (integer) values. + /// + Int_2 = 2, + /// + /// Display the data as integral (integer) values. + /// + Int_4 = 4, + /// + /// Display the data as integral (integer) values. + /// + Int_8 = 8, + /// + /// Display the data as floating point values. + /// + Float_32 = 32, + /// + /// Display the data as floating point values. + /// + Float_64 = 64, + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/HexBox.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/HexBox.cs new file mode 100644 index 0000000000..46df5a60ba --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/HexBox.cs @@ -0,0 +1,2913 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// +#pragma warning disable SA1210 // Using directives should be ordered alphabetically by namespace +#pragma warning disable SA1208 // System using directives should be placed before other using directives +using RegistryPreviewUILib.HexBox.Library.EndianConvert; +using Microsoft.UI; +using Microsoft.UI.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using SkiaSharp; +using SkiaSharp.Views.Windows; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Windows.Input; +using Windows.ApplicationModel.DataTransfer; +using Windows.Foundation; +using Windows.System; +using Windows.UI.Core; +#pragma warning restore SA1208 // System using directives should be placed before other using directives +#pragma warning restore SA1210 // Using directives should be ordered alphabetically by namespace + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace RegistryPreviewUILib.HexBox +{ + [TemplatePart(Name = "ElementCanvas", Type = typeof(SKXamlCanvas))] + [TemplatePart(Name = "ElementScrollBar", Type = typeof(ScrollBar))] + public sealed class HexBox : Control, INotifyPropertyChanged + { + /// + /// Defines the address at which the data in the begins. + /// + public static readonly DependencyProperty AddressProperty = + DependencyProperty.Register(nameof(Address), typeof(ulong), typeof(HexBox), + new PropertyMetadata(0UL, OnAddressChanged)); + + /// + /// Defines the brush used to display the addresses in the address section of the control. + /// + public static readonly DependencyProperty AddressBrushProperty = + DependencyProperty.Register(nameof(AddressBrush), typeof(SolidColorBrush), typeof(HexBox), + new PropertyMetadata(new SolidColorBrush(Colors.CornflowerBlue), OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the width of the addresses displayed in the address section of the control. + /// + public static readonly DependencyProperty AddressFormatProperty = + DependencyProperty.Register(nameof(AddressFormat), typeof(AddressFormat), typeof(HexBox), + new PropertyMetadata(AddressFormat.Address32, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the brush used for alternating for text in alternating (odd numbered) columns in the data section of the control. + /// + public static readonly DependencyProperty AlternatingDataColumnTextBrushProperty = + DependencyProperty.Register(nameof(AlternatingDataColumnTextBrush), typeof(SolidColorBrush), typeof(HexBox), + new PropertyMetadata(new SolidColorBrush(Colors.Gray), OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the number of columns to display. + /// + public static readonly DependencyProperty ColumnsProperty = + DependencyProperty.Register(nameof(Columns), typeof(int), typeof(HexBox), + new PropertyMetadata(16, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the endianness used to interpret the data. + /// + public static readonly DependencyProperty EndiannessProperty = + DependencyProperty.Register(nameof(Endianness), typeof(Endianness), typeof(HexBox), + new PropertyMetadata(Endianness.BigEndian, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the format of the data to display. + /// + public static readonly DependencyProperty DataFormatProperty = + DependencyProperty.Register(nameof(DataFormat), typeof(DataFormat), typeof(HexBox), + new PropertyMetadata(DataFormat.Hexadecimal, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the signedness of the data to display. + /// + public static readonly DependencyProperty DataSignednessProperty = + DependencyProperty.Register(nameof(DataSignedness), typeof(DataSignedness), typeof(HexBox), + new PropertyMetadata(DataSignedness.Unsigned, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the data source which is used to read the data to display within this control. + /// + public static readonly DependencyProperty DataSourceProperty = + DependencyProperty.Register(nameof(DataSource), typeof(BinaryReader), typeof(HexBox), + new PropertyMetadata(null, OnDataSourceChanged)); + + /// + /// Defines the offset from the of the first visible data element being displayed. + /// + public static readonly DependencyProperty OffsetProperty = + DependencyProperty.Register(nameof(Offset), typeof(long), typeof(HexBox), + new PropertyMetadata(0L, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the maximum number of columns, based on the size of the control, which can be displayed. + /// + public static readonly DependencyProperty MaxVisibleColumnsProperty = + DependencyProperty.Register(nameof(MaxVisibleColumns), typeof(int), typeof(HexBox), + new PropertyMetadata(int.MaxValue, OnPropertyChangedInvalidateVisual)); + + + /// + /// Defines the maximum number of rows, based on the size of the control, which can be displayed. + /// + public static readonly DependencyProperty MaxVisibleRowsProperty = + DependencyProperty.Register(nameof(MaxVisibleRows), typeof(int), typeof(HexBox), + new PropertyMetadata(int.MaxValue, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the brush used for selection fill. + /// + public static readonly DependencyProperty SelectionBrushProperty = + DependencyProperty.Register(nameof(SelectionBrush), typeof(SolidColorBrush), typeof(HexBox), + new PropertyMetadata(new SolidColorBrush(Colors.LightPink), OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the brush used for selected text. + /// + public static readonly DependencyProperty SelectionTextBrushProperty = + DependencyProperty.Register(nameof(SelectionTextBrush), typeof(SolidColorBrush), typeof(HexBox), + new PropertyMetadata(new SolidColorBrush(Colors.Black), OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the offset from of where the user selection has ended. + /// + public static readonly DependencyProperty SelectionEndProperty = + DependencyProperty.Register(nameof(SelectionEnd), typeof(long), typeof(HexBox), + new PropertyMetadata(0L, OnSelectionEndChanged)); + + /// + /// Defines the offset from of where the user selection has started. + /// + public static readonly DependencyProperty SelectionStartProperty = + DependencyProperty.Register(nameof(SelectionStart), typeof(long), typeof(HexBox), + new PropertyMetadata(0L, OnSelectionStartChanged)); + + /// + /// Determines whether the user can change the layout and data format. + /// + public static readonly DependencyProperty EnforcePropertiesProperty = + DependencyProperty.Register(nameof(EnforceProperties), typeof(bool), typeof(HexBox), + new PropertyMetadata(false, OnPropertyChangedInvalidateVisual)); + + /// + /// Determines whether to show the address section of the control. + /// + public static readonly DependencyProperty ShowAddressProperty = + DependencyProperty.Register(nameof(ShowAddress), typeof(bool), typeof(HexBox), + new PropertyMetadata(true, OnPropertyChangedInvalidateVisual)); + + /// + /// Determines whether to show the data section of the control. + /// + public static readonly DependencyProperty ShowDataProperty = + DependencyProperty.Register(nameof(ShowData), typeof(bool), typeof(HexBox), + new PropertyMetadata(true, OnPropertyChangedInvalidateVisual)); + + /// + /// Determines whether to show the text section of the control. + /// + public static readonly DependencyProperty ShowTextProperty = + DependencyProperty.Register(nameof(ShowText), typeof(bool), typeof(HexBox), + new PropertyMetadata(true, OnPropertyChangedInvalidateVisual)); + + /// + /// Defines the brush used for the fill of the vertical separator line between the areas. + /// + public static readonly DependencyProperty VerticalSeparatorLineBrushProperty = + DependencyProperty.Register(nameof(VerticalSeparatorLineBrush), typeof(SolidColorBrush), typeof(HexBox), + new PropertyMetadata(new SolidColorBrush(Colors.Black), OnPropertyChangedInvalidateVisual)); + + + /// + /// Defines the format of the text to display in the text section. + /// + public static readonly DependencyProperty TextFormatProperty = + DependencyProperty.Register(nameof(TextFormat), typeof(TextFormat), typeof(HexBox), + new PropertyMetadata(TextFormat.Ascii, OnPropertyChangedInvalidateVisual)); + + /// + /// Gets the command. + /// + public ICommand SelectAllCommand + { + get { return (ICommand)GetValue(SelectAllCommandProperty); } + set { SetValue(SelectAllCommandProperty, value); } + } + + // Using a DependencyProperty as the backing store for CopyCommand. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SelectAllCommandProperty = + DependencyProperty.Register("SelectAllCommand", typeof(ICommand), typeof(HexBox), new PropertyMetadata(null)); + + /// + /// Gets the command. + /// + public ICommand CopyCommand + { + get { return (ICommand)GetValue(CopyCommandProperty); } + set { SetValue(CopyCommandProperty, value); } + } + + // Using a DependencyProperty as the backing store for CopyCommand. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CopyCommandProperty = + DependencyProperty.Register("CopyCommand", typeof(ICommand), typeof(HexBox), new PropertyMetadata(null)); + + /// + /// Gets the for text command. + /// + public ICommand CopyTextCommand + { + get { return (ICommand)GetValue(CopyTextCommandProperty); } + set { SetValue(CopyTextCommandProperty, value); } + } + + // Using a DependencyProperty as the backing store for CopyTextCommand. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CopyTextCommandProperty = + DependencyProperty.Register("CopyTextCommand", typeof(ICommand), typeof(HexBox), new PropertyMetadata(null)); + + + private const int _MaxColumns = 128; + private const int _MaxRows = 128; + + private const int _CharsBetweenSections = 2; + private const int _CharsBetweenDataColumns = 1; + private const int _ScrollWheelScrollRows = 3; + + private Rect _AddressRect; + private Rect _DataRect; + private Rect _TextRect; + + private SKPaint _TextPaint; + private SKPaint _LinePaint; + private SKRect _TextMeasure; + private SKTypeface _TextTypeFace; + + private SKXamlCanvas _Canvas; + private string _CanvasName = "ElementCanvas"; + + private SelectionArea _HighlightBegin = SelectionArea.None; + private SelectionArea _HighlightState = SelectionArea.None; + + private double _LastVerticalScrollValue = 0; + + private ScrollBar _ScrollBar; + private string _ScrollBarName = "ElementScrollBar"; + + private SelectionAdjustment _pointerMoveSelectionAdjustment = SelectionAdjustment.None; + + /// + public event PropertyChangedEventHandler PropertyChanged; + + private enum SelectionArea + { + None, + Address, + Data, + Text, + } + + private enum SelectionAdjustment + { + None, + Up, + Down + } + + /// + /// Gets or sets the address at which the data in the begins. + /// + public ulong Address + { + get => (ulong)GetValue(AddressProperty); + + set => SetValue(AddressProperty, value); + } + + /// + /// Gets or sets the brush used to display the addresses in the address section of the control. + /// + public SolidColorBrush AddressBrush + { + get => (SolidColorBrush)GetValue(AddressBrushProperty); + + set => SetValue(AddressBrushProperty, value); + } + + /// + /// Gets or sets the brush used for alternating for text in alternating (odd numbered) columns in the data section of the control. + /// + public SolidColorBrush AlternatingDataColumnTextBrush + { + get => (SolidColorBrush)GetValue(AlternatingDataColumnTextBrushProperty); + + set => SetValue(AlternatingDataColumnTextBrushProperty, value); + } + + /// + /// Gets or sets the number of columns to display. + /// + public int Columns + { + get => (int)GetValue(ColumnsProperty); + + set => SetValue(ColumnsProperty, CoerceColumns(this, value)); + } + + /// + /// Gets or sets the endianness used to interpret the data. + /// + public Endianness Endianness + { + get => (Endianness)GetValue(EndiannessProperty); + + set => SetValue(EndiannessProperty, value); + } + + /// + /// Gets or sets the format of the data to display. + /// + public DataFormat DataFormat + { + get => (DataFormat)GetValue(DataFormatProperty); + + set => SetValue(DataFormatProperty, value); + } + + /// + /// Gets or sets the signedness of the data to display. + /// + public DataSignedness DataSignedness + { + get => (DataSignedness)GetValue(DataSignednessProperty); + + set => SetValue(DataSignednessProperty, value); + } + + /// + /// Gets or sets the data source which is used to read the data to display within this control. + /// + public BinaryReader DataSource + { + get => (BinaryReader)GetValue(DataSourceProperty); + + set => SetValue(DataSourceProperty, value); + } + + /// + /// Gets or sets the data type which is used to display within this control. + /// + public DataType DataType + { + get { return (DataType)GetValue(DataTypeProperty); } + set => SetValue(DataTypeProperty, value); + } + + // Using a DependencyProperty as the backing store for DataType. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DataTypeProperty = + DependencyProperty.Register("DataType", typeof(DataType), typeof(HexBox), new PropertyMetadata(DataType.Int_1, OnDataTypeChanged)); + + /// + /// Gets or sets the width of the data to display. + /// + private int DataWidth = 1; + + /// + /// Gets a value indicating whether the user has made any selection within the control. + /// + public bool IsSelectionActive => SelectionLength != 0; + + /// + /// Gets the maximum number of columns, based on the size of the control, which can be displayed. + /// + public int MaxVisibleColumns + { + get => (int)GetValue(MaxVisibleColumnsProperty); + + private set => SetValue(MaxVisibleColumnsProperty, CoerceMaxVisibleColumns(this, value)); + } + + /// + /// Gets the maximum number of rows, based on the size of the control, which can be displayed. + /// + public int MaxVisibleRows + { + get => (int)GetValue(MaxVisibleRowsProperty); + + private set => SetValue(MaxVisibleRowsProperty, CoerceMaxVisibleRows(this, value)); + } + + /// + /// Gets or sets the offset from the of the first visible data element being displayed. + /// + public long Offset + { + get => (long)GetValue(OffsetProperty); + + set => SetValue(OffsetProperty, CoerceOffset(this, value)); + } + + /// + /// Gets lowest order address currently being selected. + /// + public ulong SelectedAddress => Address + (ulong)SelectedOffset; + + /// + /// Gets the offset from of the . + /// + public long SelectedOffset => Math.Min(SelectionStart, SelectionEnd); + + /// + /// Gets or sets the brush used for selection fill. + /// + public SolidColorBrush SelectionBrush + { + get => (SolidColorBrush)GetValue(SelectionBrushProperty); + + set => SetValue(SelectionBrushProperty, value); + } + + /// + /// Gets the offset from of where the user selection has ended. + /// + public long SelectionEnd + { + get => (long)GetValue(SelectionEndProperty); + + private set => SetValue(SelectionEndProperty, CoerceSelectionEnd(this, value)); + } + + /// + /// Gets the number of bytes selected. + /// + public long SelectionLength + { + get + { + if (SelectionStart <= SelectionEnd) + { + return SelectionEnd - SelectionStart; + } + else + { + return SelectionStart - SelectionEnd + _BytesPerColumn; + } + } + } + + /// + /// Gets the offset from of where the user selection has started. + /// + public long SelectionStart + { + get => (long)GetValue(SelectionStartProperty); + + private set + { + SetValue(SelectionStartProperty, CoerceSelectionStart(this, value)); + + // Reset SelectionStart adjustment state + _pointerMoveSelectionAdjustment = SelectionAdjustment.None; + } + } + + /// + /// Gets or sets the brush used for selected text. + /// + public SolidColorBrush SelectionTextBrush + { + get => (SolidColorBrush)GetValue(SelectionTextBrushProperty); + + set => SetValue(SelectionTextBrushProperty, value); + } + + /// + /// Gets or sets a value indicating whether the user can change the layout and data format or not. + /// + public bool EnforceProperties + { + get => (bool)GetValue(EnforcePropertiesProperty); + set => SetValue(EnforcePropertiesProperty, value); + } + + /// + /// Gets or sets a value indicating whether to show the address section of the control. + /// + public bool ShowAddress + { + get => (bool)GetValue(ShowAddressProperty); + + set => SetValue(ShowAddressProperty, value); + } + + /// + /// Gets or sets a value indicating whether to show the data section of the control. + /// + public bool ShowData + { + get => (bool)GetValue(ShowDataProperty); + + set => SetValue(ShowDataProperty, value); + } + + /// + /// Gets or sets a value indicating whether to show the text section of the control. + /// + public bool ShowText + { + get => (bool)GetValue(ShowTextProperty); + + set => SetValue(ShowTextProperty, value); + } + + /// + /// Gets or sets the brush used to display the vertical separator line between the control areas. + /// + public SolidColorBrush VerticalSeparatorLineBrush + { + get => (SolidColorBrush)GetValue(VerticalSeparatorLineBrushProperty); + + set => SetValue(VerticalSeparatorLineBrushProperty, value); + } + + /// + /// Gets or sets the width of the addresses displayed in the address section of the control. + /// + public AddressFormat AddressFormat + { + get => (AddressFormat)GetValue(AddressFormatProperty); + + set => SetValue(AddressFormatProperty, value); + } + + /// + /// Gets or sets the format of the text to display in the text section. + /// + public TextFormat TextFormat + { + get => (TextFormat)GetValue(TextFormatProperty); + + set => SetValue(TextFormatProperty, value); + } + + private double _SelectionBoxDataXPadding => _TextMeasure.Width / 4; + + private double _SelectionBoxDataYPadding => 0; + + private double _SelectionBoxTextXPadding => 0; + + private double _SelectionBoxTextYPadding => 0; + + private int _BytesPerColumn => DataWidth; + + private int _BytesPerRow => DataWidth * Columns; + + public class HighlightedRegion + { + public long Start; + public long Length; + public long End { get { return Start + Length; } } + public Brush Color; + + public HighlightedRegion() + { + + } + + public HighlightedRegion(int Start, int Length, Brush Color) + { + this.Start = Start; + this.Length = Length; + this.Color = Color; + } + + public bool IsByteSelected(long BytePos) + { + return BytePos >= Start && BytePos <= End; + } + } + + public List HighlightedRegions + { + get { return (List)GetValue(HighlightedRegionsProperty); } + set { SetValue(HighlightedRegionsProperty, value); } + } + + // Using a DependencyProperty as the backing store for HighlightedRegions. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HighlightedRegionsProperty = + DependencyProperty.Register("HighlightedRegions", typeof(List), typeof(HexBox), new PropertyMetadata(new List(), OnPropertyChangedInvalidateVisual)); + + + /// + /// Clears the current selection + /// + public void ClearSelection() + { + SelectionStart = SelectionEnd = 0; + } + + + /// + /// Select all data. + /// + public void SelectAll() + { + SelectionStart = 0; + SelectionEnd = DataSource.BaseStream.Length; + } + + + /// + /// Copies the current selection of the control to the . + /// + /// Copy the text and not the data. + public void Copy(bool copyText) + { + if (IsSelectionActive) + { + StringBuilder builder = new(); + + long savedDataSourcePositionBeforeReadingData = DataSource.BaseStream.Position; + + // Adjust wrong SelectionEnd after selecting down or left to right + long selectionEnd = SelectionStart < SelectionEnd ? SelectionEnd - _BytesPerColumn : SelectionEnd; + + DataSource.BaseStream.Position = Math.Min(SelectionStart, selectionEnd); + + while (DataSource.BaseStream.Position <= Math.Max(SelectionStart, selectionEnd)) + { + if (copyText) + { + var formattedData = ReadFormattedText(); + builder.Append(formattedData); + } + else + { + var formattedData = ReadFormattedData(); + builder.Append(formattedData); + } + } + + DataSource.BaseStream.Position = savedDataSourcePositionBeforeReadingData; + + var dataPackage = new DataPackage(); + dataPackage.SetText(builder.ToString()); + Clipboard.SetContent(dataPackage); + } + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _Canvas = GetTemplateChild(_CanvasName) as SKXamlCanvas; + + if (_Canvas != null) + { + CopyCommand = new RelayCommand(CopyExecuted, CopyCanExecute); + CopyTextCommand = new RelayCommand(CopyTextExecuted, CopyCanExecute); + SelectAllCommand = new RelayCommand(SelectAllExecuted, SelectAllCanExecute); + _Canvas.PaintSurface += Canvas_PaintSurface; + } + else + { + throw new InvalidOperationException($"Could not find {_CanvasName} template child."); + } + + if (_ScrollBar != null) + { + _ScrollBar.Scroll -= OnVerticalScrollBarScroll; + } + + _ScrollBar = GetTemplateChild(_ScrollBarName) as ScrollBar; + + if (_ScrollBar != null) + { + _ScrollBar.Scroll += OnVerticalScrollBarScroll; + _ScrollBar.ValueChanged += OnVerticalScrollBarValueChanged; + + _ScrollBar.Minimum = 0; + _ScrollBar.SmallChange = 1; + _ScrollBar.LargeChange = MaxVisibleRows; + _TextTypeFace = SKTypeface.FromFamilyName(_ScrollBar.FontFamily.Source, SKFontStyle.Normal); + } + else + { + throw new InvalidOperationException($"Could not find {_ScrollBarName} template child."); + } + } + + private void DrawSelectionGeometry(SKCanvas Canvas, + Brush brush, + SKPaint pen, + Point point0, + Point point1, + SelectionArea relativeTo) + { + if ((long)point0.Y > (long)point1.Y) + { + throw new ArgumentException($"{point0.ToString()} > {point1.ToString()}", nameof(point0)); + } + + Point lhsVerticalLinePoint0; + Point rhsVerticalLinePoint0; + + double selectionBoxXPadding; + double selectionBoxYPadding; + + switch (relativeTo) + { + case SelectionArea.Data: + { + lhsVerticalLinePoint0 = new Point(_AddressRect.Left, _AddressRect.Top); + rhsVerticalLinePoint0 = new Point(_DataRect.Left, _DataRect.Top); + + selectionBoxXPadding = _SelectionBoxDataXPadding; + selectionBoxYPadding = _SelectionBoxDataYPadding; + } + + break; + + case SelectionArea.Text: + { + lhsVerticalLinePoint0 = new Point(_DataRect.Left, _DataRect.Top); + rhsVerticalLinePoint0 = new Point(_TextRect.Left, _TextRect.Top); + + selectionBoxXPadding = _SelectionBoxTextXPadding; + selectionBoxYPadding = _SelectionBoxTextYPadding; + } + + break; + + default: + { + throw new ArgumentException($"Invalid relative area {relativeTo}", nameof(relativeTo)); + } + } + + point0.X -= selectionBoxXPadding; + point1.X += selectionBoxXPadding; + point0.Y -= selectionBoxYPadding; + point1.Y += selectionBoxYPadding; + + var ps_CharsBetweenSections = _CharsBetweenSections * _TextMeasure.Width; + + SKPath path = new(); + SKPoint[] points; + + if ((long)point0.X < (long)point1.X) + { + if ((long)point0.Y < (long)point1.Y) + { + // +---------------------------+ + // | | + // | 0-------------2 + // | | | + // 6-------------7 1-------3 + // | | | + // 5-------------------4 | + // | | + // | | + // | | + // +---------------------------+ + Point point2 = new(rhsVerticalLinePoint0.X - ps_CharsBetweenSections + selectionBoxXPadding, point0.Y); + Point point3 = new(rhsVerticalLinePoint0.X - ps_CharsBetweenSections + selectionBoxXPadding, point1.Y); + Point point4 = new(point1.X, point1.Y + _TextMeasure.Height); + Point point5 = new(lhsVerticalLinePoint0.X + ps_CharsBetweenSections - selectionBoxXPadding, point1.Y + _TextMeasure.Height); + Point point6 = new(lhsVerticalLinePoint0.X + ps_CharsBetweenSections - selectionBoxXPadding, point0.Y + _TextMeasure.Height); + Point point7 = new(point0.X, point0.Y + _TextMeasure.Height); + + points = [point0.ToSKPoint(), point2.ToSKPoint(), point3.ToSKPoint(), point1.ToSKPoint(), point4.ToSKPoint(), point5.ToSKPoint(), point6.ToSKPoint(), point7.ToSKPoint()]; + } + else + { + // +---------------------------+ + // | | + // | 0-------------1 | + // | | | | + // | 3-------------2 | + // | | + // | | + // | | + // | | + // | | + // +---------------------------+ + Point point2 = new(point1.X, point1.Y + _TextMeasure.Height); + Point point3 = new(point0.X, point0.Y + _TextMeasure.Height); + + points = [point0.ToSKPoint(), point1.ToSKPoint(), point2.ToSKPoint(), point3.ToSKPoint()]; + } + } + else + { + if ((long)(point0.Y + _TextMeasure.Height) == (long)point1.Y) + { + // +---------------------------+ + // | | + // | 0-------------2 + // | | | + // 7--------1 4-------------3 + // | | | + // 6--------5 | + // | | + // | | + // | | + // +---------------------------+ + { + Point point2 = new(rhsVerticalLinePoint0.X - ps_CharsBetweenSections + selectionBoxXPadding, point0.Y); + Point point3 = new(rhsVerticalLinePoint0.X - ps_CharsBetweenSections + selectionBoxXPadding, point1.Y); + Point point4 = new(point0.X, point1.Y); + + points = [point0.ToSKPoint(), point2.ToSKPoint(), point3.ToSKPoint(), point4.ToSKPoint()]; + } + + path.AddPoly(points); + + { + Point point5 = new(point1.X, point1.Y + _TextMeasure.Height); + Point point6 = new(lhsVerticalLinePoint0.X + ps_CharsBetweenSections - selectionBoxXPadding, point1.Y + _TextMeasure.Height); + Point point7 = new(lhsVerticalLinePoint0.X + ps_CharsBetweenSections - selectionBoxXPadding, point1.Y); + points = [point1.ToSKPoint(), point5.ToSKPoint(), point6.ToSKPoint(), point7.ToSKPoint()]; + } + } + else + { + // +---------------------------+ + // | | + // | 0-------------2 + // | | | + // 6-------------7 | + // | | + // | 1------------------3 + // | | | + // 5--------4 | + // | | + // +---------------------------+ + Point point2 = new(rhsVerticalLinePoint0.X - ps_CharsBetweenSections + selectionBoxXPadding, point0.Y); + Point point3 = new(rhsVerticalLinePoint0.X - ps_CharsBetweenSections + selectionBoxXPadding, point1.Y); + Point point4 = new(point1.X, point1.Y + _TextMeasure.Height); + Point point5 = new(lhsVerticalLinePoint0.X + ps_CharsBetweenSections - selectionBoxXPadding, point1.Y + _TextMeasure.Height); + Point point6 = new(lhsVerticalLinePoint0.X + ps_CharsBetweenSections - selectionBoxXPadding, point0.Y + _TextMeasure.Height); + Point point7 = new(point0.X, point0.Y + _TextMeasure.Height); + + points = [point0.ToSKPoint(), point2.ToSKPoint(), point3.ToSKPoint(), point1.ToSKPoint(), point4.ToSKPoint(), point5.ToSKPoint(), point6.ToSKPoint(), point7.ToSKPoint()]; + } + } + + path.AddPoly(points); + if (brush is SolidColorBrush s) + pen.Color = s.Color.ToSKColor(); + Canvas.DrawPath(path, pen); + } + + private void DrawTextAccuracy(SKCanvas Canvas, SKPaint paint, SKPoint pt, string text) + { + //Canvas.DrawText(text, pt, paint); + int index = 0; + foreach (var c in text) + { + Canvas.DrawText(c.ToString(), pt.X + index * _TextMeasure.Width, pt.Y, paint); + index++; + } + } + + private void Canvas_PaintSurface(object sender, SKPaintSurfaceEventArgs e) + { + var view = sender as SKXamlCanvas; + var canvas = e.Surface.Canvas; + + if (_LinePaint == null) + { + _LinePaint = new() + { + IsStroke = true, + IsAntialias = true, + StrokeWidth = 1, + TextSize = (float)FontSize, + Typeface = _TextTypeFace, + TextAlign = SKTextAlign.Left, + }; + } + _LinePaint.Color = VerticalSeparatorLineBrush.Color.ToSKColor(); + + if (_TextPaint == null) + { + _TextPaint = new() + { + TextSize = (float)FontSize, + Typeface = _TextTypeFace, + TextScaleX = 1f, + IsAntialias = true, + TextAlign = SKTextAlign.Left, + HintingLevel = SKPaintHinting.Normal, + }; + } + + UpdateState(); + + if (DataSource != null) + { + canvas.Clear(); + long savedDataSourcePosition = DataSource.BaseStream.Position; + + DataSource.BaseStream.Position = Offset; + + if (ShowAddress) + { + var p0 = new Point(_AddressRect.Left, _AddressRect.Top).ToSKPoint(); + var p1 = new Point(_AddressRect.Right, _AddressRect.Bottom).ToSKPoint(); + + canvas.DrawLine(p0, p1, _LinePaint); + } + + if (ShowData) + { + var p0 = new Point(_DataRect.Left, _DataRect.Top).ToSKPoint(); + var p1 = new Point(_DataRect.Right, _DataRect.Bottom).ToSKPoint(); + + canvas.DrawLine(p0, p1, _LinePaint); + + if (HighlightedRegions.Count != 0 && MaxVisibleRows > 0 && Columns > 0) + { + var viewLimited = Offset + _BytesPerRow * MaxVisibleRows; + + foreach (var hlSection in HighlightedRegions) + { + if (hlSection.End <= Offset || (hlSection.Start >= viewLimited) || hlSection.Start >= hlSection.End) continue; + + var max_visible = Math.Min(hlSection.End, viewLimited); + + Point hlsP0 = ConvertOffsetToPosition(hlSection.Start, SelectionArea.Data); + Point hlsP1 = ConvertOffsetToPosition(max_visible, SelectionArea.Data); + + if (max_visible % _BytesPerRow == 0) + { + hlsP1.X = p1.X - _CharsBetweenSections * _TextMeasure.Width; + hlsP1.Y = Math.Max(hlsP0.Y, hlsP1.Y - _TextMeasure.Height); + } + else + { + hlsP1.X -= _TextMeasure.Width; + } + + DrawSelectionGeometry(canvas, hlSection.Color, _TextPaint, hlsP0, hlsP1, SelectionArea.Data); + } + } + } + + if (ShowText) + { + var p0 = new Point(_TextRect.Left, _TextRect.Top); + var p1 = new Point(_TextRect.Right, _TextRect.Bottom); + + canvas.DrawLine(p0.ToSKPoint(), p1.ToSKPoint(), _LinePaint); + + if (HighlightedRegions.Count != 0 && MaxVisibleRows > 0 && Columns > 0) + { + var viewLimited = Offset + MaxVisibleColumns * MaxVisibleRows; + + foreach (var hlSection in HighlightedRegions) + { + if (hlSection.End <= Offset || (hlSection.Start >= viewLimited) || hlSection.Start >= hlSection.End) continue; + + var max_visible = Math.Min(hlSection.End, viewLimited); + + Point hlsP0 = ConvertOffsetToPosition(hlSection.Start, SelectionArea.Text); + Point hlsP1 = ConvertOffsetToPosition(max_visible, SelectionArea.Text); + + if (max_visible % _BytesPerRow == 0) + { + hlsP1.X = p1.X - _CharsBetweenSections * _TextMeasure.Width; + hlsP1.Y = Math.Max(hlsP0.Y, hlsP1.Y - _TextMeasure.Height); + } + + DrawSelectionGeometry(canvas, hlSection.Color, _TextPaint, hlsP0, hlsP1, SelectionArea.Text); + } + } + } + + if (ShowData) + { + if (SelectionLength != 0 && MaxVisibleRows > 0 && Columns > 0) + { + Point sp0 = ConvertOffsetToPosition(SelectedOffset, SelectionArea.Data); + Point sp1 = ConvertOffsetToPosition(SelectedOffset + SelectionLength, SelectionArea.Data); + + if ((SelectedOffset + SelectionLength) % _BytesPerRow == 0) + { + sp1.X = _DataRect.Left - _CharsBetweenSections * _TextMeasure.Width; + sp1.Y = Math.Max(sp0.Y, sp1.Y - _TextMeasure.Height); + } + else + { + sp1.X -= _TextMeasure.Width; + } + + DrawSelectionGeometry(canvas, SelectionBrush, _TextPaint, sp0, sp1, SelectionArea.Data); + } + } + + if (ShowText) + { + if (SelectionLength != 0 && MaxVisibleRows > 0 && Columns > 0) + { + Point sp0 = ConvertOffsetToPosition(SelectedOffset, SelectionArea.Text); + Point sp1 = ConvertOffsetToPosition(SelectedOffset + SelectionLength, SelectionArea.Text); + + if ((SelectedOffset + SelectionLength) % _BytesPerRow == 0) + { + sp1.X = _TextRect.Left - _CharsBetweenSections * _TextMeasure.Width; + sp1.Y -= _TextMeasure.Height; + } + + DrawSelectionGeometry(canvas, SelectionBrush, _TextPaint, sp0, sp1, SelectionArea.Text); + } + } + + SKPoint origin = default; + origin.Y = _TextMeasure.Height * 3 / 4; /* left bottom to right top */ + + for (var row = 0; row < MaxVisibleRows; ++row) + { + if (ShowAddress) + { + if (DataSource.BaseStream.Position + _BytesPerColumn <= DataSource.BaseStream.Length) + { + var textToFormat = GetFormattedAddressText(Address + (ulong)DataSource.BaseStream.Position); + + if (AddressBrush is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + canvas.DrawText(textToFormat, origin.X, origin.Y, _TextPaint); + + origin.X += (float)((CalculateAddressColumnCharWidth() + _CharsBetweenSections) * _TextMeasure.Width); + } + } + + long savedDataSourcePositionBeforeReadingData = DataSource.BaseStream.Position; + + if (ShowData) + { + origin.X += (float)(_CharsBetweenSections * _TextMeasure.Width); + + var cachedDataColumnCharWidth = CalculateDataColumnCharWidth(); + + // Needed to track text in alternating columns so we can use a different brush when drawing + var evenColumnBuilder = new StringBuilder(Columns * DataWidth); + var oddColumnBuilder = new StringBuilder(Columns * DataWidth); + + var column = 0; + + // Draw text up until selection start point + while (column < Columns) + { + if (DataSource.BaseStream.Position + _BytesPerColumn <= DataSource.BaseStream.Length) + { + if (DataSource.BaseStream.Position >= SelectedOffset) + { + break; + } + + var textToFormat = ReadFormattedData(); + + if (column % 2 == 0) + { + evenColumnBuilder.Append(textToFormat); + evenColumnBuilder.Append(' ', _CharsBetweenDataColumns); + + oddColumnBuilder.Append(' ', textToFormat.Length + _CharsBetweenDataColumns); + } + else + { + oddColumnBuilder.Append(textToFormat); + oddColumnBuilder.Append(' ', _CharsBetweenDataColumns); + + evenColumnBuilder.Append(' ', textToFormat.Length + _CharsBetweenDataColumns); + } + } + else + { + evenColumnBuilder.Append(' ', cachedDataColumnCharWidth + _CharsBetweenDataColumns); + oddColumnBuilder.Append(' ', cachedDataColumnCharWidth + _CharsBetweenDataColumns); + } + + ++column; + } + + { + if (Foreground is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + DrawTextAccuracy(canvas, _TextPaint, origin, evenColumnBuilder.ToString()); + } + + { + if (AlternatingDataColumnTextBrush is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + DrawTextAccuracy(canvas, _TextPaint, origin, oddColumnBuilder.ToString()); + } + origin.X += evenColumnBuilder.Length * _TextMeasure.Width; + + if (column < Columns) + { + // We'll reuse this builder for drawing selection text + evenColumnBuilder.Clear(); + + // Draw text starting from selection start point + while (column < Columns) + { + if (DataSource.BaseStream.Position + _BytesPerColumn <= DataSource.BaseStream.Length) + { + if (DataSource.BaseStream.Position >= SelectedOffset + SelectionLength) + { + break; + } + + var textToFormat = ReadFormattedData(); + + evenColumnBuilder.Append(textToFormat); + evenColumnBuilder.Append(' ', _CharsBetweenDataColumns); + } + else + { + evenColumnBuilder.Append(' ', cachedDataColumnCharWidth + _CharsBetweenDataColumns); + } + + ++column; + } + + { + if (SelectionTextBrush is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + DrawTextAccuracy(canvas, _TextPaint, origin, evenColumnBuilder.ToString()); + } + + origin.X += evenColumnBuilder.Length * _TextMeasure.Width; + + if (column < Columns) + { + evenColumnBuilder.Clear(); + oddColumnBuilder.Clear(); + + // Draw text after end of selection + while (column < Columns) + { + if (DataSource.BaseStream.Position + _BytesPerColumn <= DataSource.BaseStream.Length) + { + var textToFormat = ReadFormattedData(); + if (column % 2 == 0) + { + evenColumnBuilder.Append(textToFormat); + evenColumnBuilder.Append(' ', _CharsBetweenDataColumns); + + oddColumnBuilder.Append(' ', textToFormat.Length + _CharsBetweenDataColumns); + } + else + { + oddColumnBuilder.Append(textToFormat); + oddColumnBuilder.Append(' ', _CharsBetweenDataColumns); + + evenColumnBuilder.Append(' ', textToFormat.Length + _CharsBetweenDataColumns); + } + } + else + { + evenColumnBuilder.Append(' ', cachedDataColumnCharWidth + _CharsBetweenDataColumns); + oddColumnBuilder.Append(' ', cachedDataColumnCharWidth + _CharsBetweenDataColumns); + } + + ++column; + } + + { + if (Foreground is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + DrawTextAccuracy(canvas, _TextPaint, origin, evenColumnBuilder.ToString()); + } + + { + if (AlternatingDataColumnTextBrush is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + DrawTextAccuracy(canvas, _TextPaint, origin, oddColumnBuilder.ToString()); + } + + origin.X += oddColumnBuilder.Length * _TextMeasure.Width; + } + } + + // Compensate for the extra space added at the end of the builder + origin.X += (float)((_CharsBetweenSections - _CharsBetweenDataColumns) * _TextMeasure.Width); + } + + if (ShowText) + { + origin.X += (float)(_CharsBetweenSections * _TextMeasure.Width); + + if (ShowData) + { + // Reset the stream to read one byte at a time + DataSource.BaseStream.Position = savedDataSourcePositionBeforeReadingData; + } + + var builder = new StringBuilder(Columns * DataWidth); + + var column = 0; + + // Draw text up until selection start point + while (column < Columns) + { + if (DataSource.BaseStream.Position + _BytesPerColumn <= DataSource.BaseStream.Length) + { + if (DataSource.BaseStream.Position >= SelectedOffset) + { + break; + } + + var textToFormat = ReadFormattedText(); + builder.Append(textToFormat); + } + + ++column; + } + + { + if (Foreground is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + DrawTextAccuracy(canvas, _TextPaint, origin, builder.ToString()); + } + + if (column < Columns) + { + origin.X += builder.Length * _TextMeasure.Width; + + builder.Clear(); + + // Draw text starting from selection start point + while (column < Columns) + { + if (DataSource.BaseStream.Position + _BytesPerColumn <= DataSource.BaseStream.Length) + { + if (DataSource.BaseStream.Position >= SelectedOffset + SelectionLength) + { + break; + } + + var textToFormat = ReadFormattedText(); + builder.Append(textToFormat); + } + + ++column; + } + + { + if (SelectionTextBrush is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + + DrawTextAccuracy(canvas, _TextPaint, origin, builder.ToString()); + } + + if (column < Columns) + { + origin.X += builder.Length * _TextMeasure.Width; + + builder.Clear(); + + // Draw text after end of selection + while (column < Columns) + { + if (DataSource.BaseStream.Position + _BytesPerColumn <= DataSource.BaseStream.Length) + { + var textToFormat = ReadFormattedText(); + builder.Append(textToFormat); + } + + ++column; + } + + { + if (Foreground is SolidColorBrush s) + { + _TextPaint.Color = s.Color.ToSKColor(); + } + + DrawTextAccuracy(canvas, _TextPaint, origin, builder.ToString()); + } + } + } + } + + origin.X = 0; + origin.Y += _TextMeasure.Height; + } + + DataSource.BaseStream.Position = savedDataSourcePosition; + } + } + + /// + /// Scrolls the contents of the control to the specified offset. + /// + /// + /// + /// The offset to scroll to. + /// + public void ScrollToOffset(long offset) + { + long maxBytesDisplayed = _BytesPerRow * MaxVisibleRows; + long lastByteOffset = (DataSource?.BaseStream?.Length ?? 1) - 1; + + // Adjust requested offset if not existing + if (offset < 0) + { + offset = 0; + } + else if (offset > lastByteOffset) + { + offset = lastByteOffset; + } + + if (Offset > offset) + { + // We need to scroll up + Offset -= ((Offset - offset - 1) / _BytesPerRow + 1) * _BytesPerRow; + } + + if (Offset + maxBytesDisplayed <= offset) + { + // We need to scroll down + Offset += ((offset - (Offset + maxBytesDisplayed)) / _BytesPerRow + 1) * _BytesPerRow; + } + } + + // Using .HasFlag(x) to correctly detect state of modifier keys (CTRL, SHIFT, ...) + private static bool IsKeyDown(VirtualKey key) => InputKeyboardSource.GetKeyStateForCurrentThread(key).HasFlag(CoreVirtualKeyStates.Down); + + /// + protected override void OnKeyDown(KeyRoutedEventArgs e) + { + base.OnKeyDown(e); + + // Context Menu + switch (e.Key) + { + case VirtualKey.Application: + { + ShowContextMenu(); + e.Handled = true; + return; + } + + case VirtualKey.F10: + { + if (IsKeyDown(VirtualKey.LeftShift) || IsKeyDown(VirtualKey.RightShift)) + { + ShowContextMenu(); + } + + e.Handled = true; + return; + } + } + + // Other keys + if (Columns > 0 && MaxVisibleRows > 0) + { + switch (e.Key) + { + case VirtualKey.A: + { + if (IsKeyDown(VirtualKey.LeftControl) || IsKeyDown(VirtualKey.RightControl)) + { + if (SelectAllCanExecute(null)) + { + SelectionStart = 0; + SelectionEnd = DataSource.BaseStream.Length; + } + } + + e.Handled = true; + break; + } + + case VirtualKey.C: + { + if (IsKeyDown(VirtualKey.LeftControl) || IsKeyDown(VirtualKey.RightControl)) + { + if (CopyCanExecute(null)) + { + if (IsKeyDown(VirtualKey.LeftShift) || IsKeyDown(VirtualKey.RightShift)) + { + // Copy text + Copy(true); + } + else + { + // Copy data + Copy(false); + } + } + } + + e.Handled = true; + break; + } + + case VirtualKey.Down: + { + if (IsKeyDown(VirtualKey.LeftShift) || IsKeyDown(VirtualKey.RightShift)) + { + SelectionEnd += _BytesPerRow; + } + else + { + SelectionStart += _BytesPerRow; + SelectionEnd = SelectionStart + _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + + e.Handled = true; + + break; + } + + case VirtualKey.End: + { + if (IsKeyDown(VirtualKey.LeftControl) || IsKeyDown(VirtualKey.RightControl)) + { + SelectionEnd = DataSource.BaseStream.Length; + + if (!IsKeyDown(VirtualKey.LeftShift) && !IsKeyDown(VirtualKey.RightShift)) + { + SelectionStart = SelectionEnd - _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + } + else + { + SelectionEnd += (Offset - SelectionEnd).Mod(_BytesPerRow); + + if (!IsKeyDown(VirtualKey.LeftShift) && !IsKeyDown(VirtualKey.RightShift)) + { + SelectionStart = SelectionEnd - _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + } + + e.Handled = true; + + break; + } + + case VirtualKey.Home: + { + if (IsKeyDown(VirtualKey.LeftControl) || IsKeyDown(VirtualKey.RightControl)) + { + SelectionEnd = 0; + + if (!IsKeyDown(VirtualKey.LeftShift) && !IsKeyDown(VirtualKey.RightShift)) + { + SelectionStart = SelectionEnd; + SelectionEnd = SelectionStart + _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + } + else + { + // TODO: Because of the way we represent selection there is no way to distinguish at the + // moment whether the selection ends at the start of the current line or the end of the + // previous line. As such, when the Shift+End hotkey is used twice consecutively a whole + // new line above the current selection will be selected. This is undesirable behavior + // that deviates from the canonical semantics of Shift+End. + SelectionEnd -= (SelectionEnd - 1 - Offset).Mod(_BytesPerRow) + 1; + + if (!IsKeyDown(VirtualKey.LeftShift) && !IsKeyDown(VirtualKey.RightShift)) + { + SelectionStart = SelectionEnd; + SelectionEnd = SelectionStart + _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + } + + e.Handled = true; + + break; + } + + case VirtualKey.Left: + { + if (IsKeyDown(VirtualKey.LeftShift) || IsKeyDown(VirtualKey.RightShift)) + { + SelectionEnd -= _BytesPerColumn; + } + else + { + SelectionStart -= _BytesPerColumn; + SelectionEnd = SelectionStart + _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + + e.Handled = true; + + break; + } + + case VirtualKey.PageDown: + { + bool isOffsetVisibleBeforeSelectionChange = IsOffsetVisible(SelectionEnd); + + SelectionEnd += _BytesPerRow * MaxVisibleRows; + + if (!IsKeyDown(VirtualKey.LeftShift) && !IsKeyDown(VirtualKey.RightShift)) + { + SelectionStart = SelectionEnd - _BytesPerColumn; + } + + _ScrollBar.Value += MaxVisibleRows; + + OnVerticalScrollBarScroll(_ScrollBar, ScrollEventType.SmallIncrement, _ScrollBar.Value); + + e.Handled = true; + break; + } + + case VirtualKey.PageUp: + { + bool isOffsetVisibleBeforeSelectionChange = IsOffsetVisible(SelectionEnd); + + SelectionEnd -= _BytesPerRow * MaxVisibleRows; + + if (!IsKeyDown(VirtualKey.LeftShift) && !IsKeyDown(VirtualKey.RightShift)) + { + SelectionStart = SelectionEnd - _BytesPerColumn; + SelectionEnd = SelectionStart + _BytesPerColumn; + } + + _ScrollBar.Value -= MaxVisibleRows; + + OnVerticalScrollBarScroll(_ScrollBar, ScrollEventType.SmallIncrement, _ScrollBar.Value); + + e.Handled = true; + break; + } + + case VirtualKey.Right: + { + if (IsKeyDown(VirtualKey.LeftShift) || IsKeyDown(VirtualKey.RightShift)) + { + SelectionEnd += _BytesPerColumn; + } + else + { + SelectionStart += _BytesPerColumn; + SelectionEnd = SelectionStart + _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + + e.Handled = true; + break; + } + + case VirtualKey.Up: + { + if (IsKeyDown(VirtualKey.LeftShift) || IsKeyDown(VirtualKey.RightShift)) + { + SelectionEnd -= _BytesPerRow; + } + else + { + SelectionStart -= _BytesPerRow; + SelectionEnd = SelectionStart + _BytesPerColumn; + } + + ScrollToOffset(SelectionEnd - _BytesPerColumn); + + e.Handled = true; + break; + } + } + } + } + + protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e) + { + Focus(FocusState.Programmatic); + e.Handled = true; + + if (e.PointerDeviceType == PointerDeviceType.Mouse) + { + OnMouseDoubleClick(e.GetPosition(_Canvas)); + } + } + + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + Focus(FocusState.Programmatic); + e.Handled = true; + + var pps = e.GetCurrentPoint(this).Properties; + if (pps != null) + { + if (pps.PointerUpdateKind == PointerUpdateKind.LeftButtonPressed) + { + OnMouseLeftButtonDown(e); + } + } + } + + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + base.OnPointerReleased(e); + var pps = e.GetCurrentPoint(this).Properties; + if (pps != null) + { + if (pps.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased) + { + OnMouseLeftButtonUp(e); + } + } + } + + protected override void OnPointerCanceled(PointerRoutedEventArgs e) + { + base.OnPointerCanceled(e); + var pps = e.GetCurrentPoint(this).Properties; + if (pps != null) + { + if (pps.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased) + { + OnMouseLeftButtonUp(e); + } + } + } + + protected override void OnPointerCaptureLost(PointerRoutedEventArgs e) + { + base.OnPointerCaptureLost(e); + } + + protected override void OnPointerExited(PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + } + + protected override void OnPointerEntered(PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + } + + /// + protected override void OnPointerMoved(PointerRoutedEventArgs e) + { + base.OnPointerMoved(e); + + if (_HighlightState != SelectionArea.None) + { + var position = e.GetCurrentPoint(_Canvas).Position; + var currentMouseOverOffset = ConvertPositionToOffset(position); + + switch (_HighlightState) + { + case SelectionArea.Address: + { + if (currentMouseOverOffset >= SelectionStart) + { + SelectionEnd = currentMouseOverOffset + _BytesPerRow; + } + else + { + SelectionEnd = currentMouseOverOffset; + } + + // Adjust start point + if (SelectionStart > SelectionEnd && _pointerMoveSelectionAdjustment != SelectionAdjustment.Up) + { + // If moving up and SelectionStart was previously adjusted down or not adjusted, then set SelectionStart to end of row. + SelectionStart = SelectionStart + (_BytesPerRow - _BytesPerColumn); + _pointerMoveSelectionAdjustment = SelectionAdjustment.Up; + } + else if (SelectionStart < SelectionEnd && _pointerMoveSelectionAdjustment == SelectionAdjustment.Up) + { + // If moving down and SelectionStart was previously adjusted up, then set SelectionStart to start of row. + SelectionStart = SelectionStart - (_BytesPerRow - _BytesPerColumn); + _pointerMoveSelectionAdjustment = SelectionAdjustment.Down; + } + break; + } + case SelectionArea.Data: + case SelectionArea.Text: + { + if (currentMouseOverOffset >= SelectionStart) + { + SelectionEnd = currentMouseOverOffset + _BytesPerColumn; + } + else + { + SelectionEnd = currentMouseOverOffset; + } + break; + } + } + + // Move next row into view if selection goes out of view + if (position.Y > _AddressRect.Y + _AddressRect.Height) + { + ScrollToOffset(currentMouseOverOffset + _BytesPerRow); + } + else if (position.Y < _AddressRect.Y) + { + ScrollToOffset(currentMouseOverOffset - _BytesPerRow); + } + } + } + + /// + protected override void OnPointerWheelChanged(PointerRoutedEventArgs e) + { + base.OnPointerWheelChanged(e); + var Delta = e.GetCurrentPoint(this).Properties.MouseWheelDelta; + + var value = _ScrollBar.Value; + if (Delta < 0) + { + _ScrollBar.Value += _ScrollWheelScrollRows; + + OnVerticalScrollBarScroll(_ScrollBar, ScrollEventType.SmallIncrement, _ScrollBar.Value); + } + else + { + _ScrollBar.Value -= _ScrollWheelScrollRows; + + OnVerticalScrollBarScroll(_ScrollBar, ScrollEventType.SmallDecrement, _ScrollBar.Value); + } + } + + /// + private void OnMouseDoubleClick(Point position) + { + Point addressVerticalLinePoint0 = CalculateAddressVerticalLinePoint0(); + + if (position.X < addressVerticalLinePoint0.X) + { + _HighlightBegin = SelectionArea.Address; + _HighlightState = SelectionArea.Address; + + SelectionStart = ConvertPositionToOffset(position); + SelectionEnd = SelectionStart + _BytesPerRow; + } + } + + /// + private void OnMouseLeftButtonDown(PointerRoutedEventArgs e) + { + if (_HighlightState == SelectionArea.None && CapturePointer(e.Pointer)) + { + Point position = e.GetCurrentPoint(_Canvas).Position; + + Point addressVerticalLinePoint0 = CalculateAddressVerticalLinePoint0(); + Point dataVerticalLinePoint0 = CalculateDataVerticalLinePoint0(); + Point textVerticalLinePoint0 = CalculateTextVerticalLinePoint0(); + + if (position.X < addressVerticalLinePoint0.X) + { + _HighlightBegin = SelectionArea.Address; + _HighlightState = SelectionArea.Address; + } + else if (position.X < dataVerticalLinePoint0.X) + { + _HighlightBegin = SelectionArea.Data; + _HighlightState = SelectionArea.Data; + } + else if (position.X < textVerticalLinePoint0.X) + { + _HighlightBegin = SelectionArea.Text; + _HighlightState = SelectionArea.Text; + } + + if (_HighlightState != SelectionArea.None) + { + SelectionStart = ConvertPositionToOffset(position); + + SelectionEnd = SelectionStart + _BytesPerColumn; + } + } + } + + /// + private void OnMouseLeftButtonUp(PointerRoutedEventArgs e) + { + _HighlightState = SelectionArea.None; + + ReleasePointerCapture(e.Pointer); + } + + private static void OnPropertyChangedInvalidateVisual(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var HexBox = (HexBox)d; + + HexBox.Reflush(); + } + + private static void OnSelectionEndChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var HexBox = (HexBox)d; + + HexBox.Reflush(); + + HexBox.OnPropertyChanged(nameof(SelectionEnd)); + HexBox.OnPropertyChanged(nameof(SelectionLength)); + HexBox.OnPropertyChanged(nameof(SelectedOffset)); + HexBox.OnPropertyChanged(nameof(SelectedAddress)); + HexBox.OnPropertyChanged(nameof(IsSelectionActive)); + } + + private static void OnSelectionStartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var HexBox = (HexBox)d; + + HexBox.Reflush(); + + HexBox.OnPropertyChanged(nameof(SelectionStart)); + HexBox.OnPropertyChanged(nameof(SelectionLength)); + HexBox.OnPropertyChanged(nameof(SelectedOffset)); + HexBox.OnPropertyChanged(nameof(SelectedAddress)); + HexBox.OnPropertyChanged(nameof(IsSelectionActive)); + } + + private static object CoerceColumns(DependencyObject d, object value) + { + var HexBox = (HexBox)d; + + if (HexBox.MaxVisibleColumns == 0) + { + return (int)value; + } + else + { + return Math.Min((int)value, HexBox.MaxVisibleColumns); + } + } + + private static object CoerceMaxVisibleColumns(DependencyObject d, object value) + { + return Math.Min((int)value, _MaxColumns); + } + + private static object CoerceMaxVisibleRows(DependencyObject d, object value) + { + return Math.Min((int)value, _MaxRows); + } + + private static object CoerceSelectionStart(DependencyObject d, object value) + { + var HexBox = (HexBox)d; + + if (HexBox.DataSource != null) + { + long selectionStart = (long)value; + + // Selection offset cannot start in the middle of the data width + selectionStart -= selectionStart % HexBox._BytesPerColumn; + + // Selection start cannot be at the end of the stream so adjust by data width number of bytes + value = selectionStart.Clamp(0, HexBox.DataSource.BaseStream.Length / HexBox._BytesPerColumn * HexBox._BytesPerColumn - HexBox._BytesPerColumn); + } + else + { + value = 0L; + } + + return value; + } + + private static object CoerceSelectionEnd(DependencyObject d, object value) + { + var HexBox = (HexBox)d; + + if (HexBox.DataSource != null) + { + long selectionEnd = (long)value; + + // Selection offset cannot start in the middle of the data width + selectionEnd -= selectionEnd % HexBox._BytesPerColumn; + + // Unlike selection start the selection end can be at the end of the stream + value = selectionEnd.Clamp(0, HexBox.DataSource.BaseStream.Length / HexBox._BytesPerColumn * HexBox._BytesPerColumn); + } + else + { + value = 0L; + } + + return value; + } + + private static object CoerceOffset(DependencyObject d, object value) + { + var HexBox = (HexBox)d; + + if (HexBox.DataSource != null) + { + long offset = (long)value; + + value = offset.Clamp(0, HexBox.DataSource.BaseStream.Length); + } + else + { + value = 0L; + } + + return value; + } + + private static void OnAddressChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var HexBox = (HexBox)d; + + HexBox.SelectionStart = 0; + HexBox.SelectionEnd = 0; + + HexBox.Reflush(); + + HexBox.OnPropertyChanged(nameof(Address)); + HexBox.OnPropertyChanged(nameof(SelectedAddress)); + } + + private static void OnDataTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var HexBox = (HexBox)d; + + switch (HexBox.DataType) + { + case DataType.Int_1: + HexBox.DataWidth = 1; + break; + case DataType.Int_2: + HexBox.DataWidth = 2; + break; + case DataType.Int_4: + HexBox.DataWidth = 4; + break; + case DataType.Int_8: + HexBox.DataWidth = 8; + break; + case DataType.Float_32: + HexBox.DataWidth = 4; + break; + case DataType.Float_64: + HexBox.DataWidth = 8; + break; + } + + HexBox.Reflush(); + } + + private static void OnDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var HexBox = (HexBox)d; + + HexBox.Offset = 0; + HexBox.SelectionStart = 0; + HexBox.SelectionEnd = 0; + + HexBox.Reflush(); + } + + private void Reflush() + { + if (_Canvas != null) + { + _Canvas.Invalidate(); + } + } + + private void OnPropertyChanged([CallerMemberName] string name = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + private string ReadFormattedText() + { + StringBuilder builder = new(DataWidth); + + switch (TextFormat) + { + case TextFormat.Ascii: + { + for (var k = 0; k < DataWidth; ++k) + { + byte value = DataSource.ReadByte(); + + if (value > 31 && value < 127) + { + builder.Append(Convert.ToChar(value)); + } + else + { + builder.Append('.'); + } + } + + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(TextFormat)} value."); + } + } + + return builder.ToString(); + } + + private string ReadFormattedData() + { + string result; + + if (DataType < DataType.Float_32) + { + switch (DataFormat) + { + case DataFormat.Decimal: + { + if (DataSignedness == DataSignedness.Signed) + { + switch (DataType) + { + case DataType.Int_1: + { + result = $"{DataSource.ReadSByte():+#;-#;0}".PadLeft(4); + break; + } + + case DataType.Int_2: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadInt16(), Endianness):+#;-#;0}".PadLeft(6); + break; + } + + case DataType.Int_4: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadInt32(), Endianness):+#;-#;0}".PadLeft(11); + break; + } + + case DataType.Int_8: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadInt64(), Endianness):+#;-#;0}".PadLeft(21); + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + } + else if (DataSignedness == DataSignedness.Unsigned) + { + switch (DataType) + { + case DataType.Int_1: + { + result = $"{DataSource.ReadByte()}".PadLeft(3); + break; + } + + case DataType.Int_2: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadUInt16(), Endianness)}".PadLeft(5); + break; + } + + case DataType.Int_4: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadUInt32(), Endianness)}".PadLeft(10); + break; + } + + case DataType.Int_8: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadUInt64(), Endianness)}".PadLeft(20); + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + } + else + { + throw new InvalidOperationException($"Invalid {nameof(DataType)} value."); + } + } + break; + + case DataFormat.Hexadecimal: + { + switch (DataType) + { + case DataType.Int_1: + { + result = $"{DataSource.ReadByte(),0:X2}"; + break; + } + + case DataType.Int_2: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadUInt16(), Endianness),0:X4}"; + break; + } + + case DataType.Int_4: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadUInt32(), Endianness),0:X8}"; + break; + } + + case DataType.Int_8: + { + result = $"{EndianBitConverter.Convert(DataSource.ReadUInt64(), Endianness),0:X16}"; + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataFormat)} value."); + } + } + } + else + { + switch (DataType) + { + case DataType.Float_32: + { + var bytes = BitConverter.GetBytes(EndianBitConverter.Convert(DataSource.ReadUInt32(), Endianness)); + var value = BitConverter.ToSingle(bytes, 0); + result = $"{value:E08}".PadLeft(16); + break; + } + + case DataType.Float_64: + { + var bytes = BitConverter.GetBytes(EndianBitConverter.Convert(DataSource.ReadUInt64(), Endianness)); + var value = BitConverter.ToSingle(bytes, 0); + result = $"{value:E16}".PadLeft(24); + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + } + + return result; + } + + private void SelectAllExecuted(object sender) + { + SelectAll(); + } + + private void CopyExecuted(object sender) + { + Copy(false); + } + + private void CopyTextExecuted(object sender) + { + Copy(true); + } + + private bool SelectAllCanExecute(object sender) + { + return DataSource != null && (ShowData || ShowText); + } + + private bool CopyCanExecute(object sender) + { + return IsSelectionActive && (ShowData || ShowText); + } + + private void OnVerticalScrollBarValueChanged(object sender, RangeBaseValueChangedEventArgs e) + { + _LastVerticalScrollValue = e.OldValue; + } + + private void OnVerticalScrollBarScroll(object sender, ScrollEventArgs e) + { + long newOffset = (long)e.NewValue * _BytesPerRow; + + Offset = newOffset; + } + + private void OnVerticalScrollBarScroll(object sender, ScrollEventType type, double NewValue) + { + long newOffset = (long)NewValue * _BytesPerRow; + + Offset = newOffset; + } + + private string GetFormattedAddressText(ulong address) + { + string formattedAddressText; + + switch (AddressFormat) + { + case AddressFormat.Address16: + { + formattedAddressText = $"{address & 0xFFFF,0:X4}"; + break; + } + + case AddressFormat.Address24: + { + formattedAddressText = $"{address >> 16 & 0xFF,0:X2}:{address & 0xFFFF,0:X4}"; + break; + } + + case AddressFormat.Address32: + { + formattedAddressText = $"{address >> 16 & 0xFFFF,0:X4}:{address & 0xFFFF,0:X4}"; + break; + } + + case AddressFormat.Address48: + { + formattedAddressText = $"{address >> 32 & 0xFF,0:X4}:{address & 0xFFFFFFFF,0:X8}"; + break; + } + + case AddressFormat.Address64: + { + formattedAddressText = $"{address >> 32,0:X8}:{address & 0xFFFFFFFF,0:X8}"; + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(AddressFormat)} value."); + } + } + + return formattedAddressText; + } + + private int CalculateAddressColumnCharWidth() + { + int addressColumnCharWidth; + + switch (AddressFormat) + { + case AddressFormat.Address16: + { + addressColumnCharWidth = 4; + break; + } + + case AddressFormat.Address24: + { + addressColumnCharWidth = 7; + break; + } + + case AddressFormat.Address32: + { + addressColumnCharWidth = 9; + break; + } + + case AddressFormat.Address48: + { + addressColumnCharWidth = 13; + break; + } + + case AddressFormat.Address64: + { + addressColumnCharWidth = 17; + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(AddressFormat)} value."); + } + } + + return addressColumnCharWidth; + } + + private int CalculateDataColumnCharWidth() + { + int dataColumnCharWidth; + + if (DataType < DataType.Float_32) + { + switch (DataFormat) + { + case DataFormat.Decimal: + { + switch (DataSignedness) + { + case DataSignedness.Signed: + { + switch (DataType) + { + case DataType.Int_1: + { + dataColumnCharWidth = 4; + break; + } + + case DataType.Int_2: + { + dataColumnCharWidth = 6; + break; + } + + case DataType.Int_4: + { + dataColumnCharWidth = 11; + break; + } + + case DataType.Int_8: + { + dataColumnCharWidth = 21; + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + } + + break; + + case DataSignedness.Unsigned: + { + switch (DataType) + { + case DataType.Int_1: + { + dataColumnCharWidth = 3; + break; + } + + case DataType.Int_2: + { + dataColumnCharWidth = 5; + break; + } + + case DataType.Int_4: + { + dataColumnCharWidth = 10; + break; + } + + case DataType.Int_8: + { + dataColumnCharWidth = 20; + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + } + + break; + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataType)} value."); + } + } + } + + break; + + case DataFormat.Hexadecimal: + { + switch (DataWidth) + { + case 1: + case 2: + case 4: + case 8: + { + dataColumnCharWidth = 2 * DataWidth; + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataFormat)} value."); + } + } + } + else + { + switch (DataType) + { + case DataType.Float_32: + { + dataColumnCharWidth = 16; + break; + } + + case DataType.Float_64: + { + dataColumnCharWidth = 24; + break; + } + + default: + { + throw new InvalidOperationException($"Invalid {nameof(DataWidth)} value."); + } + } + } + return dataColumnCharWidth; + } + + private Point CalculateAddressVerticalLinePoint0() + { + Point point1 = default; + + if (ShowAddress) + { + point1.X = (CalculateAddressColumnCharWidth() + _CharsBetweenSections) * _TextMeasure.Width; + } + + return point1; + } + + private Point CalculateAddressVerticalLinePoint1() + { + Point point2 = default; + + if (ShowAddress) + { + point2.X = (CalculateAddressColumnCharWidth() + _CharsBetweenSections) * _TextMeasure.Width; + } + + point2.Y = Math.Min(_TextMeasure.Height * (MaxVisibleRows + 1), _Canvas.ActualHeight); + + return point2; + } + + private Point CalculateDataVerticalLinePoint0() + { + Point point1 = CalculateAddressVerticalLinePoint0(); + + if (ShowData) + { + point1.X += (_CharsBetweenSections + (CalculateDataColumnCharWidth() + _CharsBetweenDataColumns) * Columns - _CharsBetweenDataColumns + _CharsBetweenSections) * _TextMeasure.Width; + } + + return point1; + } + + private Point CalculateDataVerticalLinePoint1() + { + Point point2 = CalculateAddressVerticalLinePoint1(); + + if (ShowData) + { + point2.X += (_CharsBetweenSections + (CalculateDataColumnCharWidth() + _CharsBetweenDataColumns) * Columns - _CharsBetweenDataColumns + _CharsBetweenSections) * _TextMeasure.Width; + } + + return point2; + } + + private int CalculateTextColumnCharWidth() + { + return _BytesPerColumn; + } + + private Point CalculateTextVerticalLinePoint0() + { + Point point1 = CalculateDataVerticalLinePoint0(); + + if (ShowText) + { + point1.X += (_CharsBetweenSections + CalculateTextColumnCharWidth() * Columns + _CharsBetweenSections) * _TextMeasure.Width; + } + + return point1; + } + + private Point CalculateTextVerticalLinePoint1() + { + Point point2 = CalculateDataVerticalLinePoint1(); + + if (ShowText) + { + point2.X += (_CharsBetweenSections + CalculateTextColumnCharWidth() * Columns + _CharsBetweenSections) * _TextMeasure.Width; + } + + return point2; + } + + private void UpdateState() + { + UpdateMaxVisibleRowsAndColumns(); + UpdateScrollBar(); + UpdateColumnsLayout(); + } + + private void UpdateColumnsLayout() + { + var p0 = CalculateAddressVerticalLinePoint0(); + var p1 = CalculateAddressVerticalLinePoint1(); + _AddressRect = new(p0, p1); + + p0 = CalculateDataVerticalLinePoint0(); + p1 = CalculateDataVerticalLinePoint1(); + _DataRect = new(p0, p1); + + p0 = CalculateTextVerticalLinePoint0(); + p1 = CalculateTextVerticalLinePoint1(); + _TextRect = new(p0, p1); + } + + private void UpdateMaxVisibleRowsAndColumns() + { + int maxVisibleRows = 0; + int maxVisibleColumns = 0; + + if ((ShowAddress || ShowData || ShowText) && _Canvas != null) + { + { + SKRect cellSize = new(); + string bigChars = "0123456789abcdef ABCDEF"; + for (int i = 0; i < bigChars.Length; i++) + { + var s = bigChars.Substring(i, 1); + if (_TextPaint.ContainsGlyphs(s)) // if the font does not contain the glyph, then skip it + { + var rect = new SKRect(); + _TextPaint.MeasureText(s, ref rect); + cellSize.Union(rect); + } + } + _TextMeasure = cellSize; + } + + _TextMeasure.Bottom = _TextMeasure.Height; /* 2 * line font height */ + + maxVisibleRows = Math.Max(0, (int)(_Canvas.ActualHeight / _TextMeasure.Height)); + + if (ShowData || ShowText) + { + int charsPerRow = (int)(_Canvas.ActualWidth / _TextMeasure.Width); + + if (ShowAddress) + { + charsPerRow -= CalculateAddressColumnCharWidth() + 2 * _CharsBetweenSections; + } + + if (ShowData && ShowText) + { + charsPerRow -= 3 * _CharsBetweenSections; + } + + int charsPerColumn = 0; + + if (ShowData) + { + charsPerColumn += CalculateDataColumnCharWidth() + _CharsBetweenDataColumns; + } + + if (ShowText) + { + charsPerColumn += CalculateTextColumnCharWidth(); + } + + if (charsPerColumn != 0) + { + maxVisibleColumns = Math.Max(0, charsPerRow / charsPerColumn); + } + } + else + { + maxVisibleColumns = 0; + } + } + + MaxVisibleRows = maxVisibleRows; + MaxVisibleColumns = maxVisibleColumns; + + // Maximum visible rows has now changed and so we must update the maximum amount we should scroll by + _ScrollBar.LargeChange = maxVisibleRows; + } + + private void UpdateScrollBar() + { + if ((ShowAddress || ShowData || ShowText) && DataSource != null && Columns > 0 && MaxVisibleRows > 0) + { + long q = DataSource.BaseStream.Length / _BytesPerRow; + long r = DataSource.BaseStream.Length % _BytesPerRow; + + // Each scroll value represents a single drawn row + _ScrollBar.Maximum = q + (r > 0 ? 1 : 0) - MaxVisibleRows; + + // Adjust the scroll value based on the current offset + _ScrollBar.Value = Offset / _BytesPerRow; + + // Adjust again to compensate for residual bytes if the number of bytes between the start of the stream + // and the current offset is less than the number of bytes we can display per row + if (_ScrollBar.Value == 0 && Offset > 0) + { + ++_ScrollBar.Value; + } + } + else + { + _ScrollBar.Maximum = 0; + } + } + + private long ConvertPositionToOffset(Point position) + { + long offset = Offset; + + switch (_HighlightBegin) + { + case SelectionArea.Address: + { + // Clamp the Y coordinate to within the address region + position.Y = position.Y.Clamp(_AddressRect.Top, _AddressRect.Bottom); + + // Convert the Y coordinate to the row number + position.Y /= _TextMeasure.Height; + + if (position.Y >= MaxVisibleRows) + { + // Due to floating point rounding we may end up with exactly the maximum number of rows, so adjust to compensate + --position.Y; + } + + offset += _BytesPerRow * (long)position.Y; + } + + break; + + case SelectionArea.Data: + { + var pix_CharsBetweenSections = _CharsBetweenSections * _TextMeasure.Width; + + // Clamp the X coordinate to within the data region + position.X = position.X.Clamp(_AddressRect.Left + pix_CharsBetweenSections, _DataRect.Left - pix_CharsBetweenSections); + + // Normalize with respect to the data region + position.X -= _AddressRect.Left + pix_CharsBetweenSections; + + // Convert the X coordinate to the column number + position.X /= (CalculateDataColumnCharWidth() + _CharsBetweenDataColumns) * _TextMeasure.Width; + + if (position.X >= Columns) + { + // Due to floating point rounding we may end up with exactly the maximum number of columns, so adjust to compensate + --position.X; + } + + // Clamp the Y coordinate to within the data region + position.Y = position.Y.Clamp(_DataRect.Top, _DataRect.Bottom); + + // Convert the Y coordinate to the row number + position.Y /= _TextMeasure.Height; + + if (position.Y >= MaxVisibleRows) + { + // Due to floating point rounding we may end up with exactly the maximum number of rows, so adjust to compensate + --position.Y; + } + + offset += ((long)position.Y * Columns + (long)position.X) * _BytesPerColumn; + } + + break; + + case SelectionArea.Text: + { + var pix_CharsBetweenSections = _CharsBetweenSections * _TextMeasure.Width; + + // Clamp the X coordinate to within the text region + position.X = position.X.Clamp(_DataRect.Left + pix_CharsBetweenSections, _TextRect.Left - pix_CharsBetweenSections); + + // Normalize with respect to the text region + position.X -= _DataRect.Left + pix_CharsBetweenSections; + + // Convert the X coordinate to the column number + position.X /= CalculateTextColumnCharWidth() * _TextMeasure.Width; + + if (position.X >= Columns) + { + // Due to floating point rounding we may end up with exactly the maximum number of columns, so + // adjust to compensate + --position.X; + } + + // Clamp the Y coordinate to within the text region + position.Y = position.Y.Clamp(_TextRect.Top, _TextRect.Bottom); + + // Convert the Y coordinate to the row number + position.Y /= _TextMeasure.Height; + + if (position.Y >= MaxVisibleRows) + { + // Due to floating point rounding we may end up with exactly the maximum number of rows, so adjust to compensate + --position.Y; + } + + offset += ((long)position.Y * Columns + (long)position.X) * _BytesPerColumn; + } + + break; + + default: + { + throw new InvalidOperationException($"Invalid highlight state ${_HighlightState}"); + } + } + + return offset; + } + + private Point ConvertOffsetToPosition(long offset, SelectionArea relativeTo) + { + Point position = default; + + switch (relativeTo) + { + case SelectionArea.Data: + { + position.X = _AddressRect.Left + _CharsBetweenSections * _TextMeasure.Width; + position.Y = _AddressRect.Top; + + // Normalize requested offset to a zero based column + long normalizedColumn = (offset - Offset) / _BytesPerColumn; + + position.X += (normalizedColumn % Columns + Columns) % Columns * (CalculateDataColumnCharWidth() + _CharsBetweenDataColumns) * _TextMeasure.Width; + + if (normalizedColumn < 0) + { + // Negative normalized offset means the Y position is above the current offset. Because division + // rounds toward zero we need to compensate here. + position.Y += ((normalizedColumn + 1) / Columns - 1) * _TextMeasure.Height; + } + else + { + position.Y += normalizedColumn / Columns * _TextMeasure.Height; + } + } + + break; + + case SelectionArea.Text: + { + position.X = _DataRect.Left + _CharsBetweenSections * _TextMeasure.Width; + position.Y = _DataRect.Top; + + // Normalize requested offset to a zero based column + long normalizedColumn = (offset - Offset) / _BytesPerColumn; + + position.X += (normalizedColumn % Columns + Columns) % Columns * CalculateTextColumnCharWidth() * _TextMeasure.Width; + + if (normalizedColumn < 0) + { + // Negative normalized offset means the Y position is above the current offset. Because division + // rounds toward zero we need to compensate here. + position.Y += ((normalizedColumn + 1) / Columns - 1) * _TextMeasure.Height; + } + else + { + position.Y += normalizedColumn / Columns * _TextMeasure.Height; + } + } + + break; + + default: + { + throw new ArgumentException($"Invalid relative area {relativeTo}", nameof(relativeTo)); + } + } + + return position; + } + + private bool IsOffsetVisible(long offset) + { + long maxBytesDisplayed = _BytesPerRow * MaxVisibleRows; + + return Offset <= offset && Offset + maxBytesDisplayed >= offset; + } + + /// + /// Show the context menu programatical. + /// Invoked if Application key or SCHIFT+F10 is pressed. + /// + private void ShowContextMenu() + { + // Get offset for context menu + var lastVisibleOffset = Offset + (_BytesPerRow * MaxVisibleRows) - 1; + var offset = Math.Max(Math.Max(SelectionStart, SelectionEnd), Offset); + var palcementOffset = Math.Min(offset, lastVisibleOffset); + + // Show menu + if (ShowData) + { + _Canvas.ContextFlyout.ShowAt(_Canvas, new FlyoutShowOptions + { + Position = ConvertOffsetToPosition(palcementOffset, SelectionArea.Data), + }); + } + else if (ShowText) + { + _Canvas.ContextFlyout.ShowAt(_Canvas, new FlyoutShowOptions + { + Position = ConvertOffsetToPosition(palcementOffset, SelectionArea.Text), + }); + } + else + { + _Canvas.ContextFlyout.ShowAt(_Canvas, new FlyoutShowOptions + { + Position = new Point(0, 0), + }); + } + } + + /// + /// Initializes static members of the class. + /// + public HexBox() + { + DefaultStyleKey = typeof(HexBox); + } + } + +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/EndianBinaryReader.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/EndianBinaryReader.cs new file mode 100644 index 0000000000..1ba0658bd7 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/EndianBinaryReader.cs @@ -0,0 +1,231 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox.Library.EndianConvert +{ + using System; + using System.IO; + using System.Text; + + /// + /// Reads primitive data types as binary values in a specific encoding and endianness. + /// + public class EndianBinaryReader : BinaryReader + { + /// + /// Initializes a new instance of the class based on the specified stream, endianness, and using UTF-8 + /// encoding. + /// + /// + /// + /// The input stream. + /// + /// + /// + /// The endianness of the data in the input stream. + /// + /// + /// + /// The stream does not support reading, is null, or is already closed. + /// + public EndianBinaryReader(Stream input, Endianness endianness) + : this(input, endianness, Encoding.UTF8) + { + // Void + } + + /// + /// Initializes a new instance of the class based on the specified stream, endianness, and character + /// encoding. + /// + /// + /// + /// The input stream. + /// + /// + /// + /// The endianness of the data in the input stream. + /// + /// + /// + /// The character encoding to use. + /// + /// + /// + /// The stream does not support reading, is null, or is already closed. + /// + public EndianBinaryReader(Stream input, Endianness endianness, Encoding encoding) + : this(input, endianness, encoding, false) + { + // Void + } + + /// + /// Initializes a new instance of the class based on the specified stream, endianness, and character + /// encoding, and optionally leaves the stream open. + /// + /// + /// + /// The input stream. + /// + /// + /// + /// The endianness of the data in the input stream. + /// + /// + /// + /// The character encoding to use. + /// + /// + /// + /// true to leave the stream open after the object is disposed; false otherwise. + /// + /// + /// + /// The stream does not support reading, is null, or is already closed. + /// + public EndianBinaryReader(Stream input, Endianness endianness, Encoding encoding, bool leaveOpen) + : base(input, encoding, leaveOpen) + { + Endianness = endianness; + } + + /// + /// Gets the endianness of the data in the input stream. + /// + public Endianness Endianness + { + get; + } + + /// + /// Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes. + /// + /// + /// + /// A decimal value read from the current stream. + /// + public override decimal ReadDecimal() + { + throw new NotImplementedException(); + } + + /// + /// Reads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes. + /// + /// + /// + /// An 8-byte floating point value read from the current stream. + /// + public override double ReadDouble() + { + throw new NotImplementedException(); + } + + /// + /// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes. + /// + /// + /// + /// A 2-byte signed integer read from the current stream. + /// + public override short ReadInt16() + { + return EndianBitConverter.Convert(base.ReadInt16(), Endianness); + } + + /// + /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes. + /// + /// + /// + /// A 4-byte signed integer read from the current stream. + /// + public override int ReadInt32() + { + return EndianBitConverter.Convert(base.ReadInt32(), Endianness); + } + + /// + /// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes. + /// + /// + /// + /// An 8-byte signed integer read from the current stream. + /// + public override long ReadInt64() + { + return EndianBitConverter.Convert(base.ReadInt64(), Endianness); + } + + /// + /// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes. + /// + /// + /// + /// A 4-byte floating point value read from the current stream. + /// + public override float ReadSingle() + { + throw new NotImplementedException(); + } + + /// + /// Reads a string from the current stream. The string is prefixed with the length, encoded as an integer seven bits at a time. + /// + /// + /// + /// The string being read. + /// + public override string ReadString() + { + throw new NotImplementedException(); + } + + /// + /// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by + /// two bytes. + /// + /// + /// + /// A 2-byte unsigned integer read from this stream. + /// + public override ushort ReadUInt16() + { + return EndianBitConverter.Convert(base.ReadUInt16(), Endianness); + } + + /// + /// Reads a 4-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by + /// four bytes. + /// + /// + /// + /// A 4-byte unsigned integer read from this stream. + /// + public override uint ReadUInt32() + { + return EndianBitConverter.Convert(base.ReadUInt32(), Endianness); + } + + /// + /// Reads an 8-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by + /// eight bytes. + /// + /// + /// + /// An 8-byte unsigned integer read from this stream. + /// + public override ulong ReadUInt64() + { + return EndianBitConverter.Convert(base.ReadUInt64(), Endianness); + } + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/EndianBitConverter.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/EndianBitConverter.cs new file mode 100644 index 0000000000..dde0287c2d --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/EndianBitConverter.cs @@ -0,0 +1,186 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox.Library.EndianConvert +{ + using System; + + /// + /// Converts integral values to the native endianness of this computer architecture. + /// + public static class EndianBitConverter + { + /// + /// Gets the native endianness of this computer architecture. + /// + public static readonly Endianness NativeEndianness = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + + /// + /// Converts a value from the specified endianness to the native endianness. + /// + /// + /// + /// The value to convert. + /// + /// + /// + /// The endianness of . + /// + /// + /// + /// The value converted from the specified endianness to the native endianness (). + /// + public static ushort Convert(ushort value, Endianness endianness) + { + if (endianness == NativeEndianness) + { + return value; + } + else + { + unchecked + { + return (ushort)((value & 0x00FFU) << 8 | + (value & 0xFF00U) >> 8); + } + } + } + + /// + /// Converts a value from the specified endianness to the native endianness. + /// + /// + /// + /// The value to convert. + /// + /// + /// + /// The endianness of . + /// + /// + /// + /// The value converted from the specified endianness to the native endianness (). + /// + public static uint Convert(uint value, Endianness endianness) + { + if (endianness == NativeEndianness) + { + return value; + } + else + { + unchecked + { + return (value & 0x000000FFU) << 24 | + (value & 0xFF000000U) >> 24 | + (value & 0x0000FF00U) << 8 | + (value & 0x00FF0000U) >> 8; + } + } + } + + /// + /// Converts a value from the specified endianness to the native endianness. + /// + /// + /// + /// The value to convert. + /// + /// + /// + /// The endianness of . + /// + /// + /// + /// The value converted from the specified endianness to the native endianness (). + /// + public static ulong Convert(ulong value, Endianness endianness) + { + if (endianness == NativeEndianness) + { + return value; + } + else + { + unchecked + { + return (value & 0x00000000000000FFUL) << 56 | + (value & 0xFF00000000000000UL) >> 56 | + (value & 0x000000000000FF00UL) << 40 | + (value & 0x00FF000000000000UL) >> 40 | + (value & 0x0000000000FF0000UL) << 24 | + (value & 0x0000FF0000000000UL) >> 24 | + (value & 0x00000000FF000000UL) << 8 | + (value & 0x000000FF00000000UL) >> 8; + } + } + } + + /// + /// Converts a value from the specified endianness to the native endianness. + /// + /// + /// + /// The value to convert. + /// + /// + /// + /// The endianness of . + /// + /// + /// + /// The value converted from the specified endianness to the native endianness (). + /// + public static short Convert(short value, Endianness endianness) + { + return (short)Convert((ushort)value, endianness); + } + + /// + /// Converts a value from the specified endianness to the native endianness. + /// + /// + /// + /// The value to convert. + /// + /// + /// + /// The endianness of . + /// + /// + /// + /// The value converted from the specified endianness to the native endianness (). + /// + public static int Convert(int value, Endianness endianness) + { + return (int)Convert((uint)value, endianness); + } + + /// + /// Converts a value from the specified endianness to the native endianness. + /// + /// + /// + /// The value to convert. + /// + /// + /// + /// The endianness of . + /// + /// + /// + /// The value converted from the specified endianness to the native endianness (). + /// + public static long Convert(long value, Endianness endianness) + { + return (long)Convert((ulong)value, endianness); + } + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/Endianness.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/Endianness.cs new file mode 100644 index 0000000000..7293e8b9d1 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/Endianness.cs @@ -0,0 +1,28 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox.Library.EndianConvert +{ + /// + /// Represents the endianness of a value in a computer architecture. + /// + public enum Endianness + { + /// + /// Most significant byte first. + /// + BigEndian, + + /// + /// Least significant byte first. + /// + LittleEndian, + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/FileFormatException.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/FileFormatException.cs new file mode 100644 index 0000000000..81b96c3858 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Library/EndianConvert/FileFormatException.cs @@ -0,0 +1,60 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox.Library.EndianConvert +{ + using System; + + /// + /// The exception that is thrown when an input file or a data stream is malformed. + /// + [Serializable] + public sealed class FileFormatException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public FileFormatException() + { + // Void + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// + /// + /// The message that describes the error. + /// + public FileFormatException(string message) + : base(message) + { + // Void + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner + /// exception that is the cause of this exception. + /// + /// + /// + /// The message that describes the error. + /// + /// + /// + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + /// + public FileFormatException(string message, Exception innerException) + : base(message, innerException) + { + // Void + } + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/TextFormat.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/TextFormat.cs new file mode 100644 index 0000000000..ca5cc99683 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/TextFormat.cs @@ -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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox +{ + /// + /// Enumerates the text section encodings/formats that the control is able to display. + /// + public enum TextFormat + { + /// + /// Display data in ASCII (ISO-8859-1) encoding. + /// + Ascii, + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Themes/Generic.xaml b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Themes/Generic.xaml new file mode 100644 index 0000000000..8b2208128d --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Themes/Generic.xaml @@ -0,0 +1,170 @@ + + + + diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/TypeConverters.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/TypeConverters.cs new file mode 100644 index 0000000000..bed7841cab --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/TypeConverters.cs @@ -0,0 +1,264 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// +#pragma warning disable SA1210 // Using directives should be ordered alphabetically by namespace +#pragma warning disable SA1208 // System using directives should be placed before other using directives +using RegistryPreviewUILib.HexBox.Library.EndianConvert; +using Microsoft.UI.Xaml.Data; +using System; +#pragma warning restore SA1208 // System using directives should be placed before other using directives +#pragma warning restore SA1210 // Using directives should be ordered alphabetically by namespace + +namespace RegistryPreviewUILib.HexBox +{ + public partial class HexboxDataTypeConverter : IValueConverter + { + /// + /// Convert a DataType value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is DataType b) + { + if (parameter is string c) + { + return c == b.ToString(); + } + } + throw new NotImplementedException(); + } + + /// + /// Convert back a DataType value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + if (value is bool b && parameter is string c) + { + if(c == "Int_1") + { + return DataType.Int_1; + } + else if (c == "Int_2") + { + return DataType.Int_2; + } + else if (c == "Int_4") + { + return DataType.Int_4; + } + else if (c == "Int_8") + { + return DataType.Int_8; + } + else if (c == "Float_32") + { + return DataType.Float_32; + } + else /*if (c == "Float_64")*/ + { + return DataType.Float_64; + } + } + throw new NotImplementedException(); + } + } + + public class HexboxDataSignednessConverter : IValueConverter + { + /// + /// Convert a DataSignedness value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is DataSignedness b) + { + if (parameter is string c) + { + var end = c == "Signed" ? DataSignedness.Signed : DataSignedness.Unsigned; + return (b == end); + } + } + throw new NotImplementedException(); + } + + /// + /// Convert back a DataSignedness value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + if (value is bool b && parameter is string c) + { + var end = c == "Signed" ? DataSignedness.Signed : DataSignedness.Unsigned; + if (b) + { + return end; + } + else + { + return c == "Signed" ? DataSignedness.Unsigned : DataSignedness.Signed; + } + } + throw new NotImplementedException(); + } + } + + public class HexboxDataFormatBoolConverter : IValueConverter + { + /// + /// Convert a DataFormat value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object Convert(object value, Type targetType, object parameter, string language) + { + if(value is DataFormat f) + { + return f != DataFormat.Hexadecimal; + } + throw new NotImplementedException(); + } + + /// + /// Convert back a DataFormat value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } + + + public class HexboxDataFormatConverter : IValueConverter + { + /// + /// Convert a DataFormat value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is DataFormat b) + { + if (parameter is string c) + { + var end = c == "Decimal" ? DataFormat.Decimal: DataFormat.Hexadecimal; + return (b == end); + } + } + throw new NotImplementedException(); + } + + /// + /// Convert back a DataFormat value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + if (value is bool b && parameter is string c) + { + var end = c == "Decimal" ? DataFormat.Decimal : DataFormat.Hexadecimal; + if (b) + { + return end; + } + else + { + return end == DataFormat.Decimal ? DataFormat.Hexadecimal : DataFormat.Decimal; + } + } + throw new NotImplementedException(); + } + } + + + public class BigEndianConverter : IValueConverter + { + /// + /// Convert a Endian value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is Endianness b) + { + if (parameter is string c) + { + var end = c == "BigEndian" ? Endianness.BigEndian : Endianness.LittleEndian; + return (b == end); + } + } + throw new NotImplementedException(); + } + + /// + /// Convert back a Endian value to its negation. + /// + /// The value to negate. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used + /// The value to be passed to the target dependency property. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + if (value is bool b && parameter is string c) + { + var end = c == "BigEndian" ? Endianness.BigEndian : Endianness.LittleEndian; + if (b) + { + return end; + } + else + { + return end == Endianness.BigEndian ? Endianness.LittleEndian : Endianness.BigEndian; + } + } + throw new NotImplementedException(); + } + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Utilities.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Utilities.cs new file mode 100644 index 0000000000..d2e4795038 --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/Utilities.cs @@ -0,0 +1,81 @@ +// 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. + +// +// 2020-... created by Filip Jeremic (fjeremic) as "HexView.Wpf". +// 2024-... republished by @hotkidfamily as "HexBox.WinUI". +// 2025 Included in PowerToys. (Branch master; commit 72dcf64dc858c693a7a16887004c8ddbab61fce7.) +// + +namespace RegistryPreviewUILib.HexBox +{ + using System; + + /// + /// A utility class with miscellaneous methods. + /// + internal static class Utilities + { + /// + /// Clamps the to the range [, ]. + /// + /// + /// + /// The type of the value to clamp. + /// + /// + /// + /// The value to clamp. + /// + /// + /// + /// The upper bound on the clamped value. + /// + /// + /// + /// The lower bound on the clmaped value. + /// + /// + /// + /// The nearest value of in the range [, + /// ]. + /// + public static T Clamp(this T value, T min, T max) + where T : IComparable + { + return value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value; + } + + /// + /// Calculates the arithmetic modulus of modulo . + /// + /// + /// + /// The type of the values. + /// + /// + /// + /// The value to compute the modulus of. + /// + /// + /// + /// The modulus. + /// + /// + /// + /// The non-negative value r such that for some integral value q: + /// = q*m + r. + /// + public static T Mod(this T n, T m) + where T : IComparable + { + dynamic dn = n; + dynamic dm = m; + + dynamic dr = dn % dm; + + return dr.CompareTo(0) < 0 ? dr + dm : dr; + } + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/MonacoEditorControl.xaml b/src/modules/registrypreview/RegistryPreviewUILib/Controls/MonacoEditor/MonacoEditorControl.xaml similarity index 100% rename from src/modules/registrypreview/RegistryPreviewUILib/MonacoEditorControl.xaml rename to src/modules/registrypreview/RegistryPreviewUILib/Controls/MonacoEditor/MonacoEditorControl.xaml diff --git a/src/modules/registrypreview/RegistryPreviewUILib/MonacoEditorControl.xaml.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/MonacoEditor/MonacoEditorControl.xaml.cs similarity index 100% rename from src/modules/registrypreview/RegistryPreviewUILib/MonacoEditorControl.xaml.cs rename to src/modules/registrypreview/RegistryPreviewUILib/Controls/MonacoEditor/MonacoEditorControl.xaml.cs diff --git a/src/modules/registrypreview/RegistryPreviewUILib/MonacoHelper.cs b/src/modules/registrypreview/RegistryPreviewUILib/Controls/MonacoEditor/MonacoHelper.cs similarity index 100% rename from src/modules/registrypreview/RegistryPreviewUILib/MonacoHelper.cs rename to src/modules/registrypreview/RegistryPreviewUILib/Controls/MonacoEditor/MonacoHelper.cs diff --git a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.DataPreview.cs b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.DataPreview.cs new file mode 100644 index 0000000000..5cdffda4dd --- /dev/null +++ b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.DataPreview.cs @@ -0,0 +1,341 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.Windows.ApplicationModel.Resources; +using Windows.Foundation.Metadata; +using HB = RegistryPreviewUILib.HexBox; + +namespace RegistryPreviewUILib +{ + public sealed partial class RegistryPreviewMainPage : Page + { + private static bool _isDataPreviewHexBoxLoaded; + + internal async Task ShowExtendedDataPreview(string name, string type, string value) + { + // Create dialog + _isDataPreviewHexBoxLoaded = false; + var panel = new StackPanel() + { + Spacing = 16, + Padding = new Thickness(0), + }; + ContentDialog contentDialog = new ContentDialog() + { + Title = resourceLoader.GetString("DataPreviewTitle") + " - " + name, + Content = panel, + CloseButtonText = resourceLoader.GetString("DataPreviewClose"), + DefaultButton = ContentDialogButton.Primary, + Padding = new Thickness(0), + }; + contentDialog.Opened += ExtendedDataPreview_Opened; + + // Add content based on value type + switch (type) + { + case "REG_DWORD": + case "REG_QWORD": + AddHexView(ref panel, ref resourceLoader, value); + break; + case "REG_NONE": + case "REG_BINARY": + // Convert value to BinaryReader + byte[] byteArray = Convert.FromHexString(value.Replace(" ", string.Empty)); + MemoryStream memoryStream = new MemoryStream(byteArray); + BinaryReader binaryData = new BinaryReader(memoryStream); + binaryData.ReadBytes(byteArray.Length); + + // Convert value to text + // For more printable asci characters the following code lines are required: + // var cpW1252 = CodePagesEncodingProvider.Instance.GetEncoding(1252); + // || b == 128 || (b >= 130 && b <= 140) || b == 142 || (b >= 145 & b <= 156) || b >= 158 + // cpW1252.GetString([b]); + string binaryDataText = string.Empty; + foreach (byte b in byteArray) + { + // ASCII codes: + // 9, 10, 13: Space, Line Feed, Carriage Return + // 32-126: Printable characters + // 128, 130-140, 142, 145-156, 158-255: Extended printable characters + if (b == 9 || b == 10 || b == 13 || (b >= 32 && b <= 126)) + { + binaryDataText += Convert.ToChar(b); + } + } + + // Add controls + AddBinaryView(ref panel, ref resourceLoader, ref binaryData, binaryDataText); + break; + case "REG_MULTI_SZ": + var multiLineBox = new TextBox() + { + IsReadOnly = true, + AcceptsReturn = true, + TextWrapping = TextWrapping.NoWrap, + MaxHeight = 200, + FontSize = 14, + RequestedTheme = panel.ActualTheme, + Text = value, + }; + ScrollViewer.SetVerticalScrollBarVisibility(multiLineBox, ScrollBarVisibility.Auto); + ScrollViewer.SetHorizontalScrollBarVisibility(multiLineBox, ScrollBarVisibility.Auto); + AutomationProperties.SetName(multiLineBox, resourceLoader.GetString("DataPreview_AutomationPropertiesName_MultilineTextValue")); + panel.Children.Add(multiLineBox); + break; + case "REG_EXPAND_SZ": + AddExpandStringView(ref panel, ref resourceLoader, value); + break; + default: // REG_SZ + var stringBox = new TextBox() + { + IsReadOnly = true, + FontSize = 14, + RequestedTheme = panel.ActualTheme, + Text = value, + }; + AutomationProperties.SetName(stringBox, resourceLoader.GetString("DataPreview_AutomationPropertiesName_TextValue")); + panel.Children.Add(stringBox); + break; + } + + // Use this code to associate the dialog to the appropriate AppWindow by setting + // the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow. + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) + { + contentDialog.XamlRoot = this.Content.XamlRoot; + } + + // Show dialog and wait. + ChangeCursor(gridPreview, false); + _ = await contentDialog.ShowAsync(); + } + + private static void AddHexView(ref StackPanel panel, ref ResourceLoader resourceLoader, string value) + { + var hexBox = new TextBox() + { + Header = resourceLoader.GetString("DataPreviewHex"), + IsReadOnly = true, + FontSize = 14, + RequestedTheme = panel.ActualTheme, + Text = value.Split(" ")[0], + }; + var decimalBox = new TextBox() + { + Header = resourceLoader.GetString("DataPreviewDec"), + IsReadOnly = true, + FontSize = 14, + RequestedTheme = panel.ActualTheme, + Text = value.Split(" ")[1].TrimStart('(').TrimEnd(')'), + }; + panel.Children.Add(hexBox); + panel.Children.Add(decimalBox); + } + + private static void AddBinaryView(ref StackPanel panel, ref ResourceLoader resourceLoader, ref BinaryReader data, string dataText) + { + // Create SelectorBar + var navBar = new SelectorBar() + { + RequestedTheme = panel.ActualTheme, + }; + navBar.SelectionChanged += BinaryPreview_SelectorChanged; + navBar.Items.Add(new SelectorBarItem() + { + Text = resourceLoader.GetString("DataPreviewDataView"), + Tag = "DataView", + FontSize = 14, + RequestedTheme = panel.ActualTheme, + IsSelected = true, + }); + navBar.Items.Add(new SelectorBarItem() + { + Text = resourceLoader.GetString("DataPreviewVisibleText"), + Tag = "TextView", + FontSize = 14, + RequestedTheme = panel.ActualTheme, + IsSelected = false, + IsEnabled = !string.IsNullOrWhiteSpace(dataText), + }); + + // Create HexBox + var binaryPreviewBox = new HB.HexBox() + { + Height = 300, + Width = 495, + ShowAddress = true, + ShowData = true, + ShowText = true, + Columns = 8, + FontSize = 13, + RequestedTheme = panel.ActualTheme, + AddressBrush = (SolidColorBrush)Application.Current.Resources["AccentTextFillColorPrimaryBrush"], + AlternatingDataColumnTextBrush = (SolidColorBrush)Application.Current.Resources["TextFillColorSecondaryBrush"], + SelectionTextBrush = (SolidColorBrush)Application.Current.Resources["HexBox_SelectionTextBrush"], + SelectionBrush = (SolidColorBrush)Application.Current.Resources["HexBox_SelectionBackgroundBrush"], + VerticalSeparatorLineBrush = (SolidColorBrush)Application.Current.Resources["HexBox_VerticalLineBrush"], + BorderBrush = (LinearGradientBrush)Application.Current.Resources["HexBox_ControlBorderBrush"], + BorderThickness = (Thickness)Application.Current.Resources["HexBox_ControlBorderThickness"], + CornerRadius = (CornerRadius)Application.Current.Resources["ControlCornerRadius"], + DataFormat = HB.DataFormat.Hexadecimal, + DataSignedness = HB.DataSignedness.Unsigned, + DataType = HB.DataType.Int_1, + EnforceProperties = true, + Visibility = Visibility.Collapsed, + DataSource = data, + }; + AutomationProperties.SetName(binaryPreviewBox, resourceLoader.GetString("DataPreview_AutomationPropertiesName_BinaryDataPreview")); + binaryPreviewBox.Loaded += BinaryPreview_HexBoxLoaded; + binaryPreviewBox.GotFocus += BinaryPreview_HexBoxFocused; + binaryPreviewBox.LostFocus += BinaryPreview_HexBoxFocusLost; + + // Create TextBox + var visibleText = new TextBox() + { + IsReadOnly = true, + AcceptsReturn = true, + TextWrapping = TextWrapping.Wrap, + Height = 300, + Width = 495, + FontSize = 13, + Text = dataText, + RequestedTheme = panel.ActualTheme, + Visibility = Visibility.Collapsed, + }; + AutomationProperties.SetName(visibleText, resourceLoader.GetString("DataPreview_AutomationPropertiesName_VisibleTextPreview")); + + // Add controls: 0 = SelectorBar, 1 = ProgressRing, 2 = HexBox, 3 = TextBox + panel.Children.Add(navBar); + panel.Children.Add(new ProgressRing()); + panel.Children.Add(binaryPreviewBox); + panel.Children.Add(visibleText); + } + + private static void AddExpandStringView(ref StackPanel panel, ref ResourceLoader resourceLoader, string value) + { + var stringBoxRaw = new TextBox() + { + Header = resourceLoader.GetString("DataPreviewRawValue"), + IsReadOnly = true, + FontSize = 14, + RequestedTheme = panel.ActualTheme, + Text = value, + }; + var stringBoxExp = new TextBox() + { + Header = resourceLoader.GetString("DataPreviewExpandedValue"), + IsReadOnly = true, + FontSize = 14, + RequestedTheme = panel.ActualTheme, + Text = Environment.ExpandEnvironmentVariables(value), + }; + panel.Children.Add(stringBoxRaw); + panel.Children.Add(stringBoxExp); + } + + private static void BinaryPreview_SelectorChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args) + { + // Child controls: 0 = SelectorBar, 1 = ProgressRing, 2 = HexBox, 3 = TextBox + var stackPanel = sender.Parent as StackPanel; + var progressRing = (ProgressRing)stackPanel.Children[1]; + var hexBox = (HB.HexBox)stackPanel.Children[2]; + var textBox = (TextBox)stackPanel.Children[3]; + + if (sender.SelectedItem.Tag.ToString() == "DataView") + { + textBox.Visibility = Visibility.Collapsed; + if (_isDataPreviewHexBoxLoaded) + { + progressRing.Visibility = Visibility.Collapsed; + hexBox.Visibility = Visibility.Visible; + + // Clear selection aligned to TextBox + hexBox.ClearSelection(); + hexBox.Focus(FocusState.Programmatic); + } + else + { + hexBox.Visibility = Visibility.Collapsed; + progressRing.Visibility = Visibility.Visible; + progressRing.Focus(FocusState.Programmatic); + } + } + else + { + progressRing.Visibility = Visibility.Collapsed; + + hexBox.Visibility = Visibility.Collapsed; + textBox.Visibility = Visibility.Visible; + + // Workaround for wrong text selection (color) after switching back to "Visible text" + textBox.Focus(FocusState.Programmatic); + textBox.Select(0, 0); + } + } + + private static void BinaryPreview_HexBoxLoaded(object sender, RoutedEventArgs e) + { + _isDataPreviewHexBoxLoaded = true; + + // Child controls: 0 = SelectorBar, 1 = ProgressRing, 2 = HexBox, 3 = TextBox + var hexBox = (HB.HexBox)sender; + var stackPanel = hexBox.Parent as StackPanel; + var selectorBar = stackPanel.Children[0] as SelectorBar; + var progressRing = stackPanel.Children[1] as ProgressRing; + + if (selectorBar.SelectedItem.Tag.ToString() == "DataView") + { + progressRing.Visibility = Visibility.Collapsed; + hexBox.Visibility = Visibility.Visible; + } + } + + /// + /// Event handler to set correct control border if focused. + /// + private static void BinaryPreview_HexBoxFocused(object sender, RoutedEventArgs e) + { + var hexBox = (HB.HexBox)sender; + + hexBox.BorderThickness = (Thickness)Application.Current.Resources["HexBox_ControlBorderFocusedThickness"]; + hexBox.BorderBrush = (LinearGradientBrush)Application.Current.Resources["HexBox_ControlBorderFocusedBrush"]; + } + + /// + /// Event handler to set correct control border if not focused. + /// + private static void BinaryPreview_HexBoxFocusLost(object sender, RoutedEventArgs e) + { + var hexBox = (HB.HexBox)sender; + + // Workaround: Verify that the newly focused control isn't the context menu of the HexBox control + if (FocusManager.GetFocusedElement(hexBox.XamlRoot).GetType() != typeof(MenuFlyoutPresenter)) + { + hexBox.BorderThickness = (Thickness)Application.Current.Resources["HexBox_ControlBorderThickness"]; + hexBox.BorderBrush = (LinearGradientBrush)Application.Current.Resources["HexBox_ControlBorderBrush"]; + } + } + + /// + /// Make sure that for REG_Binary preview the HexBox control is focused after opening. + /// + private static void ExtendedDataPreview_Opened(ContentDialog sender, ContentDialogOpenedEventArgs e) + { + // If <_isDataPreviewHexBoxLoaded == true> then we have the right content on the dialog. + if (_isDataPreviewHexBoxLoaded) + { + // Child controls: 0 = SelectorBar, 1 = ProgressRing, 2 = HexBox, 3 = TextBox + (sender.Content as StackPanel).Children[2].Focus(FocusState.Programmatic); + } + } + } +} diff --git a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs index 20009533c1..b2bf8ea277 100644 --- a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs +++ b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; - using CommunityToolkit.WinUI.UI.Controls; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; @@ -23,6 +22,10 @@ namespace RegistryPreviewUILib { private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + // Indicator if we loaded/reloaded/saved a file and need to skip TextChanged event one time. + // (Solves the problem that enabling the event handler fires it one time.) + private static bool editorContentChangedScripted; + /// /// Event that is will prevent the app from closing if the "save file" flag is active /// @@ -77,6 +80,67 @@ namespace RegistryPreviewUILib MonacoEditor.Focus(FocusState.Programmatic); } + /// + /// New button action: Ask to save last changes and reset editor content to reg header only + /// + private async void NewButton_Click(object sender, RoutedEventArgs e) + { + // Check to see if the current file has been saved + if (saveButton.IsEnabled) + { + ContentDialog contentDialog = new ContentDialog() + { + Title = resourceLoader.GetString("YesNoCancelDialogTitle"), + Content = resourceLoader.GetString("YesNoCancelDialogContent"), + PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"), + SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"), + CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"), + DefaultButton = ContentDialogButton.Primary, + }; + + // Use this code to associate the dialog to the appropriate AppWindow by setting + // the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow. + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) + { + contentDialog.XamlRoot = this.Content.XamlRoot; + } + + ContentDialogResult contentDialogResult = await contentDialog.ShowAsync(); + switch (contentDialogResult) + { + case ContentDialogResult.Primary: + // Save, then continue the new action + if (!AskFileName(string.Empty) || + !SaveFile()) + { + return; + } + + break; + case ContentDialogResult.Secondary: + // Don't save and continue the new action! + break; + default: + // Don't open the new action! + return; + } + } + + // mute the TextChanged handler to make for clean UI + MonacoEditor.TextChanged -= MonacoEditor_TextChanged; + + // reset editor, file info and ui. + _appFileName = string.Empty; + ResetEditorAndFile(); + + // disable buttons that do not make sense + UpdateUnsavedFileState(false); + refreshButton.IsEnabled = false; + + // restore the TextChanged handler + ButtonAction_RestoreTextChangedEvent(); + } + /// /// Uses a picker to select a new file to open /// @@ -107,11 +171,15 @@ namespace RegistryPreviewUILib { case ContentDialogResult.Primary: // Save, then continue the file open - SaveFile(); + if (!AskFileName(string.Empty) || + !SaveFile()) + { + return; + } + break; case ContentDialogResult.Secondary: // Don't save and continue the file open! - saveButton.IsEnabled = false; break; default: // Don't open the new file! @@ -138,14 +206,16 @@ namespace RegistryPreviewUILib { // mute the TextChanged handler to make for clean UI MonacoEditor.TextChanged -= MonacoEditor_TextChanged; + + // update file name _appFileName = storageFile.Path; UpdateToolBarAndUI(await OpenRegistryFile(_appFileName)); // disable the Save button as it's a new file - saveButton.IsEnabled = false; + UpdateUnsavedFileState(false); // Restore the event handler as we're loaded - MonacoEditor.TextChanged += MonacoEditor_TextChanged; + ButtonAction_RestoreTextChangedEvent(); } } @@ -154,7 +224,14 @@ namespace RegistryPreviewUILib /// private void SaveButton_Click(object sender, RoutedEventArgs e) { - SaveFile(); + if (!AskFileName(string.Empty)) + { + return; + } + + // save and update window title + // error handling and ui update happens in SaveFile() method + _ = SaveFile(); } /// @@ -162,47 +239,24 @@ namespace RegistryPreviewUILib /// private async void SaveAsButton_Click(object sender, RoutedEventArgs e) { - // Save out a new REG file and then open it - we have to use the direct Win32 method because FileOpenPicker crashes when it's - // called while running as admin - IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow); - string filename = SaveFilePicker.ShowDialog( - windowHandle, - resourceLoader.GetString("SuggestFileName"), - resourceLoader.GetString("FilterRegistryName") + '\0' + "*.reg" + '\0' + resourceLoader.GetString("FilterAllFiles") + '\0' + "*.*" + '\0' + '\0', - resourceLoader.GetString("SaveDialogTitle")); + // mute the TextChanged handler to make for clean UI + MonacoEditor.TextChanged -= MonacoEditor_TextChanged; - if (filename == string.Empty) + if (!AskFileName(_appFileName) || !SaveFile()) { return; } - _appFileName = filename; - SaveFile(); UpdateToolBarAndUI(await OpenRegistryFile(_appFileName)); + + // restore the TextChanged handler + ButtonAction_RestoreTextChangedEvent(); } /// /// Reloads the current REG file from storage /// private async void RefreshButton_Click(object sender, RoutedEventArgs e) - { - // mute the TextChanged handler to make for clean UI - MonacoEditor.TextChanged -= MonacoEditor_TextChanged; - - // reload the current Registry file and update the toolbar accordingly. - UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true); - - // disable the Save button as it's a new file - saveButton.IsEnabled = false; - - // restore the TextChanged handler - MonacoEditor.TextChanged += MonacoEditor_TextChanged; - } - - /// - /// Resets the editor content - /// - private async void NewButton_Click(object sender, RoutedEventArgs e) { // Check to see if the current file has been saved if (saveButton.IsEnabled) @@ -210,10 +264,9 @@ namespace RegistryPreviewUILib ContentDialog contentDialog = new ContentDialog() { Title = resourceLoader.GetString("YesNoCancelDialogTitle"), - Content = resourceLoader.GetString("YesNoCancelDialogContent"), - PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"), - SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"), - CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"), + Content = resourceLoader.GetString("ReloadDialogContent"), + PrimaryButtonText = resourceLoader.GetString("ReloadDialogPrimaryButtonText"), + CloseButtonText = resourceLoader.GetString("ReloadDialogCloseButtonText"), DefaultButton = ContentDialogButton.Primary, }; @@ -228,15 +281,10 @@ namespace RegistryPreviewUILib switch (contentDialogResult) { case ContentDialogResult.Primary: - // Save, then continue the file open - SaveFile(); - break; - case ContentDialogResult.Secondary: - // Don't save and continue the file open! - saveButton.IsEnabled = false; + // Don't save and continue the reload action! break; default: - // Don't open the new file! + // Don't continue the reload action! return; } } @@ -244,16 +292,14 @@ namespace RegistryPreviewUILib // mute the TextChanged handler to make for clean UI MonacoEditor.TextChanged -= MonacoEditor_TextChanged; - // reset editor, file info and ui. - _appFileName = string.Empty; - ResetEditorAndFile(); + // reload the current Registry file and update the toolbar accordingly. + UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true); + + // disable the Save button as it's a new file + UpdateUnsavedFileState(false); // restore the TextChanged handler - MonacoEditor.TextChanged += MonacoEditor_TextChanged; - - // disable buttons that do not make sense - saveButton.IsEnabled = false; - refreshButton.IsEnabled = false; + ButtonAction_RestoreTextChangedEvent(); } /// @@ -314,15 +360,20 @@ namespace RegistryPreviewUILib switch (contentDialogResult) { case ContentDialogResult.Primary: - // Save, then continue the file open - SaveFile(); + // Save, then continue the merge action + if (!AskFileName(string.Empty) || + !SaveFile()) + { + return; + } + break; case ContentDialogResult.Secondary: - // Don't save and continue the file open! - saveButton.IsEnabled = false; + // Don't save and continue the merge action! + UpdateUnsavedFileState(false); break; default: - // Don't open the new file! + // Don't merge the file! return; } } @@ -412,8 +463,70 @@ namespace RegistryPreviewUILib _dispatcherQueue.TryEnqueue(() => { RefreshRegistryFile(); - saveButton.IsEnabled = true; + if (!editorContentChangedScripted) + { + UpdateUnsavedFileState(true); + } + + editorContentChangedScripted = false; }); } + + /// + /// Sets indicator for programatic text change and adds text changed handler + /// + /// + /// Use this always, if button actions temporary disable the text changed event + /// + private void ButtonAction_RestoreTextChangedEvent() + { + // Solves the problem that enabling the event handler fires it one time. + // These one time fired event would causes wrong unsaved changes state. + editorContentChangedScripted = true; + MonacoEditor.TextChanged += MonacoEditor_TextChanged; + } + + // Commands to show data preview + public void ButtonExtendedPreview_Click(object sender, RoutedEventArgs e) + { + var data = ((Button)sender).DataContext as RegistryValue; + InvokeExtendedDataPreview(data); + } + + public void MenuExtendedPreview_Click(object sender, RoutedEventArgs e) + { + var data = ((MenuFlyoutItem)sender).DataContext as RegistryValue; + InvokeExtendedDataPreview(data); + } + + private async void InvokeExtendedDataPreview(RegistryValue valueData) + { + // Only one content dialog can be open at the same time and multiple instances of data preview can crash the app. + if (_dialogSemaphore.CurrentCount == 0) + { + return; + } + + try + { + // Lock ui and request dialog lock + _dialogSemaphore.Wait(); + ChangeCursor(gridPreview, true); + + await ShowExtendedDataPreview(valueData.Name, valueData.Type, valueData.Value); + } + catch + { +#if DEBUG + throw; +#endif + } + finally + { + // Unblock ui and release dialog lock + ChangeCursor(gridPreview, false); + _dialogSemaphore.Release(); + } + } } } diff --git a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs index 3ec275abf8..2211113f2c 100644 --- a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs +++ b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.UI.Input; @@ -24,7 +25,10 @@ namespace RegistryPreviewUILib { private const string NEWFILEHEADER = "Windows Registry Editor Version 5.00\r\n\r\n"; + private static readonly string _unsavedFileIndicator = "* "; + private static readonly char[] _unsavedFileIndicatorChars = [' ', '*']; private static SemaphoreSlim _dialogSemaphore = new(1); + private string lastKeyPath; public delegate void UpdateWindowTitleFunction(string title); @@ -503,6 +507,7 @@ namespace RegistryPreviewUILib case "REG_NONE": if (value.Length <= 0) { + registryValue.IsEmptyBinary = true; value = resourceLoader.GetString("ZeroLength"); } else @@ -831,42 +836,66 @@ namespace RegistryPreviewUILib /// private async void HandleDirtyClosing(string title, string content, string primaryButtonText, string secondaryButtonText, string closeButtonText) { - ContentDialog contentDialog = new ContentDialog() + if (_dialogSemaphore.CurrentCount == 0) { - Title = title, - Content = content, - PrimaryButtonText = primaryButtonText, - SecondaryButtonText = secondaryButtonText, - CloseButtonText = closeButtonText, - DefaultButton = ContentDialogButton.Primary, - }; - - // Use this code to associate the dialog to the appropriate AppWindow by setting - // the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow. - if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) - { - contentDialog.XamlRoot = this.Content.XamlRoot; + return; } - ContentDialogResult contentDialogResult = await contentDialog.ShowAsync(); - - switch (contentDialogResult) + try { - case ContentDialogResult.Primary: - // Save, then close - SaveFile(); - break; - case ContentDialogResult.Secondary: - // Don't save, and then close! - saveButton.IsEnabled = false; - break; - default: - // Cancel closing! - return; - } + await _dialogSemaphore.WaitAsync(); - // if we got here, we should try to close again - Application.Current.Exit(); + ContentDialog contentDialog = new ContentDialog() + { + Title = title, + Content = content, + PrimaryButtonText = primaryButtonText, + SecondaryButtonText = secondaryButtonText, + CloseButtonText = closeButtonText, + DefaultButton = ContentDialogButton.Primary, + }; + + // Use this code to associate the dialog to the appropriate AppWindow by setting + // the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow. + if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) + { + contentDialog.XamlRoot = this.Content.XamlRoot; + } + + ContentDialogResult contentDialogResult = await contentDialog.ShowAsync(); + + switch (contentDialogResult) + { + case ContentDialogResult.Primary: + // Save, then close + if (!AskFileName(string.Empty) || + !SaveFile()) + { + return; + } + + break; + case ContentDialogResult.Secondary: + // Don't save, and then close! + UpdateUnsavedFileState(false); + break; + default: + // Cancel closing! + return; + } + + // if we got here, we should try to close again + Application.Current.Exit(); + } + catch + { + // Normally nothing to catch here. + // But for safety the try-catch ensures that we always release the content dialog lock and exit correctly. + } + finally + { + _dialogSemaphore.Release(); + } } /// @@ -926,11 +955,71 @@ namespace RegistryPreviewUILib type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }, CultureInfo.InvariantCulture); } + public void UpdateUnsavedFileState(bool unsavedChanges) + { + // get, cut and analyze the current title + string currentTitle = Regex.Replace(_mainWindow.Title, APPNAME + @"$|\s-\s" + APPNAME + @"$", string.Empty); + bool titleContainsIndicator = currentTitle.StartsWith(_unsavedFileIndicator, StringComparison.CurrentCultureIgnoreCase); + + // update window title and save button state + if (unsavedChanges) + { + saveButton.IsEnabled = true; + + if (!titleContainsIndicator) + { + _updateWindowTitleFunction(_unsavedFileIndicator + currentTitle); + } + } + else + { + saveButton.IsEnabled = false; + + if (titleContainsIndicator) + { + _updateWindowTitleFunction(currentTitle.TrimStart(_unsavedFileIndicatorChars)); + } + } + } + + /// + /// Ask the user for the file path if it is unknown because of an unsaved file + /// + /// If not empty always ask for a file path and use the value as name. + /// Returns true if user selected a path, otherwise false + public bool AskFileName(string fileName) + { + if (string.IsNullOrEmpty(_appFileName) || !string.IsNullOrEmpty(fileName) ) + { + string fName = string.IsNullOrEmpty(fileName) ? resourceLoader.GetString("SuggestFileName") : fileName; + + // Save out a new REG file and then open it - we have to use the direct Win32 method because FileOpenPicker crashes when it's + // called while running as admin + IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow); + string filename = SaveFilePicker.ShowDialog( + windowHandle, + fName, + resourceLoader.GetString("FilterRegistryName") + '\0' + "*.reg" + '\0' + resourceLoader.GetString("FilterAllFiles") + '\0' + "*.*" + '\0' + '\0', + resourceLoader.GetString("SaveDialogTitle")); + + if (filename == string.Empty) + { + return false; + } + + _appFileName = filename; + } + + return true; + } + /// /// Wrapper method that saves the current file in place, using the current text in editor. /// - private void SaveFile() + private bool SaveFile() { + bool saveSuccess = true; + ChangeCursor(gridPreview, true); // set up the FileStream for all writing @@ -954,10 +1043,13 @@ namespace RegistryPreviewUILib streamWriter.Close(); // only change when the save is successful - saveButton.IsEnabled = false; + UpdateUnsavedFileState(false); + _updateWindowTitleFunction(_appFileName); } catch (UnauthorizedAccessException ex) { + saveSuccess = false; + // this exception is thrown if the file is there but marked as read only ShowMessageBox( resourceLoader.GetString("ErrorDialogTitle"), @@ -966,6 +1058,8 @@ namespace RegistryPreviewUILib } catch { + saveSuccess = false; + // this catch handles all other exceptions thrown when trying to write the file out ShowMessageBox( resourceLoader.GetString("ErrorDialogTitle"), @@ -983,6 +1077,8 @@ namespace RegistryPreviewUILib // restore the cursor ChangeCursor(gridPreview, false); + + return saveSuccess; } /// diff --git a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.xaml b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.xaml index 18225902e3..50a89fc223 100644 --- a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.xaml +++ b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.xaml @@ -2,6 +2,7 @@ x:Class="RegistryPreviewUILib.RegistryPreviewMainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:converter="using:CommunityToolkit.WinUI.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:RegistryPreviewUILib" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -10,6 +11,12 @@ xmlns:ui="using:CommunityToolkit.WinUI" mc:Ignorable="d"> + + + + + + - + @@ -225,10 +232,18 @@ Spacing="8"> - - + + + - - + + + @@ -274,20 +297,36 @@ + Orientation="Horizontal" + Spacing="6"> - - + + + + + Default color format - - Pick a color and open editor - - - Open editor - - - Only pick a color - Activation behavior + + Open editor + + + Pick a color first + + + Mouse actions + + + Choose what clicking individual buttons does + + + Primary click + + + Middle click + + + Secondary click + + + Pick a color and open editor + + + Pick a color and close + + + Close + Picker behavior @@ -4299,6 +4320,12 @@ Activate by holding the key for the character you want to add an accent to, then Show the release notes after an update + + This settings page is accessible by running the PowerToys executable again + + + Show system tray icon + Do not activate when Game Mode is on "Game mode" is the Windows feature to prevent notification when playing a game. @@ -5020,4 +5047,25 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m hue (Oklch) + + Exit PowerToys + + + System + + + Generate bug report package + + + Create zip folder with logs on the Desktop + + + Generate package + + + Shut down + + + Bug report package is being created + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/ViewModels/ColorPickerViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ColorPickerViewModel.cs index d6006a58f7..5e7a9e85a4 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ColorPickerViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ColorPickerViewModel.cs @@ -182,6 +182,51 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public int PrimaryClickBehavior + { + get => (int)_colorPickerSettings.Properties.PrimaryClickAction; + + set + { + if (value != (int)_colorPickerSettings.Properties.PrimaryClickAction) + { + _colorPickerSettings.Properties.PrimaryClickAction = (ColorPickerClickAction)value; + OnPropertyChanged(nameof(PrimaryClickBehavior)); + NotifySettingsChanged(); + } + } + } + + public int MiddleClickBehavior + { + get => (int)_colorPickerSettings.Properties.MiddleClickAction; + + set + { + if (value != (int)_colorPickerSettings.Properties.MiddleClickAction) + { + _colorPickerSettings.Properties.MiddleClickAction = (ColorPickerClickAction)value; + OnPropertyChanged(nameof(MiddleClickBehavior)); + NotifySettingsChanged(); + } + } + } + + public int SecondaryClickBehavior + { + get => (int)_colorPickerSettings.Properties.SecondaryClickAction; + + set + { + if (value != (int)_colorPickerSettings.Properties.SecondaryClickAction) + { + _colorPickerSettings.Properties.SecondaryClickAction = (ColorPickerClickAction)value; + OnPropertyChanged(nameof(SecondaryClickBehavior)); + NotifySettingsChanged(); + } + } + } + public bool ShowColorName { get => _colorPickerSettings.Properties.ShowColorName; diff --git a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs index c535ffa579..7a6a7fc284 100644 --- a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs @@ -146,6 +146,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels _startup = GeneralSettingsConfig.Startup; } + _showSysTrayIcon = GeneralSettingsConfig.ShowSysTrayIcon; _showNewUpdatesToastNotification = GeneralSettingsConfig.ShowNewUpdatesToastNotification; _autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates; _showWhatsNewAfterUpdates = GeneralSettingsConfig.ShowWhatsNewAfterUpdates; @@ -228,6 +229,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private static bool _isDevBuild; private bool _startup; + private bool _showSysTrayIcon; private GpoRuleConfigured _runAtStartupGpoRuleConfiguration; private bool _runAtStartupIsGPOConfigured; private bool _isElevated; @@ -258,6 +260,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _isNewVersionDownloading; private bool _isNewVersionChecked; private bool _isNoNetwork; + private bool _isBugReportRunning; private bool _settingsBackupRestoreMessageVisible; private string _settingsBackupMessage; @@ -359,6 +362,25 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + // Gets or sets a value indicating whether the PowerToys icon should be shown in the system tray. + public bool ShowSysTrayIcon + { + get + { + return _showSysTrayIcon; + } + + set + { + if (_showSysTrayIcon != value) + { + _showSysTrayIcon = value; + GeneralSettingsConfig.ShowSysTrayIcon = value; + NotifyPropertyChanged(); + } + } + } + public string RunningAsText { get @@ -931,6 +953,23 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool IsBugReportRunning + { + get + { + return _isBugReportRunning; + } + + set + { + if (value != _isBugReportRunning) + { + _isBugReportRunning = value; + NotifyPropertyChanged(); + } + } + } + public bool SettingsBackupRestoreMessageVisible { get diff --git a/src/settings-ui/Settings.UI/app.manifest b/src/settings-ui/Settings.UI/app.manifest index 9742e0b540..c5043e485d 100644 --- a/src/settings-ui/Settings.UI/app.manifest +++ b/src/settings-ui/Settings.UI/app.manifest @@ -9,7 +9,7 @@ 2) System < Windows 10 Anniversary Update --> true/PM - PerMonitorV2, PerMonitor + PerMonitorV2 diff --git a/tools/BugReportTool/BugReportTool/EventViewer.cpp b/tools/BugReportTool/BugReportTool/EventViewer.cpp index f6e9a6baf0..549ceb90b4 100644 --- a/tools/BugReportTool/BugReportTool/EventViewer.cpp +++ b/tools/BugReportTool/BugReportTool/EventViewer.cpp @@ -23,7 +23,7 @@ namespace // Report last 30 days const long long PERIOD = 10 * 24 * 3600ll * 1000; - const std::wstring QUERY = L"" \ + const std::wstring QUERY_BY_PROCESS = L"" \ L" " \ L" " \ + L" *[System[TimeCreated[timediff(@SystemTime)<%I64u]]]" \ + L" " \ + L" " \ + L""; + + std::wstring GetQuery(std::wstring processName) { wchar_t buff[1000]; memset(buff, 0, sizeof(buff)); - _snwprintf_s(buff, sizeof(buff), QUERY.c_str(), PERIOD, processName.c_str()); + _snwprintf_s(buff, sizeof(buff), QUERY_BY_PROCESS.c_str(), PERIOD, processName.c_str()); + return buff; + } + + std::wstring GetQueryByChannel(std::wstring channelName) + { + wchar_t buff[1000]; + memset(buff, 0, sizeof(buff)); + _snwprintf_s(buff, sizeof(buff), QUERY_BY_CHANNEL.c_str(), channelName.c_str(), PERIOD); return buff; } std::wofstream report; EVT_HANDLE hResults; + bool isChannel; + + bool ShouldIncludeEvent(const std::wstring& eventXml) + { + if (!isChannel) + { + return true; // Include all events if no filtering + } + + // Check if the event contains PowerToys or CommandPalette + return (eventXml.find(L"PowerToys") != std::wstring::npos || + eventXml.find(L"CommandPalette") != std::wstring::npos); + } void PrintEvent(EVT_HANDLE hEvent) { @@ -75,6 +105,17 @@ namespace } } + // Apply filtering if needed + std::wstring eventContent(pRenderedContent); + if (!ShouldIncludeEvent(eventContent)) + { + if (pRenderedContent) + { + free(pRenderedContent); + } + return; // Skip this event + } + XmlDocumentEx doc; doc.LoadXml(pRenderedContent); std::wstring formattedXml = L""; @@ -130,17 +171,27 @@ namespace } public: - EventViewerReporter(const std::filesystem::path& tmpDir, std::wstring processName) + EventViewerReporter(const std::filesystem::path& tmpDir, std::wstring queryName, std::wstring fileName, bool isChannel) + :isChannel(isChannel) { - auto query = GetQuery(processName); + std::wstring query = L""; + if (isChannel) + { + query = GetQueryByChannel(queryName); + } + else + { + query = GetQuery(queryName); + } + auto reportPath = tmpDir; - reportPath.append(L"EventViewer-" + processName + L".xml"); + reportPath.append(L"EventViewer-" + fileName + L".xml"); report = std::wofstream(reportPath); - hResults = EvtQuery(NULL, NULL, GetQuery(processName).c_str(), EvtQueryChannelPath); + hResults = EvtQuery(NULL, NULL, query.c_str(), EvtQueryChannelPath); if (NULL == hResults) { - report << "Failed to report info for " << processName << ". " << get_last_error_or_default(GetLastError()) << std::endl; + report << "Failed to report info for " << queryName << ". " << get_last_error_or_default(GetLastError()) << std::endl; return; } } @@ -175,6 +226,11 @@ void EventViewer::ReportEventViewerInfo(const std::filesystem::path& tmpDir) { for (auto& process : processes) { - EventViewerReporter(tmpDir, process).Report(); + EventViewerReporter(tmpDir, process, process, false).Report(); } } + +void EventViewer::ReportAppXDeploymentLogs(const std::filesystem::path& tmpDir) +{ + EventViewerReporter(tmpDir, L"Microsoft-Windows-AppXDeploymentServer/Operational", L"AppXDeploymentServerEventLog", true).Report(); +} diff --git a/tools/BugReportTool/BugReportTool/EventViewer.h b/tools/BugReportTool/BugReportTool/EventViewer.h index 0d50f4253e..94176b4217 100644 --- a/tools/BugReportTool/BugReportTool/EventViewer.h +++ b/tools/BugReportTool/BugReportTool/EventViewer.h @@ -4,4 +4,5 @@ namespace EventViewer { void ReportEventViewerInfo(const std::filesystem::path& tmpDir); + void ReportAppXDeploymentLogs(const std::filesystem::path& tmpDir); } diff --git a/tools/BugReportTool/BugReportTool/Main.cpp b/tools/BugReportTool/BugReportTool/Main.cpp index a653c25d57..7716512125 100644 --- a/tools/BugReportTool/BugReportTool/Main.cpp +++ b/tools/BugReportTool/BugReportTool/Main.cpp @@ -381,6 +381,9 @@ int wmain(int argc, wchar_t* argv[], wchar_t*) // Write event viewer logs info to the temporary folder EventViewer::ReportEventViewerInfo(reportDir); + // Write AppXDeployment-Server event logs to the temporary folder + EventViewer::ReportAppXDeploymentLogs(reportDir); + ReportInstallerLogs(tempDir, reportDir); ReportInstalledContextMenuPackages(reportDir);