Fix crash when unpackaged due to PrimaryLanguageOverride (#10434)

## Summary of the Pull Request

`ApplicationLanguages::PrimaryLanguageOverride` requires packaged activation.
This PR prevents any such application crashes, by skipping any calls to `PrimaryLanguageOverride`, as well as hiding the language selector in the settings UI.

## PR Checklist
* [x] I work here
* [x] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.

## Validation Steps Performed

When WT is run unpackaged:
* Doesn't crash during start ✔️
* SUI doesn't show the language selector ✔️
This commit is contained in:
Leonard Hecker 2021-06-16 23:08:14 +02:00 committed by GitHub
parent 2bd5791feb
commit 1ae6e3b772
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 126 additions and 164 deletions

View file

@ -114,7 +114,7 @@ try
std::filesystem::path modulePath{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
modulePath.replace_filename(WindowsTerminalExe);
// WindowsTerminal.exe,-101 will be the first icon group in WT
// We're using WindowsTerminal here explicitly, and not wt (from _getExePath), because
// We're using WindowsTerminal here explicitly, and not wt (from GetWtExePath), because
// WindowsTerminal is the only one built with the right icons.
const auto resource{ modulePath.wstring() + L",-101" };
return SHStrDupW(resource.c_str(), ppszIcon);

View file

@ -9,6 +9,7 @@
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
#include <LibraryResources.h>
#include <WtExeUtils.h>
using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
@ -913,18 +914,26 @@ namespace winrt::TerminalApp::implementation
}
}
void AppLogic::_ApplyLanguageSettingChange()
void AppLogic::_ApplyLanguageSettingChange() noexcept
try
{
if (!IsPackaged())
{
return;
}
using ApplicationLanguages = winrt::Windows::Globalization::ApplicationLanguages;
const auto language = _settings.GlobalSettings().Language();
// NOTE: PrimaryLanguageOverride throws if this instance is unpackaged.
const auto primaryLanguageOverride = ApplicationLanguages::PrimaryLanguageOverride();
const auto language = _settings.GlobalSettings().Language();
if (primaryLanguageOverride != language)
{
ApplicationLanguages::PrimaryLanguageOverride(language);
}
}
CATCH_LOG()
void AppLogic::_RefreshThemeRoutine()
{

View file

@ -131,7 +131,7 @@ namespace winrt::TerminalApp::implementation
void _ShowLoadWarningsDialog();
bool _IsKeyboardServiceEnabled();
void _ApplyLanguageSettingChange();
void _ApplyLanguageSettingChange() noexcept;
void _RefreshThemeRoutine();
fire_and_forget _ApplyStartupTaskStateChange();

View file

@ -7,6 +7,8 @@
#include <ShObjIdl.h>
#include <Propkey.h>
#include <WtExeUtils.h>
using namespace winrt::Microsoft::Terminal::Settings::Model;
// This property key isn't already defined in propkey.h, but is used by UWP Jumplist to determine the icon of the jumplist item.
@ -52,66 +54,6 @@ static std::wstring _normalizeIconPath(std::wstring_view path)
return std::wstring{ fullPath };
}
// Function Description:
// - Helper function for getting the path to the appropriate executable to use
// for this instance of the jumplist. For the dev build, it should be `wtd.exe`,
// but if we're preview or release, we want to make sure to get the correct
// `wt.exe` that corresponds to _us_.
// - If we're unpackaged, this needs to get us `WindowsTerminal.exe`, because
// the `wt*exe` alias won't have been installed for this install.
// Arguments:
// - <none>
// Return Value:
// - the full path to the exe, one of `wt.exe`, `wtd.exe`, or `WindowsTerminal.exe`.
static std::wstring_view _getExePath()
{
static constexpr std::wstring_view WtExe{ L"wt.exe" };
static constexpr std::wstring_view WindowsTerminalExe{ L"WindowsTerminal.exe" };
static constexpr std::wstring_view WtdExe{ L"wtd.exe" };
static constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\" };
// use C++11 magic statics to make sure we only do this once.
static const std::wstring exePath = []() -> std::wstring {
// First, check a packaged location for the exe. If we've got a package
// family name, that means we're one of the packaged Dev build, packaged
// Release build, or packaged Preview build.
//
// If we're the preview or release build, there's no way of knowing if the
// `wt.exe` on the %PATH% is us or not. Fortunately, _our_ execution alias
// is located in "%LOCALAPPDATA%\Microsoft\WindowsApps\<our package family
// name>", _always_, so we can use that to look up the exe easier.
try
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
const auto id{ package.Id() };
const std::wstring pfn{ id.FamilyName() };
const auto isDevPackage{ pfn.rfind(L"WindowsTerminalDev") == 0 };
if (!pfn.empty())
{
const std::filesystem::path windowsAppsPath{ wil::ExpandEnvironmentStringsW<std::wstring>(LocalAppDataAppsPath.data()) };
const std::filesystem::path wtPath{ windowsAppsPath / pfn / (isDevPackage ? WtdExe : WtExe) };
return wtPath;
}
}
CATCH_LOG();
// If we're here, then we couldn't resolve our exe from the package. This
// means we're running unpackaged. We should just use the
// WindowsTerminal.exe that's sitting in the directory next to us.
try
{
std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
module.replace_filename(WindowsTerminalExe);
return module;
}
CATCH_LOG();
return std::wstring{ WtExe };
}();
return exePath;
}
// Method Description:
// - Updates the items of the Jumplist based on the given settings.
// Arguments:
@ -187,7 +129,7 @@ winrt::fire_and_forget Jumplist::UpdateJumplist(const CascadiaSettings& settings
// - Creates a ShellLink object. Each item in a jumplist is a ShellLink, which is sort of
// like a shortcut. It requires the path to the application (wt.exe), the arguments to pass,
// and the path to the icon for the jumplist item. The path to the application isn't passed
// into this function, as we'll determine it with _getExePath
// into this function, as we'll determine it with GetWtExePath
// Arguments:
// - name: The name of the item displayed in the jumplist.
// - path: The path to the icon for the jumplist item.
@ -204,7 +146,7 @@ winrt::fire_and_forget Jumplist::UpdateJumplist(const CascadiaSettings& settings
{
auto sh = winrt::create_instance<IShellLinkW>(CLSID_ShellLink, CLSCTX_ALL);
const auto module{ _getExePath() };
const auto module{ GetWtExePath() };
RETURN_IF_FAILED(sh->SetPath(module.data()));
RETURN_IF_FAILED(sh->SetArguments(args.data()));

View file

@ -8,6 +8,7 @@
#include "GlobalAppearancePageNavigationState.g.cpp"
#include <LibraryResources.h>
#include <WtExeUtils.h>
using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
@ -47,6 +48,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return language.NativeName();
}
// Returns whether the language selector is available/shown.
//
// winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride()
// doesn't work for unpackaged applications. The corresponding code in TerminalApp is disabled.
// It would be confusing for our users if we presented a dysfunctional language selector.
bool GlobalAppearance::LanguageSelectorAvailable()
{
return IsPackaged();
}
// Returns the list of languages the user may override the application language with.
// The returned list are BCP 47 language tags like {"und", "en-US", "de-DE", "es-ES", ...}.
// "und" is short for "undefined" and is synonymous for "Use system language" in this code.
@ -57,6 +68,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return _languageList;
}
if (!LanguageSelectorAvailable())
{
_languageList = {};
return _languageList;
}
// In order to return the language list this code does the following:
// [1] Get all possible languages we want to allow the user to choose.
// We have to acquire languages from multiple sources, creating duplicates. See below at [1].
@ -144,30 +161,41 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::Windows::Foundation::IInspectable GlobalAppearance::CurrentLanguage()
{
if (_currentLanguage.empty())
if (_currentLanguage)
{
_currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride();
if (_currentLanguage.empty())
{
_currentLanguage = systemLanguageTag;
}
return _currentLanguage;
}
return winrt::box_value(_currentLanguage);
if (!LanguageSelectorAvailable())
{
_currentLanguage = {};
return _currentLanguage;
}
// NOTE: PrimaryLanguageOverride throws if this instance is unpackaged.
auto currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride();
if (currentLanguage.empty())
{
currentLanguage = systemLanguageTag;
}
_currentLanguage = winrt::box_value(currentLanguage);
return _currentLanguage;
}
void GlobalAppearance::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag)
{
_currentLanguage = winrt::unbox_value<winrt::hstring>(tag);
_currentLanguage = tag;
const auto currentLanguage = winrt::unbox_value<winrt::hstring>(_currentLanguage);
const auto globals = _State.Globals();
if (_currentLanguage == systemLanguageTag)
if (currentLanguage == systemLanguageTag)
{
globals.ClearLanguage();
}
else
{
globals.Language(_currentLanguage);
globals.Language(currentLanguage);
}
}
}

View file

@ -35,15 +35,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// "Deutsch (Deutschland)". This works independently of the user's locale.
static winrt::hstring LanguageDisplayConverter(const winrt::hstring& tag);
bool LanguageSelectorAvailable();
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> LanguageList();
winrt::Windows::Foundation::IInspectable CurrentLanguage();
void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag);
private:
std::vector<winrt::hstring> _GetSupportedLanguageTags();
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> _languageList{ nullptr };
winrt::hstring _currentLanguage;
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> _languageList;
winrt::Windows::Foundation::IInspectable _currentLanguage;
};
}

View file

@ -16,6 +16,7 @@ namespace Microsoft.Terminal.Settings.Editor
GlobalAppearancePageNavigationState State { get; };
static String LanguageDisplayConverter(String tag);
Boolean LanguageSelectorAvailable { get; };
Windows.Foundation.Collections.IObservableVector<String> LanguageList { get; };
IInspectable CurrentLanguage;

View file

@ -30,7 +30,8 @@
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Language -->
<local:SettingContainer x:Uid="Globals_Language"
Margin="0">
Margin="0"
Visibility="{x:Bind LanguageSelectorAvailable}">
<ComboBox ItemsSource="{x:Bind LanguageList}"
SelectedItem="{x:Bind CurrentLanguage, Mode=TwoWay}">
<ComboBox.ItemTemplate>

View file

@ -20,6 +20,7 @@
#undef GetCurrentTime
#endif
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Globalization.h>

View file

@ -2,21 +2,14 @@
// Licensed under the MIT license.
#include "pch.h"
#include <argb.h>
#include <conattrs.hpp>
#include <io.h>
#include <fcntl.h>
#include "CascadiaSettings.h"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
#include "Utils.h"
#include "LibraryResources.h"
#include "CascadiaSettings.g.cpp"
#include <LibraryResources.h>
#include "AzureCloudShellGenerator.h"
#include "PowershellCoreProfileGenerator.h"
#include "WslDistroGenerator.h"
#include "AzureCloudShellGenerator.h"
#include "CascadiaSettings.g.cpp"
using namespace ::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal;

View file

@ -147,7 +147,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
std::unordered_set<std::string> _AccumulateJsonFilesInDirectory(const std::wstring_view directory);
void _ParseAndLayerFragmentFiles(const std::unordered_set<std::string> files, const winrt::hstring source);
static bool _IsPackaged();
static void _WriteSettings(std::string_view content, const hstring filepath);
static std::optional<std::string> _ReadUserSettings();
static std::optional<std::string> _ReadFile(HANDLE hFile);

View file

@ -2,15 +2,12 @@
// Licensed under the MIT license.
#include "pch.h"
#include <argb.h>
#include "CascadiaSettings.h"
#include "../../types/inc/utils.hpp"
#include "Utils.h"
#include "JsonUtils.h"
#include <appmodel.h>
#include <shlobj.h>
#include <fmt/chrono.h>
#include "DefaultProfileUtils.h"
#include <shlobj.h>
#include <WtExeUtils.h>
// defaults.h is a file containing the default json settings in a std::string_view
#include "defaults.h"
@ -1058,20 +1055,6 @@ winrt::com_ptr<ColorScheme> CascadiaSettings::_FindMatchingColorScheme(const Jso
return nullptr;
}
// Function Description:
// - Returns true if we're running in a packaged context.
// If we are, we want to change our settings path slightly.
// Arguments:
// - <none>
// Return Value:
// - true iff we're running in a packaged context.
bool CascadiaSettings::_IsPackaged()
{
UINT32 length = 0;
LONG rc = GetCurrentPackageFullName(&length, nullptr);
return rc != APPMODEL_ERROR_NO_PACKAGE;
}
// Method Description:
// - Writes the given content in UTF-8 to a settings file using the Win32 APIS's.
// Will overwrite any existing content in the file.
@ -1216,7 +1199,7 @@ winrt::hstring CascadiaSettings::SettingsPath()
std::filesystem::path parentDirectoryForSettingsFile{ localAppDataFolder.get() };
if (!_IsPackaged())
if (!IsPackaged())
{
parentDirectoryForSettingsFile /= UnpackagedSettingsFolderName;
}

View file

@ -25,23 +25,22 @@
#include <wil/cppwinrt.h>
#include <unknwn.h>
#include <hstring.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.AppExtensions.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.System.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.Terminal.Core.h>
#include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
// Including TraceLogging essentials for the binary
#include <TraceLoggingProvider.h>
#include <winmeta.h>
@ -52,11 +51,5 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
// JsonCpp
#include <json.h>
#include <shellapi.h>
#include <winrt/Microsoft.Terminal.Core.h>
#include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"

View file

@ -1,9 +1,23 @@
constexpr std::wstring_view WtExe{ L"wt.exe" };
constexpr std::wstring_view WtdExe{ L"wtd.exe" };
constexpr std::wstring_view WindowsTerminalExe{ L"WindowsTerminal.exe" };
constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\" };
static constexpr std::wstring_view WtExe{ L"wt.exe" };
static constexpr std::wstring_view WtdExe{ L"wtd.exe" };
static constexpr std::wstring_view WindowsTerminalExe{ L"WindowsTerminal.exe" };
static constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\" };
_TIL_INLINEPREFIX bool IsPackaged()
{
static const bool isPackaged = []() -> bool {
try
{
const auto package = winrt::Windows::ApplicationModel::Package::Current();
return true;
}
catch (...)
{
return false;
}
}();
return isPackaged;
}
// Function Description:
// - This is a helper to determine if we're running as a part of the Dev Build
@ -18,19 +32,21 @@ static constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Micro
_TIL_INLINEPREFIX bool IsDevBuild()
{
// use C++11 magic statics to make sure we only do this once.
static bool isDevBuild = []() -> bool {
try
static const bool isDevBuild = []() -> bool {
if (IsPackaged())
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
const auto id = package.Id();
const std::wstring name{ id.FullName() };
// Does our PFN start with WindowsTerminalDev?
return name.rfind(L"WindowsTerminalDev", 0) == 0;
try
{
const auto package = winrt::Windows::ApplicationModel::Package::Current();
const auto id = package.Id();
const auto name = id.FullName();
return til::starts_with(name, L"WindowsTerminalDev");
}
CATCH_LOG();
}
CATCH_LOG();
return true;
}();
return isDevBuild;
}
@ -45,9 +61,8 @@ _TIL_INLINEPREFIX bool IsDevBuild()
// - <none>
// Return Value:
// - the full path to the exe, one of `wt.exe`, `wtd.exe`, or `WindowsTerminal.exe`.
_TIL_INLINEPREFIX std::wstring GetWtExePath()
_TIL_INLINEPREFIX const std::wstring& GetWtExePath()
{
// use C++11 magic statics to make sure we only do this once.
static const std::wstring exePath = []() -> std::wstring {
// First, check a packaged location for the exe. If we've got a package
// family name, that means we're one of the packaged Dev build, packaged
@ -57,37 +72,35 @@ _TIL_INLINEPREFIX std::wstring GetWtExePath()
// `wt.exe` on the %PATH% is us or not. Fortunately, _our_ execution alias
// is located in "%LOCALAPPDATA%\Microsoft\WindowsApps\<our package family
// name>", _always_, so we can use that to look up the exe easier.
try
if (IsPackaged())
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
const auto id = package.Id();
const std::wstring pfn{ id.FamilyName() };
if (!pfn.empty())
try
{
const std::filesystem::path windowsAppsPath{ wil::ExpandEnvironmentStringsW<std::wstring>(LocalAppDataAppsPath.data()) };
const std::filesystem::path wtPath = windowsAppsPath / pfn / (IsDevBuild() ? WtdExe : WtExe);
return wtPath;
const auto package = winrt::Windows::ApplicationModel::Package::Current();
const auto id = package.Id();
const auto pfn = id.FamilyName();
if (!pfn.empty())
{
const std::filesystem::path windowsAppsPath{ wil::ExpandEnvironmentStringsW<std::wstring>(LocalAppDataAppsPath.data()) };
const std::filesystem::path wtPath = windowsAppsPath / std::wstring_view{ pfn } / (IsDevBuild() ? WtdExe : WtExe);
return wtPath;
}
}
CATCH_LOG();
}
CATCH_LOG();
// If we're here, then we couldn't resolve our exe from the package. This
// means we're running unpackaged. We should just use the
// WindowsTerminal.exe that's sitting in the directory next to us.
try
{
HMODULE hModule = GetModuleHandle(nullptr);
THROW_LAST_ERROR_IF(hModule == nullptr);
std::wstring dllPathString;
THROW_IF_FAILED(wil::GetModuleFileNameW(hModule, dllPathString));
const std::filesystem::path dllPath{ dllPathString };
const std::filesystem::path rootDir = dllPath.parent_path();
std::filesystem::path wtPath = rootDir / WindowsTerminalExe;
return wtPath;
std::filesystem::path module = wil::GetModuleFileNameW<std::wstring>(nullptr);
module.replace_filename(WindowsTerminalExe);
return module;
}
CATCH_LOG();
return L"wt.exe";
return std::wstring{ WtExe };
}();
return exePath;
}