Click here to Skip to main content
15,896,154 members
Articles / Programming Languages / C#

Using multiple keyboards with different layouts on the same machine

Rate me:
Please Sign up or sign in to vote.
4.83/5 (12 votes)
23 Oct 2007CPOL3 min read 172.3K   7.3K   30  
RightKeyboard is a program that allows you to transparently switch input languages based on the keyboard you are typing on.
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;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
using System.Globalization;

namespace RightKeyboard {
	public partial class MainForm : Form {
		private IntPtr hCurrentDevice = IntPtr.Zero;

		private bool selectingLayout = false;
		private ushort currentLayout;
		private LayoutSelectionDialog layoutSelectionDialog = new LayoutSelectionDialog();

		private Dictionary<IntPtr, ushort> languageMappings = new Dictionary<IntPtr, ushort>();

		private Dictionary<string, IntPtr> devicesByName = new Dictionary<string, IntPtr>();

		public MainForm() {
			InitializeComponent();

			RAWINPUTDEVICE rawInputDevice = new RAWINPUTDEVICE(1, 6, API.RIDEV_INPUTSINK, this);
			bool ok = API.RegisterRawInputDevices(rawInputDevice);
			if(!ok) {
				throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
			}
			Debug.Assert(ok);

			WindowState = FormWindowState.Minimized;

			LoadDeviceList();
			LoadConfiguration();
		}

		protected override void OnClosed(EventArgs e) {
			base.OnClosed(e);
			SaveConfiguration();
		}

		private void SaveConfiguration() {
			try {
				string configFilePath = GetConfigFilePath();
				using(TextWriter output = File.CreateText(configFilePath)) {
					foreach(KeyValuePair<string, IntPtr> entry in devicesByName) {
						ushort layout;
						if(languageMappings.TryGetValue(entry.Value, out layout)) {
							output.WriteLine("{0}={1:X04}", entry.Key, layout);
						}
					}
				}
			}
			catch(Exception err) {
				MessageBox.Show("Could not save the configuration. Reason: " + err.Message);
			}
		}

		private void LoadConfiguration() {
			try {
				string configFilePath = GetConfigFilePath();
				if(File.Exists(configFilePath)) {
					using(TextReader input = File.OpenText(configFilePath)) {
						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);

							IntPtr deviceHandle;
							if(devicesByName.TryGetValue(deviceName, out deviceHandle)) {
								languageMappings.Add(deviceHandle, layout);
							}
						}
					}
				}
			}
			catch(Exception err) {
				MessageBox.Show("Could not load the configuration. Reason: " + err.Message);
			}
		}

		private static string GetConfigFilePath() {
			string configFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RightKeyboard");
			if(!Directory.Exists(configFileDir)) {
				Directory.CreateDirectory(configFileDir);
			}

			return Path.Combine(configFileDir, "config.txt");
		}

		private void LoadDeviceList() {
			foreach(API.RAWINPUTDEVICELIST rawInputDevice in API.GetRawInputDeviceList()) {
				if(rawInputDevice.dwType == API.RIM_TYPEKEYBOARD) {
					IntPtr deviceHandle = rawInputDevice.hDevice;
					string deviceName = API.GetRawInputDeviceName(deviceHandle);
					devicesByName.Add(deviceName, deviceHandle);
				}
			}
		}

		protected override void OnLoad(EventArgs e) {
			base.OnLoad(e);
			Hide();
		}

		protected override void WndProc(ref Message message) {
			switch(message.Msg) {
				case API.WM_INPUT:
					if(!selectingLayout) {
						ProcessInputMessage(message);
					}
					break;

				case API.WM_POWERBROADCAST:
					ProcessPowerMessage(message);
					break;

				default:
					base.WndProc(ref message);
					break;
			}
		}

		private void ProcessPowerMessage(Message message) {
			switch(message.WParam.ToInt32()) {
				case API.PBT_APMQUERYSUSPEND:
					Debug.WriteLine("PBT_APMQUERYSUSPEND");
					break;

				case API.PBT_APMQUERYSTANDBY:
					Debug.WriteLine("PBT_APMQUERYSTANDBY");
					break;

				case API.PBT_APMQUERYSUSPENDFAILED:
					Debug.WriteLine("PBT_APMQUERYSUSPENDFAILED");
					break;

				case API.PBT_APMQUERYSTANDBYFAILED:
					Debug.WriteLine("PBT_APMQUERYSTANDBYFAILED");
					break;

				case API.PBT_APMSUSPEND:
					Debug.WriteLine("PBT_APMSUSPEND");
					break;

				case API.PBT_APMSTANDBY:
					Debug.WriteLine("PBT_APMSTANDBY");
					break;

				case API.PBT_APMRESUMECRITICAL:
					Debug.WriteLine("PBT_APMRESUMECRITICAL");
					break;

				case API.PBT_APMRESUMESUSPEND:
					Debug.WriteLine("PBT_APMRESUMESUSPEND");
					break;

				case API.PBT_APMRESUMESTANDBY:
					Debug.WriteLine("PBT_APMRESUMESTANDBY");
					break;

				case API.PBT_APMBATTERYLOW:
					Debug.WriteLine("PBT_APMBATTERYLOW");
					break;

				case API.PBT_APMPOWERSTATUSCHANGE:
					Debug.WriteLine("PBT_APMPOWERSTATUSCHANGE");
					break;

				case API.PBT_APMOEMEVENT:
					Debug.WriteLine("PBT_APMOEMEVENT");
					break;

				case API.PBT_APMRESUMEAUTOMATIC:
					Debug.WriteLine("PBT_APMRESUMEAUTOMATIC");
					break;

				default:
					break;
			}
		}

		private void ProcessInputMessage(Message message) {
			RAWINPUTHEADER header;
			uint result = API.GetRawInputData(message.LParam, API.RID_HEADER, out header);
			Debug.Assert(result != uint.MaxValue);
			if(result == uint.MaxValue) {
				throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
			}

			if(header.hDevice != hCurrentDevice) {
				hCurrentDevice = header.hDevice;
				CurrentDeviceChanged(hCurrentDevice);
			}
		}

		private void CurrentDeviceChanged(IntPtr hCurrentDevice) {
			ushort layout;
			if(!languageMappings.TryGetValue(hCurrentDevice, out layout)) {
				selectingLayout = true;
				layoutSelectionDialog.ShowDialog();
				selectingLayout = false;
				layout = layoutSelectionDialog.Layout.Identifier;
				languageMappings.Add(hCurrentDevice, layout);
			}
			SetCurrentLayout(layout);
			SetDefaultLayout(layout);
		}

		private void SetCurrentLayout(ushort layout) {
			if(layout != currentLayout && layout != 0) {
				currentLayout = layout;
				uint recipients = API.BSM_APPLICATIONS;
				API.BroadcastSystemMessage(API.BSF_POSTMESSAGE, ref recipients, API.WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, new IntPtr(layout));
			}
		}

		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 exitToolStripMenuItem_Click(object sender, EventArgs e) {
			Close();
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Unknown
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions