File: winforms\Managed\System\WinForms\InputLanguage.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="InputLanguage.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Windows.Forms {
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.Security.Permissions;
 
    using System.Diagnostics;
 
    using System;
    using Microsoft.Win32;
    using System.Globalization;
    using System.ComponentModel;
    using System.Drawing;
 
    /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage"]/*' />
    /// <devdoc>
    ///    <para>
    ///       Provides methods and fields to manage the input language.
    ///    </para>
    /// </devdoc>
    public sealed class InputLanguage {
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.handle"]/*' />
        /// <devdoc>
        ///     The HKL handle.
        /// </devdoc>
        /// <internalonly/>
        private readonly IntPtr handle;
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.InputLanguage"]/*' />
        /// <devdoc>
        /// </devdoc>
        /// <internalonly/>
        internal InputLanguage(IntPtr handle) {
            this.handle = handle;
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.Culture"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Returns
        ///       the culture of the current input language.
        ///    </para>
        /// </devdoc>
        public CultureInfo Culture {
            get {
                return new CultureInfo((int)handle & 0xFFFF);
            }
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.CurrentInputLanguage"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets the input language for the current thread.
        ///    </para>
        /// </devdoc>
        public static InputLanguage CurrentInputLanguage {
            get {
                Application.OleRequired(); 
                // note we can obtain the KeyboardLayout for a given thread...
                return new InputLanguage(SafeNativeMethods.GetKeyboardLayout(0));
            }
            set {
                IntSecurity.AffectThreadBehavior.Demand();
 
                // (NDPWhidbey 8362) OleInitialize needs to be called before we can call ActivateKeyboardLayout.
                Application.OleRequired(); 
                if (value == null) {
                    value = InputLanguage.DefaultInputLanguage;
                }
                IntPtr handleOld = SafeNativeMethods.ActivateKeyboardLayout(new HandleRef(value, value.handle), 0);
                if (handleOld == IntPtr.Zero) {
                    throw new ArgumentException(SR.GetString(SR.ErrorBadInputLanguage), "value");
                }
            }
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.DefaultInputLanguage"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Returns the default input language for the system.
        ///    </para>
        /// </devdoc>
        public static InputLanguage DefaultInputLanguage {
            get {
                IntPtr[] data = new IntPtr[1];
                UnsafeNativeMethods.SystemParametersInfo(NativeMethods.SPI_GETDEFAULTINPUTLANG, 0, data, 0);
                return new InputLanguage(data[0]);
            }
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.Handle"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Returns the handle for the input language.
        ///    </para>
        /// </devdoc>
        public IntPtr Handle {
            get {
                return handle;
            }
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.InstalledInputLanguages"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Returns a list of all installed input languages.
        ///    </para>
        /// </devdoc>
        public static InputLanguageCollection InstalledInputLanguages {
            get {
                int size = SafeNativeMethods.GetKeyboardLayoutList(0, null);
                
                IntPtr[] handles = new IntPtr[size];
                SafeNativeMethods.GetKeyboardLayoutList(size, handles);
    
                InputLanguage[] ils = new InputLanguage[size];
                for (int i = 0; i < size; i++)
                    ils[i] = new InputLanguage(handles[i]);
    
                return new InputLanguageCollection(ils);
            }
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.LayoutName"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Returns
        ///       the name of the current keyboard layout as it appears in the Windows Regional Settings on the computer.
        ///    </para>
        /// </devdoc>
        public string LayoutName {
            get {
                // There is no good way to do this in Win32...  GetKeyboardLayoutName does what we want,
                // but only for the current input language; setting and resetting the current input language
                // would generate spurious InputLanguageChanged events.
    
                /*
                            HKL is a 32 bit value. HIWORD is a Device Handle. LOWORD is Language ID.
                
                HKL
                +------------------------+-------------------------+
                |     Device Handle      |       Language ID       |
                +------------------------+-------------------------+
                31                     16 15                      0   bit
                
                
                Language ID
                +---------------------------+-----------------------+
                |     Sublanguage ID        | Primary Language ID   |
                +---------------------------+-----------------------+
                15                        10 9                     0   bit
                
                WORD LangId  = MAKELANGID(primary, sublang)
                BYTE primary = PRIMARYLANGID(LangId)
                BYTE sublang = PRIMARYLANGID(LangID)
                
                How Preload is interpreted: example US-Dvorak
                Look in HKEY_CURRENT_USER\Keyboard Layout\Preload
                Name="4"  (may vary)
                Value="d0000409"  -> Language ID = 0409
                Look in HKEY_CURRENT_USER\Keyboard Layout\Substitutes
                Name="d0000409"
                Value="00010409"
                Look in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\00010409
                "Layout File": name of keyboard layout DLL (KBDDV.DLL)
                "Layout Id": ID of this layout (0002)
                Win32k will change the top nibble of layout ID to F, which makes F002.
                Combined with Language ID, the final HKL is F0020409.
                */
    
                string layoutName = null;
    
                IntPtr currentHandle = handle;
                int language = unchecked( (int) (long)currentHandle) & 0xffff;
                int device = (unchecked( (int) (long)currentHandle) >> 16) & 0x0fff;
    
                // SECREVIEW : We have to get the input information from the registry. These two 
                //           : keys only contain keyboard information. This is safe to do.
                //
                new RegistryPermission(PermissionState.Unrestricted).Assert();
                
                try {
                    if (device == language || device == 0) {
                        // Default keyboard for language
                        string keyName = Convert.ToString(language, 16);
                        keyName = PadWithZeroes(keyName, 8);
                        RegistryKey key = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + keyName);
 
                        // Attempt to extract the localized keyboard layout name
                        // using the SHLoadIndirectString API...
                        layoutName = GetLocalizedKeyboardLayoutName(key.GetValue("Layout Display Name") as string);
 
                        // Default back to our legacy codepath and obtain the name
                        // directly through the registry value
                        if (layoutName == null) {
                            layoutName = (string) key.GetValue("Layout Text");
                        }
                        
                        key.Close();
                    }
                    else {
                        // Look for a substitution 
                        //
                        RegistryKey substitutions = Registry.CurrentUser.OpenSubKey("Keyboard Layout\\Substitutes");
                        string[] encodings = null;
                        if (substitutions != null) {
                            encodings = substitutions.GetValueNames();
                        
                            foreach (string encoding in encodings) {
                                int encodingValue = Convert.ToInt32(encoding, 16);
                                if (encodingValue == unchecked( (int) (long)currentHandle) || 
                                    (encodingValue & 0x0FFFFFFF) == (unchecked( (int) (long)currentHandle) & 0x0FFFFFFF) ||
                                    (encodingValue & 0xFFFF) == language) {
                                
                                    currentHandle = (IntPtr)Convert.ToInt32((string)substitutions.GetValue(encoding), 16);
                                    language = unchecked( (int) (long)currentHandle) & 0xFFFF;
                                    device = (unchecked( (int) (long)currentHandle) >> 16) & 0xFFF;
                                    break;
                                }
                            }
                        
                            substitutions.Close();
                        }
                        
                        RegistryKey layouts = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts");
                        if (layouts != null) {
                            encodings = layouts.GetSubKeyNames();
                        
                            // Check to see if the encoding directly matches the handle -- some do.
                            //
                            foreach (string encoding in encodings) {
                                Debug.Assert(encoding.Length == 8, "unexpected key in registry: hklm\\SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + encoding);
                                if (currentHandle == (IntPtr)Convert.ToInt32(encoding, 16)) {
                                    RegistryKey key = layouts.OpenSubKey(encoding);
 
                                    // Attempt to extract the localized keyboard layout name
                                    // using the SHLoadIndirectString API...
                                    layoutName = GetLocalizedKeyboardLayoutName(key.GetValue("Layout Display Name") as string);
 
                                    // Default back to our legacy codepath and obtain the name
                                    // directly through the registry value
                                    if (layoutName == null) {
                                        layoutName = (string) key.GetValue("Layout Text");
                                    }
 
                                    key.Close();
                                    break;
                                }
                            }
                        }
                        
                        if (layoutName == null) {
                        
                            // No luck there.  Match the language first, then try to find a layout ID
                            //
                            foreach (string encoding in encodings) {
                                Debug.Assert(encoding.Length == 8, "unexpected key in registry: hklm\\SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + encoding);
                                if (language == (0xffff & Convert.ToInt32(encoding.Substring(4,4), 16))) {
                                    RegistryKey key = layouts.OpenSubKey(encoding);
                                    string codeValue = (string) key.GetValue("Layout Id");
                                    if (codeValue != null) {
                                        int value = Convert.ToInt32(codeValue, 16);
                                        if (value == device) {
 
                                            // Attempt to extract the localized keyboard layout name
                                            // using the SHLoadIndirectString API...
                                            layoutName = GetLocalizedKeyboardLayoutName(key.GetValue("Layout Display Name") as string);
 
                                            // Default back to our legacy codepath and obtain the name
                                            // directly through the registry value
                                            if (layoutName == null) {
                                                layoutName = (string) key.GetValue("Layout Text");
                                            }
                                        }
                                    }
                                    key.Close();
                                    if (layoutName != null) {
                                        break;
                                    }
                                }
                            }
                        }
                        
                        layouts.Close();
                    }
                }
                finally {
                    System.Security.CodeAccessPermission.RevertAssert();
                }
    
                if (layoutName == null) {
                    layoutName = SR.GetString(SR.UnknownInputLanguageLayout);
                }
                return layoutName;
            }
        }
 
        /// <devdoc>
        ///     Attempts to extract the localized keyboard layout name using the 
        ///     SHLoadIndirectString API (only on OSVersions >= 5).  Returning 
        ///     null from this method will force us to use the legacy codepath
        ///     (pulling the text directly from the registry).
        /// </devdoc>
        private static string GetLocalizedKeyboardLayoutName(string layoutDisplayName) {
            
            if (layoutDisplayName != null && Environment.OSVersion.Version.Major >= 5) {
 
                StringBuilder sb = new StringBuilder(512);
 
                uint res = UnsafeNativeMethods.SHLoadIndirectString(layoutDisplayName, sb, (uint)sb.Capacity, IntPtr.Zero);
 
                if (res == NativeMethods.S_OK) {
                    return sb.ToString();
                }
            }
 
            return null;
        }
        
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.CreateInputLanguageChangedEventArgs"]/*' />
        /// <devdoc>
        ///     Creates an InputLanguageChangedEventArgs given a windows message.
        /// </devdoc>
        /// <internalonly/>
        internal static InputLanguageChangedEventArgs CreateInputLanguageChangedEventArgs(Message m) {
            return new InputLanguageChangedEventArgs(new InputLanguage(m.LParam), unchecked((byte)(long)m.WParam));
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.CreateInputLanguageChangingEventArgs"]/*' />
        /// <devdoc>
        ///     Creates an InputLanguageChangingEventArgs given a windows message.
        /// </devdoc>
        /// <internalonly/>
        internal static InputLanguageChangingEventArgs CreateInputLanguageChangingEventArgs(Message m) {
            InputLanguage inputLanguage = new InputLanguage(m.LParam);
 
            // NOTE: by default we should allow any locale switch
            //
            bool localeSupportedBySystem = !(m.WParam == IntPtr.Zero);
            return new InputLanguageChangingEventArgs(inputLanguage, localeSupportedBySystem);
 
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.Equals"]/*' />
        /// <devdoc>
        ///    <para>Specifies whether two input languages are equal.</para>
        /// </devdoc>
        public override bool Equals(object value) {
            if (value is InputLanguage) {
                return(this.handle == ((InputLanguage)value).handle);
            }
            return false;
        }
 
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.FromCulture"]/*' />
        /// <devdoc>
        ///    <para>Returns the input language associated with the specified 
        ///       culture.</para>
        /// </devdoc>
        public static InputLanguage FromCulture(CultureInfo culture) {
 
            // KeyboardLayoutId is the LCID for built-in cultures, but it  
            // is the CU-preferred keyboard language for custom cultures.
            int lcid = culture.KeyboardLayoutId;
            
            foreach(InputLanguage lang in InstalledInputLanguages) {
                if ((unchecked( (int) (long)lang.handle) & 0xFFFF) == lcid) {
                    return lang;
                }
            }
 
            return null;
        }
        
        /// <include file='doc\InputLanguage.uex' path='docs/doc[@for="InputLanguage.GetHashCode"]/*' />
        /// <devdoc>
        ///    <para>Hash code for this input language.</para>
        /// </devdoc>
        public override int GetHashCode() {
            return unchecked( (int) (long)handle);
        }
 
        private static string PadWithZeroes(string input, int length) {
            return "0000000000000000".Substring(0, length - input.Length) + input;
        }
    }
}