Merge latest master: 4/22/20

This commit is contained in:
Arjun 2020-04-22 10:02:17 -07:00
commit ca3a436fe5
67 changed files with 7483 additions and 347 deletions

View File

@ -46,6 +46,7 @@ build:
include:
- 'PowerToys.exe'
- 'PowerToysSettings.exe'
- 'action_runner.exe'
- 'modules\FancyZonesEditor.exe'
- 'modules\fancyzones.dll'
- 'modules\shortcut_guide.dll'

View File

@ -158,7 +158,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "action_runner", "src\action
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msi_to_msix_upgrade_lib", "src\common\msi_to_msix_upgrade_lib\msi_to_msix_upgrade_lib.vcxproj", "{17DA04DF-E393-4397-9CF0-84DABE11032E}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "updating", "src\common\updating\updating.vcxproj", "{17DA04DF-E393-4397-9CF0-84DABE11032E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "keyboardmanager", "keyboardmanager", "{38BDB927-829B-4C65-9CD9-93FB05D66D65}"
EndProject

View File

@ -13,7 +13,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
### Via Github with MSI [Recommended]
Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.16.0-x64.msi` to download the PowerToys installer.
Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.16.1-x64.msi` to download the PowerToys installer.
This is our preferred method.

13
community.md Normal file
View File

@ -0,0 +1,13 @@
# Community
The PowerToys team is extremely grateful to have the support of an amazing active community. The work you do is incredibly important. PowerToys wouldnt be near what it is without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thanks and to recognize your work. This is a living document dedicated to highlighting the high impact community members and their contributions.
## High impact community members
### [@Niels9001](https://github.com/niels9001/) - [Niels Laute](https://nielslaute.com/)
Niels has helped drive large sums of our update toward a new [consistent and modern UX](https://github.com/microsoft/PowerToys/issues/891). This includes the [launcher work](https://github.com/microsoft/PowerToys/issues/44) and [icon design](https://github.com/microsoft/PowerToys/issues/1118).
### [@riverar](https://github.com/riverar) - [Rafael Rivera](https://withinrafael.com/)
Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/microsoft/PowerToys/issues/1907). He directly provided feedback to the CppWinRT team for bugs from this migration as well.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -39,6 +39,8 @@
<ComponentGroupRef Id="CoreComponents" />
<ComponentGroupRef Id="ResourcesComponents" />
</Feature>
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<UI>
<UIRef Id="WixUI_PTInstallDir"/>
@ -71,6 +73,9 @@
<RegistrySearch Id="ExistingImageResizerPath" Root="HKCU" Key="Software\Classes\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32" Type="raw"/>
</Property>
<InstallUISequence>
<Custom Action="DetectPrevInstallPath" After="CostFinalize" />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="SetRegisterPowerToysSchTaskParam" Before="RegisterPowerToysSchTask" />
<Custom Action="RegisterPowerToysSchTask" After="InstallFiles">
@ -163,6 +168,13 @@
DllEntry="TelemetryLogRepairFailCA"
/>
<CustomAction Id="DetectPrevInstallPath"
Return="check"
Impersonate="yes"
BinaryKey="PTCustomActions"
DllEntry="DetectPrevInstallPathCA"
/>
<!-- Close 'PowerToys.exe' before uninstall-->
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" />
<!-- Restart explorer.exe if we detect existing PowerRenameExt.dll or ImageResizerExt.dll installation -->
@ -233,7 +245,7 @@
</Shortcut>
</File>
<RegistryKey Root="HKCR" Key="powertoys" Action="createAndRemoveOnUninstall">
<RegistryKey Root="HKCR" Key="powertoys">
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Value="URL:PowerToys custom internal URI protocol"/>
<RegistryKey Key="DefaultIcon">
@ -253,6 +265,9 @@
<Component Id="notifications_dll" Guid="23B25EE4-BCA2-45DF-BBCD-82FBDF01C5AB" Win64="yes">
<File Id="Notifications.dll" KeyPath="yes" Checksum="yes" />
</Component>
<Component Id="action_runner_exe" Guid="626ABB17-16F0-4007-9A58-6998724A5E14" Win64="yes">
<File Id="action_runner.exe" KeyPath="yes" Checksum="yes" />
</Component>
<Component Id="License_rtf" Guid="3E5AE43B-CFB4-449B-A346-94CAAFF3312E" Win64="yes">
<File Source="$(var.RepoDir)\installer\License.rtf" Id="License.rtf" KeyPath="yes" />
</Component>
@ -507,6 +522,7 @@
<ComponentGroup Id="CoreComponents" Directory="INSTALLFOLDER">
<ComponentRef Id="powertoys_exe" />
<ComponentRef Id="notifications_dll" />
<ComponentRef Id="action_runner_exe" />
<ComponentRef Id="powertoys_toast_clsid" />
<ComponentRef Id="License_rtf" />
<ComponentRef Id="PowerToysSvgs" />

View File

@ -1,17 +1,5 @@
#include "stdafx.h"
#define SECURITY_WIN32
#include <Security.h>
#pragma comment(lib, "Secur32.lib")
#include <Lmcons.h>
#include <comdef.h>
#include <taskschd.h>
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")
#include <iostream>
#include <strutil.h>
#include <ProjectTelemetry.h>
using namespace std;
@ -26,6 +14,9 @@ TRACELOGGING_DEFINE_PROVIDER(
const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0'
const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0'
static const wchar_t* POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}";
static const wchar_t* POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
// Creates a Scheduled Task to run at logon for the current user.
// The path of the executable to run should be passed as the CustomActionData (Value).
// Based on the Task Scheduler Logon Trigger Example:
@ -48,6 +39,8 @@ UINT __stdcall CreateScheduledTaskCA(MSIHANDLE hInstall)
ITriggerCollection* pTriggerCollection = NULL;
IRegisteredTask* pRegisteredTask = NULL;
LPWSTR wszExecutablePath = NULL;
hr = WcaInitialize(hInstall, "CreateScheduledTaskCA");
ExitOnFailure(hr, "Failed to initialize");
@ -77,7 +70,6 @@ UINT __stdcall CreateScheduledTaskCA(MSIHANDLE hInstall)
wstrTaskName += username;
// Get the executable path passed to the custom action.
LPWSTR wszExecutablePath = NULL;
hr = WcaGetProperty(L"CustomActionData", &wszExecutablePath);
ExitOnFailure(hr, "Failed to get the executable path from CustomActionData.");
@ -571,6 +563,73 @@ LExit:
return WcaFinalize(er);
}
std::optional<std::wstring> getMsiPackageInstalledPath(const wchar_t* product_upgrade_code, const wchar_t* file_component)
{
constexpr size_t guid_length = 39;
wchar_t product_ID[guid_length];
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(product_upgrade_code, 0, 0, product_ID); !found)
{
return std::nullopt;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(product_ID); !installed)
{
return std::nullopt;
}
DWORD buf_size = MAX_PATH;
wchar_t buf[MAX_PATH];
if (ERROR_SUCCESS == MsiGetProductInfoW(product_ID, INSTALLPROPERTY_INSTALLLOCATION, buf, &buf_size) && buf_size)
{
return buf;
}
DWORD package_path_size = 0;
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size))
{
return std::nullopt;
}
std::wstring package_path(++package_path_size, L'\0');
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size))
{
return std::nullopt;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
wchar_t path[256];
DWORD path_size = 256;
MsiGetComponentPathW(product_ID, file_component, path, &path_size);
if (!path_size)
{
return std::nullopt;
}
PathCchRemoveFileSpec(path, path_size);
return path;
}
UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "DetectPrevInstallPathCA");
try
{
if (auto install_path = getMsiPackageInstalledPath(POWERTOYS_UPGRADE_CODE, POWERTOYS_EXE_COMPONENT))
{
MsiSetPropertyW(hInstall, L"INSTALLFOLDER", install_path->data());
}
}
catch(...)
{
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
// DllMain - Initialize and cleanup WiX custom action utils.
extern "C" BOOL WINAPI DllMain(__in HINSTANCE hInst, __in ULONG ulReason, __in LPVOID)
{

View File

@ -2,6 +2,7 @@ LIBRARY "PowerToysSetupCustomActions"
EXPORTS
CreateScheduledTaskCA
DetectPrevInstallPathCA
RemoveScheduledTasksCA
TelemetryLogInstallSuccessCA
TelemetryLogInstallCancelCA

View File

@ -55,11 +55,13 @@
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<WarningLevel>Level4</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpplatest</LanguageStandard>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
@ -76,11 +78,13 @@
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<WarningLevel>Level4</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpplatest</LanguageStandard>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>

View File

@ -1,13 +1,23 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>
#include <strsafe.h>
#include <msiquery.h>
#include <Msi.h>
// WiX Header Files:
#include <wcautil.h>
#define SECURITY_WIN32
#include <Security.h>
#include <Lmcons.h>
// TODO: reference additional headers your program requires here
#include <comdef.h>
#include <taskschd.h>
#include <iostream>
#include <strutil.h>
#include <string>
#include <optional>
#include <pathcch.h>

View File

@ -4,19 +4,24 @@
#include <string_view>
#include <common/msi_to_msix_upgrade_lib/msi_to_msix_upgrade.h>
#include <common/common.h>
#include <common/updating/updating.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Storage.h>
#include <Msi.h>
#include "../runner/tray_icon.h"
#include "../runner/action_runner_utils.h"
int uninstall_msi_action()
{
const auto package_path = get_msi_package_path();
const auto package_path = updating::get_msi_package_path();
if (package_path.empty())
{
return 0;
}
if (!uninstall_msi_version(package_path))
if (!updating::uninstall_msi_version(package_path))
{
return -1;
}
@ -33,6 +38,90 @@ int uninstall_msi_action()
return 0;
}
namespace fs = std::filesystem;
std::optional<fs::path> copy_self_to_temp_dir()
{
std::error_code error;
auto dst_path = fs::temp_directory_path() / "action_runner.exe";
fs::copy_file(get_module_filename(), dst_path, fs::copy_options::overwrite_existing, error);
if (error)
{
return std::nullopt;
}
return std::move(dst_path);
}
bool install_new_version_stage_1(const bool must_restart = false)
{
std::optional<fs::path> installer;
for (auto path : fs::directory_iterator{ updating::get_pending_updates_path() })
{
if (path.path().native().find(updating::installer_filename_pattern) != std::wstring::npos)
{
installer.emplace(std::move(path));
break;
}
}
if (!installer)
{
return false;
}
if (auto copy_in_temp = copy_self_to_temp_dir())
{
// detect if PT was running
const auto pt_main_window = FindWindowW(pt_tray_icon_window_class, nullptr);
const bool launch_powertoys = must_restart || pt_main_window != nullptr;
if (pt_main_window != nullptr)
{
SendMessageW(pt_main_window, WM_CLOSE, 0, 0);
}
std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2_CMDARG };
arguments += L" \"";
arguments += installer->c_str();
arguments += L"\" \"";
arguments += get_module_folderpath();
arguments += L"\" ";
arguments += launch_powertoys ? UPDATE_STAGE2_RESTART_PT_CMDARG : UPDATE_STAGE2_DONT_START_PT_CMDARG;
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
sei.lpFile = copy_in_temp->c_str();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = arguments.c_str();
return ShellExecuteExW(&sei) == TRUE;
}
else
{
return false;
}
}
bool install_new_version_stage_2(std::wstring_view installer_path, std::wstring_view install_path, const bool launch_powertoys)
{
if (MsiInstallProductW(installer_path.data(), nullptr) != ERROR_SUCCESS)
{
return false;
}
std::error_code _;
fs::remove(installer_path, _);
if (launch_powertoys)
{
std::wstring new_pt_path{ install_path };
new_pt_path += L"\\PowerToys.exe";
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
sei.lpFile = new_pt_path.c_str();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = UPDATE_REPORT_SUCCESS;
return ShellExecuteExW(&sei) == TRUE;
}
return true;
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
int nArgs = 0;
@ -47,6 +136,19 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
return uninstall_msi_action();
}
else if (action == UPDATE_NOW_LAUNCH_STAGE1_CMDARG)
{
return !install_new_version_stage_1();
}
else if (action == UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG)
{
return !install_new_version_stage_1(true);
}
else if (action == UPDATE_NOW_LAUNCH_STAGE2_CMDARG)
{
using namespace std::string_view_literals;
return !install_new_version_stage_2(args[2], args[3], args[4] == std::wstring_view{ UPDATE_STAGE2_RESTART_PT_CMDARG });
}
return 0;
}

View File

@ -157,12 +157,12 @@
<ProjectReference Include="..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
<ProjectReference Include="..\common\msi_to_msix_upgrade_lib\msi_to_msix_upgrade_lib.vcxproj">
<ProjectReference Include="..\common\updating\updating.vcxproj">
<Project>{17da04df-e393-4397-9cf0-84dabe11032e}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\runner\msi_to_msix_upgrade.h" />
<ClInclude Include="..\runner\updating.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -166,6 +166,7 @@
<ClCompile Include="start_visible.cpp" />
<ClCompile Include="tasklist_positions.cpp" />
<ClCompile Include="common.cpp" />
<ClCompile Include="version.cpp" />
<ClCompile Include="VersionHelper.cpp" />
<ClCompile Include="windows_colors.cpp" />
<ClCompile Include="window_helpers.cpp" />

View File

@ -171,6 +171,9 @@
<ClCompile Include="keyboard_layout.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="version.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@ -1,157 +0,0 @@
#include "pch.h"
#include "version.h"
#include "msi_to_msix_upgrade.h"
#include <msi.h>
#include <common/common.h>
#include <common/json.h>
#include <common/winstore.h>
#include <common/notifications.h>
#include <MsiQuery.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Headers.h>
#include <winrt/Windows.Management.Deployment.h>
#include "VersionHelper.h"
namespace
{
const wchar_t* POWER_TOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
const wchar_t* DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH = L"delete_previous_powertoys_confirm";
const wchar_t* USER_AGENT = L"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)";
const wchar_t* LATEST_RELEASE_ENDPOINT = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
const wchar_t* MSIX_PACKAGE_NAME = L"Microsoft.PowerToys";
const wchar_t* MSIX_PACKAGE_PUBLISHER = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US";
}
namespace localized_strings
{
const wchar_t* OFFER_UNINSTALL_MSI = L"We've detected a previous installation of PowerToys. Would you like to remove it?";
const wchar_t* OFFER_UNINSTALL_MSI_TITLE = L"PowerToys: uninstall previous version?";
const wchar_t* UNINSTALLATION_SUCCESS = L"Previous version of PowerToys was uninstalled successfully.";
const wchar_t* UNINSTALLATION_UNKNOWN_ERROR = L"Error: please uninstall the previous version of PowerToys manually.";
}
std::wstring get_msi_package_path()
{
std::wstring package_path;
wchar_t GUID_product_string[39];
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(POWER_TOYS_UPGRADE_CODE, 0, 0, GUID_product_string); !found)
{
return package_path;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(GUID_product_string); !installed)
{
return package_path;
}
DWORD package_path_size = 0;
if (const bool has_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size); !has_package_path)
{
return package_path;
}
package_path = std::wstring(++package_path_size, L'\0');
if (const bool got_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size); !got_package_path)
{
package_path = {};
return package_path;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
return package_path;
}
bool offer_msi_uninstallation()
{
const auto selection = SHMessageBoxCheckW(nullptr, localized_strings::OFFER_UNINSTALL_MSI, localized_strings::OFFER_UNINSTALL_MSI_TITLE, MB_ICONQUESTION | MB_YESNO, IDNO, DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH);
return selection == IDYES;
}
bool uninstall_msi_version(const std::wstring& package_path)
{
const auto uninstall_result = MsiInstallProductW(package_path.c_str(), L"REMOVE=ALL");
if (ERROR_SUCCESS == uninstall_result)
{
notifications::show_toast(localized_strings::UNINSTALLATION_SUCCESS);
return true;
}
else if (auto system_message = get_last_error_message(uninstall_result); system_message.has_value())
{
try
{
notifications::show_toast(*system_message);
}
catch (...)
{
notifications::show_toast(localized_strings::UNINSTALLATION_UNKNOWN_ERROR);
}
}
return false;
}
std::future<std::optional<new_version_download_info>> check_for_new_github_release_async()
{
try
{
winrt::Windows::Web::Http::HttpClient client;
auto headers = client.DefaultRequestHeaders();
headers.UserAgent().TryParseAdd(USER_AGENT);
auto response = co_await client.GetAsync(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT });
(void)response.EnsureSuccessStatusCode();
const auto body = co_await response.Content().ReadAsStringAsync();
auto json_body = json::JsonValue::Parse(body).GetObjectW();
auto new_version = json_body.GetNamedString(L"tag_name");
winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") };
VersionHelper github_version(winrt::to_string(new_version));
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
if (github_version > current_version)
{
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str() };
}
else
{
co_return std::nullopt;
}
}
catch (...)
{
co_return std::nullopt;
}
}
std::future<bool> uninstall_previous_msix_version_async()
{
winrt::Windows::Management::Deployment::PackageManager package_manager;
try
{
auto packages = package_manager.FindPackagesForUser({}, MSIX_PACKAGE_NAME, MSIX_PACKAGE_PUBLISHER);
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
for (auto package : packages)
{
VersionHelper msix_version(package.Id().Version().Major, package.Id().Version().Minor, package.Id().Version().Revision);
if (msix_version < current_version)
{
co_await package_manager.RemovePackageAsync(package.Id().FullName());
co_return true;
}
}
}
catch (...)
{
}
co_return false;
}

View File

@ -1,20 +0,0 @@
#pragma once
#include <optional>
#include <string>
#include <future>
#include <winrt/Windows.Foundation.h>
std::wstring get_msi_package_path();
bool uninstall_msi_version(const std::wstring& package_path);
bool offer_msi_uninstallation();
std::future<bool> uninstall_previous_msix_version_async();
struct new_version_download_info
{
winrt::Windows::Foundation::Uri release_page_uri;
std::wstring version_string;
};
std::future<std::optional<new_version_download_info>> check_for_new_github_release_async();

View File

@ -1,13 +0,0 @@
#pragma once
#ifndef PCH_H
#define PCH_H
#pragma warning (disable: 5205)
#include <winrt/base.h>
#pragma warning (default: 5205)
#include <Windows.h>
#include <MsiQuery.h>
#include <Shlwapi.h>
#endif //PCH_H

17
src/common/updating/pch.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#ifndef PCH_H
#define PCH_H
#pragma warning(disable : 5205)
#include <winrt/base.h>
#pragma warning(default : 5205)
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <MsiQuery.h>
#include <Shlwapi.h>
#include <Shobjidl.h>
#include <Knownfolders.h>
#include <ShlObj_core.h>
#endif //PCH_H

View File

@ -0,0 +1,244 @@
#include "pch.h"
#include "version.h"
#include "updating.h"
#include <msi.h>
#include <common/common.h>
#include <common/json.h>
#include <common/version.h>
#include <common/settings_helpers.h>
#include <common/winstore.h>
#include <common/notifications.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Headers.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.Networking.Connectivity.h>
#include "VersionHelper.h"
namespace
{
const wchar_t POWER_TOYS_UPGRADE_CODE[] = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
const wchar_t DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH[] = L"delete_previous_powertoys_confirm";
const wchar_t USER_AGENT[] = L"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)";
const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
const wchar_t MSIX_PACKAGE_NAME[] = L"Microsoft.PowerToys";
const wchar_t MSIX_PACKAGE_PUBLISHER[] = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US";
}
namespace localized_strings
{
const wchar_t OFFER_UNINSTALL_MSI[] = L"We've detected a previous installation of PowerToys. Would you like to remove it?";
const wchar_t OFFER_UNINSTALL_MSI_TITLE[] = L"PowerToys: uninstall previous version?";
const wchar_t UNINSTALLATION_SUCCESS[] = L"Previous version of PowerToys was uninstalled successfully.";
const wchar_t UNINSTALLATION_UNKNOWN_ERROR[] = L"Error: please uninstall the previous version of PowerToys manually.";
const wchar_t GITHUB_NEW_VERSION_READY_TO_INSTALL[] = L"An update to PowerToys is ready to install.";
const wchar_t GITHUB_NEW_VERSION_UPDATE_NOW[] = L"Update now";
const wchar_t GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART[] = L"At next launch";
const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to get ";
const wchar_t GITHUB_NEW_VERSION_AGREE[] = L"Visit";
const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D[] = L"1 day";
const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D[] = L"5 days";
}
namespace updating
{
inline winrt::Windows::Web::Http::HttpClient create_http_client()
{
winrt::Windows::Web::Http::HttpClient client;
auto headers = client.DefaultRequestHeaders();
headers.UserAgent().TryParseAdd(USER_AGENT);
return client;
}
std::wstring get_msi_package_path()
{
std::wstring package_path;
wchar_t GUID_product_string[39];
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(POWER_TOYS_UPGRADE_CODE, 0, 0, GUID_product_string); !found)
{
return package_path;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(GUID_product_string); !installed)
{
return package_path;
}
DWORD package_path_size = 0;
if (const bool has_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size); !has_package_path)
{
return package_path;
}
package_path = std::wstring(++package_path_size, L'\0');
if (const bool got_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size); !got_package_path)
{
package_path = {};
return package_path;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
return package_path;
}
bool offer_msi_uninstallation()
{
const auto selection = SHMessageBoxCheckW(nullptr, localized_strings::OFFER_UNINSTALL_MSI, localized_strings::OFFER_UNINSTALL_MSI_TITLE, MB_ICONQUESTION | MB_YESNO, IDNO, DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH);
return selection == IDYES;
}
bool uninstall_msi_version(const std::wstring& package_path)
{
const auto uninstall_result = MsiInstallProductW(package_path.c_str(), L"REMOVE=ALL");
if (ERROR_SUCCESS == uninstall_result)
{
notifications::show_toast(localized_strings::UNINSTALLATION_SUCCESS);
return true;
}
else if (auto system_message = get_last_error_message(uninstall_result); system_message.has_value())
{
try
{
notifications::show_toast(*system_message);
}
catch (...)
{
notifications::show_toast(localized_strings::UNINSTALLATION_UNKNOWN_ERROR);
}
}
return false;
}
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async()
{
try
{
auto client = create_http_client();
auto response = co_await client.GetAsync(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT });
(void)response.EnsureSuccessStatusCode();
const auto body = co_await response.Content().ReadAsStringAsync();
auto json_body = json::JsonValue::Parse(body).GetObjectW();
auto new_version = json_body.GetNamedString(L"tag_name");
winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") };
VersionHelper github_version(winrt::to_string(new_version));
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
if (github_version > current_version)
{
const std::wstring_view required_asset_extension = winstore::running_as_packaged() ? L".msix" : L".msi";
const std::wstring_view required_architecture = get_architecture_string(get_current_architecture());
constexpr const std::wstring_view required_filename_pattern = updating::installer_filename_pattern;
for (auto asset_elem : json_body.GetNamedArray(L"assets"))
{
auto asset{ asset_elem.GetObjectW() };
std::wstring filename_lower = asset.GetNamedString(L"name", {}).c_str();
std::transform(begin(filename_lower), end(filename_lower), begin(filename_lower), ::towlower);
const bool extension_matched = filename_lower.ends_with(required_asset_extension);
const bool architecture_matched = filename_lower.find(required_architecture) != std::wstring::npos;
const bool filename_matched = filename_lower.find(required_filename_pattern) != std::wstring::npos;
if (extension_matched && architecture_matched && filename_matched)
{
winrt::Windows::Foundation::Uri msi_download_url{ asset.GetNamedString(L"browser_download_url") };
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str(), std::move(msi_download_url), std::move(filename_lower) };
}
}
}
else
{
co_return std::nullopt;
}
}
catch (...)
{
co_return std::nullopt;
}
}
std::future<bool> uninstall_previous_msix_version_async()
{
winrt::Windows::Management::Deployment::PackageManager package_manager;
try
{
auto packages = package_manager.FindPackagesForUser({}, MSIX_PACKAGE_NAME, MSIX_PACKAGE_PUBLISHER);
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
for (auto package : packages)
{
VersionHelper msix_version(package.Id().Version().Major, package.Id().Version().Minor, package.Id().Version().Revision);
if (msix_version < current_version)
{
co_await package_manager.RemovePackageAsync(package.Id().FullName());
co_return true;
}
}
}
catch (...)
{
}
co_return false;
}
bool could_be_costly_connection()
{
using namespace winrt::Windows::Networking::Connectivity;
ConnectionProfile internetConnectionProfile = NetworkInformation::GetInternetConnectionProfile();
return internetConnectionProfile.IsWwanConnectionProfile();
}
std::filesystem::path get_pending_updates_path()
{
auto path_str{ PTSettingsHelper::get_root_save_folder_location() };
path_str += L"\\Updates";
return { std::move(path_str) };
}
std::future<void> try_autoupdate(const bool download_updates_automatically)
{
const auto new_version = co_await get_new_github_version_info_async();
if (!new_version)
{
co_return;
}
using namespace localized_strings;
namespace storage = winrt::Windows::Storage;
if (download_updates_automatically && !could_be_costly_connection())
{
auto client = create_http_client();
auto response = co_await client.GetAsync(new_version->msi_download_url);
(void)response.EnsureSuccessStatusCode();
auto download_dst = get_pending_updates_path();
std::error_code _;
std::filesystem::create_directories(download_dst, _);
download_dst /= new_version->msi_filename;
auto msi_installer_file_stream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(download_dst.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways);
co_await response.Content().WriteToStreamAsync(msi_installer_file_stream);
notifications::toast_params toast_params{ L"PTUpdateReadyTag", false };
std::wstring new_version_ready{ GITHUB_NEW_VERSION_READY_TO_INSTALL };
new_version_ready += L" ";
new_version_ready += new_version->version_string;
notifications::show_toast_with_activations(std::move(new_version_ready), {}, { notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://update_now/" }, notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART, L"powertoys://schedule_update/" }, notifications::snooze_button{ { { GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 }, { GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } } } }, std::move(toast_params));
}
else
{
notifications::toast_params toast_params{ L"PTUpdateNotifyTag", false };
std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
contents += new_version->version_string;
contents += L'.';
notifications::show_toast_with_activations(std::move(contents), {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString().c_str() } }, std::move(toast_params));
}
}
}

View File

@ -0,0 +1,31 @@
#pragma once
#include <optional>
#include <string>
#include <future>
#include <filesystem>
#include <winrt/Windows.Foundation.h>
namespace updating
{
std::wstring get_msi_package_path();
bool uninstall_msi_version(const std::wstring& package_path);
bool offer_msi_uninstallation();
std::future<bool> uninstall_previous_msix_version_async();
struct new_version_download_info
{
winrt::Windows::Foundation::Uri release_page_uri;
std::wstring version_string;
winrt::Windows::Foundation::Uri msi_download_url;
std::wstring msi_filename;
};
std::future<std::optional<new_version_download_info>> get_new_github_version_info_async();
std::future<void> try_autoupdate(const bool download_updates_automatically);
std::filesystem::path get_pending_updates_path();
constexpr inline std::wstring_view installer_filename_pattern = L"powertoyssetup";
}

View File

@ -22,8 +22,9 @@
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{17DA04DF-E393-4397-9CF0-84DABE11032E}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>msitomsixupgradelib</RootNamespace>
<RootNamespace>updating</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>updating</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@ -171,11 +172,11 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="msi_to_msix_upgrade.h" />
<ClInclude Include="updating.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="msi_to_msix_upgrade.cpp" />
<ClCompile Include="updating.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>

View File

@ -18,7 +18,7 @@
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msi_to_msix_upgrade.h">
<ClInclude Include="updating.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
@ -26,7 +26,7 @@
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msi_to_msix_upgrade.cpp">
<ClCompile Include="updating.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>

21
src/common/version.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "pch.h"
#include "version.h"
version_architecture get_current_architecture()
{
// TODO: detect ARM build with #ifdef
return version_architecture::x64;
}
const wchar_t* get_architecture_string(const version_architecture v)
{
switch (v)
{
case version_architecture::x64:
return L"x64";
case version_architecture::arm:
return L"arm";
default:
throw std::runtime_error("unknown architecture");
}
}

View File

@ -2,7 +2,7 @@
#define STRINGIZE2(s) #s
#define STRINGIZE(s) STRINGIZE2(s)
#include "Generated Files\version_gen.h"
#define FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, 0
@ -14,3 +14,12 @@
#define COMPANY_NAME "Microsoft Corporation"
#define COPYRIGHT_NOTE "Copyright (C) 2019 Microsoft Corporation"
enum class version_architecture
{
x64,
arm
};
version_architecture get_current_architecture();
const wchar_t* get_architecture_string(const version_architecture);

View File

@ -9,6 +9,7 @@
#include "lib/JsonHelpers.h"
#include "lib/ZoneSet.h"
#include "trace.h"
#include "VirtualDesktopUtils.h"
#include <functional>
#include <common/common.h>
@ -349,17 +350,28 @@ FancyZones::VirtualDesktopInitialize() noexcept
IFACEMETHODIMP_(void)
FancyZones::WindowCreated(HWND window) noexcept
{
std::shared_lock readLock(m_lock);
if (m_settings->GetSettings()->appLastZone_moveWindows && IsInterestingWindow(window))
{
for (const auto& [monitor, zoneWindow] : m_zoneWindowMap)
{
// WindowCreated is also invoked when a virtual desktop switch occurs, we need a way
// to figure out when that happens to avoid moving windows that should not be moved.
GUID windowDesktopId{};
GUID zoneWindowDesktopId{};
if (VirtualDesktopUtils::GetWindowDesktopId(window, &windowDesktopId) &&
VirtualDesktopUtils::GetZoneWindowDesktopId(zoneWindow.get(), &zoneWindowDesktopId) &&
(windowDesktopId != zoneWindowDesktopId))
{
return;
}
const auto activeZoneSet = zoneWindow->ActiveZoneSet();
if (activeZoneSet)
{
const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance();
wil::unique_cotaskmem_string guidString;
if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString)))
if (SUCCEEDED(StringFromCLSID(activeZoneSet->Id(), &guidString)))
{
int zoneIndex = fancyZonesData.GetAppLastZoneIndex(window, zoneWindow->UniqueId(), guidString.get());
if (zoneIndex != -1)

View File

@ -103,6 +103,7 @@
<ClInclude Include="Settings.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="util.h" />
<ClInclude Include="VirtualDesktopUtils.h" />
<ClInclude Include="Zone.h" />
<ClInclude Include="ZoneSet.h" />
<ClInclude Include="ZoneWindow.h" />
@ -117,6 +118,7 @@
<ClCompile Include="Settings.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="util.cpp" />
<ClCompile Include="VirtualDesktopUtils.cpp" />
<ClCompile Include="Zone.cpp" />
<ClCompile Include="ZoneSet.cpp" />
<ClCompile Include="ZoneWindow.cpp" />

View File

@ -48,6 +48,9 @@
<ClInclude Include="JsonHelpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="VirtualDesktopUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@ -77,6 +80,9 @@
<ClCompile Include="util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="VirtualDesktopUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="fancyzones.rc">

View File

@ -0,0 +1,49 @@
#include "pch.h"
#include "VirtualDesktopUtils.h"
namespace VirtualDesktopUtils
{
const CLSID CLSID_ImmersiveShell = { 0xC2F03A33, 0x21F5, 0x47FA, 0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39 };
const wchar_t GUID_EmptyGUID[] = L"{00000000-0000-0000-0000-000000000000}";
IServiceProvider* GetServiceProvider()
{
IServiceProvider* provider{ nullptr };
if (FAILED(CoCreateInstance(CLSID_ImmersiveShell, nullptr, CLSCTX_LOCAL_SERVER, __uuidof(provider), (PVOID*)&provider)))
{
return nullptr;
}
return provider;
}
IVirtualDesktopManager* GetVirtualDesktopManager()
{
IVirtualDesktopManager* manager{ nullptr };
IServiceProvider* serviceProvider = GetServiceProvider();
if (serviceProvider == nullptr || FAILED(serviceProvider->QueryService(__uuidof(manager), &manager)))
{
return nullptr;
}
return manager;
}
bool GetWindowDesktopId(HWND topLevelWindow, GUID* desktopId)
{
static IVirtualDesktopManager* virtualDesktopManager = GetVirtualDesktopManager();
return (virtualDesktopManager != nullptr) &&
SUCCEEDED(virtualDesktopManager->GetWindowDesktopId(topLevelWindow, desktopId));
}
bool GetZoneWindowDesktopId(IZoneWindow* zoneWindow, GUID* desktopId)
{
// Format: <device-id>_<resolution>_<virtual-desktop-id>
std::wstring uniqueId = zoneWindow->UniqueId();
std::wstring virtualDesktopId = uniqueId.substr(uniqueId.rfind('_') + 1);
if (virtualDesktopId == GUID_EmptyGUID)
{
return false;
}
return SUCCEEDED(CLSIDFromString(virtualDesktopId.c_str(), desktopId));
}
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "ZoneWindow.h"
namespace VirtualDesktopUtils
{
bool GetWindowDesktopId(HWND topLevelWindow, GUID* desktopId);
bool GetZoneWindowDesktopId(IZoneWindow* zoneWindow, GUID* desktopId);
}

View File

@ -7,6 +7,7 @@ namespace ZoneWindowUtils
const std::wstring& GetActiveZoneSetTmpPath();
const std::wstring& GetAppliedZoneSetTmpPath();
const std::wstring& GetCustomZoneSetsTmpPath();
std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId);
}

View File

@ -0,0 +1,28 @@
#include "pch.h"
#include "action_runner_utils.h"
#include <common/common.h>
#include <common/winstore.h>
SHELLEXECUTEINFOW launch_action_runner(const wchar_t* cmdline)
{
std::wstring action_runner_path;
if (winstore::running_as_packaged())
{
action_runner_path = winrt::Windows::ApplicationModel::Package::Current().InstalledLocation().Path();
}
else
{
action_runner_path = get_module_folderpath();
}
action_runner_path += L"\\action_runner.exe";
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS };
sei.lpFile = action_runner_path.c_str();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = cmdline;
ShellExecuteExW(&sei);
return sei;
}

View File

@ -0,0 +1,15 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
SHELLEXECUTEINFOW launch_action_runner(const wchar_t* cmdline);
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG = L"-update_now_and_start_pt";
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1_CMDARG = L"-update_now";
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE2_CMDARG = L"-update_now_stage_2";
const inline wchar_t* UPDATE_STAGE2_RESTART_PT_CMDARG = L"restart";
const inline wchar_t* UPDATE_STAGE2_DONT_START_PT_CMDARG = L"dont_start";
const inline wchar_t* UPDATE_REPORT_SUCCESS = L"-report_update_success";

View File

@ -10,8 +10,10 @@
#include "trace.h"
// TODO: would be nice to get rid of these globals, since they're basically cached json settings
static std::wstring settings_theme = L"system";
static bool run_as_elevated = false;
static bool download_updates_automatically = true;
// TODO: add resource.rc for settings project and localize
namespace localized_strings
@ -40,6 +42,7 @@ json::JsonObject GeneralSettings::to_json()
result.SetNamedValue(L"is_elevated", json::value(isElevated));
result.SetNamedValue(L"run_elevated", json::value(isRunElevated));
result.SetNamedValue(L"download_updates_automatically", json::value(downloadUpdatesAutomatically));
result.SetNamedValue(L"is_admin", json::value(isAdmin));
result.SetNamedValue(L"theme", json::value(theme));
result.SetNamedValue(L"system_theme", json::value(systemTheme));
@ -57,19 +60,22 @@ json::JsonObject load_general_settings()
settings_theme = L"system";
}
run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false);
download_updates_automatically = loaded.GetNamedBoolean(L"download_updates_automatically", true);
return loaded;
}
GeneralSettings get_settings()
GeneralSettings get_general_settings()
{
GeneralSettings settings{
.isPackaged = winstore::running_as_packaged(),
.isElevated = is_process_elevated(),
.isRunElevated = run_as_elevated,
.isAdmin = check_user_is_admin(),
.downloadUpdatesAutomatically = download_updates_automatically,
.theme = settings_theme,
.systemTheme = WindowsColors::is_dark_mode() ? L"dark" : L"light",
.powerToysVersion = get_product_version(),
.powerToysVersion = get_product_version()
};
if (winstore::running_as_packaged())
@ -107,16 +113,12 @@ GeneralSettings get_settings()
return settings;
}
json::JsonObject get_general_settings()
{
auto settings = get_settings();
return settings.to_json();
}
void apply_general_settings(const json::JsonObject& general_configs)
{
run_as_elevated = general_configs.GetNamedBoolean(L"run_elevated", false);
download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true);
if (json::has(general_configs, L"startup", json::JsonValueType::Boolean))
{
const bool startup = general_configs.GetNamedBoolean(L"startup");
@ -192,7 +194,7 @@ void apply_general_settings(const json::JsonObject& general_configs)
settings_theme = general_configs.GetNamedString(L"theme");
}
GeneralSettings save_settings = get_settings();
GeneralSettings save_settings = get_general_settings();
PTSettingsHelper::save_general_settings(save_settings.to_json());
Trace::SettingsChanged(save_settings);
}
@ -217,7 +219,9 @@ void start_initial_powertoys()
}
}
}
catch (...) { }
catch (...)
{
}
if (powertoys_to_disable.empty())
{

View File

@ -11,6 +11,7 @@ struct GeneralSettings
bool isElevated;
bool isRunElevated;
bool isAdmin;
bool downloadUpdatesAutomatically;
std::wstring theme;
std::wstring systemTheme;
std::wstring powerToysVersion;
@ -19,6 +20,6 @@ struct GeneralSettings
};
json::JsonObject load_general_settings();
json::JsonObject get_general_settings();
GeneralSettings get_general_settings();
void apply_general_settings(const json::JsonObject& general_configs);
void start_initial_powertoys();

View File

@ -13,12 +13,14 @@
#include <common/common.h>
#include <common/dpi_aware.h>
#include <common/msi_to_msix_upgrade_lib/msi_to_msix_upgrade.h>
#include <common/winstore.h>
#include <common/notifications.h>
#include <common/timeutil.h>
#include <common/updating/updating.h>
#include "update_state.h"
#include "update_utils.h"
#include "action_runner_utils.h"
#include <winrt/Windows.System.h>
@ -33,9 +35,6 @@ namespace localized_strings
{
const wchar_t MSI_VERSION_IS_ALREADY_RUNNING[] = L"An older version of PowerToys is already running.";
const wchar_t OLDER_MSIX_UNINSTALLED[] = L"An older MSIX version of PowerToys was uninstalled.";
const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to get ";
const wchar_t GITHUB_NEW_VERSION_AGREE[] = L"Visit";
}
namespace
@ -78,78 +77,6 @@ wil::unique_mutex_nothrow create_msix_mutex()
return create_runner_mutex(true);
}
bool start_msi_uninstallation_sequence()
{
const auto package_path = get_msi_package_path();
if (package_path.empty())
{
// No MSI version detected
return true;
}
if (!offer_msi_uninstallation())
{
// User declined to uninstall or opted for "Don't show again"
return false;
}
std::wstring action_runner_path{ winrt::Windows::ApplicationModel::Package::Current().InstalledLocation().Path() };
action_runner_path += L"\\action_runner.exe";
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS };
sei.lpFile = action_runner_path.c_str();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = L"-uninstall_msi";
ShellExecuteExW(&sei);
WaitForSingleObject(sei.hProcess, INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(sei.hProcess, &exit_code);
CloseHandle(sei.hProcess);
return exit_code == 0;
}
std::future<void> check_github_updates()
{
const auto new_version = co_await check_for_new_github_release_async();
if (!new_version)
{
co_return;
}
using namespace localized_strings;
std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
contents += new_version->version_string;
contents += L'.';
notifications::show_toast_with_activations(std::move(contents), {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString().c_str() } });
}
void github_update_checking_worker()
{
const int64_t update_check_period_minutes = 60 * 24;
auto state = UpdateState::load();
for (;;)
{
int64_t sleep_minutes_till_next_update = 0;
if (state.github_update_last_checked_date.has_value())
{
int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.github_update_last_checked_date);
if (last_checked_minutes_ago < 0)
{
last_checked_minutes_ago = update_check_period_minutes;
}
sleep_minutes_till_next_update = max(0, update_check_period_minutes - last_checked_minutes_ago);
}
std::this_thread::sleep_for(std::chrono::minutes(sleep_minutes_till_next_update));
check_github_updates().get();
state.github_update_last_checked_date.emplace(timeutil::now());
state.save();
}
}
void open_menu_from_another_instance()
{
HWND hwnd_main = FindWindow(L"PToyTrayIconWindow", NULL);
@ -172,7 +99,7 @@ int runner(bool isProcessElevated)
try
{
std::thread{ [] {
github_update_checking_worker();
github_update_worker();
} }.detach();
if (winstore::running_as_packaged())
@ -183,13 +110,12 @@ int runner(bool isProcessElevated)
}
else
{
std::thread{[] {
if(uninstall_previous_msix_version_async().get())
std::thread{ [] {
if (updating::uninstall_previous_msix_version_async().get())
{
notifications::show_toast(localized_strings::OLDER_MSIX_UNINSTALLED);
}
}}.detach();
} }.detach();
}
notifications::register_background_toast_handler();
@ -244,7 +170,8 @@ enum class SpecialMode
{
None,
Win32ToastNotificationCOMServer,
ToastNotificationHandler
ToastNotificationHandler,
ReportSuccessfulUpdate
};
SpecialMode should_run_in_special_mode(const int n_cmd_args, LPWSTR* cmd_arg_list)
@ -259,6 +186,10 @@ SpecialMode should_run_in_special_mode(const int n_cmd_args, LPWSTR* cmd_arg_lis
{
return SpecialMode::ToastNotificationHandler;
}
else if (n_cmd_args == 2 && !wcscmp(UPDATE_REPORT_SUCCESS, cmd_arg_list[i]))
{
return SpecialMode::ReportSuccessfulUpdate;
}
}
return SpecialMode::None;
@ -282,6 +213,19 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_
{
return disable_cant_drag_elevated_warning() ? toast_notification_handler_result::exit_success : toast_notification_handler_result::exit_error;
}
else if (param == L"update_now/")
{
launch_action_runner(UPDATE_NOW_LAUNCH_STAGE1_CMDARG);
return toast_notification_handler_result::exit_success;
}
else if (param == L"schedule_update/")
{
UpdateState::store([](UpdateState& state) {
state.pending_update = true;
});
return toast_notification_handler_result::exit_success;
}
else
{
return toast_notification_handler_result::exit_error;
@ -292,6 +236,11 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
{
winrt::init_apartment();
if (launch_pending_update())
{
return 0;
}
int n_cmd_args = 0;
LPWSTR* cmd_arg_list = CommandLineToArgvW(GetCommandLineW(), &n_cmd_args);
switch (should_run_in_special_mode(n_cmd_args, cmd_arg_list))
@ -306,6 +255,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
case toast_notification_handler_result::exit_success:
return 0;
}
case SpecialMode::ReportSuccessfulUpdate:
notifications::show_toast(GET_RESOURCE_STRING(IDS_AUTOUPDATE_SUCCESS));
break;
case SpecialMode::None:
// continue as usual
break;

View File

@ -6,6 +6,7 @@
#define IDS_COULDNOT_RESTART_NONELEVATED 105
#define IDS_COULDNOT_RESTART_ELEVATED 106
#define IDS_ANOTHER_INSTANCE_RUNNING 107
#define IDS_AUTOUPDATE_SUCCESS 108
#define ID_EXIT_MENU_COMMAND 40001
#define ID_SETTINGS_MENU_COMMAND 40002

View File

@ -10,6 +10,7 @@
IDS_COULDNOT_RESTART_NONELEVATED "Could not start PowerToys as a user!"
IDS_COULDNOT_RESTART_ELEVATED "Could not start PowerToys as an administrator!"
IDS_ANOTHER_INSTANCE_RUNNING "PowerToys is already running."
IDS_AUTOUPDATE_SUCCESS "PowerToys was successfully updated!"
END

View File

@ -105,6 +105,7 @@
</Manifest>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="action_runner_utils.cpp" />
<ClCompile Include="auto_start_helper.cpp" />
<ClCompile Include="general_settings.cpp" />
<ClCompile Include="lowlevel_keyboard_event.cpp" />
@ -121,14 +122,17 @@
<ClCompile Include="trace.cpp" />
<ClCompile Include="tray_icon.cpp" />
<ClCompile Include="unhandled_exception_handler.cpp" />
<ClCompile Include="update_utils.cpp" />
<ClCompile Include="update_state.cpp" />
<ClCompile Include="win_hook_event.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="action_runner_utils.h" />
<ClInclude Include="auto_start_helper.h" />
<ClInclude Include="general_settings.h" />
<ClInclude Include="lowlevel_keyboard_event.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="update_utils.h" />
<ClInclude Include="update_state.h" />
<ClInclude Include="powertoys_events.h" />
<ClInclude Include="powertoy_module.h" />
@ -234,7 +238,7 @@
<ProjectReference Include="..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
<ProjectReference Include="..\common\msi_to_msix_upgrade_lib\msi_to_msix_upgrade_lib.vcxproj">
<ProjectReference Include="..\common\updating\updating.vcxproj">
<Project>{17da04df-e393-4397-9cf0-84dabe11032e}</Project>
</ProjectReference>
</ItemGroup>

View File

@ -42,6 +42,12 @@
<ClCompile Include="update_state.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="update_utils.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="action_runner_utils.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@ -85,6 +91,12 @@
<ClInclude Include="update_state.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="update_utils.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="action_runner_utils.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Utils">

View File

@ -40,7 +40,7 @@ json::JsonObject get_all_settings()
{
json::JsonObject result;
result.SetNamedValue(L"general", get_general_settings());
result.SetNamedValue(L"general", get_general_settings().to_json());
result.SetNamedValue(L"powertoys", get_power_toys_settings());
return result;
}
@ -243,7 +243,7 @@ void run_settings_window()
DWORD powertoys_pid = GetCurrentProcessId();
// Arg 4: settings theme.
const std::wstring settings_theme_setting{ get_general_settings().GetNamedString(L"theme").c_str() };
const std::wstring settings_theme_setting{ get_general_settings().theme };
std::wstring settings_theme;
if (settings_theme_setting == L"dark" || (settings_theme_setting == L"system" && WindowsColors::is_dark_mode()))
{

View File

@ -55,6 +55,7 @@ void Trace::SettingsChanged(const GeneralSettings& settings)
TraceLoggingWideString(settings.startupDisabledReason.c_str(), "StartupDisabledReason"),
TraceLoggingWideString(enabledModules.c_str(), "ModulesEnabled"),
TraceLoggingBoolean(settings.isRunElevated, "AlwaysRunElevated"),
TraceLoggingBoolean(settings.downloadUpdatesAutomatically, "DownloadUpdatesAutomatically"),
TraceLoggingWideString(settings.theme.c_str(), "Theme"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),

View File

@ -164,17 +164,16 @@ void start_tray_icon()
{
UINT id_tray_icon = wm_icon_notify = RegisterWindowMessageW(L"WM_PowerToysIconNotify");
static LPCWSTR class_name = L"PToyTrayIconWindow";
WNDCLASS wc = {};
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = h_instance;
wc.lpszClassName = class_name;
wc.lpszClassName = pt_tray_icon_window_class;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = tray_icon_window_proc;
wc.hIcon = icon;
RegisterClass(&wc);
auto hwnd = CreateWindowW(wc.lpszClassName,
L"PToyTrayIconWindow",
pt_tray_icon_window_class,
WS_OVERLAPPEDWINDOW | WS_POPUP,
CW_USEDEFAULT,
CW_USEDEFAULT,

View File

@ -9,3 +9,5 @@ void open_settings_window();
typedef void (*main_loop_callback_function)(PVOID);
// Calls a callback in _callback
bool dispatch_run_on_main_ui_thread(main_loop_callback_function _callback, PVOID data);
const inline wchar_t* pt_tray_icon_window_class = L"PToyTrayIconWindow";

View File

@ -8,31 +8,59 @@
namespace
{
const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\update_state.json";
const wchar_t UPDATE_STATE_MUTEX[] = L"PTUpdateStateMutex";
}
UpdateState UpdateState::load()
UpdateState deserialize(const json::JsonObject& json)
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
auto json = json::from_file(file_name);
UpdateState state;
UpdateState result;
if (!json)
{
return state;
}
result.github_update_last_checked_date = timeutil::from_string(json.GetNamedString(L"github_update_last_checked_date", L"invalid").c_str());
result.pending_update = json.GetNamedBoolean(L"pending_update", false);
state.github_update_last_checked_date = timeutil::from_string(json->GetNamedString(L"github_update_last_checked_date", L"invalid").c_str());
return state;
return result;
}
void UpdateState::save()
json::JsonObject serialize(const UpdateState& state)
{
json::JsonObject json;
if (github_update_last_checked_date.has_value())
if (state.github_update_last_checked_date.has_value())
{
json.SetNamedValue(L"github_update_last_checked_date", json::value(timeutil::to_string(*github_update_last_checked_date)));
json.SetNamedValue(L"github_update_last_checked_date", json::value(timeutil::to_string(*state.github_update_last_checked_date)));
}
json.SetNamedValue(L"pending_update", json::value(state.pending_update));
return json;
}
UpdateState UpdateState::read()
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> json;
{
wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) };
auto lock = mutex.acquire();
json = json::from_file(file_name);
}
return json ? deserialize(*json) : UpdateState{};
}
void UpdateState::store(std::function<void(UpdateState&)> state_modifier)
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> json;
{
wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) };
auto lock = mutex.acquire();
json = json::from_file(file_name);
UpdateState state;
if (json)
{
state = deserialize(*json);
}
state_modifier(state);
json.emplace(serialize(state));
json::to_file(file_name, *json);
}
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
json::to_file(file_name, json);
}

View File

@ -2,11 +2,16 @@
#include <ctime>
#include <optional>
#include <functional>
// All fields must be default-initialized
struct UpdateState
{
std::optional<std::time_t> github_update_last_checked_date;
bool pending_update = false;
static UpdateState load();
void save();
// To prevent concurrent modification of the file, we enforce this interface, which locks the file while
// the state_modifier is active.
static void store(std::function<void(UpdateState&)> state_modifier);
static UpdateState read();
};

View File

@ -0,0 +1,90 @@
#include "pch.h"
#include "action_runner_utils.h"
#include "update_state.h"
#include "update_utils.h"
#include <common/timeutil.h>
#include <common/updating/updating.h>
#include <runner/general_settings.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Headers.h>
bool start_msi_uninstallation_sequence()
{
const auto package_path = updating::get_msi_package_path();
if (package_path.empty())
{
// No MSI version detected
return true;
}
if (!updating::offer_msi_uninstallation())
{
// User declined to uninstall or opted for "Don't show again"
return false;
}
auto sei = launch_action_runner(L"-uninstall_msi");
WaitForSingleObject(sei.hProcess, INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(sei.hProcess, &exit_code);
CloseHandle(sei.hProcess);
return exit_code == 0;
}
void github_update_worker()
{
const int64_t update_check_period_minutes = 60 * 24;
for (;;)
{
auto state = UpdateState::read();
int64_t sleep_minutes_till_next_update = 0;
if (state.github_update_last_checked_date.has_value())
{
int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.github_update_last_checked_date);
if (last_checked_minutes_ago < 0)
{
last_checked_minutes_ago = update_check_period_minutes;
}
sleep_minutes_till_next_update = max(0, update_check_period_minutes - last_checked_minutes_ago);
}
std::this_thread::sleep_for(std::chrono::minutes(sleep_minutes_till_next_update));
const bool download_updates_automatically = get_general_settings().downloadUpdatesAutomatically;
try
{
updating::try_autoupdate(download_updates_automatically).get();
}
catch (...)
{
// Couldn't autoupdate
}
UpdateState::store([](UpdateState& state) {
state.github_update_last_checked_date.emplace(timeutil::now());
});
}
}
bool launch_pending_update()
{
try
{
auto update_state = UpdateState::read();
if (update_state.pending_update)
{
UpdateState::store([](UpdateState& state) {
state.pending_update = false;
});
launch_action_runner(UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG);
return true;
}
}
catch (...)
{
}
return false;
}

View File

@ -0,0 +1,5 @@
#pragma once
bool start_msi_uninstallation_sequence();
void github_update_worker();
bool launch_pending_update();

View File

@ -7,6 +7,7 @@ import { CustomActionSettingsControl } from './CustomActionSettingsControl';
export class GeneralSettings extends React.Component <any, any> {
references: any = {};
download_updates_automatically_reference: any;
startup_reference: any;
elevated_reference: any;
restart_reference: any;
@ -14,10 +15,11 @@ export class GeneralSettings extends React.Component <any, any> {
parent_on_change: Function;
constructor(props: any) {
super(props);
this.references={};
this.startup_reference=null;
this.elevated_reference=null;
this.restart_reference=null;
this.references = {};
this.download_updates_automatically_reference = null;
this.startup_reference = null;
this.elevated_reference = null;
this.restart_reference = null;
this.parent_on_change = props.on_change;
this.state = {
settings_key: props.settings_key,
@ -42,6 +44,7 @@ export class GeneralSettings extends React.Component <any, any> {
});
let result : any = {};
result[this.state.settings_key]= {
download_updates_automatically: this.download_updates_automatically_reference.get_value().value,
startup: this.startup_reference.get_value().value,
run_elevated: this.elevated_reference != null && this.elevated_reference.get_value().value,
theme: this.theme_reference.get_value().value,
@ -121,6 +124,17 @@ export class GeneralSettings extends React.Component <any, any> {
}
<Separator />
<Text variant='xLarge'>General</Text>
<Stack>
<Label>Download updates automatically</Label>
<BoolToggleSettingsControl
setting={{value: this.state.settings.general.download_updates_automatically}}
on_change={this.parent_on_change}
ref={(input) => {this.download_updates_automatically_reference=input;}}
/>
</Stack>
<Stack>
{this.state.settings.general.startup_disabled_reason != null &&
<span style={{color:"#c50500"}} dangerouslySetInnerHTML={{__html: this.state.settings.general.startup_disabled_reason }} />

File diff suppressed because one or more lines are too long