11using Microsoft . Win32 ;
2+ using System ;
3+ using System . Collections . Generic ;
24using System . Reflection ;
35using System . Windows . Forms ;
46
@@ -7,43 +9,65 @@ namespace KeyLayoutAutoSwitch
79 internal static class ExtensionMethods
810 {
911 private static readonly MethodInfo GetLocalizedKeyboardLayoutNameMethod = typeof ( InputLanguage ) . GetMethod ( "GetLocalizedKeyboardLayoutName" , BindingFlags . NonPublic | BindingFlags . Static ) ;
10- public static string GetLayoutName ( this InputLanguage inputLanguage )
12+
13+ private static readonly Dictionary < uint , string > _layoutKeyNamesByLayoutId = LoadLayoutKeyNames ( ) ;
14+ private static readonly Dictionary < IntPtr , string > _layoutNameCache = new Dictionary < IntPtr , string > ( ) ;
15+
16+ private static Dictionary < uint , string > LoadLayoutKeyNames ( )
1117 {
12- var handle = ( uint ) inputLanguage . Handle ;
13- if ( ( handle & 0xf0000000 ) == 0xf0000000 ) // InputLanguage.LayoutName is bugged when layout ID is provided, so try and do better
14- {
15- var targetLanguage = ( handle & 0xffff ) . ToString ( "X4" ) ;
16- var targetLayoutId = ( ( handle >> 16 ) & 0x0fff ) . ToString ( "X4" ) ;
18+ var layoutKeyNames = new Dictionary < uint , string > ( ) ;
1719
18- var keyboardLayouts = Registry . LocalMachine . OpenSubKey ( @"SYSTEM\CurrentControlSet\Control\Keyboard Layouts" ) ;
20+ using ( var keyboardLayouts = Registry . LocalMachine . OpenSubKey ( @"SYSTEM\CurrentControlSet\Control\Keyboard Layouts" ) )
21+ {
1922 foreach ( var keyboardLayout in keyboardLayouts . GetSubKeyNames ( ) )
2023 {
21- if ( keyboardLayout . Length == 8 )
24+ using ( var keyboardLayoutKey = keyboardLayouts . OpenSubKey ( keyboardLayout ) )
2225 {
23- var layoutLanguage = keyboardLayout . Substring ( 4 ) ;
24- if ( layoutLanguage == targetLanguage )
26+ var layoutId = keyboardLayoutKey . GetValue ( "Layout Id" ) as string ;
27+ if ( layoutId != null )
2528 {
26- // Does this match the layout too?
27- using ( var keyboardLayoutKey = keyboardLayouts . OpenSubKey ( keyboardLayout ) )
28- {
29- var layoutId = keyboardLayoutKey . GetValue ( "Layout Id" ) as string ;
30- if ( layoutId == targetLayoutId )
31- {
32- // Match by language and layout ID,
29+ layoutKeyNames [ Convert . ToUInt32 ( layoutId , 16 ) ] = keyboardLayout ;
30+ }
31+ }
32+ }
33+ }
34+
35+ return layoutKeyNames ;
36+ }
37+
38+ public static string GetLayoutName ( this InputLanguage inputLanguage )
39+ {
40+ if ( ! _layoutNameCache . TryGetValue ( inputLanguage . Handle , out var layoutName ) )
41+ {
42+ // InputLanguage.LayoutName is bugged (https://github.com/dotnet/winforms/issues/4345), try to do better
43+ try
44+ {
45+ var targetLayoutIdOrLanguage = ( uint ) inputLanguage . Handle >> 16 ;
3346
47+ var keyName = ( targetLayoutIdOrLanguage & 0xf000 ) == 0xf000 ?
48+ // This is a layout ID, so get the key name from the lookup
49+ _layoutKeyNamesByLayoutId [ targetLayoutIdOrLanguage & 0x0fff ]
50+ :
51+ // This is a language ID, the layout name is just that.
52+ targetLayoutIdOrLanguage . ToString ( "X8" ) ;
3453
35- // Attempt to extract the localized keyboard layout name, default back to legacy name from registry, final default just use InputLanguage code path
36- return GetLocalizedKeyboardLayoutName ( keyboardLayoutKey . GetValue ( "Layout Display Name" ) as string )
54+ using ( var keyboardLayoutKey = Registry . LocalMachine . OpenSubKey ( @"SYSTEM\CurrentControlSet\Control\Keyboard Layouts\" + keyName ) )
55+ {
56+ // Attempt to extract the localized keyboard layout name, default back to legacy name from registry, final default just use InputLanguage code path
57+ layoutName = GetLocalizedKeyboardLayoutName ( keyboardLayoutKey . GetValue ( "Layout Display Name" ) as string )
3758 ?? keyboardLayoutKey . GetValue ( "Layout Text" ) as string
3859 ?? inputLanguage . LayoutName ;
39- }
40- }
41- }
4260 }
4361 }
62+ catch
63+ {
64+ // Fallback on the buggy WinForms implemenation
65+ layoutName = inputLanguage . LayoutName ;
66+ }
67+ _layoutNameCache . Add ( inputLanguage . Handle , layoutName ) ;
4468 }
4569
46- return inputLanguage . LayoutName ;
70+ return layoutName ;
4771 }
4872
4973 private static string GetLocalizedKeyboardLayoutName ( string layoutDisplayName )
0 commit comments