From d827d0ac9f42f88556476731c700c6ad8806e8fc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 22:22:08 +0800 Subject: [PATCH] Use language tag instead of language id --- .../NativeMethods.txt | 4 + Flow.Launcher.Infrastructure/Win32Helper.cs | 143 +++++++++++++----- 2 files changed, 108 insertions(+), 39 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index de74eb7f9..363ecb9d0 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -55,3 +55,7 @@ GetKeyboardLayoutList PostMessage WM_INPUTLANGCHANGEREQUEST INPUTLANGCHANGE_FORWARD +LOCALE_TRANSIENT_KEYBOARD1 +LOCALE_TRANSIENT_KEYBOARD2 +LOCALE_TRANSIENT_KEYBOARD3 +LOCALE_TRANSIENT_KEYBOARD4 \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 8c32899ec..c84c2e3a8 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -1,16 +1,17 @@ using System; using System.ComponentModel; -using System.Linq; +using System.Globalization; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; using System.Windows.Media; +using Flow.Launcher.Infrastructure.UserSettings; +using Microsoft.Win32; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.WindowsAndMessaging; -using Flow.Launcher.Infrastructure.UserSettings; using Point = System.Windows.Point; namespace Flow.Launcher.Infrastructure @@ -323,16 +324,16 @@ namespace Flow.Launcher.Infrastructure #region Keyboard Layout - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f - private static readonly uint[] EnglishLanguageIds = - { - 0x0009, 0x0409, 0x0809, 0x0C09, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, 0x3009, - 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, - }; + private const string UserProfileRegistryPath = @"Control Panel\International\User Profile"; - private static readonly uint[] ImeLanguageIds = + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f + private const string EnglishLanguageTag = "en"; + + private static readonly string[] ImeLanguageTags = { - 0x0004, 0x7804, 0x0804, 0x1004, 0x7C04, 0x0C04, 0x1404, 0x0404, 0x0011, 0x0411, 0x0012, 0x0412, + "zh", // Chinese + "ja", // Japanese + "ko", // Korean }; private const uint KeyboardLayoutLoWord = 0xFFFF; @@ -340,33 +341,6 @@ namespace Flow.Launcher.Infrastructure // Store the previous keyboard layout private static HKL _previousLayout; - private static unsafe HKL FindEnglishKeyboardLayout() - { - // Get the number of keyboard layouts - int count = PInvoke.GetKeyboardLayoutList(0, null); - if (count <= 0) return HKL.Null; - - // Get all keyboard layouts - var handles = new HKL[count]; - fixed (HKL* h = handles) - { - var result = PInvoke.GetKeyboardLayoutList(count, h); - if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - // Look for any English keyboard layout - foreach (var hkl in handles) - { - // The lower word contains the language identifier - var langId = (uint)hkl.Value & KeyboardLayoutLoWord; - - // Check if it's an English layout - if (EnglishLanguageIds.Contains(langId)) return hkl; - } - - return HKL.Null; - } - /// /// Switches the keyboard layout to English if available. /// @@ -392,8 +366,14 @@ namespace Flow.Launcher.Infrastructure // This is needed because for languages with IME mode, Flow Launcher just temporarily disables // the IME mode instead of switching to another layout. var currentLayout = PInvoke.GetKeyboardLayout(threadId); - var currentLayoutCode = (uint)currentLayout.Value & KeyboardLayoutLoWord; - if (ImeLanguageIds.Contains(currentLayoutCode)) return; + var currentLangId = (uint)currentLayout.Value & KeyboardLayoutLoWord; + foreach (var langTag in ImeLanguageTags) + { + if (GetLanguageTag(currentLangId).StartsWith(langTag, StringComparison.OrdinalIgnoreCase)) + { + return; + } + } // Backup current keyboard layout if (backupPrevious) _previousLayout = currentLayout; @@ -423,6 +403,91 @@ namespace Flow.Launcher.Infrastructure _previousLayout = HKL.Null; } + /// + /// Finds an installed English keyboard layout. + /// + /// + /// + private static unsafe HKL FindEnglishKeyboardLayout() + { + // Get the number of keyboard layouts + int count = PInvoke.GetKeyboardLayoutList(0, null); + if (count <= 0) return HKL.Null; + + // Get all keyboard layouts + var handles = new HKL[count]; + fixed (HKL* h = handles) + { + var result = PInvoke.GetKeyboardLayoutList(count, h); + if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + // Look for any English keyboard layout + foreach (var hkl in handles) + { + // The lower word contains the language identifier + var langId = (uint)hkl.Value & KeyboardLayoutLoWord; + var langTag = GetLanguageTag(langId); + + // Check if it's an English layout + if (langTag.StartsWith(EnglishLanguageTag, StringComparison.OrdinalIgnoreCase)) + { + return hkl; + } + } + + return HKL.Null; + } + + /// + /// Returns the + /// + /// BCP 47 language tag + /// + /// of the current input language. + /// + /// + /// Edited from: https://github.com/dotnet/winforms + /// + private static string GetLanguageTag(uint langId) + { + // We need to convert the language identifier to a language tag, because they are deprecated and may have a + // transient value. + // https://learn.microsoft.com/globalization/locale/other-locale-names#lcid + // https://learn.microsoft.com/windows/win32/winmsg/wm-inputlangchange#remarks + // + // It turns out that the LCIDToLocaleName API, which is used inside CultureInfo, may return incorrect + // language tags for transient language identifiers. For example, it returns "nqo-GN" and "jv-Java-ID" + // instead of the "nqo" and "jv-Java" (as seen in the Get-WinUserLanguageList PowerShell cmdlet). + // + // Try to extract proper language tag from registry as a workaround approved by a Windows team. + // https://github.com/dotnet/winforms/pull/8573#issuecomment-1542600949 + // + // NOTE: this logic may break in future versions of Windows since it is not documented. + if (langId is (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD1 + or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD2 + or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD3 + or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD4) + { + using RegistryKey key = Registry.CurrentUser.OpenSubKey(UserProfileRegistryPath); + if (key is not null && key.GetValue("Languages") is string[] languages) + { + foreach (string language in languages) + { + using RegistryKey subKey = key.OpenSubKey(language); + if (subKey is not null + && subKey.GetValue("TransientLangId") is int transientLangId + && transientLangId == langId) + { + return language; + } + } + } + } + + return CultureInfo.GetCultureInfo((int)langId).Name; + } + #endregion } }