From ddf5829a1d4177fe3de21e1540a3d93b79d2a9d4 Mon Sep 17 00:00:00 2001 From: Mickael Nivet Date: Thu, 28 May 2020 01:01:31 +0200 Subject: [PATCH 1/4] Fix detection of keyboard names Interpretation of input local identifier returned by GetKeyboardLayout was really partial since only the language part was interpreted. Now the language part is map with names provided by the system through CultureInfo (no need of a Layout.txt mapping file for that). And the keyboard part is read with more system call and a call to the registry. So now their is no more limitations on the number of keyboards we can setup in a language, and can switch between multiple keyboards in a same language or in different languages. --- .gitignore | 4 +- .../RightKeyboard.NUnit.csproj | 21 +++ RightKeyboard.NUnit/UnitTest1.cs | 19 +++ RightKeyboard.sln | 6 + RightKeyboard/Layout.cs | 58 ++----- RightKeyboard/LayoutSelectionDialog.cs | 16 +- RightKeyboard/Layouts.txt | 141 ------------------ RightKeyboard/MainForm.cs | 52 +++---- RightKeyboard/RightKeyboard.csproj | 1 - RightKeyboard/Win32/API.cs | 45 ++++++ 10 files changed, 128 insertions(+), 235 deletions(-) create mode 100644 RightKeyboard.NUnit/RightKeyboard.NUnit.csproj create mode 100644 RightKeyboard.NUnit/UnitTest1.cs delete mode 100644 RightKeyboard/Layouts.txt diff --git a/.gitignore b/.gitignore index 9542e63..4b82ccd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .vs/ -RightKeyboard/bin/ -RightKeyboard/obj/ +bin/ +obj/ diff --git a/RightKeyboard.NUnit/RightKeyboard.NUnit.csproj b/RightKeyboard.NUnit/RightKeyboard.NUnit.csproj new file mode 100644 index 0000000..437f72a --- /dev/null +++ b/RightKeyboard.NUnit/RightKeyboard.NUnit.csproj @@ -0,0 +1,21 @@ + + + + net4 + + false + + RightKeyboard + + + + + + + + + + + + + diff --git a/RightKeyboard.NUnit/UnitTest1.cs b/RightKeyboard.NUnit/UnitTest1.cs new file mode 100644 index 0000000..72f83b1 --- /dev/null +++ b/RightKeyboard.NUnit/UnitTest1.cs @@ -0,0 +1,19 @@ +using NUnit.Framework; +using RightKeyboard.Win32; + +namespace RightKeyboard.NUnit +{ + public class Tests + { + [Test] + public void GetKeyboardLayoutName_MustReturnLanguageNameAndKeyboardName() + { + var list = API.GetKeyboardLayoutList(); + foreach (var layout in list) + { + var name = API.GetKeyboardLayoutName(layout); + Assert.That(name, Is.Not.Empty, "Depends on what is installed on the machine, so manual human check with debugger is required."); + } + } + } +} \ No newline at end of file diff --git a/RightKeyboard.sln b/RightKeyboard.sln index 14865ad..7d00920 100644 --- a/RightKeyboard.sln +++ b/RightKeyboard.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RightKeyboard", "RightKeyboard\RightKeyboard.csproj", "{D9A15FB8-E81F-4472-8DF4-948E083AE6BC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RightKeyboard.NUnit", "RightKeyboard.NUnit\RightKeyboard.NUnit.csproj", "{79526D79-46A9-4EEB-8AC3-711344DC4A39}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {D9A15FB8-E81F-4472-8DF4-948E083AE6BC}.Debug|Any CPU.Build.0 = Debug|Any CPU {D9A15FB8-E81F-4472-8DF4-948E083AE6BC}.Release|Any CPU.ActiveCfg = Release|Any CPU {D9A15FB8-E81F-4472-8DF4-948E083AE6BC}.Release|Any CPU.Build.0 = Release|Any CPU + {79526D79-46A9-4EEB-8AC3-711344DC4A39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79526D79-46A9-4EEB-8AC3-711344DC4A39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79526D79-46A9-4EEB-8AC3-711344DC4A39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79526D79-46A9-4EEB-8AC3-711344DC4A39}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RightKeyboard/Layout.cs b/RightKeyboard/Layout.cs index 7e6d7e3..da17f9c 100644 --- a/RightKeyboard/Layout.cs +++ b/RightKeyboard/Layout.cs @@ -1,78 +1,44 @@ using System; -using System.IO; -using System.Reflection; +using RightKeyboard.Win32; +using System.Linq; using System.Collections.Generic; -using System.Globalization; namespace RightKeyboard { /// /// Represents a keyboard layout /// public class Layout { - private readonly ushort identifier; /// /// Gets the layout's identifier /// - public ushort Identifier { - get { - return identifier; - } - } - - private readonly string name; + public IntPtr Identifier { get; } /// /// Gets the layout's name /// - public string Name { - get { - return name; - } - } + public string Name { get; } /// /// Initializes a new instance of Layout /// /// /// - public Layout(ushort identifier, string name) { - this.identifier = identifier; - this.name = name; + public Layout(IntPtr identifier, string name) { + this.Identifier = identifier; + this.Name = name; } public override string ToString() { - return name; + return Name; } - private static Layout[] cachedLayouts = null; - /// - /// Gets the keyboard layouts from a ressource file + /// Gets the installed keyboard layouts /// - /// - public static Layout[] GetLayouts() { - if(cachedLayouts == null) { - List layouts = new List(); - using(Stream input = Assembly.GetExecutingAssembly().GetManifestResourceStream("RightKeyboard.Layouts.txt")) { - using(TextReader reader = new StreamReader(input)) { - string line; - while((line = reader.ReadLine()) != null) { - layouts.Add(GetLayout(line)); - } - } - } - cachedLayouts = layouts.ToArray(); - } - return cachedLayouts; - } - - private static Layout GetLayout(string line) { - string[] parts = line.Trim().Split('='); - - ushort identifier = ushort.Parse(parts[0], NumberStyles.HexNumber); - string name = parts[1]; - return new Layout(identifier, name); + public static IEnumerable EnumerateLayouts() { + return API.GetKeyboardLayoutList() + .Select(p => new Layout(p, API.GetKeyboardLayoutName(p))); } } } \ No newline at end of file diff --git a/RightKeyboard/LayoutSelectionDialog.cs b/RightKeyboard/LayoutSelectionDialog.cs index 4b9fe3c..150d0c3 100644 --- a/RightKeyboard/LayoutSelectionDialog.cs +++ b/RightKeyboard/LayoutSelectionDialog.cs @@ -1,11 +1,6 @@ using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Text; using System.Windows.Forms; -using RightKeyboard.Win32; namespace RightKeyboard { public partial class LayoutSelectionDialog : Form { @@ -19,15 +14,8 @@ private void LoadLanguageList() { lbLayouts.Items.Clear(); recentLayoutsCount = 0; - IntPtr[] installedLayouts = API.GetKeyboardLayoutList(); - - foreach(Layout layout in RightKeyboard.Layout.GetLayouts()) { - foreach(IntPtr installedLayout in installedLayouts) { - ushort languageId = unchecked((ushort)installedLayout.ToInt32()); - if(layout.Identifier == languageId) { - lbLayouts.Items.Add(layout); - } - } + foreach(Layout layout in Layout.EnumerateLayouts()) { + lbLayouts.Items.Add(layout); } lbLayouts.SelectedIndex = 0; diff --git a/RightKeyboard/Layouts.txt b/RightKeyboard/Layouts.txt deleted file mode 100644 index 10bfe70..0000000 --- a/RightKeyboard/Layouts.txt +++ /dev/null @@ -1,141 +0,0 @@ -0436=Afrikaans (South Africa) -041c=Albanian (Albania) -1401=Arabic (Algeria) -3c01=Arabic (Bahrain) -0c01=Arabic (Egypt) -0801=Arabic (Iraq) -2c01=Arabic (Jordan) -3401=Arabic (Kuwait) -3001=Arabic (Lebanon) -1001=Arabic (Libya) -1801=Arabic (Morocco) -2001=Arabic (Oman) -4001=Arabic (Qatar) -0401=Arabic (Saudi Arabia) -2801=Arabic (Syria) -1c01=Arabic (Tunisia) -3801=Arabic (U.A.E.) -2401=Arabic (Yemen) -042b=Armenian (Armenia) -044d=Assamese (India) -082c=Azeri (Azerbaijan (Cyrillic)) -042c=Azeri (Azerbaijan (Latin)) -042d=Basque (Spain) -0423=Belarusian (Belarus) -0445=Bengali (India) -0402=Bulgarian (Bulgaria) -0403=Catalan (Spain) -0c04=Chinese (Hong Kong SAR) -1404=Chinese (Macao SAR) -0804=Chinese (PRC) -1004=Chinese (Singapore) -0404=Chinese (Taiwan) -0827=Classic Lithuanian (Lithuania) -041a=Croatian (Croatia) -0405=Czech (Czech Republic) -0406=Danish (Denmark) -0465=Divehi (Maldives) -0813=Dutch (Belgium) -0413=Dutch (Netherlands) -0c09=English (Australia) -2809=English (Belize) -1009=English (Canada) -2409=English (Caribbean) -1809=English (Ireland) -2009=English (Jamaica) -1409=English (New Zealand) -3409=English (Philippines) -1c09=English (South Africa) -2c09=English (Trinidad) -0809=English (United Kingdom) -0409=English (United States) -3009=English (Zimbabwe) -0425=Estonian (Estonia) -0438=Faeroese (Faeroe Islands) -0429=Farsi (Iran) -040b=Finnish (Finland) -080c=French (Belgium) -0c0c=French (Canada) -040c=French (France) -140c=French (Luxembourg) -180c=French (Monaco) -100c=French (Switzerland) -042f=Macedonian (FYROM) (Macedonian (FYROM)) -0456=Galician (Spain) -0437=Georgian (Georgia) -0c07=German (Austria) -0407=German (Germany) -1407=German (Liechtenstein) -1007=German (Luxembourg) -0807=German (Switzerland) -0408=Greek (Greece) -0447=Gujarati (India ) -040d=Hebrew (Israel) -0439=Hindi (India) -040e=Hungarian (Hungary) -040f=Icelandic (Iceland) -0421=Indonesian (Indonesia (Bahasa)) -0410=Italian (Italy) -0810=Italian (Switzerland) -0411=Japanese (Japan) -044b=Kannada (India (Kannada script)) -043f=Kazakh (Kazakstan) -0457=Konkani (India) -0412=Korean (Korea) -0440=Kyrgyz (Kyrgyzstan) -0426=Latvian (Latvia) -0427=Lithuanian (Lithuania) -083e=Malay (Brunei Darussalam) -043e=Malay (Malaysia) -044c=Malayalam (India) -044e=Marathi (India) -0450=Mongolian (Cyrillic) (Mongolia) -0414=Norwegian (Norway (Bokmål)) -0814=Norwegian (Norway (Nynorsk)) -0448=Oriya (India) -0415=Polish (Poland) -0416=Portuguese (Brazil) -0816=Portuguese (Portugal) -0446=Punjabi (India (Gurmukhi script)) -0418=Romanian (Romania) -0419=Russian (Russia) -044f=Sanskrit (India) -0c1a=Serbian (Serbia (Cyrillic)) -081a=Serbian (Serbia (Latin)) -041b=Slovak (Slovakia) -0424=Slovenian (Slovenia) -2c0a=Spanish (Argentina) -400a=Spanish (Bolivia) -340a=Spanish (Chile) -240a=Spanish (Colombia) -140a=Spanish (Costa Rica) -1c0a=Spanish (Dominican Republic) -300a=Spanish (Ecuador) -440a=Spanish (El Salvador) -100a=Spanish (Guatemala) -480a=Spanish (Honduras) -080a=Spanish (Mexico) -4c0a=Spanish (Nicaragua) -180a=Spanish (Panama) -3c0a=Spanish (Paraguay) -280a=Spanish (Peru) -500a=Spanish (Puerto Rico) -040a=Spanish (Spain (Traditional sort)) -0c0a=Spanish (Spain (International sort)) -380a=Spanish (Uruguay) -200a=Spanish (Venezuela) -0441=Swahili (Kenya) -081d=Swedish (Finland) -041d=Swedish (Sweden) -045a=Syriac (Syria) -0449=Tamil (India) -0444=Tatar (Tatarstan) -044a=Telugu (India (Telugu script)) -041e=Thai (Thailand) -041f=Turkish (Turkey) -0422=Ukrainian (Ukraine) -0420=Urdu (Pakistan) -0820=Urdu (India) -0843=Uzbek (Uzbekistan (Cyrillic)) -0443=Uzbek (Uzbekistan (Latin)) -042a=Vietnamese (Viet Nam) \ No newline at end of file diff --git a/RightKeyboard/MainForm.cs b/RightKeyboard/MainForm.cs index 73a3f31..800b3d3 100644 --- a/RightKeyboard/MainForm.cs +++ b/RightKeyboard/MainForm.cs @@ -10,13 +10,14 @@ using System.Runtime.InteropServices; using System.IO; using System.Globalization; +using System.Linq; namespace RightKeyboard { public partial class MainForm : Form { private bool selectingLayout = false; private LayoutSelectionDialog layoutSelectionDialog = new LayoutSelectionDialog(); - private Dictionary languageMappings = new Dictionary(); + private Dictionary languageMappings = new Dictionary(); private Dictionary devicesByName = new Dictionary(); @@ -46,9 +47,8 @@ private void SaveConfiguration() { string configFilePath = GetConfigFilePath(); using(TextWriter output = File.CreateText(configFilePath)) { foreach(KeyValuePair entry in devicesByName) { - ushort layout; - if(languageMappings.TryGetValue(entry.Value, out layout)) { - output.WriteLine("{0}={1:X04}", entry.Key, layout); + if(languageMappings.TryGetValue(entry.Value, out var layout)) { + output.WriteLine("{0}={1:X8}", entry.Key, layout.Identifier.ToInt32()); } } } @@ -63,16 +63,19 @@ private void LoadConfiguration() { string configFilePath = GetConfigFilePath(); if(File.Exists(configFilePath)) { using(TextReader input = File.OpenText(configFilePath)) { + + var layouts = RightKeyboard.Layout.EnumerateLayouts().ToDictionary(k => k.Identifier, v => v); + string line; while((line = input.ReadLine()) != null) { string[] parts = line.Split('='); Debug.Assert(parts.Length == 2); string deviceName = parts[0]; - ushort layout = ushort.Parse(parts[1], NumberStyles.HexNumber); + var layoutId = new IntPtr(int.Parse(parts[1], NumberStyles.HexNumber)); - IntPtr deviceHandle; - if(devicesByName.TryGetValue(deviceName, out deviceHandle)) { + if(devicesByName.TryGetValue(deviceName, out var deviceHandle) + && layouts.TryGetValue(layoutId, out var layout)) { languageMappings.Add(deviceHandle, layout); } } @@ -196,48 +199,35 @@ private void ProcessInputMessage(Message message) { } private void ValidateCurrentDevice(IntPtr hCurrentDevice) { - ushort layout; - if (!languageMappings.TryGetValue(hCurrentDevice, out layout)) { + if (!languageMappings.TryGetValue(hCurrentDevice, out var layout)) { selectingLayout = true; layoutSelectionDialog.ShowDialog(); selectingLayout = false; - layout = layoutSelectionDialog.Layout.Identifier; + layout = layoutSelectionDialog.Layout; languageMappings.Add(hCurrentDevice, layout); } - ushort currentSysLayout = (ushort)API.GetKeyboardLayout().ToInt32(); + var currentSysLayout = API.GetKeyboardLayout(); - if (currentSysLayout != layout) + if (currentSysLayout != layout.Identifier) { - SetCurrentLayout(layout); - SetDefaultLayout(layout); + SetCurrentLayout(layout.Identifier); + SetDefaultLayout(layout.Identifier); } } - private void SetCurrentLayout(ushort layout) { + private void SetCurrentLayout(IntPtr layoutId) { uint recipients = API.BSM_APPLICATIONS; - API.BroadcastSystemMessage(API.BSF_POSTMESSAGE, ref recipients, API.WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, new IntPtr(layout)); + API.BroadcastSystemMessage(API.BSF_POSTMESSAGE, ref recipients, API.WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, layoutId); } - private void SetDefaultLayout(ushort layout) { - //IntPtr hkl = API.LoadKeyboardLayout(layout, 0); - //Debug.Assert(hkl != IntPtr.Zero); - //if(hkl == IntPtr.Zero) { - // throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); - //} - - IntPtr hkl = new IntPtr(unchecked((int)((uint)layout << 16 | (uint)layout))); - - bool ok = API.SystemParametersInfo(API.SPI_SETDEFAULTINPUTLANG, 0, new IntPtr[] { hkl }, API.SPIF_SENDCHANGE); - //Debug.Assert(ok); - //if(!ok) { - // throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); - //} + private void SetDefaultLayout(IntPtr layoutId) { + API.SystemParametersInfo(API.SPI_SETDEFAULTINPUTLANG, 0, new IntPtr[] { layoutId }, API.SPIF_SENDCHANGE); } private void clearToolStripMenuItem_Click(object sender, EventArgs e) { - languageMappings = new Dictionary(); + languageMappings.Clear(); } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { diff --git a/RightKeyboard/RightKeyboard.csproj b/RightKeyboard/RightKeyboard.csproj index acb1aed..5ef1394 100644 --- a/RightKeyboard/RightKeyboard.csproj +++ b/RightKeyboard/RightKeyboard.csproj @@ -90,7 +90,6 @@ -