File: Shared\MS\Internal\TextServicesLoader.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//---------------------------------------------------------------------------
//
// <copyright file=TextServicesLoader.cs company=Microsoft>
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
// 
//
// Description: Creates ITfThreadMgr instances, the root object of the Text
//              Services Framework.
//
// History:  
//  07/16/2003 : Microsoft - ported from dotnet tree.
//
//---------------------------------------------------------------------------
 
using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security;
using System.Threading;
using MS.Internal;
using Microsoft.Win32;
using MS.Win32;
using System.Diagnostics;
 
#if WINDOWS_BASE
    using MS.Internal.WindowsBase;
#elif PRESENTATION_CORE
    using MS.Internal.PresentationCore;
#elif PRESENTATIONFRAMEWORK
    using MS.Internal.PresentationFramework;
#elif DRT
    using MS.Internal.Drt;
#else
#error Attempt to use FriendAccessAllowedAttribute from an unknown assembly.
using MS.Internal.YourAssemblyName;
#endif
 
namespace MS.Internal
{
    // Creates ITfThreadMgr instances, the root object of the Text Services
    // Framework.
    [FriendAccessAllowed]
    internal class TextServicesLoader
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // Private ctor to prevent anyone from instantiating this static class.
        private TextServicesLoader() {}
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
        
        /// <summary>
        /// Loads an instance of the Text Services Framework.
        /// </summary>
        /// <returns>
        /// May return null if no text services are available.
        /// </returns>
        /// <SecurityNote>
        /// SecurityCritical: As this causes elevation of privilige
        /// </SecurityNote>
        [SecurityCritical]
        internal static UnsafeNativeMethods.ITfThreadMgr Load()
        {
            UnsafeNativeMethods.ITfThreadMgr threadManager;
            
            Invariant.Assert(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA, "Load called on MTA thread!");
 
            if (ServicesInstalled)
            {
                // NB: a COMException here means something went wrong initialzing Cicero.
                // Cicero will throw an exception if it doesn't think it should have been
                // loaded (no TIPs to run), you can check that in msctf.dll's NoTipsInstalled
                // which lives in nt\windows\advcore\ctf\lib\immxutil.cpp.  If that's the
                // problem, ServicesInstalled is out of sync with Cicero's thinking.
                if (UnsafeNativeMethods.TF_CreateThreadMgr(out threadManager) == NativeMethods.S_OK)
                {
                    return threadManager;
                }
            }
 
            return null;
        }
 
        /// <summary>
        /// Informs the caller if text services are installed for the current user.
        /// </summary>
        /// <returns>
        /// true if one or more text services are installed for the current user, otherwise false.
        /// </returns>
        /// <remarks>
        /// If this method returns false, TextServicesLoader.Load is guarenteed to return null.
        /// Callers can use this information to avoid overhead that would otherwise be
        /// required to support text services.
        /// </remarks>
        internal static bool ServicesInstalled
        {
            get
            {
                lock (s_servicesInstalledLock)
                {
                    if (s_servicesInstalled == InstallState.Unknown)
                    {
                        s_servicesInstalled = TIPsWantToRun() ? InstallState.Installed : InstallState.NotInstalled;
                    }
                }
 
                return (s_servicesInstalled == InstallState.Installed);
            }
        }
 
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Internal Events
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        //
        // This method tries to stop Avalon from loading Cicero when there are no TIPs to run.
        // The perf tradeoff is a typically small number of registry checks versus loading and
        // initializing cicero.
        //
        // The Algorithm:
        //
        // Do a quick check vs. the global disable flag, return false if it is set.
        // For each key under HKLM\SOFTWARE\Microsoft\CTF\TIP (a TIP or category clsid)
        //  If the the key has a LanguageProfile subkey (it's a TIP clsid)
        //      Iterate under the matching TIP entry in HKCU.
        //          For each key under the LanguageProfile (a particular LANGID)
        //              For each key under the LANGID (an assembly GUID)
        //                  Try to read the Enable value.
        //                  If the value is set non-zero, then stop all processing and return true.
        //                  If the value is set zero, continue.
        //                  If the value does not exist, continue (default is disabled).
        //      If any Enable values were found under HKCU for the TIP, then stop all processing and return false.
        //      Else, no Enable values have been found thus far and we keep going to investigate HKLM.
        //      Iterate under the TIP entry in HKLM.
        //          For each key under the LanguageProfile (a particular LANGID)
        //              For each key under the LANGID (an assembly GUID)
        //                  Try to read the Enable value.
        //                  If the value is set non-zero, then stop all processing and return true.
        //                  If the value does not exist, then stop all processing and return true (default is enabled).
        //                  If the value is set zero, continue.
        // If we finish iterating all entries under HKLM without returning true, return false.
        //
 
        ///<SecurityNote>
        ///  Safe - no critical state stored, disclosure that Tips wanting to run is safe
        ///  Critical - critical because we do an assert
        ///</SecurityNote>
        [SecurityTreatAsSafe, SecurityCritical]
        private static bool TIPsWantToRun()
        {
            object obj;
            RegistryKey key;
            bool tipsWantToRun = false;
 
            PermissionSet ps = new PermissionSet(PermissionState.None);
            ps.AddPermission(new RegistryPermission(RegistryPermissionAccess.Read, "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\CTF"));
            ps.AddPermission(new RegistryPermission(RegistryPermissionAccess.Read, "HKEY_CURRENT_USER\\Software\\Microsoft\\CTF"));
            ps.Assert(); // BlessedAssert: 
            try
            {
                key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\CTF", false);
 
                // Is cicero disabled completely for the current user?
                if (key != null)
                {
                    obj = key.GetValue("Disable Thread Input Manager");
 
                    if (obj is int && (int)obj != 0)
                        return false;
                }
 
                // Loop through all the TIP entries for machine and current user.
                tipsWantToRun = IterateSubKeys(Registry.LocalMachine, "SOFTWARE\\Microsoft\\CTF\\TIP",new IterateHandler(SingleTIPWantsToRun), true) == EnableState.Enabled;
            }
            finally
            {
                CodeAccessPermission.RevertAssert();
            }
 
            return tipsWantToRun;
        }
 
        // Returns EnableState.Enabled if one or more TIPs are installed and
        // enabled for the current user.
        private static EnableState SingleTIPWantsToRun(RegistryKey keyLocalMachine, string subKeyName, bool localMachine)
        {
            EnableState result;
 
            if (subKeyName.Length != CLSIDLength)
                return EnableState.Disabled;
 
            // We want subkey\LanguageProfile key.
            // Loop through all the langid entries for TIP.
 
            // First, check current user.
            result = IterateSubKeys(Registry.CurrentUser, "SOFTWARE\\Microsoft\\CTF\\TIP\\" + subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), false);
 
            // Any explicit value short circuits the process.
            // Otherwise check local machine.
            if (result == EnableState.None || result == EnableState.Error)
            {
                result = IterateSubKeys(keyLocalMachine, subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), true);
 
                if (result == EnableState.None)
                {
                    result = EnableState.Enabled;
                }
            }
 
            return result;
        }
 
        // Returns EnableState.Enabled if the supplied subkey is a valid LANGID key with enabled
        // cicero assembly.
        private static EnableState IsLangidEnabled(RegistryKey key, string subKeyName, bool localMachine)
        {
            if (subKeyName.Length != LANGIDLength)
                return EnableState.Error;
 
            // Loop through all the assembly entries for the langid
            return IterateSubKeys(key, subKeyName, new IterateHandler(IsAssemblyEnabled), localMachine);
        }
 
        // Returns EnableState.Enabled if the supplied assembly key is enabled.
        private static EnableState IsAssemblyEnabled(RegistryKey key, string subKeyName, bool localMachine)
        {
            RegistryKey subKey;
            object obj;
 
            if (subKeyName.Length != CLSIDLength)
                return EnableState.Error;
 
            // Open the local machine assembly key.
            subKey = key.OpenSubKey(subKeyName);
 
            if (subKey == null)
                return EnableState.Error;
 
            // Try to read the "Enable" value.
            obj = subKey.GetValue("Enable");
 
            if (obj is int)
            {
                return ((int)obj == 0) ? EnableState.Disabled : EnableState.Enabled;
            }
 
            return EnableState.None;
        }
 
        // Calls the supplied delegate on each of the children of keyBase.
        private static EnableState IterateSubKeys(RegistryKey keyBase, string subKey, IterateHandler handler, bool localMachine)
        {
            RegistryKey key;
            string[] subKeyNames;
            EnableState state;
 
            key = keyBase.OpenSubKey(subKey, false);
 
            if (key == null)
                return EnableState.Error;
 
            subKeyNames = key.GetSubKeyNames();
            state = EnableState.Error;
 
            foreach (string name in subKeyNames)
            {
                switch (handler(key, name, localMachine))
                {
                    case EnableState.Error:
                        break;
                    case EnableState.None:
                        if (localMachine) // For lm, want to return here right away.
                            return EnableState.None;
 
                        // For current user, remember that we found no Enable value.
                        if (state == EnableState.Error)
                        {
                            state = EnableState.None;
                        }
                        break;
                    case EnableState.Disabled:
                        state = EnableState.Disabled;
                        break;
                    case EnableState.Enabled:
                        return EnableState.Enabled;
                }
            }
 
            return state;
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // String consts used to validate registry entires.
        private const int CLSIDLength = 38;  // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
        private const int LANGIDLength = 10; // 0x12345678
 
        // Status of a TIP assembly.
        private enum EnableState
        { 
            Error,      // Invalid entry.
            None,       // No explicit Enable entry on the assembly.
            Enabled,    // Assembly is enabled.
            Disabled    // Assembly is disabled.
        };
 
        // Callback delegate for the IterateSubKeys method.
        private delegate EnableState IterateHandler(RegistryKey key, string subKeyName, bool localMachine);
 
        // Install state.
        private enum InstallState
        { 
            Unknown,        // Haven't checked to see if any TIPs are installed yet.
            Installed,      // Checked and installed.
            NotInstalled    // Checked and not installed.
        }
 
        // Cached install state value.
        // Writes are not thread safe, but we don't mind the neglible perf hit
        // of potentially writing it twice.
        private static InstallState s_servicesInstalled = InstallState.Unknown;
        private static object s_servicesInstalledLock = new object();
 
        #endregion Private Fields
    }
}