Compare commits

...

115 Commits

Author SHA1 Message Date
Jaime Bernardo
98ef8c4e70 Fix ARM64 CI build 2024-06-26 11:21:13 +01:00
seraphima
c89b4bb9fd fix launching with command line args 2024-06-24 16:34:17 +02:00
donlaci
17a607fda0 [Projects] fix boundary calculation, use DPI aware values 2024-06-24 10:38:08 +02:00
donlaci
371a05ac5e [Projects] Re-implementing preview drawing - one common image 2024-06-24 09:34:47 +02:00
seraphima
65e75d1c42 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-23 23:15:32 +02:00
seraphima
15ae5a03dc app placement 2024-06-23 23:15:26 +02:00
seraphima
f39e4bb058 set awareness 2024-06-23 23:15:06 +02:00
seraphima
0679664d4c unsigned monitor number 2024-06-23 23:14:57 +02:00
seraphima
5e012b8891 added convert rect 2024-06-23 23:11:24 +02:00
seraphima
77c59faa8a moved display utils 2024-06-23 23:10:54 +02:00
seraphima
ed7d3ec973 moved on thread executor to common 2024-06-23 13:17:29 +02:00
donlaci
26bbefa004 [Projects] Removing not selected apps on save 2024-06-20 13:31:56 +02:00
donlaci
e6b56c4152 [Projects] optimizing click handlers 2024-06-20 13:25:32 +02:00
donlaci
4c4630ef75 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-20 13:23:18 +02:00
donlaci
6fc164df98 [Projects] bringing back the breadcrumb on the editor page. Make it clickable. 2024-06-20 13:22:02 +02:00
seraphima
5f42c6ef66 fix placing windows of the same app in the project 2024-06-20 12:54:47 +02:00
seraphima
1e28cb8c40 remove opening the first proj 2024-06-20 12:54:39 +02:00
seraphima
0c83e632ed changed "no projects" text color and position 2024-06-20 11:29:13 +02:00
donlaci
852d67b569 [Projects] Making popup having rounded corners 2024-06-20 11:02:04 +02:00
donlaci
01d6c916d6 [Projects] Adding Edit button to the popup. + minor changes 2024-06-19 15:17:25 +02:00
donlaci
90979cbecb [projects] Adding info message for cases: there are no projects or no results for the search 2024-06-19 14:58:29 +02:00
donlaci
20a7dd4a7f Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 12:29:17 +02:00
donlaci
ba19333ad7 [Projects] re-implementing icon size calculation to have similar sized icons for every app. 2024-06-19 12:28:09 +02:00
seraphima
6eafa57c68 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 11:17:20 +02:00
seraphima
8c450f3457 spellcheck 2024-06-19 11:16:58 +02:00
donlaci
0137bc9697 spell checker 2024-06-19 11:11:27 +02:00
donlaci
e4ccde49f7 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 11:03:55 +02:00
donlaci
0efe6c91e8 [Projects] Editor: brining the highlighted app's icon into the foreground. + minor UI fixes 2024-06-19 11:02:41 +02:00
seraphima
a62d95c8fa shortcut saving fix 2024-06-19 00:06:57 +02:00
seraphima
ba13f5c0c4 update projects names to filter them out 2024-06-18 22:10:48 +02:00
seraphima
bd50d6961d editor dll signing 2024-06-18 16:44:23 +02:00
seraphima
fbb9f4188f spellcheck 2024-06-17 20:40:16 +02:00
seraphima
ae91aa3869 editor version 2024-06-17 20:40:07 +02:00
seraphima
4cccbecf54 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 19:57:14 +02:00
seraphima
0e74b2ee6b version 2024-06-17 19:57:09 +02:00
donlaci
f7cab16fb6 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 15:22:34 +02:00
donlaci
bfb569e894 Do not allow saving project if name or applist is empty. Also minor UI changes 2024-06-17 15:21:13 +02:00
seraphima
ce39ef2360 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 14:09:35 +02:00
seraphima
0c3108bca1 arm build fix 2024-06-17 14:09:31 +02:00
donlaci
28bf6de36c [Projects] fixing general settings gpo handling in runner + minor changes 2024-06-17 10:31:34 +02:00
seraphima
3de23f1c82 guid prefix 2024-06-14 16:26:20 +02:00
seraphima
198b7a6890 Merge remote-tracking branch 'microsoft/main' into dev/feature/projects 2024-06-14 12:52:38 +02:00
seraphima
f9fe9cc204 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-14 12:15:10 +02:00
seraphima
5d09e2e821 pipeline 2024-06-14 12:14:44 +02:00
seraphima
9914d31d6b github 2024-06-14 12:14:13 +02:00
seraphima
add5078f43 dsc 2024-06-14 12:13:41 +02:00
donlaci
68a66ff03d [Projects] Editor: Main page: fix layout if there are many apps, launch button not disappearing on the right side 2024-06-14 11:50:56 +02:00
donlaci
9cac577b7f [Projects] fix grammatical issue #43 (1 app - many apps) 2024-06-14 10:50:26 +02:00
donlaci
cc9b7d62df extend search for projects by search over the containing apps' names 2024-06-14 10:36:09 +02:00
seraphima
27d0620cac exit event constant 2024-06-13 23:11:26 +02:00
seraphima
6a353e0941 gpo 2024-06-13 23:07:43 +02:00
seraphima
73146e844e installer 2024-06-13 22:55:36 +02:00
seraphima
07ab6191d0 bug report tool 2024-06-13 21:52:04 +02:00
seraphima
4a672e2ed5 rename projects editor 2024-06-13 21:30:26 +02:00
seraphima
f869f99144 module interface 2024-06-13 21:30:03 +02:00
seraphima
2493fd6a1a changed the default hotkey 2024-06-13 21:29:08 +02:00
donlaci
64c0ca77bf Adding OOBE Projects page 2024-06-13 17:32:34 +02:00
donlaci
0bdfa32cd0 spell checker 2024-06-13 13:31:06 +02:00
donlaci
8f69109689 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-13 13:24:25 +02:00
donlaci
5abba947d8 adding GPO role + wrappers + GPO-check methods 2024-06-13 13:20:21 +02:00
seraphima
12bc9d7206 spelling 2024-06-13 12:34:08 +02:00
seraphima
a5c21d3432 snapshot logger 2024-06-12 22:00:11 +02:00
seraphima
6bfe924234 remove quotation marks 2024-06-12 21:47:31 +02:00
seraphima
3cc3701465 launcher logger 2024-06-12 21:45:39 +02:00
seraphima
71c7241fe1 common dependencies 2024-06-12 19:31:43 +02:00
seraphima
ae6cb122c8 init projects 2024-06-12 17:52:28 +02:00
seraphima
6fba21d9ad update data folders 2024-06-12 17:52:04 +02:00
seraphima
7e93d1f767 updated app names in editor 2024-06-12 17:45:27 +02:00
seraphima
e08aa76085 changed editor output path 2024-06-12 17:42:38 +02:00
seraphima
e5bbeb738e updated SnapshotTool project 2024-06-12 17:24:50 +02:00
seraphima
404a189c87 update Launcher project 2024-06-12 17:24:19 +02:00
seraphima
26dd8ea18e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-12 14:13:10 +02:00
seraphima
80f4611b0a shortcut icons 2024-06-12 13:16:34 +02:00
seraphima
a5b69d9be5 optimized launching 2024-06-11 15:43:42 +02:00
donlaci
927ac511c8 First steps toward integration of the projects utility to PT 2024-06-11 13:32:40 +02:00
seraphima
07e0ef8a2c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-10 15:58:59 +02:00
donlaci
1cf5d88507 minor adjustments on the not found warning icon 2024-06-10 15:57:28 +02:00
seraphima
1a33257314 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-10 14:19:34 +02:00
donlaci
6c7b99fbbc Adding not found icon + logic for not present detection. Moving icon drawing into drawhelper class. adjusting "selected cursor" size for preview drawing 2024-06-10 10:19:57 +02:00
seraphima
29e0bad3ea changed waiting time 2024-06-06 16:47:48 +02:00
seraphima
67a336bc46 checkbox default style 2024-06-05 19:10:50 +02:00
seraphima
40f2572b22 save-cancel buttons style 2024-06-05 19:10:38 +02:00
seraphima
cdb40a91fa remove unused 2024-06-05 19:10:23 +02:00
seraphima
3dea02a981 updated light theme colors 2024-06-05 18:58:30 +02:00
seraphima
4b371726c2 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-05 16:30:37 +02:00
seraphima
93bda77554 removed unused package 2024-06-05 16:30:25 +02:00
seraphima
47ccfde156 removed log 2024-06-05 16:30:14 +02:00
seraphima
782a2e1b1e check single-instance apps running 2024-06-05 16:28:00 +02:00
seraphima
5c600ebc92 moved app utils 2024-06-05 16:27:40 +02:00
donlaci
b07b15cf42 Modifying preview drawing colors for light theme. 2024-06-05 13:58:39 +02:00
donlaci
910b259ce1 fixing colors of the remove button (dark and light mode) 2024-06-05 12:53:59 +02:00
seraphima
3d75b8471c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-05 11:28:45 +02:00
seraphima
f5cc0cba40 detecting and placing new windows 2024-06-05 11:28:35 +02:00
donlaci
449078be8e minor modification in light mode colors 2024-06-05 09:53:01 +02:00
donlaci
690b537e9f spell check 2024-06-04 12:55:05 +02:00
donlaci
af49b36d20 adding shortcut icon drawing. Minor changes 2024-06-04 12:45:50 +02:00
seraphima
9867395b5b moved utils to common 2024-06-03 16:49:08 +02:00
donlaci
cb2a4ec6e9 corrections in the snippet tool.
extending icon handling in the editor: for packaged apps look for the exe in the current path
adding shortcut icon creation.
2024-05-31 19:09:45 +02:00
donlaci
7e80c1bf73 spell checker 2024-05-29 13:51:28 +02:00
donlaci
24add7d4f8 adding dependent projects to the projects solution 2024-05-28 19:43:32 +02:00
donlaci
e11deec96f Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-05-28 18:26:36 +02:00
donlaci
feba2e6f17 adding logs 2024-05-28 18:21:30 +02:00
seraphima
c18c70afdb spellcheck 2024-05-27 21:44:42 +02:00
seraphima
aa788dad04 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-05-27 17:01:06 +02:00
seraphima
42905045b4 launch 2024-05-27 16:33:56 +02:00
seraphima
ccaf327baa run launcher 2024-05-27 16:33:30 +02:00
seraphima
6f813d9e66 app name fix 2024-05-27 16:33:13 +02:00
seraphima
2b71d97449 removed hwnd from json 2024-05-27 16:23:03 +02:00
seraphima
9f61f0793d updated app data 2024-05-27 15:15:34 +02:00
seraphima
cc553a60d5 get packaged app info 2024-05-27 15:07:53 +02:00
donlaci
3cd57eee63 creating drawing helper class to separate drawing functionallity 2024-05-24 15:36:01 +02:00
seraphima
6f6c32f989 update packages 2024-05-24 13:50:27 +02:00
seraphima
15c3956eda spelling 2024-05-21 17:48:58 +02:00
seraphima
ce19c4e5ff spelling 2024-05-21 17:45:07 +02:00
seraphima
c157e64f28 Projects 2024-05-21 16:55:15 +02:00
142 changed files with 9093 additions and 4 deletions

View File

@@ -68,6 +68,7 @@ body:
- Peek
- PowerRename
- PowerToys Run
- Projects
- Quick Accent
- Registry Preview
- Screen ruler

View File

@@ -42,6 +42,7 @@ body:
- Peek
- PowerRename
- PowerToys Run
- Projects
- Quick Accent
- Registry Preview
- Screen ruler

View File

@@ -61,6 +61,7 @@ appmanifest
APPNAME
appref
appsettings
appsfolder
appwindow
appwiz
APSTUDIO
@@ -238,6 +239,7 @@ CONTEXTMENUHANDLER
CONTROLL
CONTROLPARENT
copiedcolorrepresentation
COREWINDOW
cotaskmem
COULDNOT
countof
@@ -1205,9 +1207,14 @@ PRODUCTVERSION
Progman
programdata
projectname
PROJECTSEDITOR
PROJECTSLAUNCHER
PROJECTSSNAPSHOTTOOL
PROPBAG
PROPERTYKEY
propkey
propsys
PROPVARIANT
propvarutil
prvpane
psapi
@@ -1373,6 +1380,7 @@ sddl
SDKDDK
sdns
searchterm
SEARCHUI
secpol
SENDCHANGE
sendinput
@@ -1556,7 +1564,9 @@ SYSKEYUP
SYSLIB
SYSMENU
SYSTEMAPPS
systemsettings
SYSTEMTIME
SYSTEMWOW
tapp
TApplication
TApplied
@@ -1668,9 +1678,11 @@ urlmon
Usb
USEDEFAULT
USEFILEATTRIBUTES
USEPOSITION
USERDATA
Userenv
USESHOWWINDOW
USESIZE
USESTDHANDLES
USRDLL
UType

View File

@@ -189,6 +189,12 @@
"WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll",
"WinUI3Apps\\PowerRenameContextMenuPackage.msix",
"PowerToys.ProjectsSnapshotTool.exe",
"PowerToys.ProjectsLauncher.exe",
"PowerToys.ProjectsEditor.exe",
"PowerToys.ProjectsEditor.dll",
"PowerToys.ProjectsModuleInterface.dll",
"WinUI3Apps\\PowerToys.RegistryPreviewExt.dll",
"WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll",
"WinUI3Apps\\PowerToys.RegistryPreview.dll",

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@
<?define AdvancedPasteProjectName="AdvancedPaste"?>
<?define RegistryPreviewProjectName="RegistryPreview"?>
<?define PeekProjectName="Peek"?>
<?define ProjectsProjectName="Projects"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?if $(var.Platform) = x64?>

View File

@@ -449,6 +449,15 @@
</RegistryKey>
<File Id="PowerOCR_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.PowerOCR.resources.dll" />
</Component>
<Component
Id="ProjectsEditor_$(var.IdSafeLanguage)_Component"
Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER"
Guid="$(var.CompGUIDPrefix)21">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="ProjectsEditor_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="ProjectsEditor_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.ProjectsEditor.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
<?endforeach?>

View File

@@ -1223,7 +1223,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 32> processesToTerminate = {
std::array<std::wstring_view, 35> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.AdvancedPaste.exe",
@@ -1255,6 +1255,9 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
L"PowerToys.MouseWithoutBordersService.exe",
L"PowerToys.CropAndLock.exe",
L"PowerToys.EnvironmentVariables.exe",
L"PowerToys.ProjectsSnapshotTool.exe",
L"PowerToys.ProjectsLauncher.exe",
L"PowerToys.ProjectsEditor.exe",
L"PowerToys.exe",
};

View File

@@ -31,6 +31,7 @@ namespace Common.UI
EnvironmentVariables,
Dashboard,
AdvancedPaste,
Projects,
}
private static string SettingsWindowNameToString(SettingsWindow value)
@@ -77,6 +78,8 @@ namespace Common.UI
return "Dashboard";
case SettingsWindow.AdvancedPaste:
return "AdvancedPaste";
case SettingsWindow.Projects:
return "Projects";
default:
{
return string.Empty;

View File

@@ -24,15 +24,18 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\;..\..\common;.\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="DisplayUtils.h" />
<ClInclude Include="MonitorEnumerator.h" />
<ClInclude Include="monitors.h" />
<ClInclude Include="dpi_aware.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="DisplayUtils.cpp" />
<ClCompile Include="monitors.cpp" />
<ClCompile Include="dpi_aware.cpp" />
</ItemGroup>

View File

@@ -0,0 +1,147 @@
#include "DisplayUtils.h"
#include <algorithm>
#include <iterator>
#include <dpi_aware.h>
#include <MonitorEnumerator.h>
#include <utils/OnThreadExecutor.h>
namespace DisplayUtils
{
constexpr 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<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::pair<bool, std::vector<DisplayUtils::DisplayData>> GetDisplays()
{
bool success = true;
std::vector<DisplayUtils::DisplayData> result{};
auto allMonitors = MonitorEnumerator::Enumerate();
OnThreadExecutor dpiUnawareThread;
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
} }).wait();
for (auto& monitorData : allMonitors)
{
MONITORINFOEX monitorInfo = monitorData.second;
MONITORINFOEX dpiUnawareMonitorInfo{};
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
dpiUnawareMonitorInfo.cbSize = sizeof(dpiUnawareMonitorInfo);
if (!GetMonitorInfo(monitorData.first, &dpiUnawareMonitorInfo))
{
return;
}
} }).wait();
UINT dpi = 0;
if (DPIAware::GetScreenDPIForMonitor(monitorData.first, dpi) != S_OK)
{
success = false;
break;
}
DisplayUtils::DisplayData data{
.monitor = monitorData.first,
.dpi = dpi,
.monitorRectDpiAware = monitorInfo.rcMonitor,
.monitorRectDpiUnaware = dpiUnawareMonitorInfo.rcMonitor,
};
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);
data.id = deviceId.first;
data.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);
data.number = std::stoi(numberStr);
}
catch (...)
{
success = false;
break;
}
}
else
{
success = false;
// Use the display name as a fallback value when no proper device was found.
data.id = monitorInfo.szDevice;
data.instanceId = L"";
try
{
std::wstring numberStr = monitorInfo.szDevice; // \\.\DISPLAY1
numberStr = remove_non_digits(numberStr);
data.number = std::stoi(numberStr);
}
catch (...)
{
success = false;
break;
}
}
result.push_back(data);
}
return { success, result };
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <Windows.h>
#include <string>
#include <vector>
namespace DisplayUtils
{
struct DisplayData
{
HMONITOR monitor{};
std::wstring id;
std::wstring instanceId;
unsigned int number{};
unsigned int dpi{};
RECT monitorRectDpiAware{};
RECT monitorRectDpiUnaware{};
};
std::pair<bool, std::vector<DisplayData>> GetDisplays();
};

View 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;
};

View File

@@ -1,7 +1,9 @@
#include "dpi_aware.h"
#include "monitors.h"
#include <ShellScalingApi.h>
#include <array>
#include <cmath>
namespace DPIAware
{
@@ -60,6 +62,24 @@ namespace DPIAware
}
}
void Convert(HMONITOR monitor_handle, RECT& rect)
{
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)
{
rect.left = static_cast<long>(std::round(rect.left * static_cast<float>(dpi_x) / DEFAULT_DPI));
rect.right = static_cast<long>(std::round(rect.right * static_cast<float>(dpi_x) / DEFAULT_DPI));
rect.top = static_cast<long>(std::round(rect.top * static_cast<float>(dpi_y) / DEFAULT_DPI));
rect.bottom = static_cast<long>(std::round(rect.bottom * static_cast<float>(dpi_y) / DEFAULT_DPI));
}
}
void ConvertByCursorPosition(float& width, float& height)
{
HMONITOR targetMonitor = nullptr;

View File

@@ -12,6 +12,7 @@ namespace DPIAware
HRESULT GetScreenDPIForPoint(POINT p, UINT& dpi);
HRESULT GetScreenDPIForCursor(UINT& dpi);
void Convert(HMONITOR monitor_handle, float& width, float& height);
void Convert(HMONITOR monitor_handle, RECT& rect);
void ConvertByCursorPosition(float& width, float& height);
void InverseConvert(HMONITOR monitor_handle, float& width, float& height);
void EnableDPIAwarenessForThisProcess();

View File

@@ -1,4 +1,4 @@
#include "pch.h"
#include "pch.h"
#include "GPOWrapper.h"
#include "GPOWrapper.g.cpp"
@@ -176,4 +176,8 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredProjectsEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredProjectsEnabledValue());
}
}

View File

@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "GPOWrapper.g.h"
#include <common/utils/gpo.h>
@@ -50,6 +50,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
static GpoRuleConfigured GetConfiguredProjectsEnabledValue();
};
}

View File

@@ -54,6 +54,7 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
static GpoRuleConfigured GetConfiguredProjectsEnabledValue();
}
}
}

View File

@@ -61,5 +61,10 @@ namespace PowerToys.GPOWrapperProjection
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetRunPluginEnabledValue(pluginID);
}
public static GpoRuleConfigured GetConfiguredProjectsEnabledValue()
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredProjectsEnabledValue();
}
}
}

View File

@@ -26,6 +26,7 @@ namespace ManagedCommon
PowerRename,
PowerLauncher,
PowerAccent,
Projects,
RegistryPreview,
MeasureTool,
ShortcutGuide,

View File

@@ -96,6 +96,8 @@ namespace CommonSharedConstants
const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978";
const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2";
const wchar_t PROJECTS_EXIT_EVENT[] = L"Local\\PowerToys-Projects-ExitEvent-29a1566f-f4f8-4d56-9435-d2a437f727c6";
// Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100;
}

View File

@@ -69,6 +69,10 @@ struct LogSettings
inline const static std::string environmentVariablesLoggerName = "environment-variables";
inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt";
inline const static std::string cmdNotFoundLoggerName = "cmd-not-found";
inline const static std::string projectsLauncherLoggerName = "projects-launcher";
inline const static std::wstring projectsLauncherLogPath = L"projects-launcher-log.txt";
inline const static std::string projectsSnapshotToolLoggerName = "projects-snapshot-tool";
inline const static std::wstring projectsSnapshotToolLogPath = L"projects-snapshot-tool-log.txt";
inline const static int retention = 30;
std::wstring logLevel;
LogSettings();

View File

@@ -0,0 +1,72 @@
#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() :
_shutdown_request{ false },
_worker_thread{ [this] { worker_thread(); } }
{
}
~OnThreadExecutor()
{
_shutdown_request = true;
_task_cv.notify_one();
_worker_thread.join();
}
std::future<void> 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 cancel()
{
std::lock_guard lock{ _task_mutex };
_task_queue = {};
_task_cv.notify_one();
}
private:
void 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();
}
}
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;
};

View File

@@ -59,6 +59,7 @@ namespace powertoys_gpo {
const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables";
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
const std::wstring POLICY_CONFIGURE_ENABLED_PROJECTS = L"ConfigureEnabledUtilityProjects";
// The registry value names for PowerToys installer and update policies.
const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled";
@@ -366,6 +367,11 @@ namespace powertoys_gpo {
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE);
}
inline gpo_rule_configured_t getConfiguredProjectsEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_PROJECTS);
}
inline gpo_rule_configured_t getConfiguredVideoConferenceMuteEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE);

View File

@@ -57,6 +57,8 @@ properties:
EnableQoiThumbnail: false
PowerOcr:
Enabled: false
Projects:
Enabled: false
ShortcutGuide:
Enabled: false
VideoConference:

View File

@@ -57,6 +57,8 @@ properties:
EnableQoiThumbnail: true
PowerOcr:
Enabled: true
Projects:
Enabled: true
ShortcutGuide:
Enabled: true
VideoConference:

View File

@@ -356,6 +356,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityProjects" class="Both" displayName="$(string.ConfigureEnabledUtilityProjects)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityProjects">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_81_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityQuickAccent" class="Both" displayName="$(string.ConfigureEnabledUtilityQuickAccent)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityQuickAccent">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />

View File

@@ -10,6 +10,7 @@
<string id="InstallerUpdates">Installer and Updates</string>
<string id="PowerToysRun">PowerToys Run</string>
<string id="AdvancedPaste">Advanced Paste</string>
<string id="Projects">Projects</string>
<string id="SUPPORTED_POWERTOYS_0_64_0">PowerToys version 0.64.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_68_0">PowerToys version 0.68.0 or later</string>
@@ -107,6 +108,12 @@ If you don't configure this setting, users are able to enable or disable the plu
You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins".
Note: Changes require a restart of PowerToys Run.
</string>
<string id="ConfigureEnabledUtilityProjects">This policy configures the enabled disable state for the Projects utility.
If you enable or don't configure this policy, the user takes control over the enabled state of the Projects utility.
If you disable this policy, the user won't be able to enable Enable and use the Projects utility.
</string>
<string id="PowerToysRunIndividualPluginEnabledStateDescription">With this policy you can configure an individual enabled state for each PowerToys Run plugin that you add to the list.
@@ -157,6 +164,7 @@ If you disable this policy, the user won't be able to enable Enable paste with A
<string id="ConfigureEnabledUtilityPeek">Peek: Configure enabled state</string>
<string id="ConfigureEnabledUtilityPowerRename">Power Rename: Configure enabled state</string>
<string id="ConfigureEnabledUtilityPowerLauncher">PowerToys Run: Configure enabled state</string>
<string id="ConfigureEnabledUtilityProjects">PowerToys Projects: Configure enabled state</string>
<string id="ConfigureEnabledUtilityQuickAccent">Quick Accent: Configure enabled state</string>
<string id="ConfigureEnabledUtilityRegistryPreview">Registry Preview: Configure enabled state</string>
<string id="ConfigureEnabledUtilityScreenRuler">Screen Ruler: Configure enabled state</string>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,146 @@

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\AppUtils.h = projects-common\AppUtils.h
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\VirtualDesktop.h = projects-common\VirtualDesktop.h
projects-common\WindowEnumerator.h = projects-common\WindowEnumerator.h
projects-common\WindowFilter.h = projects-common\WindowFilter.h
projects-common\WindowUtils.h = projects-common\WindowUtils.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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedCommon", "..\..\common\ManagedCommon\ManagedCommon.csproj", "{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "..\..\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedTelemetry", "..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj", "{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM64 = Release|ARM64
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|ARM64.ActiveCfg = Debug|ARM64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.Build.0 = Debug|ARM64
{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|ARM64.ActiveCfg = Release|ARM64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.Build.0 = Release|ARM64
{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|ARM64.ActiveCfg = Debug|ARM64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.Build.0 = Debug|ARM64
{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|ARM64.ActiveCfg = Release|ARM64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.Build.0 = Release|ARM64
{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|ARM64.ActiveCfg = Debug|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.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|ARM64.ActiveCfg = Release|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|ARM64.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
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|Any CPU.ActiveCfg = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|Any CPU.Build.0 = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|ARM64.Build.0 = Debug|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x64.ActiveCfg = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x64.Build.0 = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x86.ActiveCfg = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x86.Build.0 = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|Any CPU.ActiveCfg = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|Any CPU.Build.0 = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|ARM64.ActiveCfg = Release|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|ARM64.Build.0 = Release|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x64.ActiveCfg = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x64.Build.0 = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x86.ActiveCfg = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x86.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|Any CPU.ActiveCfg = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|Any CPU.Build.0 = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.Build.0 = Debug|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.ActiveCfg = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.Build.0 = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x86.ActiveCfg = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x86.Build.0 = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|Any CPU.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|Any CPU.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|ARM64.ActiveCfg = Release|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|ARM64.Build.0 = Release|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|Any CPU.ActiveCfg = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|Any CPU.Build.0 = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|ARM64.ActiveCfg = Debug|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|ARM64.Build.0 = Debug|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x64.ActiveCfg = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x64.Build.0 = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x86.ActiveCfg = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x86.Build.0 = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|Any CPU.ActiveCfg = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|Any CPU.Build.0 = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|ARM64.ActiveCfg = Release|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|ARM64.Build.0 = Release|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x64.ActiveCfg = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x64.Build.0 = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x86.ActiveCfg = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BE6AD818-2650-419C-8FDE-535C22ED09B3}
EndGlobalSection
EndGlobal

View 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>

View 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>

View File

@@ -0,0 +1,109 @@
// 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 ManagedCommon;
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)
{
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredProjectsEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
{
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
Shutdown(0);
return;
}
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
Logger.InitializeLogger("\\Projects\\Logs");
_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)
{
Logger.LogInfo($"Started with a parameter: {args[1]}. Trying to launch that project.");
_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);
}
}
}

View File

@@ -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)
{
}
}
}

View 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,
}
}

View 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 static 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 static 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);
}

View File

@@ -0,0 +1,29 @@
// 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.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ProjectsEditor.Converters
{
public class BooleanToInvertedVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,110 @@
// 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 ProjectsEditor.Utils;
using static ProjectsEditor.Data.ProjectsData;
namespace ProjectsEditor.Data
{
public class ProjectsData : ProjectsEditorData<ProjectsListWrapper>
{
public string File
{
get
{
return FolderUtils.DataFolder() + "\\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 string Application { get; set; }
public string ApplicationPath { get; set; }
public string Title { get; set; }
public string PackageFullName { get; set; }
public string CommandLineArguments { get; set; }
public bool Minimized { get; set; }
public bool Maximized { get; set; }
public WindowPositionWrapper Position { get; set; }
public int Monitor { get; set; }
}
public struct MonitorConfigurationWrapper
{
public struct MonitorRectWrapper
{
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
public string Id { get; set; }
public string InstanceId { get; set; }
public int MonitorNumber { get; set; }
public int Dpi { get; set; }
public MonitorRectWrapper MonitorRectDpiAware { get; set; }
public MonitorRectWrapper MonitorRectDpiUnaware { get; set; }
}
public struct ProjectWrapper
{
public string Id { get; set; }
public string Name { get; set; }
public long CreationTime { get; set; }
public long LastLaunchedTime { get; set; }
public bool IsShortcutNeeded { get; set; }
public List<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,
}
}
}

View File

@@ -0,0 +1,36 @@
// 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;
using ProjectsEditor.Utils;
namespace Projects.Data
{
public class ProjectsEditorData<T>
{
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);
}
}
}

View 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;
}
}
}
}

View File

@@ -0,0 +1,322 @@
<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:converters="clr-namespace:ProjectsEditor.Converters"
xmlns:local="clr-namespace:ProjectsEditor"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="MainPage">
<Page.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<converters:BooleanToInvertedVisibilityConverter x:Key="BooleanToInvertedVisibilityConverter" />
<Thickness x:Key="ContentDialogPadding">24,16,0,24</Thickness>
<Thickness x:Key="ContentDialogCommandSpaceMargin">0,24,24,0</Thickness>
<Style x:Key="DeleteButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource TertiaryBackgroundBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="Transparent" Padding="26,6,26,6">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="{DynamicResource TitleBarSecondaryForegroundBrush}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="{DynamicResource TitleBarSecondaryForegroundBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</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="&#xE710;" />
<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}"
ToolTip="{x:Static props:Resources.SearchExplanation}"
/>
<TextBlock
IsHitTestVisible="False"
Text="{x:Static props:Resources.Search}"
VerticalAlignment="Center"
Foreground="{DynamicResource SecondaryForegroundBrush}"
ToolTip="{x:Static props:Resources.SearchExplanation}"
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="-50,0,34,0"
AutomationProperties.Name="{x:Static props:Resources.Search}"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
Foreground="{DynamicResource SecondaryForegroundBrush}"
Text="&#xE71E;" />
</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>
<TextBlock
Grid.Row="2"
Text="{Binding EmptyProjectsViewMessage, UpdateSourceTrigger=PropertyChanged}"
FontSize="20"
Foreground="{DynamicResource SecondaryForegroundBrush}"
TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding IsProjectsViewEmpty, Mode=OneWay, Converter={StaticResource BoolToVis}, UpdateSourceTrigger=PropertyChanged}"/>
<ScrollViewer
VerticalContentAlignment="Stretch"
VerticalScrollBarVisibility="Auto"
Grid.Row="2"
Margin="40,15,40,40"
Visibility="{Binding IsProjectsViewEmpty, Mode=OneWay, Converter={StaticResource BooleanToInvertedVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}">
<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">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="110" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" 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 PreviewIcons, 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="&#xE81C;"
Margin="0,3,10,0"/>
<TextBlock Text="{Binding LastLaunched, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Column="1" 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="&#xE712;"/>
</Button>
<Popup
IsOpen="{Binding IsPopupVisible, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
StaysOpen="False"
AllowsTransparency="True"
PlacementTarget="{Binding ElementName=MoreButton}"
Placement="Left">
<Grid
Background="{DynamicResource PrimaryBackgroundBrush}">
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=OpacityBorder}" />
</Grid.OpacityMask>
<Border
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
x:Name="OpacityBorder"
Background="Black"
CornerRadius="5" />
<StackPanel
Background="{DynamicResource PrimaryBackgroundBrush}"
Orientation="Vertical">
<Button
Style="{StaticResource DeleteButtonStyle}"
AutomationProperties.Name="{x:Static props:Resources.Edit}"
Click="EditButtonClicked">
<StackPanel Orientation="Horizontal">
<TextBlock
AutomationProperties.Name="{x:Static props:Resources.Edit}"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
Text="&#xE70F;" />
<TextBlock
Margin="10,0,0,0"
AutomationProperties.Name="{x:Static props:Resources.Edit}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
Text="{x:Static props:Resources.Edit}" />
</StackPanel>
</Button>
<Button
Style="{StaticResource DeleteButtonStyle}"
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 PrimaryForegroundBrush}"
Text="&#xE74D;" />
<TextBlock
Margin="10,0,0,0"
AutomationProperties.Name="{x:Static props:Resources.Delete}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
Text="{x:Static props:Resources.Delete}" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</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>
</Grid>
</Border>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Page>

View File

@@ -0,0 +1,63 @@
// 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)
{
_mainViewModel.CloseAllPopups();
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);
}
}
}

View 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="680"
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>

View 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();
}
}
}

View 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.
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;
}
}
}
}

View File

@@ -0,0 +1,298 @@
// 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.Threading.Tasks;
using System.Windows.Media.Imaging;
using ManagedCommon;
using Windows.ApplicationModel.Core;
using Windows.Management.Deployment;
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 string AppName { get; set; }
public string AppPath { get; set; }
public string AppTitle { get; set; }
public string PackageFullName { get; set; }
public string CommandLineArguments { get; set; }
public bool Minimized { get; set; }
public bool Maximized { get; set; }
private bool _isNotFound;
[JsonIgnore]
public bool IsNotFound
{
get
{
return _isNotFound;
}
set
{
if (_isNotFound != value)
{
_isNotFound = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
}
}
}
[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
{
if (!File.Exists(AppPath) && IsPackagedApp)
{
Task<AppListEntry> task = Task.Run<AppListEntry>(async () => await GetAppByPackageFamilyNameAsync());
AppListEntry packApp = task.Result;
if (packApp == null)
{
IsNotFound = true;
_icon = new Icon(@"images\DefaultIcon.ico");
}
else
{
string filename = Path.GetFileName(AppPath);
string newExeLocation = Path.Combine(packApp.AppInfo.Package.InstalledPath, filename);
_icon = Icon.ExtractAssociatedIcon(newExeLocation);
}
}
else
{
_icon = Icon.ExtractAssociatedIcon(AppPath);
}
}
catch (Exception)
{
Logger.LogWarning($"Icon not found on app path: {AppPath}. Using default icon");
IsNotFound = true;
_icon = new Icon(@"images\DefaultIcon.ico");
}
}
return _icon;
}
}
public async Task<AppListEntry> GetAppByPackageFamilyNameAsync()
{
var pkgManager = new PackageManager();
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
if (pkg == null)
{
return null;
}
var apps = await pkg.GetAppListEntriesAsync();
if (apps == null || apps.Count == 0)
{
return null;
}
AppListEntry firstApp = apps[0];
// RandomAccessStreamReference stream = firstApp.AppInfo.DisplayInfo.GetLogo(new Windows.Foundation.Size(64, 64));
// IRandomAccessStreamWithContentType content = await stream.OpenReadAsync();
// BitmapImage bitmapImage = new BitmapImage();
// bitmapImage.StreamSource = (Stream)content;
return firstApp;
}
private BitmapImage _iconBitmapImage;
public BitmapImage IconBitmapImage
{
get
{
if (_iconBitmapImage == null)
{
try
{
Bitmap previewBitmap = new Bitmap(32, 32);
using (Graphics graphics = Graphics.FromImage(previewBitmap))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
}
using (var memory = new MemoryStream())
{
previewBitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
_iconBitmapImage = bitmapImage;
}
}
catch (Exception e)
{
Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}");
}
}
return _iconBitmapImage;
}
}
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);
}
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);
}
}
}

View 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;
}
}
}

View File

@@ -0,0 +1,29 @@
// 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
{
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)
{
}
}
}

View File

@@ -0,0 +1,307 @@
// 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;
using ManagedCommon;
using ProjectsEditor.Utils;
namespace ProjectsEditor.Models
{
public class Project : INotifyPropertyChanged
{
[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)));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanBeSaved)));
}
}
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);
}
}
}
public bool CanBeSaved
{
get => Name.Length > 0 && Applications.Where(x => x.IsSelected).Any();
}
private bool _isPopupVisible;
[JsonIgnore]
public bool IsPopupVisible
{
get
{
return _isPopupVisible;
}
set
{
_isPopupVisible = 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
{
int count = Applications.Where(x => x.IsSelected).Count();
return count.ToString(CultureInfo.InvariantCulture) + " " + (count == 1 ? Properties.Resources.App : Properties.Resources.Apps);
}
}
public List<MonitorSetup> Monitors { get; set; }
private BitmapImage _previewIcons;
private BitmapImage _previewImage;
private double _previewImageWidth;
public Project(Project selectedProject)
{
Name = selectedProject.Name;
PreviewIcons = selectedProject.PreviewIcons;
PreviewImage = selectedProject.PreviewImage;
IsShortcutNeeded = selectedProject.IsShortcutNeeded;
int screenIndex = 1;
Monitors = new List<MonitorSetup>();
foreach (var item in selectedProject.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));
screenIndex++;
}
Applications = new List<Application>();
foreach (var item in selectedProject.Applications)
{
Applications.Add(new Application()
{
AppName = item.AppName,
AppPath = item.AppPath,
AppTitle = item.AppTitle,
CommandLineArguments = item.CommandLineArguments,
PackageFullName = item.PackageFullName,
Minimized = item.Minimized,
Maximized = item.Maximized,
IsSelected = item.IsSelected,
MonitorNumber = item.MonitorNumber,
IsNotFound = item.IsNotFound,
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 PreviewIcons
{
get
{
return _previewIcons;
}
set
{
_previewIcons = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewIcons)));
}
}
public BitmapImage PreviewImage
{
get
{
return _previewImage;
}
set
{
_previewImage = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImage)));
}
}
public double PreviewImageWidth
{
get
{
return _previewImageWidth;
}
set
{
_previewImageWidth = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImageWidth)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
public async void Initialize()
{
PreviewIcons = await Task.Run(() => DrawHelper.DrawPreviewIcons(this));
Rectangle commonBounds = GetCommonBounds();
PreviewImage = await Task.Run(() => DrawHelper.DrawPreview(this, commonBounds));
PreviewImageWidth = commonBounds.Width / (commonBounds.Height * 1.2 / 200);
}
private Rectangle GetCommonBounds()
{
double minX = Monitors.First().MonitorDpiAwareBounds.Left;
double minY = Monitors.First().MonitorDpiAwareBounds.Top;
double maxX = Monitors.First().MonitorDpiAwareBounds.Right;
double maxY = Monitors.First().MonitorDpiAwareBounds.Bottom;
for (int monitorIndex = 1; monitorIndex < Monitors.Count; monitorIndex++)
{
Monitor monitor = Monitors[monitorIndex];
minX = Math.Min(minX, monitor.MonitorDpiAwareBounds.Left);
minY = Math.Min(minY, monitor.MonitorDpiAwareBounds.Top);
maxX = Math.Max(maxX, monitor.MonitorDpiAwareBounds.Right);
maxY = Math.Max(maxY, monitor.MonitorDpiAwareBounds.Bottom);
}
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
}
}
}

View File

@@ -0,0 +1,233 @@
<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>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<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="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Text="&#xE7BA;"
Foreground="#EED202"
FontSize="14"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
FontWeight="Normal"
Margin="5 0 0 0"
ToolTip="{x:Static props:Resources.NotFoundTooltip}"
Visibility="{Binding IsNotFound, Converter={StaticResource BoolToVis}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"/>
<Image
Grid.Column="1"
Width="20"
Height="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="10"
Source="{Binding IconBitmapImage}"/>
<TextBlock
Grid.Column="2"
Text="{Binding RepeatIndexString, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
FontSize="14"
FontWeight="Normal"
Width="20"
VerticalAlignment="Center"/>
<TextBlock
Grid.Column="3"
Text="{Binding AppName}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
FontSize="14"
FontWeight="Normal"
VerticalAlignment="Center"/>
<TextBox
x:Name="CommandLineTextBox"
Grid.Column="4"
Text="{Binding CommandLineArguments, Mode=TwoWay}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
Background="{DynamicResource TertiaryBackgroundBrush}"
BorderThickness="0"
FontSize="14"
FontWeight="Normal"
VerticalContentAlignment="Center" />
<TextBlock
Grid.Column="4"
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="5"
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="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button
Margin="0,20,0,20"
Click="CancelButtonClicked"
Background="Transparent"
VerticalAlignment="Center">
<TextBlock
Text="{x:Static props:Resources.Projects}"
FontSize="24"
FontWeight="Normal"
Foreground="{DynamicResource PrimaryForegroundBrush}"
VerticalAlignment="Center"/>
</Button>
<TextBlock
FontFamily="{DynamicResource SymbolThemeFontFamily}"
Foreground="{DynamicResource PrimaryForegroundBrush}"
FontSize="16"
Margin="10,0,0,0"
Text="&#xE76C;"
VerticalAlignment="Center"/>
<TextBlock
Text="{Binding EditorWindowTitle}"
FontSize="24"
FontWeight="SemiBold"
Margin="10,0,0,0"
Foreground="{DynamicResource PrimaryForegroundBrush}"
VerticalAlignment="Center"/>
</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"
TextChanged="EditNameTextBox_TextChanged"
KeyDown="EditNameTextBoxKeyDown" />
</StackPanel>
<Border
Grid.Row="2"
HorizontalAlignment="Stretch"
Background="{DynamicResource MonitorViewBackgroundBrush}"
CornerRadius="5">
<Image
Width="{Binding PreviewImageWidth, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Height="200"
Source="{Binding PreviewImage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Stretch="Fill"
Margin="2"/>
</Border>
<ScrollViewer
Margin="0,10,0,0"
VerticalScrollBarVisibility="Auto"
Grid.Row="3">
<StackPanel Orientation="Vertical">
<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="4" 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"
Padding="24,0,24,0"
Content="{x:Static props:Resources.Cancel}"
Background="{DynamicResource SecondaryBackgroundBrush}"
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
Click="CancelButtonClicked">
</Button>
<Button
x:Name="SaveButton"
Margin="20,0,0,0"
Padding="24,0,24,0"
Height="36"
IsEnabled="{Binding CanBeSaved, UpdateSourceTrigger=PropertyChanged}"
Content="{x:Static props:Resources.Save_project}"
AutomationProperties.Name="{x:Static props:Resources.Save_project}"
Click="SaveButtonClicked"
Style="{StaticResource AccentButtonStyle}">
</Button>
</StackPanel>
</DockPanel>
</Grid>
</Page>

View File

@@ -0,0 +1,95 @@
// 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.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.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Project.CanBeSaved)));
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();
}
private void EditNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
Project project = this.DataContext as Project;
project.Name = EditNameTextBox.Text;
project.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Project.CanBeSaved)));
}
}
}

View File

@@ -0,0 +1,122 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<AssemblyTitle>PowerToys.ProjectsEditor</AssemblyTitle>
<AssemblyDescription>PowerToys Projects Editor</AssemblyDescription>
<Description>PowerToys Projects Editor</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;ARM64</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>{367D7543-7DBA-4381-99F1-BF6142A996C4}</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>PowerToys.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>
<COMReference Include="Shell32">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</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>
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
</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>

View File

@@ -0,0 +1,477 @@
//------------------------------------------------------------------------------
// <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.
/// </summary>
public static string App {
get {
return ResourceManager.GetString("App", resourceCulture);
}
}
/// <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 apps.
/// </summary>
public static string Apps {
get {
return ResourceManager.GetString("Apps", 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 No projects match the current search..
/// </summary>
public static string NoProjectsMatch {
get {
return ResourceManager.GetString("NoProjectsMatch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The application cannot be found.
/// </summary>
public static string NotFoundTooltip {
get {
return ResourceManager.GetString("NotFoundTooltip", 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 Project name.
/// </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 Search for projects or apps.
/// </summary>
public static string SearchExplanation {
get {
return ResourceManager.GetString("SearchExplanation", 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);
}
}
}
}

View File

@@ -0,0 +1,258 @@
<?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" xml:space="preserve">
<value>app</value>
</data>
<data name="Apps" xml:space="preserve">
<value>apps</value>
</data>
<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="NoProjectsMatch" xml:space="preserve">
<value>No projects match the current search.</value>
</data>
<data name="NotFoundTooltip" xml:space="preserve">
<value>The application cannot be found</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>Project name</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="SearchExplanation" xml:space="preserve">
<value>Search for projects or apps</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>

View 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;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View 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>

View 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>

View 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">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>

View 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">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>

View 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">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>

View 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">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>

View 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="#FFf3f3f3" />
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FFfbfbfb" />
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FFfefefe" />
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FFF9F9F9" />
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF191919" />
<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>

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,347 @@
// 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.Windows.Media.Imaging;
using ManagedCommon;
using ProjectsEditor.Models;
namespace ProjectsEditor.Utils
{
public class DrawHelper
{
private static Font font = new("Tahoma", 24);
public static BitmapImage DrawPreview(Project project, Rectangle bounds)
{
List<double> horizontalGaps = new List<double>();
List<double> verticalGaps = new List<double>();
double gapWidth = bounds.Width * 0.01;
double gapHeight = bounds.Height * 0.01;
double scale = 0.1;
int Scaled(double value)
{
return (int)(value * scale);
}
int TransformX(double posX)
{
double gapTransform = verticalGaps.Where(x => x <= posX).Count() * gapWidth;
return Scaled(posX - bounds.Left + gapTransform);
}
int TransformY(double posY)
{
double gapTransform = horizontalGaps.Where(x => x <= posY).Count() * gapHeight;
return Scaled(posY - bounds.Top + gapTransform);
}
Dictionary<string, int> repeatCounter = new Dictionary<string, int>();
var selectedApps = project.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 repetitions) by setting them to 0
foreach (Application app in selectedApps.Where(x => repeatCounter[x.AppPath] == 1))
{
app.RepeatIndex = 0;
}
foreach (Application app in project.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 project.Applications)
{
app.OnPropertyChanged(new PropertyChangedEventArgs("RepeatIndexString"));
}
foreach (MonitorSetup monitor in project.Monitors)
{
// check for vertical gap
if (monitor.MonitorDpiAwareBounds.Left > bounds.Left && project.Monitors.Any(x => x.MonitorDpiAwareBounds.Right <= monitor.MonitorDpiAwareBounds.Left))
{
verticalGaps.Add(monitor.MonitorDpiAwareBounds.Left);
}
// check for horizontal gap
if (monitor.MonitorDpiAwareBounds.Top > bounds.Top && project.Monitors.Any(x => x.MonitorDpiAwareBounds.Bottom <= monitor.MonitorDpiAwareBounds.Top))
{
horizontalGaps.Add(monitor.MonitorDpiAwareBounds.Top);
}
}
Bitmap previewBitmap = new Bitmap(Scaled(bounds.Width + (verticalGaps.Count * gapWidth)), Scaled((bounds.Height * 1.2) + (horizontalGaps.Count * gapHeight)));
double desiredIconSize = Scaled(Math.Min(bounds.Width, bounds.Height)) * 0.3;
using (Graphics g = Graphics.FromImage(previewBitmap))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
Brush brush = new SolidBrush(Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? Color.FromArgb(10, 255, 255, 255) : Color.FromArgb(10, 0, 0, 0));
// draw the monitors
foreach (MonitorSetup monitor in project.Monitors)
{
Brush monitorBrush = new SolidBrush(Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? Color.FromArgb(32, 7, 91, 155) : Color.FromArgb(32, 7, 91, 155));
g.FillRectangle(monitorBrush, new Rectangle(TransformX(monitor.MonitorDpiAwareBounds.Left), TransformY(monitor.MonitorDpiAwareBounds.Top), Scaled(monitor.MonitorDpiAwareBounds.Width), Scaled(monitor.MonitorDpiAwareBounds.Height)));
}
var appsToDraw = project.Applications.Where(x => x.IsSelected && !x.Minimized);
// draw the highlighted app at the end to have its icon in the foreground for the case there are overlapping icons
foreach (Application app in appsToDraw.Where(x => !x.IsHighlighted))
{
Rectangle rect = new Rectangle(TransformX(app.ScaledPosition.X), TransformY(app.ScaledPosition.Y), Scaled(app.ScaledPosition.Width), Scaled(app.ScaledPosition.Height));
DrawWindow(g, brush, rect, app, desiredIconSize);
}
foreach (Application app in appsToDraw.Where(x => x.IsHighlighted))
{
Rectangle rect = new Rectangle(TransformX(app.ScaledPosition.X), TransformY(app.ScaledPosition.Y), Scaled(app.ScaledPosition.Width), Scaled(app.ScaledPosition.Height));
DrawWindow(g, brush, rect, app, desiredIconSize);
}
// draw the minimized windows
Rectangle rectMinimized = new Rectangle(0, Scaled((bounds.Height * 1.02) + (horizontalGaps.Count * gapHeight)), Scaled(bounds.Width + (verticalGaps.Count * gapWidth)), Scaled(bounds.Height * 0.18));
DrawWindow(g, brush, rectMinimized, project.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;
}
}
public static void DrawWindow(Graphics graphics, Brush brush, Rectangle bounds, Application app, double desiredIconSize)
{
if (graphics == null)
{
return;
}
if (brush == null)
{
return;
}
using (GraphicsPath path = RoundedRect(bounds))
{
if (app.IsHighlighted)
{
graphics.DrawPath(new Pen(Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? Color.White : Color.DarkGray, graphics.VisibleClipBounds.Height / 50), path);
}
else
{
graphics.DrawPath(new Pen(Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? Color.FromArgb(128, 82, 82, 82) : Color.FromArgb(128, 160, 160, 160), graphics.VisibleClipBounds.Height / 200), path);
}
graphics.FillPath(brush, path);
}
double iconSize = Math.Min(Math.Min(bounds.Width - 4, bounds.Height - 4), desiredIconSize);
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);
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(Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? Color.White : Color.DarkGray, graphics.VisibleClipBounds.Height / 50), path);
}
else
{
graphics.DrawPath(new Pen(Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? Color.FromArgb(128, 82, 82, 82) : Color.FromArgb(128, 160, 160, 160), graphics.VisibleClipBounds.Height / 200), 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);
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 BitmapImage DrawPreviewIcons(Project project)
{
var selectedApps = project.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 e)
{
Logger.LogError($"Exception while drawing the icon for app {app.AppName}. Exception message: {e.Message}");
}
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 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;
}
}
}

View File

@@ -0,0 +1,28 @@
// 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;
namespace ProjectsEditor.Utils
{
public class FolderUtils
{
public static string Desktop()
{
return Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
}
public static string Temp()
{
return Path.GetTempPath();
}
// Note: the same path should be used in SnapshotTool and Launcher
public static string DataFolder()
{
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\PowerToys\\Projects";
}
}
}

View 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;
}
}
}

View 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);
}
}

View 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;
}
}
}

View File

@@ -0,0 +1,152 @@
// 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.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
namespace ProjectsEditor.Utils
{
public class ProjectIcon : IDisposable
{
private const int IconSize = 128;
public static readonly Brush LightThemeIconBackground = new SolidBrush(Color.FromArgb(255, 239, 243, 251));
public static readonly Brush LightThemeIconForeground = new SolidBrush(Color.FromArgb(255, 47, 50, 56));
public static readonly Brush DarkThemeIconBackground = new SolidBrush(Color.FromArgb(255, 55, 55, 55));
public static readonly Brush DarkThemeIconForeground = new SolidBrush(Color.FromArgb(255, 228, 228, 228));
public static readonly Font IconFont = new("Aptos", 24, FontStyle.Bold);
public static string IconTextFromProjectName(string projectName)
{
string result = string.Empty;
char[] delimiterChars = { ' ', ',', '.', ':', '-', '\t' };
string[] words = projectName.Split(delimiterChars);
foreach (string word in words)
{
if (string.IsNullOrEmpty(word))
{
continue;
}
if (word.All(char.IsDigit))
{
result += word;
}
else
{
result += word.ToUpper(System.Globalization.CultureInfo.CurrentCulture).ToCharArray()[0];
}
}
return result;
}
public static Bitmap DrawIcon(string text)
{
Brush background = Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? DarkThemeIconBackground : LightThemeIconBackground;
Brush foreground = Common.ThemeManager.GetCurrentTheme() == Common.Theme.Dark ? DarkThemeIconForeground : LightThemeIconForeground;
Bitmap bitmap = new Bitmap(IconSize, IconSize);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.FillEllipse(background, 0, 0, IconSize, IconSize);
var textSize = graphics.MeasureString(text, IconFont);
var state = graphics.Save();
// Calculate scaling factors
float scaleX = (float)IconSize / textSize.Width;
float scaleY = (float)IconSize / textSize.Height;
float scale = Math.Min(scaleX, scaleY) * 0.8f; // Use the smaller scale factor to maintain aspect ratio
// Calculate the position to center the text
float textX = (IconSize - (textSize.Width * scale)) / 2;
float textY = ((IconSize - (textSize.Height * scale)) / 2) + 6;
graphics.TranslateTransform(textX, textY);
graphics.ScaleTransform(scale, scale);
graphics.DrawString(text, IconFont, foreground, 0, 0);
graphics.Restore(state);
}
return bitmap;
}
public static void SaveIcon(Bitmap icon, string path)
{
if (Path.Exists(path))
{
File.Delete(path);
}
FileStream fileStream = new FileStream(path, FileMode.CreateNew);
using (var memoryStream = new MemoryStream())
{
icon.Save(memoryStream, ImageFormat.Png);
BinaryWriter iconWriter = new BinaryWriter(fileStream);
if (fileStream != null && iconWriter != null)
{
// 0-1 reserved, 0
iconWriter.Write((byte)0);
iconWriter.Write((byte)0);
// 2-3 image type, 1 = icon, 2 = cursor
iconWriter.Write((short)1);
// 4-5 number of images
iconWriter.Write((short)1);
// image entry 1
// 0 image width
iconWriter.Write((byte)IconSize);
// 1 image height
iconWriter.Write((byte)IconSize);
// 2 number of colors
iconWriter.Write((byte)0);
// 3 reserved
iconWriter.Write((byte)0);
// 4-5 color planes
iconWriter.Write((short)0);
// 6-7 bits per pixel
iconWriter.Write((short)32);
// 8-11 size of image data
iconWriter.Write((int)memoryStream.Length);
// 12-15 offset of image data
iconWriter.Write((int)(6 + 16));
// write image data
// png data must contain the whole png data file
iconWriter.Write(memoryStream.ToArray());
iconWriter.Flush();
}
}
fileStream.Flush();
fileStream.Close();
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,244 @@
// 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 ManagedCommon;
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))
{
Logger.LogWarning($"Projects storage file not found: {parser.File}");
return new ParsingResult(true);
}
ProjectsData.ProjectsListWrapper projects = parser.Read(parser.File);
if (!SetProjects(mainViewModel, projects))
{
Logger.LogWarning($"Projects storage file content could not be set. Reason: {Properties.Resources.Error_Parsing_Message}");
return new ParsingResult(false, ProjectsEditor.Properties.Resources.Error_Parsing_Message);
}
return new ParsingResult(true);
}
catch (Exception e)
{
Logger.LogError($"Exception while parsing storage file: {e.Message}");
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))
{
Logger.LogWarning($"ParseProject method. Projects storage file not found: {parser.File}");
return new ParsingResult(true);
}
ProjectsData.ProjectsListWrapper projects = parser.Read(fileName);
if (!ExtractProject(projects, out project))
{
Logger.LogWarning($"ParseProject method. Projects storage file content could not be set. Reason: {Properties.Resources.Error_Parsing_Message}");
return new ParsingResult(false, ProjectsEditor.Properties.Resources.Error_Parsing_Message);
}
return new ParsingResult(true);
}
catch (Exception e)
{
Logger.LogError($"ParseProject method. Exception while parsing storage file: {e.Message}");
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 = GetProjectFromWrapper(projectWrapper);
return true;
}
private Project GetProjectFromWrapper(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()
{
AppName = app.Application,
AppPath = app.ApplicationPath,
AppTitle = app.Title,
PackageFullName = app.PackageFullName,
Parent = newProject,
CommandLineArguments = app.CommandLineArguments,
Maximized = app.Maximized,
Minimized = app.Minimized,
IsSelected = true,
IsNotFound = false,
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
{
Application = app.AppName,
ApplicationPath = app.AppPath,
Title = app.AppTitle,
PackageFullName = app.PackageFullName,
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 e)
{
// TODO: show error
Logger.LogError($"Exception while writing storage file: {e.Message}");
}
}
private bool AddProjects(MainViewModel mainViewModel, ProjectsData.ProjectsListWrapper projects)
{
foreach (var project in projects.Projects)
{
mainViewModel.Projects.Add(GetProjectFromWrapper(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);
}
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,364 @@
// 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.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using ManagedCommon;
using ProjectsEditor.Models;
using ProjectsEditor.Utils;
using static ProjectsEditor.Data.ProjectsData;
namespace ProjectsEditor.ViewModels
{
public class MainViewModel : INotifyPropertyChanged, IDisposable
{
private ProjectsEditorIO _projectsEditorIO;
public ObservableCollection<Project> Projects { get; set; } = new ObservableCollection<Project>();
public IEnumerable<Project> ProjectsView
{
get
{
IEnumerable<Project> projects = GetFilteredProjects();
IsProjectsViewEmpty = !(projects != null && projects.Any());
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsProjectsViewEmpty)));
if (IsProjectsViewEmpty)
{
if (Projects != null && Projects.Any())
{
EmptyProjectsViewMessage = Properties.Resources.NoProjectsMatch;
}
else
{
EmptyProjectsViewMessage = Properties.Resources.No_Projects_Message;
}
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EmptyProjectsViewMessage)));
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);
}
}
}
public bool IsProjectsViewEmpty { get; set; }
public string EmptyProjectsViewMessage { get; set; }
// return those projects where the project name or any of the selected apps' name contains the search term
private IEnumerable<Project> GetFilteredProjects()
{
if (string.IsNullOrEmpty(_searchTerm))
{
return Projects;
}
return Projects.Where(x =>
{
if (x.Name.Contains(_searchTerm, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
if (x.Applications == null)
{
return false;
}
return x.Applications.Any(app => app.IsSelected && app.AppName.Contains(_searchTerm, StringComparison.InvariantCultureIgnoreCase));
});
}
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.PreviewIcons = projectToSave.PreviewIcons;
editedProject.PreviewImage = projectToSave.PreviewImage;
for (int appIndex = editedProject.Applications.Count - 1; appIndex >= 0; appIndex--)
{
if (!projectToSave.Applications[appIndex].IsSelected)
{
editedProject.Applications.RemoveAt(appIndex);
}
else
{
editedProject.Applications[appIndex].IsSelected = true;
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 project)
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
string shortcutAddress = Path.Combine(FolderUtils.Desktop(), project.Name + ".lnk");
string shortcutIconFilename = Path.Combine(FolderUtils.Temp(), project.Id + ".ico");
Bitmap icon = ProjectIcon.DrawIcon(ProjectIcon.IconTextFromProjectName(project.Name));
ProjectIcon.SaveIcon(icon, shortcutIconFilename);
try
{
// Workaround to be able to create a shortcut with unicode filename
File.WriteAllBytes(shortcutAddress, Array.Empty<byte>());
// Create a ShellLinkObject that references the .lnk file
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder dir = shell.NameSpace(FolderUtils.Desktop());
Shell32.FolderItem folderItem = dir.Items().Item($"{project.Name}.lnk");
Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
// Set the .lnk file properties
link.Description = $"Project Launcher {project.Id}";
link.Path = Path.Combine(basePath, "PowerToys.ProjectsLauncher.exe");
link.Arguments = project.Id.ToString();
link.WorkingDirectory = basePath;
link.SetIconLocation(shortcutIconFilename, 0);
link.Save(shortcutAddress);
}
catch (Exception ex)
{
Logger.LogError($"Shortcut creation error: {ex.Message}");
}
}
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 };
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())
{
Logger.LogWarning($"Project to launch not find. Id: {projectId}");
return;
}
LaunchProject(Projects.Where(x => x.Id == projectId).First(), true);
}
public async void LaunchProject(Project project, bool exitAfterLaunch = false)
{
await Task.Run(() => RunLauncher(project.Id));
if (_projectsEditorIO.ParseProjects(this).Result == true)
{
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
}
if (exitAfterLaunch)
{
Logger.LogInfo($"Launched the project {project.Name}. Exiting.");
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 RunSnapshotTool(string filename = null)
{
Process p = new Process();
p.StartInfo = new ProcessStartInfo(@".\PowerToys.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(@".\PowerToys.ProjectsLauncher.exe", projectId);
p.StartInfo.CreateNoWindow = true;
p.Start();
p.WaitForExit();
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
internal void CloseAllPopups()
{
foreach (Project project in Projects)
{
project.IsPopupVisible = false;
}
}
}
}

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,340 @@
#include "pch.h"
#include "AppLauncher.h"
#include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.ApplicationModel.Core.h>
#include <iostream>
#include <projects-common/AppUtils.h>
#include <projects-common/MonitorEnumerator.h>
#include <projects-common/MonitorUtils.h>
#include <projects-common/WindowEnumerator.h>
#include <projects-common/WindowFilter.h>
#include <common/Display/dpi_aware.h>
#include <common/utils/winapi_error.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Management::Deployment;
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, HMONITOR monitor, RECT& rect)
{
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
GetMonitorInfoW(monitor, &monitorInfo);
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
DPIAware::Convert(monitor, rect);
auto referenceRect = RECT(rect.left - xOffset, rect.top - yOffset, rect.right - xOffset, rect.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;
}
inline bool SizeWindowToRect(HWND window, HMONITOR monitor, bool isMinimized, bool isMaximized, RECT rect) noexcept
{
WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);
if (isMinimized)
{
placement.showCmd = SW_MINIMIZE;
}
else
{
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
(placement.showCmd != SW_MINIMIZE))
{
if (placement.showCmd == SW_SHOWMAXIMIZED)
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
placement.showCmd = SW_RESTORE;
}
ScreenToWorkAreaCoords(window, monitor, rect);
placement.rcNormalPosition = rect;
}
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
auto result = ::SetWindowPlacement(window, &placement);
if (!result)
{
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
return false;
}
// make sure window is moved to the correct monitor before maximize.
if (isMaximized)
{
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)
{
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
return false;
}
return true;
}
}
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs)
{
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
PROCESS_INFORMATION processInfo;
ZeroMemory(&processInfo, sizeof(processInfo));
std::wstring fullCommandLine = L"\"" + appPath + L"\" " + commandLineArgs;
if (CreateProcess(nullptr, fullCommandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo))
{
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
return true;
}
else
{
Logger::error(L"Failed to launch process. {}", get_last_error_or_default(GetLastError()));
}
return false;
}
bool LaunchPackagedApp(const std::wstring& packageFullName)
{
try
{
PackageManager packageManager;
for (const auto& package : packageManager.FindPackagesForUser({}))
{
if (package.Id().FullName() == packageFullName)
{
auto getAppListEntriesOperation = package.GetAppListEntriesAsync();
auto appEntries = getAppListEntriesOperation.get();
if (appEntries.Size() > 0)
{
IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
bool launchResult = launchOperation.get();
return launchResult;
}
else
{
Logger::error(L"No app entries found for the package.");
}
}
}
}
catch (const hresult_error& ex)
{
Logger::error(L"Packaged app launching error: {}", ex.message());
}
return false;
}
bool Launch(const Project::Application& app)
{
bool launched;
if (!app.packageFullName.empty() && app.commandLineArgs.empty())
{
Logger::trace(L"Launching packaged {}", app.name);
launched = LaunchPackagedApp(app.packageFullName);
}
else
{
// TODO: verify app path is up to date.
// Packaged apps have version in the path, it will be outdated after update.
Logger::trace(L"Launching {} at {}", app.name, app.path);
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
{
Logger::error(L"File not found at {}", app.path);
return false;
}
launched = LaunchApp(app.path, app.commandLineArgs);
}
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
return launched;
}
void Launch(const Project& project)
{
// Get the set of windows before launching the app
std::vector<HWND> windowsBefore = WindowEnumerator::Enumerate(WindowFilter::Filter);
std::vector<std::pair<Project::Application, HWND>> launchedWindows{};
auto apps = Utils::Apps::GetAppsList();
auto monitors = MonitorUtils::IdentifyMonitors();
for (const auto& app : project.apps)
{
if (Launch(app))
{
launchedWindows.push_back({ app, nullptr });
}
}
// Get newly opened windows after launching apps, keep retrying for 5 seconds
for (int attempt = 0; attempt < 50; attempt++)
{
std::vector<HWND> windowsAfter = WindowEnumerator::Enumerate(WindowFilter::Filter);
for (HWND window : windowsAfter)
{
// Find the new window
if (std::find(windowsBefore.begin(), windowsBefore.end(), window) == windowsBefore.end())
{
// find the corresponding app in the list
auto app = Utils::Apps::GetApp(window, apps);
if (!app.has_value())
{
continue;
}
// set the window
auto res = std::find_if(launchedWindows.begin(), launchedWindows.end(), [&](const std::pair<Project::Application, HWND>& val) { return val.first.name == app->name && val.second == nullptr; });
if (res != launchedWindows.end())
{
res->second = window;
}
}
}
// check if all windows were found
auto res = std::find_if(launchedWindows.begin(), launchedWindows.end(), [&](const std::pair<Project::Application, HWND>& val) { return val.second == nullptr; });
if (res == launchedWindows.end())
{
Logger::trace(L"All windows found.");
break;
}
else
{
Logger::trace(L"Not all windows found, retry.");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
// Check single-instance app windows if they were launched before
if (launchedWindows.empty())
{
auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
for (HWND window : windows)
{
auto app = Utils::Apps::GetApp(window, apps);
if (!app.has_value())
{
continue;
}
auto res = std::find_if(launchedWindows.begin(), launchedWindows.end(), [&](const std::pair<Project::Application, HWND>& val) { return val.second == nullptr && val.first.name == app->name; });
if (res != launchedWindows.end())
{
res->second = window;
}
}
}
// Place windows
for (const auto& [app, window] : launchedWindows)
{
if (window == nullptr)
{
Logger::warn(L"{} window not found.", app.name);
continue;
}
auto snapMonitorIter = std::find_if(project.monitors.begin(), project.monitors.end(), [&](const Project::Monitor& val) { return val.number == app.monitor; });
if (snapMonitorIter == project.monitors.end())
{
Logger::error(L"No monitor saved for launching the app");
continue;
}
HMONITOR currentMonitor{};
UINT currentDpi = DPIAware::DEFAULT_DPI;
auto currentMonitorIter = std::find_if(monitors.begin(), monitors.end(), [&](const Project::Monitor& val) { return val.number == app.monitor; });
if (currentMonitorIter != monitors.end())
{
currentMonitor = currentMonitorIter->monitor;
currentDpi = currentMonitorIter->dpi;
}
else
{
currentMonitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY);
DPIAware::GetScreenDPIForMonitor(currentMonitor, currentDpi);
}
RECT rect = app.position.toRect();
float mult = static_cast<float>(snapMonitorIter->dpi) / currentDpi;
rect.left = static_cast<long>(std::round(rect.left * mult));
rect.right = static_cast<long>(std::round(rect.right * mult));
rect.top = static_cast<long>(std::round(rect.top * mult));
rect.bottom = static_cast<long>(std::round(rect.bottom * mult));
if (FancyZones::SizeWindowToRect(window, currentMonitor, app.isMinimized, app.isMaximized, rect))
{
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
}
else
{
Logger::error(L"Failed placing {}", app.name);
}
}
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include <projects-common/Data.h>
bool Launch(const Project::Application& app);
void Launch(const Project& project);

View File

@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Project configurations -->
<!-- Props that should be disabled while building on CI server -->
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<!-- C++ source compile-specific things for all configurations -->
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
</Link>
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<!-- C++ source compile-specific things for Debug/Release configurations -->
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<!-- Global props -->
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{2cac093e-5fcf-4102-9c2c-ac7dd5d9eb96}</ProjectGuid>
<RootNamespace>ProjectsLauncher</RootNamespace>
</PropertyGroup>
<!-- Props that are constant for both Debug and Release configurations -->
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<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>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<TargetName>PowerToys.$(MSBuildProjectName)</TargetName>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="AppLauncher.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AppLauncher.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ProjectLauncherResource.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\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.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,47 @@
<?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>
<ClInclude Include="resource.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="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ProjectLauncherResource.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View 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>

View File

@@ -0,0 +1,95 @@
#include "pch.h"
#include <iostream>
#include <projects-common/Data.h>
#include <AppLauncher.h>
#include <common/utils/gpo.h>
#include <common/utils/logger_helper.h>
#include <common/utils/UnhandledExceptionHandler.h>
const std::wstring moduleName = L"Projects\\ProjectsLauncher";
const std::wstring internalPath = L"";
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cmdShow)
{
LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::projectsLauncherLoggerName);
InitUnhandledExceptionHandler();
if (powertoys_gpo::getConfiguredProjectsEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
{
Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
return 0;
}
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
// 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 ex)
{
Logger::error("Exception on reading projects: {}", ex.what());
return 1;
}
if (projects.empty())
{
Logger::warn("Projects file is empty");
return 1;
}
Project projectToLaunch{};
std::string idStr(cmdline);
std::wstring id(idStr.begin(), idStr.end());
if (!id.empty())
{
for (const auto& proj : projects)
{
if (proj.id == id)
{
projectToLaunch = proj;
break;
}
}
}
if (projectToLaunch.id.empty())
{
Logger::info(L"Project {} not found", id);
return 1;
}
Logger::info(L"Launch Project {} : {}", projectToLaunch.name, projectToLaunch.id);
// launch apps
Launch(projectToLaunch);
// 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;
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
</packages>

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,6 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <common/logger/logger.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>

View File

@@ -0,0 +1,13 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by ProjectLauncherResource.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys Projects Launcher"
#define INTERNAL_NAME "PowerToys.ProjectsLauncher"
#define ORIGINAL_FILENAME "PowerToys.ProjectsLauncher.exe"
// Non-localizable
//////////////////////////////

View File

@@ -0,0 +1,55 @@
#include <windows.h>
#include "resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
STRINGTABLE
BEGIN
IDS_PROJECTS_NAME L"Projects"
IDS_PROJECTS_SETTINGS_DESC "Projects description"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,79 @@
<?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.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{45285DF2-9742-4ECA-9AC9-58951FC26489}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>projects</RootNamespace>
<ProjectName>ProjectsModuleInterface</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
<TargetName>PowerToys.ProjectsModuleInterface</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>PROJECTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;wbemuuid.lib;comsuppw.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ProjectsModuleInterface.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Import Project="..\..\..\..\deps\spdlog.props" />
<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.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Resource Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{21926bf1-03b3-482d-8f60-8bc4fbfc6564}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{2f10207d-d8d1-4a42-8027-8ca597b3cb23}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{a4241930-ecae-44e2-be82-25eff2499fcd}</UniqueIdentifier>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{8d479404-964b-4eb1-8fe8-554be3e68c9b}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ProjectsModuleInterface.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,328 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/interop/shared_constants.h>
#include <common/logger/logger.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/logger_helper.h>
#include <common/utils/resources.h>
#include <common/utils/winapi_error.h>
#include <shellapi.h>
#include "resource.h"
// Non-localizable
const std::wstring projectsLauncherPath = L"PowerToys.ProjectsLauncher.exe";
const std::wstring projectsSnapshotToolPath = L"PowerToys.ProjectsSnapshotTool.exe";
const std::wstring projectsEditorPath = L"PowerToys.ProjectsEditor.exe";
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_RUN_EDITOR_HOTKEY[] = L"hotkey";
const wchar_t JSON_KEY_RUN_SNAPSHOT_TOOL_HOTKEY[] = L"run-snapshot-tool-hotkey";
const wchar_t JSON_KEY_RUN_LAUNCHER_HOTKEY[] = L"run-launcher-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// TODO: Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
// TODO: Trace::UnregisterProvider();
break;
}
return TRUE;
}
class ProjectsModuleInterface : public PowertoyModuleIface
{
public:
// Return the localized display name of the powertoy
virtual PCWSTR get_name() override
{
return app_name.c_str();
}
// Return the non localized key of the powertoy, this will be cached by the runner
virtual const wchar_t* get_key() override
{
return app_key.c_str();
}
virtual std::optional<HotkeyEx> GetHotkeyEx() override
{
return m_hotkey;
}
virtual void OnHotkeyEx() override
{
launch_editor();
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredProjectsEnabledValue();
}
// Return JSON with the configuration options.
// These are the settings shown on the settings page along with their current values.
virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(GET_RESOURCE_STRING(IDS_PROJECTS_SETTINGS_DESC));
settings.set_overview_link(L"https://aka.ms/PowerToysOverview_Projects");
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Passes JSON with the configuration settings for the powertoy.
// This is called when the user hits Save on the settings page.
virtual void set_config(PCWSTR config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
parse_hotkeys(values);
auto settingsObject = values.get_raw_json();
// TODO: telemetry
values.save_to_settings_file();
}
catch (std::exception&)
{
// Improper JSON.
}
}
// Signal from the Settings editor to call a custom action.
// This can be used to spawn more complex editors.
virtual void call_custom_action(const wchar_t* /*action*/) override
{
SetEvent(m_toggleEditorEvent);
}
// Enable the powertoy
virtual void enable()
{
Logger::info("Projects enabling");
Enable();
}
// Disable the powertoy
virtual void disable()
{
Logger::info("Projects disabling");
Disable(true);
}
// Returns if the powertoy is enabled
virtual bool is_enabled() override
{
Logger::info("enabled = {}", m_enabled);
return m_enabled;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
Disable(false);
if (m_toggleEditorEvent)
{
CloseHandle(m_toggleEditorEvent);
m_toggleEditorEvent = nullptr;
}
delete this;
}
virtual void send_settings_telemetry() override
{
Logger::info("Send settings telemetry");
// TODO
}
ProjectsModuleInterface()
{
app_name = GET_RESOURCE_STRING(IDS_PROJECTS_NAME);
app_key = L"Projects";
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "Projects");
init_settings();
}
private:
void Enable()
{
Logger::info("Enable");
m_enabled = true;
// Log telemetry
// TODO: Trace::Projects::EnableProjects(true);
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
}
void SendCloseEvent()
{
auto exitEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::PROJECTS_EXIT_EVENT);
if (!exitEvent)
{
Logger::warn(L"Failed to create exitEvent. {}", get_last_error_or_default(GetLastError()));
}
else
{
Logger::trace(L"Signaled exitEvent");
if (!SetEvent(exitEvent))
{
Logger::warn(L"Failed to signal exitEvent. {}", get_last_error_or_default(GetLastError()));
}
ResetEvent(exitEvent);
CloseHandle(exitEvent);
}
}
void Disable(bool const traceEvent)
{
Logger::info("Disable");
m_enabled = false;
// Log telemetry
if (traceEvent)
{
// TODO: Trace::Projects::EnableProjects(false);
}
if (m_toggleEditorEvent)
{
ResetEvent(m_toggleEditorEvent);
}
if (m_hProcess)
{
TerminateProcess(m_hProcess, 0);
SendCloseEvent();
m_hProcess = nullptr;
}
}
// Load the settings file.
void init_settings()
{
try
{
Logger::trace(L"Read settings {}", get_key());
// Load and parse the settings file for this PowerToy.
PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
parse_hotkeys(settings);
}
catch (std::exception&)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.
}
}
void parse_hotkeys(PowerToysSettings::PowerToyValues& settings)
{
auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size())
{
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_RUN_EDITOR_HOTKEY))
{
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_RUN_EDITOR_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_hotkey = HotkeyEx();
if (hotkey.win_pressed())
{
m_hotkey.modifiersMask |= MOD_WIN;
}
if (hotkey.ctrl_pressed())
{
m_hotkey.modifiersMask |= MOD_CONTROL;
}
if (hotkey.shift_pressed())
{
m_hotkey.modifiersMask |= MOD_SHIFT;
}
if (hotkey.alt_pressed())
{
m_hotkey.modifiersMask |= MOD_ALT;
}
m_hotkey.vkCode = static_cast<WORD>(hotkey.get_code());
}
}
}
void launch_editor()
{
Logger::trace(L"Starting ProjectsEditor");
/*unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));*/
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
sei.lpFile = L"PowerToys.ProjectsEditor.exe";
sei.nShow = SW_SHOWNORMAL;
//sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei))
{
Logger::trace("Successfully started the ProjectsEditor");
}
else
{
Logger::error(L"ProjectsEditor failed to start. {}", get_last_error_or_default(GetLastError()));
}
m_hProcess = sei.hProcess;
}
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
bool m_enabled = false;
HANDLE m_hProcess = nullptr;
// Handle to event used to invoke Projects Editor
HANDLE m_toggleEditorEvent;
// Hotkey to invoke the module
HotkeyEx m_hotkey{
.modifiersMask = MOD_SHIFT | MOD_WIN,
.vkCode = 0x50, // P key;
};
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new ProjectsModuleInterface();
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
</packages>

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,9 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <Unknwn.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <ProjectTelemetry.h>
#include <TraceLoggingActivity.h>

View File

@@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by ProjectsModuleInterface.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys Projects Module"
#define INTERNAL_NAME "PowerToys.ProjectsModuleInterface"
#define ORIGINAL_FILENAME "PowerToys.ProjectsModuleInterface.dll"
// Non-localizable
//////////////////////////////
#define IDS_PROJECTS_NAME 101
#define IDS_PROJECTS_SETTINGS_DESC 102

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Project configurations -->
<!-- Props that should be disabled while building on CI server -->
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<!-- C++ source compile-specific things for all configurations -->
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
</Link>
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<!-- C++ source compile-specific things for Debug/Release configurations -->
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<SDLCheck>false</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<!-- Global props -->
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{3d63307b-9d27-44fd-b033-b26f39245b85}</ProjectGuid>
<RootNamespace>ProjectsSnapshotTool</RootNamespace>
</PropertyGroup>
<!-- Props that are constant for both Debug and Release configurations -->
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<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>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<TargetName>PowerToys.$(MSBuildProjectName)</TargetName>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ProjectsSnapshotTool.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\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.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,41 @@
<?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="resource.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>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ProjectsSnapshotTool.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View 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>

Some files were not shown because too many files have changed in this diff Show More