Repository: microsoft/terminal (language: cpp)

Issue:
Has the automatic switching input method function been canceled?
The automatic switching input method function before version 1.21.2361.0 is very useful! But after the update, this feature became invalid, and I couldn't find any relevant options in the settings. May I ask if this function can be restored? This is of great convenience for those who do not speak English as their mother tongue!



Discussion / hints:
Wait, just to be sure - you actually liked that it switched back to English by default? 

We had pretty much universally negative feedback on that, or at least the way it was implemented. 

In 1.21 we did entirely rewrite the IME code, so we probably can't just bring it back like it was before. But I can imagine we could add an opt-in setting that would bring that back...

---

older context:
* #12731
* #13028
* #13398
* #13681
* #14407
* #17067
Yes, my friend. When the focus is on the WindowsTerminal window, automatically switch the input method to English; At the same time, when the focus leaves the WindowsTerminal window, switch back to the original input method. I think this feature is quite practical!!!
My native language is Chinese, but when executing commands on a WindowsTerminal, I use English.
Previously, I could search for Chinese content in a browser and execute commands in a WindowsTerminal without having to switch input method languages back and forth. But now when I first open the WindowsTerminal, I have to manually change the input method to English first🤣
Edit: Oh, I haven't reloaded the website while writing this comment. I wrote it before either of you two had commented.

We've received a lot of complaints from our users about the automatic input method switching. It was never an intentional feature either, so we fixed this supposed issue.

But the new version ensures that the input method doesn't change when you switch between applications. When you launch Windows Terminal, it'll be in the composition mode (中), press Shift and it'll switch to English input (英). Then when you switch to another application and come back to Windows Terminal it should restore English input (英). Does this not happen for you?
My previous description may not have been clear enough, but I would like to have this feature:
When opening a new Windows terminal window, always ensure that the default input method at this time is the English input method.
This feature seemed to exist in previous versions (as there was no need to manually switch input methods at the time), but after updating to version 1.21.2361.0, when opening a new Windows terminal window, it uses the Chinese input method.


* The following picture shows that I am currently using an English input method
![image](https://github.com/user-attachments/assets/d2d31be5-1ce0-43e1-9e8d-66412d831c3b)


* When I open a new WindowsTerminal window, I didn't manually switch input methods, but now the input method has changed to Chinese input method (although Chinese is my preferred language on my computer)
![image](https://github.com/user-attachments/assets/89c29c6f-3852-466a-8593-721ac4f84dc6)


**My intention is to automatically switch the default input method language to English input method if the focus is on the Windows Terminal window, without the need for manual switching, just like before version 1.21.2361.0**❤️
Would your issue also be solved if we ensured the input method doesn't change when you create a new window or tab?

Generally speaking, I don't mind adding a setting that changes the method to English on start.
Yes, it would be great if a setting could be added that allows users to decide which language input method to use when opening a WindowsTerminal window!!!
I encountered the same problem. After upgrading, I had to switch the input method every time I opened the terminal.

I don't think it's necessary to add a setting option. Like the previous version, the default English should be a better choice. Because most of the input in the terminal is English commands, few people should use commands in other languages. When you need to input other languages, just use Shift to switch.
> I don't think it's necessary to add a setting option. Like the previous version, the default English should be a better choice.

As mentioned above, *a lot* of people complained about the switching, so we won't do that anymore. The IME now uses whatever language Windows picks for us.

@Delphier By the way, if you don't mind, can you explain why this change is annoying for you? I know that you can switch to English input by pressing Shift, right? So, is this more about convenience for you, or are there some bugs in Windows Terminal where it switches between Chinese and English input unexpectedly?
@lhecker 

Since Windows 11 (maybe Windows 10, I'm not sure), the problem of input method switching has been bothering me:
My default input method is Chinese, but some programs are in English input state by default after opening, such as Chrome, Edge, VSCode, and the old version of Windows Terminal, but some programs are in Chinese input state after opening, such as Sublime Text, Notepad and many other programs.

I don't know much about input method development, so I'm not sure where the problem lies.

I just checked the Windows input method settings again and found that the input method I use can set the default mode (Chinese or English). It was always Chinese before. After I switched to English, I found that the new version of Windows Terminal will be in English input state by default.

![image](https://github.com/user-attachments/assets/a6809142-192e-41cc-86df-76c5d79392e3)

So my previous suggestion should be inaccurate. The current Windows Terminal input method processing should be correct: keep it consistent with the Windows input method settings.
Considering the widespread use of WindowsTerminal, I believe adding a setting for users to decide which language input method to use when opening WindowsTerminal is the most widely accepted choice. Combined with the feature in Windows that allows different input methods to be used in different windows, it should be possible to completely avoid manually switching input methods.


Ensure that the default input method when typing in WindowsTerminal is English (unless the user switches to it after opening the terminal), and that windows outside the terminal are determined by the Windows system and the user.

Fix patch:
diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index 6a768edbcd5..3a6ca5cf14e 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
@@ -1290,6 +1291,7 @@ parms
 PATCOPY
 pathcch
 PATTERNID
+pbstr
 pcb
 pcch
 PCCHAR
@@ -1375,9 +1377,11 @@ POSTCHARBREAKS
 POSX
 POSXSCROLL
 POSYSCROLL
+ppbstr
 PPEB
 ppf
 ppidl
+pprg
 PPROC
 ppropvar
 ppsi
@@ -1693,6 +1697,7 @@ srcsrv
 SRCSRVTRG
 srctool
 srect
+SRGS
 srvinit
 srvpipe
 ssa
@@ -1839,7 +1844,6 @@ triaging
 TRIMZEROHEADINGS
 trx
 tsa
-tsattrs
 tsgr
 tsm
 TStr
@@ -1967,10 +1971,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 de859743b69..0cdaee97659 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 565ea02ccf6..aeb13df6505 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -863,6 +863,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 @@
     </Page.Resources>
 
     <StackPanel Style="{StaticResource SettingsStackStyle}">
-        <!--  Language  -->
-        <local:SettingContainer x:Uid="Globals_Language"
-                                Visibility="{x:Bind ViewModel.LanguageSelectorAvailable}">
-            <ComboBox ItemsSource="{x:Bind ViewModel.LanguageList}"
-                      SelectedItem="{x:Bind ViewModel.CurrentLanguage, Mode=TwoWay}"
-                      Style="{StaticResource ComboBoxSettingStyle}">
-                <ComboBox.ItemTemplate>
-                    <DataTemplate x:DataType="x:String">
-                        <TextBlock Text="{x:Bind local:GlobalAppearanceViewModel.LanguageDisplayConverter((x:String))}" />
-                    </DataTemplate>
-                </ComboBox.ItemTemplate>
-            </ComboBox>
-        </local:SettingContainer>
-
         <!--  Theme  -->
         <local:SettingContainer x:Uid="Globals_Theme">
             <ComboBox AutomationProperties.AccessibilityView="Content"
diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.cpp b/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.cpp
index 27c5abbec8a..b5e580666f1 100644
--- a/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.cpp
+++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearanceViewModel.cpp
@@ -7,7 +7,6 @@
 #include "EnumEntry.h"
 
 #include <LibraryResources.h>
-#include <WtExeUtils.h>
 
 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<winrt::hstring> 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<winrt::hstring> 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<winrt::hstring>(_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<winrt::hstring> 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<winrt::hstring> _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<String> LanguageList { get; };
-        IInspectable CurrentLanguage;
-
         IInspectable CurrentTheme;
         static String ThemeNameConverter(Microsoft.Terminal.Settings.Model.Theme theme);
         Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Model.Theme> 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 @@
                 </ComboBox>
             </local:SettingContainer>
 
+            <!--  Language  -->
+            <local:SettingContainer x:Uid="Globals_Language"
+                                    Visibility="{x:Bind ViewModel.LanguageSelectorAvailable}">
+                <ComboBox ItemsSource="{x:Bind ViewModel.LanguageList}"
+                          SelectedItem="{x:Bind ViewModel.CurrentLanguage, Mode=TwoWay}"
+                          Style="{StaticResource ComboBoxSettingStyle}">
+                    <ComboBox.ItemTemplate>
+                        <DataTemplate x:DataType="x:String">
+                            <TextBlock Text="{x:Bind local:LaunchViewModel.LanguageDisplayConverter((x:String))}" />
+                        </DataTemplate>
+                    </ComboBox.ItemTemplate>
+                </ComboBox>
+            </local:SettingContainer>
+
+            <!--  Language  -->
+            <local:SettingContainer x:Uid="Globals_DefaultInputScope">
+                <ComboBox AutomationProperties.AccessibilityView="Content"
+                          ItemTemplate="{StaticResource EnumComboBoxTemplate}"
+                          ItemsSource="{x:Bind ViewModel.DefaultInputScopeList}"
+                          SelectedItem="{x:Bind ViewModel.CurrentDefaultInputScope, Mode=TwoWay}"
+                          Style="{StaticResource ComboBoxSettingStyle}" />
+            </local:SettingContainer>
+
             <!--  Start on User Login  -->
             <local:SettingContainer x:Uid="Globals_StartOnUserLogin">
                 <ToggleSwitch IsOn="{x:Bind ViewModel.StartOnUserLogin, Mode=TwoWay}"
diff --git a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp
index dcaaff6e3e4..29364e0e61f 100644
--- a/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp
+++ b/src/cascadia/TerminalSettingsEditor/LaunchViewModel.cpp
@@ -5,7 +5,9 @@
 #include "LaunchViewModel.h"
 #include "LaunchViewModel.g.cpp"
 #include "EnumEntry.h"
+
 #include <LibraryResources.h>
+#include <WtExeUtils.h>
 
 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<winrt::hstring> 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<winrt::hstring> 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<winrt::hstring>(_currentLanguage);
+        if (currentLanguage == systemLanguageTag)
+        {
+            _Settings.GlobalSettings().ClearLanguage();
+        }
+        else
+        {
+            _Settings.GlobalSettings().Language(currentLanguage);
+        }
+    }
+
     winrt::hstring LaunchViewModel::LaunchParametersCurrentValue()
     {
         const auto launchModeString = CurrentLaunchMode().as<EnumEntry>()->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<winrt::hstring> 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<winrt::Microsoft::Terminal::Settings::Editor::EnumEntry> 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<winrt::hstring> _languageList;
+        winrt::Windows::Foundation::IInspectable _currentLanguage;
         bool _useDefaultLaunchPosition;
 
         winrt::Windows::Foundation::Collections::IObservableVector<winrt::Microsoft::Terminal::Settings::Editor::EnumEntry> _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<String> LanguageList { get; };
+        IInspectable CurrentLanguage;
+
+        IInspectable CurrentDefaultInputScope;
+        IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> 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 @@
     <comment>A description explaining how this control changes the app's language. {Locked="Windows"}</comment>
   </data>
   <data name="Globals_LanguageDefault" xml:space="preserve">
-    <value>Use system default</value>
+    <value>Default</value>
     <comment>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.</comment>
   </data>
+  <data name="Globals_DefaultInputScope.Header" xml:space="preserve">
+    <value>Default IME input mode</value>
+    <comment>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.</comment>
+  </data>
+  <data name="Globals_DefaultInputScope.HelpText" xml:space="preserve">
+    <value>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.</value>
+  </data>
+  <data name="Globals_DefaultInputScopeDefault.Content" xml:space="preserve">
+    <value>Default</value>
+  </data>
+  <data name="Globals_DefaultInputScopeAlphanumericHalfWidth.Content" xml:space="preserve">
+    <value>Alphanumeric Half-Width (English)</value>
+  </data>
   <data name="Globals_AlwaysShowTabs.Header" xml:space="preserve">
     <value>Always show tabs</value>
     <comment>Header for a control to toggle if the app should always show the tabs (similar to a website browser).</comment>
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<winrt::hstring, winrt::Windows::UI::Xaml::ElementTheme> ElementTheme();
         static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, NewTabPosition> NewTabPosition();
         static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode> TabViewWidthMode();
+        static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::DefaultInputScope> DefaultInputScope();
         static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, FirstWindowPreference> FirstWindowPreference();
         static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, LaunchMode> LaunchMode();
         static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, TabSwitcherMode> 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<String, Windows.UI.Xaml.ElementTheme> ElementTheme { get; };
         static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.NewTabPosition> NewTabPosition { get; };
         static Windows.Foundation.Collections.IMap<String, Microsoft.UI.Xaml.Controls.TabViewWidthMode> TabViewWidthMode { get; };
+        static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.DefaultInputScope> DefaultInputScope { get; };
         static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.FirstWindowPreference> FirstWindowPreference { get; };
         static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.LaunchMode> LaunchMode { get; };
         static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.TabSwitcherMode> 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<TF_PROPERTYVAL, decltype(&TfPropertyvalClose), &TfPropertyvalClose>;
 
+void Implementation::SetDefaultScopeAlphanumericHalfWidth(bool enable) noexcept
+{
+    _wantsAnsiInputScope.store(enable, std::memory_order_relaxed);
+}
+
 void Implementation::Initialize()
 {
     _categoryMgr = wil::CoCreateInstance<ITfCategoryMgr>(CLSID_TF_CategoryMgr, CLSCTX_INPROC_SERVER);
@@ -41,6 +46,8 @@ void Implementation::Initialize()
     TfEditCookie ecTextStore;
     THROW_IF_FAILED(_documentMgr->CreateContext(_clientId, 0, static_cast<ITfContextOwnerCompositionSink*>(this), _context.addressof(), &ecTextStore));
 
+    _ownerCompositionServices = _context.try_query<ITfContextOwnerCompositionServices>();
+
     _contextSource = _context.query<ITfSource>();
     THROW_IF_FAILED(_contextSource->AdviseSink(IID_ITfContextOwner, static_cast<ITfContextOwner*>(this), &_cookieContextOwner));
     THROW_IF_FAILED(_contextSource->AdviseSink(IID_ITfTextEditSink, static_cast<ITfTextEditSink*>(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<ITfContextOwnerCompositionServices>())
-        {
-            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<ITfInputScope*>(this);
+    }
+    else if (IsEqualGUID(riid, IID_IUnknown))
+    {
+        *ppvObj = static_cast<IUnknown*>(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<InputScope*>(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<ITfThreadMgrEx> _threadMgrEx;
         wil::com_ptr<ITfDocumentMgr> _documentMgr;
         wil::com_ptr<ITfContext> _context;
+        wil::com_ptr<ITfContextOwnerCompositionServices> _ownerCompositionServices;
         wil::com_ptr<ITfSource> _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<bool> _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 <ole2.h>
-#include <windows.h>
-
-extern "C" {
-#include <winuser.h>
-
-#include <ime.h>
-#include <intsafe.h>
-#include <strsafe.h>
-}
-
-#include <climits>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <string_view>
-#include <span>
-
-#include <msctf.h> // Cicero header
-#include <tsattrs.h> // ITextStore standard attributes
-
-// This includes support libraries from the CRT, STL, WIL, and GSL
 #include "LibraryIncludes.h"
+
+#include <msctf.h>
+#include <InputScope.h>


Test patch: