From 24475acba845c2f2bf24d58245470d66ef487164 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 24 Sep 2024 23:14:31 +0200 Subject: [PATCH] Add input scope startup setting (#17953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a "defaultInputScope" setting, hooks it up to our TSF, and exposes it as a setting in the UI under the startup page. In order to stay close with the other language setting, I moved that one from the appearance to the startup page as well. 20 out of the 26 files in this PR are boilerplate unfortunately. Closes #17816 ## Validation Steps Performed * Install and use the Chinese IME * Launch WT * Chinese input ✅ * Change setting to `alphanumericHalfWidth` * Restart WT * English input ✅ (cherry picked from commit fc606d2bae8a99b7dc7a42dd205b0faafff0e7d8) Service-Card-Id: PVTI_lADOAF3p4s4AmhmQzgTTKAI Service-Version: 1.22 --- .github/actions/spelling/expect/expect.txt | 8 +- src/cascadia/TerminalControl/EventArgs.idl | 6 + .../TerminalControl/IControlSettings.idl | 1 + src/cascadia/TerminalControl/TermControl.cpp | 5 + .../GlobalAppearance.xaml | 14 -- .../GlobalAppearanceViewModel.cpp | 163 ----------------- .../GlobalAppearanceViewModel.h | 12 -- .../GlobalAppearanceViewModel.idl | 5 - .../TerminalSettingsEditor/Launch.xaml | 23 +++ .../LaunchViewModel.cpp | 165 ++++++++++++++++++ .../TerminalSettingsEditor/LaunchViewModel.h | 13 ++ .../LaunchViewModel.idl | 8 + .../Resources/en-US/Resources.resw | 15 +- .../TerminalSettingsModel/EnumMappings.cpp | 1 + .../TerminalSettingsModel/EnumMappings.h | 1 + .../TerminalSettingsModel/EnumMappings.idl | 1 + .../GlobalAppSettings.cpp | 4 + .../GlobalAppSettings.idl | 1 + .../TerminalSettingsModel/MTSMSettings.h | 1 + .../TerminalSettings.cpp | 1 + .../TerminalSettingsModel/TerminalSettings.h | 1 + .../TerminalSettingsSerializationHelpers.h | 8 + src/cascadia/inc/ControlProperties.h | 1 + src/tsf/Handle.cpp | 5 + src/tsf/Handle.h | 1 + src/tsf/Implementation.cpp | 114 ++++++++++-- src/tsf/Implementation.h | 26 +++ src/tsf/precomp.h | 50 +----- 28 files changed, 399 insertions(+), 255 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 1621856787d..6d5ae8b95f4 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -864,6 +864,7 @@ INLINEPREFIX inproc Inputkeyinfo Inputreadhandledata +INPUTSCOPE INSERTMODE INTERACTIVITYBASE INTERCEPTCOPYPASTE @@ -1289,6 +1290,7 @@ parms PATCOPY pathcch PATTERNID +pbstr pcb pcch PCCHAR @@ -1374,9 +1376,11 @@ POSTCHARBREAKS POSX POSXSCROLL POSYSCROLL +ppbstr PPEB ppf ppidl +pprg PPROC ppropvar ppsi @@ -1692,6 +1696,7 @@ srcsrv SRCSRVTRG srctool srect +SRGS srvinit srvpipe ssa @@ -1838,7 +1843,6 @@ triaging TRIMZEROHEADINGS trx tsa -tsattrs tsgr tsm TStr @@ -1966,10 +1970,8 @@ vswhere vtapp VTE VTID -vtio vtmode vtpipeterm -vtpt VTRGB VTRGBTo vtseq diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index c3255bc6672..4bc768c264b 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -25,6 +25,12 @@ namespace Microsoft.Terminal.Control Console, }; + enum DefaultInputScope + { + Default, + AlphanumericHalfWidth, + }; + runtimeclass FontSizeChangedArgs { Int32 Width { get; }; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index a7c7581ed50..96b5b6b95f8 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -63,6 +63,7 @@ namespace Microsoft.Terminal.Control Boolean DisablePartialInvalidation { get; }; Boolean SoftwareRendering { get; }; Microsoft.Terminal.Control.TextMeasurement TextMeasurement { get; }; + Microsoft.Terminal.Control.DefaultInputScope DefaultInputScope { get; }; Boolean ShowMarks { get; }; Boolean UseBackgroundImageForWindow { get; }; Boolean RightClickContextMenu { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 74c9a679ca6..97e0bb42a35 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -861,6 +861,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation } _interactivity.UpdateSettings(); + { + const auto inputScope = settings.DefaultInputScope(); + const auto alpha = inputScope == DefaultInputScope::AlphanumericHalfWidth; + ::Microsoft::Console::TSF::Handle::SetDefaultScopeAlphanumericHalfWidth(alpha); + } if (_automationPeer) { _automationPeer.SetControlPadding(Core::Padding{ diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml index 26bb23a4149..c1b8c31acac 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml @@ -27,20 +27,6 @@ - - - - - - - - - - - -#include using namespace winrt; using namespace winrt::Windows::UI::Xaml; @@ -18,28 +17,6 @@ using namespace winrt::Windows::Foundation::Collections; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - // For ComboBox an empty SelectedItem string denotes no selection. - // What we want instead is for "Use system language" to be selected by default. - // --> "und" is synonymous for "Use system language". - constexpr std::wstring_view systemLanguageTag{ L"und" }; - - static constexpr std::array appLanguageTags{ - L"en-US", - L"de-DE", - L"es-ES", - L"fr-FR", - L"it-IT", - L"ja", - L"ko", - L"pt-BR", - L"qps-PLOC", - L"qps-PLOCA", - L"qps-PLOCM", - L"ru", - L"zh-Hans", - L"zh-Hant", - }; - constexpr std::wstring_view systemThemeName{ L"system" }; constexpr std::wstring_view darkThemeName{ L"dark" }; constexpr std::wstring_view lightThemeName{ L"light" }; @@ -56,146 +33,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _UpdateThemeList(); } - winrt::hstring GlobalAppearanceViewModel::LanguageDisplayConverter(const winrt::hstring& tag) - { - if (tag == systemLanguageTag) - { - return RS_(L"Globals_LanguageDefault"); - } - - winrt::Windows::Globalization::Language language{ tag }; - 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 GlobalAppearanceViewModel::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. - winrt::Windows::Foundation::Collections::IObservableVector GlobalAppearanceViewModel::LanguageList() - { - if (_languageList) - { - 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]. - // [2] Sort languages by their ASCII tags, forcing the UI in a consistent/stable order. - // I wanted to sort the localized language names initially, but it turned out to be complex. - // [3] Remove potential duplicates in our language list from [1]. - // We don't want to have en-US twice in the list, do we? - // [4] Optionally remove unwanted language tags (like pseudo-localizations). - - std::vector tags; - - // [1]: - { - // ManifestLanguages contains languages the app ships with. - // Unfortunately, we cannot use this source. Our manifest must contain the - // ~100 languages that are localized for the shell extension and start menu - // presentation so we align with Windows display languages for those surfaces. - // However, the actual content of our application is limited to a much smaller - // subset of approximately 14 languages. As such, we will code the limited - // subset of languages that we support for selection within the Settings - // dropdown to steer users towards the ones that we can display in the app. - - // As per the function definition, the first item - // is always "Use system language" ("und"). - tags.emplace_back(systemLanguageTag); - - // Add our hard-coded languages after the system definition. - for (const auto& v : appLanguageTags) - { - tags.push_back(v); - } - } - - // NOTE: The size of tags is always >0, due to tags[0] being hard-coded to "und". - const auto tagsBegin = ++tags.begin(); - const auto tagsEnd = tags.end(); - - // [2]: - std::sort(tagsBegin, tagsEnd); - - // I'd love for both, std::unique and std::remove_if, to occur in a single loop, - // but the code turned out to be complex and even less maintainable, so I gave up. - { - // [3] part 1: - auto it = std::unique(tagsBegin, tagsEnd); - - // The qps- languages are useful for testing ("pseudo-localization"). - // --> Leave them in if debug features are enabled. - if (!_GlobalSettings.DebugFeaturesEnabled()) - { - // [4] part 1: - it = std::remove_if(tagsBegin, it, [](const winrt::hstring& tag) -> bool { - return til::starts_with(tag, L"qps-"); - }); - } - - // [3], [4] part 2 (completing the so called "erase-remove idiom"): - tags.erase(it, tagsEnd); - } - - _languageList = winrt::single_threaded_observable_vector(std::move(tags)); - return _languageList; - } - - winrt::Windows::Foundation::IInspectable GlobalAppearanceViewModel::CurrentLanguage() - { - if (_currentLanguage) - { - return _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 GlobalAppearanceViewModel::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag) - { - _currentLanguage = tag; - - const auto currentLanguage = winrt::unbox_value(_currentLanguage); - if (currentLanguage == systemLanguageTag) - { - _GlobalSettings.ClearLanguage(); - } - else - { - _GlobalSettings.Language(currentLanguage); - } - } - // Function Description: // - Updates the list of all themes available to choose from. void GlobalAppearanceViewModel::_UpdateThemeList() diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.h b/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.h index e71a034de95..052ae6b7ca3 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.h @@ -22,16 +22,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, _GlobalSettings.TabWidthMode); public: - // LanguageDisplayConverter maps the given BCP 47 tag to a localized string. - // For instance "en-US" produces "English (United States)", while "de-DE" produces - // "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 LanguageList(); - winrt::Windows::Foundation::IInspectable CurrentLanguage(); - void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag); - winrt::Windows::Foundation::IInspectable CurrentTheme(); void CurrentTheme(const winrt::Windows::Foundation::IInspectable& tag); static winrt::hstring ThemeNameConverter(const Model::Theme& theme); @@ -52,8 +42,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation private: Model::GlobalAppSettings _GlobalSettings; - winrt::Windows::Foundation::Collections::IObservableVector _languageList; - winrt::Windows::Foundation::IInspectable _currentLanguage; winrt::Windows::Foundation::IInspectable _currentTheme; void _UpdateThemeList(); diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.idl b/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.idl index 8dd700edc3d..c8e2f8809c8 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.idl @@ -11,11 +11,6 @@ namespace Microsoft.Terminal.Settings.Editor { GlobalAppearanceViewModel(Microsoft.Terminal.Settings.Model.GlobalAppSettings globalSettings); - static String LanguageDisplayConverter(String tag); - Boolean LanguageSelectorAvailable { get; }; - Windows.Foundation.Collections.IObservableVector LanguageList { get; }; - IInspectable CurrentLanguage; - IInspectable CurrentTheme; static String ThemeNameConverter(Microsoft.Terminal.Settings.Model.Theme theme); Windows.Foundation.Collections.IObservableVector ThemeList { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 2498662a4ce..77afb1a178c 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -139,6 +139,29 @@ + + + + + + + + + + + + + + + + +#include using namespace winrt::Windows::UI::Xaml::Navigation; using namespace winrt::Windows::Foundation; @@ -14,11 +16,34 @@ using namespace winrt::Windows::UI::Xaml::Data; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { + // For ComboBox an empty SelectedItem string denotes no selection. + // What we want instead is for "Use system language" to be selected by default. + // --> "und" is synonymous for "Use system language". + constexpr std::wstring_view systemLanguageTag{ L"und" }; + + static constexpr std::array appLanguageTags{ + L"en-US", + L"de-DE", + L"es-ES", + L"fr-FR", + L"it-IT", + L"ja", + L"ko", + L"pt-BR", + L"qps-PLOC", + L"qps-PLOCA", + L"qps-PLOCM", + L"ru", + L"zh-Hans", + L"zh-Hant", + }; + LaunchViewModel::LaunchViewModel(Model::CascadiaSettings settings) : _Settings{ settings } { _useDefaultLaunchPosition = isnan(InitialPosX()) && isnan(InitialPosY()); + INITIALIZE_BINDABLE_ENUM_SETTING(DefaultInputScope, DefaultInputScope, winrt::Microsoft::Terminal::Control::DefaultInputScope, L"Globals_DefaultInputScope", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(FirstWindowPreference, FirstWindowPreference, FirstWindowPreference, L"Globals_FirstWindowPreference", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_LaunchMode", L"Content"); // More options were added to the JSON mapper when the enum was made into [Flags] @@ -40,6 +65,146 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation }); } + winrt::hstring LaunchViewModel::LanguageDisplayConverter(const winrt::hstring& tag) + { + if (tag == systemLanguageTag) + { + return RS_(L"Globals_LanguageDefault"); + } + + winrt::Windows::Globalization::Language language{ tag }; + 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 LaunchViewModel::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. + winrt::Windows::Foundation::Collections::IObservableVector LaunchViewModel::LanguageList() + { + if (_languageList) + { + 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]. + // [2] Sort languages by their ASCII tags, forcing the UI in a consistent/stable order. + // I wanted to sort the localized language names initially, but it turned out to be complex. + // [3] Remove potential duplicates in our language list from [1]. + // We don't want to have en-US twice in the list, do we? + // [4] Optionally remove unwanted language tags (like pseudo-localizations). + + std::vector tags; + + // [1]: + { + // ManifestLanguages contains languages the app ships with. + // Unfortunately, we cannot use this source. Our manifest must contain the + // ~100 languages that are localized for the shell extension and start menu + // presentation so we align with Windows display languages for those surfaces. + // However, the actual content of our application is limited to a much smaller + // subset of approximately 14 languages. As such, we will code the limited + // subset of languages that we support for selection within the Settings + // dropdown to steer users towards the ones that we can display in the app. + + // As per the function definition, the first item + // is always "Use system language" ("und"). + tags.emplace_back(systemLanguageTag); + + // Add our hard-coded languages after the system definition. + for (const auto& v : appLanguageTags) + { + tags.push_back(v); + } + } + + // NOTE: The size of tags is always >0, due to tags[0] being hard-coded to "und". + const auto tagsBegin = ++tags.begin(); + const auto tagsEnd = tags.end(); + + // [2]: + std::sort(tagsBegin, tagsEnd); + + // I'd love for both, std::unique and std::remove_if, to occur in a single loop, + // but the code turned out to be complex and even less maintainable, so I gave up. + { + // [3] part 1: + auto it = std::unique(tagsBegin, tagsEnd); + + // The qps- languages are useful for testing ("pseudo-localization"). + // --> Leave them in if debug features are enabled. + if (!_Settings.GlobalSettings().DebugFeaturesEnabled()) + { + // [4] part 1: + it = std::remove_if(tagsBegin, it, [](const winrt::hstring& tag) -> bool { + return til::starts_with(tag, L"qps-"); + }); + } + + // [3], [4] part 2 (completing the so called "erase-remove idiom"): + tags.erase(it, tagsEnd); + } + + _languageList = winrt::single_threaded_observable_vector(std::move(tags)); + return _languageList; + } + + winrt::Windows::Foundation::IInspectable LaunchViewModel::CurrentLanguage() + { + if (_currentLanguage) + { + return _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 LaunchViewModel::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag) + { + _currentLanguage = tag; + + const auto currentLanguage = winrt::unbox_value(_currentLanguage); + if (currentLanguage == systemLanguageTag) + { + _Settings.GlobalSettings().ClearLanguage(); + } + else + { + _Settings.GlobalSettings().Language(currentLanguage); + } + } + winrt::hstring LaunchViewModel::LaunchParametersCurrentValue() { const auto launchModeString = CurrentLaunchMode().as()->EnumName(); diff --git a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h index baa8cb56856..be4ba46728c 100644 --- a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.h @@ -14,6 +14,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation public: LaunchViewModel(Model::CascadiaSettings settings); + // LanguageDisplayConverter maps the given BCP 47 tag to a localized string. + // For instance "en-US" produces "English (United States)", while "de-DE" produces + // "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 LanguageList(); + winrt::Windows::Foundation::IInspectable CurrentLanguage(); + void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag); + winrt::hstring LaunchParametersCurrentValue(); double InitialPosX(); double InitialPosY(); @@ -35,6 +45,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void CurrentLaunchMode(const winrt::Windows::Foundation::IInspectable& enumEntry); winrt::Windows::Foundation::Collections::IObservableVector LaunchModeList(); + GETSET_BINDABLE_ENUM_SETTING(DefaultInputScope, winrt::Microsoft::Terminal::Control::DefaultInputScope, _Settings.GlobalSettings().DefaultInputScope); GETSET_BINDABLE_ENUM_SETTING(FirstWindowPreference, Model::FirstWindowPreference, _Settings.GlobalSettings().FirstWindowPreference); GETSET_BINDABLE_ENUM_SETTING(WindowingBehavior, Model::WindowingMode, _Settings.GlobalSettings().WindowingBehavior); @@ -45,6 +56,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation private: Model::CascadiaSettings _Settings; + winrt::Windows::Foundation::Collections::IObservableVector _languageList; + winrt::Windows::Foundation::IInspectable _currentLanguage; bool _useDefaultLaunchPosition; winrt::Windows::Foundation::Collections::IObservableVector _LaunchModeList; diff --git a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl index c5534636f40..da1cebfe630 100644 --- a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.idl @@ -11,6 +11,14 @@ namespace Microsoft.Terminal.Settings.Editor { LaunchViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); + static String LanguageDisplayConverter(String tag); + Boolean LanguageSelectorAvailable { get; }; + Windows.Foundation.Collections.IObservableVector LanguageList { get; }; + IInspectable CurrentLanguage; + + IInspectable CurrentDefaultInputScope; + IObservableVector DefaultInputScopeList { get; }; + String LaunchParametersCurrentValue { get; }; Double InitialPosX; Double InitialPosY; diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index cffae127f5a..bf341968913 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -246,9 +246,22 @@ A description explaining how this control changes the app's language. {Locked="Windows"} - Use system default + Default The app contains a control allowing users to choose the app's language. If this value is chosen, the language preference list of the system settings is used instead. + + Default IME input mode + The "input scope" of an IME tells it what type of characters should be available for input. A Chinese IME for instance may show English letters instead of Chinese ones. It's a technical term. We're using "input mode" instead of "input scope" to make it easier to understand for users. + + + Informs the IME what language to use by default. For languages that rely on an IME and don't use Latin characters by default, this setting can be used to force English input on startup. + + + Default + + + Alphanumeric Half-Width (English) + Always show tabs Header for a control to toggle if the app should always show the tabs (similar to a website browser). diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index 87b65b4fb2d..2ab4afc15c7 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -33,6 +33,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation DEFINE_ENUM_MAP(winrt::Windows::UI::Xaml::ElementTheme, ElementTheme); DEFINE_ENUM_MAP(Model::NewTabPosition, NewTabPosition); DEFINE_ENUM_MAP(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabViewWidthMode); + DEFINE_ENUM_MAP(Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope); DEFINE_ENUM_MAP(Model::FirstWindowPreference, FirstWindowPreference); DEFINE_ENUM_MAP(Model::LaunchMode, LaunchMode); DEFINE_ENUM_MAP(Model::TabSwitcherMode, TabSwitcherMode); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index f76274b0497..57f5681de8f 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -29,6 +29,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::Windows::Foundation::Collections::IMap ElementTheme(); static winrt::Windows::Foundation::Collections::IMap NewTabPosition(); static winrt::Windows::Foundation::Collections::IMap TabViewWidthMode(); + static winrt::Windows::Foundation::Collections::IMap DefaultInputScope(); static winrt::Windows::Foundation::Collections::IMap FirstWindowPreference(); static winrt::Windows::Foundation::Collections::IMap LaunchMode(); static winrt::Windows::Foundation::Collections::IMap TabSwitcherMode(); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index f5def5fbaf9..8b4fc9493ac 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -11,6 +11,7 @@ namespace Microsoft.Terminal.Settings.Model static Windows.Foundation.Collections.IMap ElementTheme { get; }; static Windows.Foundation.Collections.IMap NewTabPosition { get; }; static Windows.Foundation.Collections.IMap TabViewWidthMode { get; }; + static Windows.Foundation.Collections.IMap DefaultInputScope { get; }; static Windows.Foundation.Collections.IMap FirstWindowPreference { get; }; static Windows.Foundation.Collections.IMap LaunchMode { get; }; static Windows.Foundation.Collections.IMap TabSwitcherMode { get; }; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index e7f930c175a..192a3eaaa78 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -269,6 +269,10 @@ Json::Value GlobalAppSettings::ToJson() { _TextMeasurement.reset(); } + if (_DefaultInputScope == Control::DefaultInputScope::Default) + { + _DefaultInputScope.reset(); + } if (_DisablePartialInvalidation == false) { diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 1a1bab60b9b..af2fe8dc3fc 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -73,6 +73,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, TrimPaste); INHERITABLE_SETTING(LaunchPosition, InitialPosition); INHERITABLE_SETTING(Boolean, CenterOnLaunch); + INHERITABLE_SETTING(Microsoft.Terminal.Control.DefaultInputScope, DefaultInputScope); INHERITABLE_SETTING(FirstWindowPreference, FirstWindowPreference); INHERITABLE_SETTING(LaunchMode, LaunchMode); INHERITABLE_SETTING(Boolean, SnapToGridOnResize); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 85e091077a8..c9bfcf08e6d 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -28,6 +28,7 @@ Author(s): X(bool, DisablePartialInvalidation, "rendering.disablePartialInvalidation", false) \ X(bool, SoftwareRendering, "rendering.software", false) \ X(winrt::Microsoft::Terminal::Control::TextMeasurement, TextMeasurement, "compatibility.textMeasurement") \ + X(winrt::Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope, "defaultInputScope") \ X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \ X(bool, ForceVTInput, "experimental.input.forceVT", false) \ X(bool, TrimBlockSelection, "trimBlockSelection", true) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 9e4c0ba9864..b392a9e1832 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -368,6 +368,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _DisablePartialInvalidation = globalSettings.DisablePartialInvalidation(); _SoftwareRendering = globalSettings.SoftwareRendering(); _TextMeasurement = globalSettings.TextMeasurement(); + _DefaultInputScope = globalSettings.DefaultInputScope(); _UseBackgroundImageForWindow = globalSettings.UseBackgroundImageForWindow(); _ForceVTInput = globalSettings.ForceVTInput(); _TrimBlockSelection = globalSettings.TrimBlockSelection(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index dc478ddb838..4af374c3638 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -161,6 +161,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, bool, DisablePartialInvalidation, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, SoftwareRendering, false); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextMeasurement, TextMeasurement); + INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope); INHERITABLE_SETTING(Model::TerminalSettings, bool, UseBackgroundImageForWindow, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, ForceVTInput, false); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 4b22d61cf20..4d2a84a5e6c 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -782,3 +782,11 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::TextMeasurement) pair_type{ "console", ValueType::Console }, }; }; + +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::DefaultInputScope) +{ + JSON_MAPPINGS(2) = { + pair_type{ "default", ValueType::Default }, + pair_type{ "alphanumericHalfWidth", ValueType::AlphanumericHalfWidth }, + }; +}; diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index bc5e3748b4b..c6543a49fec 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -79,6 +79,7 @@ X(bool, DisablePartialInvalidation, false) \ X(bool, SoftwareRendering, false) \ X(winrt::Microsoft::Terminal::Control::TextMeasurement, TextMeasurement) \ + X(winrt::Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope, winrt::Microsoft::Terminal::Control::DefaultInputScope::Default) \ X(bool, UseBackgroundImageForWindow, false) \ X(bool, ShowMarks, false) \ X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ diff --git a/src/tsf/Handle.cpp b/src/tsf/Handle.cpp index ab3f7c5f786..e60e2513072 100644 --- a/src/tsf/Handle.cpp +++ b/src/tsf/Handle.cpp @@ -16,6 +16,11 @@ Handle Handle::Create() return handle; } +void Handle::SetDefaultScopeAlphanumericHalfWidth(bool enable) +{ + Implementation::SetDefaultScopeAlphanumericHalfWidth(enable); +} + Handle::~Handle() { if (_impl) diff --git a/src/tsf/Handle.h b/src/tsf/Handle.h index fc56920ae40..7e2e9125744 100644 --- a/src/tsf/Handle.h +++ b/src/tsf/Handle.h @@ -33,6 +33,7 @@ namespace Microsoft::Console::TSF struct Handle { static Handle Create(); + static void SetDefaultScopeAlphanumericHalfWidth(bool enable); Handle() = default; ~Handle(); diff --git a/src/tsf/Implementation.cpp b/src/tsf/Implementation.cpp index 856d124ec60..234c43df233 100644 --- a/src/tsf/Implementation.cpp +++ b/src/tsf/Implementation.cpp @@ -27,6 +27,11 @@ static void TfPropertyvalClose(TF_PROPERTYVAL* val) } using unique_tf_propertyval = wil::unique_struct; +void Implementation::SetDefaultScopeAlphanumericHalfWidth(bool enable) noexcept +{ + _wantsAnsiInputScope.store(enable, std::memory_order_relaxed); +} + void Implementation::Initialize() { _categoryMgr = wil::CoCreateInstance(CLSID_TF_CategoryMgr, CLSCTX_INPROC_SERVER); @@ -41,6 +46,8 @@ void Implementation::Initialize() TfEditCookie ecTextStore; THROW_IF_FAILED(_documentMgr->CreateContext(_clientId, 0, static_cast(this), _context.addressof(), &ecTextStore)); + _ownerCompositionServices = _context.try_query(); + _contextSource = _context.query(); THROW_IF_FAILED(_contextSource->AdviseSink(IID_ITfContextOwner, static_cast(this), &_cookieContextOwner)); THROW_IF_FAILED(_contextSource->AdviseSink(IID_ITfTextEditSink, static_cast(this), &_cookieTextEditSink)); @@ -155,12 +162,9 @@ void Implementation::Unfocus(IDataProvider* provider) _provider.reset(); - if (_compositions > 0) + if (_compositions > 0 && _ownerCompositionServices) { - if (const auto svc = _context.try_query()) - { - svc->TerminateComposition(nullptr); - } + std::ignore = _ownerCompositionServices->TerminateComposition(nullptr); } } @@ -229,9 +233,7 @@ STDMETHODIMP Implementation::GetACPFromPoint(const POINT* ptScreen, DWORD dwFlag return E_NOTIMPL; } -// This returns rectangle of current command line edit area. -// When a user types in East Asian language, candidate window is shown at this position. -// Emoji and more panel (Win+.) is shown at the position, too. +// The returned rectangle is used to position the TSF candidate window. STDMETHODIMP Implementation::GetTextExt(LONG acpStart, LONG acpEnd, RECT* prc, BOOL* pfClipped) noexcept try { @@ -249,8 +251,7 @@ try } CATCH_RETURN() -// This returns Rectangle of the text box of whole console. -// When a user taps inside the rectangle while hardware keyboard is not available, touch keyboard is invoked. +// The returned rectangle is used to activate the touch keyboard. STDMETHODIMP Implementation::GetScreenExt(RECT* prc) noexcept try { @@ -306,7 +307,16 @@ STDMETHODIMP Implementation::GetWnd(HWND* phwnd) noexcept STDMETHODIMP Implementation::GetAttribute(REFGUID rguidAttribute, VARIANT* pvarValue) noexcept { - return E_NOTIMPL; + if (_wantsAnsiInputScope.load(std::memory_order_relaxed) && IsEqualGUID(rguidAttribute, GUID_PROP_INPUTSCOPE)) + { + _ansiInputScope.AddRef(); + pvarValue->vt = VT_UNKNOWN; + pvarValue->punkVal = &_ansiInputScope; + return S_OK; + } + + pvarValue->vt = VT_EMPTY; + return S_OK; } #pragma endregion ITfContextOwner @@ -403,13 +413,89 @@ STDMETHODIMP Implementation::EditSessionProxyBase::QueryInterface(REFIID riid, v ULONG STDMETHODCALLTYPE Implementation::EditSessionProxyBase::AddRef() noexcept { - return InterlockedIncrement(&referenceCount); + InterlockedIncrement(&referenceCount); + return self->AddRef(); } ULONG STDMETHODCALLTYPE Implementation::EditSessionProxyBase::Release() noexcept { - FAIL_FAST_IF(referenceCount == 0); - return InterlockedDecrement(&referenceCount); + InterlockedDecrement(&referenceCount); + return self->Release(); +} + +Implementation::AnsiInputScope::AnsiInputScope(Implementation* self) noexcept : + self{ self } +{ +} + +HRESULT Implementation::AnsiInputScope::QueryInterface(const IID& riid, void** ppvObj) noexcept +{ + if (!ppvObj) + { + return E_POINTER; + } + + if (IsEqualGUID(riid, IID_ITfInputScope)) + { + *ppvObj = static_cast(this); + } + else if (IsEqualGUID(riid, IID_IUnknown)) + { + *ppvObj = static_cast(this); + } + else + { + *ppvObj = nullptr; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +ULONG Implementation::AnsiInputScope::AddRef() noexcept +{ + return self->AddRef(); +} + +ULONG Implementation::AnsiInputScope::Release() noexcept +{ + return self->Release(); +} + +HRESULT Implementation::AnsiInputScope::GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) noexcept +{ + const auto scopes = static_cast(CoTaskMemAlloc(1 * sizeof(InputScope))); + if (!scopes) + { + return E_OUTOFMEMORY; + } + + scopes[0] = IS_ALPHANUMERIC_HALFWIDTH; + + *pprgInputScopes = scopes; + *pcCount = 1; + return S_OK; +} + +HRESULT Implementation::AnsiInputScope::GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) noexcept +{ + return E_NOTIMPL; +} + +HRESULT Implementation::AnsiInputScope::GetRegularExpression(BSTR* pbstrRegExp) noexcept +{ + return E_NOTIMPL; +} + +HRESULT Implementation::AnsiInputScope::GetSRGS(BSTR* pbstrSRGS) noexcept +{ + return E_NOTIMPL; +} + +HRESULT Implementation::AnsiInputScope::GetXML(BSTR* pbstrXML) noexcept +{ + return E_NOTIMPL; } [[nodiscard]] HRESULT Implementation::_request(EditSessionProxyBase& session, DWORD flags) const diff --git a/src/tsf/Implementation.h b/src/tsf/Implementation.h index 7071141cae5..5bf7251f6a0 100644 --- a/src/tsf/Implementation.h +++ b/src/tsf/Implementation.h @@ -16,6 +16,8 @@ namespace Microsoft::Console::TSF struct Implementation : ITfContextOwner, ITfContextOwnerCompositionSink, ITfTextEditSink { + static void SetDefaultScopeAlphanumericHalfWidth(bool enable) noexcept; + virtual ~Implementation() = default; void Initialize(); @@ -82,6 +84,26 @@ namespace Microsoft::Console::TSF } }; + struct AnsiInputScope : ITfInputScope + { + explicit AnsiInputScope(Implementation* self) noexcept; + virtual ~AnsiInputScope() = default; + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj) noexcept override; + ULONG STDMETHODCALLTYPE AddRef() noexcept override; + ULONG STDMETHODCALLTYPE Release() noexcept override; + + // ITfInputScope methods + STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) noexcept override; + STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) noexcept override; + STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) noexcept override; + STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) noexcept override; + STDMETHODIMP GetXML(BSTR* pbstrXML) noexcept override; + + Implementation* self = nullptr; + }; + [[nodiscard]] HRESULT _request(EditSessionProxyBase& session, DWORD flags) const; void _doCompositionUpdate(TfEditCookie ec); TextAttribute _textAttributeFromAtom(TfGuidAtom atom) const; @@ -97,6 +119,7 @@ namespace Microsoft::Console::TSF wil::com_ptr _threadMgrEx; wil::com_ptr _documentMgr; wil::com_ptr _context; + wil::com_ptr _ownerCompositionServices; wil::com_ptr _contextSource; DWORD _cookieContextOwner = TF_INVALID_COOKIE; DWORD _cookieTextEditSink = TF_INVALID_COOKIE; @@ -104,5 +127,8 @@ namespace Microsoft::Console::TSF EditSessionProxy<&Implementation::_doCompositionUpdate> _editSessionCompositionUpdate{ this }; int _compositions = 0; + + AnsiInputScope _ansiInputScope{ this }; + inline static std::atomic _wantsAnsiInputScope{ false }; }; } diff --git a/src/tsf/precomp.h b/src/tsf/precomp.h index bf7aac22627..201fc956af3 100644 --- a/src/tsf/precomp.h +++ b/src/tsf/precomp.h @@ -1,47 +1,11 @@ -/*++ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. -Copyright (c) Microsoft Corporation. -Licensed under the MIT license. +#pragma once -Module Name: +#define INITGUID - precomp.h - -Abstract: - - This file precompiled header file. - -Author: - -Revision History: - -Notes: - ---*/ - -#define NOMINMAX - -#define _OLEAUT32_ -#include -#include - -extern "C" { -#include - -#include -#include -#include -} - -#include -#include -#include -#include -#include -#include - -#include // Cicero header -#include // ITextStore standard attributes - -// This includes support libraries from the CRT, STL, WIL, and GSL #include "LibraryIncludes.h" + +#include +#include