Skip to content

Commit

Permalink
[bootstrapper] progress bar UI polishing (#9644)
Browse files Browse the repository at this point in the history
* Formatting, coding style, variable & function name

* Progress dialog: label position and background
  • Loading branch information
enricogior committed Feb 11, 2021
1 parent d190e33 commit 687b281
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 92 deletions.
5 changes: 5 additions & 0 deletions installer/PowerToysBootstrapper/bootstrapper/RcResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@ std::optional<RcResource> RcResource::create(int resource_id, const std::wstring
{
return std::nullopt;
}

const HGLOBAL memHandle = LoadResource(nullptr, resHandle);
if (!memHandle)
{
return std::nullopt;
}

const size_t resSize = SizeofResource(nullptr, resHandle);
if (!resSize)
{
return std::nullopt;
}

auto res = static_cast<const std::byte*>(LockResource(memHandle));
if (!res)
{
return std::nullopt;
}

return RcResource{ res, resSize };
}

Expand All @@ -35,6 +39,7 @@ bool RcResource::saveAsFile(const std::filesystem::path destination)
{
return false;
}

installerFile.write(reinterpret_cast<const char*>(_memory), _size);
return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BOOTSTRAPPER_PROGRESS_TITLE" xml:space="preserve">
<value>PowerToys installer</value>
<value>PowerToys Installer</value>
</data>
<data name="DOTNET_CORE_DOWNLOAD_FAILURE" xml:space="preserve">
<value>Couldn't download .NET Core Desktop Runtime 3.1, please install it manually.</value>
Expand Down
54 changes: 35 additions & 19 deletions installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,19 @@ namespace // Strings in this namespace should not be localized

namespace fs = std::filesystem;

std::optional<fs::path> extractEmbeddedInstaller(const fs::path extractPath)
std::optional<fs::path> ExtractEmbeddedInstaller(const fs::path extractPath)
{
auto executableRes = RcResource::create(IDR_BIN_MSIINSTALLER, L"BIN");
if (!executableRes)
{
return std::nullopt;
}

auto installerPath = extractPath / L"PowerToysBootstrappedInstaller-" PRODUCT_VERSION_STRING L".msi";
return executableRes->saveAsFile(installerPath) ? std::make_optional(std::move(installerPath)) : std::nullopt;
}

void setup_log(fs::path directory, const spdlog::level::level_enum severity)
void SetupLogger(fs::path directory, const spdlog::level::level_enum severity)
{
try
{
Expand All @@ -65,6 +66,7 @@ void setup_log(fs::path directory, const spdlog::level::level_enum severity)
{
logger = spdlog::null_logger_mt("null");
}

logger->set_pattern("[%L][%d-%m-%C-%T] %v");
logger->set_level(severity);
spdlog::set_default_logger(std::move(logger));
Expand All @@ -76,26 +78,29 @@ void setup_log(fs::path directory, const spdlog::level::level_enum severity)
}
}

void show_error_box(const wchar_t* message, const wchar_t* title)
void ShowMessageBoxError(const wchar_t* message, const wchar_t* title)
{
MessageBoxW(nullptr,
message,
title,
MB_OK | MB_ICONERROR);
}

int bootstrapper(HINSTANCE hInstance)
int Bootstrapper(HINSTANCE hInstance)
{
winrt::init_apartment();
char* programFilesDir = nullptr;
size_t size = 0;
std::string defaultInstallDir;

if (!_dupenv_s(&programFilesDir, &size, "PROGRAMFILES"))
{
defaultInstallDir += programFilesDir;
defaultInstallDir += "\\PowerToys";
}

cxxopts::Options options{ "PowerToysBootstrapper" };

// clang-format off
options.add_options()
("h,help", "Show help")
Expand All @@ -108,6 +113,7 @@ int bootstrapper(HINSTANCE hInstance)
("install_dir", "Installation directory", cxxopts::value<std::string>()->default_value(defaultInstallDir))
("extract_msi", "Extract MSI to the working directory and exit. Use only if you must access MSI directly.");
// clang-format on

cxxopts::ParseResult cmdArgs;
bool showHelp = false;
try
Expand Down Expand Up @@ -178,13 +184,14 @@ int bootstrapper(HINSTANCE hInstance)
{
severity = spdlog::level::err;
}
setup_log(logDir, severity);
spdlog::debug("PowerToys Bootstrapper is launched!\nnoFullUI: {}\nsilent: {}\nno_start_pt: {}\nskip_dotnet_install: {}\nlog_level: {}\ninstall_dir: {}\nextract_msi: {}\n", noFullUI, silent, noStartPT, skipDotnetInstall, logLevel, installDirArg, extract_msi_only);

SetupLogger(logDir, severity);
spdlog::debug("PowerToys Bootstrapper is launched\nnoFullUI: {}\nsilent: {}\nno_start_pt: {}\nskip_dotnet_install: {}\nlog_level: {}\ninstall_dir: {}\nextract_msi: {}\n", noFullUI, silent, noStartPT, skipDotnetInstall, logLevel, installDirArg, extract_msi_only);

// If a user requested an MSI -> extract it and exit
if (extract_msi_only)
{
if (const auto installerPath = extractEmbeddedInstaller(fs::current_path()))
if (const auto installerPath = ExtractEmbeddedInstaller(fs::current_path()))
{
spdlog::info("MSI installer was extracted to {}", installerPath->string());
}
Expand All @@ -200,6 +207,7 @@ int bootstrapper(HINSTANCE hInstance)
{
MsiSetInternalUI(INSTALLUILEVEL_FULL, nullptr);
}

if (silent)
{
if (is_process_elevated())
Expand Down Expand Up @@ -232,12 +240,14 @@ int bootstrapper(HINSTANCE hInstance)
params += L' ';
}
}

const auto processHandle = run_elevated(argList[0], params.c_str());
if (!processHandle)
{
spdlog::error("Couldn't restart elevated to enable silent mode! ({})", GetLastError());
return 1;
}

if (WaitForSingleObject(processHandle, 3600000) == WAIT_OBJECT_0)
{
DWORD exitCode = 0;
Expand All @@ -259,6 +269,7 @@ int bootstrapper(HINSTANCE hInstance)
{
TerminateProcess(handle.get(), 0);
}

auto powerToysMutex = createAppMutex(POWERTOYS_MSI_MUTEX_NAME);
auto instanceMutex = createAppMutex(POWERTOYS_BOOTSTRAPPER_MUTEX_NAME);
if (!instanceMutex)
Expand All @@ -281,16 +292,18 @@ int bootstrapper(HINSTANCE hInstance)
}

spdlog::debug("Extracting embedded MSI installer");
const auto installerPath = extractEmbeddedInstaller(fs::temp_directory_path());
const auto installerPath = ExtractEmbeddedInstaller(fs::temp_directory_path());
if (!installerPath)
{
if (!silent)
{
show_error_box(GET_RESOURCE_STRING(IDS_INSTALLER_EXTRACT_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
ShowMessageBoxError(GET_RESOURCE_STRING(IDS_INSTALLER_EXTRACT_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
}

spdlog::error("Couldn't install the MSI installer ({})", GetLastError());
return 1;
}

auto removeExtractedInstaller = wil::scope_exit([&] {
std::error_code _;
fs::remove(*installerPath, _);
Expand All @@ -306,18 +319,20 @@ int bootstrapper(HINSTANCE hInstance)
{
spdlog::debug("Existing MSI package path not found");
}

if (!package_path.empty() && !updating::uninstall_msi_version(package_path, Strings))
{
spdlog::error("Couldn't install the existing MSI package ({})", GetLastError());
if (!silent)
{
show_error_box(GET_RESOURCE_STRING(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
ShowMessageBoxError(GET_RESOURCE_STRING(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
}
}

const bool installDotnet = !skipDotnetInstall;
if (!silent)
{
open_progressbar_window(hInstance, 0, GET_RESOURCE_STRING(IDS_BOOTSTRAPPER_PROGRESS_TITLE).c_str(), GET_RESOURCE_STRING(IDS_DOWNLOADING_DOTNET).c_str());
OpenProgressBarDialog(hInstance, 0, GET_RESOURCE_STRING(IDS_BOOTSTRAPPER_PROGRESS_TITLE).c_str(), GET_RESOURCE_STRING(IDS_DOWNLOADING_DOTNET).c_str());
}

try
Expand All @@ -329,13 +344,13 @@ int bootstrapper(HINSTANCE hInstance)
spdlog::debug("Dotnet is already installed: {}", dotnetInstalled);
if (!dotnetInstalled)
{
bool installed_successfully = false;
bool installedSuccessfully = false;
if (const auto dotnet_installer_path = updating::download_dotnet())
{
// Dotnet installer has its own progress bar
close_progressbar_window();
installed_successfully = updating::install_dotnet(*dotnet_installer_path, silent);
if (!installed_successfully)
CloseProgressBarDialog();
installedSuccessfully = updating::install_dotnet(*dotnet_installer_path, silent);
if (!installedSuccessfully)
{
spdlog::error("Couldn't install dotnet");
}
Expand All @@ -345,11 +360,11 @@ int bootstrapper(HINSTANCE hInstance)
spdlog::error("Couldn't download dotnet");
}

if (!installed_successfully)
if (!installedSuccessfully)
{
if (!silent)
{
show_error_box(GET_RESOURCE_STRING(IDS_DOTNET_INSTALL_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
ShowMessageBoxError(GET_RESOURCE_STRING(IDS_DOTNET_INSTALL_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
}
}
}
Expand All @@ -362,7 +377,7 @@ int bootstrapper(HINSTANCE hInstance)
}

// At this point, there's no reason to show progress bar window, since MSI installers have their own
close_progressbar_window();
CloseProgressBarDialog();

const std::wstring msiProps = installFolderProp;
spdlog::debug("Launching MSI installation for new package {}", installerPath->string());
Expand All @@ -384,6 +399,7 @@ int bootstrapper(HINSTANCE hInstance)
spdlog::error("Couldn't determine new MSI package install location ({})", GetLastError());
return 1;
}

*newPTPath += L"\\PowerToys.exe";
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE };
Expand All @@ -399,7 +415,7 @@ int WINAPI WinMain(HINSTANCE hi, HINSTANCE, LPSTR, int)
{
try
{
return bootstrapper(hi);
return Bootstrapper(hi);
}
catch (const std::exception& ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
<ClCompile Include="RcResource.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\runner\updating.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="progressbar_window.h" />
</ItemGroup>
Expand Down

1 comment on commit 687b281

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New misspellings found, please review:

  • CTLCOLORSTATIC
  • UITo
To accept these changes, run the following commands
perl -e '
my @expect_files=qw('".github/actions/spell-check/expect.txt"');
@ARGV=@expect_files;
my @stale=qw('"acf Actionkeyword bdaa benjamhooper bsearch deondre Elems eriawan FDFC FFB filesfolder globalplugins happlebao HKE jhutchings jsoref msazure QWORD removefolder Runtimes Rutkas Searcn Segoe snickler systray verifybothfolderfilesequal welcomeoverview XJson YJson "');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
  if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
  next if /^($re)(?:$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spell-check/expect.txt";
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"CTLCOLORSTATIC qword runtimes segoe UITo "');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a) cmp lc($b)} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;'
git add .github/actions/spell-check || echo '... you want to ensure .github/actions/spell-check/expect.txt is added to your repository...'

Please sign in to comment.