diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index d861ded8a1..0f526be938 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -57,6 +57,7 @@ appdata APPEXECLINK Appium Applicationcan +APPLICATIONFRAMEHOST appmanifest APPNAME appref diff --git a/PowerToys.sln b/PowerToys.sln index ed6a86dace..a67288a272 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -605,6 +605,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "projects-common", "projects EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectsModuleInterface", "src\modules\Projects\ProjectsModuleInterface\ProjectsModuleInterface.vcxproj", "{45285DF2-9742-4ECA-9AC9-58951FC26489}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectsLib", "src\modules\Projects\ProjectsLib\ProjectsLib.vcxproj", "{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -3423,6 +3425,22 @@ Global {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.Build.0 = Release|x64 {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.ActiveCfg = Release|x64 {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.Build.0 = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|Any CPU.ActiveCfg = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|Any CPU.Build.0 = Debug|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 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.Build.0 = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.ActiveCfg = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.Build.0 = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|Any CPU.ActiveCfg = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|Any CPU.Build.0 = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.ActiveCfg = Release|ARM64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.Build.0 = Release|ARM64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.ActiveCfg = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.Build.0 = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.ActiveCfg = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3645,6 +3663,7 @@ Global {3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {45285DF2-9742-4ECA-9AC9-58951FC26489} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/modules/Projects/ProjectsEditor/Data/ProjectData.cs b/src/modules/Projects/ProjectsEditor/Data/ProjectData.cs new file mode 100644 index 0000000000..e1c52ffdd0 --- /dev/null +++ b/src/modules/Projects/ProjectsEditor/Data/ProjectData.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Projects.Data; +using static ProjectsEditor.Data.ProjectData; + +namespace ProjectsEditor.Data +{ + public class ProjectData : ProjectsEditorData + { + public struct ApplicationWrapper + { + public struct WindowPositionWrapper + { + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + } + + public string Application { get; set; } + + public string ApplicationPath { get; set; } + + public string Title { get; set; } + + public string PackageFullName { get; set; } + + public string CommandLineArguments { get; set; } + + public bool Minimized { get; set; } + + public bool Maximized { get; set; } + + public WindowPositionWrapper Position { get; set; } + + public int Monitor { get; set; } + } + + public struct MonitorConfigurationWrapper + { + public struct MonitorRectWrapper + { + public int Top { get; set; } + + public int Left { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + } + + public string Id { get; set; } + + public string InstanceId { get; set; } + + public int MonitorNumber { get; set; } + + public int Dpi { get; set; } + + public MonitorRectWrapper MonitorRectDpiAware { get; set; } + + public MonitorRectWrapper MonitorRectDpiUnaware { get; set; } + } + + public struct ProjectWrapper + { + public string Id { get; set; } + + public string Name { get; set; } + + public long CreationTime { get; set; } + + public long LastLaunchedTime { get; set; } + + public bool IsShortcutNeeded { get; set; } + + public List MonitorConfiguration { get; set; } + + public List Applications { get; set; } + } + } +} diff --git a/src/modules/Projects/ProjectsEditor/Data/ProjectsData.cs b/src/modules/Projects/ProjectsEditor/Data/ProjectsData.cs index e5937ca45f..603c9c41e7 100644 --- a/src/modules/Projects/ProjectsEditor/Data/ProjectsData.cs +++ b/src/modules/Projects/ProjectsEditor/Data/ProjectsData.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Projects.Data; using ProjectsEditor.Utils; +using static ProjectsEditor.Data.ProjectData; using static ProjectsEditor.Data.ProjectsData; namespace ProjectsEditor.Data @@ -19,81 +20,6 @@ namespace ProjectsEditor.Data } } - public struct ApplicationWrapper - { - public struct WindowPositionWrapper - { - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - } - - public string Application { get; set; } - - public string ApplicationPath { get; set; } - - public string Title { get; set; } - - public string PackageFullName { get; set; } - - public string CommandLineArguments { get; set; } - - public bool Minimized { get; set; } - - public bool Maximized { get; set; } - - public WindowPositionWrapper Position { get; set; } - - public int Monitor { get; set; } - } - - public struct MonitorConfigurationWrapper - { - public struct MonitorRectWrapper - { - public int Top { get; set; } - - public int Left { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - } - - public string Id { get; set; } - - public string InstanceId { get; set; } - - public int MonitorNumber { get; set; } - - public int Dpi { get; set; } - - public MonitorRectWrapper MonitorRectDpiAware { get; set; } - - public MonitorRectWrapper MonitorRectDpiUnaware { get; set; } - } - - public struct ProjectWrapper - { - public string Id { get; set; } - - public string Name { get; set; } - - public long CreationTime { get; set; } - - public long LastLaunchedTime { get; set; } - - public bool IsShortcutNeeded { get; set; } - - public List MonitorConfiguration { get; set; } - - public List Applications { get; set; } - } - public struct ProjectsListWrapper { public List Projects { get; set; } diff --git a/src/modules/Projects/ProjectsEditor/Data/TempProjectData.cs b/src/modules/Projects/ProjectsEditor/Data/TempProjectData.cs new file mode 100644 index 0000000000..d39c911c0e --- /dev/null +++ b/src/modules/Projects/ProjectsEditor/Data/TempProjectData.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using ProjectsEditor.Utils; + +namespace ProjectsEditor.Data +{ + public class TempProjectData : ProjectData + { + public string File + { + get + { + return FolderUtils.DataFolder() + "\\temp-project.json"; + } + } + + public void DeleteTempFile() + { + if (System.IO.File.Exists(File)) + { + System.IO.File.Delete(File); + } + } + } +} diff --git a/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml.cs b/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml.cs index 63d6bdece7..ba9ddfbc1e 100644 --- a/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml.cs +++ b/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using ProjectsEditor.Data; using ProjectsEditor.Models; using ProjectsEditor.ViewModels; @@ -27,13 +28,24 @@ namespace ProjectsEditor private void SaveButtonClicked(object sender, RoutedEventArgs e) { Project projectToSave = this.DataContext as Project; - _mainViewModel.SaveProject(projectToSave); + if (projectToSave.EditorWindowTitle == Properties.Resources.CreateProject) + { + _mainViewModel.AddNewProject(projectToSave); + } + else + { + _mainViewModel.SaveProject(projectToSave); + } + _mainViewModel.SwitchToMainView(); } private void CancelButtonClicked(object sender, RoutedEventArgs e) { - _mainViewModel.CancelLastEdit(); + // delete the temp file created by the snapshot tool + TempProjectData parser = new TempProjectData(); + parser.DeleteTempFile(); + _mainViewModel.SwitchToMainView(); } diff --git a/src/modules/Projects/ProjectsEditor/Properties/Resources.Designer.cs b/src/modules/Projects/ProjectsEditor/Properties/Resources.Designer.cs index a0f5beb491..d08c096523 100644 --- a/src/modules/Projects/ProjectsEditor/Properties/Resources.Designer.cs +++ b/src/modules/Projects/ProjectsEditor/Properties/Resources.Designer.cs @@ -150,6 +150,15 @@ namespace ProjectsEditor.Properties { } } + /// + /// Looks up a localized string similar to Project. + /// + public static string DefaultProjectNamePrefix { + get { + return ResourceManager.GetString("DefaultProjectNamePrefix", resourceCulture); + } + } + /// /// Looks up a localized string similar to Remove. /// diff --git a/src/modules/Projects/ProjectsEditor/Properties/Resources.resx b/src/modules/Projects/ProjectsEditor/Properties/Resources.resx index 4404e223e3..60d86314ff 100644 --- a/src/modules/Projects/ProjectsEditor/Properties/Resources.resx +++ b/src/modules/Projects/ProjectsEditor/Properties/Resources.resx @@ -147,6 +147,9 @@ days ago + + Project + Remove diff --git a/src/modules/Projects/ProjectsEditor/SnapshotWindow.xaml.cs b/src/modules/Projects/ProjectsEditor/SnapshotWindow.xaml.cs index 704e092185..44367d9bd0 100644 --- a/src/modules/Projects/ProjectsEditor/SnapshotWindow.xaml.cs +++ b/src/modules/Projects/ProjectsEditor/SnapshotWindow.xaml.cs @@ -2,19 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; using ProjectsEditor.ViewModels; namespace ProjectsEditor @@ -41,7 +29,7 @@ namespace ProjectsEditor private void SnapshotButtonClicked(object sender, RoutedEventArgs e) { Close(); - _mainViewModel.AddNewProject(); + _mainViewModel.SnapNewProject(); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) diff --git a/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs b/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs index 9f594e09ee..68f6d41c1e 100644 --- a/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs +++ b/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs @@ -46,24 +46,20 @@ namespace ProjectsEditor.Utils } } - public ParsingResult ParseProject(string fileName, out Project project) + public ParsingResult ParseTempProject(out Project project) { project = null; try { - ProjectsData parser = new ProjectsData(); - if (!File.Exists(fileName)) + TempProjectData parser = new TempProjectData(); + if (!File.Exists(parser.File)) { Logger.LogWarning($"ParseProject method. Projects storage file not found: {parser.File}"); - return new ParsingResult(true); + return new ParsingResult(false); } - ProjectsData.ProjectsListWrapper projects = parser.Read(fileName); - if (!ExtractProject(projects, out project)) - { - Logger.LogWarning($"ParseProject method. Projects storage file content could not be set. Reason: {Properties.Resources.Error_Parsing_Message}"); - return new ParsingResult(false, ProjectsEditor.Properties.Resources.Error_Parsing_Message); - } + project = GetProjectFromWrapper(parser.Read(parser.File)); + parser.DeleteTempFile(); return new ParsingResult(true); } @@ -74,25 +70,7 @@ namespace ProjectsEditor.Utils } } - private bool ExtractProject(ProjectsData.ProjectsListWrapper projects, out Project project) - { - project = null; - if (projects.Projects == null) - { - return false; - } - - if (projects.Projects.Count != 1) - { - return false; - } - - ProjectsData.ProjectWrapper projectWrapper = projects.Projects[0]; - project = GetProjectFromWrapper(projectWrapper); - return true; - } - - private Project GetProjectFromWrapper(ProjectsData.ProjectWrapper project) + private Project GetProjectFromWrapper(ProjectData.ProjectWrapper project) { Project newProject = new Project() { @@ -143,24 +121,24 @@ namespace ProjectsEditor.Utils { ProjectsData serializer = new ProjectsData(); ProjectsData.ProjectsListWrapper projectsWrapper = new ProjectsData.ProjectsListWrapper { }; - projectsWrapper.Projects = new List(); + projectsWrapper.Projects = new List(); foreach (Project project in projects) { - ProjectsData.ProjectWrapper wrapper = new ProjectsData.ProjectWrapper + ProjectData.ProjectWrapper wrapper = new ProjectData.ProjectWrapper { Id = project.Id, Name = project.Name, CreationTime = project.CreationTime, IsShortcutNeeded = project.IsShortcutNeeded, LastLaunchedTime = project.LastLaunchedTime, - Applications = new List { }, - MonitorConfiguration = new List { }, + Applications = new List { }, + MonitorConfiguration = new List { }, }; foreach (var app in project.Applications) { - wrapper.Applications.Add(new ProjectsData.ApplicationWrapper + wrapper.Applications.Add(new ProjectData.ApplicationWrapper { Application = app.AppName, ApplicationPath = app.AppPath, @@ -169,7 +147,7 @@ namespace ProjectsEditor.Utils CommandLineArguments = app.CommandLineArguments, Maximized = app.Maximized, Minimized = app.Minimized, - Position = new ProjectsData.ApplicationWrapper.WindowPositionWrapper + Position = new ProjectData.ApplicationWrapper.WindowPositionWrapper { X = app.Position.X, Y = app.Position.Y, @@ -182,20 +160,20 @@ namespace ProjectsEditor.Utils foreach (var monitor in project.Monitors) { - wrapper.MonitorConfiguration.Add(new ProjectsData.MonitorConfigurationWrapper + wrapper.MonitorConfiguration.Add(new ProjectData.MonitorConfigurationWrapper { Id = monitor.MonitorName, InstanceId = monitor.MonitorInstanceId, MonitorNumber = monitor.MonitorNumber, Dpi = monitor.Dpi, - MonitorRectDpiAware = new ProjectsData.MonitorConfigurationWrapper.MonitorRectWrapper + MonitorRectDpiAware = new ProjectData.MonitorConfigurationWrapper.MonitorRectWrapper { Left = (int)monitor.MonitorDpiAwareBounds.Left, Top = (int)monitor.MonitorDpiAwareBounds.Top, Width = (int)monitor.MonitorDpiAwareBounds.Width, Height = (int)monitor.MonitorDpiAwareBounds.Height, }, - MonitorRectDpiUnaware = new ProjectsData.MonitorConfigurationWrapper.MonitorRectWrapper + MonitorRectDpiUnaware = new ProjectData.MonitorConfigurationWrapper.MonitorRectWrapper { Left = (int)monitor.MonitorDpiUnawareBounds.Left, Top = (int)monitor.MonitorDpiUnawareBounds.Top, diff --git a/src/modules/Projects/ProjectsEditor/ViewModels/MainViewModel.cs b/src/modules/Projects/ProjectsEditor/ViewModels/MainViewModel.cs index 01862a4beb..ab50c911b3 100644 --- a/src/modules/Projects/ProjectsEditor/ViewModels/MainViewModel.cs +++ b/src/modules/Projects/ProjectsEditor/ViewModels/MainViewModel.cs @@ -8,9 +8,9 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Drawing; +using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Timers; using ManagedCommon; @@ -127,7 +127,6 @@ namespace ProjectsEditor.ViewModels } private Project editedProject; - private bool isEditedProjectNewlyCreated; private string projectNameBeingEdited; private MainWindow _mainWindow; private System.Timers.Timer lastUpdatedTimer; @@ -218,45 +217,62 @@ namespace ProjectsEditor.ViewModels project.Name = projectNameBeingEdited; } - public async void AddNewProject() + public async void SnapNewProject() { CancelSnapshot(); - await Task.Run(() => RunSnapshotTool()); - if (_projectsEditorIO.ParseProjects(this).Result == true && Projects.Count != 0) - { - int repeatCounter = 1; - string newName = Projects.Count != 0 ? Projects.Last().Name : "Project 1"; // TODO: localizable project name - while (Projects.Where(x => x.Name.Equals(Projects.Last().Name, StringComparison.Ordinal)).Count() > 1) - { - Projects.Last().Name = $"{newName} ({repeatCounter})"; - repeatCounter++; - } - _projectsEditorIO.SerializeProjects(Projects.ToList()); - EditProject(Projects.Last(), true); + await Task.Run(() => RunSnapshotTool()); + + Project project = new Project(); + if (_projectsEditorIO.ParseTempProject(out project).Result) + { + EditProject(project, true); } } public void EditProject(Project selectedProject, bool isNewlyCreated = false) { - isEditedProjectNewlyCreated = isNewlyCreated; var editPage = new ProjectEditor(this); SetEditedProject(selectedProject); - Project projectEdited = new Project(selectedProject) { EditorWindowTitle = isNewlyCreated ? Properties.Resources.CreateProject : Properties.Resources.EditProject }; - projectEdited.Initialize(); - editPage.DataContext = projectEdited; + if (isNewlyCreated) + { + // generate a default name for the new project + string defaultNamePrefix = Properties.Resources.DefaultProjectNamePrefix; + int nextProjectIndex = 0; + foreach (var proj in Projects) + { + if (proj.Name.StartsWith(defaultNamePrefix, StringComparison.CurrentCulture)) + { + try + { + int index = int.Parse(proj.Name[(defaultNamePrefix.Length + 1)..], CultureInfo.CurrentCulture); + if (nextProjectIndex < index) + { + nextProjectIndex = index; + } + } + catch (Exception) + { + } + } + } + + selectedProject.Name = defaultNamePrefix + " " + (nextProjectIndex + 1).ToString(CultureInfo.CurrentCulture); + } + + selectedProject.EditorWindowTitle = isNewlyCreated ? Properties.Resources.CreateProject : Properties.Resources.EditProject; + selectedProject.Initialize(); + + editPage.DataContext = selectedProject; _mainWindow.ShowPage(editPage); lastUpdatedTimer.Stop(); } - public void CancelLastEdit() + public void AddNewProject(Project project) { - if (isEditedProjectNewlyCreated) - { - Projects.Remove(editedProject); - _projectsEditorIO.SerializeProjects(Projects.ToList()); - } + Projects.Add(project); + _projectsEditorIO.SerializeProjects(Projects.ToList()); } public void DeleteProject(Project selectedProject) diff --git a/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp b/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp index be36d69641..27a08cc862 100644 --- a/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp +++ b/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include diff --git a/src/modules/Projects/ProjectsLauncher/ProjectLauncherResource.rc b/src/modules/Projects/ProjectsLauncher/ProjectLauncherResource.base.rc similarity index 100% rename from src/modules/Projects/ProjectsLauncher/ProjectLauncherResource.rc rename to src/modules/Projects/ProjectsLauncher/ProjectLauncherResource.base.rc diff --git a/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj b/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj index f096291e15..958c05b7c2 100644 --- a/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj +++ b/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj @@ -3,6 +3,9 @@ + + + @@ -143,15 +146,19 @@ {caba8dfb-823b-4bf2-93ac-3f31984150d9} - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - {6955446d-23f7-4023-9bb3-8657f904af99} + + {b31fcc55-b5a4-4ea7-b414-2dceae6af332} + - + + + + + diff --git a/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj.filters b/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj.filters index c58cbe303a..507dfa3964 100644 --- a/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj.filters +++ b/src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj.filters @@ -46,8 +46,13 @@ - + Resource Files + + + Resource Files + + \ No newline at end of file diff --git a/src/modules/Projects/ProjectsLauncher/Resource.resx b/src/modules/Projects/ProjectsLauncher/Resource.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/src/modules/Projects/ProjectsLauncher/Resource.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/modules/Projects/ProjectsLauncher/resource.h b/src/modules/Projects/ProjectsLauncher/resource.base.h similarity index 100% rename from src/modules/Projects/ProjectsLauncher/resource.h rename to src/modules/Projects/ProjectsLauncher/resource.base.h diff --git a/src/modules/Projects/ProjectsLib/ProjectsLib.vcxproj b/src/modules/Projects/ProjectsLib/ProjectsLib.vcxproj new file mode 100644 index 0000000000..41e95bf188 --- /dev/null +++ b/src/modules/Projects/ProjectsLib/ProjectsLib.vcxproj @@ -0,0 +1,49 @@ + + + + 16.0 + {b31fcc55-b5a4-4ea7-b414-2dceae6af332} + Win32Proj + ProjectsLib + ProjectsLib + + + + StaticLibrary + v143 + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + + _LIB;%(PreprocessorDefinitions) + ..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + + + + + + + + Create + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + + + + \ No newline at end of file diff --git a/src/modules/Projects/ProjectsLib/ProjectsLib.vcxproj.filters b/src/modules/Projects/ProjectsLib/ProjectsLib.vcxproj.filters new file mode 100644 index 0000000000..edb4a24874 --- /dev/null +++ b/src/modules/Projects/ProjectsLib/ProjectsLib.vcxproj.filters @@ -0,0 +1,23 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/src/modules/Projects/ProjectsLib/pch.cpp b/src/modules/Projects/ProjectsLib/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/Projects/ProjectsLib/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/Projects/ProjectsLib/pch.h b/src/modules/Projects/ProjectsLib/pch.h new file mode 100644 index 0000000000..dba977e716 --- /dev/null +++ b/src/modules/Projects/ProjectsLib/pch.h @@ -0,0 +1,10 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +#endif //PCH_H diff --git a/src/modules/Projects/ProjectsSnapshotTool/JsonUtils.h b/src/modules/Projects/ProjectsSnapshotTool/JsonUtils.h new file mode 100644 index 0000000000..ce83c0f8ad --- /dev/null +++ b/src/modules/Projects/ProjectsSnapshotTool/JsonUtils.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include + +#include + +namespace ProjectsJsonUtils +{ + inline std::vector Read(const std::wstring& fileName) + { + std::vector projects{}; + try + { + auto savedProjectsJson = json::from_file(fileName); + if (savedProjectsJson.has_value()) + { + auto savedProjects = JsonUtils::ProjectsListJSON::FromJson(savedProjectsJson.value()); + if (savedProjects.has_value()) + { + projects = savedProjects.value(); + } + } + } + catch (std::exception ex) + { + Logger::error("Error reading projects file. {}", ex.what()); + } + + return projects; + } + + inline void Write(const std::wstring& fileName, const std::vector& projects) + { + try + { + json::to_file(fileName, JsonUtils::ProjectsListJSON::ToJson(projects)); + } + catch (std::exception ex) + { + Logger::error("Error writing projects file. {}", ex.what()); + } + } + + inline void Write(const std::wstring& fileName, const Project& project) + { + try + { + json::to_file(fileName, JsonUtils::ProjectJSON::ToJson(project)); + } + catch (std::exception ex) + { + Logger::error("Error writing projects file. {}", ex.what()); + } + } +} \ No newline at end of file diff --git a/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj b/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj index 6917ff39fc..6bfa72ecf9 100644 --- a/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj +++ b/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj @@ -3,6 +3,9 @@ + + + @@ -127,10 +130,14 @@ Create + + + - + + @@ -139,15 +146,19 @@ {caba8dfb-823b-4bf2-93ac-3f31984150d9} - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - {6955446d-23f7-4023-9bb3-8657f904af99} + + {b31fcc55-b5a4-4ea7-b414-2dceae6af332} + - + + + + + diff --git a/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj.filters b/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj.filters index bb9fecde08..e2fe55ad94 100644 --- a/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj.filters +++ b/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.vcxproj.filters @@ -18,7 +18,16 @@ Header Files - + + Header Files + + + Header Files + + + Header Files + + Header Files @@ -29,12 +38,23 @@ Source Files + + Source Files + + + Resource Files + - + + Resource Files + + + + Resource Files diff --git a/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.rc b/src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotToolResources.base.rc similarity index 100% rename from src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotTool.rc rename to src/modules/Projects/ProjectsSnapshotTool/ProjectsSnapshotToolResources.base.rc diff --git a/src/modules/Projects/ProjectsSnapshotTool/Resource.resx b/src/modules/Projects/ProjectsSnapshotTool/Resource.resx new file mode 100644 index 0000000000..4c8a6b3d8b --- /dev/null +++ b/src/modules/Projects/ProjectsSnapshotTool/Resource.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Project + + \ No newline at end of file diff --git a/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.cpp b/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.cpp new file mode 100644 index 0000000000..3775920823 --- /dev/null +++ b/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.cpp @@ -0,0 +1,73 @@ +#include "pch.h" +#include "SnapshotUtils.h" + +#include + +#include +#include +#include + +#include +#include + +namespace SnapshotUtils +{ + std::vector GetApps(const std::function getMonitorNumberFromWindowHandle) + { + std::vector apps{}; + + auto installedApps = Utils::Apps::GetAppsList(); + auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter); + + for (const auto window : windows) + { + // filter by window rect size + RECT rect = WindowUtils::GetWindowRect(window); + if (rect.right - rect.left <= 0 || rect.bottom - rect.top <= 0) + { + continue; + } + + // filter by window title + std::wstring title = WindowUtils::GetWindowTitle(window); + if (title.empty()) + { + continue; + } + + // filter by app path + std::wstring processPath = get_process_path_waiting_uwp(window); + if (processPath.empty() || WindowUtils::IsExcludedByDefault(window, processPath, title)) + { + continue; + } + + auto data = Utils::Apps::GetApp(processPath, installedApps); + if (!data.has_value() || data->name.empty()) + { + continue; + } + + Project::Application app{ + .name = data.value().name, + .title = title, + .path = processPath, + .packageFullName = data.value().packageFullName, + .commandLineArgs = L"", + .isMinimized = WindowUtils::IsMinimized(window), + .isMaximized = WindowUtils::IsMaximized(window), + .position = Project::Application::Position{ + .x = rect.left, + .y = rect.top, + .width = rect.right - rect.left, + .height = rect.bottom - rect.top, + }, + .monitor = getMonitorNumberFromWindowHandle(window), + }; + + apps.push_back(app); + } + + return apps; + } +} \ No newline at end of file diff --git a/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.h b/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.h new file mode 100644 index 0000000000..4274ecdf66 --- /dev/null +++ b/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace SnapshotUtils +{ + std::vector GetApps(const std::function getMonitorNumberFromWindowHandle); +}; diff --git a/src/modules/Projects/ProjectsSnapshotTool/main.cpp b/src/modules/Projects/ProjectsSnapshotTool/main.cpp index 624177d656..b2bb309c57 100644 --- a/src/modules/Projects/ProjectsSnapshotTool/main.cpp +++ b/src/modules/Projects/ProjectsSnapshotTool/main.cpp @@ -1,14 +1,13 @@ #include "pch.h" #include -#include -#include #include #include #include -#include -#include + +#include +#include #include #include @@ -45,88 +44,13 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cm fileName = fileNameParam; } - // read previously saved projects - std::vector projects; - try - { - auto savedProjectsJson = json::from_file(fileName); - if (savedProjectsJson.has_value()) - { - auto savedProjects = JsonUtils::ProjectsListJSON::FromJson(savedProjectsJson.value()); - if (savedProjects.has_value()) - { - projects = savedProjects.value(); - } - } - } - catch (std::exception ex) - { - Logger::error("Error reading projects file. {}", ex.what()); - } - - // new project name - std::wstring defaultNamePrefix = L"Project"; // TODO: localizable - int nextProjectIndex = 0; - for (const auto& proj : projects) - { - const std::wstring& name = proj.name; - if (name.starts_with(defaultNamePrefix)) - { - try - { - int index = std::stoi(name.substr(defaultNamePrefix.length() + 1)); - if (nextProjectIndex < index) - { - nextProjectIndex = index; - } - } - catch (std::exception) {} - } - } - - std::wstring projectName = defaultNamePrefix + L" " + std::to_wstring(nextProjectIndex + 1); + // create new project time_t creationTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - Project project{ .id = CreateGuidString(), .name = projectName, .creationTime = creationTime }; + Project project{ .id = CreateGuidString(), .creationTime = creationTime }; Logger::trace(L"Creating project {}:{}", project.name, project.id); - // save monitor configuration project.monitors = MonitorUtils::IdentifyMonitors(); - - // get list of windows - auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter); - - // get installed apps list - auto apps = Utils::Apps::GetAppsList(); - - for (const auto& window : windows) - { - // filter by window rect size - RECT rect = WindowUtils::GetWindowRect(window); - if (rect.right - rect.left <= 0 || rect.bottom - rect.top <= 0) - { - continue; - } - - // filter by window title - std::wstring title = WindowUtils::GetWindowTitle(window); - if (title.empty()) - { - continue; - } - - // filter by app path - std::wstring processPath = get_process_path_waiting_uwp(window); - if (processPath.empty() || WindowUtils::IsExcludedByDefault(window, processPath, title)) - { - continue; - } - - auto data = Utils::Apps::GetApp(processPath, apps); - if (!data.has_value()) - { - continue; - } - + project.apps = SnapshotUtils::GetApps([&](HWND window) -> unsigned int { auto windowMonitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY); unsigned int monitorNumber = 0; for (const auto& monitor : project.monitors) @@ -138,28 +62,11 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cm } } - Project::Application app { - .name = data.value().name, - .title = title, - .path = processPath, - .packageFullName = data.value().packageFullName, - .commandLineArgs = L"", - .isMinimized = WindowUtils::IsMinimized(window), - .isMaximized = WindowUtils::IsMaximized(window), - .position = Project::Application::Position { - .x = rect.left, - .y = rect.top, - .width = rect.right - rect.left, - .height = rect.bottom - rect.top, - }, - .monitor = monitorNumber, - }; + return monitorNumber; + }); - project.apps.push_back(app); - } - - projects.push_back(project); - json::to_file(fileName, JsonUtils::ProjectsListJSON::ToJson(projects)); + ProjectsJsonUtils::Write(JsonUtils::TempProjectsFile(), project); Logger::trace(L"Project {}:{} created", project.name, project.id); + return 0; } diff --git a/src/modules/Projects/ProjectsSnapshotTool/resource.h b/src/modules/Projects/ProjectsSnapshotTool/resource.base.h similarity index 100% rename from src/modules/Projects/ProjectsSnapshotTool/resource.h rename to src/modules/Projects/ProjectsSnapshotTool/resource.base.h diff --git a/src/modules/Projects/projects-common/AppUtils.h b/src/modules/Projects/projects-common/AppUtils.h index 8135bb375b..fac0a12b28 100644 --- a/src/modules/Projects/projects-common/AppUtils.h +++ b/src/modules/Projects/projects-common/AppUtils.h @@ -16,12 +16,12 @@ namespace Utils { namespace NonLocalizable { - const wchar_t* PackageFullNameProp = L"System.AppUserModel.PackageFullName"; - const wchar_t* PackageInstallPathProp = L"System.AppUserModel.PackageInstallPath"; - const wchar_t* InstallPathProp = L"System.Link.TargetParsingPath"; + constexpr const wchar_t* PackageFullNameProp = L"System.AppUserModel.PackageFullName"; + constexpr const wchar_t* PackageInstallPathProp = L"System.AppUserModel.PackageInstallPath"; + constexpr const wchar_t* InstallPathProp = L"System.Link.TargetParsingPath"; - const wchar_t* FileExplorerName = L"File Explorer"; - const wchar_t* FileExplorerPath = L"C:\\WINDOWS\\EXPLORER.EXE"; + constexpr const wchar_t* FileExplorerName = L"File Explorer"; + constexpr const wchar_t* FileExplorerPath = L"C:\\WINDOWS\\EXPLORER.EXE"; } struct AppData diff --git a/src/modules/Projects/projects-common/Data.h b/src/modules/Projects/projects-common/Data.h index f44a247f05..04f08f4c20 100644 --- a/src/modules/Projects/projects-common/Data.h +++ b/src/modules/Projects/projects-common/Data.h @@ -81,6 +81,12 @@ namespace JsonUtils return std::wstring(settingsFolderPath) + L"\\projects.json"; } + inline std::wstring TempProjectsFile() + { + std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey); + return std::wstring(settingsFolderPath) + L"\\temp-project.json"; + } + namespace ProjectJSON { namespace ApplicationJSON diff --git a/src/modules/Projects/projects-common/WindowUtils.h b/src/modules/Projects/projects-common/WindowUtils.h index 37a9c5bae5..c364661c5a 100644 --- a/src/modules/Projects/projects-common/WindowUtils.h +++ b/src/modules/Projects/projects-common/WindowUtils.h @@ -1,28 +1,23 @@ #pragma once -#include -#include - -#include - #include #include #include -// FancyZones WindowUtils namespace WindowUtils { // Non-Localizable strings namespace NonLocalizable { - const wchar_t SystemAppsFolder[] = L"SYSTEMAPPS"; - const wchar_t System[] = L"WINDOWS/SYSTEM"; - const wchar_t System32[] = L"SYSTEM32"; - const wchar_t SystemWOW64[] = L"SYSTEMWOW64"; const char SplashClassName[] = "MsoSplash"; + + const wchar_t SystemAppsFolder[] = L"SYSTEMAPPS"; + const wchar_t CoreWindow[] = L"WINDOWS.UI.CORE.COREWINDOW"; const wchar_t SearchUI[] = L"SEARCHUI.EXE"; - const wchar_t HelpWindow[] = L"C:\\WINDOWS\\HH.EXE"; + const wchar_t HelpWindow[] = L"WINDOWS\\HH.EXE"; + const wchar_t ApplicationFrameHost[] = L"WINDOWS\\SYSTEM32\\APPLICATIONFRAMEHOST.EXE"; + const wchar_t ProjectsSnapshotTool[] = L"POWERTOYS.PROJECTSSNAPSHOTTOOL"; const wchar_t ProjectsEditor[] = L"POWERTOYS.PROJECTSEDITOR"; const wchar_t ProjectsLauncher[] = L"POWERTOYS.PROJECTSLAUNCHER"; @@ -33,6 +28,11 @@ namespace WindowUtils return GetAncestor(window, GA_ROOT) == window; } + inline bool IsMinimized(HWND window) + { + return IsIconic(window); + } + inline bool IsMaximized(HWND window) noexcept { WINDOWPLACEMENT placement{}; @@ -55,10 +55,7 @@ namespace WindowUtils CharUpperBuffW(processPathUpper.data(), static_cast(processPathUpper.length())); static std::vector defaultExcludedFolders = { - NonLocalizable::SystemAppsFolder, - NonLocalizable::System, - NonLocalizable::System32, - NonLocalizable::SystemWOW64 + NonLocalizable::SystemAppsFolder, }; if (find_folder_in_path(processPathUpper, defaultExcludedFolders)) { @@ -109,15 +106,6 @@ namespace WindowUtils return rect; } -} - -// addition for Projects -namespace WindowUtils -{ - inline bool IsMinimized(HWND window) - { - return IsIconic(window); - } #define MAX_TITLE_LENGTH 255 inline std::wstring GetWindowTitle(HWND window)