1
1
using System ;
2
2
using System . ComponentModel ;
3
- using System . Linq ;
3
+ using System . Globalization ;
4
4
using System . Runtime . InteropServices ;
5
5
using System . Windows ;
6
6
using System . Windows . Interop ;
7
7
using System . Windows . Media ;
8
+ using Flow . Launcher . Infrastructure . UserSettings ;
9
+ using Microsoft . Win32 ;
8
10
using Windows . Win32 ;
9
11
using Windows . Win32 . Foundation ;
10
12
using Windows . Win32 . Graphics . Dwm ;
11
13
using Windows . Win32 . UI . Input . KeyboardAndMouse ;
12
14
using Windows . Win32 . UI . WindowsAndMessaging ;
13
- using Flow . Launcher . Infrastructure . UserSettings ;
14
15
using Point = System . Windows . Point ;
15
16
16
17
namespace Flow . Launcher . Infrastructure
@@ -323,50 +324,23 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false)
323
324
324
325
#region Keyboard Layout
325
326
327
+ private const string UserProfileRegistryPath = @"Control Panel\International\User Profile" ;
328
+
326
329
// 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" ;
332
331
333
- private static readonly uint [ ] ImeLanguageIds =
332
+ private static readonly string [ ] ImeLanguageTags =
334
333
{
335
- 0x0004 , 0x7804 , 0x0804 , 0x1004 , 0x7C04 , 0x0C04 , 0x1404 , 0x0404 , 0x0011 , 0x0411 , 0x0012 , 0x0412 ,
334
+ "zh" , // Chinese
335
+ "ja" , // Japanese
336
+ "ko" , // Korean
336
337
} ;
337
338
338
339
private const uint KeyboardLayoutLoWord = 0xFFFF ;
339
340
340
341
// Store the previous keyboard layout
341
342
private static HKL _previousLayout ;
342
343
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
-
370
344
/// <summary>
371
345
/// Switches the keyboard layout to English if available.
372
346
/// </summary>
@@ -392,8 +366,14 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious)
392
366
// This is needed because for languages with IME mode, Flow Launcher just temporarily disables
393
367
// the IME mode instead of switching to another layout.
394
368
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
+ }
397
377
398
378
// Backup current keyboard layout
399
379
if ( backupPrevious ) _previousLayout = currentLayout ;
@@ -423,6 +403,91 @@ public static void RestorePreviousKeyboardLayout()
423
403
_previousLayout = HKL . Null ;
424
404
}
425
405
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
+
426
491
#endregion
427
492
}
428
493
}
0 commit comments