mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 14:59:16 +08:00
[Workspaces] Implement PWA recognition, launch. (#35913)
* [Workspaces] PWA: first steps: implement PWA app searcher, add basic controls to the editor * spell checker * Snapshot tool: adding command line args for edge * PWA: add icon handling, add launch of PWA * Impllement Aumid getters and comparison to connect PWA windows and processes. Update LauncherUI, Launcher * Minor fixes, simplifications * Spell checker * Removing manual PWA selection, spell checker * Fix merge conflict * Trying to convince spell checker, that "PEB" is a correct word. * XAML format fix * Extending snapshot tool by logs for better testablility * spell checker fix * extending logs * extending logs * Removing some logs, modifying search criteria for pwa helper process search * extending PWA detection for the case the directory with the app-id is missing * Fix issue when pwaAppId is null * fix missing pwa-app-id handling in the editor. Removed unused property (code cleaning) and updating json parser in Launcher * Code cleaning: Moving duplicate code to a common project * Fix issue: adding new Guid as app id if it is empty * Code cleanup: moving Pwa related code from snapshotUtils to PwaHelper * Code cleaning * Code cleanup: Move common Application model to Csharp Library * code cleanup * modifying package name * Ading project reference to Common.UI * Code cleaning, fixing references --------- Co-authored-by: donlaci <donlaci@yahoo.com>
This commit is contained in:
parent
f66450f6a8
commit
6b3bbf7e8f
5
.github/actions/spell-check/expect.txt
vendored
5
.github/actions/spell-check/expect.txt
vendored
@ -259,6 +259,7 @@ CRH
|
|||||||
critsec
|
critsec
|
||||||
Crossdevice
|
Crossdevice
|
||||||
CRSEL
|
CRSEL
|
||||||
|
crx
|
||||||
crw
|
crw
|
||||||
CSearch
|
CSearch
|
||||||
CSettings
|
CSettings
|
||||||
@ -639,6 +640,7 @@ HWNDLAST
|
|||||||
HWNDNEXT
|
HWNDNEXT
|
||||||
HWNDPREV
|
HWNDPREV
|
||||||
hyjiacan
|
hyjiacan
|
||||||
|
IApp
|
||||||
IBeam
|
IBeam
|
||||||
ICapture
|
ICapture
|
||||||
IClass
|
IClass
|
||||||
@ -1140,7 +1142,7 @@ pdo
|
|||||||
pdto
|
pdto
|
||||||
pdtobj
|
pdtobj
|
||||||
pdw
|
pdw
|
||||||
Peb
|
peb
|
||||||
pef
|
pef
|
||||||
PElems
|
PElems
|
||||||
Pels
|
Pels
|
||||||
@ -1627,6 +1629,7 @@ tkconverters
|
|||||||
TLayout
|
TLayout
|
||||||
tlb
|
tlb
|
||||||
tlbimp
|
tlbimp
|
||||||
|
tlhelp
|
||||||
TMPVAR
|
TMPVAR
|
||||||
TNP
|
TNP
|
||||||
Toolhelp
|
Toolhelp
|
||||||
|
@ -630,6 +630,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "src\common\Tele
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTests", "src\modules\MouseWithoutBorders\MouseWithoutBorders.UnitTests\MouseWithoutBorders.UnitTests.csproj", "{66614C26-314C-4B91-9071-76133422CFEF}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTests", "src\modules\MouseWithoutBorders\MouseWithoutBorders.UnitTests\MouseWithoutBorders.UnitTests.csproj", "{66614C26-314C-4B91-9071-76133422CFEF}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesCsharpLibrary", "src\modules\Workspaces\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj", "{89D0E199-B17A-418C-B2F8-7375B6708357}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|ARM64 = Debug|ARM64
|
Debug|ARM64 = Debug|ARM64
|
||||||
@ -2246,6 +2248,30 @@ Global
|
|||||||
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x64.Build.0 = Release|x64
|
{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.ActiveCfg = Release|x64
|
||||||
{8A08D663-4995-40E3-B42C-3F910625F284}.Release|x86.Build.0 = 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.ActiveCfg = Debug|ARM64
|
||||||
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.Build.0 = Debug|ARM64
|
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|x64.ActiveCfg = Debug|x64
|
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
@ -2646,30 +2672,6 @@ Global
|
|||||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
|
{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.ActiveCfg = Release|x64
|
||||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = 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.ActiveCfg = Debug|ARM64
|
||||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
@ -2778,6 +2780,18 @@ Global
|
|||||||
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x64.Build.0 = Release|x64
|
{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.ActiveCfg = Release|x64
|
||||||
{66614C26-314C-4B91-9071-76133422CFEF}.Release|x86.Build.0 = 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
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -2956,6 +2970,8 @@ Global
|
|||||||
{B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
{B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
||||||
{A663E672-B26D-4EC0-BEAB-FE2E424AC46F} = {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}
|
{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}
|
{D962A009-834F-4EEC-AABB-430DF8F98E39} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||||
{9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
{9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||||
{FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B}
|
{FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B}
|
||||||
@ -2995,8 +3011,6 @@ Global
|
|||||||
{8ACB33D9-C95B-47D4-8363-9731EE0930A0} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
|
{8ACB33D9-C95B-47D4-8363-9731EE0930A0} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
|
||||||
{CA716AE6-FE5C-40AC-BB8F-2C87912687AC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
{CA716AE6-FE5C-40AC-BB8F-2C87912687AC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||||
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
|
{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}
|
{A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||||
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
{14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
{14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
@ -3009,6 +3023,7 @@ Global
|
|||||||
{37D07516-4185-43A4-924F-3C7A5D95ECF6} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
{8F021B46-362B-485C-BFBA-CCF83E820CBD} = {8F62026A-294B-41C6-8839-87463613F216}
|
{8F021B46-362B-485C-BFBA-CCF83E820CBD} = {8F62026A-294B-41C6-8839-87463613F216}
|
||||||
{66614C26-314C-4B91-9071-76133422CFEF} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
{66614C26-314C-4B91-9071-76133422CFEF} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
|
||||||
|
{89D0E199-B17A-418C-B2F8-7375B6708357} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||||
|
@ -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 AppUserModelId { get; set; }
|
||||||
|
|
||||||
|
public string PwaAppId { get; set; }
|
||||||
|
|
||||||
public string CommandLineArguments { get; set; }
|
public string CommandLineArguments { get; set; }
|
||||||
|
|
||||||
public bool IsElevated { get; set; }
|
public bool IsElevated { get; set; }
|
||||||
|
@ -13,18 +13,17 @@ using System.Linq;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Windows.Management.Deployment;
|
using Windows.Management.Deployment;
|
||||||
|
using WorkspacesCsharpLibrary;
|
||||||
|
using WorkspacesCsharpLibrary.Models;
|
||||||
|
|
||||||
namespace WorkspacesEditor.Models
|
namespace WorkspacesEditor.Models
|
||||||
{
|
{
|
||||||
public class Application : INotifyPropertyChanged, IDisposable
|
public class Application : BaseApplication, IDisposable
|
||||||
{
|
{
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
public Application()
|
public Application()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -37,6 +36,7 @@ namespace WorkspacesEditor.Models
|
|||||||
AppTitle = other.AppTitle;
|
AppTitle = other.AppTitle;
|
||||||
PackageFullName = other.PackageFullName;
|
PackageFullName = other.PackageFullName;
|
||||||
AppUserModelId = other.AppUserModelId;
|
AppUserModelId = other.AppUserModelId;
|
||||||
|
PwaAppId = other.PwaAppId;
|
||||||
CommandLineArguments = other.CommandLineArguments;
|
CommandLineArguments = other.CommandLineArguments;
|
||||||
IsElevated = other.IsElevated;
|
IsElevated = other.IsElevated;
|
||||||
CanLaunchElevated = other.CanLaunchElevated;
|
CanLaunchElevated = other.CanLaunchElevated;
|
||||||
@ -100,8 +100,6 @@ namespace WorkspacesEditor.Models
|
|||||||
|
|
||||||
public string AppName { get; set; }
|
public string AppName { get; set; }
|
||||||
|
|
||||||
public string AppPath { get; set; }
|
|
||||||
|
|
||||||
public string AppTitle { get; set; }
|
public string AppTitle { get; set; }
|
||||||
|
|
||||||
public string PackageFullName { get; set; }
|
public string PackageFullName { get; set; }
|
||||||
@ -187,26 +185,6 @@ namespace WorkspacesEditor.Models
|
|||||||
|
|
||||||
public bool IsAppMainParamVisible { get => !string.IsNullOrWhiteSpace(_appMainParams); }
|
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]
|
[JsonIgnore]
|
||||||
public bool IsHighlighted { get; set; }
|
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;
|
private WindowPosition _position;
|
||||||
|
|
||||||
public WindowPosition Position
|
public WindowPosition Position
|
||||||
@ -367,56 +251,11 @@ namespace WorkspacesEditor.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InitializationFinished()
|
public void InitializationFinished()
|
||||||
{
|
{
|
||||||
_isInitialized = true;
|
_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;
|
private bool _isExpanded;
|
||||||
|
|
||||||
public bool IsExpanded
|
public bool IsExpanded
|
||||||
@ -454,11 +293,6 @@ namespace WorkspacesEditor.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void CommandLineTextChanged(string newCommandLineValue)
|
internal void CommandLineTextChanged(string newCommandLineValue)
|
||||||
{
|
{
|
||||||
CommandLineArguments = newCommandLineValue;
|
CommandLineArguments = newCommandLineValue;
|
||||||
|
@ -251,10 +251,11 @@ namespace WorkspacesEditor.Models
|
|||||||
{
|
{
|
||||||
Models.Application newApp = new Models.Application()
|
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,
|
AppName = app.Application,
|
||||||
AppPath = app.ApplicationPath,
|
AppPath = app.ApplicationPath,
|
||||||
AppTitle = app.Title,
|
AppTitle = app.Title,
|
||||||
|
PwaAppId = string.IsNullOrEmpty(app.PwaAppId) ? string.Empty : app.PwaAppId,
|
||||||
PackageFullName = app.PackageFullName,
|
PackageFullName = app.PackageFullName,
|
||||||
AppUserModelId = app.AppUserModelId,
|
AppUserModelId = app.AppUserModelId,
|
||||||
Parent = this,
|
Parent = this,
|
||||||
|
@ -75,16 +75,16 @@ namespace WorkspacesEditor.Utils
|
|||||||
|
|
||||||
foreach (Application app in appsIncluded)
|
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
|
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))
|
foreach (Application app in project.Applications.Where(x => !x.IsIncluded))
|
||||||
|
@ -103,6 +103,7 @@ namespace WorkspacesEditor.Utils
|
|||||||
Title = app.AppTitle,
|
Title = app.AppTitle,
|
||||||
PackageFullName = app.PackageFullName,
|
PackageFullName = app.PackageFullName,
|
||||||
AppUserModelId = app.AppUserModelId,
|
AppUserModelId = app.AppUserModelId,
|
||||||
|
PwaAppId = app.PwaAppId,
|
||||||
CommandLineArguments = app.CommandLineArguments,
|
CommandLineArguments = app.CommandLineArguments,
|
||||||
IsElevated = app.IsElevated,
|
IsElevated = app.IsElevated,
|
||||||
CanLaunchElevated = app.CanLaunchElevated,
|
CanLaunchElevated = app.CanLaunchElevated,
|
||||||
|
@ -14,10 +14,10 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library;
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
using Microsoft.PowerToys.Telemetry;
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
using WorkspacesCsharpLibrary;
|
||||||
using WorkspacesEditor.Data;
|
using WorkspacesEditor.Data;
|
||||||
using WorkspacesEditor.Models;
|
using WorkspacesEditor.Models;
|
||||||
using WorkspacesEditor.Telemetry;
|
using WorkspacesEditor.Telemetry;
|
||||||
@ -39,6 +39,7 @@ namespace WorkspacesEditor.ViewModels
|
|||||||
private MainWindow _mainWindow;
|
private MainWindow _mainWindow;
|
||||||
private Timer lastUpdatedTimer;
|
private Timer lastUpdatedTimer;
|
||||||
private WorkspacesSettings settings;
|
private WorkspacesSettings settings;
|
||||||
|
private PwaHelper _pwaHelper;
|
||||||
|
|
||||||
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
|
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
|
||||||
|
|
||||||
@ -147,6 +148,7 @@ namespace WorkspacesEditor.ViewModels
|
|||||||
settings = Utils.Settings.ReadSettings();
|
settings = Utils.Settings.ReadSettings();
|
||||||
_orderByIndex = (int)settings.Properties.SortBy;
|
_orderByIndex = (int)settings.Properties.SortBy;
|
||||||
_workspacesEditorIO = workspacesEditorIO;
|
_workspacesEditorIO = workspacesEditorIO;
|
||||||
|
_pwaHelper = new PwaHelper();
|
||||||
lastUpdatedTimer = new System.Timers.Timer();
|
lastUpdatedTimer = new System.Timers.Timer();
|
||||||
lastUpdatedTimer.Interval = 1000;
|
lastUpdatedTimer.Interval = 1000;
|
||||||
lastUpdatedTimer.Elapsed += LastUpdatedTimerElapsed;
|
lastUpdatedTimer.Elapsed += LastUpdatedTimerElapsed;
|
||||||
|
@ -65,7 +65,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ControlzEx" />
|
|
||||||
<PackageReference Include="ModernWpfUI" />
|
<PackageReference Include="ModernWpfUI" />
|
||||||
<PackageReference Include="System.IO.Abstractions" />
|
<PackageReference Include="System.IO.Abstractions" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -77,6 +76,7 @@
|
|||||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||||
|
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Properties\Resources.Designer.cs">
|
<Compile Update="Properties\Resources.Designer.cs">
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
Margin="10"
|
Margin="10"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Source="{Binding IconBitmapImage}" />
|
Source="{Binding IconBitmapImage, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Width="20"
|
Width="20"
|
||||||
|
@ -18,6 +18,15 @@ using namespace Windows::Management::Deployment;
|
|||||||
|
|
||||||
namespace AppLauncher
|
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)
|
Result<SHELLEXECUTEINFO, std::wstring> LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
|
||||||
{
|
{
|
||||||
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
||||||
@ -133,30 +142,50 @@ namespace AppLauncher
|
|||||||
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
|
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)
|
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)
|
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||||
{
|
{
|
||||||
Logger::error(L"File not found at {}", app.path);
|
Logger::error(L"File not found at {}", appPathFinal);
|
||||||
launchErrors.push_back({ std::filesystem::path(app.path).filename(), L"File not found" });
|
launchErrors.push_back({ std::filesystem::path(appPathFinal).filename(), L"File not found" });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto res = LaunchApp(app.path, app.commandLineArgs, app.isElevated);
|
auto res = LaunchApp(appPathFinal, commandLineArgsFinal, app.isElevated);
|
||||||
if (res.isOk())
|
if (res.isOk())
|
||||||
{
|
{
|
||||||
launched = true;
|
launched = true;
|
||||||
}
|
}
|
||||||
else
|
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;
|
return launched;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,6 +16,8 @@ namespace WorkspacesLauncherUI.Data
|
|||||||
|
|
||||||
public string AppUserModelId { get; set; }
|
public string AppUserModelId { get; set; }
|
||||||
|
|
||||||
|
public string PwaAppId { get; set; }
|
||||||
|
|
||||||
public string CommandLineArguments { get; set; }
|
public string CommandLineArguments { get; set; }
|
||||||
|
|
||||||
public bool IsElevated { get; set; }
|
public bool IsElevated { get; set; }
|
||||||
|
@ -3,77 +3,23 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Drawing2D;
|
using System.Drawing.Drawing2D;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Windows.Management.Deployment;
|
using WorkspacesCsharpLibrary.Models;
|
||||||
using WorkspacesLauncherUI.Data;
|
using WorkspacesLauncherUI.Data;
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI.Models
|
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;
|
public bool Loading => LaunchState == LaunchingState.Waiting || LaunchState == LaunchingState.Launched;
|
||||||
|
|
||||||
private Icon _icon;
|
public string Name { get; set; }
|
||||||
|
|
||||||
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 LaunchingState LaunchState { 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)),
|
_ => 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 System.Diagnostics;
|
||||||
|
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
|
using WorkspacesCsharpLibrary;
|
||||||
using WorkspacesLauncherUI.Data;
|
using WorkspacesLauncherUI.Data;
|
||||||
using WorkspacesLauncherUI.Models;
|
using WorkspacesLauncherUI.Models;
|
||||||
|
using WorkspacesLauncherUI.Utils;
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI.ViewModels
|
namespace WorkspacesLauncherUI.ViewModels
|
||||||
{
|
{
|
||||||
@ -20,6 +22,7 @@ namespace WorkspacesLauncherUI.ViewModels
|
|||||||
|
|
||||||
private StatusWindow _snapshotWindow;
|
private StatusWindow _snapshotWindow;
|
||||||
private int launcherProcessID;
|
private int launcherProcessID;
|
||||||
|
private PwaHelper _pwaHelper;
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
@ -30,6 +33,8 @@ namespace WorkspacesLauncherUI.ViewModels
|
|||||||
|
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
|
_pwaHelper = new PwaHelper();
|
||||||
|
|
||||||
// receive IPC Message
|
// receive IPC Message
|
||||||
App.IPCMessageReceivedCallback = (string msg) =>
|
App.IPCMessageReceivedCallback = (string msg) =>
|
||||||
{
|
{
|
||||||
@ -54,7 +59,11 @@ namespace WorkspacesLauncherUI.ViewModels
|
|||||||
{
|
{
|
||||||
appLaunchingList.Add(new AppLaunching()
|
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,
|
LaunchState = app.State,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@
|
|||||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||||
|
<ProjectReference Include="..\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Properties\Resources.Designer.cs">
|
<Compile Update="Properties\Resources.Designer.cs">
|
||||||
|
@ -79,6 +79,7 @@ namespace WorkspacesData
|
|||||||
const static wchar_t* AppPathID = L"application-path";
|
const static wchar_t* AppPathID = L"application-path";
|
||||||
const static wchar_t* AppPackageFullNameID = L"package-full-name";
|
const static wchar_t* AppPackageFullNameID = L"package-full-name";
|
||||||
const static wchar_t* AppUserModelId = L"app-user-model-id";
|
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* AppTitleID = L"title";
|
||||||
const static wchar_t* CommandLineArgsID = L"command-line-arguments";
|
const static wchar_t* CommandLineArgsID = L"command-line-arguments";
|
||||||
const static wchar_t* ElevatedID = L"is-elevated";
|
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::AppTitleID, json::value(data.title));
|
||||||
json.SetNamedValue(NonLocalizable::AppPackageFullNameID, json::value(data.packageFullName));
|
json.SetNamedValue(NonLocalizable::AppPackageFullNameID, json::value(data.packageFullName));
|
||||||
json.SetNamedValue(NonLocalizable::AppUserModelId, json::value(data.appUserModelId));
|
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::CommandLineArgsID, json::value(data.commandLineArgs));
|
||||||
json.SetNamedValue(NonLocalizable::ElevatedID, json::value(data.isElevated));
|
json.SetNamedValue(NonLocalizable::ElevatedID, json::value(data.isElevated));
|
||||||
json.SetNamedValue(NonLocalizable::CanLaunchElevatedID, json::value(data.canLaunchElevated));
|
json.SetNamedValue(NonLocalizable::CanLaunchElevatedID, json::value(data.canLaunchElevated));
|
||||||
@ -136,6 +138,11 @@ namespace WorkspacesData
|
|||||||
result.appUserModelId = json.GetNamedString(NonLocalizable::AppUserModelId);
|
result.appUserModelId = json.GetNamedString(NonLocalizable::AppUserModelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json.HasKey(NonLocalizable::PwaAppId))
|
||||||
|
{
|
||||||
|
result.pwaAppId = json.GetNamedString(NonLocalizable::PwaAppId);
|
||||||
|
}
|
||||||
|
|
||||||
result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID);
|
result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID);
|
||||||
|
|
||||||
if (json.HasKey(NonLocalizable::ElevatedID))
|
if (json.HasKey(NonLocalizable::ElevatedID))
|
||||||
|
@ -31,6 +31,7 @@ namespace WorkspacesData
|
|||||||
std::wstring path;
|
std::wstring path;
|
||||||
std::wstring packageFullName;
|
std::wstring packageFullName;
|
||||||
std::wstring appUserModelId;
|
std::wstring appUserModelId;
|
||||||
|
std::wstring pwaAppId;
|
||||||
std::wstring commandLineArgs;
|
std::wstring commandLineArgs;
|
||||||
bool isElevated{};
|
bool isElevated{};
|
||||||
bool canLaunchElevated{};
|
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 "pch.h"
|
||||||
#include "SnapshotUtils.h"
|
#include "SnapshotUtils.h"
|
||||||
|
|
||||||
#include <comdef.h>
|
|
||||||
#include <Wbemidl.h>
|
|
||||||
|
|
||||||
#include <common/utils/elevation.h>
|
#include <common/utils/elevation.h>
|
||||||
#include <common/utils/process_path.h>
|
#include <common/utils/process_path.h>
|
||||||
#include <common/notifications/NotificationUtil.h>
|
#include <common/notifications/NotificationUtil.h>
|
||||||
@ -12,142 +9,17 @@
|
|||||||
#include <workspaces-common/WindowFilter.h>
|
#include <workspaces-common/WindowFilter.h>
|
||||||
|
|
||||||
#include <WorkspacesLib/AppUtils.h>
|
#include <WorkspacesLib/AppUtils.h>
|
||||||
|
#include <PwaHelper.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "ntdll.lib")
|
||||||
|
|
||||||
namespace SnapshotUtils
|
namespace SnapshotUtils
|
||||||
{
|
{
|
||||||
namespace NonLocalizable
|
namespace NonLocalizable
|
||||||
{
|
{
|
||||||
const std::wstring ApplicationFrameHost = L"ApplicationFrameHost.exe";
|
const std::wstring ApplicationFrameHost = L"ApplicationFrameHost.exe";
|
||||||
}
|
const std::wstring EdgeFilename = L"msedge.exe";
|
||||||
|
const std::wstring ChromeFilename = L"chrome.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsProcessElevated(DWORD processID)
|
bool IsProcessElevated(DWORD processID)
|
||||||
@ -168,17 +40,24 @@ namespace SnapshotUtils
|
|||||||
return false;
|
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)
|
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{};
|
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
|
||||||
|
|
||||||
auto installedApps = Utils::Apps::GetAppsList();
|
auto installedApps = Utils::Apps::GetAppsList();
|
||||||
auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||||
|
|
||||||
// for command line args detection
|
|
||||||
// WbemHelper wbemHelper;
|
|
||||||
// wbemHelper.Initialize();
|
|
||||||
|
|
||||||
for (const auto window : windows)
|
for (const auto window : windows)
|
||||||
{
|
{
|
||||||
// filter by window rect size
|
// filter by window rect size
|
||||||
@ -249,6 +128,48 @@ namespace SnapshotUtils
|
|||||||
continue;
|
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);
|
bool isMinimized = WindowUtils::IsMinimized(window);
|
||||||
unsigned int monitorNumber = getMonitorNumberFromWindowHandle(window);
|
unsigned int monitorNumber = getMonitorNumberFromWindowHandle(window);
|
||||||
|
|
||||||
@ -263,12 +184,13 @@ namespace SnapshotUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
WorkspacesData::WorkspacesProject::Application app{
|
WorkspacesData::WorkspacesProject::Application app{
|
||||||
.name = data.value().name,
|
.name = finalName,
|
||||||
.title = title,
|
.title = title,
|
||||||
.path = data.value().installPath,
|
.path = data.value().installPath,
|
||||||
.packageFullName = data.value().packageFullName,
|
.packageFullName = data.value().packageFullName,
|
||||||
.appUserModelId = data.value().appUserModelId,
|
.appUserModelId = data.value().appUserModelId,
|
||||||
.commandLineArgs = L"", // GetCommandLineArgs(pid, wbemHelper),
|
.pwaAppId = pwaAppId,
|
||||||
|
.commandLineArgs = L"",
|
||||||
.isElevated = IsProcessElevated(pid),
|
.isElevated = IsProcessElevated(pid),
|
||||||
.canLaunchElevated = data.value().canLaunchElevated,
|
.canLaunchElevated = data.value().canLaunchElevated,
|
||||||
.isMinimized = isMinimized,
|
.isMinimized = isMinimized,
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
<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>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||||
@ -130,10 +130,12 @@
|
|||||||
<ClCompile Include="pch.cpp">
|
<ClCompile Include="pch.cpp">
|
||||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="PwaHelper.cpp" />
|
||||||
<ClCompile Include="SnapshotUtils.cpp" />
|
<ClCompile Include="SnapshotUtils.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
|
<ClInclude Include="PwaHelper.h" />
|
||||||
<ClInclude Include="resource.base.h" />
|
<ClInclude Include="resource.base.h" />
|
||||||
<ClInclude Include="SnapshotUtils.h" />
|
<ClInclude Include="SnapshotUtils.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -24,6 +24,9 @@
|
|||||||
<ClInclude Include="resource.base.h">
|
<ClInclude Include="resource.base.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="PwaHelper.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="pch.cpp">
|
<ClCompile Include="pch.cpp">
|
||||||
@ -35,6 +38,9 @@
|
|||||||
<ClCompile Include="SnapshotUtils.cpp">
|
<ClCompile Include="SnapshotUtils.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="PwaHelper.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
Loading…
Reference in New Issue
Block a user