File: System\Windows\Input\TextServicesContext.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
//
// Description: Manages Text Services Framework state.
//
//
 
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Threading;
using MS.Win32;
using MS.Internal;
 
namespace System.Windows.Input
{
    //------------------------------------------------------
    //
    //  TextServicesContext class
    //
    //------------------------------------------------------
 
    /// <summary>
    /// This class manages the ITfThreadMgr, EmptyDim and the reference to
    /// the default TextStore.
    /// The instance of TextServicesContext class is created per Dispatcher.
    /// </summary>
    /// <remarks>
    /// </remarks>
    internal class TextServicesContext
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        /// Instantiates a TextServicesContext.
        /// </summary>
        private TextServicesContext()
        {
            Debug.Assert(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA, "SetDispatcherThreaad on MTA thread");
 
            // We will clean up Cicero's resource when Dispatcher is terminated or
            // when AppDomain is unloaded.
            TextServicesContextShutDownListener listener = new TextServicesContextShutDownListener(this, ShutDownEvents.DispatcherShutdown | ShutDownEvents.DomainUnload);
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Releases all unmanaged resources allocated by the
        /// TextServicesContext.
        /// </summary>
        /// <remarks>
        /// if appDomainShutdown == false, this method must be called on the
        /// Dispatcher thread.  Otherwise, the caller is an AppDomain.Shutdown
        /// listener, and is calling from a worker thread.
        /// </remarks>
        internal void Uninitialize(bool appDomainShutdown)
        {
            // Unregister DefaultTextStore.
            if (_defaultTextStore != null)
            {
                StopTransitoryExtension();
                if (_defaultTextStore.DocumentManager != null)
                {
                    _defaultTextStore.DocumentManager.Pop(UnsafeNativeMethods.PopFlags.TF_POPF_ALL);
                    Marshal.ReleaseComObject(_defaultTextStore.DocumentManager);
                    _defaultTextStore.DocumentManager = null;
                }
 
                // We can't use tls during AppDomainShutdown -- we're called on
                // a worker thread.  But we don't need to cleanup in that case
                // either.
                if (!appDomainShutdown)
                {
                    InputMethod.Current.DefaultTextStore = null;
                }
 
                _defaultTextStore = null;
            }
 
            // Free up any remaining textstores.
            if (_istimactivated == true)
            {
                // Shut down the thread manager when the last TextStore goes away.
                // On XP, if we're called on a worker thread (during AppDomain shutdown)
                // we can't call call any methods on _threadManager.  The problem is
                // that there's no proxy registered for ITfThreadMgr on OS versions
                // previous to Vista.  Not calling Deactivate will leak the IMEs, but
                // in practice (1) they're singletons, so it's not unbounded; and (2)
                // most applications will share the thread with other AppDomains that
                // have a UI, in which case the IME won't be released until the process
                // shuts down in any case.  In theory we could also work around this
                // problem by creating our own XP proxy/stub implementation, which would
                // be added to WPF setup....
                if (!appDomainShutdown || System.Environment.OSVersion.Version.Major >= 6)
                {
                    _threadManager.Deactivate();
                }
                _istimactivated = false;
            }
 
            // Release the empty dim.
            if (_dimEmpty is not null)
            {
                Marshal.ReleaseComObject(_dimEmpty);
                _dimEmpty = null;
            }
 
            // Release the ThreadManager.
            // We don't do this in UnregisterTextStore because someone may have
            // called get_ThreadManager after the last TextStore was unregistered.
            if (_threadManager is not null)
            {
                Marshal.ReleaseComObject(_threadManager);
                _threadManager = null;
            }
        }
 
        /// <summary>
        /// Feeds a keystroke to the Text Services Framework, wrapper for
        /// ITfKeystrokeMgr::TestKeyUp/TestKeyDown/KeyUp/KeyDown.
        /// </summary>
        /// <remarks>
        /// Must be called on the main dispatcher thread.
        /// </remarks>
        /// <returns>
        /// true if the keystroke will be eaten by the Text Services Framework,
        /// false otherwise.
        /// Callers should stop further processing of the keystroke on true,
        /// continue otherwise.
        /// </returns>
        internal bool Keystroke(int wParam, int lParam, KeyOp op)
        {
            bool fConsume;
            UnsafeNativeMethods.ITfKeystrokeMgr keystrokeMgr;
 
            // We delay load cicero until someone creates an ITextStore.
            // Or this thread may not have a ThreadMgr.
            if (_threadManager is null)
            {
                return false;
            }
 
            keystrokeMgr = _threadManager as UnsafeNativeMethods.ITfKeystrokeMgr;
 
            switch (op)
            {
                case KeyOp.TestUp:
                    keystrokeMgr.TestKeyUp(wParam, lParam, out fConsume);
                    break;
                case KeyOp.TestDown:
                    keystrokeMgr.TestKeyDown(wParam, lParam, out fConsume);
                    break;
                case KeyOp.Up:
                    keystrokeMgr.KeyUp(wParam, lParam, out fConsume);
                    break;
                case KeyOp.Down:
                    keystrokeMgr.KeyDown(wParam, lParam, out fConsume);
                    break;
                default:
                    fConsume = false;
                    break;
            }
 
            return fConsume;
        }
 
        // Called by framework's TextStore class.  This method registers a
        // document with TSF.  The TextServicesContext must maintain this list
        // to ensure all native resources are released after gc or uninitialization.
        internal void RegisterTextStore(DefaultTextStore defaultTextStore)
        {
            // We must cache the DefaultTextStore because we'll need it from
            // a worker thread if the AppDomain is torn down before the Dispatcher
            // is shutdown.
            _defaultTextStore = defaultTextStore;
 
            UnsafeNativeMethods.ITfThreadMgr threadManager = ThreadManager;
 
            if (threadManager != null)
            {
                UnsafeNativeMethods.ITfDocumentMgr doc;
                UnsafeNativeMethods.ITfContext context;
                int editCookie = UnsafeNativeMethods.TF_INVALID_COOKIE;
 
                // Activate TSF on this thread if this is the first TextStore.
                if (_istimactivated == false)
                {
                    //temp variable created to retrieve the value
                    // which is then stored in the critical data.
                    int clientIdTemp;
                    threadManager.Activate(out clientIdTemp);
                    _clientId = clientIdTemp;
                    _istimactivated = true;
                }
 
                // Create a TSF document.
                threadManager.CreateDocumentMgr(out doc);
                doc.CreateContext(_clientId, 0 /* flags */, _defaultTextStore, out context, out editCookie);
                doc.Push(context);
 
                // Release any native resources we're done with.
                Marshal.ReleaseComObject(context);
 
                // Same DocumentManager and EditCookie in _defaultTextStore.
                _defaultTextStore.DocumentManager = doc;
                _defaultTextStore.EditCookie = editCookie;
 
                // Start the transitory extenstion so we can have Level 1 composition window from Cicero.
                StartTransitoryExtension();
            }
        }
 
 
        // Cal ITfThreadMgr.SetFocus() with the dim for the default text store
        internal void SetFocusOnDefaultTextStore()
        {
            SetFocusOnDim(DefaultTextStore.Current.DocumentManager);
        }
 
        // Cal ITfThreadMgr.SetFocus() with the empty dim.
        internal void SetFocusOnEmptyDim()
        {
            SetFocusOnDim(EmptyDocumentManager);
        }
 
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
 
        // Get TextServicesContext that is linked to the current Dispatcher.
        // return NULL if the default text store is not registered yet.
        internal static TextServicesContext DispatcherCurrent
        {
            get
            {
                // Create TextServicesContext on demand.
                if (InputMethod.Current.TextServicesContext == null)
                {
                    InputMethod.Current.TextServicesContext = new TextServicesContext();
                }
 
                return InputMethod.Current.TextServicesContext;
            }
        }
 
        /// <summary>
        /// The ITfThreadMgr for this thread.
        /// </summary>
        internal UnsafeNativeMethods.ITfThreadMgr ThreadManager => _threadManager ??= TextServicesLoader.Load();
 
        //------------------------------------------------------
        //
        //  Internal Events
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Internal Enums
        //
        //------------------------------------------------------
 
        #region Internal Enums
 
        /// <summary>
        /// Specifies the type of keystroke operation to perform in the
        /// TextServicesContext.Keystroke method.
        /// </summary>
        internal enum KeyOp
        {
            /// <summary>
            /// ITfKeystrokeMgr::TestKeyUp
            /// </summary>
            TestUp,
 
            /// <summary>
            /// ITfKeystrokeMgr::TestKeyDown
            /// </summary>
            TestDown,
 
            /// <summary>
            /// ITfKeystrokeMgr::KeyUp
            /// </summary>
            Up,
 
            /// <summary>
            /// ITfKeystrokeMgr::KeyDown
            /// </summary>
            Down
        };
 
        #endregion Internal Enums
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        // Cal ITfThreadMgr.SetFocus() with dim
        private void SetFocusOnDim(UnsafeNativeMethods.ITfDocumentMgr dim)
        {
            UnsafeNativeMethods.ITfThreadMgr threadmgr = ThreadManager;
 
            if (threadmgr != null)
            {
                threadmgr.SetFocus(dim);
            }
        }
 
        // Start the transitory extestion for Cicero Level1/Level2 composition window support.
        private void StartTransitoryExtension()
        {
            Guid guid;
            Object var;
            UnsafeNativeMethods.ITfCompartmentMgr compmgr;
            UnsafeNativeMethods.ITfCompartment comp;
            UnsafeNativeMethods.ITfSource source;
            int transitoryExtensionSinkCookie;
 
            // Start TransitryExtension
            compmgr = _defaultTextStore.DocumentManager as UnsafeNativeMethods.ITfCompartmentMgr;
 
            // Set GUID_COMPARTMENT_TRANSITORYEXTENSION
            guid = UnsafeNativeMethods.GUID_COMPARTMENT_TRANSITORYEXTENSION;
            compmgr.GetCompartment(ref guid, out comp);
            var = (int)1;
            comp.SetValue(0, ref var);
 
            // Advise TransitoryExtension Sink and store the cookie.
            guid = UnsafeNativeMethods.IID_ITfTransitoryExtensionSink;
            source = _defaultTextStore.DocumentManager as UnsafeNativeMethods.ITfSource;
            if (source != null)
            {
                // DocumentManager only supports ITfSource on Longhorn, XP does not support it
                source.AdviseSink(ref guid, _defaultTextStore, out transitoryExtensionSinkCookie);
                _defaultTextStore.TransitoryExtensionSinkCookie = transitoryExtensionSinkCookie;
            }
 
            Marshal.ReleaseComObject(comp);
        }
 
        // Stop TransitoryExtesion
        private void StopTransitoryExtension()
        {
            // Unadvice the transitory extension sink.
            if (_defaultTextStore.TransitoryExtensionSinkCookie != UnsafeNativeMethods.TF_INVALID_COOKIE)
            {
                UnsafeNativeMethods.ITfSource source;
                source = _defaultTextStore.DocumentManager as UnsafeNativeMethods.ITfSource;
                if (source != null)
                {
                    // DocumentManager only supports ITfSource on Longhorn, XP does not support it
                    source.UnadviseSink(_defaultTextStore.TransitoryExtensionSinkCookie);
                }
                _defaultTextStore.TransitoryExtensionSinkCookie = UnsafeNativeMethods.TF_INVALID_COOKIE;
            }
 
            // Reset GUID_COMPARTMENT_TRANSITORYEXTENSION
            UnsafeNativeMethods.ITfCompartmentMgr compmgr;
            compmgr = _defaultTextStore.DocumentManager as UnsafeNativeMethods.ITfCompartmentMgr;
 
            if (compmgr != null)
            {
                Guid guid;
                Object var;
                UnsafeNativeMethods.ITfCompartment comp;
                guid = UnsafeNativeMethods.GUID_COMPARTMENT_TRANSITORYEXTENSION;
                compmgr.GetCompartment(ref guid, out comp);
 
                if (comp != null)
                {
                    var = (int)0;
                    comp.SetValue(0, ref var);
                    Marshal.ReleaseComObject(comp);
                }
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
 
 
        // Create an empty dim on demand.
        private UnsafeNativeMethods.ITfDocumentMgr EmptyDocumentManager
        {
            get
            {
                if (_dimEmpty == null)
                {
                    UnsafeNativeMethods.ITfThreadMgr threadManager = ThreadManager;
                    if (threadManager == null)
                    {
                        return null;
                    }
 
                    //creating temp variable to retrieve from call and store in security critical data
                    UnsafeNativeMethods.ITfDocumentMgr dimEmptyTemp;
 
                    // Create a TSF document.
                    threadManager.CreateDocumentMgr(out dimEmptyTemp);
                    _dimEmpty = dimEmptyTemp;
                }
 
                return _dimEmpty;
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // Cached Dispatcher default text store.
        // We must cache the DefaultTextStore because we sometimes need it from
        // a worker thread if the AppDomain is torn down before the Dispatcher
        // is shutdown.
        private DefaultTextStore _defaultTextStore;
 
        // This is true if thread manager is activated.
        private bool _istimactivated;
 
        // The root TSF object, created on demand.
        private UnsafeNativeMethods.ITfThreadMgr _threadManager;
 
        // TSF ClientId from Activate call.
        private int _clientId;
 
        // The empty dim for this thread. Created on demand.
        private UnsafeNativeMethods.ITfDocumentMgr _dimEmpty;
 
        #endregion Private Fields
 
        #region WeakEventTableShutDownListener
 
        private sealed class TextServicesContextShutDownListener : ShutDownListener
        {
            public TextServicesContextShutDownListener(TextServicesContext target, ShutDownEvents events) : base(target, events)
            {
            }
 
            internal override void OnShutDown(object target, object sender, EventArgs e)
            {
                TextServicesContext textServicesContext = (TextServicesContext)target;
                textServicesContext.Uninitialize(!(sender is Dispatcher) /*appDomainShutdown*/);
            }
        }
 
        #endregion TextServicesContextShutDownListener
    }
}