Reputation: 15797
I need to display a list of input languages to my users the way the language bar displays them.
For example:
Currently I have
class Program
{
static void Main(string[] args)
{
var langs = InputLanguage.InstalledInputLanguages;
foreach (InputLanguage lang in langs)
{
Console.WriteLine(lang.LayoutName);
}
}
}
This prints the following
US
Bulgarian
Bulgarian
As you can see we can't tell the difference between the two "Bulgarians" which one is BGPT and which one is BG only.
Culture
is also the same for both.
The only difference is the lang.Handle
which is of type IntPtr
.
I suppose I have to P/Invoke some win32 API using the handle to get to that display name.
Any ideas of what it might be?
EDIT:
Executing Get-WinUserLanguageList
in powershell displays the following
LanguageTag : en-US
Autonym : English (United States)
EnglishName : English
LocalizedName : English (United States)
ScriptName : Latin
InputMethodTips : {0409:00000409}
Spellchecking : True
Handwriting : False
LanguageTag : bg
Autonym : български
EnglishName : Bulgarian
LocalizedName : Bulgarian
ScriptName : Cyrillic
InputMethodTips : {0402:00040402, 0402:00030402}
Spellchecking : True
Handwriting : False
Input method tips is the key here.
0402:00040402 is BG and 0402:00030402 is BGPT
Upvotes: 2
Views: 1123
Reputation: 1421
This is bug in InputLanguage.LayoutName
implementation that was already fixed: https://github.com/dotnet/winforms/pull/8439
You can get the idea from fixed code:
private static string KeyboardLayoutsRegistryPath => @"SYSTEM\CurrentControlSet\Control\Keyboard Layouts";
public string LayoutName
{
get
{
// There is no good way to do this in Windows. GetKeyboardLayoutName does what we want, but only for the
// current input language; setting and resetting the current input language would generate spurious
// InputLanguageChanged events.
// Try to extract needed information manually.
string layoutName = GetKeyboardLayoutNameForHKL(_handle);
// https://learn.microsoft.com/windows/win32/intl/using-registry-string-redirection#create-resources-for-keyboard-layout-strings
using RegistryKey? key = Registry.LocalMachine.OpenSubKey($@"{KeyboardLayoutsRegistryPath}\{layoutName}");
if (key is not null)
{
// Localizable string resource associated with the keyboard layout
if (key.GetValue("Layout Display Name") is string layoutDisplayName &&
SHLoadIndirectString(ref layoutDisplayName))
{
return layoutDisplayName;
}
// Fallback to human-readable name for backward compatibility
if (key.GetValue("Layout Text") is string layoutText)
{
return layoutText;
}
}
return SR.UnknownInputLanguageLayout;
}
}
internal static string GetKeyboardLayoutNameForHKL(IntPtr hkl)
{
// According to the GetKeyboardLayout API function docs low word of HKL contains input language.
int language = PARAM.LOWORD(hkl);
// High word of HKL contains a device handle to the physical layout of the keyboard but exact format of this
// handle is not documented. For older keyboard layouts device handle seems contains keyboard layout
// language which we can use as KLID.
int device = PARAM.HIWORD(hkl);
// But for newer keyboard layouts device handle contains layout id if its high nibble is 0xF. This id may be
// used to search for keyboard layout under registry.
// NOTE: this logic may break in future versions of Windows since it is not documented.
if ((device & 0xF000) == 0xF000)
{
// Extract layout id from the device handle
int layoutId = device & 0x0FFF;
using RegistryKey? key = Registry.LocalMachine.OpenSubKey(KeyboardLayoutsRegistryPath);
if (key is not null)
{
// Match keyboard layout by layout id
foreach (string subKeyName in key.GetSubKeyNames())
{
using RegistryKey? subKey = key.OpenSubKey(subKeyName);
if (subKey is null)
{
continue;
}
if (subKey.GetValue("Layout Id") is not string subKeyLayoutId)
{
continue;
}
if (layoutId == Convert.ToInt32(subKeyLayoutId, 16))
{
Debug.Assert(subKeyName.Length == 8, $"unexpected key length in registry: {subKey.Name}");
return subKeyName;
}
}
}
}
else
{
// Keyboard layout language overrides input language, if available. This is crucial in cases when
// keyboard is installed more than once or under different languages. For example when French keyboard
// is installed under US input language we need to return French keyboard name.
if (device != 0)
{
language = device;
}
}
return language.ToString("x8");
}
After you have to combine it into one string $"{InputLanguage.Culture.DisplayName} - {InputLanguage.LayoutName} keyboard"
Upvotes: 2
Reputation: 648
If PowerShell has what you are looking for you could always just get it from there. Add reference to C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll and C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.InternationalSettings.Commands\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.InternationalSettings.Commands.dll
class Program
{
static void Main(string[] args)
{
System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create();
List<Microsoft.InternationalSettings.Commands.WinUserLanguage> userLangList = ps.AddCommand("Get-WinUserLanguageList").Invoke()[0].BaseObject as List<Microsoft.InternationalSettings.Commands.WinUserLanguage>;
foreach (Microsoft.InternationalSettings.Commands.WinUserLanguage userLang in userLangList)
{
Console.WriteLine("{0,-31}{1,-47}", "Antonym", userLang.Autonym);
Console.WriteLine("{0,-31}{1,-47}", "EnglishName", userLang.EnglishName);
Console.WriteLine("{0,-31}{1,-47}", "Handwriting", userLang.Handwriting);
Console.WriteLine("{0,-31}{1,-47}", "InputMethodTips", String.Join(",", userLang.InputMethodTips));
Console.WriteLine("{0,-31}{1,-47}", "LanguageTag", userLang.LanguageTag);
Console.WriteLine("{0,-31}{1,-47}", "LocalizedName", userLang.LocalizedName);
Console.WriteLine("{0,-31}{1,-47}", "ScriptName", userLang.ScriptName);
Console.WriteLine("{0,-31}{1,-47}", "Spellchecking", userLang.Spellchecking);
Console.WriteLine();
}
}
}
Alternately, Microsoft gets much of this information from the registry, you could do the same:
class Program
{
static void Main(string[] args)
{
(new System.Security.Permissions.RegistryPermission(System.Security.Permissions.PermissionState.Unrestricted)).Assert();
Microsoft.Win32.RegistryKey rkLanguages = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Control Panel\\International\\User Profile");
foreach (string str in rkLanguages.GetSubKeyNames())
{
Console.WriteLine(str);
Microsoft.Win32.RegistryKey rkLang = rkLanguages.OpenSubKey(str);
foreach (string value in rkLang.GetValueNames())
{
if (rkLang.GetValueKind(value) == Microsoft.Win32.RegistryValueKind.DWord)
{
string blah = String.Concat("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\", value.Split(new char[] { ':' })[1]);
Microsoft.Win32.RegistryKey rkKeyboardLayout = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(blah);
Console.WriteLine(rkKeyboardLayout.GetValue("Layout Text"));
}
}
Console.WriteLine();
}
System.Security.CodeAccessPermission.RevertAssert();
}
}
Upvotes: 4