mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Projects
This commit is contained in:
74
src/modules/Projects/Projects.sln
Normal file
74
src/modules/Projects/Projects.sln
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34622.214
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectsSnapshotTool", "ProjectsSnapshotTool\ProjectsSnapshotTool.vcxproj", "{3D63307B-9D27-44FD-B033-B26F39245B85}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "projects-common", "projects-common", "{BA45247D-3046-408D-BE01-128587A7799F}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
projects-common\Data.h = projects-common\Data.h
|
||||
projects-common\GuidUtils.h = projects-common\GuidUtils.h
|
||||
projects-common\json.h = projects-common\json.h
|
||||
projects-common\MonitorEnumerator.h = projects-common\MonitorEnumerator.h
|
||||
projects-common\WindowEnumerator.h = projects-common\WindowEnumerator.h
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectsLauncher", "ProjectsLauncher\ProjectsLauncher.vcxproj", "{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectsEditor", "ProjectsEditor\ProjectsEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.Build.0 = Debug|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.Build.0 = Debug|Win32
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|Any CPU.Build.0 = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.ActiveCfg = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.Build.0 = Release|x64
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.ActiveCfg = Release|Win32
|
||||
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.Build.0 = Release|Win32
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.Build.0 = Debug|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.Build.0 = Debug|Win32
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|Any CPU.Build.0 = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.ActiveCfg = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.ActiveCfg = Release|Win32
|
||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.Build.0 = Release|Win32
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.Build.0 = Debug|x64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.ActiveCfg = Release|x64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.Build.0 = Release|x64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BE6AD818-2650-419C-8FDE-535C22ED09B3}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
9
src/modules/Projects/ProjectsEditor/App.config
Normal file
9
src/modules/Projects/ProjectsEditor/App.config
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
</startup>
|
||||
<runtime>
|
||||
<AppContextSwitchOverrides value = "Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
|
||||
</runtime>
|
||||
</configuration>
|
||||
20
src/modules/Projects/ProjectsEditor/App.xaml
Normal file
20
src/modules/Projects/ProjectsEditor/App.xaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<Application
|
||||
x:Class="ProjectsEditor.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:ProjectsEditor"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
Exit="OnExit"
|
||||
Startup="OnStartup">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources />
|
||||
<ui:XamlControlsResources />
|
||||
<ResourceDictionary Source="pack://application:,,,/Styles/ButtonStyles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style x:Key="HeadingTextBlock" TargetType="TextBlock" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
98
src/modules/Projects/ProjectsEditor/App.xaml.cs
Normal file
98
src/modules/Projects/ProjectsEditor/App.xaml.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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.Windows;
|
||||
using ProjectsEditor.Common;
|
||||
using ProjectsEditor.Utils;
|
||||
using ProjectsEditor.ViewModels;
|
||||
|
||||
namespace ProjectsEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application, IDisposable
|
||||
{
|
||||
public static ProjectsEditorIO ProjectsEditorIO { get; private set; }
|
||||
|
||||
private MainWindow _mainWindow;
|
||||
|
||||
private MainViewModel _mainViewModel;
|
||||
|
||||
private ThemeManager _themeManager;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public App()
|
||||
{
|
||||
ProjectsEditorIO = new ProjectsEditorIO();
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
|
||||
_themeManager = new ThemeManager(this);
|
||||
|
||||
if (_mainViewModel == null)
|
||||
{
|
||||
_mainViewModel = new MainViewModel(ProjectsEditorIO);
|
||||
}
|
||||
|
||||
var parseResult = ProjectsEditorIO.ParseProjects(_mainViewModel);
|
||||
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
if (args != null && args.Length > 1)
|
||||
{
|
||||
_mainViewModel.LaunchProject(args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// normal start of editor
|
||||
if (_mainWindow == null)
|
||||
{
|
||||
_mainWindow = new MainWindow(_mainViewModel);
|
||||
}
|
||||
|
||||
// reset main window owner to keep it on the top
|
||||
_mainWindow.ShowActivated = true;
|
||||
_mainWindow.Topmost = true;
|
||||
_mainWindow.Show();
|
||||
|
||||
// we can reset topmost flag after it's opened
|
||||
_mainWindow.Topmost = false;
|
||||
}
|
||||
|
||||
private void OnExit(object sender, ExitEventArgs e)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||
{
|
||||
// TODO: log the error and show an error message
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_themeManager?.Dispose();
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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 ControlzEx.Theming;
|
||||
|
||||
namespace ProjectsEditor.Common
|
||||
{
|
||||
public class CustomLibraryThemeProvider : LibraryThemeProvider
|
||||
{
|
||||
public static readonly CustomLibraryThemeProvider DefaultInstance = new CustomLibraryThemeProvider();
|
||||
|
||||
public CustomLibraryThemeProvider()
|
||||
: base(true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void FillColorSchemeValues(Dictionary<string, string> values, RuntimeThemeColorValues colorValues)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/modules/Projects/ProjectsEditor/Common/Theme.cs
Normal file
23
src/modules/Projects/ProjectsEditor/Common/Theme.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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 ProjectsEditor.Common
|
||||
{
|
||||
public enum Theme
|
||||
{
|
||||
System,
|
||||
Light,
|
||||
Dark,
|
||||
HighContrastOne,
|
||||
HighContrastTwo,
|
||||
HighContrastBlack,
|
||||
HighContrastWhite,
|
||||
}
|
||||
|
||||
public enum AppTheme
|
||||
{
|
||||
Dark = 0,
|
||||
Light = 1,
|
||||
}
|
||||
}
|
||||
205
src/modules/Projects/ProjectsEditor/Common/ThemeManager.cs
Normal file
205
src/modules/Projects/ProjectsEditor/Common/ThemeManager.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
// 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 ManagedCommon;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace ProjectsEditor.Common
|
||||
{
|
||||
public class ThemeManager : IDisposable
|
||||
{
|
||||
private readonly Application _app;
|
||||
private const string LightTheme = "Light.Accent1";
|
||||
private const string DarkTheme = "Dark.Accent1";
|
||||
private const string HighContrastOneTheme = "HighContrast.Accent2";
|
||||
private const string HighContrastTwoTheme = "HighContrast.Accent3";
|
||||
private const string HighContrastBlackTheme = "HighContrast.Accent4";
|
||||
private const string HighContrastWhiteTheme = "HighContrast.Accent5";
|
||||
|
||||
private Theme _currentTheme;
|
||||
private Theme _settingsTheme;
|
||||
private bool _disposed;
|
||||
|
||||
public event ThemeChangedHandler ThemeChanged;
|
||||
|
||||
public ThemeManager(Application app)
|
||||
{
|
||||
_app = app;
|
||||
|
||||
Uri highContrastOneThemeUri = new Uri("pack://application:,,,/Themes/HighContrast1.xaml");
|
||||
Uri highContrastTwoThemeUri = new Uri("pack://application:,,,/Themes/HighContrast2.xaml");
|
||||
Uri highContrastBlackThemeUri = new Uri("pack://application:,,,/Themes/HighContrastWhite.xaml");
|
||||
Uri highContrastWhiteThemeUri = new Uri("pack://application:,,,/Themes/HighContrastBlack.xaml");
|
||||
Uri lightThemeUri = new Uri("pack://application:,,,/Themes/Light.xaml");
|
||||
Uri darkThemeUri = new Uri("pack://application:,,,/Themes/Dark.xaml");
|
||||
|
||||
ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme(
|
||||
new ControlzEx.Theming.LibraryTheme(
|
||||
highContrastOneThemeUri,
|
||||
CustomLibraryThemeProvider.DefaultInstance));
|
||||
ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme(
|
||||
new ControlzEx.Theming.LibraryTheme(
|
||||
highContrastTwoThemeUri,
|
||||
CustomLibraryThemeProvider.DefaultInstance));
|
||||
ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme(
|
||||
new ControlzEx.Theming.LibraryTheme(
|
||||
highContrastBlackThemeUri,
|
||||
CustomLibraryThemeProvider.DefaultInstance));
|
||||
ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme(
|
||||
new ControlzEx.Theming.LibraryTheme(
|
||||
highContrastWhiteThemeUri,
|
||||
CustomLibraryThemeProvider.DefaultInstance));
|
||||
ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme(
|
||||
new ControlzEx.Theming.LibraryTheme(
|
||||
lightThemeUri,
|
||||
CustomLibraryThemeProvider.DefaultInstance));
|
||||
ControlzEx.Theming.ThemeManager.Current.AddLibraryTheme(
|
||||
new ControlzEx.Theming.LibraryTheme(
|
||||
darkThemeUri,
|
||||
CustomLibraryThemeProvider.DefaultInstance));
|
||||
|
||||
ResetTheme();
|
||||
ControlzEx.Theming.ThemeManager.Current.ThemeSyncMode = ControlzEx.Theming.ThemeSyncMode.SyncWithAppMode;
|
||||
ControlzEx.Theming.ThemeManager.Current.ThemeChanged += Current_ThemeChanged;
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.HighContrast))
|
||||
{
|
||||
ResetTheme();
|
||||
}
|
||||
}
|
||||
|
||||
public Theme GetCurrentTheme()
|
||||
{
|
||||
return _currentTheme;
|
||||
}
|
||||
|
||||
private static Theme GetHighContrastBaseType()
|
||||
{
|
||||
string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes";
|
||||
string theme = (string)Registry.GetValue(registryKey, "CurrentTheme", string.Empty);
|
||||
theme = theme.Split('\\').Last().Split('.').First().ToString();
|
||||
|
||||
switch (theme)
|
||||
{
|
||||
case "hc1":
|
||||
return Theme.HighContrastOne;
|
||||
case "hc2":
|
||||
return Theme.HighContrastTwo;
|
||||
case "hcwhite":
|
||||
return Theme.HighContrastWhite;
|
||||
case "hcblack":
|
||||
return Theme.HighContrastBlack;
|
||||
default:
|
||||
return Theme.HighContrastOne;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetTheme()
|
||||
{
|
||||
ChangeTheme(_settingsTheme == Theme.System ? Theme.System : _currentTheme);
|
||||
}
|
||||
|
||||
public static string GetWindowsBaseColor()
|
||||
{
|
||||
return ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
|
||||
}
|
||||
|
||||
public void ChangeTheme(Theme theme, bool fromSettings = false)
|
||||
{
|
||||
if (fromSettings)
|
||||
{
|
||||
_settingsTheme = theme;
|
||||
}
|
||||
|
||||
Theme oldTheme = _currentTheme;
|
||||
|
||||
if (theme == Theme.System)
|
||||
{
|
||||
_currentTheme = Theme.System;
|
||||
if (ControlzEx.Theming.WindowsThemeHelper.IsHighContrastEnabled())
|
||||
{
|
||||
Theme highContrastBaseType = GetHighContrastBaseType();
|
||||
ChangeTheme(highContrastBaseType, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
string baseColor = ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
|
||||
ChangeTheme((Theme)Enum.Parse(typeof(Theme), baseColor));
|
||||
}
|
||||
}
|
||||
else if (theme == Theme.HighContrastOne)
|
||||
{
|
||||
_currentTheme = Theme.HighContrastOne;
|
||||
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(_app, HighContrastOneTheme);
|
||||
}
|
||||
else if (theme == Theme.HighContrastTwo)
|
||||
{
|
||||
_currentTheme = Theme.HighContrastTwo;
|
||||
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(_app, HighContrastTwoTheme);
|
||||
}
|
||||
else if (theme == Theme.HighContrastWhite)
|
||||
{
|
||||
_currentTheme = Theme.HighContrastWhite;
|
||||
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(_app, HighContrastWhiteTheme);
|
||||
}
|
||||
else if (theme == Theme.HighContrastBlack)
|
||||
{
|
||||
_currentTheme = Theme.HighContrastBlack;
|
||||
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(_app, HighContrastBlackTheme);
|
||||
}
|
||||
else if (theme == Theme.Light)
|
||||
{
|
||||
_currentTheme = Theme.Light;
|
||||
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(_app, LightTheme);
|
||||
}
|
||||
else if (theme == Theme.Dark)
|
||||
{
|
||||
_currentTheme = Theme.Dark;
|
||||
ControlzEx.Theming.ThemeManager.Current.ChangeTheme(_app, DarkTheme);
|
||||
}
|
||||
|
||||
ThemeChanged?.Invoke(oldTheme, _currentTheme);
|
||||
}
|
||||
|
||||
private void Current_ThemeChanged(object sender, ControlzEx.Theming.ThemeChangedEventArgs e)
|
||||
{
|
||||
ControlzEx.Theming.ThemeManager.Current.ThemeChanged -= Current_ThemeChanged;
|
||||
try
|
||||
{
|
||||
ResetTheme();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ControlzEx.Theming.ThemeManager.Current.ThemeChanged += Current_ThemeChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
ControlzEx.Theming.ThemeManager.Current.ThemeChanged -= Current_ThemeChanged;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void ThemeChangedHandler(Theme oldTheme, Theme newTheme);
|
||||
}
|
||||
107
src/modules/Projects/ProjectsEditor/Data/ProjectsData.cs
Normal file
107
src/modules/Projects/ProjectsEditor/Data/ProjectsData.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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.ProjectsData;
|
||||
|
||||
namespace ProjectsEditor.Data
|
||||
{
|
||||
public class ProjectsData : ProjectsEditorData<ProjectsListWrapper>
|
||||
{
|
||||
public string File
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetDataFolder() + "\\projects.json";
|
||||
}
|
||||
}
|
||||
|
||||
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 long Hwnd { get; set; }
|
||||
|
||||
public string Application { get; set; }
|
||||
|
||||
public string Title { 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<MonitorConfigurationWrapper> MonitorConfiguration { get; set; }
|
||||
|
||||
public List<ApplicationWrapper> Applications { get; set; }
|
||||
}
|
||||
|
||||
public struct ProjectsListWrapper
|
||||
{
|
||||
public List<ProjectWrapper> Projects { get; set; }
|
||||
}
|
||||
|
||||
public enum OrderBy
|
||||
{
|
||||
LastViewed = 0,
|
||||
Created = 1,
|
||||
Name = 2,
|
||||
Unknown = 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.IO;
|
||||
using System.Text.Json;
|
||||
using ProjectsEditor.Utils;
|
||||
|
||||
namespace Projects.Data
|
||||
{
|
||||
public class ProjectsEditorData<T>
|
||||
{
|
||||
// Note: the same path should be used in SnapshotTool and Launcher
|
||||
public string GetDataFolder()
|
||||
{
|
||||
// return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
return Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
}
|
||||
|
||||
public string GetTempDataFolder()
|
||||
{
|
||||
return Path.GetTempPath();
|
||||
}
|
||||
|
||||
protected JsonSerializerOptions JsonOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
WriteIndented = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public T Read(string file)
|
||||
{
|
||||
IOUtils ioUtils = new IOUtils();
|
||||
string data = ioUtils.ReadFile(file);
|
||||
return JsonSerializer.Deserialize<T>(data, JsonOptions);
|
||||
}
|
||||
|
||||
public string Serialize(T data)
|
||||
{
|
||||
return JsonSerializer.Serialize(data, JsonOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/modules/Projects/ProjectsEditor/HeadingTextBlock.cs
Normal file
31
src/modules/Projects/ProjectsEditor/HeadingTextBlock.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Automation.Peers;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace ProjectsEditor
|
||||
{
|
||||
public class HeadingTextBlock : TextBlock
|
||||
{
|
||||
protected override AutomationPeer OnCreateAutomationPeer()
|
||||
{
|
||||
return new HeadingTextBlockAutomationPeer(this);
|
||||
}
|
||||
|
||||
internal sealed class HeadingTextBlockAutomationPeer : TextBlockAutomationPeer
|
||||
{
|
||||
public HeadingTextBlockAutomationPeer(HeadingTextBlock owner)
|
||||
: base(owner)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AutomationControlType GetAutomationControlTypeCore()
|
||||
{
|
||||
return AutomationControlType.Header;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
272
src/modules/Projects/ProjectsEditor/MainPage.xaml
Normal file
272
src/modules/Projects/ProjectsEditor/MainPage.xaml
Normal file
@@ -0,0 +1,272 @@
|
||||
<Page
|
||||
x:Class="ProjectsEditor.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:props="clr-namespace:ProjectsEditor.Properties"
|
||||
xmlns:local="clr-namespace:ProjectsEditor"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Title="MainPage">
|
||||
<Page.Resources>
|
||||
<Thickness x:Key="ContentDialogPadding">24,16,0,24</Thickness>
|
||||
<Thickness x:Key="ContentDialogCommandSpaceMargin">0,24,24,0</Thickness>
|
||||
<Style x:Key="CancelButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="#9d0808" />
|
||||
<Setter Property="BorderBrush" Value="#FF262E34"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}" BorderBrush="#FF262E34" BorderThickness="1">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#ec4d37"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#9d0808"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<local:HeadingTextBlock
|
||||
x:Name="ProjectsHeaderBlock"
|
||||
AutomationProperties.HeadingLevel="Level1"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Static props:Resources.Projects}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Grid.Row="0"
|
||||
Margin="40,20,40,20"/>
|
||||
|
||||
<Button
|
||||
x:Name="NewProjectButton"
|
||||
Height="36"
|
||||
Padding="0"
|
||||
Grid.Row="0"
|
||||
Margin="0,20,40,20"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
AutomationProperties.Name="{x:Static props:Resources.CreateProject}"
|
||||
Click="NewProjectButton_Click"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
TabIndex="3">
|
||||
<StackPanel Margin="12, 8, 12, 8" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
AutomationProperties.Name="{x:Static props:Resources.CreateProject}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="" />
|
||||
<TextBlock
|
||||
Margin="12,-3,0,0"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="{x:Static props:Resources.CreateProject}" />
|
||||
</StackPanel>
|
||||
<Button.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="6"
|
||||
Opacity="0.32"
|
||||
ShadowDepth="1" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
|
||||
<Border
|
||||
HorizontalAlignment="Left"
|
||||
Grid.Row="1"
|
||||
Margin="40,0,0,0"
|
||||
BorderThickness="2"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="5">
|
||||
<StackPanel
|
||||
Orientation="Horizontal">
|
||||
<Grid>
|
||||
<TextBox
|
||||
x:Name="SearchTextBox"
|
||||
Width="320"
|
||||
Text="{Binding SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
/>
|
||||
<TextBlock
|
||||
IsHitTestVisible="False"
|
||||
Text="{x:Static props:Resources.Search}"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||
Margin="10,0,0,0">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Text, ElementName=SearchTextBox}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="-24,0,10,0"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Search}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||
Text="" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Margin="0,0,40,0">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="10,0,10,0"
|
||||
Text="{x:Static props:Resources.SortBy}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
/>
|
||||
<ComboBox
|
||||
Width="140"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
SelectedIndex="{Binding OrderByIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ComboBoxItem Content="{x:Static props:Resources.LastLaunched}" />
|
||||
<ComboBoxItem Content="{x:Static props:Resources.Created}" />
|
||||
<ComboBoxItem Content="{x:Static props:Resources.Name}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer
|
||||
VerticalContentAlignment="Stretch"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Grid.Row="2"
|
||||
Margin="40,15,40,40">
|
||||
<ItemsControl ItemsSource="{Binding ProjectsView, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel
|
||||
IsItemsHost="True"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="models:Project">
|
||||
<Button
|
||||
x:Name="EditButton"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||
Margin="0,12,0,0"
|
||||
Click="EditButtonClicked"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
Padding="1">
|
||||
<Border Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
HorizontalAlignment="Stretch"
|
||||
CornerRadius="5">
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="12,14,10,10">
|
||||
<TextBlock
|
||||
Text="{Binding Name, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="16"
|
||||
Margin="0,0,0,8"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"/>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,8" >
|
||||
<Image
|
||||
Source="{Binding PreviewImage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="20" />
|
||||
<TextBlock
|
||||
Text="{Binding AppsCountString}"
|
||||
Margin="6,0,4,0"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text=""
|
||||
Margin="0,3,10,0"/>
|
||||
<TextBlock Text="{Binding LastLaunched, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" DockPanel.Dock="Left" Margin="12,12,12,12">
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button
|
||||
x:Name="MoreButton"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource IconOnlyButtonStyle}"
|
||||
Click="MoreButton_Click">
|
||||
<TextBlock
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Text=""/>
|
||||
</Button>
|
||||
<Popup
|
||||
IsOpen="{Binding IsPopupVisible, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
StaysOpen="False"
|
||||
PlacementTarget="{Binding ElementName=MoreButton}"
|
||||
Placement="Left">
|
||||
<Button
|
||||
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||
Click="DeleteButtonClicked">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="" />
|
||||
<TextBlock
|
||||
Margin="10,0,0,0"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="{x:Static props:Resources.Delete}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
<Button
|
||||
Padding="20,4,20,4"
|
||||
Margin="0,6,0,0"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Launch}"
|
||||
Content="{x:Static props:Resources.Launch}"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource SecondaryBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Click="LaunchButton_Click"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
62
src/modules/Projects/ProjectsEditor/MainPage.xaml.cs
Normal file
62
src/modules/Projects/ProjectsEditor/MainPage.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using ProjectsEditor.Models;
|
||||
using ProjectsEditor.ViewModels;
|
||||
|
||||
namespace ProjectsEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainPage.xaml
|
||||
/// </summary>
|
||||
public partial class MainPage : Page
|
||||
{
|
||||
private MainViewModel _mainViewModel;
|
||||
|
||||
public MainPage(MainViewModel mainViewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
_mainViewModel = mainViewModel;
|
||||
this.DataContext = _mainViewModel;
|
||||
}
|
||||
|
||||
private /*async*/ void NewProjectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_mainViewModel.AddNewProject();
|
||||
}
|
||||
|
||||
private void EditButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Button button = sender as Button;
|
||||
Project selectedProject = button.DataContext as Project;
|
||||
_mainViewModel.EditProject(selectedProject);
|
||||
}
|
||||
|
||||
private void DeleteButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
Button button = sender as Button;
|
||||
Project selectedProject = button.DataContext as Project;
|
||||
_mainViewModel.DeleteProject(selectedProject);
|
||||
}
|
||||
|
||||
private void MoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
Button button = sender as Button;
|
||||
Project project = button.DataContext as Project;
|
||||
project.IsPopupVisible = true;
|
||||
}
|
||||
|
||||
private void LaunchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
Button button = sender as Button;
|
||||
Project project = button.DataContext as Project;
|
||||
_mainViewModel.LaunchProject(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/modules/Projects/ProjectsEditor/MainWindow.xaml
Normal file
33
src/modules/Projects/ProjectsEditor/MainWindow.xaml
Normal file
@@ -0,0 +1,33 @@
|
||||
<Window
|
||||
x:Class="ProjectsEditor.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:props="clr-namespace:ProjectsEditor.Properties"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
x:Name="ProjectsMainWindow"
|
||||
Title="{x:Static props:Resources.MainTitle}"
|
||||
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
ui:TitleBar.InactiveBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||
ui:TitleBar.IsIconVisible="True"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
MinWidth="700"
|
||||
MinHeight="480"
|
||||
AutomationProperties.Name="Projects Editor"
|
||||
Closing="OnClosing"
|
||||
ContentRendered="OnContentRendered"
|
||||
ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d"
|
||||
Background="{DynamicResource PrimaryBackgroundBrush}">
|
||||
<Border
|
||||
CornerRadius="20"
|
||||
BorderThickness="1">
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Frame
|
||||
x:Name="ContentFrame"
|
||||
NavigationUIVisibility="Hidden"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
100
src/modules/Projects/ProjectsEditor/MainWindow.xaml.cs
Normal file
100
src/modules/Projects/ProjectsEditor/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Interop;
|
||||
using ProjectsEditor.Utils;
|
||||
using ProjectsEditor.ViewModels;
|
||||
|
||||
namespace ProjectsEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private bool haveTriedToGetFocusAlready;
|
||||
|
||||
public MainViewModel MainViewModel { get; set; }
|
||||
|
||||
private static MainPage _mainPage;
|
||||
|
||||
public MainWindow(MainViewModel mainViewModel)
|
||||
{
|
||||
MainViewModel = mainViewModel;
|
||||
mainViewModel.SetMainWindow(this);
|
||||
InitializeComponent();
|
||||
|
||||
_mainPage = new MainPage(mainViewModel);
|
||||
|
||||
ContentFrame.Navigate(_mainPage);
|
||||
|
||||
MaxWidth = SystemParameters.PrimaryScreenWidth;
|
||||
MaxHeight = SystemParameters.PrimaryScreenHeight;
|
||||
}
|
||||
|
||||
private void BringToFront()
|
||||
{
|
||||
// Get the window handle of the Projects Editor window
|
||||
IntPtr handle = new WindowInteropHelper(this).Handle;
|
||||
|
||||
// Get the handle of the window currently in the foreground
|
||||
IntPtr foregroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
|
||||
// Get the thread IDs of the current thread and the thread of the foreground window
|
||||
uint currentThreadId = NativeMethods.GetCurrentThreadId();
|
||||
uint activeThreadId = NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, IntPtr.Zero);
|
||||
|
||||
// Check if the active thread is different from the current thread
|
||||
if (activeThreadId != currentThreadId)
|
||||
{
|
||||
// Attach the input processing mechanism of the current thread to the active thread
|
||||
NativeMethods.AttachThreadInput(activeThreadId, currentThreadId, true);
|
||||
|
||||
// Set the Projects Editor window as the foreground window
|
||||
NativeMethods.SetForegroundWindow(handle);
|
||||
|
||||
// Detach the input processing mechanism of the current thread from the active thread
|
||||
NativeMethods.AttachThreadInput(activeThreadId, currentThreadId, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the Projects Editor window as the foreground window
|
||||
NativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
|
||||
// Bring the Projects Editor window to the foreground and activate it
|
||||
NativeMethods.SwitchToThisWindow(handle, true);
|
||||
|
||||
haveTriedToGetFocusAlready = true;
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, EventArgs e)
|
||||
{
|
||||
App.Current.Shutdown();
|
||||
}
|
||||
|
||||
// This is required to fix a WPF rendering bug when using custom chrome
|
||||
private void OnContentRendered(object sender, EventArgs e)
|
||||
{
|
||||
if (!haveTriedToGetFocusAlready)
|
||||
{
|
||||
BringToFront();
|
||||
}
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
public void ShowPage(ProjectEditor editPage)
|
||||
{
|
||||
ContentFrame.Navigate(editPage);
|
||||
}
|
||||
|
||||
public void SwitchToMainView()
|
||||
{
|
||||
ContentFrame.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 ProjectsEditor.Models
|
||||
{
|
||||
public sealed class AppListDataTemplateSelector : System.Windows.Controls.DataTemplateSelector
|
||||
{
|
||||
public System.Windows.DataTemplate HeaderTemplate { get; set; }
|
||||
|
||||
public System.Windows.DataTemplate AppTemplate { get; set; }
|
||||
|
||||
public AppListDataTemplateSelector()
|
||||
{
|
||||
HeaderTemplate = new System.Windows.DataTemplate();
|
||||
AppTemplate = new System.Windows.DataTemplate();
|
||||
}
|
||||
|
||||
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
|
||||
{
|
||||
if (item is string)
|
||||
{
|
||||
return HeaderTemplate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
252
src/modules/Projects/ProjectsEditor/Models/Application.cs
Normal file
252
src/modules/Projects/ProjectsEditor/Models/Application.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace ProjectsEditor.Models
|
||||
{
|
||||
public class Application : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public Project Parent { get; set; }
|
||||
|
||||
public struct WindowPosition
|
||||
{
|
||||
public int X { get; set; }
|
||||
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
public IntPtr Hwnd { get; set; }
|
||||
|
||||
public string AppPath { get; set; }
|
||||
|
||||
public string AppTitle { get; set; }
|
||||
|
||||
public string CommandLineArguments { get; set; }
|
||||
|
||||
public bool Minimized { get; set; }
|
||||
|
||||
public bool Maximized { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsHighlighted { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int RepeatIndex { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string RepeatIndexString
|
||||
{
|
||||
get
|
||||
{
|
||||
return RepeatIndex == 0 ? string.Empty : RepeatIndex.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private Icon _icon = null;
|
||||
|
||||
[JsonIgnore]
|
||||
public Icon Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_icon == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_icon = Icon.ExtractAssociatedIcon(AppPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_icon = new Icon(@"images\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;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
_iconBitmapImage = bitmapImage;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
return _iconBitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string AppName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (File.Exists(AppPath))
|
||||
{
|
||||
return Path.GetFileNameWithoutExtension(AppPath);
|
||||
}
|
||||
|
||||
return AppPath.Split('\\').LastOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public WindowPosition Position { get; set; }
|
||||
|
||||
private WindowPosition? _scaledPosition;
|
||||
|
||||
public WindowPosition ScaledPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_scaledPosition == null)
|
||||
{
|
||||
double scaleFactor = MonitorSetup.Dpi / 96.0;
|
||||
_scaledPosition = new WindowPosition()
|
||||
{
|
||||
X = (int)(scaleFactor * Position.X),
|
||||
Y = (int)(scaleFactor * Position.Y),
|
||||
Height = (int)(scaleFactor * Position.Height),
|
||||
Width = (int)(scaleFactor * Position.Width),
|
||||
};
|
||||
}
|
||||
|
||||
return _scaledPosition.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public int MonitorNumber { get; set; }
|
||||
|
||||
private MonitorSetup _monitorSetup;
|
||||
|
||||
public MonitorSetup MonitorSetup
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_monitorSetup == null)
|
||||
{
|
||||
_monitorSetup = Parent.Monitors.Where(x => x.MonitorNumber == MonitorNumber).FirstOrDefault();
|
||||
}
|
||||
|
||||
return _monitorSetup;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
internal bool IsMyAppPath(string path)
|
||||
{
|
||||
if (!IsPackagedApp)
|
||||
{
|
||||
return path.Equals(AppPath, StringComparison.Ordinal);
|
||||
}
|
||||
else
|
||||
{
|
||||
return path.Contains(PackagedName + "_", StringComparison.InvariantCultureIgnoreCase) && path.Contains(PackagedPublisherID, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
private bool? _isPackagedApp;
|
||||
|
||||
public string PackagedId { get; set; }
|
||||
|
||||
public string PackagedName { get; set; }
|
||||
|
||||
public string PackagedPublisherID { get; set; }
|
||||
|
||||
public string Aumid { get; set; }
|
||||
|
||||
public bool IsPackagedApp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isPackagedApp == null)
|
||||
{
|
||||
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_isPackagedApp = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
Match match = packagedAppPathRegex.Match(appPath);
|
||||
_isPackagedApp = match.Success;
|
||||
if (match.Success)
|
||||
{
|
||||
PackagedName = match.Groups["APPID"].Value;
|
||||
PackagedPublisherID = match.Groups["PublisherID"].Value;
|
||||
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
|
||||
Aumid = $"{PackagedId}!App";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _isPackagedApp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/modules/Projects/ProjectsEditor/Models/Monitor.cs
Normal file
33
src/modules/Projects/ProjectsEditor/Models/Monitor.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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.Windows;
|
||||
|
||||
namespace ProjectsEditor.Models
|
||||
{
|
||||
public class Monitor
|
||||
{
|
||||
public string MonitorName { get; private set; }
|
||||
|
||||
public string MonitorInstanceId { get; private set; }
|
||||
|
||||
public int MonitorNumber { get; private set; }
|
||||
|
||||
public int Dpi { get; private set; }
|
||||
|
||||
public Rect MonitorDpiUnawareBounds { get; private set; }
|
||||
|
||||
public Rect MonitorDpiAwareBounds { get; private set; }
|
||||
|
||||
public Monitor(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
|
||||
{
|
||||
MonitorName = monitorName;
|
||||
MonitorInstanceId = monitorInstanceId;
|
||||
MonitorNumber = number;
|
||||
Dpi = dpi;
|
||||
MonitorDpiAwareBounds = dpiAwareBounds;
|
||||
MonitorDpiUnawareBounds = dpiUnawareBounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/modules/Projects/ProjectsEditor/Models/MonitorSetup.cs
Normal file
45
src/modules/Projects/ProjectsEditor/Models/MonitorSetup.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace ProjectsEditor.Models
|
||||
{
|
||||
public class MonitorSetup : Monitor, INotifyPropertyChanged
|
||||
{
|
||||
private BitmapImage _previewImage;
|
||||
|
||||
public BitmapImage PreviewImage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previewImage;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_previewImage = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImage)));
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public string MonitorInfo { get => MonitorName; }
|
||||
|
||||
public string MonitorInfoWithResolution { get => $"{MonitorName} {MonitorDpiAwareBounds.Width}x{MonitorDpiAwareBounds.Height}"; }
|
||||
|
||||
public MonitorSetup(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
|
||||
: base(monitorName, monitorInstanceId, number, dpi, dpiAwareBounds, dpiUnawareBounds)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
545
src/modules/Projects/ProjectsEditor/Models/Project.cs
Normal file
545
src/modules/Projects/ProjectsEditor/Models/Project.cs
Normal file
@@ -0,0 +1,545 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace ProjectsEditor.Models
|
||||
{
|
||||
public class Project : INotifyPropertyChanged
|
||||
{
|
||||
public class ScreenHeader : Application
|
||||
{
|
||||
public string Title { get; set; }
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string EditorWindowTitle { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Name)));
|
||||
}
|
||||
}
|
||||
|
||||
public long CreationTime { get; set; } // in seconds
|
||||
|
||||
public long LastLaunchedTime { get; set; } // in seconds
|
||||
|
||||
public bool IsShortcutNeeded { get; set; }
|
||||
|
||||
public string LastLaunched
|
||||
{
|
||||
get
|
||||
{
|
||||
string lastLaunched = ProjectsEditor.Properties.Resources.LastLaunched + ": ";
|
||||
if (LastLaunchedTime == 0)
|
||||
{
|
||||
return lastLaunched + ProjectsEditor.Properties.Resources.Never;
|
||||
}
|
||||
|
||||
const int SECOND = 1;
|
||||
const int MINUTE = 60 * SECOND;
|
||||
const int HOUR = 60 * MINUTE;
|
||||
const int DAY = 24 * HOUR;
|
||||
const int MONTH = 30 * DAY;
|
||||
|
||||
DateTime lastLaunchDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(LastLaunchedTime);
|
||||
|
||||
var now = DateTime.UtcNow.Ticks;
|
||||
var ts = DateTime.UtcNow - lastLaunchDateTime;
|
||||
double delta = Math.Abs(ts.TotalSeconds);
|
||||
|
||||
if (delta < 1 * MINUTE)
|
||||
{
|
||||
return lastLaunched + ProjectsEditor.Properties.Resources.Recently;
|
||||
}
|
||||
|
||||
if (delta < 2 * MINUTE)
|
||||
{
|
||||
return lastLaunched + ProjectsEditor.Properties.Resources.OneMinuteAgo;
|
||||
}
|
||||
|
||||
if (delta < 45 * MINUTE)
|
||||
{
|
||||
return lastLaunched + ts.Minutes + " " + ProjectsEditor.Properties.Resources.MinutesAgo;
|
||||
}
|
||||
|
||||
if (delta < 90 * MINUTE)
|
||||
{
|
||||
return lastLaunched + ProjectsEditor.Properties.Resources.OneHourAgo;
|
||||
}
|
||||
|
||||
if (delta < 24 * HOUR)
|
||||
{
|
||||
return lastLaunched + ts.Hours + " " + ProjectsEditor.Properties.Resources.HoursAgo;
|
||||
}
|
||||
|
||||
if (delta < 48 * HOUR)
|
||||
{
|
||||
return lastLaunched + ProjectsEditor.Properties.Resources.Yesterday;
|
||||
}
|
||||
|
||||
if (delta < 30 * DAY)
|
||||
{
|
||||
return lastLaunched + ts.Days + " " + ProjectsEditor.Properties.Resources.DaysAgo;
|
||||
}
|
||||
|
||||
if (delta < 12 * MONTH)
|
||||
{
|
||||
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
|
||||
return lastLaunched + (months <= 1 ? ProjectsEditor.Properties.Resources.OneMonthAgo : months + " " + ProjectsEditor.Properties.Resources.MonthsAgo);
|
||||
}
|
||||
else
|
||||
{
|
||||
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
|
||||
return lastLaunched + (years <= 1 ? ProjectsEditor.Properties.Resources.OneYearAgo : years + " " + ProjectsEditor.Properties.Resources.YearsAgo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isPopoupVisible;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsPopupVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isPopoupVisible;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_isPopoupVisible = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsPopupVisible)));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Application> Applications { get; set; }
|
||||
|
||||
public List<object> ApplicationsListed
|
||||
{
|
||||
get
|
||||
{
|
||||
List<object> applicationsListed = new List<object>();
|
||||
ILookup<MonitorSetup, Application> apps = Applications.Where(x => !x.Minimized).ToLookup(x => x.MonitorSetup);
|
||||
foreach (var appItem in apps.OrderBy(x => x.Key.MonitorDpiUnawareBounds.Left).ThenBy(x => x.Key.MonitorDpiUnawareBounds.Top))
|
||||
{
|
||||
applicationsListed.Add(appItem.Key.MonitorInfo);
|
||||
foreach (Application app in appItem)
|
||||
{
|
||||
applicationsListed.Add(app);
|
||||
}
|
||||
}
|
||||
|
||||
var minimizedApps = Applications.Where(x => x.Minimized);
|
||||
if (minimizedApps.Any())
|
||||
{
|
||||
applicationsListed.Add("Minimized Apps");
|
||||
foreach (Application app in minimizedApps)
|
||||
{
|
||||
applicationsListed.Add(app);
|
||||
}
|
||||
}
|
||||
|
||||
return applicationsListed;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string AppsCountString
|
||||
{
|
||||
get
|
||||
{
|
||||
return Applications.Where(x => x.IsSelected).Count().ToString(CultureInfo.InvariantCulture) + " apps";
|
||||
}
|
||||
}
|
||||
|
||||
public List<MonitorSetup> Monitors { get; set; }
|
||||
|
||||
private BitmapImage _previewImage;
|
||||
|
||||
public Project(Project selectedProjeсt)
|
||||
{
|
||||
Name = selectedProjeсt.Name;
|
||||
PreviewImage = selectedProjeсt.PreviewImage;
|
||||
IsShortcutNeeded = selectedProjeсt.IsShortcutNeeded;
|
||||
|
||||
int screenIndex = 1;
|
||||
|
||||
Monitors = new List<MonitorSetup>();
|
||||
foreach (var item in selectedProjeсt.Monitors.OrderBy(x => x.MonitorDpiAwareBounds.Left).ThenBy(x => x.MonitorDpiAwareBounds.Top))
|
||||
{
|
||||
Monitors.Add(new MonitorSetup($"Screen {screenIndex}", item.MonitorInstanceId, item.MonitorNumber, item.Dpi, item.MonitorDpiAwareBounds, item.MonitorDpiUnawareBounds) { PreviewImage = item.PreviewImage });
|
||||
screenIndex++;
|
||||
}
|
||||
|
||||
Applications = new List<Application>();
|
||||
foreach (var item in selectedProjeсt.Applications)
|
||||
{
|
||||
Applications.Add(new Application()
|
||||
{
|
||||
Hwnd = item.Hwnd,
|
||||
AppPath = item.AppPath,
|
||||
AppTitle = item.AppTitle,
|
||||
CommandLineArguments = item.CommandLineArguments,
|
||||
Minimized = item.Minimized,
|
||||
Maximized = item.Maximized,
|
||||
IsSelected = item.IsSelected,
|
||||
MonitorNumber = item.MonitorNumber,
|
||||
Position = new Application.WindowPosition() { X = item.Position.X, Y = item.Position.Y, Height = item.Position.Height, Width = item.Position.Width },
|
||||
Parent = this,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Project()
|
||||
{
|
||||
}
|
||||
|
||||
public BitmapImage PreviewImage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previewImage;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_previewImage = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImage)));
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public async void Initialize()
|
||||
{
|
||||
PreviewImage = await Task.Run(() => DrawPreviewIcons());
|
||||
foreach (MonitorSetup monitor in Monitors)
|
||||
{
|
||||
System.Windows.Rect rect = monitor.MonitorDpiAwareBounds;
|
||||
monitor.PreviewImage = await Task.Run(() => DrawPreview(new Rectangle((int)rect.Left, (int)rect.Top, (int)(rect.Right - rect.Left), (int)(rect.Bottom - rect.Top))));
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapImage DrawPreviewIcons()
|
||||
{
|
||||
var selectedApps = Applications.Where(x => x.IsSelected);
|
||||
int appsCount = selectedApps.Count();
|
||||
if (appsCount == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap previewBitmap = new Bitmap(32 * appsCount, 24);
|
||||
using (Graphics graphics = Graphics.FromImage(previewBitmap))
|
||||
{
|
||||
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
int appIndex = 0;
|
||||
foreach (var app in selectedApps)
|
||||
{
|
||||
try
|
||||
{
|
||||
graphics.DrawIcon(app.Icon, new Rectangle(32 * appIndex, 0, 24, 24));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
appIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapImage DrawPreview(Rectangle bounds)
|
||||
{
|
||||
double scale = 0.1;
|
||||
int Scaled(double value)
|
||||
{
|
||||
return (int)(value * scale);
|
||||
}
|
||||
|
||||
Dictionary<string, int> repeatCounter = new Dictionary<string, int>();
|
||||
|
||||
var selectedApps = Applications.Where(x => x.IsSelected);
|
||||
foreach (Application app in selectedApps)
|
||||
{
|
||||
if (repeatCounter.TryGetValue(app.AppPath, out int value))
|
||||
{
|
||||
repeatCounter[app.AppPath] = ++value;
|
||||
}
|
||||
else
|
||||
{
|
||||
repeatCounter.Add(app.AppPath, 1);
|
||||
}
|
||||
|
||||
app.RepeatIndex = repeatCounter[app.AppPath];
|
||||
}
|
||||
|
||||
// remove those repeatIndexes, which are single 1-es (no repetions) by setting them to 0
|
||||
foreach (Application app in selectedApps.Where(x => repeatCounter[x.AppPath] == 1))
|
||||
{
|
||||
app.RepeatIndex = 0;
|
||||
}
|
||||
|
||||
foreach (Application app in Applications.Where(x => !x.IsSelected))
|
||||
{
|
||||
app.RepeatIndex = 0;
|
||||
}
|
||||
|
||||
// now that all repeat index values are set, update the repeat index strings on UI
|
||||
foreach (Application app in Applications)
|
||||
{
|
||||
app.OnPropertyChanged(new PropertyChangedEventArgs("RepeatIndexString"));
|
||||
}
|
||||
|
||||
Bitmap previewBitmap = new Bitmap(Scaled(bounds.Width), Scaled(bounds.Height * 1.2));
|
||||
using (Graphics g = Graphics.FromImage(previewBitmap))
|
||||
{
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
|
||||
g.Clear(Color.FromArgb(0, 0, 0, 0));
|
||||
Brush brush = new SolidBrush(Color.FromArgb(10, 255, 255, 255)); // TODO: set theme-related colors
|
||||
foreach (Application app in Applications.Where(x => x.IsSelected && !x.Minimized))
|
||||
{
|
||||
Rectangle rect = new Rectangle(Scaled(app.ScaledPosition.X - bounds.Left), Scaled(app.ScaledPosition.Y - bounds.Top), Scaled(app.ScaledPosition.Width), Scaled(app.ScaledPosition.Height));
|
||||
DrawWindow(g, brush, rect, app);
|
||||
}
|
||||
|
||||
Rectangle rectMinimized = new Rectangle(0, Scaled(bounds.Height), Scaled(bounds.Width), Scaled(bounds.Height * 0.2));
|
||||
DrawWindow(g, brush, rectMinimized, Applications.Where(x => x.IsSelected && x.Minimized));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
|
||||
private Rectangle GetCommonBounds()
|
||||
{
|
||||
double minX = Monitors.First().MonitorDpiUnawareBounds.Left;
|
||||
double minY = Monitors.First().MonitorDpiUnawareBounds.Top;
|
||||
double maxX = Monitors.First().MonitorDpiUnawareBounds.Right;
|
||||
double maxY = Monitors.First().MonitorDpiUnawareBounds.Bottom;
|
||||
foreach (var monitor in Monitors)
|
||||
{
|
||||
minX = Math.Min(minX, monitor.MonitorDpiUnawareBounds.Left);
|
||||
minY = Math.Min(minY, monitor.MonitorDpiUnawareBounds.Top);
|
||||
maxX = Math.Max(maxX, monitor.MonitorDpiUnawareBounds.Right);
|
||||
maxY = Math.Max(maxY, monitor.MonitorDpiUnawareBounds.Bottom);
|
||||
}
|
||||
|
||||
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
|
||||
}
|
||||
|
||||
public static GraphicsPath RoundedRect(Rectangle bounds)
|
||||
{
|
||||
int minorSize = Math.Min(bounds.Width, bounds.Height);
|
||||
int radius = (int)(minorSize / 8);
|
||||
|
||||
int diameter = radius * 2;
|
||||
Size size = new Size(diameter, diameter);
|
||||
Rectangle arc = new Rectangle(bounds.Location, size);
|
||||
GraphicsPath path = new GraphicsPath();
|
||||
|
||||
if (radius == 0)
|
||||
{
|
||||
path.AddRectangle(bounds);
|
||||
return path;
|
||||
}
|
||||
|
||||
// top left arc
|
||||
path.AddArc(arc, 180, 90);
|
||||
|
||||
// top right arc
|
||||
arc.X = bounds.Right - diameter;
|
||||
path.AddArc(arc, 270, 90);
|
||||
|
||||
// bottom right arc
|
||||
arc.Y = bounds.Bottom - diameter;
|
||||
path.AddArc(arc, 0, 90);
|
||||
|
||||
// bottom left arc
|
||||
arc.X = bounds.Left;
|
||||
path.AddArc(arc, 90, 90);
|
||||
|
||||
path.CloseFigure();
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void DrawWindow(Graphics graphics, Brush brush, Rectangle bounds, Application app)
|
||||
{
|
||||
if (graphics == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (brush == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (GraphicsPath path = RoundedRect(bounds))
|
||||
{
|
||||
if (app.IsHighlighted)
|
||||
{
|
||||
graphics.DrawPath(new Pen(Color.White, graphics.VisibleClipBounds.Height / 25), path); // TODO: set theme-related colors
|
||||
}
|
||||
else
|
||||
{
|
||||
graphics.DrawPath(new Pen(Color.FromArgb(128, 82, 82, 82), graphics.VisibleClipBounds.Height / 100), path); // TODO: set theme-related colors
|
||||
}
|
||||
|
||||
graphics.FillPath(brush, path);
|
||||
}
|
||||
|
||||
double iconSize = Math.Min(bounds.Width, bounds.Height) * 0.3;
|
||||
Rectangle iconBounds = new Rectangle((int)(bounds.Left + (bounds.Width / 2) - (iconSize / 2)), (int)(bounds.Top + (bounds.Height / 2) - (iconSize / 2)), (int)iconSize, (int)iconSize);
|
||||
|
||||
try
|
||||
{
|
||||
graphics.DrawIcon(app.Icon, iconBounds);
|
||||
if (app.RepeatIndex > 0)
|
||||
{
|
||||
string indexString = app.RepeatIndex.ToString(CultureInfo.InvariantCulture);
|
||||
System.Drawing.Font font = new System.Drawing.Font("Tahoma", 8);
|
||||
int indexSize = (int)(iconBounds.Width * 0.5);
|
||||
Rectangle indexBounds = new Rectangle(iconBounds.Right - indexSize, iconBounds.Bottom - indexSize, indexSize, indexSize);
|
||||
|
||||
var textSize = graphics.MeasureString(indexString, font);
|
||||
var state = graphics.Save();
|
||||
graphics.TranslateTransform(indexBounds.Left, indexBounds.Top);
|
||||
graphics.ScaleTransform(indexBounds.Width / textSize.Width, indexBounds.Height / textSize.Height);
|
||||
graphics.DrawString(indexString, font, Brushes.Black, PointF.Empty);
|
||||
graphics.Restore(state);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// sometimes drawing an icon throws an exception despite that the icon seems to be ok
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawWindow(Graphics graphics, Brush brush, Rectangle bounds, IEnumerable<Application> apps)
|
||||
{
|
||||
int appsCount = apps.Count();
|
||||
if (appsCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (graphics == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (brush == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (GraphicsPath path = RoundedRect(bounds))
|
||||
{
|
||||
if (apps.Where(x => x.IsHighlighted).Any())
|
||||
{
|
||||
graphics.DrawPath(new Pen(Color.White, graphics.VisibleClipBounds.Height / 25), path);
|
||||
}
|
||||
else
|
||||
{
|
||||
graphics.DrawPath(new Pen(Color.FromArgb(128, 82, 82, 82), graphics.VisibleClipBounds.Height / 100), path);
|
||||
}
|
||||
|
||||
graphics.FillPath(brush, path);
|
||||
}
|
||||
|
||||
double iconSize = Math.Min(bounds.Width, bounds.Height) * 0.5;
|
||||
for (int iconCounter = 0; iconCounter < appsCount; iconCounter++)
|
||||
{
|
||||
Application app = apps.ElementAt(iconCounter);
|
||||
Rectangle iconBounds = new Rectangle((int)(bounds.Left + (bounds.Width / 2) - (iconSize * ((appsCount / 2) - iconCounter))), (int)(bounds.Top + (bounds.Height / 2) - (iconSize / 2)), (int)iconSize, (int)iconSize);
|
||||
|
||||
try
|
||||
{
|
||||
graphics.DrawIcon(app.Icon, iconBounds);
|
||||
if (app.RepeatIndex > 0)
|
||||
{
|
||||
string indexString = app.RepeatIndex.ToString(CultureInfo.InvariantCulture);
|
||||
System.Drawing.Font font = new System.Drawing.Font("Tahoma", 8);
|
||||
int indexSize = (int)(iconBounds.Width * 0.5);
|
||||
Rectangle indexBounds = new Rectangle(iconBounds.Right - indexSize, iconBounds.Bottom - indexSize, indexSize, indexSize);
|
||||
|
||||
var textSize = graphics.MeasureString(indexString, font);
|
||||
var state = graphics.Save();
|
||||
graphics.TranslateTransform(indexBounds.Left, indexBounds.Top);
|
||||
graphics.ScaleTransform(indexBounds.Width / textSize.Width, indexBounds.Height / textSize.Height);
|
||||
graphics.DrawString(indexString, font, Brushes.Black, PointF.Empty);
|
||||
graphics.Restore(state);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// sometimes drawing an icon throws an exception despite that the icon seems to be ok
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
317
src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml
Normal file
317
src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml
Normal file
@@ -0,0 +1,317 @@
|
||||
<Page x:Class="ProjectsEditor.ProjectEditor"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:props="clr-namespace:ProjectsEditor.Properties"
|
||||
xmlns:local="clr-namespace:ProjectsEditor"
|
||||
xmlns:models="clr-namespace:ProjectsEditor.Models"
|
||||
mc:Ignorable="d"
|
||||
Title="Project Editor"
|
||||
Background="{DynamicResource PrimaryBackgroundBrush}">
|
||||
<Page.Resources>
|
||||
<Style TargetType="{x:Type CheckBox}">
|
||||
<Setter Property="Background" Value="{DynamicResource TertiaryBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource SecondaryBorderBrush}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource SecondaryForegroundBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type CheckBox}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" >
|
||||
<Border BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" Width="15" Height="15">
|
||||
<Grid>
|
||||
<Grid Background="{TemplateBinding Foreground}" Margin="1" Visibility="Collapsed" Name="nullBlock"/>
|
||||
<Path Stretch="Uniform" Width="15" Height="10" Fill="{TemplateBinding Foreground}" Name="eliCheck" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Visibility="Collapsed"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<TextBlock Margin="5,0,0,0" VerticalAlignment="Center" Foreground="{DynamicResource PrimaryForegroundBrush}" Text="{TemplateBinding Content}"></TextBlock>
|
||||
</StackPanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryBorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryForegroundBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="eliCheck" Property="Visibility" Value="Visible"></Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="{x:Null}">
|
||||
<Setter TargetName="nullBlock" Property="Visibility" Value="Visible"></Setter>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="#9d0808" />
|
||||
<Setter Property="BorderBrush" Value="#FF262E34"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}" BorderBrush="#FF262E34" BorderThickness="1">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#ec4d37"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#9d0808"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<DataTemplate x:Key="headerTemplate">
|
||||
<Border>
|
||||
<TextBlock
|
||||
Text="{Binding .}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
FontSize="14"
|
||||
FontWeight="Normal"
|
||||
Margin="0,20,20,5"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="appTemplate">
|
||||
<Border
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
MouseEnter="AppBorder_MouseEnter"
|
||||
MouseLeave="AppBorder_MouseLeave">
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="10"
|
||||
Source="{Binding IconBitmapImage}"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Text="{Binding RepeatIndexString, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
FontSize="14"
|
||||
FontWeight="Normal"
|
||||
Width="20"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Text="{Binding AppName}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
FontSize="14"
|
||||
FontWeight="Normal"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox
|
||||
x:Name="CommandLineTextBox"
|
||||
Grid.Column="3"
|
||||
Text="{Binding CommandLineArguments, Mode=TwoWay}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
BorderThickness="0"
|
||||
FontSize="14"
|
||||
FontWeight="Normal"
|
||||
VerticalContentAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
IsHitTestVisible="False"
|
||||
Text="{x:Static props:Resources.WriteArgs}"
|
||||
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||
FontSize="14"
|
||||
FontWeight="Normal"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,12,0">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Text, ElementName=CommandLineTextBox}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<CheckBox
|
||||
Grid.Column="4"
|
||||
IsChecked="{Binding IsSelected, Mode=TwoWay}"
|
||||
Checked="CheckBox_Checked"
|
||||
Unchecked="CheckBox_Checked"
|
||||
Margin="10"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
<models:AppListDataTemplateSelector
|
||||
HeaderTemplate="{StaticResource headerTemplate}"
|
||||
AppTemplate="{StaticResource appTemplate}"
|
||||
x:Key="AppListDataTemplateSelector"/>
|
||||
</Page.Resources>
|
||||
<Grid Margin="40,0,40,40">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||
<TextBlock Text="{x:Static props:Resources.Projects}" FontSize="24" FontWeight="Normal" Margin="0,20,0,20" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<TextBlock
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||
FontSize="16"
|
||||
Margin="10,30,0,20"
|
||||
Text="" />
|
||||
<TextBlock Text="{Binding EditorWindowTitle}" FontSize="24" FontWeight="SemiBold" Margin="10,20,0,20" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Orientation="Vertical">
|
||||
<TextBlock Text="{x:Static props:Resources.ProjectName}" FontSize="14" FontWeight="Normal" Foreground="{DynamicResource PrimaryForegroundBrush}"/>
|
||||
<TextBox
|
||||
x:Name="EditNameTextBox"
|
||||
Width="320"
|
||||
Text="{Binding Name, Mode=TwoWay}"
|
||||
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||
BorderThickness="2"
|
||||
Margin="0,6,0,6"
|
||||
HorizontalAlignment="Left"
|
||||
GotFocus="EditNameTextBox_GotFocus"
|
||||
KeyDown="EditNameTextBoxKeyDown" />
|
||||
</StackPanel>
|
||||
<ScrollViewer
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Grid.Row="2">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource MonitorViewBackgroundBrush}"
|
||||
CornerRadius="5">
|
||||
<ScrollViewer
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Monitors, Mode=OneWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel
|
||||
IsItemsHost="True"
|
||||
Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="models:MonitorSetup">
|
||||
<Grid
|
||||
Margin="20,20,20,20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="{Binding MonitorInfoWithResolution}" Foreground="{DynamicResource PrimaryForegroundBrush}" FontSize="16" FontWeight="Normal" Margin="0,5,0,5"/>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
CornerRadius="5"
|
||||
BorderBrush="{DynamicResource TertiaryBackgroundBrush}"
|
||||
BorderThickness="2"
|
||||
Margin="0,0,0,10">
|
||||
<Image
|
||||
Width="200"
|
||||
Height="140"
|
||||
Source="{Binding PreviewImage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Stretch="Fill"
|
||||
Margin="2"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding ApplicationsListed, Mode=OneWay}"
|
||||
ItemTemplateSelector="{StaticResource AppListDataTemplateSelector}">
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<DockPanel Grid.Row="3" Margin="0,20,0,20">
|
||||
<CheckBox
|
||||
DockPanel.Dock="Left"
|
||||
Content="{x:Static props:Resources.CreateShortcut}"
|
||||
IsChecked="{Binding IsShortcutNeeded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="14"/>
|
||||
<StackPanel
|
||||
DockPanel.Dock="Right"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="40,0,0,0">
|
||||
<Button
|
||||
x:Name="CancelButton"
|
||||
Margin="20,0,0,0"
|
||||
Height="36"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
|
||||
Click="CancelButtonClicked">
|
||||
<StackPanel Orientation="Horizontal" Margin="12, 2, 12, 0" >
|
||||
<TextBlock
|
||||
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="" />
|
||||
<TextBlock
|
||||
Margin="12,-4,0,0"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="{x:Static props:Resources.Cancel}" />
|
||||
</StackPanel>
|
||||
<Button.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="6"
|
||||
Opacity="0.32"
|
||||
ShadowDepth="1" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="SaveButton"
|
||||
Margin="20,0,0,0"
|
||||
Height="36"
|
||||
AutomationProperties.Name="{x:Static props:Resources.Save_project}"
|
||||
Click="SaveButtonClicked"
|
||||
Style="{StaticResource AccentButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Margin="12, 2, 12, 0" >
|
||||
<TextBlock
|
||||
AutomationProperties.Name="{x:Static props:Resources.Save_project}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="" />
|
||||
<TextBlock
|
||||
Margin="12,-4,0,0"
|
||||
Foreground="{DynamicResource AccentButtonForeground}"
|
||||
Text="{x:Static props:Resources.Save_project}" />
|
||||
</StackPanel>
|
||||
<Button.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="6"
|
||||
Opacity="0.32"
|
||||
ShadowDepth="1" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using ProjectsEditor.Models;
|
||||
using ProjectsEditor.ViewModels;
|
||||
|
||||
namespace ProjectsEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ProjectEditor.xaml
|
||||
/// </summary>
|
||||
public partial class ProjectEditor : Page
|
||||
{
|
||||
private MainViewModel _mainViewModel;
|
||||
|
||||
public ProjectEditor(MainViewModel mainViewModel)
|
||||
{
|
||||
_mainViewModel = mainViewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CheckBox_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckBox checkBox = sender as CheckBox;
|
||||
Models.Application application = checkBox.DataContext as Models.Application;
|
||||
Models.Project project = application.Parent;
|
||||
project.Initialize();
|
||||
}
|
||||
|
||||
private void SaveButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Project projectToSave = this.DataContext as Project;
|
||||
_mainViewModel.SaveProject(projectToSave);
|
||||
_mainViewModel.SwitchToMainView();
|
||||
}
|
||||
|
||||
private void CancelButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_mainViewModel.CancelLastEdit();
|
||||
_mainViewModel.SwitchToMainView();
|
||||
}
|
||||
|
||||
private void EditNameTextBoxKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
e.Handled = true;
|
||||
Project project = this.DataContext as Project;
|
||||
project.Name = EditNameTextBox.Text;
|
||||
}
|
||||
else if (e.Key == Key.Escape)
|
||||
{
|
||||
e.Handled = true;
|
||||
Project project = this.DataContext as Project;
|
||||
_mainViewModel.CancelProjectName(project);
|
||||
}
|
||||
}
|
||||
|
||||
private void EditNameTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_mainViewModel.SaveProjectName(DataContext as Project);
|
||||
}
|
||||
|
||||
private void AppBorder_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
Border border = sender as Border;
|
||||
Models.Application app = border.DataContext as Models.Application;
|
||||
app.IsHighlighted = true;
|
||||
Project project = app.Parent;
|
||||
project.Initialize();
|
||||
}
|
||||
|
||||
private void AppBorder_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
Border border = sender as Border;
|
||||
Models.Application app = border.DataContext as Models.Application;
|
||||
app.IsHighlighted = false;
|
||||
Project project = app.Parent;
|
||||
project.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/modules/Projects/ProjectsEditor/ProjectsEditor.csproj
Normal file
105
src/modules/Projects/ProjectsEditor/ProjectsEditor.csproj
Normal file
@@ -0,0 +1,105 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>ProjectsEditor</AssemblyTitle>
|
||||
<AssemblyDescription>ProjectsEditor</AssemblyDescription>
|
||||
<Description>ProjectsEditor</Description>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<OutputPath>..\$(Platform)\$(Configuration)</OutputPath>
|
||||
<SelfContained>true</SelfContained>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- SelfContained=true requires RuntimeIdentifier to be set -->
|
||||
<PropertyGroup Condition="'$(Platform)'=='x64'">
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)'=='ARM64'">
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}</ProjectGuid>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>images\Projects.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AssemblyName>ProjectsEditor</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="images\DefaultIcon.ico" />
|
||||
<None Remove="images\Projects.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="IWshRuntimeLibrary">
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>f935dc20-1cf0-11d0-adb9-00c04fd58a0b</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="images\DefaultIcon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="images\Projects.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="app.manifest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ControlzEx" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" />
|
||||
<PackageReference Include="ModernWpfUI" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
432
src/modules/Projects/ProjectsEditor/Properties/Resources.Designer.cs
generated
Normal file
432
src/modules/Projects/ProjectsEditor/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,432 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ProjectsEditor.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ProjectsEditor.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to App name.
|
||||
/// </summary>
|
||||
public static string App_name {
|
||||
get {
|
||||
return ResourceManager.GetString("App_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure?.
|
||||
/// </summary>
|
||||
public static string Are_You_Sure {
|
||||
get {
|
||||
return ResourceManager.GetString("Are_You_Sure", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure you want to delete this project?.
|
||||
/// </summary>
|
||||
public static string Are_You_Sure_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Are_You_Sure_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cancel.
|
||||
/// </summary>
|
||||
public static string Cancel {
|
||||
get {
|
||||
return ResourceManager.GetString("Cancel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Created.
|
||||
/// </summary>
|
||||
public static string Created {
|
||||
get {
|
||||
return ResourceManager.GetString("Created", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Create project.
|
||||
/// </summary>
|
||||
public static string CreateProject {
|
||||
get {
|
||||
return ResourceManager.GetString("CreateProject", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Create Shortcut.
|
||||
/// </summary>
|
||||
public static string CreateShortcut {
|
||||
get {
|
||||
return ResourceManager.GetString("CreateShortcut", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to days ago.
|
||||
/// </summary>
|
||||
public static string DaysAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("DaysAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove.
|
||||
/// </summary>
|
||||
public static string Delete {
|
||||
get {
|
||||
return ResourceManager.GetString("Delete", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delete project dialog..
|
||||
/// </summary>
|
||||
public static string Delete_Project_Dialog_Announce {
|
||||
get {
|
||||
return ResourceManager.GetString("Delete_Project_Dialog_Announce", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit.
|
||||
/// </summary>
|
||||
public static string Edit {
|
||||
get {
|
||||
return ResourceManager.GetString("Edit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to opened.
|
||||
/// </summary>
|
||||
public static string Edit_Project_Open_Announce {
|
||||
get {
|
||||
return ResourceManager.GetString("Edit_Project_Open_Announce", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit project.
|
||||
/// </summary>
|
||||
public static string EditProject {
|
||||
get {
|
||||
return ResourceManager.GetString("EditProject", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error parsing projects data..
|
||||
/// </summary>
|
||||
public static string Error_Parsing_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("Error_Parsing_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to hours ago.
|
||||
/// </summary>
|
||||
public static string HoursAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("HoursAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Last launched.
|
||||
/// </summary>
|
||||
public static string LastLaunched {
|
||||
get {
|
||||
return ResourceManager.GetString("LastLaunched", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch.
|
||||
/// </summary>
|
||||
public static string Launch {
|
||||
get {
|
||||
return ResourceManager.GetString("Launch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch args.
|
||||
/// </summary>
|
||||
public static string Launch_args {
|
||||
get {
|
||||
return ResourceManager.GetString("Launch_args", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Projects demo app.
|
||||
/// </summary>
|
||||
public static string MainTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("MainTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to minutes ago.
|
||||
/// </summary>
|
||||
public static string MinutesAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("MinutesAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to months ago.
|
||||
/// </summary>
|
||||
public static string MonthsAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("MonthsAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name.
|
||||
/// </summary>
|
||||
public static string Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to never.
|
||||
/// </summary>
|
||||
public static string Never {
|
||||
get {
|
||||
return ResourceManager.GetString("Never", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New project.
|
||||
/// </summary>
|
||||
public static string New_project {
|
||||
get {
|
||||
return ResourceManager.GetString("New_project", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There are no saved projects..
|
||||
/// </summary>
|
||||
public static string No_Projects_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("No_Projects_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to an hour ago.
|
||||
/// </summary>
|
||||
public static string OneHourAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneHourAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to a minute ago.
|
||||
/// </summary>
|
||||
public static string OneMinuteAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneMinuteAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to one month ago.
|
||||
/// </summary>
|
||||
public static string OneMonthAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneMonthAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to one second ago.
|
||||
/// </summary>
|
||||
public static string OneSecondAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneSecondAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to one year ago.
|
||||
/// </summary>
|
||||
public static string OneYearAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("OneYearAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pin Project to Taskbar.
|
||||
/// </summary>
|
||||
public static string PinToTaskbar {
|
||||
get {
|
||||
return ResourceManager.GetString("PinToTaskbar", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name project.
|
||||
/// </summary>
|
||||
public static string ProjectName {
|
||||
get {
|
||||
return ResourceManager.GetString("ProjectName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Projects.
|
||||
/// </summary>
|
||||
public static string Projects {
|
||||
get {
|
||||
return ResourceManager.GetString("Projects", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to recently.
|
||||
/// </summary>
|
||||
public static string Recently {
|
||||
get {
|
||||
return ResourceManager.GetString("Recently", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save project.
|
||||
/// </summary>
|
||||
public static string Save_project {
|
||||
get {
|
||||
return ResourceManager.GetString("Save_project", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search.
|
||||
/// </summary>
|
||||
public static string Search {
|
||||
get {
|
||||
return ResourceManager.GetString("Search", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to seconds ago.
|
||||
/// </summary>
|
||||
public static string SecondsAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("SecondsAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Sort by.
|
||||
/// </summary>
|
||||
public static string SortBy {
|
||||
get {
|
||||
return ResourceManager.GetString("SortBy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Write arguments here.
|
||||
/// </summary>
|
||||
public static string WriteArgs {
|
||||
get {
|
||||
return ResourceManager.GetString("WriteArgs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to years ago.
|
||||
/// </summary>
|
||||
public static string YearsAgo {
|
||||
get {
|
||||
return ResourceManager.GetString("YearsAgo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to yesterday.
|
||||
/// </summary>
|
||||
public static string Yesterday {
|
||||
get {
|
||||
return ResourceManager.GetString("Yesterday", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
243
src/modules/Projects/ProjectsEditor/Properties/Resources.resx
Normal file
243
src/modules/Projects/ProjectsEditor/Properties/Resources.resx
Normal file
@@ -0,0 +1,243 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="App_name" xml:space="preserve">
|
||||
<value>App name</value>
|
||||
</data>
|
||||
<data name="Are_You_Sure" xml:space="preserve">
|
||||
<value>Are you sure?</value>
|
||||
</data>
|
||||
<data name="Are_You_Sure_Description" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this project?</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Created" xml:space="preserve">
|
||||
<value>Created</value>
|
||||
</data>
|
||||
<data name="CreateProject" xml:space="preserve">
|
||||
<value>Create project</value>
|
||||
</data>
|
||||
<data name="CreateShortcut" xml:space="preserve">
|
||||
<value>Create Shortcut</value>
|
||||
</data>
|
||||
<data name="DaysAgo" xml:space="preserve">
|
||||
<value>days ago</value>
|
||||
</data>
|
||||
<data name="Delete" xml:space="preserve">
|
||||
<value>Remove</value>
|
||||
</data>
|
||||
<data name="Delete_Project_Dialog_Announce" xml:space="preserve">
|
||||
<value>Delete project dialog.</value>
|
||||
</data>
|
||||
<data name="Edit" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="EditProject" xml:space="preserve">
|
||||
<value>Edit project</value>
|
||||
</data>
|
||||
<data name="Edit_Project_Open_Announce" xml:space="preserve">
|
||||
<value>opened</value>
|
||||
</data>
|
||||
<data name="Error_Parsing_Message" xml:space="preserve">
|
||||
<value>Error parsing projects data.</value>
|
||||
</data>
|
||||
<data name="HoursAgo" xml:space="preserve">
|
||||
<value>hours ago</value>
|
||||
</data>
|
||||
<data name="LastLaunched" xml:space="preserve">
|
||||
<value>Last launched</value>
|
||||
</data>
|
||||
<data name="Launch" xml:space="preserve">
|
||||
<value>Launch</value>
|
||||
</data>
|
||||
<data name="Launch_args" xml:space="preserve">
|
||||
<value>Launch args</value>
|
||||
</data>
|
||||
<data name="MainTitle" xml:space="preserve">
|
||||
<value>Projects demo app</value>
|
||||
</data>
|
||||
<data name="MinutesAgo" xml:space="preserve">
|
||||
<value>minutes ago</value>
|
||||
</data>
|
||||
<data name="MonthsAgo" xml:space="preserve">
|
||||
<value>months ago</value>
|
||||
</data>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Never" xml:space="preserve">
|
||||
<value>never</value>
|
||||
</data>
|
||||
<data name="New_project" xml:space="preserve">
|
||||
<value>New project</value>
|
||||
</data>
|
||||
<data name="No_Projects_Message" xml:space="preserve">
|
||||
<value>There are no saved projects.</value>
|
||||
</data>
|
||||
<data name="OneHourAgo" xml:space="preserve">
|
||||
<value>an hour ago</value>
|
||||
</data>
|
||||
<data name="OneMinuteAgo" xml:space="preserve">
|
||||
<value>a minute ago</value>
|
||||
</data>
|
||||
<data name="OneMonthAgo" xml:space="preserve">
|
||||
<value>one month ago</value>
|
||||
</data>
|
||||
<data name="OneSecondAgo" xml:space="preserve">
|
||||
<value>one second ago</value>
|
||||
</data>
|
||||
<data name="OneYearAgo" xml:space="preserve">
|
||||
<value>one year ago</value>
|
||||
</data>
|
||||
<data name="PinToTaskbar" xml:space="preserve">
|
||||
<value>Pin Project to Taskbar</value>
|
||||
</data>
|
||||
<data name="ProjectName" xml:space="preserve">
|
||||
<value>Name project</value>
|
||||
</data>
|
||||
<data name="Projects" xml:space="preserve">
|
||||
<value>Projects</value>
|
||||
</data>
|
||||
<data name="Recently" xml:space="preserve">
|
||||
<value>recently</value>
|
||||
</data>
|
||||
<data name="Save_project" xml:space="preserve">
|
||||
<value>Save project</value>
|
||||
</data>
|
||||
<data name="Search" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
<data name="SecondsAgo" xml:space="preserve">
|
||||
<value>seconds ago</value>
|
||||
</data>
|
||||
<data name="SortBy" xml:space="preserve">
|
||||
<value>Sort by</value>
|
||||
</data>
|
||||
<data name="WriteArgs" xml:space="preserve">
|
||||
<value>Write arguments here</value>
|
||||
</data>
|
||||
<data name="YearsAgo" xml:space="preserve">
|
||||
<value>years ago</value>
|
||||
</data>
|
||||
<data name="Yesterday" xml:space="preserve">
|
||||
<value>yesterday</value>
|
||||
</data>
|
||||
</root>
|
||||
26
src/modules/Projects/ProjectsEditor/Properties/Settings.Designer.cs
generated
Normal file
26
src/modules/Projects/ProjectsEditor/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ProjectsEditor.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
55
src/modules/Projects/ProjectsEditor/Styles/ButtonStyles.xaml
Normal file
55
src/modules/Projects/ProjectsEditor/Styles/ButtonStyles.xaml
Normal file
@@ -0,0 +1,55 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019">
|
||||
|
||||
<Style
|
||||
x:Key="IconOnlyButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Background"
|
||||
Background="Transparent"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Border>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
25
src/modules/Projects/ProjectsEditor/Themes/Dark.xaml
Normal file
25
src/modules/Projects/ProjectsEditor/Themes/Dark.xaml
Normal file
@@ -0,0 +1,25 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">Dark.Accent1</system:String>
|
||||
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent1 (Dark)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">Dark</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent1</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">Black</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF2b2b2b" />
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF373737" />
|
||||
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFFFFFFF" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="BackdropBrush" Color="#40F0F0F0" />
|
||||
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,25 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent2</system:String>
|
||||
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent2 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent2</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF1c1c1c" />
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffff00" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF00ff00" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="BackdropBrush" Color="#E55B5B5B" />
|
||||
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,25 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent3</system:String>
|
||||
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent3 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent3</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF1c1c1c" />
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffff00" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FFc0c0c0" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="BackdropBrush" Color="#E55B5B5B" />
|
||||
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,25 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent4</system:String>
|
||||
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent4 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent4</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF1c1c1c" />
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffffff" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF1aebff" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="BackdropBrush" Color="#E55B5B5B" />
|
||||
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,25 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent5</system:String>
|
||||
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent5 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent5</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FFf9f9f9" />
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FFeeeeee" />
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FFF3F3F3" />
|
||||
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF000000" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF37006e" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||
<SolidColorBrush x:Key="BackdropBrush" Color="#E5949494" />
|
||||
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF949494" />
|
||||
</ResourceDictionary>
|
||||
25
src/modules/Projects/ProjectsEditor/Themes/Light.xaml
Normal file
25
src/modules/Projects/ProjectsEditor/Themes/Light.xaml
Normal file
@@ -0,0 +1,25 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">Light.Accent1</system:String>
|
||||
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent1 (Light)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">Light</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent1</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FFF9F9F9" />
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FFeeeeee" />
|
||||
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FFF3F3F3" />
|
||||
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FFF9F9F9" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF000000" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF6A6A6A" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FFe5e5e5" />
|
||||
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FFe5e5e5" />
|
||||
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FFe5e5e5" />
|
||||
<SolidColorBrush x:Key="BackdropBrush" Color="#85F0F0F0" />
|
||||
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF949494" />
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Text.Json;
|
||||
|
||||
namespace ProjectsEditor.Utils
|
||||
{
|
||||
public class DashCaseNamingPolicy : JsonNamingPolicy
|
||||
{
|
||||
public static DashCaseNamingPolicy Instance { get; } = new DashCaseNamingPolicy();
|
||||
|
||||
public override string ConvertName(string name)
|
||||
{
|
||||
return name.UpperCamelCaseToDashCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/modules/Projects/ProjectsEditor/Utils/IOUtils.cs
Normal file
54
src/modules/Projects/ProjectsEditor/Utils/IOUtils.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProjectsEditor.Utils
|
||||
{
|
||||
public class IOUtils
|
||||
{
|
||||
private readonly IFileSystem _fileSystem = new FileSystem();
|
||||
|
||||
public IOUtils()
|
||||
{
|
||||
}
|
||||
|
||||
public void WriteFile(string fileName, string data)
|
||||
{
|
||||
_fileSystem.File.WriteAllText(fileName, data);
|
||||
}
|
||||
|
||||
public string ReadFile(string fileName)
|
||||
{
|
||||
if (_fileSystem.File.Exists(fileName))
|
||||
{
|
||||
var attempts = 0;
|
||||
while (attempts < 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Stream inputStream = _fileSystem.File.Open(fileName, FileMode.Open))
|
||||
using (StreamReader reader = new StreamReader(inputStream))
|
||||
{
|
||||
string data = reader.ReadToEnd();
|
||||
inputStream.Close();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Task.Delay(10).Wait();
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/modules/Projects/ProjectsEditor/Utils/NativeMethods.cs
Normal file
41
src/modules/Projects/ProjectsEditor/Utils/NativeMethods.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ProjectsEditor.Utils
|
||||
{
|
||||
internal sealed class NativeMethods
|
||||
{
|
||||
public const int SW_RESTORE = 9;
|
||||
public const int SW_NORMAL = 1;
|
||||
public const int SW_MINIMIZE = 6;
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
|
||||
|
||||
[DllImport("USER32.DLL")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
||||
}
|
||||
}
|
||||
22
src/modules/Projects/ProjectsEditor/Utils/ParsingResult.cs
Normal file
22
src/modules/Projects/ProjectsEditor/Utils/ParsingResult.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// 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 ProjectsEditor.Utils
|
||||
{
|
||||
public struct ParsingResult
|
||||
{
|
||||
public bool Result { get; }
|
||||
|
||||
public string Message { get; }
|
||||
|
||||
public string MalformedData { get; }
|
||||
|
||||
public ParsingResult(bool result, string message = "", string data = "")
|
||||
{
|
||||
Result = result;
|
||||
Message = message;
|
||||
MalformedData = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
233
src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs
Normal file
233
src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
// 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.Windows;
|
||||
using ProjectsEditor.Data;
|
||||
using ProjectsEditor.Models;
|
||||
using ProjectsEditor.ViewModels;
|
||||
|
||||
namespace ProjectsEditor.Utils
|
||||
{
|
||||
public class ProjectsEditorIO
|
||||
{
|
||||
public ProjectsEditorIO()
|
||||
{
|
||||
}
|
||||
|
||||
public ParsingResult ParseProjects(MainViewModel mainViewModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProjectsData parser = new ProjectsData();
|
||||
if (!File.Exists(parser.File))
|
||||
{
|
||||
return new ParsingResult(true);
|
||||
}
|
||||
|
||||
ProjectsData.ProjectsListWrapper projects = parser.Read(parser.File);
|
||||
if (!SetProjects(mainViewModel, projects))
|
||||
{
|
||||
return new ParsingResult(false, ProjectsEditor.Properties.Resources.Error_Parsing_Message);
|
||||
}
|
||||
|
||||
return new ParsingResult(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ParsingResult(false, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public ParsingResult ParseProject(string fileName, out Project project)
|
||||
{
|
||||
project = null;
|
||||
try
|
||||
{
|
||||
ProjectsData parser = new ProjectsData();
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
return new ParsingResult(true);
|
||||
}
|
||||
|
||||
ProjectsData.ProjectsListWrapper projects = parser.Read(fileName);
|
||||
if (!ExtractProject(projects, out project))
|
||||
{
|
||||
return new ParsingResult(false, ProjectsEditor.Properties.Resources.Error_Parsing_Message);
|
||||
}
|
||||
|
||||
return new ParsingResult(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ParsingResult(false, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
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 = GetProjectFromWrappper(projectWrapper);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Project GetProjectFromWrappper(ProjectsData.ProjectWrapper project)
|
||||
{
|
||||
Project newProject = new Project()
|
||||
{
|
||||
Id = project.Id,
|
||||
Name = project.Name,
|
||||
CreationTime = project.CreationTime,
|
||||
LastLaunchedTime = project.LastLaunchedTime,
|
||||
IsShortcutNeeded = project.IsShortcutNeeded,
|
||||
Monitors = new List<MonitorSetup>() { },
|
||||
Applications = new List<Models.Application> { },
|
||||
};
|
||||
|
||||
foreach (var app in project.Applications)
|
||||
{
|
||||
newProject.Applications.Add(new Models.Application()
|
||||
{
|
||||
Hwnd = new IntPtr(app.Hwnd),
|
||||
AppPath = app.Application,
|
||||
AppTitle = app.Title,
|
||||
Parent = newProject,
|
||||
CommandLineArguments = app.CommandLineArguments,
|
||||
Maximized = app.Maximized,
|
||||
Minimized = app.Minimized,
|
||||
IsSelected = true,
|
||||
Position = new Models.Application.WindowPosition()
|
||||
{
|
||||
Height = app.Position.Height,
|
||||
Width = app.Position.Width,
|
||||
X = app.Position.X,
|
||||
Y = app.Position.Y,
|
||||
},
|
||||
MonitorNumber = app.Monitor,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var monitor in project.MonitorConfiguration)
|
||||
{
|
||||
Rect dpiAware = new Rect(monitor.MonitorRectDpiAware.Left, monitor.MonitorRectDpiAware.Top, monitor.MonitorRectDpiAware.Width, monitor.MonitorRectDpiAware.Height);
|
||||
Rect dpiUnaware = new Rect(monitor.MonitorRectDpiUnaware.Left, monitor.MonitorRectDpiUnaware.Top, monitor.MonitorRectDpiUnaware.Width, monitor.MonitorRectDpiUnaware.Height);
|
||||
newProject.Monitors.Add(new MonitorSetup(monitor.Id, monitor.InstanceId, monitor.MonitorNumber, monitor.Dpi, dpiAware, dpiUnaware));
|
||||
}
|
||||
|
||||
return newProject;
|
||||
}
|
||||
|
||||
public void SerializeProjects(List<Project> projects)
|
||||
{
|
||||
ProjectsData serializer = new ProjectsData();
|
||||
ProjectsData.ProjectsListWrapper projectsWrapper = new ProjectsData.ProjectsListWrapper { };
|
||||
projectsWrapper.Projects = new List<ProjectsData.ProjectWrapper>();
|
||||
|
||||
foreach (Project project in projects)
|
||||
{
|
||||
ProjectsData.ProjectWrapper wrapper = new ProjectsData.ProjectWrapper
|
||||
{
|
||||
Id = project.Id,
|
||||
Name = project.Name,
|
||||
CreationTime = project.CreationTime,
|
||||
IsShortcutNeeded = project.IsShortcutNeeded,
|
||||
LastLaunchedTime = project.LastLaunchedTime,
|
||||
Applications = new List<ProjectsData.ApplicationWrapper> { },
|
||||
MonitorConfiguration = new List<ProjectsData.MonitorConfigurationWrapper> { },
|
||||
};
|
||||
|
||||
foreach (var app in project.Applications)
|
||||
{
|
||||
if (app.IsSelected)
|
||||
{
|
||||
wrapper.Applications.Add(new ProjectsData.ApplicationWrapper
|
||||
{
|
||||
Hwnd = app.Hwnd,
|
||||
Application = app.AppPath,
|
||||
Title = app.AppTitle,
|
||||
CommandLineArguments = app.CommandLineArguments,
|
||||
Maximized = app.Maximized,
|
||||
Minimized = app.Minimized,
|
||||
Position = new ProjectsData.ApplicationWrapper.WindowPositionWrapper
|
||||
{
|
||||
X = app.Position.X,
|
||||
Y = app.Position.Y,
|
||||
Height = app.Position.Height,
|
||||
Width = app.Position.Width,
|
||||
},
|
||||
Monitor = app.MonitorNumber,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var monitor in project.Monitors)
|
||||
{
|
||||
wrapper.MonitorConfiguration.Add(new ProjectsData.MonitorConfigurationWrapper
|
||||
{
|
||||
Id = monitor.MonitorName,
|
||||
InstanceId = monitor.MonitorInstanceId,
|
||||
MonitorNumber = monitor.MonitorNumber,
|
||||
Dpi = monitor.Dpi,
|
||||
MonitorRectDpiAware = new ProjectsData.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
|
||||
{
|
||||
Left = (int)monitor.MonitorDpiUnawareBounds.Left,
|
||||
Top = (int)monitor.MonitorDpiUnawareBounds.Top,
|
||||
Width = (int)monitor.MonitorDpiUnawareBounds.Width,
|
||||
Height = (int)monitor.MonitorDpiUnawareBounds.Height,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
projectsWrapper.Projects.Add(wrapper);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IOUtils ioUtils = new IOUtils();
|
||||
ioUtils.WriteFile(serializer.File, serializer.Serialize(projectsWrapper));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: show error
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddProjects(MainViewModel mainViewModel, ProjectsData.ProjectsListWrapper projects)
|
||||
{
|
||||
foreach (var project in projects.Projects)
|
||||
{
|
||||
mainViewModel.Projects.Add(GetProjectFromWrappper(project));
|
||||
}
|
||||
|
||||
mainViewModel.Initialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SetProjects(MainViewModel mainViewModel, ProjectsData.ProjectsListWrapper projects)
|
||||
{
|
||||
mainViewModel.Projects = new System.Collections.ObjectModel.ObservableCollection<Project> { };
|
||||
return AddProjects(mainViewModel, projects);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/modules/Projects/ProjectsEditor/Utils/StringUtils.cs
Normal file
22
src/modules/Projects/ProjectsEditor/Utils/StringUtils.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// 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.Linq;
|
||||
|
||||
namespace ProjectsEditor.Utils
|
||||
{
|
||||
public static class StringUtils
|
||||
{
|
||||
public static string UpperCamelCaseToDashCase(this string str)
|
||||
{
|
||||
// If it's single letter variable, leave it as it is
|
||||
if (str.Length == 1)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "-" + x.ToString() : x.ToString())).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
511
src/modules/Projects/ProjectsEditor/ViewModels/MainViewModel.cs
Normal file
511
src/modules/Projects/ProjectsEditor/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,511 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using ProjectsEditor.Models;
|
||||
using ProjectsEditor.Utils;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Management.Deployment;
|
||||
using static ProjectsEditor.Data.ProjectsData;
|
||||
|
||||
namespace ProjectsEditor.ViewModels
|
||||
{
|
||||
public class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private ProjectsEditorIO _projectsEditorIO;
|
||||
|
||||
public ObservableCollection<Project> Projects { get; set; }
|
||||
|
||||
public IEnumerable<Project> ProjectsView
|
||||
{
|
||||
get
|
||||
{
|
||||
IEnumerable<Project> projects = string.IsNullOrEmpty(_searchTerm) ? Projects : Projects.Where(x => x.Name.Contains(_searchTerm, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (projects == null)
|
||||
{
|
||||
return Enumerable.Empty<Project>();
|
||||
}
|
||||
|
||||
OrderBy orderBy = (OrderBy)_orderByIndex;
|
||||
if (orderBy == OrderBy.LastViewed)
|
||||
{
|
||||
return projects.OrderByDescending(x => x.LastLaunchedTime);
|
||||
}
|
||||
else if (orderBy == OrderBy.Created)
|
||||
{
|
||||
return projects.OrderByDescending(x => x.CreationTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
return projects.OrderBy(x => x.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _searchTerm;
|
||||
|
||||
public string SearchTerm
|
||||
{
|
||||
get => _searchTerm;
|
||||
set
|
||||
{
|
||||
_searchTerm = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
|
||||
}
|
||||
}
|
||||
|
||||
private int _orderByIndex;
|
||||
|
||||
public int OrderByIndex
|
||||
{
|
||||
get => _orderByIndex;
|
||||
set
|
||||
{
|
||||
_orderByIndex = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private Project editedProject;
|
||||
private bool isEditedProjectNewlyCreated;
|
||||
private string projectNameBeingEdited;
|
||||
private MainWindow _mainWindow;
|
||||
private System.Timers.Timer lastUpdatedTimer;
|
||||
|
||||
public MainViewModel(ProjectsEditorIO projectsEditorIO)
|
||||
{
|
||||
_projectsEditorIO = projectsEditorIO;
|
||||
lastUpdatedTimer = new System.Timers.Timer();
|
||||
lastUpdatedTimer.Interval = 1000;
|
||||
lastUpdatedTimer.Elapsed += LastUpdatedTimerElapsed;
|
||||
lastUpdatedTimer.Start();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (Project project in Projects)
|
||||
{
|
||||
project.Initialize();
|
||||
}
|
||||
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
|
||||
}
|
||||
|
||||
public void SetEditedProject(Project editedProject)
|
||||
{
|
||||
this.editedProject = editedProject;
|
||||
}
|
||||
|
||||
public void SaveProject(Project projectToSave)
|
||||
{
|
||||
editedProject.Name = projectToSave.Name;
|
||||
editedProject.IsShortcutNeeded = projectToSave.IsShortcutNeeded;
|
||||
editedProject.PreviewImage = projectToSave.PreviewImage;
|
||||
for (int appIndex = 0; appIndex < editedProject.Applications.Count; appIndex++)
|
||||
{
|
||||
editedProject.Applications[appIndex].IsSelected = projectToSave.Applications[appIndex].IsSelected;
|
||||
editedProject.Applications[appIndex].CommandLineArguments = projectToSave.Applications[appIndex].CommandLineArguments;
|
||||
}
|
||||
|
||||
editedProject.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("AppsCountString"));
|
||||
editedProject.Initialize();
|
||||
_projectsEditorIO.SerializeProjects(Projects.ToList());
|
||||
if (editedProject.IsShortcutNeeded)
|
||||
{
|
||||
CreateShortcut(editedProject);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateShortcut(Project editedProject)
|
||||
{
|
||||
object shDesktop = (object)"Desktop";
|
||||
IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
|
||||
string shortcutAddress = (string)shell.SpecialFolders.Item(ref shDesktop) + $"\\{editedProject.Name}.lnk";
|
||||
IWshRuntimeLibrary.IWshShortcut shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(shortcutAddress);
|
||||
shortcut.Description = $"Project Launcher {editedProject.Id}";
|
||||
string basePath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
shortcut.TargetPath = Path.Combine(basePath, "ProjectsEditor.exe");
|
||||
shortcut.Arguments = '"' + editedProject.Id + '"';
|
||||
shortcut.WorkingDirectory = basePath;
|
||||
shortcut.Save();
|
||||
}
|
||||
|
||||
public void SaveProjectName(Project project)
|
||||
{
|
||||
projectNameBeingEdited = project.Name;
|
||||
}
|
||||
|
||||
public void CancelProjectName(Project project)
|
||||
{
|
||||
project.Name = projectNameBeingEdited;
|
||||
}
|
||||
|
||||
public async void AddNewProject()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
if (isNewlyCreated)
|
||||
{
|
||||
projectEdited.Initialize();
|
||||
}
|
||||
|
||||
editPage.DataContext = projectEdited;
|
||||
_mainWindow.ShowPage(editPage);
|
||||
lastUpdatedTimer.Stop();
|
||||
}
|
||||
|
||||
public void CancelLastEdit()
|
||||
{
|
||||
if (isEditedProjectNewlyCreated)
|
||||
{
|
||||
Projects.Remove(editedProject);
|
||||
_projectsEditorIO.SerializeProjects(Projects.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteProject(Project selectedProject)
|
||||
{
|
||||
Projects.Remove(selectedProject);
|
||||
_projectsEditorIO.SerializeProjects(Projects.ToList());
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
|
||||
}
|
||||
|
||||
public void SetMainWindow(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public void SwitchToMainView()
|
||||
{
|
||||
_mainWindow.SwitchToMainView();
|
||||
SearchTerm = string.Empty;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchTerm)));
|
||||
lastUpdatedTimer.Start();
|
||||
}
|
||||
|
||||
public void LaunchProject(string projectId)
|
||||
{
|
||||
if (!Projects.Where(x => x.Id == projectId).Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LaunchProject(Projects.Where(x => x.Id == projectId).First(), true);
|
||||
}
|
||||
|
||||
public async void LaunchProject(Project project, bool exitAfterLaunch = false)
|
||||
{
|
||||
Project actualSetup = await GetActualSetup();
|
||||
|
||||
// Launch apps
|
||||
// TODO: move launching to ProjectsLauncher
|
||||
List<Application> newlyStartedApps = new List<Application>();
|
||||
foreach (Application app in project.Applications.Where(x => x.IsSelected))
|
||||
{
|
||||
string launchParam = app.AppPath;
|
||||
if (string.IsNullOrEmpty(launchParam))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (actualSetup.Applications.Exists(x => x.AppPath.Equals(app.AppPath, StringComparison.Ordinal)))
|
||||
{
|
||||
// just move the existing window to the desired position
|
||||
Application existingApp = actualSetup.Applications.Where(x => x.AppPath.Equals(app.AppPath, StringComparison.Ordinal)).First();
|
||||
NativeMethods.ShowWindow(existingApp.Hwnd, app.Minimized ? NativeMethods.SW_MINIMIZE : NativeMethods.SW_NORMAL);
|
||||
if (!app.Minimized)
|
||||
{
|
||||
MoveWindowWithScaleAdjustment(project, existingApp.Hwnd, app, true);
|
||||
NativeMethods.SetForegroundWindow(existingApp.Hwnd);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (launchParam.EndsWith("systemsettings.exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
string args = string.IsNullOrWhiteSpace(app.CommandLineArguments) ? "home" : app.CommandLineArguments;
|
||||
bool result = await Windows.System.Launcher.LaunchUriAsync(new Uri($"ms-settings:{args}"));
|
||||
newlyStartedApps.Add(app);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// todo exception handling
|
||||
}
|
||||
}
|
||||
|
||||
// todo: check the user's packaged apps folder
|
||||
else if (app.IsPackagedApp)
|
||||
{
|
||||
bool started = false;
|
||||
int retryCountLaunch = 50;
|
||||
|
||||
while (!started && retryCountLaunch > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(app.CommandLineArguments))
|
||||
{
|
||||
// the official app launching method.No parameters are expected
|
||||
var packApp = await GetAppByPackageFamilyNameAsync(app.PackagedId);
|
||||
if (packApp != null)
|
||||
{
|
||||
await packApp.LaunchAsync();
|
||||
}
|
||||
|
||||
newlyStartedApps.Add(app);
|
||||
started = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo = new ProcessStartInfo("cmd.exe");
|
||||
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
p.StartInfo.Arguments = $"cmd /c start shell:appsfolder\\{app.Aumid} {app.CommandLineArguments}";
|
||||
p.EnableRaisingEvents = true;
|
||||
p.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
started = false;
|
||||
};
|
||||
p.Start();
|
||||
p.WaitForExit();
|
||||
});
|
||||
newlyStartedApps.Add(app);
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// todo exception handling
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
retryCountLaunch--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo = new ProcessStartInfo(launchParam);
|
||||
p.StartInfo.Arguments = app.CommandLineArguments;
|
||||
p.Start();
|
||||
});
|
||||
newlyStartedApps.Add(app);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// try again as admin
|
||||
try
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo = new ProcessStartInfo(launchParam);
|
||||
p.StartInfo.Arguments = app.CommandLineArguments;
|
||||
p.StartInfo.UseShellExecute = true;
|
||||
p.StartInfo.Verb = "runas"; // administrator privilages, some apps start only that way
|
||||
p.Start();
|
||||
});
|
||||
newlyStartedApps.Add(app);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// todo exception handling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the official launching method needs this task.
|
||||
static async Task<AppListEntry> GetAppByPackageFamilyNameAsync(string packageFamilyName)
|
||||
{
|
||||
var pkgManager = new PackageManager();
|
||||
var pkg = pkgManager.FindPackagesForUser(string.Empty, packageFamilyName).FirstOrDefault();
|
||||
|
||||
if (pkg == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var apps = await pkg.GetAppListEntriesAsync();
|
||||
var firstApp = apps.Any() ? apps[0] : null;
|
||||
return firstApp;
|
||||
}
|
||||
|
||||
// next step: move newly created apps to their desired position
|
||||
// the OS needs some time to show the apps, so do it in multiple steps until all windows all in place
|
||||
// retry every 100ms for 5 sec totally
|
||||
int retryCount = 50;
|
||||
while (newlyStartedApps.Count > 0 && retryCount > 0)
|
||||
{
|
||||
List<Application> finishedApps = new List<Application>();
|
||||
actualSetup = await GetActualSetup();
|
||||
foreach (Application app in newlyStartedApps)
|
||||
{
|
||||
IEnumerable<Application> candidates = actualSetup.Applications.Where(x => app.IsMyAppPath(x.AppPath));
|
||||
if (candidates.Any())
|
||||
{
|
||||
if (app.AppPath.EndsWith("PowerToys.Settings.exe", StringComparison.Ordinal))
|
||||
{
|
||||
// give it time to not get confused (the app tries to position itself)
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the other apps worked fine and reacted correctly to the MoveWindow event, but to be safe, give them also a little time
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// just move the existing window to the desired position
|
||||
Application existingApp = candidates.First();
|
||||
NativeMethods.ShowWindow(existingApp.Hwnd, app.Minimized ? NativeMethods.SW_MINIMIZE : NativeMethods.SW_NORMAL);
|
||||
if (!app.Minimized)
|
||||
{
|
||||
MoveWindowWithScaleAdjustment(project, existingApp.Hwnd, app, true);
|
||||
NativeMethods.SetForegroundWindow(existingApp.Hwnd);
|
||||
}
|
||||
|
||||
finishedApps.Add(app);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Application app in finishedApps)
|
||||
{
|
||||
newlyStartedApps.Remove(app);
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
retryCount--;
|
||||
}
|
||||
|
||||
// update last launched time
|
||||
var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
project.LastLaunchedTime = (long)ts.TotalSeconds;
|
||||
_projectsEditorIO.SerializeProjects(Projects.ToList());
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
|
||||
|
||||
/*await Task.Run(() => RunLauncher(project.Id));
|
||||
if (_projectsEditorIO.ParseProjects(this).Result == true)
|
||||
{
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("ProjectsView"));
|
||||
}*/
|
||||
|
||||
if (exitAfterLaunch)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void LastUpdatedTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (Projects == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Project project in Projects)
|
||||
{
|
||||
project.OnPropertyChanged(new PropertyChangedEventArgs("LastLaunched"));
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveWindowWithScaleAdjustment(Project project, nint hwnd, Application app, bool repaint)
|
||||
{
|
||||
NativeMethods.SetForegroundWindow(hwnd);
|
||||
|
||||
NativeMethods.MoveWindow(hwnd, app.ScaledPosition.X, app.ScaledPosition.Y, app.ScaledPosition.Width, app.ScaledPosition.Height, repaint);
|
||||
}
|
||||
|
||||
private async Task<Project> GetActualSetup()
|
||||
{
|
||||
string filename = Path.GetTempFileName();
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
File.Delete(filename);
|
||||
}
|
||||
|
||||
await Task.Run(() => RunSnapshotTool(filename));
|
||||
Project actualProject;
|
||||
_projectsEditorIO.ParseProject(filename, out actualProject);
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
File.Delete(filename);
|
||||
}
|
||||
|
||||
return actualProject;
|
||||
}
|
||||
|
||||
private void RunSnapshotTool(string filename = null)
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo = new ProcessStartInfo(@".\ProjectsSnapshotTool.exe");
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
p.StartInfo.Arguments = filename;
|
||||
}
|
||||
|
||||
p.Start();
|
||||
p.WaitForExit();
|
||||
}
|
||||
|
||||
private void RunLauncher(string projectId)
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo = new ProcessStartInfo(@".\ProjectsLauncher.exe", projectId);
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.Start();
|
||||
p.WaitForExit();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/modules/Projects/ProjectsEditor/app.manifest
Normal file
74
src/modules/Projects/ProjectsEditor/app.manifest
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC Manifest Options
|
||||
If you want to change the Windows User Account Control level replace the
|
||||
requestedExecutionLevel node with one of the following.
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||
Remove this element if your application requires this virtualization for backwards
|
||||
compatibility.
|
||||
-->
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
|
||||
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
|
||||
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
|
||||
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
BIN
src/modules/Projects/ProjectsEditor/images/DefaultIcon.ico
Normal file
BIN
src/modules/Projects/ProjectsEditor/images/DefaultIcon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
BIN
src/modules/Projects/ProjectsEditor/images/Projects.ico
Normal file
BIN
src/modules/Projects/ProjectsEditor/images/Projects.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
653
src/modules/Projects/ProjectsLauncher/AppLauncher.cpp
Normal file
653
src/modules/Projects/ProjectsLauncher/AppLauncher.cpp
Normal file
@@ -0,0 +1,653 @@
|
||||
#include "pch.h"
|
||||
#include "AppLauncher.h"
|
||||
|
||||
#include <TlHelp32.h>
|
||||
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <ShellScalingApi.h>
|
||||
|
||||
#include <winrt/Windows.UI.Notifications.h>
|
||||
#include <winrt/Windows.Data.Xml.Dom.h>
|
||||
|
||||
#include "../projects-common/MonitorEnumerator.h"
|
||||
#include "../projects-common/WindowEnumerator.h"
|
||||
|
||||
namespace FancyZones
|
||||
{
|
||||
inline void SizeWindowToRect(HWND window, RECT rect, BOOL snapZone) noexcept;
|
||||
}
|
||||
|
||||
namespace Common
|
||||
{
|
||||
namespace Display
|
||||
{
|
||||
namespace DPIAware
|
||||
{
|
||||
enum AwarenessLevel
|
||||
{
|
||||
UNAWARE,
|
||||
SYSTEM_AWARE,
|
||||
PER_MONITOR_AWARE,
|
||||
PER_MONITOR_AWARE_V2,
|
||||
UNAWARE_GDISCALED
|
||||
};
|
||||
|
||||
AwarenessLevel GetAwarenessLevel(DPI_AWARENESS_CONTEXT system_returned_value)
|
||||
{
|
||||
const std::array levels{ DPI_AWARENESS_CONTEXT_UNAWARE,
|
||||
DPI_AWARENESS_CONTEXT_SYSTEM_AWARE,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
||||
DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED };
|
||||
for (size_t i = 0; i < size(levels); ++i)
|
||||
{
|
||||
if (AreDpiAwarenessContextsEqual(levels[i], system_returned_value))
|
||||
{
|
||||
return static_cast<DPIAware::AwarenessLevel>(i);
|
||||
}
|
||||
}
|
||||
return AwarenessLevel::UNAWARE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
namespace Elevation
|
||||
{
|
||||
// Run command as non-elevated user, returns true if succeeded, puts the process id into returnPid if returnPid != NULL
|
||||
inline bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr, const bool showWindow = true, const RECT& windowRect = {})
|
||||
{
|
||||
//Logger::info(L"run_non_elevated with params={}", params);
|
||||
auto executable_args = L"\"" + file + L"\"";
|
||||
if (!params.empty())
|
||||
{
|
||||
executable_args += L" " + params;
|
||||
}
|
||||
|
||||
HWND hwnd = GetShellWindow();
|
||||
if (!hwnd)
|
||||
{
|
||||
if (GetLastError() == ERROR_SUCCESS)
|
||||
{
|
||||
//Logger::warn(L"GetShellWindow() returned null. Shell window is not available");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Logger::error(L"GetShellWindow() failed. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
DWORD pid;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
|
||||
winrt::handle process{ OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid) };
|
||||
if (!process)
|
||||
{
|
||||
//Logger::error(L"OpenProcess() failed. {}", get_last_error_or_default(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
SIZE_T size = 0;
|
||||
|
||||
InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
|
||||
auto pproc_buffer = std::make_unique<char[]>(size);
|
||||
auto pptal = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(pproc_buffer.get());
|
||||
if (!pptal)
|
||||
{
|
||||
//Logger::error(L"pptal failed to initialize. {}", get_last_error_or_default(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitializeProcThreadAttributeList(pptal, 1, 0, &size))
|
||||
{
|
||||
//Logger::error(L"InitializeProcThreadAttributeList() failed. {}", get_last_error_or_default(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE process_handle = process.get();
|
||||
if (!UpdateProcThreadAttribute(pptal,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
|
||||
&process_handle,
|
||||
sizeof(process_handle),
|
||||
nullptr,
|
||||
nullptr))
|
||||
{
|
||||
//Logger::error(L"UpdateProcThreadAttribute() failed. {}", get_last_error_or_default(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
STARTUPINFOEX siex = { 0 };
|
||||
siex.lpAttributeList = pptal;
|
||||
siex.StartupInfo.cb = sizeof(siex);
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
auto dwCreationFlags = EXTENDED_STARTUPINFO_PRESENT;
|
||||
|
||||
if (!showWindow)
|
||||
{
|
||||
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
||||
siex.StartupInfo.wShowWindow = SW_HIDE;
|
||||
dwCreationFlags = CREATE_NO_WINDOW;
|
||||
}
|
||||
else
|
||||
{
|
||||
siex.StartupInfo.dwFlags = STARTF_USEPOSITION | STARTF_USESIZE;
|
||||
siex.StartupInfo.dwX = windowRect.left;
|
||||
siex.StartupInfo.dwY = windowRect.top;
|
||||
siex.StartupInfo.dwXSize = windowRect.right - windowRect.left;
|
||||
siex.StartupInfo.dwYSize = windowRect.bottom - windowRect.top;
|
||||
}
|
||||
|
||||
auto succeeded = CreateProcessW(file.c_str(),
|
||||
&executable_args[0],
|
||||
nullptr,
|
||||
nullptr,
|
||||
FALSE,
|
||||
dwCreationFlags,
|
||||
nullptr,
|
||||
workingDir,
|
||||
&siex.StartupInfo,
|
||||
&pi);
|
||||
if (succeeded)
|
||||
{
|
||||
if (pi.hProcess)
|
||||
{
|
||||
if (returnPid)
|
||||
{
|
||||
*returnPid = GetProcessId(pi.hProcess);
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
}
|
||||
if (pi.hThread)
|
||||
{
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Logger::error(L"CreateProcessW() failed. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FancyZones
|
||||
{
|
||||
inline bool allMonitorsHaveSameDpiScaling()
|
||||
{
|
||||
auto monitors = MonitorEnumerator::Enumerate();
|
||||
if (monitors.size() < 2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
UINT firstMonitorDpiX;
|
||||
UINT firstMonitorDpiY;
|
||||
|
||||
if (S_OK != GetDpiForMonitor(monitors[0].first, MDT_EFFECTIVE_DPI, &firstMonitorDpiX, &firstMonitorDpiY))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < monitors.size(); i++)
|
||||
{
|
||||
UINT iteratedMonitorDpiX;
|
||||
UINT iteratedMonitorDpiY;
|
||||
|
||||
if (S_OK != GetDpiForMonitor(monitors[i].first, MDT_EFFECTIVE_DPI, &iteratedMonitorDpiX, &iteratedMonitorDpiY) ||
|
||||
iteratedMonitorDpiX != firstMonitorDpiX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void ScreenToWorkAreaCoords(HWND window, RECT& rect)
|
||||
{
|
||||
// First, find the correct monitor. The monitor cannot be found using the given rect itself, we must first
|
||||
// translate it to relative workspace coordinates.
|
||||
HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
|
||||
GetMonitorInfoW(monitor, &monitorInfo);
|
||||
|
||||
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||
|
||||
auto referenceRect = rect;
|
||||
|
||||
referenceRect.left -= xOffset;
|
||||
referenceRect.right -= xOffset;
|
||||
referenceRect.top -= yOffset;
|
||||
referenceRect.bottom -= yOffset;
|
||||
|
||||
// Now, this rect should be used to determine the monitor and thus taskbar size. This fixes
|
||||
// scenarios where the zone lies approximately between two monitors, and the taskbar is on the left.
|
||||
monitor = MonitorFromRect(&referenceRect, MONITOR_DEFAULTTOPRIMARY);
|
||||
GetMonitorInfoW(monitor, &monitorInfo);
|
||||
|
||||
xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||
yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||
|
||||
rect.left -= xOffset;
|
||||
rect.right -= xOffset;
|
||||
rect.top -= yOffset;
|
||||
rect.bottom -= yOffset;
|
||||
|
||||
const auto level = Common::Display::DPIAware::GetAwarenessLevel(GetWindowDpiAwarenessContext(window));
|
||||
const bool accountForUnawareness = level < Common::Display::DPIAware::PER_MONITOR_AWARE;
|
||||
|
||||
if (accountForUnawareness && !allMonitorsHaveSameDpiScaling())
|
||||
{
|
||||
rect.left = max(monitorInfo.rcMonitor.left, rect.left);
|
||||
rect.right = min(monitorInfo.rcMonitor.right - xOffset, rect.right);
|
||||
rect.top = max(monitorInfo.rcMonitor.top, rect.top);
|
||||
rect.bottom = min(monitorInfo.rcMonitor.bottom - yOffset, rect.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
inline void SizeWindowToRect(HWND window, RECT rect, BOOL snapZone) noexcept
|
||||
{
|
||||
|
||||
WINDOWPLACEMENT placement{};
|
||||
::GetWindowPlacement(window, &placement);
|
||||
|
||||
// Wait if SW_SHOWMINIMIZED would be removed from window (Issue #1685)
|
||||
for (int i = 0; i < 5 && (placement.showCmd == SW_SHOWMINIMIZED); ++i)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
::GetWindowPlacement(window, &placement);
|
||||
}
|
||||
|
||||
BOOL maximizeLater = false;
|
||||
if (IsWindowVisible(window))
|
||||
{
|
||||
// If is not snap zone then need keep maximize state (move to active monitor)
|
||||
if (!snapZone && placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
{
|
||||
maximizeLater = true;
|
||||
}
|
||||
|
||||
// Do not restore minimized windows. We change their placement though so they restore to the correct zone.
|
||||
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
|
||||
(placement.showCmd != SW_MINIMIZE))
|
||||
{
|
||||
// Remove maximized show command to make sure window is moved to the correct zone.
|
||||
if (placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
|
||||
|
||||
placement.showCmd = SW_RESTORE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
placement.showCmd = SW_HIDE;
|
||||
}
|
||||
|
||||
ScreenToWorkAreaCoords(window, rect);
|
||||
|
||||
placement.rcNormalPosition = rect;
|
||||
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
|
||||
|
||||
std::wcout << "Set window placement" << std::endl;
|
||||
auto result = ::SetWindowPlacement(window, &placement);
|
||||
if (!result)
|
||||
{
|
||||
std::wcout << "Set window placement failed" << std::endl;
|
||||
//Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
// make sure window is moved to the correct monitor before maximize.
|
||||
if (maximizeLater)
|
||||
{
|
||||
placement.showCmd = SW_SHOWMAXIMIZED;
|
||||
}
|
||||
|
||||
// Do it again, allowing Windows to resize the window and set correct scaling
|
||||
// This fixes Issue #365
|
||||
result = ::SetWindowPlacement(window, &placement);
|
||||
if (!result)
|
||||
{
|
||||
std::wcout << "Set window placement failed" << std::endl;
|
||||
//Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace KBM
|
||||
{
|
||||
using namespace winrt;
|
||||
using namespace Windows::UI::Notifications;
|
||||
using namespace Windows::Data::Xml::Dom;
|
||||
|
||||
// Use to find a process by its name
|
||||
std::wstring GetFileNameFromPath(const std::wstring& fullPath)
|
||||
{
|
||||
size_t found = fullPath.find_last_of(L"\\");
|
||||
if (found != std::wstring::npos)
|
||||
{
|
||||
return fullPath.substr(found + 1);
|
||||
}
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
DWORD GetProcessIdByName(const std::wstring& processName)
|
||||
{
|
||||
DWORD pid = 0;
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
|
||||
if (snapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
PROCESSENTRY32 processEntry;
|
||||
processEntry.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(snapshot, &processEntry))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_wcsicmp(processEntry.szExeFile, processName.c_str()) == 0)
|
||||
{
|
||||
pid = processEntry.th32ProcessID;
|
||||
break;
|
||||
}
|
||||
} while (Process32Next(snapshot, &processEntry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
std::vector<DWORD> GetProcessesIdByName(const std::wstring& processName)
|
||||
{
|
||||
std::vector<DWORD> processIds;
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
|
||||
if (snapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
PROCESSENTRY32 processEntry;
|
||||
processEntry.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(snapshot, &processEntry))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_wcsicmp(processEntry.szExeFile, processName.c_str()) == 0)
|
||||
{
|
||||
processIds.push_back(processEntry.th32ProcessID);
|
||||
}
|
||||
} while (Process32Next(snapshot, &processEntry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
|
||||
return processIds;
|
||||
}
|
||||
|
||||
struct handle_data
|
||||
{
|
||||
unsigned long process_id;
|
||||
HWND window_handle;
|
||||
};
|
||||
|
||||
// used by FindMainWindow
|
||||
BOOL CALLBACK EnumWindowsCallbackAllowNonVisible(HWND handle, LPARAM lParam)
|
||||
{
|
||||
handle_data& data = *reinterpret_cast<handle_data*>(lParam);
|
||||
unsigned long process_id = 0;
|
||||
GetWindowThreadProcessId(handle, &process_id);
|
||||
|
||||
if (data.process_id == process_id)
|
||||
{
|
||||
data.window_handle = handle;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// used by FindMainWindow
|
||||
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam)
|
||||
{
|
||||
handle_data& data = *reinterpret_cast<handle_data*>(lParam);
|
||||
unsigned long process_id = 0;
|
||||
GetWindowThreadProcessId(handle, &process_id);
|
||||
|
||||
if (data.process_id != process_id || !(GetWindow(handle, GW_OWNER) == static_cast<HWND>(0) && IsWindowVisible(handle)))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
data.window_handle = handle;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// used for reactivating a window for a program we already started.
|
||||
HWND FindMainWindow(unsigned long process_id, const bool allowNonVisible)
|
||||
{
|
||||
handle_data data;
|
||||
data.process_id = process_id;
|
||||
data.window_handle = 0;
|
||||
|
||||
if (allowNonVisible)
|
||||
{
|
||||
EnumWindows(EnumWindowsCallbackAllowNonVisible, reinterpret_cast<LPARAM>(&data));
|
||||
}
|
||||
else
|
||||
{
|
||||
EnumWindows(EnumWindowsCallback, reinterpret_cast<LPARAM>(&data));
|
||||
}
|
||||
|
||||
return data.window_handle;
|
||||
}
|
||||
|
||||
bool ShowProgram(DWORD pid, std::wstring programName, bool isNewProcess, bool minimizeIfVisible, const RECT& windowPosition, int retryCount)
|
||||
{
|
||||
// a good place to look for this...
|
||||
// https://github.com/ritchielawrence/cmdow
|
||||
|
||||
// try by main window.
|
||||
auto allowNonVisible = true;
|
||||
HWND hwnd = FindMainWindow(pid, allowNonVisible);
|
||||
|
||||
if (hwnd == NULL)
|
||||
{
|
||||
if (retryCount < 20)
|
||||
{
|
||||
auto future = std::async(std::launch::async, [=] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
ShowProgram(pid, programName, isNewProcess, minimizeIfVisible, windowPosition, retryCount + 1);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hwnd == GetForegroundWindow())
|
||||
{
|
||||
// only hide if this was a call from a already open program, don't make small if we just opened it.
|
||||
if (!isNewProcess && minimizeIfVisible)
|
||||
{
|
||||
return ShowWindow(hwnd, SW_MINIMIZE);
|
||||
}
|
||||
|
||||
FancyZones::SizeWindowToRect(hwnd, windowPosition, false);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the window is minimized
|
||||
if (IsIconic(hwnd))
|
||||
{
|
||||
// Show the window since SetForegroundWindow fails on minimized windows
|
||||
if (!ShowWindow(hwnd, SW_RESTORE))
|
||||
{
|
||||
std::wcout << L"ShowWindow failed" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
//INPUT inputs[1] = { {.type = INPUT_MOUSE } };
|
||||
//SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
|
||||
|
||||
if (!SetForegroundWindow(hwnd))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
FancyZones::SizeWindowToRect(hwnd, windowPosition, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewProcess)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (false)
|
||||
{
|
||||
// try by console.
|
||||
hwnd = FindWindow(nullptr, nullptr);
|
||||
if (AttachConsole(pid))
|
||||
{
|
||||
// Get the console window handle
|
||||
hwnd = GetConsoleWindow();
|
||||
auto showByConsoleSuccess = false;
|
||||
if (hwnd != NULL)
|
||||
{
|
||||
ShowWindow(hwnd, SW_RESTORE);
|
||||
if (SetForegroundWindow(hwnd))
|
||||
{
|
||||
showByConsoleSuccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Detach from the console
|
||||
FreeConsole();
|
||||
if (showByConsoleSuccess)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to just show them all (if they have a title)!.
|
||||
hwnd = FindWindow(nullptr, nullptr);
|
||||
if (hwnd)
|
||||
{
|
||||
while (hwnd)
|
||||
{
|
||||
DWORD pidForHwnd;
|
||||
GetWindowThreadProcessId(hwnd, &pidForHwnd);
|
||||
if (pid == pidForHwnd)
|
||||
{
|
||||
int length = GetWindowTextLength(hwnd);
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
ShowWindow(hwnd, SW_RESTORE);
|
||||
|
||||
// hwnd is the window handle with targetPid
|
||||
if (SetForegroundWindow(hwnd))
|
||||
{
|
||||
FancyZones::SizeWindowToRect(hwnd, windowPosition, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
hwnd = FindWindowEx(NULL, hwnd, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HideProgram(DWORD pid, std::wstring programName, int retryCount)
|
||||
{
|
||||
HWND hwnd = FindMainWindow(pid, false);
|
||||
if (hwnd == NULL)
|
||||
{
|
||||
if (retryCount < 20)
|
||||
{
|
||||
auto future = std::async(std::launch::async, [=] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
HideProgram(pid, programName, retryCount + 1);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
hwnd = FindWindow(nullptr, nullptr);
|
||||
while (hwnd)
|
||||
{
|
||||
DWORD pidForHwnd;
|
||||
GetWindowThreadProcessId(hwnd, &pidForHwnd);
|
||||
if (pid == pidForHwnd)
|
||||
{
|
||||
if (IsWindowVisible(hwnd))
|
||||
{
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
}
|
||||
}
|
||||
hwnd = FindWindowEx(NULL, hwnd, NULL, NULL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Launch(const std::wstring& appPath, bool startMinimized, const std::wstring& commandLineArgs, const RECT& windowPosition) noexcept
|
||||
{
|
||||
WCHAR fullExpandedFilePath[MAX_PATH];
|
||||
ExpandEnvironmentStrings(appPath.c_str(), fullExpandedFilePath, MAX_PATH);
|
||||
|
||||
auto fileNamePart = KBM::GetFileNameFromPath(fullExpandedFilePath);
|
||||
|
||||
DWORD dwAttrib = GetFileAttributesW(fullExpandedFilePath);
|
||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD processId = 0;
|
||||
if (Common::Utils::Elevation::run_non_elevated(fullExpandedFilePath, commandLineArgs, &processId, nullptr, !startMinimized))
|
||||
{
|
||||
std::wcout << "Launched " << fileNamePart << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wcout << "Failed to launch " << fileNamePart << std::endl;
|
||||
}
|
||||
|
||||
if (processId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto targetPid = KBM::GetProcessIdByName(fileNamePart);
|
||||
if (!startMinimized)
|
||||
{
|
||||
KBM::ShowProgram(targetPid, fileNamePart, false, false, windowPosition, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
KBM::HideProgram(targetPid, fileNamePart, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
5
src/modules/Projects/ProjectsLauncher/AppLauncher.h
Normal file
5
src/modules/Projects/ProjectsLauncher/AppLauncher.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
void Launch(const std::wstring& appPath, bool startMinimized, const std::wstring& commandLineArgs, const RECT& rect) noexcept;
|
||||
143
src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj
Normal file
143
src/modules/Projects/ProjectsLauncher/ProjectsLauncher.vcxproj
Normal file
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<CppWinRTOptimized>true</CppWinRTOptimized>
|
||||
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
|
||||
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
|
||||
<MinimalCoreWin>true</MinimalCoreWin>
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{2cac093e-5fcf-4102-9c2c-ac7dd5d9eb96}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>ProjectsLauncher</RootNamespace>
|
||||
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.22621.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.17134.0</WindowsTargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0'">v140</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="PropertySheet.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<PreprocessorDefinitions>_CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<AdditionalOptions>%(AdditionalOptions) /permissive- /bigobj</AdditionalOptions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">shcore.lib;Shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Release|x64'">stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">shcore.lib;Shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AppLauncher.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AppLauncher.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="PropertySheet.props" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AppLauncher.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AppLauncher.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="PropertySheet.props" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
16
src/modules/Projects/ProjectsLauncher/PropertySheet.props
Normal file
16
src/modules/Projects/ProjectsLauncher/PropertySheet.props
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Label="PropertySheets" />
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<!--
|
||||
To customize common C++/WinRT project properties:
|
||||
* right-click the project node
|
||||
* expand the Common Properties item
|
||||
* select the C++/WinRT property page
|
||||
|
||||
For more advanced scenarios, and complete documentation, please see:
|
||||
https://github.com/Microsoft/cppwinrt/tree/master/nuget
|
||||
-->
|
||||
<PropertyGroup />
|
||||
<ItemDefinitionGroup />
|
||||
</Project>
|
||||
71
src/modules/Projects/ProjectsLauncher/main.cpp
Normal file
71
src/modules/Projects/ProjectsLauncher/main.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../projects-common/Data.h"
|
||||
|
||||
#include "AppLauncher.h"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
// read projects
|
||||
std::vector<Project> projects;
|
||||
try
|
||||
{
|
||||
auto savedProjectsJson = json::from_file(JsonUtils::ProjectsFile());
|
||||
if (savedProjectsJson.has_value())
|
||||
{
|
||||
auto savedProjects = JsonUtils::ProjectsListJSON::FromJson(savedProjectsJson.value());
|
||||
if (savedProjects.has_value())
|
||||
{
|
||||
projects = savedProjects.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (projects.empty())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
Project projectToLaunch = projects[0];
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
std::string idStr = argv[1];
|
||||
std::wstring id(idStr.begin(), idStr.end());
|
||||
for (const auto& proj : projects)
|
||||
{
|
||||
if (proj.id == id)
|
||||
{
|
||||
projectToLaunch = proj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// launch apps
|
||||
for (const auto& app : projectToLaunch.apps)
|
||||
{
|
||||
Launch(app.appPath, app.isMinimized, app.commandLineArgs, app.position.toRect());
|
||||
}
|
||||
|
||||
// update last-launched time
|
||||
time_t launchedTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
projectToLaunch.lastLaunchedTime = launchedTime;
|
||||
for (int i = 0; i < projects.size(); i++)
|
||||
{
|
||||
if (projects[i].id == projectToLaunch.id)
|
||||
{
|
||||
projects[i] = projectToLaunch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
json::to_file(JsonUtils::ProjectsFile(), JsonUtils::ProjectsListJSON::ToJson(projects));
|
||||
|
||||
return 0;
|
||||
}
|
||||
4
src/modules/Projects/ProjectsLauncher/packages.config
Normal file
4
src/modules/Projects/ProjectsLauncher/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.220531.1" targetFramework="native" />
|
||||
</packages>
|
||||
1
src/modules/Projects/ProjectsLauncher/pch.cpp
Normal file
1
src/modules/Projects/ProjectsLauncher/pch.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
3
src/modules/Projects/ProjectsLauncher/pch.h
Normal file
3
src/modules/Projects/ProjectsLauncher/pch.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
215
src/modules/Projects/ProjectsSnapshotTool/MonitorUtils.cpp
Normal file
215
src/modules/Projects/ProjectsSnapshotTool/MonitorUtils.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include "pch.h"
|
||||
#include "MonitorUtils.h"
|
||||
|
||||
#include <ShellScalingApi.h>
|
||||
|
||||
#include "../projects-common/MonitorEnumerator.h"
|
||||
#include "OnThreadExecutor.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
namespace Display
|
||||
{
|
||||
namespace DPIAware
|
||||
{
|
||||
constexpr inline int DEFAULT_DPI = 96;
|
||||
|
||||
void Convert(HMONITOR monitor_handle, float& width, float& height)
|
||||
{
|
||||
if (monitor_handle == NULL)
|
||||
{
|
||||
const POINT ptZero = { 0, 0 };
|
||||
monitor_handle = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
|
||||
}
|
||||
|
||||
UINT dpi_x, dpi_y;
|
||||
if (GetDpiForMonitor(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y) == S_OK)
|
||||
{
|
||||
width = width * dpi_x / DEFAULT_DPI;
|
||||
height = height * dpi_y / DEFAULT_DPI;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT GetScreenDPIForMonitor(HMONITOR targetMonitor, UINT& dpi)
|
||||
{
|
||||
if (targetMonitor != nullptr)
|
||||
{
|
||||
UINT dummy = 0;
|
||||
return GetDpiForMonitor(targetMonitor, MDT_EFFECTIVE_DPI, &dpi, &dummy);
|
||||
}
|
||||
else
|
||||
{
|
||||
dpi = DPIAware::DEFAULT_DPI;
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MonitorUtils
|
||||
{
|
||||
namespace Display
|
||||
{
|
||||
constexpr inline bool not_digit(wchar_t ch)
|
||||
{
|
||||
return '0' <= ch && ch <= '9';
|
||||
}
|
||||
|
||||
std::wstring remove_non_digits(const std::wstring& input)
|
||||
{
|
||||
std::wstring result;
|
||||
std::copy_if(input.begin(), input.end(), std::back_inserter(result), not_digit);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<bool, std::vector<Project::Monitor>> GetDisplays()
|
||||
{
|
||||
bool success = true;
|
||||
std::vector<Project::Monitor> result{};
|
||||
|
||||
auto allMonitors = MonitorEnumerator::Enumerate();
|
||||
for (auto& monitorData : allMonitors)
|
||||
{
|
||||
MONITORINFOEX monitorInfo = monitorData.second;
|
||||
MONITORINFOEX dpiUnawareMonitorInfo{};
|
||||
|
||||
OnThreadExecutor dpiUnawareThread;
|
||||
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
|
||||
|
||||
dpiUnawareMonitorInfo.cbSize = sizeof(dpiUnawareMonitorInfo);
|
||||
if (!GetMonitorInfo(monitorData.first, &dpiUnawareMonitorInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} }).wait();
|
||||
|
||||
float width = static_cast<float>(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
|
||||
float height = static_cast<float>(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
|
||||
|
||||
float dpiUnawareWidth = static_cast<float>(dpiUnawareMonitorInfo.rcMonitor.right - dpiUnawareMonitorInfo.rcMonitor.left);
|
||||
float dpiUnawareHeight = static_cast<float>(dpiUnawareMonitorInfo.rcMonitor.bottom - dpiUnawareMonitorInfo.rcMonitor.top);
|
||||
|
||||
UINT dpi = 0;
|
||||
if (Common::Display::DPIAware::GetScreenDPIForMonitor(monitorData.first, dpi) != S_OK)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Project::Monitor monitorId{
|
||||
.monitor = monitorData.first,
|
||||
.dpi = dpi,
|
||||
.monitorRectDpiAware = Project::Monitor::MonitorRect {
|
||||
.top = monitorInfo.rcMonitor.top,
|
||||
.left = monitorInfo.rcMonitor.left,
|
||||
.width = static_cast<int>(std::roundf(width)),
|
||||
.height = static_cast<int>(std::roundf(height)),
|
||||
},
|
||||
.monitorRectDpiUnaware = Project::Monitor::MonitorRect {
|
||||
.top = dpiUnawareMonitorInfo.rcMonitor.top,
|
||||
.left = dpiUnawareMonitorInfo.rcMonitor.left,
|
||||
.width = static_cast<int>(std::roundf(dpiUnawareWidth)),
|
||||
.height = static_cast<int>(std::roundf(dpiUnawareHeight)),
|
||||
},
|
||||
};
|
||||
|
||||
bool foundActiveMonitor = false;
|
||||
DISPLAY_DEVICE displayDevice{ .cb = sizeof(displayDevice) };
|
||||
DWORD displayDeviceIndex = 0;
|
||||
while (EnumDisplayDevicesW(monitorInfo.szDevice, displayDeviceIndex, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME))
|
||||
{
|
||||
/*
|
||||
* if (WI_IsFlagSet(displayDevice.StateFlags, DISPLAY_DEVICE_ACTIVE) &&
|
||||
WI_IsFlagClear(displayDevice.StateFlags, DISPLAY_DEVICE_MIRRORING_DRIVER))
|
||||
*/
|
||||
if (((displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE) == DISPLAY_DEVICE_ACTIVE) &&
|
||||
(displayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0)
|
||||
{
|
||||
// Find display devices associated with the display.
|
||||
foundActiveMonitor = true;
|
||||
break;
|
||||
}
|
||||
|
||||
displayDeviceIndex++;
|
||||
}
|
||||
|
||||
if (foundActiveMonitor)
|
||||
{
|
||||
auto deviceId = SplitDisplayDeviceId(displayDevice.DeviceID);
|
||||
monitorId.id = deviceId.first;
|
||||
monitorId.instanceId = deviceId.second;
|
||||
try
|
||||
{
|
||||
std::wstring numberStr = displayDevice.DeviceName; // \\.\DISPLAY1\Monitor0
|
||||
numberStr = numberStr.substr(0, numberStr.find_last_of('\\')); // \\.\DISPLAY1
|
||||
numberStr = remove_non_digits(numberStr);
|
||||
monitorId.number = std::stoi(numberStr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
monitorId.number = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
|
||||
// Use the display name as a fallback value when no proper device was found.
|
||||
monitorId.id = monitorInfo.szDevice;
|
||||
monitorId.instanceId = L"";
|
||||
|
||||
try
|
||||
{
|
||||
std::wstring numberStr = monitorInfo.szDevice; // \\.\DISPLAY1
|
||||
numberStr = remove_non_digits(numberStr);
|
||||
monitorId.number = std::stoi(numberStr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
monitorId.number = 0;
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(std::move(monitorId));
|
||||
}
|
||||
|
||||
return { success, result };
|
||||
}
|
||||
|
||||
std::pair<std::wstring, std::wstring> SplitDisplayDeviceId(const std::wstring& str) noexcept
|
||||
{
|
||||
// format: \\?\DISPLAY#{device id}#{instance id}#{some other id}
|
||||
// example: \\?\DISPLAY#GSM1388#4&125707d6&0&UID8388688#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||
// output: { GSM1388, 4&125707d6&0&UID8388688 }
|
||||
|
||||
size_t nameStartPos = str.find_first_of('#');
|
||||
size_t uidStartPos = str.find('#', nameStartPos + 1);
|
||||
size_t uidEndPos = str.find('#', uidStartPos + 1);
|
||||
|
||||
if (nameStartPos == std::string::npos || uidStartPos == std::string::npos || uidEndPos == std::string::npos)
|
||||
{
|
||||
return { str, L"" };
|
||||
}
|
||||
|
||||
return { str.substr(nameStartPos + 1, uidStartPos - nameStartPos - 1), str.substr(uidStartPos + 1, uidEndPos - uidStartPos - 1) };
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Project::Monitor> IdentifyMonitors() noexcept
|
||||
{
|
||||
auto displaysResult = Display::GetDisplays();
|
||||
|
||||
// retry
|
||||
int retryCounter = 0;
|
||||
while (!displaysResult.first && retryCounter < 100)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
displaysResult = Display::GetDisplays();
|
||||
retryCounter++;
|
||||
}
|
||||
|
||||
return displaysResult.second;
|
||||
}
|
||||
}
|
||||
17
src/modules/Projects/ProjectsSnapshotTool/MonitorUtils.h
Normal file
17
src/modules/Projects/ProjectsSnapshotTool/MonitorUtils.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../projects-common/Data.h"
|
||||
|
||||
// FancyZones: MonitorUtils.h
|
||||
namespace MonitorUtils
|
||||
{
|
||||
namespace Display
|
||||
{
|
||||
std::pair<bool, std::vector<Project::Monitor>> GetDisplays();
|
||||
std::pair<std::wstring, std::wstring> SplitDisplayDeviceId(const std::wstring& str) noexcept;
|
||||
}
|
||||
|
||||
std::vector<Project::Monitor> IdentifyMonitors() noexcept;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "OnThreadExecutor.h"
|
||||
|
||||
OnThreadExecutor::OnThreadExecutor() :
|
||||
_shutdown_request{ false }, _worker_thread{ [this] { worker_thread(); } }
|
||||
{
|
||||
}
|
||||
|
||||
std::future<void> OnThreadExecutor::submit(task_t task)
|
||||
{
|
||||
auto future = task.get_future();
|
||||
std::lock_guard lock{ _task_mutex };
|
||||
_task_queue.emplace(std::move(task));
|
||||
_task_cv.notify_one();
|
||||
return future;
|
||||
}
|
||||
|
||||
void OnThreadExecutor::cancel()
|
||||
{
|
||||
std::lock_guard lock{ _task_mutex };
|
||||
_task_queue = {};
|
||||
_task_cv.notify_one();
|
||||
}
|
||||
|
||||
|
||||
void OnThreadExecutor::worker_thread()
|
||||
{
|
||||
while (!_shutdown_request)
|
||||
{
|
||||
task_t task;
|
||||
{
|
||||
std::unique_lock task_lock{ _task_mutex };
|
||||
_task_cv.wait(task_lock, [this] { return !_task_queue.empty() || _shutdown_request; });
|
||||
if (_shutdown_request)
|
||||
{
|
||||
return;
|
||||
}
|
||||
task = std::move(_task_queue.front());
|
||||
_task_queue.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
OnThreadExecutor::~OnThreadExecutor()
|
||||
{
|
||||
_shutdown_request = true;
|
||||
_task_cv.notify_one();
|
||||
_worker_thread.join();
|
||||
}
|
||||
31
src/modules/Projects/ProjectsSnapshotTool/OnThreadExecutor.h
Normal file
31
src/modules/Projects/ProjectsSnapshotTool/OnThreadExecutor.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
|
||||
// OnThreadExecutor allows its caller to off-load some work to a persistently running background thread.
|
||||
// This might come in handy if you use the API which sets thread-wide global state and the state needs
|
||||
// to be isolated.
|
||||
|
||||
class OnThreadExecutor final
|
||||
{
|
||||
public:
|
||||
using task_t = std::packaged_task<void()>;
|
||||
|
||||
OnThreadExecutor();
|
||||
~OnThreadExecutor();
|
||||
std::future<void> submit(task_t task);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
void worker_thread();
|
||||
|
||||
std::mutex _task_mutex;
|
||||
std::condition_variable _task_cv;
|
||||
std::atomic_bool _shutdown_request;
|
||||
std::queue<std::packaged_task<void()>> _task_queue;
|
||||
std::thread _worker_thread;
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<CppWinRTOptimized>true</CppWinRTOptimized>
|
||||
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
|
||||
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
|
||||
<MinimalCoreWin>true</MinimalCoreWin>
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{3d63307b-9d27-44fd-b033-b26f39245b85}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>ProjectsSnapshotTool</RootNamespace>
|
||||
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.22621.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.17134.0</WindowsTargetPlatformMinVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0'">v140</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="PropertySheet.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<PreprocessorDefinitions>_CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<AdditionalOptions>%(AdditionalOptions) /permissive- /bigobj</AdditionalOptions>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">shcore.lib;Shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">shcore.lib;Shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MonitorUtils.h" />
|
||||
<ClInclude Include="OnThreadExecutor.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="VirtualDesktop.h" />
|
||||
<ClInclude Include="WindowFilter.h" />
|
||||
<ClInclude Include="WindowUtils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="MonitorUtils.cpp" />
|
||||
<ClCompile Include="OnThreadExecutor.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VirtualDesktop.cpp" />
|
||||
<ClCompile Include="WindowUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="PropertySheet.props" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.220531.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VirtualDesktop.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowFilter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MonitorUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="OnThreadExecutor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VirtualDesktop.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MonitorUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OnThreadExecutor.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="PropertySheet.props" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Label="PropertySheets" />
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<!--
|
||||
To customize common C++/WinRT project properties:
|
||||
* right-click the project node
|
||||
* expand the Common Properties item
|
||||
* select the C++/WinRT property page
|
||||
|
||||
For more advanced scenarios, and complete documentation, please see:
|
||||
https://github.com/Microsoft/cppwinrt/tree/master/nuget
|
||||
-->
|
||||
<PropertyGroup />
|
||||
<ItemDefinitionGroup />
|
||||
</Project>
|
||||
39
src/modules/Projects/ProjectsSnapshotTool/VirtualDesktop.cpp
Normal file
39
src/modules/Projects/ProjectsSnapshotTool/VirtualDesktop.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "pch.h"
|
||||
#include "VirtualDesktop.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
VirtualDesktop::VirtualDesktop()
|
||||
{
|
||||
auto res = CoCreateInstance(CLSID_VirtualDesktopManager, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_vdManager));
|
||||
if (FAILED(res))
|
||||
{
|
||||
// Logger::error("Failed to create VirtualDesktopManager instance");
|
||||
std::cout << "Failed to create VirtualDesktopManager instance\n";
|
||||
}
|
||||
}
|
||||
|
||||
VirtualDesktop::~VirtualDesktop()
|
||||
{
|
||||
if (m_vdManager)
|
||||
{
|
||||
m_vdManager->Release();
|
||||
}
|
||||
}
|
||||
|
||||
VirtualDesktop& VirtualDesktop::instance()
|
||||
{
|
||||
static VirtualDesktop self;
|
||||
return self;
|
||||
}
|
||||
|
||||
bool VirtualDesktop::IsWindowOnCurrentDesktop(HWND window) const
|
||||
{
|
||||
BOOL isWindowOnCurrentDesktop = false;
|
||||
if (m_vdManager)
|
||||
{
|
||||
m_vdManager->IsWindowOnCurrentVirtualDesktop(window, &isWindowOnCurrentDesktop);
|
||||
}
|
||||
|
||||
return isWindowOnCurrentDesktop;
|
||||
}
|
||||
20
src/modules/Projects/ProjectsSnapshotTool/VirtualDesktop.h
Normal file
20
src/modules/Projects/ProjectsSnapshotTool/VirtualDesktop.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <ShObjIdl.h>
|
||||
|
||||
class VirtualDesktop
|
||||
{
|
||||
public:
|
||||
static VirtualDesktop& instance();
|
||||
|
||||
bool IsWindowOnCurrentDesktop(HWND window) const;
|
||||
|
||||
private:
|
||||
VirtualDesktop();
|
||||
~VirtualDesktop();
|
||||
|
||||
IVirtualDesktopManager* m_vdManager{ nullptr };
|
||||
};
|
||||
|
||||
|
||||
54
src/modules/Projects/ProjectsSnapshotTool/WindowFilter.h
Normal file
54
src/modules/Projects/ProjectsSnapshotTool/WindowFilter.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "VirtualDesktop.h"
|
||||
#include "WindowUtils.h"
|
||||
|
||||
namespace WindowFilter
|
||||
{
|
||||
inline bool Filter(HWND window)
|
||||
{
|
||||
auto style = GetWindowLong(window, GWL_STYLE);
|
||||
auto exStyle = GetWindowLong(window, GWL_EXSTYLE);
|
||||
|
||||
if (!WindowUtils::HasStyle(style, WS_VISIBLE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsWindowVisible(window))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WindowUtils::HasStyle(exStyle, WS_EX_TOOLWINDOW))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WindowUtils::IsRoot(window))
|
||||
{
|
||||
// child windows such as buttons, combo boxes, etc.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isPopup = WindowUtils::HasStyle(style, WS_POPUP);
|
||||
bool hasThickFrame = WindowUtils::HasStyle(style, WS_THICKFRAME);
|
||||
bool hasCaption = WindowUtils::HasStyle(style, WS_CAPTION);
|
||||
bool hasMinimizeMaximizeButtons = WindowUtils::HasStyle(style, WS_MINIMIZEBOX) || WindowUtils::HasStyle(style, WS_MAXIMIZEBOX);
|
||||
if (isPopup && !(hasThickFrame && (hasCaption || hasMinimizeMaximizeButtons)))
|
||||
{
|
||||
// popup windows we want to snap: e.g. Calculator, Telegram
|
||||
// popup windows we don't want to snap: start menu, notification popup, tray window, etc.
|
||||
// WS_CAPTION, WS_MINIMIZEBOX, WS_MAXIMIZEBOX are used for filtering out menus,
|
||||
// e.g., in Edge "Running as admin" menu when creating a new PowerToys issue.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VirtualDesktop::instance().IsWindowOnCurrentDesktop(window))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
114
src/modules/Projects/ProjectsSnapshotTool/WindowUtils.cpp
Normal file
114
src/modules/Projects/ProjectsSnapshotTool/WindowUtils.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "pch.h"
|
||||
#include "WindowUtils.h"
|
||||
|
||||
#include <ShellScalingApi.h>
|
||||
|
||||
namespace Common
|
||||
{
|
||||
namespace Display
|
||||
{
|
||||
namespace DPIAware
|
||||
{
|
||||
constexpr inline int DEFAULT_DPI = 96;
|
||||
|
||||
void InverseConvert(HMONITOR monitor_handle, float& width, float& height)
|
||||
{
|
||||
if (monitor_handle == NULL)
|
||||
{
|
||||
const POINT ptZero = { 0, 0 };
|
||||
monitor_handle = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
|
||||
}
|
||||
|
||||
UINT dpi_x, dpi_y;
|
||||
if (GetDpiForMonitor(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y) == S_OK)
|
||||
{
|
||||
width = width * DPIAware::DEFAULT_DPI / dpi_x;
|
||||
height = height * DPIAware::DEFAULT_DPI / dpi_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 CoreWindow[] = L"WINDOWS.UI.CORE.COREWINDOW";
|
||||
const wchar_t SearchUI[] = L"SEARCHUI.EXE";
|
||||
const wchar_t ProjectsSnapshotTool[] = L"PROJECTSSNAPSHOTTOOL";
|
||||
const wchar_t ProjectsEditor[] = L"PROJECTSEDITOR";
|
||||
const wchar_t ProjectsLauncher[] = L"PROJECTSLAUNCHER";
|
||||
}
|
||||
|
||||
bool IsRoot(HWND window) noexcept
|
||||
{
|
||||
return GetAncestor(window, GA_ROOT) == window;
|
||||
}
|
||||
|
||||
bool IsMaximized(HWND window) noexcept
|
||||
{
|
||||
WINDOWPLACEMENT placement{};
|
||||
if (GetWindowPlacement(window, &placement) &&
|
||||
placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsExcludedByDefault(HWND window, const std::wstring& processPath, const std::wstring& title)
|
||||
{
|
||||
std::wstring processPathUpper = processPath;
|
||||
CharUpperBuffW(processPathUpper.data(), static_cast<DWORD>(processPathUpper.length()));
|
||||
|
||||
static std::vector<std::wstring> defaultExcludedFolders = { NonLocalizable::SystemAppsFolder, NonLocalizable::System, NonLocalizable::System32, NonLocalizable::SystemWOW64 };
|
||||
if (Common::Utils::ExcludedApps::find_folder_in_path(processPathUpper, defaultExcludedFolders))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<char, 256> className;
|
||||
GetClassNameA(window, className.data(), static_cast<int>(className.size()));
|
||||
if (Common::Utils::Window::is_system_window(window, className.data()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(NonLocalizable::SplashClassName, className.data()) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<std::wstring> defaultExcludedApps = { NonLocalizable::CoreWindow, NonLocalizable::SearchUI, NonLocalizable::ProjectsEditor, NonLocalizable::ProjectsLauncher, NonLocalizable::ProjectsSnapshotTool };
|
||||
return (Common::Utils::ExcludedApps::check_excluded_app(processPathUpper, title, defaultExcludedApps));
|
||||
}
|
||||
|
||||
RECT GetWindowRect(HWND window)
|
||||
{
|
||||
RECT rect;
|
||||
if (GetWindowRect(window, &rect))
|
||||
{
|
||||
float width = static_cast<float>(rect.right - rect.left);
|
||||
float height = static_cast<float>(rect.bottom - rect.top);
|
||||
float originX = static_cast<float>(rect.left);
|
||||
float originY = static_cast<float>(rect.top);
|
||||
|
||||
Common::Display::DPIAware::InverseConvert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), width, height);
|
||||
Common::Display::DPIAware::InverseConvert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), originX, originY);
|
||||
|
||||
return RECT(static_cast<LONG>(std::roundf(originX)),
|
||||
static_cast<LONG>(std::roundf(originY)),
|
||||
static_cast<LONG>(std::roundf(originX + width)),
|
||||
static_cast<LONG>(std::roundf(originY + height)));
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
229
src/modules/Projects/ProjectsSnapshotTool/WindowUtils.h
Normal file
229
src/modules/Projects/ProjectsSnapshotTool/WindowUtils.h
Normal file
@@ -0,0 +1,229 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Common
|
||||
{
|
||||
namespace Display
|
||||
{
|
||||
namespace DPIAware
|
||||
{
|
||||
void InverseConvert(HMONITOR monitor_handle, float& width, float& height);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
namespace ProcessPath
|
||||
{
|
||||
// Get the executable path or module name for modern apps
|
||||
inline std::wstring get_process_path(DWORD pid) noexcept
|
||||
{
|
||||
auto process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, TRUE, pid);
|
||||
std::wstring name;
|
||||
if (process != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
name.resize(MAX_PATH);
|
||||
DWORD name_length = static_cast<DWORD>(name.length());
|
||||
if (QueryFullProcessImageNameW(process, 0, name.data(), &name_length) == 0)
|
||||
{
|
||||
name_length = 0;
|
||||
}
|
||||
name.resize(name_length);
|
||||
CloseHandle(process);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// Get the executable path or module name for modern apps
|
||||
inline std::wstring get_process_path(HWND window) noexcept
|
||||
{
|
||||
const static std::wstring app_frame_host = L"ApplicationFrameHost.exe";
|
||||
|
||||
DWORD pid{};
|
||||
GetWindowThreadProcessId(window, &pid);
|
||||
auto name = get_process_path(pid);
|
||||
|
||||
if (name.length() >= app_frame_host.length() &&
|
||||
name.compare(name.length() - app_frame_host.length(), app_frame_host.length(), app_frame_host) == 0)
|
||||
{
|
||||
// It is a UWP app. We will enumerate the windows and look for one created
|
||||
// by something with a different PID
|
||||
DWORD new_pid = pid;
|
||||
|
||||
EnumChildWindows(
|
||||
window, [](HWND hwnd, LPARAM param) -> BOOL {
|
||||
auto new_pid_ptr = reinterpret_cast<DWORD*>(param);
|
||||
DWORD pid;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
if (pid != *new_pid_ptr)
|
||||
{
|
||||
*new_pid_ptr = pid;
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
},
|
||||
reinterpret_cast<LPARAM>(&new_pid));
|
||||
|
||||
// If we have a new pid, get the new name.
|
||||
if (new_pid != pid)
|
||||
{
|
||||
return get_process_path(new_pid);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
inline std::wstring get_process_path_waiting_uwp(HWND window)
|
||||
{
|
||||
const static std::wstring appFrameHost = L"ApplicationFrameHost.exe";
|
||||
|
||||
int attempt = 0;
|
||||
auto processPath = get_process_path(window);
|
||||
|
||||
while (++attempt < 30 && processPath.length() >= appFrameHost.length() &&
|
||||
processPath.compare(processPath.length() - appFrameHost.length(), appFrameHost.length(), appFrameHost) == 0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
processPath = get_process_path(window);
|
||||
}
|
||||
|
||||
return processPath;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ExcludedApps
|
||||
{
|
||||
inline bool find_folder_in_path(const std::wstring& where, const std::vector<std::wstring>& what)
|
||||
{
|
||||
for (const auto& row : what)
|
||||
{
|
||||
const auto pos = where.rfind(row);
|
||||
if (pos != std::wstring::npos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks if a process path is included in a list of strings.
|
||||
inline bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wstring>& what)
|
||||
{
|
||||
for (const auto& row : what)
|
||||
{
|
||||
const auto pos = where.rfind(row);
|
||||
const auto last_slash = where.rfind('\\');
|
||||
//Check that row occurs in where, and its last occurrence contains in itself the first character after the last backslash.
|
||||
if (pos != std::wstring::npos && pos <= last_slash + 1 && pos + row.length() > last_slash)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool check_excluded_app_with_title(const std::vector<std::wstring>& excludedApps, std::wstring title)
|
||||
{
|
||||
CharUpperBuffW(title.data(), static_cast<DWORD>(title.length()));
|
||||
|
||||
for (const auto& app : excludedApps)
|
||||
{
|
||||
if (title.contains(app))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool check_excluded_app(const std::wstring& processPath, const std::wstring& title, const std::vector<std::wstring>& excludedApps)
|
||||
{
|
||||
bool res = find_app_name_in_path(processPath, excludedApps);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
res = check_excluded_app_with_title(excludedApps, title);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Window
|
||||
{
|
||||
// Check if window is part of the shell or the taskbar.
|
||||
inline bool is_system_window(HWND hwnd, const char* class_name)
|
||||
{
|
||||
// We compare the HWND against HWND of the desktop and shell windows,
|
||||
// we also filter out some window class names know to belong to the taskbar.
|
||||
constexpr std::array system_classes = { "SysListView32", "WorkerW", "Shell_TrayWnd", "Shell_SecondaryTrayWnd", "Progman" };
|
||||
const std::array system_hwnds = { GetDesktopWindow(), GetShellWindow() };
|
||||
for (auto system_hwnd : system_hwnds)
|
||||
{
|
||||
if (hwnd == system_hwnd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto system_class : system_classes)
|
||||
{
|
||||
if (!strcmp(system_class, class_name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FancyZones WindowUtils
|
||||
namespace WindowUtils
|
||||
{
|
||||
bool IsRoot(HWND window) noexcept;
|
||||
bool IsMaximized(HWND window) noexcept;
|
||||
|
||||
constexpr bool HasStyle(LONG style, LONG styleToCheck) noexcept
|
||||
{
|
||||
return ((style & styleToCheck) == styleToCheck);
|
||||
}
|
||||
|
||||
bool IsExcludedByDefault(HWND window, const std::wstring& processPath, const std::wstring& title);
|
||||
|
||||
RECT GetWindowRect(HWND window);
|
||||
}
|
||||
|
||||
// addition for Projects
|
||||
namespace WindowUtils
|
||||
{
|
||||
inline bool IsMinimized(HWND window)
|
||||
{
|
||||
return IsIconic(window);
|
||||
}
|
||||
|
||||
#define MAX_TITLE_LENGTH 255
|
||||
inline std::wstring GetWindowTitle(HWND window)
|
||||
{
|
||||
WCHAR title[MAX_TITLE_LENGTH];
|
||||
int len = GetWindowTextW(window, title, MAX_TITLE_LENGTH);
|
||||
if (len <= 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::wstring(title);
|
||||
}
|
||||
|
||||
/*bool IsFullscreen(HWND window)
|
||||
{
|
||||
TODO
|
||||
}*/
|
||||
}
|
||||
136
src/modules/Projects/ProjectsSnapshotTool/main.cpp
Normal file
136
src/modules/Projects/ProjectsSnapshotTool/main.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "../projects-common/Data.h"
|
||||
#include "../projects-common/GuidUtils.h"
|
||||
#include "../projects-common/WindowEnumerator.h"
|
||||
|
||||
#include "MonitorUtils.h"
|
||||
#include "WindowFilter.h"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
HRESULT comInitHres = CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
if (FAILED(comInitHres))
|
||||
{
|
||||
std::wcout << L"Failed to initialize COM library. " << comInitHres << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::wstring fileName = JsonUtils::ProjectsFile();
|
||||
if (argc > 1)
|
||||
{
|
||||
std::string fileNameParam = argv[1];
|
||||
std::wstring filenameStr(fileNameParam.begin(), fileNameParam.end());
|
||||
fileName = filenameStr;
|
||||
}
|
||||
|
||||
// read previously saved projects
|
||||
std::vector<Project> 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)
|
||||
{
|
||||
}
|
||||
|
||||
// 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);
|
||||
time_t creationTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
Project project{ .id = CreateGuidString(), .name = projectName, .creationTime = creationTime };
|
||||
|
||||
// save monitor configuration
|
||||
project.monitors = MonitorUtils::IdentifyMonitors();
|
||||
|
||||
// get list of windows
|
||||
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 = Common::Utils::ProcessPath::get_process_path_waiting_uwp(window);
|
||||
if (WindowUtils::IsExcludedByDefault(window, processPath, title))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto windowMonitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
|
||||
int monitorNumber = 0;
|
||||
for (const auto& monitor : project.monitors)
|
||||
{
|
||||
if (monitor.monitor == windowMonitor)
|
||||
{
|
||||
monitorNumber = monitor.number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Project::Application app {
|
||||
.hwnd = window,
|
||||
.appPath = processPath,
|
||||
.appTitle = title,
|
||||
.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,
|
||||
};
|
||||
|
||||
project.apps.push_back(app);
|
||||
}
|
||||
|
||||
projects.push_back(project);
|
||||
json::to_file(fileName, JsonUtils::ProjectsListJSON::ToJson(projects));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.220531.1" targetFramework="native" />
|
||||
</packages>
|
||||
1
src/modules/Projects/ProjectsSnapshotTool/pch.cpp
Normal file
1
src/modules/Projects/ProjectsSnapshotTool/pch.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
3
src/modules/Projects/ProjectsSnapshotTool/pch.h
Normal file
3
src/modules/Projects/ProjectsSnapshotTool/pch.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
405
src/modules/Projects/projects-common/Data.h
Normal file
405
src/modules/Projects/projects-common/Data.h
Normal file
@@ -0,0 +1,405 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <shlobj.h>
|
||||
|
||||
#include "json.h"
|
||||
|
||||
struct Project
|
||||
{
|
||||
struct Application
|
||||
{
|
||||
struct Position
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
RECT toRect() const noexcept
|
||||
{
|
||||
return RECT{.left = x, .top = y, .right = x + width, .bottom = y + height };
|
||||
}
|
||||
};
|
||||
|
||||
HWND hwnd{};
|
||||
std::wstring appPath;
|
||||
std::wstring appTitle;
|
||||
std::wstring commandLineArgs;
|
||||
bool isMinimized{};
|
||||
bool isMaximized{};
|
||||
Position position{};
|
||||
int monitor{};
|
||||
};
|
||||
|
||||
struct Monitor
|
||||
{
|
||||
struct MonitorRect
|
||||
{
|
||||
int top;
|
||||
int left;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
HMONITOR monitor{};
|
||||
std::wstring id;
|
||||
std::wstring instanceId;
|
||||
unsigned int number{};
|
||||
unsigned int dpi{};
|
||||
MonitorRect monitorRectDpiAware{};
|
||||
MonitorRect monitorRectDpiUnaware{};
|
||||
};
|
||||
|
||||
std::wstring id;
|
||||
std::wstring name;
|
||||
time_t creationTime;
|
||||
std::optional<time_t> lastLaunchedTime;
|
||||
bool isShortcutNeeded;
|
||||
std::vector<Monitor> monitors;
|
||||
std::vector<Application> apps;
|
||||
};
|
||||
|
||||
struct ProjectsList
|
||||
{
|
||||
std::vector<Project> projects;
|
||||
};
|
||||
|
||||
namespace JsonUtils
|
||||
{
|
||||
inline std::wstring ProjectsFile()
|
||||
{
|
||||
wchar_t path[MAX_PATH + 1] = { 0 };
|
||||
SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, path);
|
||||
return std::wstring(path) + L"\\projects.json";
|
||||
}
|
||||
|
||||
namespace ProjectJSON
|
||||
{
|
||||
namespace ApplicationJSON
|
||||
{
|
||||
namespace PositionJSON
|
||||
{
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const static wchar_t* XAxisID = L"X";
|
||||
const static wchar_t* YAxisID = L"Y";
|
||||
const static wchar_t* WidthID = L"width";
|
||||
const static wchar_t* HeightID = L"height";
|
||||
}
|
||||
|
||||
inline json::JsonObject ToJson(const Project::Application::Position& position)
|
||||
{
|
||||
json::JsonObject json{};
|
||||
json.SetNamedValue(NonLocalizable::XAxisID, json::value(position.x));
|
||||
json.SetNamedValue(NonLocalizable::YAxisID, json::value(position.y));
|
||||
json.SetNamedValue(NonLocalizable::WidthID, json::value(position.width));
|
||||
json.SetNamedValue(NonLocalizable::HeightID, json::value(position.height));
|
||||
return json;
|
||||
}
|
||||
|
||||
inline std::optional<Project::Application::Position> FromJson(const json::JsonObject& json)
|
||||
{
|
||||
Project::Application::Position result;
|
||||
try
|
||||
{
|
||||
result.x = static_cast<int>(json.GetNamedNumber(NonLocalizable::XAxisID, 0));
|
||||
result.y = static_cast<int>(json.GetNamedNumber(NonLocalizable::YAxisID, 0));
|
||||
result.width = static_cast<int>(json.GetNamedNumber(NonLocalizable::WidthID, 0));
|
||||
result.height = static_cast<int>(json.GetNamedNumber(NonLocalizable::HeightID, 0));
|
||||
}
|
||||
catch (const winrt::hresult_error&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const static wchar_t* AppPathID = L"application";
|
||||
const static wchar_t* HwndID = L"hwnd";
|
||||
const static wchar_t* AppTitleID = L"title";
|
||||
const static wchar_t* CommandLineArgsID = L"command-line-arguments";
|
||||
const static wchar_t* MinimizedID = L"minimized";
|
||||
const static wchar_t* MaximizedID = L"maximized";
|
||||
const static wchar_t* PositionID = L"position";
|
||||
const static wchar_t* MonitorID = L"monitor";
|
||||
}
|
||||
|
||||
inline json::JsonObject ToJson(const Project::Application& data)
|
||||
{
|
||||
json::JsonObject json{};
|
||||
json.SetNamedValue(NonLocalizable::HwndID, json::value(static_cast<double>(reinterpret_cast<long long>(data.hwnd))));
|
||||
json.SetNamedValue(NonLocalizable::AppPathID, json::value(data.appPath));
|
||||
json.SetNamedValue(NonLocalizable::AppTitleID, json::value(data.appTitle));
|
||||
json.SetNamedValue(NonLocalizable::CommandLineArgsID, json::value(data.commandLineArgs));
|
||||
json.SetNamedValue(NonLocalizable::MinimizedID, json::value(data.isMinimized));
|
||||
json.SetNamedValue(NonLocalizable::MaximizedID, json::value(data.isMaximized));
|
||||
json.SetNamedValue(NonLocalizable::PositionID, PositionJSON::ToJson(data.position));
|
||||
json.SetNamedValue(NonLocalizable::MonitorID, json::value(data.monitor));
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
inline std::optional<Project::Application> FromJson(const json::JsonObject& json)
|
||||
{
|
||||
Project::Application result;
|
||||
try
|
||||
{
|
||||
result.hwnd = reinterpret_cast<HWND>(static_cast<long long>(json.GetNamedNumber(NonLocalizable::HwndID)));
|
||||
result.appPath = json.GetNamedString(NonLocalizable::AppPathID);
|
||||
result.appTitle = json.GetNamedString(NonLocalizable::AppTitleID);
|
||||
result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID);
|
||||
result.isMaximized = json.GetNamedBoolean(NonLocalizable::MaximizedID);
|
||||
result.isMinimized = json.GetNamedBoolean(NonLocalizable::MinimizedID);
|
||||
result.monitor = static_cast<int>(json.GetNamedNumber(NonLocalizable::MonitorID));
|
||||
if (json.HasKey(NonLocalizable::PositionID))
|
||||
{
|
||||
auto position = PositionJSON::FromJson(json.GetNamedObject(NonLocalizable::PositionID));
|
||||
if (!position.has_value())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.position = position.value();
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MonitorJSON
|
||||
{
|
||||
namespace MonitorRectJSON
|
||||
{
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const static wchar_t* TopID = L"top";
|
||||
const static wchar_t* LeftID = L"left";
|
||||
const static wchar_t* WidthID = L"width";
|
||||
const static wchar_t* HeightID = L"height";
|
||||
}
|
||||
|
||||
inline json::JsonObject ToJson(const Project::Monitor::MonitorRect& data)
|
||||
{
|
||||
json::JsonObject json{};
|
||||
json.SetNamedValue(NonLocalizable::TopID, json::value(data.top));
|
||||
json.SetNamedValue(NonLocalizable::LeftID, json::value(data.left));
|
||||
json.SetNamedValue(NonLocalizable::WidthID, json::value(data.width));
|
||||
json.SetNamedValue(NonLocalizable::HeightID, json::value(data.height));
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
inline std::optional<Project::Monitor::MonitorRect> FromJson(const json::JsonObject& json)
|
||||
{
|
||||
Project::Monitor::MonitorRect result;
|
||||
try
|
||||
{
|
||||
result.top = static_cast<int>(json.GetNamedNumber(NonLocalizable::TopID));
|
||||
result.left = static_cast<int>(json.GetNamedNumber(NonLocalizable::LeftID));
|
||||
result.width = static_cast<int>(json.GetNamedNumber(NonLocalizable::WidthID));
|
||||
result.height = static_cast<int>(json.GetNamedNumber(NonLocalizable::HeightID));
|
||||
}
|
||||
catch (const winrt::hresult_error&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const static wchar_t* MonitorID = L"id";
|
||||
const static wchar_t* InstanceID = L"instance-id";
|
||||
const static wchar_t* NumberID = L"monitor-number";
|
||||
const static wchar_t* DpiID = L"dpi";
|
||||
const static wchar_t* MonitorRectDpiAwareID = L"monitor-rect-dpi-aware";
|
||||
const static wchar_t* MonitorRectDpiUnawareID = L"monitor-rect-dpi-unaware";
|
||||
}
|
||||
|
||||
inline json::JsonObject ToJson(const Project::Monitor& data)
|
||||
{
|
||||
json::JsonObject json{};
|
||||
json.SetNamedValue(NonLocalizable::MonitorID, json::value(data.id));
|
||||
json.SetNamedValue(NonLocalizable::InstanceID, json::value(data.instanceId));
|
||||
json.SetNamedValue(NonLocalizable::NumberID, json::value(data.number));
|
||||
json.SetNamedValue(NonLocalizable::DpiID, json::value(data.dpi));
|
||||
json.SetNamedValue(NonLocalizable::MonitorRectDpiAwareID, MonitorRectJSON::ToJson(data.monitorRectDpiAware));
|
||||
json.SetNamedValue(NonLocalizable::MonitorRectDpiUnawareID, MonitorRectJSON::ToJson(data.monitorRectDpiUnaware));
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
inline std::optional<Project::Monitor> FromJson(const json::JsonObject& json)
|
||||
{
|
||||
Project::Monitor result;
|
||||
try
|
||||
{
|
||||
result.id = json.GetNamedString(NonLocalizable::MonitorID);
|
||||
result.instanceId = json.GetNamedString(NonLocalizable::InstanceID);
|
||||
result.number = static_cast<int>(json.GetNamedNumber(NonLocalizable::NumberID));
|
||||
result.dpi = static_cast<int>(json.GetNamedNumber(NonLocalizable::DpiID));
|
||||
auto rectDpiAware = MonitorRectJSON::FromJson(json.GetNamedObject(NonLocalizable::MonitorRectDpiAwareID));
|
||||
if (!rectDpiAware.has_value())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto rectDpiUnaware = MonitorRectJSON::FromJson(json.GetNamedObject(NonLocalizable::MonitorRectDpiUnawareID));
|
||||
if (!rectDpiUnaware.has_value())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.monitorRectDpiAware = rectDpiAware.value();
|
||||
result.monitorRectDpiUnaware = rectDpiUnaware.value();
|
||||
}
|
||||
catch (const winrt::hresult_error&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const static wchar_t* IdID = L"id";
|
||||
const static wchar_t* NameID = L"name";
|
||||
const static wchar_t* CreationTimeID = L"creation-time";
|
||||
const static wchar_t* LastLaunchedTimeID = L"last-launched-time";
|
||||
const static wchar_t* IsShortcutNeededID = L"is-shortcut-needed";
|
||||
const static wchar_t* MonitorConfigurationID = L"monitor-configuration";
|
||||
const static wchar_t* AppsID = L"applications";
|
||||
}
|
||||
|
||||
inline json::JsonObject ToJson(const Project& data)
|
||||
{
|
||||
json::JsonObject json{};
|
||||
|
||||
json::JsonArray appsArray{};
|
||||
for (const auto& app : data.apps)
|
||||
{
|
||||
appsArray.Append(ApplicationJSON::ToJson(app));
|
||||
}
|
||||
|
||||
json::JsonArray monitorsArray{};
|
||||
for (const auto& monitor : data.monitors)
|
||||
{
|
||||
monitorsArray.Append(MonitorJSON::ToJson(monitor));
|
||||
}
|
||||
|
||||
json.SetNamedValue(NonLocalizable::IdID, json::value(data.id));
|
||||
json.SetNamedValue(NonLocalizable::NameID, json::value(data.name));
|
||||
json.SetNamedValue(NonLocalizable::CreationTimeID, json::value(static_cast<long>(data.creationTime)));
|
||||
if (data.lastLaunchedTime.has_value())
|
||||
{
|
||||
json.SetNamedValue(NonLocalizable::LastLaunchedTimeID, json::value(static_cast<long>(data.lastLaunchedTime.value())));
|
||||
}
|
||||
json.SetNamedValue(NonLocalizable::IsShortcutNeededID, json::value(data.isShortcutNeeded));
|
||||
json.SetNamedValue(NonLocalizable::MonitorConfigurationID, monitorsArray);
|
||||
json.SetNamedValue(NonLocalizable::AppsID, appsArray);
|
||||
return json;
|
||||
}
|
||||
|
||||
inline std::optional<Project> FromJson(const json::JsonObject& json)
|
||||
{
|
||||
Project result{};
|
||||
|
||||
try
|
||||
{
|
||||
result.id = json.GetNamedString(NonLocalizable::IdID);
|
||||
result.name = json.GetNamedString(NonLocalizable::NameID);
|
||||
result.creationTime = static_cast<time_t>(json.GetNamedNumber(NonLocalizable::CreationTimeID));
|
||||
|
||||
if (json.HasKey(NonLocalizable::LastLaunchedTimeID))
|
||||
{
|
||||
result.lastLaunchedTime = static_cast<time_t>(json.GetNamedNumber(NonLocalizable::LastLaunchedTimeID));
|
||||
}
|
||||
result.isShortcutNeeded = json.GetNamedBoolean(NonLocalizable::IsShortcutNeededID);
|
||||
|
||||
auto appsArray = json.GetNamedArray(NonLocalizable::AppsID);
|
||||
for (uint32_t i = 0; i < appsArray.Size(); ++i)
|
||||
{
|
||||
if (auto obj = ApplicationJSON::FromJson(appsArray.GetObjectAt(i)); obj.has_value())
|
||||
{
|
||||
result.apps.push_back(obj.value());
|
||||
}
|
||||
}
|
||||
|
||||
auto monitorsArray = json.GetNamedArray(NonLocalizable::MonitorConfigurationID);
|
||||
for (uint32_t i = 0; i < monitorsArray.Size(); ++i)
|
||||
{
|
||||
if (auto obj = MonitorJSON::FromJson(monitorsArray.GetObjectAt(i)); obj.has_value())
|
||||
{
|
||||
result.monitors.push_back(obj.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ProjectsListJSON
|
||||
{
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const static wchar_t* ProjectsID = L"projects";
|
||||
}
|
||||
|
||||
inline json::JsonObject ToJson(const std::vector<Project>& data)
|
||||
{
|
||||
json::JsonObject json{};
|
||||
json::JsonArray projectsArray{};
|
||||
|
||||
for (const auto& project : data)
|
||||
{
|
||||
projectsArray.Append(ProjectJSON::ToJson(project));
|
||||
}
|
||||
|
||||
json.SetNamedValue(NonLocalizable::ProjectsID, projectsArray);
|
||||
return json;
|
||||
}
|
||||
|
||||
inline std::optional<std::vector<Project>> FromJson(const json::JsonObject& json)
|
||||
{
|
||||
std::vector<Project> result{};
|
||||
|
||||
try
|
||||
{
|
||||
auto array = json.GetNamedArray(NonLocalizable::ProjectsID);
|
||||
for (uint32_t i = 0; i < array.Size(); ++i)
|
||||
{
|
||||
if (auto obj = ProjectJSON::FromJson(array.GetObjectAt(i)); obj.has_value())
|
||||
{
|
||||
result.push_back(obj.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const winrt::hresult_error&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/modules/Projects/projects-common/GuidUtils.h
Normal file
34
src/modules/Projects/projects-common/GuidUtils.h
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
|
||||
inline std::optional<GUID> GuidFromString(const std::wstring& str) noexcept
|
||||
{
|
||||
GUID id;
|
||||
if (SUCCEEDED(CLSIDFromString(str.c_str(), &id)))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline std::wstring GuidToString(const GUID& guid) noexcept
|
||||
{
|
||||
OLECHAR* guidString;
|
||||
StringFromCLSID(guid, &guidString);
|
||||
|
||||
std::wstring guidWString(guidString);
|
||||
::CoTaskMemFree(guidString);
|
||||
|
||||
return guidWString;
|
||||
}
|
||||
|
||||
inline std::wstring CreateGuidString()
|
||||
{
|
||||
GUID guid;
|
||||
if (CoCreateGuid(&guid) == S_OK)
|
||||
{
|
||||
return GuidToString(guid);
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
35
src/modules/Projects/projects-common/MonitorEnumerator.h
Normal file
35
src/modules/Projects/projects-common/MonitorEnumerator.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <Windows.h>
|
||||
|
||||
class MonitorEnumerator
|
||||
{
|
||||
public:
|
||||
static std::vector<std::pair<HMONITOR, MONITORINFOEX>> Enumerate()
|
||||
{
|
||||
MonitorEnumerator inst;
|
||||
EnumDisplayMonitors(NULL, NULL, Callback, reinterpret_cast<LPARAM>(&inst));
|
||||
return inst.m_monitors;
|
||||
}
|
||||
|
||||
private:
|
||||
MonitorEnumerator() = default;
|
||||
~MonitorEnumerator() = default;
|
||||
|
||||
static BOOL CALLBACK Callback(HMONITOR monitor, HDC /*hdc*/, LPRECT /*pRect*/, LPARAM param)
|
||||
{
|
||||
MonitorEnumerator* inst = reinterpret_cast<MonitorEnumerator*>(param);
|
||||
MONITORINFOEX mi;
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfo(monitor, &mi))
|
||||
{
|
||||
inst->m_monitors.push_back({monitor, mi});
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::vector<std::pair<HMONITOR, MONITORINFOEX>> m_monitors;
|
||||
};
|
||||
35
src/modules/Projects/projects-common/WindowEnumerator.h
Normal file
35
src/modules/Projects/projects-common/WindowEnumerator.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <Windows.h>
|
||||
|
||||
class WindowEnumerator
|
||||
{
|
||||
public:
|
||||
static std::vector<HWND> Enumerate(const std::function<bool(HWND)>& filter)
|
||||
{
|
||||
WindowEnumerator inst;
|
||||
inst.m_filter = filter;
|
||||
EnumWindows(Callback, reinterpret_cast<LPARAM>(&inst));
|
||||
return inst.m_windows;
|
||||
}
|
||||
|
||||
private:
|
||||
WindowEnumerator() = default;
|
||||
~WindowEnumerator() = default;
|
||||
|
||||
static BOOL CALLBACK Callback(HWND window, LPARAM data)
|
||||
{
|
||||
WindowEnumerator* inst = reinterpret_cast<WindowEnumerator*>(data);
|
||||
if (inst->m_filter(window))
|
||||
{
|
||||
inst->m_windows.push_back(window);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::vector<HWND> m_windows;
|
||||
std::function<bool(HWND)> m_filter = [](HWND) { return true; };
|
||||
};
|
||||
110
src/modules/Projects/projects-common/json.h
Normal file
110
src/modules/Projects/projects-common/json.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Data.Json.h>
|
||||
|
||||
#include <optional>
|
||||
#include <fstream>
|
||||
|
||||
// common/utils/json.h
|
||||
namespace json
|
||||
{
|
||||
using namespace winrt::Windows::Data::Json;
|
||||
|
||||
inline std::optional<JsonObject> from_file(std::wstring_view file_name)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream file(file_name.data(), std::ios::binary);
|
||||
if (file.is_open())
|
||||
{
|
||||
using isbi = std::istreambuf_iterator<char>;
|
||||
std::string obj_str{ isbi{ file }, isbi{} };
|
||||
return JsonValue::Parse(winrt::to_hstring(obj_str)).GetObjectW();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
inline void to_file(std::wstring_view file_name, const JsonObject& obj)
|
||||
{
|
||||
std::wstring obj_str{ obj.Stringify().c_str() };
|
||||
std::ofstream{ file_name.data(), std::ios::binary } << winrt::to_string(obj_str);
|
||||
}
|
||||
|
||||
inline bool has(
|
||||
const json::JsonObject& o,
|
||||
std::wstring_view name,
|
||||
const json::JsonValueType type = JsonValueType::Object)
|
||||
{
|
||||
return o.HasKey(name) && o.GetNamedValue(name).ValueType() == type;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::enable_if_t<std::is_arithmetic_v<T>, JsonValue> value(const T arithmetic)
|
||||
{
|
||||
return json::JsonValue::CreateNumberValue(arithmetic);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::enable_if_t<!std::is_arithmetic_v<T>, JsonValue> value(T s)
|
||||
{
|
||||
return json::JsonValue::CreateStringValue(s);
|
||||
}
|
||||
|
||||
inline JsonValue value(const bool boolean)
|
||||
{
|
||||
return json::JsonValue::CreateBooleanValue(boolean);
|
||||
}
|
||||
|
||||
inline JsonValue value(JsonObject value)
|
||||
{
|
||||
return value.as<JsonValue>();
|
||||
}
|
||||
|
||||
inline JsonValue value(JsonValue value)
|
||||
{
|
||||
return value; // identity function overload for convenience
|
||||
}
|
||||
|
||||
template<typename T, typename D = std::optional<T>>
|
||||
requires std::constructible_from<std::optional<T>, D>
|
||||
void get(const json::JsonObject& o, const wchar_t* name, T& destination, D default_value = std::nullopt)
|
||||
{
|
||||
try
|
||||
{
|
||||
if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
destination = o.GetNamedBoolean(name);
|
||||
}
|
||||
else if constexpr (std::is_arithmetic_v<T>)
|
||||
{
|
||||
destination = static_cast<T>(o.GetNamedNumber(name));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::wstring>)
|
||||
{
|
||||
destination = o.GetNamedString(name);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, json::JsonObject>)
|
||||
{
|
||||
destination = o.GetNamedObject(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(std::bool_constant<std::is_same_v<T, T&>>::value, "Unsupported type");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::optional<T> maybe_default{ std::move(default_value) };
|
||||
if (maybe_default.has_value())
|
||||
destination = std::move(*maybe_default);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user