Compare commits

...

24 Commits

Author SHA1 Message Date
Leilei Zhang
c79f3b4c39 Merge branch 'main' of https://github.com/microsoft/PowerToys into leilzh/testing 2025-06-17 15:44:05 +08:00
Kai Tao
c487638758 Workspaces: PWA apps should not necessarily configure profile to launch. (#39971)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Workspace support for pwa is now limited, it is tight to specific
Profile launch. If you create a pwa app with a profile other than
"Default", launch will fail. Then you have to manually configure that
profile to launch.
This pr fix it by launching with shell:appsfolder\appusermodelId

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [X] **Closes:** #36384
- [ ] **Communication:** I've discussed this with core contributors
already. If 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
- [ ] **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
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [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

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
- [X] Create a new workspace with a pwa app(Other than default profile)
should be no problem.
- [X] Existing workspace with a pwa(default profile and other profile)
should launch successfully without problem

1. with pt version 91.1, create a loop pwa with "Profile 1" instead of
"Default" in edge.
2. capture and launch actually launch the edge instead of loop
3. Create profile with this impl and launch
4. Launch pwa successfully

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-17 15:39:44 +08:00
Kai Tao
252dbb5853 Settings: Generate bug report should tell user there is bug report generating (#40060)
### <!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
### Bug report tool status tracking:

Currently, After clicking the generate package button, button is still
active, as we do not have bug report progress, this will confuse user
whether they actually clicks the button.
Add an enable status to acknowledge the bug generating

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **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
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [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
1. Progress bar should be present in generating report place when there
is bug report going on.
2. Runner&Settings should know each other when they trigger the bug
report.
3. Runner tray icon menu item should be disabled when there is one bug
report going on.
4. After bug report generation, everything should be like before.


## Validation Steps Performed


https://github.com/user-attachments/assets/dcbf8e6e-c5e1-4d23-9dab-f16c11ed56cf

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2025-06-17 14:49:54 +08:00
leileizhang
655398e173 Fix PDF generation – reset stream position (#40072)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This is an additional fix that extends the work done in
https://github.com/microsoft/PowerToys/pull/40023

fix #39851
2025-06-17 14:25:08 +08:00
S. M. Mohiuddin Khan Shiam
f7257bf1d1 Fix PDF thumbnail generation – add System.Drawing reference and reset stream position (#40023)
##  Problem

[PdfThumbnailProvider](cci:1://file:///d:/Github/PowerToys/src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.cs:15:8-18:9)
could fail at compile-time **and/or** produce empty or invalid
thumbnails at runtime:

1.
[Image](cci:1://file:///d:/Github/PowerToys/src/modules/previewpane/PdfThumbnailProvider/PdfThumbnailProvider.cs:81:8-103:9)
/ `Bitmap` types come from **System.Drawing**, but the namespace was
missing, causing build errors in environments that don’t rely on
implicit global usings.
2. After rendering the first PDF page to an in-memory stream, the
stream’s cursor remained at the end.
`Image.FromStream(stream.AsStream())` therefore read **zero bytes**,
throwing an exception or creating an empty bitmap.

##  Fix
* Added explicit `using System.Drawing;`.
* Added `stream.Seek(0);` right before `Image.FromStream(...)` to rewind
the stream to the beginning.

```csharp
using System.Drawing;   // new
...
page.RenderToStreamAsync(...).GetAwaiter().GetResult();
stream.Seek(0);         // rewind so Image.FromStream reads data
imageOfPage = Image.FromStream(stream.AsStream());
```
## Testing

- Rebuilt the solution – compilation succeeds without missing-namespace
errors.
- Ran thumbnail handler against multiple PDFs; thumbnails now render
correctly and no runtime exceptions are thrown.

## Impact
Users regain reliable PDF thumbnail previews in Explorer, improving
usability and preventing crashes or blank icons.

---------

Co-authored-by: leileizhang <leilzh@microsoft.com>
2025-06-17 14:24:52 +08:00
Leilei Zhang
4f209235af Merge branch 'main' of https://github.com/microsoft/PowerToys into leilzh/testing 2025-06-17 12:52:33 +08:00
Leonardo Farias Bona
bdedc02ea5 [CropAndLock] theme (#38044)
## Summary of the Pull Request

Theme aware cropped windows.

## PR Checklist
- [x] **Closes:** #28348
- [ ] **Communication:** I've discussed this with core contributors
already. If 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
- [ ] **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
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [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

Added a Theme aware implementation that was already found on other
components, the only difference is that we are dealing with multiple
windows.
It will follow the Power Toys application theme, (windows too in case
PowerToys follows the System).
I decided to change the scope of a variable to not create a second
control of croppedWindows, this may need to be taken into consideration.

## Validation Steps Performed

Using Crop (Win + Ctrl + Shift +R)
Crop one or multiple windows.
All of them have the same theme as the application.
Went to windows theme settings, change to light theme, the app changes
accordingly.

Same behavior was tested with Thumbnail (Win  + Ctrl + Shift + T)

Closing windows and changing the theme again.
2025-06-17 11:49:31 +08:00
Heiko
b2d7182dcd [RegPreview] Various improvements on how files are saved (#37628)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

### This PR implements various fixes and improvements into
RegistryPreview for saving files:
1. Adds an unsaved file indicator in the title bar like in Notepad. (As
indicator we show the * character before the file title.)
2. The save button behaves like a "save as" button, if the file does not
exist on disk like in Notepad. (Without fix when running as non-admin
you get an access denied error message.)
3. If the app gets closed without saving and the file does not exist on
disk, then the user is now asked for the path. (Fixes crash on clicking
save button while closing the app.)
4. Failed save actions are handled now correctly on dirty closing,
opening files and all other actions that require saving the current
state. They will stop the process.
5. A fix for an incorrect enabled state of the save button after
opening, reloading and saving a file.
6. Reuse file name on save as button, if known. Otherwise use "new file"
template name like in Notepad.
7. Fix an app crash if you click the window's close button a second time
while the "Should save?" dialog is opened.
8. Added an reload dialog in case of unsaved changes.


![image](https://github.com/user-attachments/assets/9045446e-e9a3-4b81-8aa0-515b0821a969)


![image](https://github.com/user-attachments/assets/0888fbd2-851b-4101-a177-be9a3675b5ae)


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #36876, #36875
- [x] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **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
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [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

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Local test build.

---------

Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
2025-06-17 11:35:35 +08:00
Heiko
82e9d42e02 [CmdPal > Tiem & Date] Fix missing command of FallbackItem (#40048)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

The fallback items of Time & Date plugin missing the command. This PR
add the missing copy command.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40029
- [x] **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
- [ ] **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
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-06-17 00:20:47 +08:00
Leilei Zhang
012fd0eb8b update more version 2025-06-16 20:58:05 +08:00
Leilei Zhang
041cdc57dc add more version 2025-06-16 18:43:55 +08:00
Leilei Zhang
3bf0c04ad5 update NOTICE.md 2025-06-16 17:46:58 +08:00
Jiří Polášek
74d92df078 Add 'Restart Windows Explorer' command, implemented using Restart Manager (#39258)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
- Adds a new **"Restart Windows Explorer"** command to the **"Windows
System Commands"** provider
- Implemented using **Restart Manager** to allow restoring previously
opened Explorer windows after restart
- This depends on the *"Restore previous folder windows at logon"*
option in File Explorer Options
- An explicit timeout was added for terminating processes, since Restart
Manager uses very long timeouts 😴
- The shell process name (`explorer`) is hardcoded  
- The command attempts to terminate all `explorer` processes
indiscriminately
- Execution requires confirmation (if enabled in settings)


![image](https://github.com/user-attachments/assets/a9a955d5-a52b-4a57-bcb1-85293362c17a)


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #39213 
- [ ] **Communication:** I've discussed this with core contributors
already. If 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
- [ ] **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
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [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

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-06-16 17:21:21 +08:00
Leilei Zhang
c2975dfe55 update all version 2025-06-16 17:00:12 +08:00
Leilei Zhang
e53961c30b update version 2025-06-16 15:19:30 +08:00
Leilei Zhang
821f0b7aa7 Merge branch 'main' of https://github.com/microsoft/PowerToys into alzollin/actionsInvoke 2025-06-16 13:59:01 +08:00
Leilei Zhang
512f55a481 unique the build version 2025-06-16 12:08:10 +08:00
Leilei Zhang
c5c64767c1 Merge branch 'main' of https://github.com/microsoft/PowerToys into alzollin/actionsInvoke 2025-06-16 08:11:25 +08:00
Leilei Zhang
a40b7d23ff fix spelling 2025-06-15 20:14:52 +08:00
Leilei Zhang
b539c75240 fix cswin32 build error in peek 2025-06-15 20:03:52 +08:00
Alexandre Zollinger Chohfi
d168ae5b9b Added lock for UpdateMoreCommands. 2025-06-12 10:31:03 +08:00
Alexandre Zollinger Chohfi
1870fc31ee Update src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx
Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-06-12 10:31:03 +08:00
Alexandre Zollinger Chohfi
54f7e1e123 Addressed PR Feedback. 2025-06-12 10:31:03 +08:00
Gordon Lam (SH)
39fa4c37d3 Rebased with main 2025-06-12 10:30:59 +08:00
82 changed files with 1133 additions and 321 deletions

View File

@@ -156,6 +156,7 @@ builttoroam
BVal
BValue
byapp
BYCOMMAND
BYPOSITION
CALCRECT
CALG
@@ -202,6 +203,7 @@ CLIPSIBLINGS
closesocket
clp
CLSCTX
CLSCTXLOCALSERVER
clsids
Clusion
cmder
@@ -815,6 +817,7 @@ LOCALDISPLAY
localpackage
LOCALSYSTEM
LOCATIONCHANGE
LOCKTYPE
LOGFONT
LOGFONTW
logon
@@ -1716,6 +1719,7 @@ trx
tsa
TSender
TServer
tskill
tstoi
TStr
tweakme

View File

@@ -99,7 +99,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.
@@ -332,6 +332,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)

View File

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

View File

@@ -96,8 +96,8 @@
<!-- Global props OverrideWindowsTargetPlatformVersion-->
<PropertyGroup Label="Globals">
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
<TargetPlatformVersion>10.0.22621.0</TargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>

View File

@@ -47,14 +47,14 @@
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.6" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.6" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
TODO: in Common.Dotnet.CsWinRT.props, on upgrade, verify RemoveCsWinRTPackageAnalyzer is no longer needed.
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />

View File

@@ -1533,9 +1533,9 @@ SOFTWARE.
- Microsoft.Web.WebView2 1.0.2903.40
- Microsoft.Win32.SystemEvents 9.0.6
- Microsoft.Windows.Compatibility 9.0.6
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWin32 0.3.183
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
- 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

View File

@@ -8,10 +8,10 @@ SET VCToolsVersion=!VCToolsVersion!
SET ClearDevCommandPromptEnvVars=false
rem In case of Release we should not use Debug CRT in VCRT forwarders
msbuild !PTRoot!\src\modules\previewpane\MonacoPreviewHandler\MonacoPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml
msbuild !PTRoot!\src\modules\previewpane\MonacoPreviewHandler\MonacoPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml
msbuild !PTRoot!\src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml
msbuild !PTRoot!\src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0
msbuild !PTRoot!\src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvider.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml
msbuild !PTRoot!\src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvider.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml -p:TargetFramework=net9.0-windows10.0.26100.0

View File

@@ -4,8 +4,8 @@
<Import Project=".\Common.Dotnet.PrepareGeneratedFolder.targets" />
<PropertyGroup>
<WindowsSdkPackageVersion>10.0.22621.57</WindowsSdkPackageVersion>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>

View File

@@ -10,7 +10,7 @@
<ProjectGuid>{f5e1146e-b7b3-4e11-85fd-270a500bd78c}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>CropAndLock</RootNamespace>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.22621.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.26100.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
@@ -154,6 +154,9 @@
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\Themes\Themes.vcxproj">
<Project>{98537082-0fdb-40de-abd8-0dc5a4269bab}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />

View File

@@ -17,6 +17,9 @@
#include <common/Telemetry/EtwTrace/EtwTrace.h>
#include <common/Themes/theme_helpers.h>
#include <common/Themes/theme_listener.h>
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
namespace winrt
@@ -35,6 +38,23 @@ namespace util
const std::wstring instanceMutexName = L"Local\\PowerToys_CropAndLock_InstanceMutex";
bool m_running = true;
// Theming
ThemeListener theme_listener{};
// Keep a list of our cropped windows
std::vector<std::shared_ptr<CropAndLockWindow>> croppedWindows;
void handleTheme()
{
auto theme = theme_listener.AppTheme;
auto isDark = theme == Theme::Dark;
Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light");
for (auto&& croppedWindow : croppedWindows)
{
ThemeHelpers::SetImmersiveDarkMode(croppedWindow->Handle(), isDark);
}
}
int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _In_ int)
{
// Initialize COM
@@ -42,6 +62,8 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
Trace::CropAndLock::RegisterProvider();
theme_listener.AddChangedHandler(handleTheme);
Shared::Trace::ETWTrace trace;
trace.UpdateState(true);
@@ -107,8 +129,6 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
// Create our overlay window
std::unique_ptr<OverlayWindow> overlayWindow;
// Keep a list of our cropped windows
std::vector<std::shared_ptr<CropAndLockWindow>> croppedWindows;
// Handles and thread for the events sent from runner
HANDLE m_reparent_event_handle;
@@ -167,6 +187,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
croppedWindow->CropAndLock(targetWindow, cropRect);
croppedWindow->OnClosed(removeWindowCallback);
croppedWindows.push_back(croppedWindow);
handleTheme();
};
overlayWindow.reset();

View File

@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<PublishDir>$(PowerToysRoot)\$(Platform)\$(Configuration)\WinUI3Apps</PublishDir>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
@@ -139,7 +139,7 @@
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" />
</ImportGroup>
@@ -150,8 +150,8 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets'))" />

View File

@@ -3,6 +3,6 @@
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
</packages>

View File

@@ -9,7 +9,7 @@
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{0db0f63a-d2f8-4da3-a650-2d0b8724218e}</ProjectGuid>
<RootNamespace>NewPlusShellExtensionWin10</RootNamespace>
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">

View File

@@ -49,6 +49,8 @@ namespace WorkspacesEditor.Data
public WindowPositionWrapper Position { get; set; }
public int Monitor { get; set; }
public string Version { get; set; }
}
public struct MonitorConfigurationWrapper

View File

@@ -41,6 +41,7 @@ namespace WorkspacesEditor.Models
Maximized = other.Maximized;
Position = other.Position;
MonitorNumber = other.MonitorNumber;
Version = other.Version;
Parent = other.Parent;
IsNotFound = other.IsNotFound;
@@ -274,5 +275,7 @@ namespace WorkspacesEditor.Models
CommandLineArguments = newCommandLineValue;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams)));
}
public string Version { get; set; }
}
}

View File

@@ -246,6 +246,7 @@ namespace WorkspacesEditor.Models
AppPath = app.ApplicationPath,
AppTitle = app.Title,
PwaAppId = string.IsNullOrEmpty(app.PwaAppId) ? string.Empty : app.PwaAppId,
Version = string.IsNullOrEmpty(app.Version) ? string.Empty : app.Version,
PackageFullName = app.PackageFullName,
AppUserModelId = app.AppUserModelId,
Parent = this,

View File

@@ -107,6 +107,7 @@ namespace WorkspacesEditor.Utils
CommandLineArguments = app.CommandLineArguments,
IsElevated = app.IsElevated,
CanLaunchElevated = app.CanLaunchElevated,
Version = app.Version,
Maximized = app.Maximized,
Minimized = app.Minimized,
Position = new ProjectData.ApplicationWrapper.WindowPositionWrapper

View File

@@ -165,16 +165,52 @@ namespace AppLauncher
if (!launched && !app.pwaAppId.empty())
{
std::filesystem::path appPath(app.path);
if (appPath.filename() == NonLocalizable::EdgeFilename)
int version = 0;
if (app.version != L"")
{
appPathFinal = appPath.parent_path() / NonLocalizable::EdgePwaFilename;
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
try
{
version = std::stoi(app.version);
}
catch (const std::invalid_argument&)
{
Logger::error(L"Invalid version format: {}", app.version);
version = 0;
}
catch (const std::out_of_range&)
{
Logger::error(L"Version out of range: {}", app.version);
version = 0;
}
}
if (appPath.filename() == NonLocalizable::ChromeFilename)
if (version >= 1)
{
appPathFinal = appPath.parent_path() / NonLocalizable::ChromePwaFilename;
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
auto res = LaunchApp(L"shell:AppsFolder\\" + app.appUserModelId, app.commandLineArgs, app.isElevated);
if (res.isOk())
{
launched = true;
}
else
{
launchErrors.push_back({ app.appUserModelId, res.error() });
}
}
if (!launched)
{
std::filesystem::path appPath(app.path);
if (appPath.filename() == NonLocalizable::EdgeFilename)
{
appPathFinal = appPath.parent_path() / NonLocalizable::EdgePwaFilename;
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
}
if (appPath.filename() == NonLocalizable::ChromeFilename)
{
appPathFinal = appPath.parent_path() / NonLocalizable::ChromePwaFilename;
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
}
}
}

View File

@@ -76,19 +76,6 @@ namespace WorkspacesLibUnitTests
Assert::IsTrue(result == nonExistentWindowAumid);
}
TEST_METHOD (PwaHelper_GetAUMIDFromWindow_InvalidWindow_ReturnsEmpty)
{
// Arrange
Utils::PwaHelper helper;
HWND invalidWindow = nullptr;
// Act
std::wstring result = helper.GetAUMIDFromWindow(invalidWindow);
// Assert
Assert::IsTrue(result.empty());
}
TEST_METHOD (PwaHelper_GetEdgeAppId_ValidConstruction_DoesNotCrash)
{
// Arrange

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "PwaHelper.h"
#include "WindowUtils.h"
#include <filesystem>
@@ -51,7 +52,7 @@ namespace Utils
localFolder = L""; // Ensure it is explicitly set to empty on failure
}
}
return localFolder;
}
@@ -193,7 +194,7 @@ namespace Utils
return std::nullopt;
}
std::optional<std::wstring> PwaHelper::GetChromeAppId(const std::wstring& windowAumid) const
{
const auto appIdIndexStart = windowAumid.find(NonLocalizable::ChromeAppIdIdentifier);
@@ -256,85 +257,4 @@ namespace Utils
return result;
}
std::wstring PwaHelper::GetAUMIDFromWindow(HWND hwnd) const
{
std::wstring result{};
if (hwnd == NULL)
{
return result;
}
Microsoft::WRL::ComPtr<IPropertyStore> propertyStore;
HRESULT hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&propertyStore));
if (FAILED(hr))
{
return result;
}
PROPVARIANT propVariant;
PropVariantInit(&propVariant);
hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &propVariant);
if (SUCCEEDED(hr) && propVariant.vt == VT_LPWSTR && propVariant.pwszVal != nullptr)
{
result = propVariant.pwszVal;
}
PropVariantClear(&propVariant);
Logger::info(L"Found a window with aumid {}", result);
return result;
}
std::wstring PwaHelper::GetAUMIDFromProcessId(DWORD processId) const
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId);
if (hProcess == NULL)
{
Logger::error(L"Failed to open process handle. Error: {}", get_last_error_or_default(GetLastError()));
return {};
}
// Get the package full name for the process
UINT32 packageFullNameLength = 0;
LONG rc = GetPackageFullName(hProcess, &packageFullNameLength, nullptr);
if (rc != ERROR_INSUFFICIENT_BUFFER)
{
Logger::error(L"Failed to get package full name length. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
std::vector<wchar_t> packageFullName(packageFullNameLength);
rc = GetPackageFullName(hProcess, &packageFullNameLength, packageFullName.data());
if (rc != ERROR_SUCCESS)
{
Logger::error(L"Failed to get package full name. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
// Get the AUMID for the package
UINT32 appModelIdLength = 0;
rc = GetApplicationUserModelId(hProcess, &appModelIdLength, nullptr);
if (rc != ERROR_INSUFFICIENT_BUFFER)
{
Logger::error(L"Failed to get AppUserModelId length. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
std::vector<wchar_t> appModelId(appModelIdLength);
rc = GetApplicationUserModelId(hProcess, &appModelIdLength, appModelId.data());
if (rc != ERROR_SUCCESS)
{
Logger::error(L"Failed to get AppUserModelId. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
CloseHandle(hProcess);
return std::wstring(appModelId.data());
}
}

View File

@@ -12,19 +12,16 @@ namespace Utils
PwaHelper();
~PwaHelper() = default;
std::wstring GetAUMIDFromWindow(HWND hWnd) const;
std::optional<std::wstring> GetEdgeAppId(const std::wstring& windowAumid) const;
std::optional<std::wstring> GetChromeAppId(const std::wstring& windowAumid) const;
std::optional<std::wstring> GetChromeAppId(const std::wstring& windowAumid) const;
std::wstring SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const;
private:
void InitAppIds(const std::wstring& browserDataFolder, const std::wstring& browserDirPrefix, const std::function<void(const std::wstring&)>& addingAppIdCallback);
void InitEdgeAppIds();
void InitChromeAppIds();
std::wstring GetAppIdFromCommandLineArgs(const std::wstring& commandLineArgs) const;
std::wstring GetAUMIDFromProcessId(DWORD processId) const;
std::map<std::wstring, std::wstring> m_edgeAppIds;
std::vector<std::wstring> m_chromeAppIds;

View File

@@ -0,0 +1,18 @@
#include "pch.h"
#include "StringUtils.h"
namespace StringUtils
{
bool CaseInsensitiveEquals(const std::wstring& str1, const std::wstring& str2)
{
if (str1.size() != str2.size())
{
return false;
}
return std::equal(str1.begin(), str1.end(), str2.begin(), [](wchar_t ch1, wchar_t ch2) {
return towupper(ch1) == towupper(ch2);
});
}
}

View File

@@ -6,15 +6,5 @@
namespace StringUtils
{
inline bool CaseInsensitiveEquals(const std::wstring& str1, const std::wstring& str2)
{
if (str1.size() != str2.size())
{
return false;
}
return std::equal(str1.begin(), str1.end(), str2.begin(), [](wchar_t ch1, wchar_t ch2) {
return towupper(ch1) == towupper(ch2);
});
}
bool CaseInsensitiveEquals(const std::wstring& str1, const std::wstring& str2);
}

View File

@@ -0,0 +1,105 @@
#include "pch.h"
#include "WindowUtils.h"
#include <filesystem>
#include <appmodel.h>
#include <shellapi.h>
#include <ShlObj.h>
#include <shobjidl.h>
#include <tlhelp32.h>
#include <wrl.h>
#include <propkey.h>
#include <wil/com.h>
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
#include <WorkspacesLib/AppUtils.h>
#include <WorkspacesLib/CommandLineArgsHelper.h>
#include <WorkspacesLib/StringUtils.h>
namespace Utils
{
std::wstring GetAUMIDFromProcessId(DWORD processId)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId);
if (hProcess == NULL)
{
Logger::error(L"Failed to open process handle. Error: {}", get_last_error_or_default(GetLastError()));
return {};
}
// Get the package full name for the process
UINT32 packageFullNameLength = 0;
LONG rc = GetPackageFullName(hProcess, &packageFullNameLength, nullptr);
if (rc != ERROR_INSUFFICIENT_BUFFER)
{
Logger::error(L"Failed to get package full name length. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
std::vector<wchar_t> packageFullName(packageFullNameLength);
rc = GetPackageFullName(hProcess, &packageFullNameLength, packageFullName.data());
if (rc != ERROR_SUCCESS)
{
Logger::error(L"Failed to get package full name. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
// Get the AUMID for the package
UINT32 appModelIdLength = 0;
rc = GetApplicationUserModelId(hProcess, &appModelIdLength, nullptr);
if (rc != ERROR_INSUFFICIENT_BUFFER)
{
Logger::error(L"Failed to get AppUserModelId length. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
std::vector<wchar_t> appModelId(appModelIdLength);
rc = GetApplicationUserModelId(hProcess, &appModelIdLength, appModelId.data());
if (rc != ERROR_SUCCESS)
{
Logger::error(L"Failed to get AppUserModelId. Error code: {}", rc);
CloseHandle(hProcess);
return {};
}
CloseHandle(hProcess);
return std::wstring(appModelId.data());
}
std::wstring GetAUMIDFromWindow(HWND hwnd)
{
std::wstring result{};
if (hwnd == NULL)
{
return result;
}
Microsoft::WRL::ComPtr<IPropertyStore> propertyStore;
HRESULT hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&propertyStore));
if (FAILED(hr))
{
return result;
}
PROPVARIANT propVariant;
PropVariantInit(&propVariant);
hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &propVariant);
if (SUCCEEDED(hr) && propVariant.vt == VT_LPWSTR && propVariant.pwszVal != nullptr)
{
result = propVariant.pwszVal;
}
PropVariantClear(&propVariant);
Logger::info(L"Found a window with aumid {}", result);
return result;
}
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <functional>
#include <WorkspacesLib/AppUtils.h>
#include <wtypes.h>
namespace Utils
{
std::wstring GetAUMIDFromWindow(HWND hWnd);
std::wstring GetAUMIDFromProcessId(DWORD processId);
};

View File

@@ -87,6 +87,7 @@ namespace WorkspacesData
const static wchar_t* MaximizedID = L"maximized";
const static wchar_t* PositionID = L"position";
const static wchar_t* MonitorID = L"monitor";
const static wchar_t* VersionID = L"version";
}
json::JsonObject ToJson(const WorkspacesProject::Application& data)
@@ -106,6 +107,7 @@ namespace WorkspacesData
json.SetNamedValue(NonLocalizable::MaximizedID, json::value(data.isMaximized));
json.SetNamedValue(NonLocalizable::PositionID, PositionJSON::ToJson(data.position));
json.SetNamedValue(NonLocalizable::MonitorID, json::value(data.monitor));
json.SetNamedValue(NonLocalizable::VersionID, json::value(data.version));
return json;
}
@@ -168,6 +170,11 @@ namespace WorkspacesData
result.position = position.value();
}
if (json.HasKey(NonLocalizable::VersionID))
{
result.version = json.GetNamedString(NonLocalizable::VersionID);
}
}
catch (const winrt::hresult_error&)
{
@@ -286,6 +293,7 @@ namespace WorkspacesData
const static wchar_t* MoveExistingWindowsID = L"move-existing-windows";
const static wchar_t* MonitorConfigurationID = L"monitor-configuration";
const static wchar_t* AppsID = L"applications";
const static wchar_t* Version = L"version";
}
json::JsonObject ToJson(const WorkspacesProject& data)

View File

@@ -33,6 +33,9 @@ namespace WorkspacesData
std::wstring appUserModelId;
std::wstring pwaAppId;
std::wstring commandLineArgs;
// empty to 1,
std::wstring version;
bool isElevated{};
bool canLaunchElevated{};
bool isMinimized{};
@@ -86,7 +89,7 @@ namespace WorkspacesData
{
WorkspacesData::WorkspacesProject::Application application;
HWND window{};
LaunchingState state { LaunchingState::Waiting };
LaunchingState state{ LaunchingState::Waiting };
};
using LaunchingAppStateMap = std::map<WorkspacesData::WorkspacesProject::Application, LaunchingAppState>;

View File

@@ -45,6 +45,7 @@
<ClInclude Include="StringUtils.h" />
<ClInclude Include="utils.h" />
<ClInclude Include="WbemHelper.h" />
<ClInclude Include="WindowUtils.h" />
<ClInclude Include="WorkspacesData.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
@@ -59,8 +60,10 @@
</ClCompile>
<ClCompile Include="PwaHelper.cpp" />
<ClCompile Include="SteamGameHelper.cpp" />
<ClCompile Include="StringUtils.cpp" />
<ClCompile Include="two_way_pipe_message_ipc.cpp" />
<ClCompile Include="WbemHelper.cpp" />
<ClCompile Include="WindowUtils.cpp" />
<ClCompile Include="WorkspacesData.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>

View File

@@ -56,6 +56,9 @@
<ClInclude Include="SteamHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WindowUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -94,6 +97,12 @@
<ClCompile Include="SteamGameHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WindowUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="StringUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -11,6 +11,7 @@
#include <WorkspacesLib/AppUtils.h>
#include <WorkspacesLib/PwaHelper.h>
#include <WorkspacesLib/WindowUtils.h>
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
#include "Generated Files/resource.h"
@@ -139,7 +140,7 @@ namespace SnapshotUtils
bool isChrome = appData.IsChrome();
if (isEdge || isChrome)
{
auto windowAumid = pwaHelper.GetAUMIDFromWindow(window);
auto windowAumid = Utils::GetAUMIDFromWindow(window);
std::optional<std::wstring> pwaAppId{};
if (isEdge)
@@ -158,6 +159,8 @@ namespace SnapshotUtils
appData.pwaAppId = pwaAppId.value();
appData.name = pwaName + L" (" + appData.name + L")";
// If it's pwa app, appUserModelId should be their own pwa id.
appData.appUserModelId = windowAumid;
}
}
@@ -185,6 +188,7 @@ namespace SnapshotUtils
.appUserModelId = appData.appUserModelId,
.pwaAppId = appData.pwaAppId,
.commandLineArgs = L"",
.version = L"1",
.isElevated = IsProcessElevated(pid),
.canLaunchElevated = appData.canLaunchElevated,
.isMinimized = isMinimized,

View File

@@ -13,6 +13,7 @@
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
#include <WorkspacesLib/PwaHelper.h>
#include <WorkspacesLib/WindowUtils.h>
namespace NonLocalizable
{
@@ -203,7 +204,7 @@ std::optional<WindowWithDistance> WindowArranger::GetNearestWindow(const Workspa
if (!data->IsSteamGame() && !WindowUtils::HasThickFrame(window))
{
// Only care about steam games if it has no thick frame to remain consistent with
// Only care about steam games if it has no thick frame to remain consistent with
// the behavior as before.
continue;
}
@@ -220,7 +221,7 @@ std::optional<WindowWithDistance> WindowArranger::GetNearestWindow(const Workspa
bool isChrome = appData.IsChrome();
if (isEdge || isChrome)
{
auto windowAumid = pwaHelper.GetAUMIDFromWindow(window);
auto windowAumid = Utils::GetAUMIDFromWindow(window);
std::optional<std::wstring> pwaAppId{};
if (isEdge)

View File

@@ -8,7 +8,7 @@
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />

View File

@@ -4,8 +4,8 @@
<RootNamespace>TemplateCmdPalExtension</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<WindowsSdkPackageVersion>10.0.22621.57</WindowsSdkPackageVersion>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>

View File

@@ -57,7 +57,11 @@ public sealed partial class MainWindow : WindowEx,
InitializeComponent();
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
CommandPaletteHost.SetHostHwnd((ulong)_hwnd.Value);
unsafe
{
CommandPaletteHost.SetHostHwnd((ulong)_hwnd.Value);
}
_keyboardListener = new KeyboardListener();
_keyboardListener.Start();

View File

@@ -19,7 +19,7 @@
<AppContainerApplication>false</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.22621.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.26100.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />

View File

@@ -221,11 +221,10 @@ public class UWPApplication : IProgram
var capacity = 1024U;
PWSTR outBuffer = new PWSTR((char*)(void*)Marshal.AllocHGlobal((int)capacity * sizeof(char)));
var source = $"@{{{packageFullName}? {parsed}}}";
void* reserved = null;
try
{
PInvoke.SHLoadIndirectString(source, outBuffer, capacity, ref reserved).ThrowOnFailure();
PInvoke.SHLoadIndirectString(source, outBuffer.AsSpan()).ThrowOnFailure();
var loaded = outBuffer.ToString();
return string.IsNullOrEmpty(loaded) ? string.Empty : loaded;
@@ -235,7 +234,7 @@ public class UWPApplication : IProgram
try
{
var sourceFallback = $"@{{{packageFullName}?{parsedFallback}}}";
PInvoke.SHLoadIndirectString(sourceFallback, outBuffer, capacity, ref reserved).ThrowOnFailure();
PInvoke.SHLoadIndirectString(sourceFallback, outBuffer.AsSpan()).ThrowOnFailure();
var loaded = outBuffer.ToString();
return string.IsNullOrEmpty(loaded) ? string.Empty : loaded;
}

View File

@@ -0,0 +1,42 @@
// 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 Microsoft.CmdPal.Ext.Indexer.Native;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using WinRT;
using WinRT.Interop;
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 = NativeMethods.CoCreateInstance(ref Unsafe.AsRef(in classId), IntPtr.Zero, NativeHelpers.CLSCTXLOCALSERVER, ref iid, out abiPtr);
Marshal.ThrowExceptionForHR((int)hresult);
return MarshalInterface<global::Windows.AI.Actions.ActionRuntime>.FromAbi(abiPtr);
}
finally
{
MarshalInspectable<object>.DisposeAbi(abiPtr);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -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<CommandResult> InvokeAsync()
{
try
{
await actionInstance.InvokeAsync();
return CommandResult.GoHome();
}
catch (Exception ex)
{
return CommandResult.ShowToast("Failed to invoke action " + actionInstance.Definition.Id + ": " + ex.Message);
}
}
}

View File

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

View File

@@ -34,6 +34,9 @@
<Content Update="Assets\FileExplorer.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Actions.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\FileExplorer.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@@ -11,6 +11,7 @@ public sealed partial class NativeHelpers
public const uint SEEMASKINVOKEIDLIST = 12;
public const uint CLSCTXINPROCALL = 0x17;
public const uint CLSCTXLOCALSERVER = 0x4;
public struct PropertyKeys
{

View File

@@ -1,6 +1,7 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": false,
"emitSingleFile": false,
"public": true
"comInterop": {
"preserveSigMethods": [ "*" ]
}
}

View File

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

View File

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

View File

@@ -60,6 +60,15 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Actions.
/// </summary>
internal static string Indexer_Command_Actions {
get {
return ResourceManager.GetString("Indexer_Command_Actions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Browse.
/// </summary>

View File

@@ -138,6 +138,9 @@
<data name="Indexer_Command_OpenWith" xml:space="preserve">
<value>Open with</value>
</data>
<data name="Indexer_Command_Actions" xml:space="preserve">
<value>Actions...</value>
</data>
<data name="Indexer_Command_ShowInFolder" xml:space="preserve">
<value>Show in folder</value>
</data>

View File

@@ -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)
{

View File

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

View File

@@ -645,6 +645,42 @@ namespace Microsoft.CmdPal.Ext.System {
}
}
/// <summary>
/// Looks up a localized string similar to Restart Windows Explorer.
/// </summary>
public static string Microsoft_plugin_sys_RestartShell {
get {
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You are about to restart Windows Explorer, are you sure?.
/// </summary>
public static string Microsoft_plugin_sys_RestartShell_confirmation {
get {
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_confirmation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to End and restart the Windows Explorer shell process.
/// </summary>
public static string Microsoft_plugin_sys_RestartShell_description {
get {
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Restart.
/// </summary>
public static string Microsoft_plugin_sys_RestartShell_name {
get {
return ResourceManager.GetString("Microsoft_plugin_sys_RestartShell_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ip; mac; address.
/// </summary>

View File

@@ -417,4 +417,16 @@
<data name="Microsoft_plugin_ext_fallback_display_title" xml:space="preserve">
<value>Open System Command</value>
</data>
<data name="Microsoft_plugin_sys_RestartShell" xml:space="preserve">
<value>Restart Windows Explorer</value>
</data>
<data name="Microsoft_plugin_sys_RestartShell_description" xml:space="preserve">
<value>End and restart the Windows Explorer shell process</value>
</data>
<data name="Microsoft_plugin_sys_RestartShell_name" xml:space="preserve">
<value>Restart</value>
</data>
<data name="Microsoft_plugin_sys_RestartShell_confirmation" xml:space="preserve">
<value>You are about to restart Windows Explorer, are you sure?</value>
</data>
</root>

View File

@@ -68,6 +68,7 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
Title = result.Title;
Subtitle = result.Subtitle;
Icon = result.Icon;
Command = result.Command;
}
else
{

View File

@@ -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;
@@ -188,17 +189,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);
}
}
}

View File

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

View File

@@ -4,7 +4,7 @@
<PathToRoot>..\..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250513003</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428</WindowsSdkBuildToolsNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188</WindowsSdkBuildToolsNuget>
<WebView2Nuget>$(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2903.40</WebView2Nuget>
</PropertyGroup>
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" />
@@ -24,7 +24,7 @@
<ApplicationType>Windows Store</ApplicationType>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">

View File

@@ -2,6 +2,6 @@
<packages>
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
</packages>

View File

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

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": false
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": false,
"className": "PInvoke_FilePreviewer"
}

View File

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

View File

@@ -93,7 +93,7 @@ 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 var pFactory);
Marshal.ThrowExceptionForHR(hr);
// Storing the factory in memory helps makes the handlers load faster
@@ -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);

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": false,
"className": "PInvoke_PeekUI"
}

View File

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

View File

@@ -213,7 +213,7 @@ namespace Peek.UI
/// <param name="e">PreviewSizeChangedArgs</param>
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();

View File

@@ -6,7 +6,7 @@
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{2694e2fb-dcd5-4bff-a418-b6c3c7ce3b8e}</ProjectGuid>
<RootNamespace>Test</RootNamespace>
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<ProjectName>PowerRename.FuzzingTest</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
@@ -205,7 +205,7 @@
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\boost.1.87.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.87.0\build\boost.targets')" />
@@ -218,8 +218,8 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets'))" />

View File

@@ -5,6 +5,6 @@
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
</packages>

View File

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

View File

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

View File

@@ -22,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;
/// <summary>
/// Event that is will prevent the app from closing if the "save file" flag is active
/// </summary>
@@ -76,6 +80,67 @@ namespace RegistryPreviewUILib
MonacoEditor.Focus(FocusState.Programmatic);
}
/// <summary>
/// New button action: Ask to save last changes and reset editor content to reg header only
/// </summary>
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();
}
/// <summary>
/// Uses a picker to select a new file to open
/// </summary>
@@ -106,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!
@@ -137,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();
}
}
@@ -153,7 +224,14 @@ namespace RegistryPreviewUILib
/// </summary>
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();
}
/// <summary>
@@ -161,47 +239,24 @@ namespace RegistryPreviewUILib
/// </summary>
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();
}
/// <summary>
/// Reloads the current REG file from storage
/// </summary>
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;
}
/// <summary>
/// Resets the editor content
/// </summary>
private async void NewButton_Click(object sender, RoutedEventArgs e)
{
// Check to see if the current file has been saved
if (saveButton.IsEnabled)
@@ -209,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,
};
@@ -227,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;
}
}
@@ -243,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();
}
/// <summary>
@@ -313,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;
}
}
@@ -411,10 +463,29 @@ namespace RegistryPreviewUILib
_dispatcherQueue.TryEnqueue(() =>
{
RefreshRegistryFile();
saveButton.IsEnabled = true;
if (!editorContentChangedScripted)
{
UpdateUnsavedFileState(true);
}
editorContentChangedScripted = false;
});
}
/// <summary>
/// Sets indicator for programatic text change and adds text changed handler
/// </summary>
/// <remarks>
/// Use this always, if button actions temporary disable the text changed event
/// </remarks>
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)
{

View File

@@ -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);
@@ -832,42 +836,66 @@ namespace RegistryPreviewUILib
/// </summary>
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();
}
}
/// <summary>
@@ -927,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));
}
}
}
/// <summary>
/// Ask the user for the file path if it is unknown because of an unsaved file
/// </summary>
/// <param name="fileName">If not empty always ask for a file path and use the value as name.</param>
/// <returns>Returns true if user selected a path, otherwise false</returns>
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;
}
/// <summary>
/// Wrapper method that saves the current file in place, using the current text in editor.
/// </summary>
private void SaveFile()
private bool SaveFile()
{
bool saveSuccess = true;
ChangeCursor(gridPreview, true);
// set up the FileStream for all writing
@@ -955,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"),
@@ -967,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"),
@@ -984,6 +1077,8 @@ namespace RegistryPreviewUILib
// restore the cursor
ChangeCursor(gridPreview, false);
return saveSuccess;
}
/// <summary>

View File

@@ -258,12 +258,18 @@
<data name="YesNoCancelDialogCloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ReloadDialogCloseButtonText" xml:space="preserve">
<value>No</value>
</data>
<data name="YesNoCancelDialogContent" xml:space="preserve">
<value>Save changes?</value>
</data>
<data name="YesNoCancelDialogPrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="ReloadDialogPrimaryButtonText" xml:space="preserve">
<value>Yes</value>
</data>
<data name="YesNoCancelDialogSecondaryButtonText" xml:space="preserve">
<value>Don't save</value>
</data>
@@ -363,4 +369,7 @@
<data name="NewButton.Label" xml:space="preserve">
<value>New</value>
</data>
<data name="ReloadDialogContent" xml:space="preserve">
<value>You lose any unsaved changes. Reload anyway?</value>
</data>
</root>

View File

@@ -4,17 +4,52 @@
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
std::atomic_bool isBugReportThreadRunning = false;
BugReportManager& BugReportManager::instance()
{
static BugReportManager instance;
return instance;
}
void launch_bug_report() noexcept
void BugReportManager::register_callback(const BugReportCallback& callback)
{
std::lock_guard<std::mutex> lock(m_callbacksMutex);
m_callbacks.push_back(callback);
}
void BugReportManager::clear_callbacks()
{
std::lock_guard<std::mutex> lock(m_callbacksMutex);
m_callbacks.clear();
}
void BugReportManager::notify_observers(bool isRunning)
{
std::lock_guard<std::mutex> lock(m_callbacksMutex);
for (const auto& callback : m_callbacks)
{
try
{
callback(isRunning);
}
catch (...)
{
// Ignore callback exceptions to prevent one bad callback from affecting others
}
}
}
void BugReportManager::launch_bug_report() noexcept
{
std::wstring bug_report_path = get_module_folderpath();
bug_report_path += L"\\Tools\\PowerToys.BugReportTool.exe";
bool expected_isBugReportThreadRunning = false;
if (isBugReportThreadRunning.compare_exchange_strong(expected_isBugReportThreadRunning, true))
bool expected_isBugReportRunning = false;
if (m_isBugReportRunning.compare_exchange_strong(expected_isBugReportRunning, true))
{
std::thread([bug_report_path]() {
// Notify observers that bug report is starting
notify_observers(true);
std::thread([this, bug_report_path]() {
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE };
sei.lpFile = bug_report_path.c_str();
@@ -27,7 +62,29 @@ void launch_bug_report() noexcept
MessageBoxW(nullptr, bugreport_success.c_str(), L"PowerToys", MB_OK);
}
isBugReportThreadRunning.store(false);
m_isBugReportRunning.store(false);
// Notify observers that bug report has finished
notify_observers(false);
}).detach();
}
else
{
notify_observers(false);
}
}
bool BugReportManager::is_bug_report_running() const noexcept
{
return m_isBugReportRunning.load();
}
// Legacy functions for backward compatibility
void launch_bug_report() noexcept
{
BugReportManager::instance().launch_bug_report();
}
bool is_bug_report_running() noexcept
{
return BugReportManager::instance().is_bug_report_running();
}

View File

@@ -1,3 +1,43 @@
#pragma once
void launch_bug_report() noexcept;
#include <functional>
#include <vector>
#include <mutex>
// Observer pattern for bug report status changes
using BugReportCallback = std::function<void(bool isRunning)>;
class BugReportManager
{
public:
static BugReportManager& instance();
// Register a callback to be notified when bug report status changes
void register_callback(const BugReportCallback& callback);
// Remove all callbacks (useful for cleanup)
void clear_callbacks();
// Launch bug report and notify observers
void launch_bug_report() noexcept;
// Check if bug report is currently running
bool is_bug_report_running() const noexcept;
private:
BugReportManager() = default;
~BugReportManager() = default;
BugReportManager(const BugReportManager&) = delete;
BugReportManager& operator=(const BugReportManager&) = delete;
// Notify all registered callbacks
void notify_observers(bool isRunning);
std::atomic_bool m_isBugReportRunning = false;
std::vector<BugReportCallback> m_callbacks;
mutable std::mutex m_callbacksMutex;
};
// Legacy functions for backward compatibility
void launch_bug_report() noexcept;
bool is_bug_report_running() noexcept;

View File

@@ -227,6 +227,14 @@ void dispatch_received_json(const std::wstring& json_to_parse)
{
launch_bug_report();
}
else if (name == L"bug_report_status")
{
json::JsonObject result;
result.SetNamedValue(L"bug_report_running", winrt::Windows::Data::Json::JsonValue::CreateBooleanValue(is_bug_report_running()));
std::unique_lock lock{ ipc_mutex };
if (current_settings_ipc)
current_settings_ipc->send(result.Stringify().c_str());
}
else if (name == L"killrunner")
{
const auto pt_main_window = FindWindowW(pt_tray_icon_window_class, nullptr);
@@ -488,7 +496,18 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
std::unique_lock lock{ ipc_mutex };
current_settings_ipc = new TwoWayPipeMessageIPC(powertoys_pipe_name, settings_pipe_name, receive_json_send_to_main_thread);
current_settings_ipc->start(hToken);
// Register callback for bug report status changes
BugReportManager::instance().register_callback([](bool isRunning) {
json::JsonObject result;
result.SetNamedValue(L"bug_report_running", winrt::Windows::Data::Json::JsonValue::CreateBooleanValue(isRunning));
std::unique_lock lock{ ipc_mutex };
if (current_settings_ipc)
current_settings_ipc->send(result.Stringify().c_str());
});
}
g_settings_process_id = process_info.dwProcessId;
if (process_info.hProcess)

View File

@@ -207,6 +207,8 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
change_menu_item_text(ID_EXIT_MENU_COMMAND, exit_menuitem_label.data());
change_menu_item_text(ID_SHOW_TRAY_ICON_MENU_COMMAND, show_tray_icon_menuitem_label.data());
change_menu_item_text(ID_REPORT_BUG_COMMAND, submit_bug_menuitem_label.data());
bool bug_report_disabled = is_bug_report_running();
EnableMenuItem(h_sub_menu, ID_REPORT_BUG_COMMAND, MF_BYCOMMAND | (bug_report_disabled ? MF_GRAYED : MF_ENABLED));
change_menu_item_text(ID_DOCUMENTATION_MENU_COMMAND, documentation_menuitem_label.data());
change_menu_item_text(ID_QUICK_ACCESS_MENU_COMMAND, quick_access_menuitem_label.data());
}
@@ -269,6 +271,14 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
return DefWindowProc(window, message, wparam, lparam);
}
void update_bug_report_menu_status(bool isRunning)
{
if (h_sub_menu != nullptr)
{
EnableMenuItem(h_sub_menu, ID_REPORT_BUG_COMMAND, MF_BYCOMMAND | (isRunning ? MF_GRAYED : MF_ENABLED));
}
}
void start_tray_icon(bool isProcessElevated)
{
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
@@ -319,6 +329,16 @@ void start_tray_icon(bool isProcessElevated)
ChangeWindowMessageFilterEx(hwnd, WM_COMMAND, MSGFLT_ALLOW, nullptr);
tray_icon_created = Shell_NotifyIcon(NIM_ADD, &tray_icon_data) == TRUE;
// Register callback to update bug report menu item status
BugReportManager::instance().register_callback([](bool isRunning) {
dispatch_run_on_main_ui_thread([](PVOID data) {
bool* running = static_cast<bool*>(data);
update_bug_report_menu_status(*running);
delete running;
},
new bool(isRunning));
});
}
}
@@ -334,6 +354,8 @@ void stop_tray_icon()
{
if (tray_icon_created)
{
// Clear bug report callbacks
BugReportManager::instance().clear_callbacks();
SendMessage(tray_icon_hwnd, WM_CLOSE, 0, 0);
}
}
}

View File

@@ -454,11 +454,25 @@
<Button x:Uid="GeneralPage_ViewDiagnosticDataViewerInfoButton" Click="Click_ViewDiagnosticDataViewerRestart" />
</InfoBar.ActionButton>
</InfoBar>
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=&#xEBE8;}">
<tkcontrols:SettingsCard
x:Uid="GeneralPage_ReportBugPackage"
HeaderIcon="{ui:FontIcon Glyph=&#xEBE8;}"
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}">
<Button
x:Uid="GeneralPageReportBugPackage"
HorizontalAlignment="Right"
Click="BugReportToolClicked" />
Click="BugReportToolClicked"
IsEnabled="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
x:Uid="GeneralPage_ReportBugPackage"
HeaderIcon="{ui:FontIcon Glyph=&#xEBE8;}"
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<ProgressRing
Width="24"
Height="24"
HorizontalAlignment="Right"
VerticalAlignment="Center" />
</tkcontrols:SettingsCard>
</StackPanel>
</controls:SettingsGroup>

View File

@@ -5,14 +5,15 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Flyout;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Data.Json;
namespace Microsoft.PowerToys.Settings.UI.Views
{
@@ -87,6 +88,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel.InitializeReportBugLink();
// Register IPC handler for bug report status
ShellPage.ShellHandler.IPCResponseHandleList.Add(HandleBugReportStatusResponse); // Register cleanup on unload
this.Unloaded += GeneralPage_Unloaded;
CheckBugReportStatus();
doRefreshBackupRestoreStatus(100);
}
@@ -182,8 +189,48 @@ namespace Microsoft.PowerToys.Settings.UI.Views
private void BugReportToolClicked(object sender, RoutedEventArgs e)
{
// Start bug report
var launchPage = new LaunchPage();
launchPage.ReportBugBtn_Click(sender, e);
ViewModel.IsBugReportRunning = true;
// No need to start timer - the observer pattern will notify us when it finishes
}
private void CheckBugReportStatus()
{
// Send one-time request to check current bug report status
string ipcMessage = "{ \"bug_report_status\": { } }";
ShellPage.SendDefaultIPCMessage(ipcMessage);
}
private void HandleBugReportStatusResponse(JsonObject response)
{
if (response.ContainsKey("bug_report_running"))
{
var isRunning = response.GetNamedBoolean("bug_report_running");
// Update UI on the UI thread
this.DispatcherQueue.TryEnqueue(() =>
{
ViewModel.IsBugReportRunning = isRunning;
});
}
}
private void GeneralPage_Unloaded(object sender, RoutedEventArgs e)
{
CleanupBugReportHandlers();
}
private void CleanupBugReportHandlers()
{
// Remove IPC handler
if (ShellPage.ShellHandler?.IPCResponseHandleList != null)
{
ShellPage.ShellHandler.IPCResponseHandleList.Remove(HandleBugReportStatusResponse);
}
}
}
}

View File

@@ -260,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;
@@ -952,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