mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-23 19:49:17 +08:00
Merged main
This commit is contained in:
commit
85bf36028c
6
.github/actions/spell-check/allow/names.txt
vendored
6
.github/actions/spell-check/allow/names.txt
vendored
@ -56,7 +56,6 @@ damienleroy
|
||||
davidegiacometti
|
||||
debian
|
||||
Deibisu
|
||||
Deibisu
|
||||
Delimarsky
|
||||
Deondre
|
||||
DHowett
|
||||
@ -87,7 +86,6 @@ jefflord
|
||||
Jordi
|
||||
jyuwono
|
||||
Kairu
|
||||
Kairu
|
||||
Kamra
|
||||
Kantarci
|
||||
Karthick
|
||||
@ -123,7 +121,6 @@ Quriz
|
||||
randyrants
|
||||
ricardosantos
|
||||
riri
|
||||
riri
|
||||
ritchielawrence
|
||||
robmikh
|
||||
Rutkas
|
||||
@ -147,6 +144,7 @@ TBM
|
||||
tilovell
|
||||
Triet
|
||||
waaverecords
|
||||
Whuihuan
|
||||
Xpg
|
||||
ycv
|
||||
Yuniardi
|
||||
@ -157,6 +155,8 @@ Zykova
|
||||
|
||||
# OTHERS
|
||||
|
||||
Bilibili
|
||||
BVID
|
||||
cmdow
|
||||
Controlz
|
||||
cortana
|
||||
|
5
.github/actions/spell-check/expect.txt
vendored
5
.github/actions/spell-check/expect.txt
vendored
@ -259,6 +259,7 @@ CRH
|
||||
critsec
|
||||
Crossdevice
|
||||
CRSEL
|
||||
crx
|
||||
crw
|
||||
CSearch
|
||||
CSettings
|
||||
@ -639,6 +640,7 @@ HWNDLAST
|
||||
HWNDNEXT
|
||||
HWNDPREV
|
||||
hyjiacan
|
||||
IApp
|
||||
IAI
|
||||
IBeam
|
||||
ICapture
|
||||
@ -1141,7 +1143,7 @@ pdo
|
||||
pdto
|
||||
pdtobj
|
||||
pdw
|
||||
Peb
|
||||
peb
|
||||
pef
|
||||
PElems
|
||||
Pels
|
||||
@ -1629,6 +1631,7 @@ tkconverters
|
||||
TLayout
|
||||
tlb
|
||||
tlbimp
|
||||
tlhelp
|
||||
TMPVAR
|
||||
TNP
|
||||
Toolhelp
|
||||
|
@ -1,53 +1,53 @@
|
||||
# PowerToys Contributor's Guide
|
||||
|
||||
Below is our guidance for how to report issues, propose new features, and submit contributions via Pull Requests (PRs). Our philosophy is heavily based around understanding the problem and scenarios first, this is why we follow this pattern before work has started.
|
||||
Below is our guidance for reporting issues, proposing new features, and submitting contributions via Pull Requests (PRs). Our philosophy is to understand the problem and scenarios first, which is why we follow this pattern before work starts.
|
||||
|
||||
1. There is an issue
|
||||
2. There has been a conversation
|
||||
3. There is agreement on the problem, the fit for PowerToys, and the solution to the problem (implementation)
|
||||
1. There is an issue.
|
||||
2. There has been a conversation.
|
||||
3. There is agreement on the problem, the fit for PowerToys, and the solution to the problem (implementation).
|
||||
|
||||
## Filing an issue
|
||||
## Filing an Issue
|
||||
|
||||
Please follow this simple rule to help us eliminate any unnecessary wasted effort & frustration, and ensure an efficient and effective use of everyone's time - yours, ours, and other community members':
|
||||
**Importance of Filing an Issue First**
|
||||
|
||||
> 👉 If you have a question, think you've discovered an issue, would like to propose a new feature, etc., then find/file an issue **BEFORE** starting work to fix/implement it.
|
||||
Please follow this rule to help eliminate wasted effort and frustration, and to ensure an efficient and effective use of everyone’s time:
|
||||
|
||||
When requesting new features / enhancements, understanding problem and scenario around it is extremely important. Having additional evidence, data, tweets, blog posts, research, ... anything is extremely helpful. This information provides context to the scenario that may otherwise be lost.
|
||||
> 👉 If you have a question, think you've discovered an issue, or would like to propose a new feature, please find/file an issue **BEFORE** starting work to fix/implement it.
|
||||
|
||||
* Don't know whether you're reporting an issue or requesting a feature? File an issue
|
||||
* Have a question that you don't see answered in docs, videos, etc.? File an issue
|
||||
* Want to know if we're planning on building a particular feature? File an issue
|
||||
* Got a great idea for a new utility or feature? File an issue/request/idea
|
||||
* Don't understand how to do something? File an issue/Community Guidance Request
|
||||
* Found an existing issue that describes yours? Great - upvote and add additional commentary / info / repro-steps / etc.
|
||||
When requesting new features or enhancements, providing additional evidence, data, tweets, blog posts, or research is extremely helpful. This information gives context to the scenario that may otherwise be lost.
|
||||
|
||||
A quick search before filing an issue also could be helpful. It is likely someone else has found the problem you're seeing, and someone may be working on or have already contributed a fix!
|
||||
* Unsure whether it’s an issue or feature request? File an issue.
|
||||
* Have a question that isn't answered in the docs, videos, etc.? File an issue.
|
||||
* Want to know if we’re planning a particular feature? File an issue.
|
||||
* Got a great idea for a new utility or feature? File an issue/request/idea.
|
||||
* Don’t understand how to do something? File an issue/Community Guidance Request.
|
||||
* Found an existing issue that describes yours? Great! Upvote and add additional commentary, info, or repro steps.
|
||||
|
||||
### How to tell the PowerToys team this is an interesting thing to focus on
|
||||
A quick search before filing an issue could be helpful. It’s likely someone else has found the same problem, and they may even be working on or have already contributed a fix!
|
||||
|
||||
Upvote the original issue by clicking its [+😊] button and hitting 👍 (+1) icon or a different one. This way allows us to measure how impactful different issues are compared to others. The issue with comments like "+1", "me too", or similar is they actually make it harder to have a conversation and harder to quickly determine trending important requests.
|
||||
### Indicating Interest in Issues
|
||||
|
||||
To let the team know which issues are important, upvote by clicking the [+😊] button and the 👍 icon on the original issue post. Avoid comments like "+1" or "me too" as they clutter the discussion and make it harder to prioritize requests.
|
||||
|
||||
---
|
||||
|
||||
## Contributing fixes / features
|
||||
## Contributing Fixes/Features
|
||||
|
||||
Please comment on [our "Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769) to let us know you're interested in working on something before you start the work. Not only does this avoid multiple people unexpectedly working on the same thing at the same time but it enables us to make sure everyone is clear on what should be done to implement any new functionality. It's less work for everyone, in the long run, to establish this up front.
|
||||
Please comment on our ["Would you like to contribute to PowerToys?"](https://github.com/microsoft/PowerToys/issues/28769) thread to let us know you're interested in working on something before you start. This helps avoid multiple people unexpectedly working on the same thing and ensures everyone is clear on what should be done. It's less work for everyone to establish this up front.
|
||||
|
||||
### Localization issues
|
||||
### Localization Issues
|
||||
|
||||
Please file localization issues, so our internal localization team can identify and fix them. However we currently don't accept community Pull Requests fixing localization issues. Localization is handled by the internal Microsoft team only.
|
||||
For localization issues, please file an issue to notify our internal localization team, as community PRs for localization aren't accepted. Localization is handled exclusively by the internal Microsoft team.
|
||||
|
||||
### To Spec or not to Spec
|
||||
### To Spec or Not to Spec
|
||||
|
||||
A key point is for everyone to understand the approach that will be taken. We want to be sure if anyone does work, we will accept it in. Items that are larger in scope we'll want some type of spec to understand what is being planned and have a discussion. Specs help collaborators discuss different approaches to solve a problem, describe how the feature will behave, how the feature will impact the user, what happens if something goes wrong, etc. Driving towards agreement in a spec, before any code is written, often results in simpler code, and less wasted effort in the long run.
|
||||
A key point is for everyone to understand the approach that will be taken. We want to be sure that any work done will be accepted. Larger-scope items will require a spec to outline the approach and allow for discussion. Specs help collaborators consider different solutions, describe feature behavior, and plan for errors. Achieving agreement in a spec before writing code often results in simpler code and less wasted effort.
|
||||
|
||||
For such scenarios, once a team member has agreed with your approach, skip ahead to the section headed "Development" section below.
|
||||
|
||||
Team members will be happy to help review specs and guide them to completion.
|
||||
Once a team member has agreed with your approach, proceed to the "Development" section below. Team members are happy to help review specs and guide them to completion.
|
||||
|
||||
### Help Wanted
|
||||
|
||||
Once the team has approved an issue/spec approach to solving, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/PowerToys/labels/Help%20Wanted).
|
||||
Once the team has approved an issue/spec approach, development can proceed. If no developers are immediately available, the spec may be parked and labeled "Help Wanted," ready for a developer to get started. For development opportunities, visit [Issues labeled Help Wanted](https://github.com/microsoft/PowerToys/labels/Help%20Wanted).
|
||||
|
||||
---
|
||||
|
||||
@ -55,18 +55,18 @@ Once the team has approved an issue/spec approach to solving, development can pr
|
||||
|
||||
Follow the [development guidelines](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md).
|
||||
|
||||
### Naming of features and functionality
|
||||
### Naming Features and Functionality
|
||||
|
||||
Naming should be descriptive and straight forward. We want names to be clear about functionality and usefulness moving forward.
|
||||
Names should be descriptive and straightforward, clearly reflecting functionality and usefulness.
|
||||
|
||||
### How can I become a collaborator on the PowerToys team
|
||||
### Becoming a Collaborator on the PowerToys Team
|
||||
|
||||
Be a great community member. Just help out a lot and make useful additions, filing bugs/suggestions, help develop fixes and features, code reviews, and always, docs. Lets continue to make the PowerToys repository a great spot to learn and make a great set of utilities.
|
||||
Be an active community member! Make helpful contributions by filing bugs, offering suggestions, developing fixes and features, conducting code reviews, and updating documentation.
|
||||
|
||||
When the time comes, Microsoft will reach out and help make you a formal team member. Just make sure they can reach out to you :)
|
||||
When the time comes, Microsoft will reach out to you about becoming a formal team member. Just make sure they have a way to contact you. 😊
|
||||
|
||||
---
|
||||
|
||||
## Thank you
|
||||
## Thank You
|
||||
|
||||
Thank you in advance for your contribution!
|
||||
Thank you in advance for your contribution! We appreciate your help in making PowerToys a better tool for everyone.
|
||||
|
@ -67,6 +67,10 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<td>Microsoft.PowerToys.TrayFlyoutModuleRunEvent</td>
|
||||
<td>Logs when a utility from the tray flyout menu is run.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.Uninstall_Success</td>
|
||||
<td>Logs when PowerToys is successfully uninstalled (who would do such a thing!).</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### OOBE (Out-of-box experience)
|
||||
@ -139,6 +143,10 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<td>Microsoft.PowerToys.AdvancedPasteInAppKeyboardShortcutEvent</td>
|
||||
<td>Triggered when a keyboard shortcut is used within the Advanced Paste interface.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.AdvancedPasteSemanticKernelFormatEvent</td>
|
||||
<td>Triggered when Advanced Paste leverages the Semantic Kernel.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Always on Top
|
||||
|
@ -630,6 +630,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "src\common\Tele
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTests", "src\modules\MouseWithoutBorders\MouseWithoutBorders.UnitTests\MouseWithoutBorders.UnitTests.csproj", "{66614C26-314C-4B91-9071-76133422CFEF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesCsharpLibrary", "src\modules\Workspaces\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj", "{89D0E199-B17A-418C-B2F8-7375B6708357}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste.UnitTests", "src\modules\AdvancedPaste\AdvancedPaste.UnitTests\AdvancedPaste.UnitTests.csproj", "{D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE}"
|
||||
EndProject
|
||||
Global
|
||||
@ -2248,6 +2250,30 @@ Global
|
||||
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x64.Build.0 = Release|x64
|
||||
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x86.ActiveCfg = Release|x64
|
||||
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x86.Build.0 = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.Build.0 = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.Build.0 = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.ActiveCfg = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.Build.0 = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.ActiveCfg = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.Build.0 = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.Build.0 = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.Build.0 = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.ActiveCfg = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.Build.0 = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.ActiveCfg = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.Build.0 = Release|x64
|
||||
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@ -2648,30 +2674,6 @@ Global
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.Build.0 = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x86.Build.0 = Debug|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.ActiveCfg = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.Build.0 = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.ActiveCfg = Release|x64
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x86.Build.0 = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.Build.0 = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x86.Build.0 = Debug|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.ActiveCfg = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.Build.0 = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.ActiveCfg = Release|x64
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x86.Build.0 = Release|x64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@ -2780,6 +2782,18 @@ Global
|
||||
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x64.Build.0 = Release|x64
|
||||
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x86.ActiveCfg = Release|x64
|
||||
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x86.Build.0 = Release|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x64.Build.0 = Debug|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x86.Build.0 = Debug|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x64.ActiveCfg = Release|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x64.Build.0 = Release|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x86.ActiveCfg = Release|x64
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x86.Build.0 = Release|x64
|
||||
{D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@ -2970,6 +2984,8 @@ Global
|
||||
{B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
||||
{A663E672-B26D-4EC0-BEAB-FE2E424AC46F} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
||||
{8A08D663-4995-40E3-B42C-3F910625F284} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{D962A009-834F-4EEC-AABB-430DF8F98E39} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B}
|
||||
@ -3009,8 +3025,6 @@ Global
|
||||
{8ACB33D9-C95B-47D4-8363-9731EE0930A0} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
|
||||
{CA716AE6-FE5C-40AC-BB8F-2C87912687AC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
|
||||
{923DF87C-CA99-4D1C-B1D2-959174E95BFA} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
@ -3023,6 +3037,7 @@ Global
|
||||
{37D07516-4185-43A4-924F-3C7A5D95ECF6} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD} = {8F62026A-294B-41C6-8839-87463613F216}
|
||||
{66614C26-314C-4B91-9071-76133422CFEF} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
||||
{89D0E199-B17A-418C-B2F8-7375B6708357} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE} = {9873BA05-4C41-4819-9283-CF45D795431B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
@ -58,3 +58,4 @@ Below are community created plugins that target a website or software. They are
|
||||
| [PowerSearch for 1Password](https://github.com/KairuDeibisu/PowerToysRunPlugin1Password) | [KairuDeibisu](https://github.com/KairuDeibisu) | An unofficial plugin for searching 1Password for usernames and passwords |
|
||||
| [HackMD](https://github.com/8LWXpg/PowerToysRun-HackMD) | [8LWXpg](https://github.com/8LWXpg) | Open HackMD notes |
|
||||
| [SSH](https://github.com/8LWXpg/PowerToysRun-SSH) | [8LWXpg](https://github.com/8LWXpg) | Connect to ssh clients |
|
||||
| [Bilibili](https://github.com/Whuihuan/PowerToysRun-Bilibili) | [Whuihuan](https://github.com/Whuihuan) | Use AVID or BVID to parse and jump to Bilibili |
|
||||
|
@ -0,0 +1,217 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Windows.Management.Deployment;
|
||||
|
||||
namespace WorkspacesCsharpLibrary.Models
|
||||
{
|
||||
public class BaseApplication : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public string PwaAppId { get; set; }
|
||||
|
||||
public string AppPath { get; set; }
|
||||
|
||||
private bool _isNotFound;
|
||||
|
||||
public string PackagedId { get; set; }
|
||||
|
||||
public string PackagedName { get; set; }
|
||||
|
||||
public string PackagedPublisherID { get; set; }
|
||||
|
||||
public string Aumid { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsNotFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isNotFound;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isNotFound != value)
|
||||
{
|
||||
_isNotFound = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Icon _icon;
|
||||
|
||||
public Icon Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_icon == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPackagedApp)
|
||||
{
|
||||
Uri uri = GetAppLogoByPackageFamilyName();
|
||||
var bitmap = new Bitmap(uri.LocalPath);
|
||||
var iconHandle = bitmap.GetHicon();
|
||||
_icon = Icon.FromHandle(iconHandle);
|
||||
}
|
||||
else if (IsEdge || IsChrome)
|
||||
{
|
||||
string iconFilename = PwaHelper.GetPwaIconFilename(PwaAppId);
|
||||
if (!string.IsNullOrEmpty(iconFilename))
|
||||
{
|
||||
Bitmap bitmap;
|
||||
if (iconFilename.EndsWith("ico", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
bitmap = new Bitmap(iconFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
bitmap = (Bitmap)Image.FromFile(iconFilename);
|
||||
}
|
||||
|
||||
var iconHandle = bitmap.GetHicon();
|
||||
_icon = Icon.FromHandle(iconHandle);
|
||||
}
|
||||
}
|
||||
|
||||
if (_icon == null)
|
||||
{
|
||||
_icon = Icon.ExtractAssociatedIcon(AppPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
IsNotFound = true;
|
||||
_icon = new Icon(@"Assets\Workspaces\DefaultIcon.ico");
|
||||
}
|
||||
}
|
||||
|
||||
return _icon;
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapImage _iconBitmapImage;
|
||||
|
||||
public BitmapImage IconBitmapImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconBitmapImage == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap previewBitmap = new Bitmap(32, 32);
|
||||
using (Graphics graphics = Graphics.FromImage(previewBitmap))
|
||||
{
|
||||
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
|
||||
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
|
||||
}
|
||||
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
previewBitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
BitmapImage bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
_iconBitmapImage = bitmapImage;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return _iconBitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEdge
|
||||
{
|
||||
get => AppPath.EndsWith("edge.exe", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public bool IsChrome
|
||||
{
|
||||
get => AppPath.EndsWith("chrome.exe", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public Uri GetAppLogoByPackageFamilyName()
|
||||
{
|
||||
var pkgManager = new PackageManager();
|
||||
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
|
||||
|
||||
if (pkg == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return pkg.Logo;
|
||||
}
|
||||
|
||||
private bool? _isPackagedApp;
|
||||
|
||||
public bool IsPackagedApp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isPackagedApp == null)
|
||||
{
|
||||
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_isPackagedApp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
Match match = packagedAppPathRegex.Match(appPath);
|
||||
_isPackagedApp = match.Success;
|
||||
if (match.Success)
|
||||
{
|
||||
PackagedName = match.Groups["APPID"].Value;
|
||||
PackagedPublisherID = match.Groups["PublisherID"].Value;
|
||||
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
|
||||
Aumid = $"{PackagedId}!App";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _isPackagedApp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
15
src/modules/Workspaces/WorkspacesCsharpLibrary/PwaApp.cs
Normal file
15
src/modules/Workspaces/WorkspacesCsharpLibrary/PwaApp.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
namespace WorkspacesCsharpLibrary
|
||||
{
|
||||
public class PwaApp
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
|
||||
public required string IconFilename { get; set; }
|
||||
|
||||
public required string AppId { get; set; }
|
||||
}
|
||||
}
|
90
src/modules/Workspaces/WorkspacesCsharpLibrary/PwaHelper.cs
Normal file
90
src/modules/Workspaces/WorkspacesCsharpLibrary/PwaHelper.cs
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace WorkspacesCsharpLibrary
|
||||
{
|
||||
public class PwaHelper
|
||||
{
|
||||
private const string ChromeBase = "Google\\Chrome\\User Data\\Default\\Web Applications";
|
||||
private const string EdgeBase = "Microsoft\\Edge\\User Data\\Default\\Web Applications";
|
||||
private const string ResourcesDir = "Manifest Resources";
|
||||
private const string IconsDir = "Icons";
|
||||
private const string PwaDirIdentifier = "_CRX_";
|
||||
|
||||
private static List<PwaApp> pwaApps = new List<PwaApp>();
|
||||
|
||||
public PwaHelper()
|
||||
{
|
||||
InitPwaData(EdgeBase);
|
||||
InitPwaData(ChromeBase);
|
||||
}
|
||||
|
||||
private void InitPwaData(string p_baseDir)
|
||||
{
|
||||
var baseFolderName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), p_baseDir);
|
||||
if (Directory.Exists(baseFolderName))
|
||||
{
|
||||
foreach (string subDir in Directory.GetDirectories(baseFolderName))
|
||||
{
|
||||
string dirName = Path.GetFileName(subDir);
|
||||
if (!dirName.StartsWith(PwaDirIdentifier, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string appId = dirName.Substring(PwaDirIdentifier.Length, dirName.Length - PwaDirIdentifier.Length).Trim('_');
|
||||
|
||||
foreach (string iconFile in Directory.GetFiles(subDir, "*.ico"))
|
||||
{
|
||||
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(iconFile);
|
||||
|
||||
pwaApps.Add(new PwaApp() { Name = filenameWithoutExtension, IconFilename = iconFile, AppId = appId });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string resourcesDir = Path.Combine(baseFolderName, ResourcesDir);
|
||||
if (Directory.Exists(resourcesDir))
|
||||
{
|
||||
foreach (string subDir in Directory.GetDirectories(resourcesDir))
|
||||
{
|
||||
string dirName = Path.GetFileName(subDir);
|
||||
if (pwaApps.Any(app => app.AppId == dirName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string iconsDir = Path.Combine(subDir, IconsDir);
|
||||
if (Directory.Exists(iconsDir))
|
||||
{
|
||||
foreach (string iconFile in Directory.GetFiles(iconsDir, "*.png"))
|
||||
{
|
||||
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(iconFile);
|
||||
|
||||
pwaApps.Add(new PwaApp() { Name = filenameWithoutExtension, IconFilename = iconFile, AppId = dirName });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPwaIconFilename(string pwaAppId)
|
||||
{
|
||||
var candidates = pwaApps.Where(x => x.AppId == pwaAppId).ToList();
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
return candidates.First().IconFilename;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.WorkspacesCsharpLibrary</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys Workspaces Csharp Library</AssemblyDescription>
|
||||
<Description>PowerToys Workspaces Csharp Library</Description>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
|
||||
<AssemblyName>PowerToys.WorkspacesCsharpLibrary</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -37,6 +37,8 @@ namespace WorkspacesEditor.Data
|
||||
|
||||
public string AppUserModelId { get; set; }
|
||||
|
||||
public string PwaAppId { get; set; }
|
||||
|
||||
public string CommandLineArguments { get; set; }
|
||||
|
||||
public bool IsElevated { get; set; }
|
||||
|
@ -13,18 +13,17 @@ using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using ManagedCommon;
|
||||
using Windows.Management.Deployment;
|
||||
using WorkspacesCsharpLibrary;
|
||||
using WorkspacesCsharpLibrary.Models;
|
||||
|
||||
namespace WorkspacesEditor.Models
|
||||
{
|
||||
public class Application : INotifyPropertyChanged, IDisposable
|
||||
public class Application : BaseApplication, IDisposable
|
||||
{
|
||||
private bool _isInitialized;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public Application()
|
||||
{
|
||||
}
|
||||
@ -37,6 +36,7 @@ namespace WorkspacesEditor.Models
|
||||
AppTitle = other.AppTitle;
|
||||
PackageFullName = other.PackageFullName;
|
||||
AppUserModelId = other.AppUserModelId;
|
||||
PwaAppId = other.PwaAppId;
|
||||
CommandLineArguments = other.CommandLineArguments;
|
||||
IsElevated = other.IsElevated;
|
||||
CanLaunchElevated = other.CanLaunchElevated;
|
||||
@ -100,8 +100,6 @@ namespace WorkspacesEditor.Models
|
||||
|
||||
public string AppName { get; set; }
|
||||
|
||||
public string AppPath { get; set; }
|
||||
|
||||
public string AppTitle { get; set; }
|
||||
|
||||
public string PackageFullName { get; set; }
|
||||
@ -187,26 +185,6 @@ namespace WorkspacesEditor.Models
|
||||
|
||||
public bool IsAppMainParamVisible { get => !string.IsNullOrWhiteSpace(_appMainParams); }
|
||||
|
||||
private bool _isNotFound;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsNotFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isNotFound;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isNotFound != value)
|
||||
{
|
||||
_isNotFound = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsHighlighted { get; set; }
|
||||
|
||||
@ -222,100 +200,6 @@ namespace WorkspacesEditor.Models
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private Icon _icon = null;
|
||||
|
||||
[JsonIgnore]
|
||||
public Icon Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_icon == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPackagedApp)
|
||||
{
|
||||
Uri uri = GetAppLogoByPackageFamilyName();
|
||||
var bitmap = new Bitmap(uri.LocalPath);
|
||||
var iconHandle = bitmap.GetHicon();
|
||||
_icon = Icon.FromHandle(iconHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
_icon = Icon.ExtractAssociatedIcon(AppPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.LogWarning($"Icon not found on app path: {AppPath}. Using default icon");
|
||||
IsNotFound = true;
|
||||
_icon = new Icon(@"Assets\Workspaces\DefaultIcon.ico");
|
||||
}
|
||||
}
|
||||
|
||||
return _icon;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri GetAppLogoByPackageFamilyName()
|
||||
{
|
||||
var pkgManager = new PackageManager();
|
||||
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
|
||||
|
||||
if (pkg == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return pkg.Logo;
|
||||
}
|
||||
|
||||
private BitmapImage _iconBitmapImage;
|
||||
|
||||
public BitmapImage IconBitmapImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconBitmapImage == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap previewBitmap = new Bitmap(32, 32);
|
||||
using (Graphics graphics = Graphics.FromImage(previewBitmap))
|
||||
{
|
||||
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
|
||||
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
|
||||
}
|
||||
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
previewBitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
_iconBitmapImage = bitmapImage;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return _iconBitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
private WindowPosition _position;
|
||||
|
||||
public WindowPosition Position
|
||||
@ -367,56 +251,11 @@ namespace WorkspacesEditor.Models
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public void InitializationFinished()
|
||||
{
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
private bool? _isPackagedApp;
|
||||
|
||||
public string PackagedId { get; set; }
|
||||
|
||||
public string PackagedName { get; set; }
|
||||
|
||||
public string PackagedPublisherID { get; set; }
|
||||
|
||||
public string Aumid { get; set; }
|
||||
|
||||
public bool IsPackagedApp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isPackagedApp == null)
|
||||
{
|
||||
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_isPackagedApp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
Match match = packagedAppPathRegex.Match(appPath);
|
||||
_isPackagedApp = match.Success;
|
||||
if (match.Success)
|
||||
{
|
||||
PackagedName = match.Groups["APPID"].Value;
|
||||
PackagedPublisherID = match.Groups["PublisherID"].Value;
|
||||
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
|
||||
Aumid = $"{PackagedId}!App";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _isPackagedApp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isExpanded;
|
||||
|
||||
public bool IsExpanded
|
||||
@ -454,11 +293,6 @@ namespace WorkspacesEditor.Models
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal void CommandLineTextChanged(string newCommandLineValue)
|
||||
{
|
||||
CommandLineArguments = newCommandLineValue;
|
||||
|
@ -251,10 +251,11 @@ namespace WorkspacesEditor.Models
|
||||
{
|
||||
Models.Application newApp = new Models.Application()
|
||||
{
|
||||
Id = app.Id != null ? app.Id : $"{{{Guid.NewGuid().ToString()}}}",
|
||||
Id = string.IsNullOrEmpty(app.Id) ? $"{{{Guid.NewGuid().ToString()}}}" : app.Id,
|
||||
AppName = app.Application,
|
||||
AppPath = app.ApplicationPath,
|
||||
AppTitle = app.Title,
|
||||
PwaAppId = string.IsNullOrEmpty(app.PwaAppId) ? string.Empty : app.PwaAppId,
|
||||
PackageFullName = app.PackageFullName,
|
||||
AppUserModelId = app.AppUserModelId,
|
||||
Parent = this,
|
||||
|
@ -75,16 +75,16 @@ namespace WorkspacesEditor.Utils
|
||||
|
||||
foreach (Application app in appsIncluded)
|
||||
{
|
||||
if (repeatCounter.TryGetValue(app.AppPath, out int value))
|
||||
if (repeatCounter.TryGetValue(app.AppPath + app.AppTitle, out int value))
|
||||
{
|
||||
repeatCounter[app.AppPath] = ++value;
|
||||
repeatCounter[app.AppPath + app.AppTitle] = ++value;
|
||||
}
|
||||
else
|
||||
{
|
||||
repeatCounter.Add(app.AppPath, 1);
|
||||
repeatCounter.Add(app.AppPath + app.AppTitle, 1);
|
||||
}
|
||||
|
||||
app.RepeatIndex = repeatCounter[app.AppPath];
|
||||
app.RepeatIndex = repeatCounter[app.AppPath + app.AppTitle];
|
||||
}
|
||||
|
||||
foreach (Application app in project.Applications.Where(x => !x.IsIncluded))
|
||||
|
@ -103,6 +103,7 @@ namespace WorkspacesEditor.Utils
|
||||
Title = app.AppTitle,
|
||||
PackageFullName = app.PackageFullName,
|
||||
AppUserModelId = app.AppUserModelId,
|
||||
PwaAppId = app.PwaAppId,
|
||||
CommandLineArguments = app.CommandLineArguments,
|
||||
IsElevated = app.IsElevated,
|
||||
CanLaunchElevated = app.CanLaunchElevated,
|
||||
|
@ -14,10 +14,10 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using WorkspacesCsharpLibrary;
|
||||
using WorkspacesEditor.Data;
|
||||
using WorkspacesEditor.Models;
|
||||
using WorkspacesEditor.Telemetry;
|
||||
@ -39,6 +39,7 @@ namespace WorkspacesEditor.ViewModels
|
||||
private MainWindow _mainWindow;
|
||||
private Timer lastUpdatedTimer;
|
||||
private WorkspacesSettings settings;
|
||||
private PwaHelper _pwaHelper;
|
||||
|
||||
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
|
||||
|
||||
@ -147,6 +148,7 @@ namespace WorkspacesEditor.ViewModels
|
||||
settings = Utils.Settings.ReadSettings();
|
||||
_orderByIndex = (int)settings.Properties.SortBy;
|
||||
_workspacesEditorIO = workspacesEditorIO;
|
||||
_pwaHelper = new PwaHelper();
|
||||
lastUpdatedTimer = new System.Timers.Timer();
|
||||
lastUpdatedTimer.Interval = 1000;
|
||||
lastUpdatedTimer.Elapsed += LastUpdatedTimerElapsed;
|
||||
|
@ -65,7 +65,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ControlzEx" />
|
||||
<PackageReference Include="ModernWpfUI" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
</ItemGroup>
|
||||
@ -77,6 +76,7 @@
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
|
@ -80,7 +80,7 @@
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Source="{Binding IconBitmapImage}" />
|
||||
Source="{Binding IconBitmapImage, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Width="20"
|
||||
|
@ -18,6 +18,15 @@ using namespace Windows::Management::Deployment;
|
||||
|
||||
namespace AppLauncher
|
||||
{
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const std::wstring EdgeFilename = L"msedge.exe";
|
||||
const std::wstring EdgePwaFilename = L"msedge_proxy.exe";
|
||||
const std::wstring ChromeFilename = L"chrome.exe";
|
||||
const std::wstring ChromePwaFilename = L"chrome_proxy.exe";
|
||||
const std::wstring PwaCommandLineAddition = L"--profile-directory=Default --app-id=";
|
||||
}
|
||||
|
||||
Result<SHELLEXECUTEINFO, std::wstring> LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
|
||||
{
|
||||
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
||||
@ -133,30 +142,50 @@ namespace AppLauncher
|
||||
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
|
||||
}
|
||||
|
||||
std::wstring appPathFinal;
|
||||
std::wstring commandLineArgsFinal;
|
||||
appPathFinal = app.path;
|
||||
commandLineArgsFinal = app.commandLineArgs;
|
||||
|
||||
if (!launched && !app.pwaAppId.empty())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!launched)
|
||||
{
|
||||
Logger::trace(L"Launching {} at {}", app.name, app.path);
|
||||
Logger::trace(L"Launching {} at {}", app.name, appPathFinal);
|
||||
|
||||
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
|
||||
DWORD dwAttrib = GetFileAttributesW(appPathFinal.c_str());
|
||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
Logger::error(L"File not found at {}", app.path);
|
||||
launchErrors.push_back({ std::filesystem::path(app.path).filename(), L"File not found" });
|
||||
Logger::error(L"File not found at {}", appPathFinal);
|
||||
launchErrors.push_back({ std::filesystem::path(appPathFinal).filename(), L"File not found" });
|
||||
return false;
|
||||
}
|
||||
|
||||
auto res = LaunchApp(app.path, app.commandLineArgs, app.isElevated);
|
||||
auto res = LaunchApp(appPathFinal, commandLineArgsFinal, app.isElevated);
|
||||
if (res.isOk())
|
||||
{
|
||||
launched = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
|
||||
launchErrors.push_back({ std::filesystem::path(appPathFinal).filename(), res.error() });
|
||||
}
|
||||
}
|
||||
|
||||
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
|
||||
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), appPathFinal);
|
||||
return launched;
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ namespace WorkspacesLauncherUI.Data
|
||||
|
||||
public string AppUserModelId { get; set; }
|
||||
|
||||
public string PwaAppId { get; set; }
|
||||
|
||||
public string CommandLineArguments { get; set; }
|
||||
|
||||
public bool IsElevated { get; set; }
|
||||
|
@ -3,77 +3,23 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using ManagedCommon;
|
||||
using Windows.Management.Deployment;
|
||||
using WorkspacesCsharpLibrary.Models;
|
||||
using WorkspacesLauncherUI.Data;
|
||||
|
||||
namespace WorkspacesLauncherUI.Models
|
||||
{
|
||||
public class AppLaunching : INotifyPropertyChanged, IDisposable
|
||||
public class AppLaunching : BaseApplication, IDisposable
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public ApplicationWrapper Application { get; set; }
|
||||
|
||||
public bool Loading => LaunchState == LaunchingState.Waiting || LaunchState == LaunchingState.Launched;
|
||||
|
||||
private Icon _icon;
|
||||
|
||||
public Icon Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_icon == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPackagedApp)
|
||||
{
|
||||
Uri uri = GetAppLogoByPackageFamilyName();
|
||||
var bitmap = new Bitmap(uri.LocalPath);
|
||||
var iconHandle = bitmap.GetHicon();
|
||||
_icon = Icon.FromHandle(iconHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
_icon = Icon.ExtractAssociatedIcon(Application.ApplicationPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.LogWarning($"Icon not found on app path: {Application.ApplicationPath}. Using default icon");
|
||||
IsNotFound = true;
|
||||
_icon = new Icon(@"Assets\Workspaces\DefaultIcon.ico");
|
||||
}
|
||||
}
|
||||
|
||||
return _icon;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return Application.Application;
|
||||
}
|
||||
}
|
||||
public string Name { get; set; }
|
||||
|
||||
public LaunchingState LaunchState { get; set; }
|
||||
|
||||
@ -96,128 +42,5 @@ namespace WorkspacesLauncherUI.Models
|
||||
_ => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
|
||||
};
|
||||
}
|
||||
|
||||
private bool _isNotFound;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsNotFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isNotFound;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isNotFound != value)
|
||||
{
|
||||
_isNotFound = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Uri GetAppLogoByPackageFamilyName()
|
||||
{
|
||||
var pkgManager = new PackageManager();
|
||||
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
|
||||
|
||||
if (pkg == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return pkg.Logo;
|
||||
}
|
||||
|
||||
private bool? _isPackagedApp;
|
||||
|
||||
public string PackagedId { get; set; }
|
||||
|
||||
public string PackagedName { get; set; }
|
||||
|
||||
public string PackagedPublisherID { get; set; }
|
||||
|
||||
public string Aumid { get; set; }
|
||||
|
||||
public bool IsPackagedApp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isPackagedApp == null)
|
||||
{
|
||||
if (!Application.ApplicationPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_isPackagedApp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
string appPath = Application.ApplicationPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
Match match = packagedAppPathRegex.Match(appPath);
|
||||
_isPackagedApp = match.Success;
|
||||
if (match.Success)
|
||||
{
|
||||
PackagedName = match.Groups["APPID"].Value;
|
||||
PackagedPublisherID = match.Groups["PublisherID"].Value;
|
||||
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
|
||||
Aumid = $"{PackagedId}!App";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _isPackagedApp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapImage _iconBitmapImage;
|
||||
|
||||
public BitmapImage IconBitmapImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconBitmapImage == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap previewBitmap = new Bitmap(32, 32);
|
||||
using (Graphics graphics = Graphics.FromImage(previewBitmap))
|
||||
{
|
||||
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
|
||||
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
|
||||
}
|
||||
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
previewBitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
_iconBitmapImage = bitmapImage;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Exception while drawing icon for app with path: {Application.ApplicationPath}. Exception message: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return _iconBitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,10 @@ using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
using ManagedCommon;
|
||||
using WorkspacesCsharpLibrary;
|
||||
using WorkspacesLauncherUI.Data;
|
||||
using WorkspacesLauncherUI.Models;
|
||||
using WorkspacesLauncherUI.Utils;
|
||||
|
||||
namespace WorkspacesLauncherUI.ViewModels
|
||||
{
|
||||
@ -20,6 +22,7 @@ namespace WorkspacesLauncherUI.ViewModels
|
||||
|
||||
private StatusWindow _snapshotWindow;
|
||||
private int launcherProcessID;
|
||||
private PwaHelper _pwaHelper;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
@ -30,6 +33,8 @@ namespace WorkspacesLauncherUI.ViewModels
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
_pwaHelper = new PwaHelper();
|
||||
|
||||
// receive IPC Message
|
||||
App.IPCMessageReceivedCallback = (string msg) =>
|
||||
{
|
||||
@ -54,7 +59,11 @@ namespace WorkspacesLauncherUI.ViewModels
|
||||
{
|
||||
appLaunchingList.Add(new AppLaunching()
|
||||
{
|
||||
Application = app.Application,
|
||||
Name = app.Application.Application,
|
||||
AppPath = app.Application.ApplicationPath,
|
||||
PackagedName = app.Application.PackageFullName,
|
||||
Aumid = app.Application.AppUserModelId,
|
||||
PwaAppId = app.Application.PwaAppId,
|
||||
LaunchState = app.State,
|
||||
});
|
||||
}
|
||||
|
@ -79,6 +79,7 @@
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
|
@ -79,6 +79,7 @@ namespace WorkspacesData
|
||||
const static wchar_t* AppPathID = L"application-path";
|
||||
const static wchar_t* AppPackageFullNameID = L"package-full-name";
|
||||
const static wchar_t* AppUserModelId = L"app-user-model-id";
|
||||
const static wchar_t* PwaAppId = L"pwa-app-id";
|
||||
const static wchar_t* AppTitleID = L"title";
|
||||
const static wchar_t* CommandLineArgsID = L"command-line-arguments";
|
||||
const static wchar_t* ElevatedID = L"is-elevated";
|
||||
@ -98,6 +99,7 @@ namespace WorkspacesData
|
||||
json.SetNamedValue(NonLocalizable::AppTitleID, json::value(data.title));
|
||||
json.SetNamedValue(NonLocalizable::AppPackageFullNameID, json::value(data.packageFullName));
|
||||
json.SetNamedValue(NonLocalizable::AppUserModelId, json::value(data.appUserModelId));
|
||||
json.SetNamedValue(NonLocalizable::PwaAppId, json::value(data.pwaAppId));
|
||||
json.SetNamedValue(NonLocalizable::CommandLineArgsID, json::value(data.commandLineArgs));
|
||||
json.SetNamedValue(NonLocalizable::ElevatedID, json::value(data.isElevated));
|
||||
json.SetNamedValue(NonLocalizable::CanLaunchElevatedID, json::value(data.canLaunchElevated));
|
||||
@ -136,6 +138,11 @@ namespace WorkspacesData
|
||||
result.appUserModelId = json.GetNamedString(NonLocalizable::AppUserModelId);
|
||||
}
|
||||
|
||||
if (json.HasKey(NonLocalizable::PwaAppId))
|
||||
{
|
||||
result.pwaAppId = json.GetNamedString(NonLocalizable::PwaAppId);
|
||||
}
|
||||
|
||||
result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID);
|
||||
|
||||
if (json.HasKey(NonLocalizable::ElevatedID))
|
||||
|
@ -31,6 +31,7 @@ namespace WorkspacesData
|
||||
std::wstring path;
|
||||
std::wstring packageFullName;
|
||||
std::wstring appUserModelId;
|
||||
std::wstring pwaAppId;
|
||||
std::wstring commandLineArgs;
|
||||
bool isElevated{};
|
||||
bool canLaunchElevated{};
|
||||
|
409
src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.cpp
Normal file
409
src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
#include "pch.h"
|
||||
#include "PwaHelper.h"
|
||||
#include <ShlObj.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <winternl.h>
|
||||
#include <initguid.h>
|
||||
#include <filesystem>
|
||||
#include <wil/result_macros.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <wil\com.h>
|
||||
#pragma comment(lib, "ntdll.lib")
|
||||
|
||||
namespace SnapshotUtils
|
||||
{
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const std::wstring EdgeAppIdIdentifier = L"--app-id=";
|
||||
const std::wstring ChromeAppIdIdentifier = L"Chrome._crx_";
|
||||
const std::wstring ChromeBase = L"Google\\Chrome\\User Data\\Default\\Web Applications";
|
||||
const std::wstring EdgeBase = L"Microsoft\\Edge\\User Data\\Default\\Web Applications";
|
||||
const std::wstring ChromeDirPrefix = L"_crx_";
|
||||
const std::wstring EdgeDirPrefix = L"_crx__";
|
||||
}
|
||||
// {c8900b66-a973-584b-8cae-355b7f55341b}
|
||||
DEFINE_GUID(CLSID_StartMenuCacheAndAppResolver, 0x660b90c8, 0x73a9, 0x4b58, 0x8c, 0xae, 0x35, 0x5b, 0x7f, 0x55, 0x34, 0x1b);
|
||||
|
||||
// {46a6eeff-908e-4dc6-92a6-64be9177b41c}
|
||||
DEFINE_GUID(IID_IAppResolver_7, 0x46a6eeff, 0x908e, 0x4dc6, 0x92, 0xa6, 0x64, 0xbe, 0x91, 0x77, 0xb4, 0x1c);
|
||||
|
||||
// {de25675a-72de-44b4-9373-05170450c140}
|
||||
DEFINE_GUID(IID_IAppResolver_8, 0xde25675a, 0x72de, 0x44b4, 0x93, 0x73, 0x05, 0x17, 0x04, 0x50, 0xc1, 0x40);
|
||||
|
||||
struct IAppResolver_7 : public IUnknown
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcut() = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAppIDForWindow(HWND hWnd, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAppIDForProcess(DWORD dwProcessId, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
|
||||
};
|
||||
|
||||
struct IAppResolver_8 : public IUnknown
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcut() = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcutObject() = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAppIDForWindow(HWND hWnd, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetAppIDForProcess(DWORD dwProcessId, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0;
|
||||
};
|
||||
|
||||
BOOL GetAppId_7(HWND hWnd, std::wstring* result)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
wil::com_ptr<IAppResolver_7> appResolver;
|
||||
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast<void**>(appResolver.put()));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
wil::unique_cotaskmem_string pszAppId;
|
||||
hr = appResolver->GetAppIDForWindow(hWnd, &pszAppId, NULL, NULL, NULL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
*result = std::wstring(pszAppId.get());
|
||||
}
|
||||
|
||||
appResolver->Release();
|
||||
}
|
||||
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
BOOL GetAppId_8(HWND hWnd, std::wstring* result)
|
||||
{
|
||||
HRESULT hr;
|
||||
*result = L"";
|
||||
|
||||
wil::com_ptr<IAppResolver_8> appResolver;
|
||||
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast<void**>(appResolver.put()));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
wil::unique_cotaskmem_string pszAppId;
|
||||
hr = appResolver->GetAppIDForWindow(hWnd, &pszAppId, NULL, NULL, NULL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
*result = std::wstring(pszAppId.get());
|
||||
}
|
||||
|
||||
appResolver->Release();
|
||||
}
|
||||
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
BOOL PwaHelper::GetAppId(HWND hWnd, std::wstring* result)
|
||||
{
|
||||
HRESULT hr = GetAppId_8(hWnd, result);
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
hr = GetAppId_7(hWnd, result);
|
||||
}
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
BOOL GetProcessId_7(DWORD dwProcessId, std::wstring* result)
|
||||
{
|
||||
HRESULT hr;
|
||||
*result = L"";
|
||||
|
||||
wil::com_ptr<IAppResolver_7> appResolver;
|
||||
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast<void**>(appResolver.put()));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
wil::unique_cotaskmem_string pszAppId;
|
||||
hr = appResolver->GetAppIDForProcess(dwProcessId, &pszAppId, NULL, NULL, NULL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
*result = std::wstring(pszAppId.get());
|
||||
}
|
||||
|
||||
appResolver->Release();
|
||||
}
|
||||
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
BOOL GetProcessId_8(DWORD dwProcessId, std::wstring* result)
|
||||
{
|
||||
HRESULT hr;
|
||||
*result = L"";
|
||||
|
||||
wil::com_ptr<IAppResolver_8> appResolver;
|
||||
hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast<void**>(appResolver.put()));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
wil::unique_cotaskmem_string pszAppId;
|
||||
hr = appResolver->GetAppIDForProcess(dwProcessId, &pszAppId, NULL, NULL, NULL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
*result = std::wstring(pszAppId.get());
|
||||
}
|
||||
|
||||
appResolver->Release();
|
||||
}
|
||||
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
BOOL GetProcessId(DWORD dwProcessId, std::wstring* result)
|
||||
{
|
||||
HRESULT hr = GetProcessId_8(dwProcessId, result);
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
hr = GetProcessId_7(dwProcessId, result);
|
||||
}
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
std::wstring GetProcCommandLine(DWORD pid)
|
||||
{
|
||||
std::wstring commandLine;
|
||||
|
||||
// Open a handle to the process
|
||||
const HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
||||
if (process == NULL)
|
||||
{
|
||||
Logger::error(L"Failed to open the process, error: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the address of the ProcessEnvironmentBlock
|
||||
PROCESS_BASIC_INFORMATION pbi = {};
|
||||
NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
|
||||
if (status != STATUS_SUCCESS)
|
||||
{
|
||||
Logger::error(L"Failed to query the process, error: {}", status);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the address of the process parameters in the ProcessEnvironmentBlock
|
||||
PEB processEnvironmentBlock = {};
|
||||
if (!ReadProcessMemory(process, pbi.PebBaseAddress, &processEnvironmentBlock, sizeof(processEnvironmentBlock), NULL))
|
||||
{
|
||||
Logger::error(L"Failed to read the process ProcessEnvironmentBlock, error: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the command line arguments from the process parameters
|
||||
RTL_USER_PROCESS_PARAMETERS params = {};
|
||||
if (!ReadProcessMemory(process, processEnvironmentBlock.ProcessParameters, ¶ms, sizeof(params), NULL))
|
||||
{
|
||||
Logger::error(L"Failed to read the process params, error: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
else
|
||||
{
|
||||
UNICODE_STRING& commandLineArgs = params.CommandLine;
|
||||
std::vector<WCHAR> buffer(commandLineArgs.Length / sizeof(WCHAR));
|
||||
if (!ReadProcessMemory(process, commandLineArgs.Buffer, buffer.data(), commandLineArgs.Length, NULL))
|
||||
{
|
||||
Logger::error(L"Failed to read the process command line, error: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
else
|
||||
{
|
||||
commandLine.assign(buffer.data(), buffer.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(process);
|
||||
}
|
||||
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
// Finds all PwaHelper.exe processes with the specified parent process ID
|
||||
std::vector<DWORD> FindPwaHelperProcessIds()
|
||||
{
|
||||
std::vector<DWORD> pwaHelperProcessIds;
|
||||
const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Logger::info(L"Invalid handle when creating snapshot for the search for PwaHelper processes");
|
||||
return pwaHelperProcessIds;
|
||||
}
|
||||
|
||||
PROCESSENTRY32 pe;
|
||||
pe.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(hSnapshot, &pe))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_wcsicmp(pe.szExeFile, L"PwaHelper.exe") == 0)
|
||||
{
|
||||
Logger::info(L"Found a PWA process with id {}", pe.th32ProcessID);
|
||||
pwaHelperProcessIds.push_back(pe.th32ProcessID);
|
||||
}
|
||||
} while (Process32Next(hSnapshot, &pe));
|
||||
}
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return pwaHelperProcessIds;
|
||||
}
|
||||
|
||||
void PwaHelper::InitAumidToAppId()
|
||||
{
|
||||
if (pwaAumidToAppId.size() > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pwaHelperProcessIds = FindPwaHelperProcessIds();
|
||||
Logger::info(L"Found {} edge Pwa helper processes", pwaHelperProcessIds.size());
|
||||
for (const auto subProcessID : pwaHelperProcessIds)
|
||||
{
|
||||
std::wstring aumidID;
|
||||
GetProcessId(subProcessID, &aumidID);
|
||||
std::wstring commandLineArg = GetProcCommandLine(subProcessID);
|
||||
auto appIdIndexStart = commandLineArg.find(NonLocalizable::EdgeAppIdIdentifier);
|
||||
if (appIdIndexStart != std::wstring::npos)
|
||||
{
|
||||
commandLineArg = commandLineArg.substr(appIdIndexStart + NonLocalizable::EdgeAppIdIdentifier.size());
|
||||
auto appIdIndexEnd = commandLineArg.find(L" ");
|
||||
if (appIdIndexEnd != std::wstring::npos)
|
||||
{
|
||||
commandLineArg = commandLineArg.substr(0, appIdIndexEnd);
|
||||
}
|
||||
}
|
||||
std::wstring appId{ commandLineArg };
|
||||
pwaAumidToAppId.insert(std::map<std::wstring, std::wstring>::value_type(aumidID, appId));
|
||||
Logger::info(L"Found an edge Pwa helper process with AumidID {} and PwaAppId {}", aumidID, appId);
|
||||
|
||||
PWSTR path = NULL;
|
||||
HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
std::filesystem::path fsPath(path);
|
||||
fsPath /= NonLocalizable::EdgeBase;
|
||||
for (const auto& directory : std::filesystem::directory_iterator(fsPath))
|
||||
{
|
||||
if (directory.is_directory())
|
||||
{
|
||||
const std::filesystem::path directoryName = directory.path().filename();
|
||||
if (directoryName.wstring().find(NonLocalizable::EdgeDirPrefix) == 0)
|
||||
{
|
||||
const std::wstring appIdDir = directoryName.wstring().substr(NonLocalizable::EdgeDirPrefix.size());
|
||||
if (appIdDir == appId)
|
||||
{
|
||||
for (const auto& filename : std::filesystem::directory_iterator(directory))
|
||||
{
|
||||
if (!filename.is_directory())
|
||||
{
|
||||
const std::filesystem::path filenameString = filename.path().filename();
|
||||
if (filenameString.extension().wstring() == L".ico")
|
||||
{
|
||||
pwaAppIdsToAppNames.insert(std::map<std::wstring, std::wstring>::value_type(appId, filenameString.stem().wstring()));
|
||||
Logger::info(L"Storing an edge Pwa app name {} for PwaAppId {}", filenameString.stem().wstring(), appId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CoTaskMemFree(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOL PwaHelper::GetPwaAppId(std::wstring windowAumid, std::wstring* result)
|
||||
{
|
||||
const auto pwaIndex = pwaAumidToAppId.find(windowAumid);
|
||||
if (pwaIndex != pwaAumidToAppId.end())
|
||||
{
|
||||
*result = pwaIndex->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOL PwaHelper::SearchPwaName(std::wstring pwaAppId, std::wstring windowAumid, std::wstring* pwaName)
|
||||
{
|
||||
const auto index = pwaAppIdsToAppNames.find(pwaAppId);
|
||||
if (index != pwaAppIdsToAppNames.end())
|
||||
{
|
||||
*pwaName = index->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring nameFromAumid{ windowAumid };
|
||||
const std::size_t delimiterPos = nameFromAumid.find(L"-");
|
||||
if (delimiterPos != std::string::npos)
|
||||
{
|
||||
nameFromAumid = nameFromAumid.substr(0, delimiterPos);
|
||||
}
|
||||
|
||||
*pwaName = nameFromAumid;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PwaHelper::InitChromeAppIds()
|
||||
{
|
||||
if (chromeAppIds.size() > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PWSTR path = NULL;
|
||||
HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
std::filesystem::path fsPath(path);
|
||||
fsPath /= NonLocalizable::ChromeBase;
|
||||
for (const auto& directory : std::filesystem::directory_iterator(fsPath))
|
||||
{
|
||||
if (directory.is_directory())
|
||||
{
|
||||
const std::filesystem::path directoryName = directory.path().filename();
|
||||
if (directoryName.wstring().find(NonLocalizable::ChromeDirPrefix) == 0)
|
||||
{
|
||||
const std::wstring appId = directoryName.wstring().substr(NonLocalizable::ChromeDirPrefix.size());
|
||||
chromeAppIds.push_back(appId);
|
||||
for (const auto& filename : std::filesystem::directory_iterator(directory))
|
||||
{
|
||||
if (!filename.is_directory())
|
||||
{
|
||||
const std::filesystem::path filenameString = filename.path().filename();
|
||||
if (filenameString.extension().wstring() == L".ico")
|
||||
{
|
||||
pwaAppIdsToAppNames.insert(std::map<std::wstring, std::wstring>::value_type(appId, filenameString.stem().wstring()));
|
||||
Logger::info(L"Found an installed chrome Pwa app {} with PwaAppId {}", filenameString.stem().wstring(), appId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CoTaskMemFree(path);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL PwaHelper::SearchPwaAppId(std::wstring windowAumid, std::wstring* pwaAppId)
|
||||
{
|
||||
const auto appIdIndexStart = windowAumid.find(NonLocalizable::ChromeAppIdIdentifier);
|
||||
if (appIdIndexStart != std::wstring::npos)
|
||||
{
|
||||
windowAumid = windowAumid.substr(appIdIndexStart + NonLocalizable::ChromeAppIdIdentifier.size());
|
||||
const auto appIdIndexEnd = windowAumid.find(L" ");
|
||||
if (appIdIndexEnd != std::wstring::npos)
|
||||
{
|
||||
windowAumid = windowAumid.substr(0, appIdIndexEnd);
|
||||
}
|
||||
|
||||
const std::wstring windowAumidBegin = windowAumid.substr(0, 10);
|
||||
for (const auto chromeAppId : chromeAppIds)
|
||||
{
|
||||
if (chromeAppId.find(windowAumidBegin) == 0)
|
||||
{
|
||||
*pwaAppId = chromeAppId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*pwaAppId = L"";
|
||||
return false;
|
||||
}
|
||||
}
|
20
src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.h
Normal file
20
src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
namespace SnapshotUtils
|
||||
{
|
||||
class PwaHelper
|
||||
{
|
||||
public:
|
||||
void InitAumidToAppId();
|
||||
BOOL GetAppId(HWND hWnd, std::wstring* result);
|
||||
BOOL GetPwaAppId(std::wstring windowAumid, std::wstring* result);
|
||||
BOOL SearchPwaName(std::wstring pwaAppId, std::wstring windowAumid, std::wstring* pwaName);
|
||||
void InitChromeAppIds();
|
||||
BOOL SearchPwaAppId(std::wstring windowAumid, std::wstring* pwaAppId);
|
||||
|
||||
private:
|
||||
std::map<std::wstring, std::wstring> pwaAumidToAppId;
|
||||
std::vector<std::wstring> chromeAppIds;
|
||||
std::map<std::wstring, std::wstring> pwaAppIdsToAppNames;
|
||||
};
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
#include "pch.h"
|
||||
#include "SnapshotUtils.h"
|
||||
|
||||
#include <comdef.h>
|
||||
#include <Wbemidl.h>
|
||||
|
||||
#include <common/utils/elevation.h>
|
||||
#include <common/utils/process_path.h>
|
||||
#include <common/notifications/NotificationUtil.h>
|
||||
@ -12,142 +9,17 @@
|
||||
#include <workspaces-common/WindowFilter.h>
|
||||
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
#include <PwaHelper.h>
|
||||
|
||||
#pragma comment(lib, "ntdll.lib")
|
||||
|
||||
namespace SnapshotUtils
|
||||
{
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const std::wstring ApplicationFrameHost = L"ApplicationFrameHost.exe";
|
||||
}
|
||||
|
||||
class WbemHelper
|
||||
{
|
||||
public:
|
||||
WbemHelper() = default;
|
||||
~WbemHelper()
|
||||
{
|
||||
if (m_services)
|
||||
{
|
||||
m_services->Release();
|
||||
}
|
||||
|
||||
if (m_locator)
|
||||
{
|
||||
m_locator->Release();
|
||||
}
|
||||
}
|
||||
|
||||
bool Initialize()
|
||||
{
|
||||
// Obtain the initial locator to WMI.
|
||||
HRESULT hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast<LPVOID*>(&m_locator));
|
||||
if (FAILED(hres))
|
||||
{
|
||||
Logger::error(L"Failed to create IWbemLocator object. Error: {}", get_last_error_or_default(hres));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connect to WMI through the IWbemLocator::ConnectServer method.
|
||||
hres = m_locator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &m_services);
|
||||
if (FAILED(hres))
|
||||
{
|
||||
Logger::error(L"Could not connect to WMI. Error: {}", get_last_error_or_default(hres));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set security levels on the proxy.
|
||||
hres = CoSetProxyBlanket(m_services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
|
||||
if (FAILED(hres))
|
||||
{
|
||||
Logger::error(L"Could not set proxy blanket. Error: {}", get_last_error_or_default(hres));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring GetCommandLineArgs(DWORD processID) const
|
||||
{
|
||||
static std::wstring property = L"CommandLine";
|
||||
std::wstring query = L"SELECT " + property + L" FROM Win32_Process WHERE ProcessId = " + std::to_wstring(processID);
|
||||
return Query(query, property);
|
||||
}
|
||||
|
||||
std::wstring GetExecutablePath(DWORD processID) const
|
||||
{
|
||||
static std::wstring property = L"ExecutablePath";
|
||||
std::wstring query = L"SELECT " + property + L" FROM Win32_Process WHERE ProcessId = " + std::to_wstring(processID);
|
||||
return Query(query, property);
|
||||
}
|
||||
|
||||
private:
|
||||
std::wstring Query(const std::wstring& query, const std::wstring& propertyName) const
|
||||
{
|
||||
if (!m_locator || !m_services)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
IEnumWbemClassObject* pEnumerator = NULL;
|
||||
|
||||
HRESULT hres = m_services->ExecQuery(bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
|
||||
if (FAILED(hres))
|
||||
{
|
||||
Logger::error(L"Query for process failed. Error: {}", get_last_error_or_default(hres));
|
||||
return L"";
|
||||
}
|
||||
|
||||
IWbemClassObject* pClassObject = NULL;
|
||||
ULONG uReturn = 0;
|
||||
std::wstring result = L"";
|
||||
while (pEnumerator)
|
||||
{
|
||||
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pClassObject, &uReturn);
|
||||
if (uReturn == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
VARIANT vtProp;
|
||||
hr = pClassObject->Get(propertyName.c_str(), 0, &vtProp, 0, 0);
|
||||
if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR)
|
||||
{
|
||||
result = vtProp.bstrVal;
|
||||
}
|
||||
VariantClear(&vtProp);
|
||||
|
||||
pClassObject->Release();
|
||||
}
|
||||
|
||||
pEnumerator->Release();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
IWbemLocator* m_locator = NULL;
|
||||
IWbemServices* m_services = NULL;
|
||||
};
|
||||
|
||||
std::wstring GetCommandLineArgs(DWORD processID, const WbemHelper& wbemHelper)
|
||||
{
|
||||
std::wstring executablePath = wbemHelper.GetExecutablePath(processID);
|
||||
std::wstring commandLineArgs = wbemHelper.GetCommandLineArgs(processID);
|
||||
|
||||
if (!commandLineArgs.empty())
|
||||
{
|
||||
auto pos = commandLineArgs.find(executablePath);
|
||||
if (pos != std::wstring::npos)
|
||||
{
|
||||
commandLineArgs = commandLineArgs.substr(pos + executablePath.size());
|
||||
auto spacePos = commandLineArgs.find_first_of(' ');
|
||||
if (spacePos != std::wstring::npos)
|
||||
{
|
||||
commandLineArgs = commandLineArgs.substr(spacePos + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return commandLineArgs;
|
||||
const std::wstring EdgeFilename = L"msedge.exe";
|
||||
const std::wstring ChromeFilename = L"chrome.exe";
|
||||
}
|
||||
|
||||
bool IsProcessElevated(DWORD processID)
|
||||
@ -168,17 +40,24 @@ namespace SnapshotUtils
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsEdge(Utils::Apps::AppData appData)
|
||||
{
|
||||
return appData.installPath.ends_with(NonLocalizable::EdgeFilename);
|
||||
}
|
||||
|
||||
bool IsChrome(Utils::Apps::AppData appData)
|
||||
{
|
||||
return appData.installPath.ends_with(NonLocalizable::ChromeFilename);
|
||||
}
|
||||
|
||||
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect)
|
||||
{
|
||||
PwaHelper pwaHelper{};
|
||||
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
|
||||
|
||||
auto installedApps = Utils::Apps::GetAppsList();
|
||||
auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||
|
||||
// for command line args detection
|
||||
// WbemHelper wbemHelper;
|
||||
// wbemHelper.Initialize();
|
||||
|
||||
for (const auto window : windows)
|
||||
{
|
||||
// filter by window rect size
|
||||
@ -249,6 +128,48 @@ namespace SnapshotUtils
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wstring pwaAppId = L"";
|
||||
std::wstring finalName = data.value().name;
|
||||
std::wstring pwaName = L"";
|
||||
if (IsEdge(data.value()))
|
||||
{
|
||||
pwaHelper.InitAumidToAppId();
|
||||
|
||||
std::wstring windowAumid;
|
||||
pwaHelper.GetAppId(window, &windowAumid);
|
||||
Logger::info(L"Found an edge window with aumid {}", windowAumid);
|
||||
|
||||
if (pwaHelper.GetPwaAppId(windowAumid, &pwaAppId))
|
||||
{
|
||||
Logger::info(L"The found edge window is a PWA app with appId {}", pwaAppId);
|
||||
if (pwaHelper.SearchPwaName(pwaAppId, windowAumid ,& pwaName))
|
||||
{
|
||||
Logger::info(L"The found edge window is a PWA app with name {}", finalName);
|
||||
}
|
||||
finalName = pwaName + L" (" + finalName + L")";
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"The found edge window does not contain a PWA app", pwaAppId);
|
||||
}
|
||||
}
|
||||
else if (IsChrome(data.value()))
|
||||
{
|
||||
pwaHelper.InitChromeAppIds();
|
||||
|
||||
std::wstring windowAumid;
|
||||
pwaHelper.GetAppId(window, &windowAumid);
|
||||
Logger::info(L"Found a chrome window with aumid {}", windowAumid);
|
||||
|
||||
if (pwaHelper.SearchPwaAppId(windowAumid, &pwaAppId))
|
||||
{
|
||||
if (pwaHelper.SearchPwaName(pwaAppId, windowAumid, &pwaName))
|
||||
{
|
||||
finalName = pwaName + L" (" + finalName + L")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isMinimized = WindowUtils::IsMinimized(window);
|
||||
unsigned int monitorNumber = getMonitorNumberFromWindowHandle(window);
|
||||
|
||||
@ -263,12 +184,13 @@ namespace SnapshotUtils
|
||||
}
|
||||
|
||||
WorkspacesData::WorkspacesProject::Application app{
|
||||
.name = data.value().name,
|
||||
.name = finalName,
|
||||
.title = title,
|
||||
.path = data.value().installPath,
|
||||
.packageFullName = data.value().packageFullName,
|
||||
.appUserModelId = data.value().appUserModelId,
|
||||
.commandLineArgs = L"", // GetCommandLineArgs(pid, wbemHelper),
|
||||
.pwaAppId = pwaAppId,
|
||||
.commandLineArgs = L"",
|
||||
.isElevated = IsProcessElevated(pid),
|
||||
.canLaunchElevated = data.value().canLaunchElevated,
|
||||
.isMinimized = isMinimized,
|
||||
|
@ -104,7 +104,7 @@
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;wbemuuid.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
@ -130,10 +130,12 @@
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PwaHelper.cpp" />
|
||||
<ClCompile Include="SnapshotUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="PwaHelper.h" />
|
||||
<ClInclude Include="resource.base.h" />
|
||||
<ClInclude Include="SnapshotUtils.h" />
|
||||
</ItemGroup>
|
||||
|
@ -24,6 +24,9 @@
|
||||
<ClInclude Include="resource.base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PwaHelper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
@ -35,6 +38,9 @@
|
||||
<ClCompile Include="SnapshotUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PwaHelper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
Loading…
Reference in New Issue
Block a user