From 6b3bbf7e8f94f47d9881e32a1b8ed6f11920db41 Mon Sep 17 00:00:00 2001 From: Laszlo Nemeth <57342539+donlaci@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:15:18 +0100 Subject: [PATCH] [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 --- .github/actions/spell-check/expect.txt | 5 +- PowerToys.sln | 67 +-- .../Models/BaseApplication.cs | 217 ++++++++++ .../WorkspacesCsharpLibrary/PwaApp.cs | 15 + .../WorkspacesCsharpLibrary/PwaHelper.cs | 90 ++++ .../WorkspacesCsharpLibrary.csproj | 18 + .../WorkspacesEditor/Data/ProjectData.cs | 2 + .../WorkspacesEditor/Models/Application.cs | 174 +------- .../WorkspacesEditor/Models/Project.cs | 3 +- .../WorkspacesEditor/Utils/DrawHelper.cs | 8 +- .../Utils/WorkspacesEditorIO.cs | 1 + .../ViewModels/MainViewModel.cs | 4 +- .../WorkspacesEditor/WorkspacesEditor.csproj | 2 +- .../WorkspacesEditorPage.xaml | 2 +- .../WorkspacesLauncher/AppLauncher.cpp | 43 +- .../Data/ApplicationWrapper.cs | 2 + .../Models/AppLaunching.cs | 183 +------- .../ViewModels/MainViewModel.cs | 11 +- .../WorkspacesLauncherUI.csproj | 1 + .../WorkspacesLib/WorkspacesData.cpp | 15 +- .../Workspaces/WorkspacesLib/WorkspacesData.h | 1 + .../WorkspacesSnapshotTool/PwaHelper.cpp | 409 ++++++++++++++++++ .../WorkspacesSnapshotTool/PwaHelper.h | 20 + .../WorkspacesSnapshotTool/SnapshotUtils.cpp | 202 +++------ .../WorkspacesSnapshotTool.vcxproj | 4 +- .../WorkspacesSnapshotTool.vcxproj.filters | 6 + 26 files changed, 967 insertions(+), 538 deletions(-) create mode 100644 src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs create mode 100644 src/modules/Workspaces/WorkspacesCsharpLibrary/PwaApp.cs create mode 100644 src/modules/Workspaces/WorkspacesCsharpLibrary/PwaHelper.cs create mode 100644 src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj create mode 100644 src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.cpp create mode 100644 src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.h diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index a6d3b032db..2271d20e11 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -259,6 +259,7 @@ CRH critsec Crossdevice CRSEL +crx crw CSearch CSettings @@ -639,6 +640,7 @@ HWNDLAST HWNDNEXT HWNDPREV hyjiacan +IApp IBeam ICapture IClass @@ -1140,7 +1142,7 @@ pdo pdto pdtobj pdw -Peb +peb pef PElems Pels @@ -1627,6 +1629,7 @@ tkconverters TLayout tlb tlbimp +tlhelp TMPVAR TNP Toolhelp diff --git a/PowerToys.sln b/PowerToys.sln index 80722be402..053bfd0c73 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|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 @@ -2646,30 +2672,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 @@ -2778,6 +2780,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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2956,6 +2970,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} @@ -2995,8 +3011,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} @@ -3009,6 +3023,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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs b/src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs new file mode 100644 index 0000000000..81437fb845 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs @@ -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(@"(?[^_]*)_\d+.\d+.\d+.\d+_x64__(?[^\\]*)", 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); + } + } +} diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/PwaApp.cs b/src/modules/Workspaces/WorkspacesCsharpLibrary/PwaApp.cs new file mode 100644 index 0000000000..237191b3c3 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/PwaApp.cs @@ -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; } + } +} diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/PwaHelper.cs b/src/modules/Workspaces/WorkspacesCsharpLibrary/PwaHelper.cs new file mode 100644 index 0000000000..4b7229bd59 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/PwaHelper.cs @@ -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 pwaApps = new List(); + + 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; + } + } +} diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj b/src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj new file mode 100644 index 0000000000..eea9001b12 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj @@ -0,0 +1,18 @@ + + + + + + PowerToys.WorkspacesCsharpLibrary + PowerToys Workspaces Csharp Library + PowerToys Workspaces Csharp Library + true + true + false + false + true + ..\..\..\..\$(Platform)\$(Configuration) + PowerToys.WorkspacesCsharpLibrary + + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs b/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs index 7eb0a63831..4a0897fef8 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs @@ -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; } diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs index 02681dd841..3d30c38cab 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs @@ -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(@"(?[^_]*)_\d+.\d+.\d+.\d+_x64__(?[^\\]*)", 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; diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs index e65853f1e3..19b867f388 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs @@ -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, diff --git a/src/modules/Workspaces/WorkspacesEditor/Utils/DrawHelper.cs b/src/modules/Workspaces/WorkspacesEditor/Utils/DrawHelper.cs index 45da7a6c2c..da215a5f0d 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Utils/DrawHelper.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Utils/DrawHelper.cs @@ -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)) diff --git a/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs b/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs index a3a4bc418d..7ad416cf17 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs @@ -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, diff --git a/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs b/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs index aae85161cb..ccee84c9c5 100644 --- a/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs +++ b/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs @@ -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 Workspaces { get; set; } = new ObservableCollection(); @@ -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; diff --git a/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj b/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj index aa359291eb..3f7d153e56 100644 --- a/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj +++ b/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj @@ -65,7 +65,6 @@ - @@ -77,6 +76,7 @@ + diff --git a/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditorPage.xaml b/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditorPage.xaml index bb8c373f50..e176ff432b 100644 --- a/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditorPage.xaml +++ b/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditorPage.xaml @@ -80,7 +80,7 @@ Margin="10" HorizontalAlignment="Center" VerticalAlignment="Center" - Source="{Binding IconBitmapImage}" /> + Source="{Binding IconBitmapImage, UpdateSourceTrigger=PropertyChanged}" /> 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(@"(?[^_]*)_\d+.\d+.\d+.\d+_x64__(?[^\\]*)", 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); - } } } diff --git a/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs b/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs index 103b08d6f7..aa029d7ea2 100644 --- a/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs +++ b/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs @@ -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, }); } diff --git a/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj b/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj index a915e0f0a3..839c08f90d 100644 --- a/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj +++ b/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj @@ -79,6 +79,7 @@ + diff --git a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp index 8a15001eac..c5f13d74c9 100644 --- a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp +++ b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp @@ -23,7 +23,7 @@ namespace WorkspacesData std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey); return settingsFolderPath + L"\\temp-workspaces.json"; } - + RECT WorkspacesProject::Application::Position::toRect() const noexcept { return RECT{ .left = x, .top = y, .right = x + width, .bottom = y + height }; @@ -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)) @@ -330,11 +337,11 @@ namespace WorkspacesData { result.isShortcutNeeded = json.GetNamedBoolean(NonLocalizable::IsShortcutNeededID); } - + if (json.HasKey(NonLocalizable::MoveExistingWindowsID)) { - result.moveExistingWindows = json.GetNamedBoolean(NonLocalizable::MoveExistingWindowsID); - } + result.moveExistingWindows = json.GetNamedBoolean(NonLocalizable::MoveExistingWindowsID); + } auto appsArray = json.GetNamedArray(NonLocalizable::AppsID); for (uint32_t i = 0; i < appsArray.Size(); ++i) diff --git a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h index d728337996..272cf65d5a 100644 --- a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h +++ b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h @@ -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{}; diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.cpp b/src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.cpp new file mode 100644 index 0000000000..38b620e165 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.cpp @@ -0,0 +1,409 @@ +#include "pch.h" +#include "PwaHelper.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 appResolver; + hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast(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 appResolver; + hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast(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 appResolver; + hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast(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 appResolver; + hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast(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 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 FindPwaHelperProcessIds() + { + std::vector 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::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::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::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; + } +} \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.h b/src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.h new file mode 100644 index 0000000000..8f66351a41 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/PwaHelper.h @@ -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 pwaAumidToAppId; + std::vector chromeAppIds; + std::map pwaAppIdsToAppNames; + }; +} diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp index 519f2ccfa8..d52adc2e80 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp @@ -1,9 +1,6 @@ #include "pch.h" #include "SnapshotUtils.h" -#include -#include - #include #include #include @@ -12,142 +9,17 @@ #include #include +#include + +#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(&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,16 +40,23 @@ 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 GetApps(const std::function getMonitorNumberFromWindowHandle, const std::function getMonitorRect) { + PwaHelper pwaHelper{}; std::vector 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) { @@ -232,7 +111,7 @@ namespace SnapshotUtils if (pid != otherPid && title == WindowUtils::GetWindowTitle(otherWindow)) { processPath = get_process_path(otherPid); - break; + break; } } } @@ -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, diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj index b817abd043..3345e3dc56 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj @@ -104,7 +104,7 @@ Windows true - shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;wbemuuid.lib + shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib @@ -130,10 +130,12 @@ Create + + diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj.filters b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj.filters index 2402f15f61..808e311f90 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj.filters +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj.filters @@ -24,6 +24,9 @@ Header Files + + Header Files + @@ -35,6 +38,9 @@ Source Files + + Source Files +