Skip to content

Commit d827d0a

Browse files
committed
Use language tag instead of language id
1 parent 4fc7f70 commit d827d0a

File tree

2 files changed

+107
-38
lines changed

2 files changed

+107
-38
lines changed

Flow.Launcher.Infrastructure/NativeMethods.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ GetKeyboardLayoutList
5555
PostMessage
5656
WM_INPUTLANGCHANGEREQUEST
5757
INPUTLANGCHANGE_FORWARD
58+
LOCALE_TRANSIENT_KEYBOARD1
59+
LOCALE_TRANSIENT_KEYBOARD2
60+
LOCALE_TRANSIENT_KEYBOARD3
61+
LOCALE_TRANSIENT_KEYBOARD4

Flow.Launcher.Infrastructure/Win32Helper.cs

Lines changed: 103 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
using System;
22
using System.ComponentModel;
3-
using System.Linq;
3+
using System.Globalization;
44
using System.Runtime.InteropServices;
55
using System.Windows;
66
using System.Windows.Interop;
77
using System.Windows.Media;
8+
using Flow.Launcher.Infrastructure.UserSettings;
9+
using Microsoft.Win32;
810
using Windows.Win32;
911
using Windows.Win32.Foundation;
1012
using Windows.Win32.Graphics.Dwm;
1113
using Windows.Win32.UI.Input.KeyboardAndMouse;
1214
using Windows.Win32.UI.WindowsAndMessaging;
13-
using Flow.Launcher.Infrastructure.UserSettings;
1415
using Point = System.Windows.Point;
1516

1617
namespace Flow.Launcher.Infrastructure
@@ -323,50 +324,23 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false)
323324

324325
#region Keyboard Layout
325326

327+
private const string UserProfileRegistryPath = @"Control Panel\International\User Profile";
328+
326329
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f
327-
private static readonly uint[] EnglishLanguageIds =
328-
{
329-
0x0009, 0x0409, 0x0809, 0x0C09, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, 0x3009,
330-
0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09,
331-
};
330+
private const string EnglishLanguageTag = "en";
332331

333-
private static readonly uint[] ImeLanguageIds =
332+
private static readonly string[] ImeLanguageTags =
334333
{
335-
0x0004, 0x7804, 0x0804, 0x1004, 0x7C04, 0x0C04, 0x1404, 0x0404, 0x0011, 0x0411, 0x0012, 0x0412,
334+
"zh", // Chinese
335+
"ja", // Japanese
336+
"ko", // Korean
336337
};
337338

338339
private const uint KeyboardLayoutLoWord = 0xFFFF;
339340

340341
// Store the previous keyboard layout
341342
private static HKL _previousLayout;
342343

343-
private static unsafe HKL FindEnglishKeyboardLayout()
344-
{
345-
// Get the number of keyboard layouts
346-
int count = PInvoke.GetKeyboardLayoutList(0, null);
347-
if (count <= 0) return HKL.Null;
348-
349-
// Get all keyboard layouts
350-
var handles = new HKL[count];
351-
fixed (HKL* h = handles)
352-
{
353-
var result = PInvoke.GetKeyboardLayoutList(count, h);
354-
if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
355-
}
356-
357-
// Look for any English keyboard layout
358-
foreach (var hkl in handles)
359-
{
360-
// The lower word contains the language identifier
361-
var langId = (uint)hkl.Value & KeyboardLayoutLoWord;
362-
363-
// Check if it's an English layout
364-
if (EnglishLanguageIds.Contains(langId)) return hkl;
365-
}
366-
367-
return HKL.Null;
368-
}
369-
370344
/// <summary>
371345
/// Switches the keyboard layout to English if available.
372346
/// </summary>
@@ -392,8 +366,14 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious)
392366
// This is needed because for languages with IME mode, Flow Launcher just temporarily disables
393367
// the IME mode instead of switching to another layout.
394368
var currentLayout = PInvoke.GetKeyboardLayout(threadId);
395-
var currentLayoutCode = (uint)currentLayout.Value & KeyboardLayoutLoWord;
396-
if (ImeLanguageIds.Contains(currentLayoutCode)) return;
369+
var currentLangId = (uint)currentLayout.Value & KeyboardLayoutLoWord;
370+
foreach (var langTag in ImeLanguageTags)
371+
{
372+
if (GetLanguageTag(currentLangId).StartsWith(langTag, StringComparison.OrdinalIgnoreCase))
373+
{
374+
return;
375+
}
376+
}
397377

398378
// Backup current keyboard layout
399379
if (backupPrevious) _previousLayout = currentLayout;
@@ -423,6 +403,91 @@ public static void RestorePreviousKeyboardLayout()
423403
_previousLayout = HKL.Null;
424404
}
425405

406+
/// <summary>
407+
/// Finds an installed English keyboard layout.
408+
/// </summary>
409+
/// <returns></returns>
410+
/// <exception cref="Win32Exception"></exception>
411+
private static unsafe HKL FindEnglishKeyboardLayout()
412+
{
413+
// Get the number of keyboard layouts
414+
int count = PInvoke.GetKeyboardLayoutList(0, null);
415+
if (count <= 0) return HKL.Null;
416+
417+
// Get all keyboard layouts
418+
var handles = new HKL[count];
419+
fixed (HKL* h = handles)
420+
{
421+
var result = PInvoke.GetKeyboardLayoutList(count, h);
422+
if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
423+
}
424+
425+
// Look for any English keyboard layout
426+
foreach (var hkl in handles)
427+
{
428+
// The lower word contains the language identifier
429+
var langId = (uint)hkl.Value & KeyboardLayoutLoWord;
430+
var langTag = GetLanguageTag(langId);
431+
432+
// Check if it's an English layout
433+
if (langTag.StartsWith(EnglishLanguageTag, StringComparison.OrdinalIgnoreCase))
434+
{
435+
return hkl;
436+
}
437+
}
438+
439+
return HKL.Null;
440+
}
441+
442+
/// <summary>
443+
/// Returns the
444+
/// <see href="https://learn.microsoft.com/globalization/locale/standard-locale-names">
445+
/// BCP 47 language tag
446+
/// </see>
447+
/// of the current input language.
448+
/// </summary>
449+
/// <remarks>
450+
/// Edited from: https://github.com/dotnet/winforms
451+
/// </remarks>
452+
private static string GetLanguageTag(uint langId)
453+
{
454+
// We need to convert the language identifier to a language tag, because they are deprecated and may have a
455+
// transient value.
456+
// https://learn.microsoft.com/globalization/locale/other-locale-names#lcid
457+
// https://learn.microsoft.com/windows/win32/winmsg/wm-inputlangchange#remarks
458+
//
459+
// It turns out that the LCIDToLocaleName API, which is used inside CultureInfo, may return incorrect
460+
// language tags for transient language identifiers. For example, it returns "nqo-GN" and "jv-Java-ID"
461+
// instead of the "nqo" and "jv-Java" (as seen in the Get-WinUserLanguageList PowerShell cmdlet).
462+
//
463+
// Try to extract proper language tag from registry as a workaround approved by a Windows team.
464+
// https://github.com/dotnet/winforms/pull/8573#issuecomment-1542600949
465+
//
466+
// NOTE: this logic may break in future versions of Windows since it is not documented.
467+
if (langId is (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD1
468+
or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD2
469+
or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD3
470+
or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD4)
471+
{
472+
using RegistryKey key = Registry.CurrentUser.OpenSubKey(UserProfileRegistryPath);
473+
if (key is not null && key.GetValue("Languages") is string[] languages)
474+
{
475+
foreach (string language in languages)
476+
{
477+
using RegistryKey subKey = key.OpenSubKey(language);
478+
if (subKey is not null
479+
&& subKey.GetValue("TransientLangId") is int transientLangId
480+
&& transientLangId == langId)
481+
{
482+
return language;
483+
}
484+
}
485+
}
486+
}
487+
488+
return CultureInfo.GetCultureInfo((int)langId).Name;
489+
}
490+
426491
#endregion
427492
}
428493
}

0 commit comments

Comments
 (0)