Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Documents / ImmComposition.cs / 1 / ImmComposition.cs
//---------------------------------------------------------------------------- // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // // Description: // This class handles IMM32 IME's composition string and support level 3 input to TextBox and RichTextBox. // // History: // 11/09/2004 : yutakas - created // //--------------------------------------------------------------------------- using System; using System.Runtime.InteropServices; using System.Threading; using System.Collections; using System.Diagnostics; using System.Windows.Media; using System.Windows.Input; using System.Windows.Documents; using System.Windows.Interop; using System.Windows.Threading; using System.Security; using System.Security.Permissions; using System.Text; using MS.Win32; using MS.Internal.Documents; using MS.Internal.PresentationFramework; using MS.Internal; // Enable presharp pragma warning suppress directives. #pragma warning disable 1634, 1691 namespace System.Windows.Documents { //----------------------------------------------------- // // ImmComposition class // //----------------------------------------------------- // // This class handles IMM32 IME's composition string and // support level 3 input to TextBox and RichTextBox. // internal class ImmComposition { //------------------------------------------------------ // // Constructors // //----------------------------------------------------- #region Constructors // // Creates a new ImmComposition instance. // ////// Critical - This class exists purely to prevent the security exceptions from /// percolating /// TreatAsSafe: Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] static ImmComposition() { } // // Creates a new ImmComposition instance. // ////// Critical - calls critical code (UpdateSource) /// [SecurityCritical] internal ImmComposition(HwndSource source) { UpdateSource(null, source); } #endregion Constructors //------------------------------------------------------ // // Internal Method // //------------------------------------------------------ // // Create an instance of ImmComposition per source window. // ////// Critical - gets HwndSource (protected), then creates a new /// composition based on this. /// [SecurityCritical] internal static ImmComposition GetImmComposition(FrameworkElement scope) { HwndSource source = PresentationSource.CriticalFromVisual(scope) as HwndSource; ImmComposition immComposition = null; if (source != null) { lock (_list) { immComposition = (ImmComposition)_list[source]; if (immComposition == null) { immComposition = new ImmComposition(source); _list[source] = immComposition; } } } return immComposition; } // // This is called when TextEditor is detached. // We need to remove event handlers. // ////// Critical: This code removes the handler for OnSourceChanged which is critical /// TreatAsSafe:Removing the handler is a safe operation /// [SecurityCritical,SecurityTreatAsSafe] internal void OnDetach() { if (_editor != null) { PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); _editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange); } _editor = null; } // // Callback from TextEditor when it gets focus. // ////// Critical: This code calls into PresentationSource to remove and add source changed handlers /// TreatAsSafe: Calling this is safe. Since this does not expose the presentation source. Also the /// handler that is attached is private and critical /// [SecurityCritical,SecurityTreatAsSafe] internal void OnGotFocus(TextEditor editor) { if (editor == _editor) { // If an event listener does a reentrant SetFocus, we can get // here without a matching OnLostFocus. Early out so // that we don't attach too many handlers. return; } // remove source changed handler for previous editor. if (_editor != null) { PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); _editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange); } // Update the current focus TextEditor, RenderScope and UiScope. _editor = editor; // we need to track the source change. PresentationSource.AddSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); _editor.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange); // Update the current composition window position. UpdateNearCaretCompositionWindow(); } // // Callback from TextEditor when it lost focus. // internal void OnLostFocus() { if (_editor == null) return; _losingFocus = true; try { // complete the composition string when it lost focus. CompleteComposition(); } finally { _losingFocus = false; } } // // Callback from TextEditor when the layout is updated // internal void OnLayoutUpdated() { if (_updateCompWndPosAtNextLayoutUpdate && IsReadingWindowIme()) { UpdateNearCaretCompositionWindow(); } _updateCompWndPosAtNextLayoutUpdate = false; } // // complete the composition string by calling ImmNotifyIME. // ////// Critical - elevates to access protected resource (hwnd) /// TreatAsSafe - forces the composition to complete, which at worst /// causes someone's current input to commit. No additional /// spoofing or information disclosure could occur. /// [SecurityCritical, SecurityTreatAsSafe] internal void CompleteComposition() { UnregisterMouseListeners(); if (_source == null) { // Do nothing if HwndSource is already gone(disposed) or disconnected. return; } _compositionModifiedByApp = true; IntPtr hwnd = IntPtr.Zero; new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { hwnd = ((IWin32Window)_source).Handle; } finally { CodeAccessPermission.RevertAssert(); } IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { UnsafeNativeMethods.ImmNotifyIME(new HandleRef(this, himc), NativeMethods.NI_COMPOSITIONSTR, NativeMethods.CPS_COMPLETE, 0); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); } if (_compositionAdorner != null) { _compositionAdorner.Uninitialize(); _compositionAdorner = null; } _startComposition = null; _endComposition = null; } // Called as the selection changes. // We can't modify document state here in any way. internal void OnSelectionChange() { _compositionModifiedByApp = true; } // Callback for TextSelection.Changed event. internal void OnSelectionChanged() { if (!this.IsInKeyboardFocus) { return; } // Update the current composition window position. UpdateNearCaretCompositionWindow(); } //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ // // Returns true if we're in the middle of an ongoing composition. // internal bool IsComposition { get { return _startComposition != null; } } //----------------------------------------------------- // // Private Methods // //----------------------------------------------------- //----------------------------------------------------- // // SourceChanged callback // ////// Critical - calls critical code - NewSource, OldSource and UpdateSource. /// [SecurityCritical] private void OnSourceChanged(object sender, SourceChangedEventArgs e) { HwndSource newSource = null; HwndSource oldSource = null; new UIPermission(PermissionState.Unrestricted).Assert(); // BlessedAssert try { newSource = e.NewSource as HwndSource; oldSource = e.OldSource as HwndSource; } finally { UIPermission.RevertAssert(); } UpdateSource(oldSource, newSource); // Clean up the old source changed event handler that was connected with UiScope. if (oldSource != null && UiScope != null) { // Remove the source changed event handler here. // Ohterwise, we'll get the leak of the SourceChangedEventHandler. // New source changed event handler will be added by getting OnGotFocus on new UiScope. PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); } } // // Update _list and _source with new source. // ////// Critical - Calls critical code (add/remove hook) /// [SecurityCritical] private void UpdateSource(HwndSource oldSource, HwndSource newSource) { if (_source != null) { Debug.Assert((oldSource == null) || (oldSource == _source)); new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { _source.RemoveHook(new HwndSourceHook(ImmCompositionFilterMessage)); } finally { UIPermission.RevertAssert(); } _source.Disposed -= new EventHandler(OnHwndDisposed); // Remove HwndSource from the list. _list.Remove(_source); _source = null; } if (newSource != null) { _list[newSource] = this; _source = newSource; new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { _source.AddHook(new HwndSourceHook(ImmCompositionFilterMessage)); } finally { UIPermission.RevertAssert(); } _source.Disposed += new EventHandler(OnHwndDisposed); } // _source should always be a newSource. Debug.Assert(newSource == _source); } // // Window Hook to track WM_IME_ messages. // ////// Critical - access raw Win32 messages, raw input, etc. and can be used to spoof input /// [SecurityCritical] private IntPtr ImmCompositionFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { IntPtr lret = IntPtr.Zero ; switch (msg) { case NativeMethods.WM_IME_CHAR: OnWmImeChar(wParam, ref handled); break; case NativeMethods.WM_IME_NOTIFY: // we don't have to update handled. OnWmImeNotify(hwnd, wParam); break; case NativeMethods.WM_IME_STARTCOMPOSITION: case NativeMethods.WM_IME_ENDCOMPOSITION: if (IsInKeyboardFocus && !IsReadOnly) { // Do Level 2 for legacy Chinese IMM32 IMEs. if (!IsReadingWindowIme()) { handled = true; } } break; case NativeMethods.WM_IME_COMPOSITION: OnWmImeComposition(hwnd, lParam, ref handled); break; case NativeMethods.WM_IME_REQUEST: lret = OnWmImeRequest(wParam, lParam, ref handled); break; case NativeMethods.WM_INPUTLANGCHANGE: // Set the composition window position (reading window position) for // legacy Chinese IMM32 IMEs. if (IsReadingWindowIme()) { UpdateNearCaretCompositionWindow(); } break; } return lret; } // // WM_IME_COMPOSITION handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private void OnWmImeComposition(IntPtr hwnd, IntPtr lParam, ref bool handled) { IntPtr himc; int size; int cursorPos = 0; int deltaStart = 0; char[] result = null; char[] composition = null; int[] clauseInfo = null; byte[] attributes = null; if (IsReadingWindowIme()) { // Don't handle WM_IME_COMPOSITION for Chinese Legacy IMEs. return; } if (!IsInKeyboardFocus && !_losingFocus) { // Don't handle WM_IME_COMPOSITION if we don't have a focus. return; } if (IsReadOnly) { // Don't handle WM_IME_COMPOSITION if it is readonly. return; } himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc == IntPtr.Zero) { // we don't do anything with NULL-HIMC. return; } // // Get the result string from hIMC. // if (((int)lParam & NativeMethods.GCS_RESULTSTR) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_RESULTSTR, IntPtr.Zero, 0); if (size > 0) { result = new char[size / Marshal.SizeOf(typeof(short))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_RESULTSTR, result, size); } } // // Get the composition string from hIMC. // if (((int)lParam & NativeMethods.GCS_COMPSTR) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPSTR, IntPtr.Zero, 0); if (size > 0) { composition = new char[size / Marshal.SizeOf(typeof(short))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPSTR, composition, size); // // Get the caret position from hIMC. // if (((int)lParam & NativeMethods.GCS_CURSORPOS) != 0) { cursorPos = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_CURSORPOS, IntPtr.Zero, 0); } // // Get the delta start position from hIMC. // if (((int)lParam & NativeMethods.GCS_DELTASTART) != 0) { deltaStart = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_DELTASTART, IntPtr.Zero, 0); } // // Get the clause information from hIMC. // if (((int)lParam & NativeMethods.GCS_COMPCLAUSE) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPCLAUSE, IntPtr.Zero, 0); if (size > 0) { clauseInfo = new int[size / Marshal.SizeOf(typeof(int))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPCLAUSE, clauseInfo, size); } } // // Get the attribute information from hIMC. // if (((int)lParam & NativeMethods.GCS_COMPATTR) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPATTR, IntPtr.Zero, 0); if (size > 0) { attributes = new byte[size / Marshal.SizeOf(typeof(byte))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPATTR, attributes, size); } } } } UpdateCompositionString(result, composition, cursorPos, deltaStart, clauseInfo, attributes); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); handled = true; } // // WM_IME_CHAR handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private void OnWmImeChar(IntPtr wParam, ref bool handled) { if (!IsInKeyboardFocus && !_losingFocus) { // Don't handle WM_IME_CAHR if we don't have a focus. return; } if (IsReadOnly) { // Don't handle WM_IME_CAHR if it is readonly. return; } if (_handlingImeMessage) { // We will be called reentrantly while completing compositions // in response to application listeners. In that case, don't // propegate events to listeners. return; } _handlingImeMessage = true; try { int resultLength; string compositionString = BuildCompositionString(null, new char[] { (char)wParam }, out resultLength); if (compositionString == null) { CompleteComposition(); } else { FrameworkTextComposition composition = TextStore.CreateComposition(_editor, this); _compositionModifiedByApp = false; _caretOffset = 1; // // Raise TextInputStart. // bool handledbyApp = RaiseTextInputStartEvent(composition, resultLength, compositionString); if (handledbyApp) { CompleteComposition(); } else { // // Raise TextInput. // bool handledByApp = RaiseTextInputEvent(composition, compositionString); if (handledByApp) { CompleteComposition(); goto Exit; } } } } finally { _handlingImeMessage = false; } // the string has been finalized. Update the reading window position for // legacy Chinese IMEs. if (IsReadingWindowIme()) { UpdateNearCaretCompositionWindow(); } Exit: handled = true; } // // WM_IME_NOTIFY handler // ////// Critical - accepts raw Win32 messages, calls unmanaged code, deals /// with unmanaged data structures /// [SecurityCritical] private void OnWmImeNotify(IntPtr hwnd, IntPtr wParam) { int convmode; int sentence; IntPtr himc; // we don't have to do anything if _editor is null. if (!IsInKeyboardFocus) { return; } switch ( (int) wParam) { case NativeMethods.IMN_OPENCANDIDATE: himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { NativeMethods.CANDIDATEFORM candform = new NativeMethods.CANDIDATEFORM(); // // At IMN_OPENCANDIDATE, we need to set the candidate window location to hIMC. // if (IsReadingWindowIme()) { // Level 2 for Chinese legacy IMEs. // We have already set the composition form. The candidate window will follow it. candform.dwIndex = 0; candform.dwStyle = NativeMethods.CFS_DEFAULT; candform.rcArea.left = 0; candform.rcArea.right = 0; candform.rcArea.top = 0; candform.rcArea.bottom = 0; candform.ptCurrentPos = new NativeMethods.POINT(0, 0); } else { ITextView view; ITextPointer startNavigator; ITextPointer endNavigator; ITextPointer caretNavigator; GeneralTransform transform; Point milPointTopLeft; Point milPointBottomRight; Point milPointCaret; Rect rectStart; Rect rectEnd; Rect rectCaret; CompositionTarget compositionTarget; compositionTarget = _source.CompositionTarget; if (_startComposition != null) { startNavigator = _startComposition.CreatePointer(); } else { startNavigator = _editor.Selection.Start.CreatePointer(); } if (_endComposition != null) { endNavigator = _endComposition.CreatePointer(); } else { endNavigator = _editor.Selection.End.CreatePointer(); } if (_startComposition != null) { caretNavigator = _caretOffset > 0 ? _startComposition.CreatePointer(_caretOffset, LogicalDirection.Forward) : _endComposition; } else { caretNavigator = _editor.Selection.End.CreatePointer(); } ITextPointer startPosition = startNavigator.CreatePointer(LogicalDirection.Forward); ITextPointer endPosition = endNavigator.CreatePointer(LogicalDirection.Backward); ITextPointer caretPosition = caretNavigator.CreatePointer(LogicalDirection.Forward); // We need to update the layout before getting rect. It could be dirty. if (!startPosition.ValidateLayout() || !endPosition.ValidateLayout() || !caretPosition.ValidateLayout()) { return; } view = TextEditor.GetTextView(RenderScope); rectStart = view.GetRectangleFromTextPosition(startPosition); rectEnd = view.GetRectangleFromTextPosition(endPosition); rectCaret = view.GetRectangleFromTextPosition(caretPosition); // Take the "extended" union of the first and last char's bounding box. milPointTopLeft = new Point(Math.Min(rectStart.Left, rectEnd.Left), Math.Min(rectStart.Top, rectEnd.Top)); milPointBottomRight = new Point(Math.Max(rectStart.Left, rectEnd.Left), Math.Max(rectStart.Bottom, rectEnd.Bottom)); milPointCaret = new Point(rectCaret.Left, rectCaret.Bottom); // Transform to root visual coordinates. transform = RenderScope.TransformToAncestor(compositionTarget.RootVisual); transform.TryTransform(milPointTopLeft, out milPointTopLeft); transform.TryTransform(milPointBottomRight, out milPointBottomRight); transform.TryTransform(milPointCaret, out milPointCaret); // Transform to device units. milPointTopLeft = compositionTarget.TransformToDevice.Transform(milPointTopLeft); milPointBottomRight = compositionTarget.TransformToDevice.Transform(milPointBottomRight); milPointCaret = compositionTarget.TransformToDevice.Transform(milPointCaret); // Build CANDIDATEFORM. CANDIDATEFORM is window coodidate. candform.dwIndex = 0; candform.dwStyle = NativeMethods.CFS_EXCLUDE; candform.rcArea.left = ConvertToInt32(milPointTopLeft.X); candform.rcArea.right = ConvertToInt32(milPointBottomRight.X); candform.rcArea.top = ConvertToInt32(milPointTopLeft.Y); candform.rcArea.bottom = ConvertToInt32(milPointBottomRight.Y); candform.ptCurrentPos = new NativeMethods.POINT(ConvertToInt32(milPointCaret.X), ConvertToInt32(milPointCaret.Y)); } // Call IMM32 to set new candidate position to hIMC. // ImmSetCandidateWindow fails when // - candform.dwIndex is invalid (over 4). // - himc belongs to other threads. // - fail to lock IMC. // Those cases are ignorable for us. // In addition, it does not set win32 last error and we have no clue to handle error. #pragma warning suppress 6031 UnsafeNativeMethods.ImmSetCandidateWindow(new HandleRef(this, himc), ref candform); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); } // We want to pass this message to DefWindowProc. // We don't update "handled". break; case NativeMethods.IMN_SETCONVERSIONMODE: // The Conversion Mode has been changed by IMM32 API. We need to update InputMethod.ImeConversionMode property. ImeConversionModeValues imeConversionMode = 0; convmode = 0; sentence = 0; himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); // IME_CMODE_ALPHANUMERIC is 0. if ((convmode & (NativeMethods.IME_CMODE_NATIVE | NativeMethods.IME_CMODE_KATAKANA)) == 0) imeConversionMode |= ImeConversionModeValues.Alphanumeric; if ((convmode & NativeMethods.IME_CMODE_NATIVE) != 0) imeConversionMode |= ImeConversionModeValues.Native; if ((convmode & NativeMethods.IME_CMODE_KATAKANA) != 0) imeConversionMode |= ImeConversionModeValues.Katakana; if ((convmode & NativeMethods.IME_CMODE_FULLSHAPE) != 0) imeConversionMode |= ImeConversionModeValues.FullShape; if ((convmode & NativeMethods.IME_CMODE_ROMAN) != 0) imeConversionMode |= ImeConversionModeValues.Roman; if ((convmode & NativeMethods.IME_CMODE_CHARCODE) != 0) imeConversionMode |= ImeConversionModeValues.CharCode; if ((convmode & NativeMethods.IME_CMODE_NOCONVERSION) != 0) imeConversionMode |= ImeConversionModeValues.NoConversion; if ((convmode & NativeMethods.IME_CMODE_EUDC) != 0) imeConversionMode |= ImeConversionModeValues.Eudc; if ((convmode & NativeMethods.IME_CMODE_SYMBOL) != 0) imeConversionMode |= ImeConversionModeValues.Symbol; if ((convmode & NativeMethods.IME_CMODE_FIXED) != 0) imeConversionMode |= ImeConversionModeValues.Fixed; InputMethod.Current.ImeConversionMode = imeConversionMode; } break; case NativeMethods.IMN_SETSENTENCEMODE: // The Sentence Mode has been changed by IMM32 API. We need to update InputMethod.ImeSentenceMode property. ImeSentenceModeValues imeSentenceMode = 0; convmode = 0; sentence = 0; himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); // TF_SENTENCEMODE_ALPHANUMERIC is 0. if (sentence == NativeMethods.IME_SMODE_NONE) { imeSentenceMode = ImeSentenceModeValues.None; } else { if ((sentence & NativeMethods.IME_SMODE_PLAURALCLAUSE) != 0) imeSentenceMode |= ImeSentenceModeValues.PluralClause; if ((sentence & NativeMethods.IME_SMODE_SINGLECONVERT) != 0) imeSentenceMode |= ImeSentenceModeValues.SingleConversion; if ((sentence & NativeMethods.IME_SMODE_AUTOMATIC) != 0) imeSentenceMode |= ImeSentenceModeValues.Automatic; if ((sentence & NativeMethods.IME_SMODE_PHRASEPREDICT) != 0) imeSentenceMode |= ImeSentenceModeValues.PhrasePrediction; if ((sentence & NativeMethods.IME_SMODE_CONVERSATION) != 0) imeSentenceMode |= ImeSentenceModeValues.Conversation; } InputMethod.Current.ImeSentenceMode = imeSentenceMode; } break; case NativeMethods.IMN_SETOPENSTATUS: // The open status has been changed by IMM32 API. We need to update InputMethod.ImeState property. himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { bool fOpen = UnsafeNativeMethods.ImmGetOpenStatus(new HandleRef(this, himc)); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); InputMethod.Current.ImeState = fOpen ? InputMethodState.On : InputMethodState.Off; } break; } } // // Use Level 2 for Chinese IME // ////// Critical - elevates to access protected resources (hwnd) /// TreatAsSafe - positions the composition window, which is safe to do /// [SecurityCritical, SecurityTreatAsSafe] private void UpdateNearCaretCompositionWindow() { ITextView view; Rect rectUi; GeneralTransform transform; Point milPointTopLeft; Point milPointBottomRight; Point milPointCaret; Rect rectCaret; CompositionTarget compositionTarget; IntPtr hwnd; if (!IsInKeyboardFocus) { return; } if (_source == null) { return; } // get hwnd from _source. new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { hwnd = ((IWin32Window)_source).Handle; } finally { CodeAccessPermission.RevertAssert(); } rectUi = UiScope.VisualContentBounds; view = _editor.TextView; // // // During incremental layout update, the region of the view covered by // the selection may not be ready yet. if (!_editor.Selection.End.HasValidLayout) { _updateCompWndPosAtNextLayoutUpdate = true; return; } compositionTarget = _source.CompositionTarget; // HwndSource.CompositionTarget may return null if the target hwnd is being destroyed and disposed. if (compositionTarget == null) { return; } // If the mouse click happens before rendering, the seleciton move notification is generated. // However the visual tree is not completely connected yet. We need to check it. if (!compositionTarget.RootVisual.IsAncestorOf(RenderScope)) { return; } IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { rectCaret = view.GetRectangleFromTextPosition(_editor.Selection.End.CreatePointer(LogicalDirection.Backward)); // Take the points of the renderScope. milPointTopLeft = new Point(rectUi.Left, rectUi.Top); milPointBottomRight = new Point(rectUi.Right, rectUi.Bottom); // Take the "extended" union of the first and last char's bounding box. // milPointCaret = new Point(rectCaret.Left, rectCaret.Top); milPointCaret = new Point(rectCaret.Left, rectCaret.Bottom); // Transform to root visual coordinates. transform = RenderScope.TransformToAncestor(compositionTarget.RootVisual); transform.TryTransform(milPointTopLeft, out milPointTopLeft); transform.TryTransform(milPointBottomRight, out milPointBottomRight); transform.TryTransform(milPointCaret, out milPointCaret); // Transform to device units. milPointTopLeft = compositionTarget.TransformToDevice.Transform(milPointTopLeft); milPointBottomRight = compositionTarget.TransformToDevice.Transform(milPointBottomRight); milPointCaret = compositionTarget.TransformToDevice.Transform(milPointCaret); // Build COMPOSITIONFORM. COMPOSITIONFORM is window coodidate. NativeMethods.COMPOSITIONFORM compform = new NativeMethods.COMPOSITIONFORM(); compform.dwStyle = NativeMethods.CFS_RECT; compform.rcArea.left = ConvertToInt32(milPointTopLeft.X); compform.rcArea.right = ConvertToInt32(milPointBottomRight.X); compform.rcArea.top = ConvertToInt32(milPointTopLeft.Y); compform.rcArea.bottom = ConvertToInt32(milPointBottomRight.Y); compform.ptCurrentPos = new NativeMethods.POINT(ConvertToInt32(milPointCaret.X), ConvertToInt32(milPointCaret.Y)); // Call IMM32 to set new candidate position to hIMC. // ImmSetCompositionWindow fails when // - himc belongs to other threads. // - fail to lock IMC. // Those cases are ignorable for us. // In addition, it does not set win32 last error and we have no clue to handle error. UnsafeNativeMethods.ImmSetCompositionWindow(new HandleRef(this, himc), ref compform); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); } } // // Hwnd disposed callback. // ////// Critical - unparents the composition window visually /// TreatAsSafe - while a DOS, this doesn't present any specific security threat. /// [SecurityCritical, SecurityTreatAsSafe] private void OnHwndDisposed(object sender, EventArgs args) { UpdateSource(_source, null); } // // update the composition string on the scope // private void UpdateCompositionString(char[] resultChars, char[] compositionChars, int caretOffset, int deltaStart, int[] clauseInfo, byte[] attributes) { if (_handlingImeMessage) { // We will be called reentrantly while completing compositions // in response to application listeners. In that case, don't // propegate events to listeners. return; } _handlingImeMessage = true; try { // // Remove any existing composition adorner for display attribute. // if (_compositionAdorner != null) { _compositionAdorner.Uninitialize(); _compositionAdorner = null; } // // Build up an array of resultChars + compositionChars -- the complete span of changing text. // int resultLength; string compositionString = BuildCompositionString(resultChars, compositionChars, out resultLength); if (compositionString == null) { CompleteComposition(); return; } // // Remember where the IME placed the caret. // RecordCaretOffset(caretOffset, attributes, compositionString.Length); FrameworkTextComposition composition = TextStore.CreateComposition(_editor, this); _compositionModifiedByApp = false; if (_startComposition == null) { Invariant.Assert(_endComposition == null); // // Raise TextInputStart. // bool handledbyApp = RaiseTextInputStartEvent(composition, resultLength, compositionString); if (handledbyApp) { CompleteComposition(); return; } } else if (compositionChars != null) { // // Raise TextInputUpdate. // bool handledByApp = RaiseTextInputUpdateEvent(composition, resultLength, compositionString); if (handledByApp) { CompleteComposition(); return; } } if (compositionChars == null) { // // Raise TextInput. // bool handledByApp = RaiseTextInputEvent(composition, compositionString); if (handledByApp) { CompleteComposition(); return; } } if (_startComposition != null) { SetCompositionAdorner(clauseInfo, attributes); } } finally { _handlingImeMessage = false; } } // Attempts to build a string containing zero or more result chars // followed by zero or more composition chars. // // Will return null if an underlying TextBox has MaxLength property // that is exceeded by the new content. private string BuildCompositionString(char[] resultChars, char[] compositionChars, out int resultLength) { int compositionLength = compositionChars == null ? 0 : compositionChars.Length; resultLength = resultChars == null ? 0 : resultChars.Length; char[] compositionText; if (resultChars == null) { compositionText = compositionChars; } else if (compositionChars == null) { compositionText = resultChars; } else { compositionText = new char[resultLength + compositionLength]; Array.Copy(resultChars, 0, compositionText, 0, resultLength); Array.Copy(compositionChars, 0, compositionText, resultLength, compositionLength); } string compositionString = new string(compositionText); if (!_editor.AcceptsRichContent) { int charsToReplaceCount; if (_startComposition == null) { charsToReplaceCount = _editor.Selection.Start.GetOffsetToPosition(_editor.Selection.End); } else { charsToReplaceCount = _startComposition.GetOffsetToPosition(_endComposition); } compositionString = _editor._FilterText(compositionString, charsToReplaceCount); } int originalLength = (compositionText == null) ? 0 : compositionText.Length; return (compositionString.Length == originalLength) ? compositionString : null; } // Caches the IME specified caret offset. // Value is the offset in unicode code points from the composition start. private void RecordCaretOffset(int caretOffset, byte[] attributes, int compositionLength) { // Use the suggested value if it is on ATTR_INPUT, otherwise set the caret at the end of // composition string. So it always stays where the new char is inserted. if ((attributes != null) && // If the next char of the cursorPos is INPUTATTR. (((caretOffset >= 0) && (caretOffset < attributes.Length) && (attributes[caretOffset] == NativeMethods.ATTR_INPUT)) || // If the prev char os the cursorPos is INPUTATTR. ((caretOffset > 0) && ((caretOffset - 1) < attributes.Length) && (attributes[caretOffset - 1] == NativeMethods.ATTR_INPUT)))) { _caretOffset = caretOffset; } else { _caretOffset = -1; } } // Raises a public TextInputStart event. // Returns true if a listener handles the event or modifies document state. ////// Critical - calls critical (TextCompositionManager) code. /// TreatAsSafe - doesn't accept or return critical information. /// [SecurityCritical, SecurityTreatAsSafe] private bool RaiseTextInputStartEvent(FrameworkTextComposition composition, int resultLength, string compositionString) { composition.Stage = TextCompositionStage.None; composition.SetCompositionPositions(_editor.Selection.Start, _editor.Selection.End, compositionString); // PUBLIC event: bool handled = TextCompositionManager.StartComposition(composition); if (handled || composition.PendingComplete || _compositionModifiedByApp) { return true; } // UpdateCompositionText raises a PUBLIC EVENT.... UpdateCompositionText(composition, resultLength, true /* includeResultText */, out _startComposition, out _endComposition); if (_compositionModifiedByApp) { return true; } RegisterMouseListeners(); return false; } // Raises a public TextInputUpdate event. // Returns true if a listener handles the event or modifies document state. ////// Critical - calls critical (TextCompositionManager) code. /// TreatAsSafe - doesn't accept or return critical information. /// [SecurityCritical, SecurityTreatAsSafe] private bool RaiseTextInputUpdateEvent(FrameworkTextComposition composition, int resultLength, string compositionString) { composition.Stage = TextCompositionStage.Started; composition.SetCompositionPositions(_startComposition, _endComposition, compositionString); // PUBLIC event: bool handled = TextCompositionManager.UpdateComposition(composition); if (handled || composition.PendingComplete || _compositionModifiedByApp) { return true; } // UpdateCompositionText raises a PUBLIC EVENT.... UpdateCompositionText(composition, resultLength, false /* includeResultText */, out _startComposition, out _endComposition); if (_compositionModifiedByApp) { return true; } return false; } // Raises a public TextInput event. // Returns true if a listener handles the event or modifies document state. ////// Critical - calls critical (TextCompositionManager) code. /// TreatAsSafe - doesn't accept or return critical information. /// [SecurityCritical, SecurityTreatAsSafe] private bool RaiseTextInputEvent(FrameworkTextComposition composition, string compositionString) { composition.Stage = TextCompositionStage.Started; composition.SetResultPositions(_startComposition, _endComposition, compositionString); _startComposition = null; _endComposition = null; UnregisterMouseListeners(); _handledByEditorListener = false; // PUBLIC event: TextCompositionManager.CompleteComposition(composition); _compositionUndoUnit = null; return (!_handledByEditorListener || composition.PendingComplete || _compositionModifiedByApp); } // Inserts composition text into the document. // Raises public text, selection changed events. // Called by default editor TextInputEvent handler. internal void UpdateCompositionText(FrameworkTextComposition composition) { ITextPointer start; ITextPointer end; UpdateCompositionText(composition, 0, true /* includeResultText */ , out start, out end); } // Inserts composition text into the document. // Raises public text, selection changed events. // Returns the position of the inserted text. If includeResultText is // true, start/end will cover all the inserted text. Otherwise, text // from offset 0 to resultLength is omitted from start/end. internal void UpdateCompositionText(FrameworkTextComposition composition, int resultLength, bool includeResultText, out ITextPointer start, out ITextPointer end) { start = null; end = null; if (_compositionModifiedByApp) { // If the app has modified the document since this event was raised // (by hooking a TextInput event), then we don't know what to do, // so do nothing. return; } _handledByEditorListener = true; UndoCloseAction undoCloseAction = UndoCloseAction.Rollback; OpenCompositionUndoUnit(); try { _editor.Selection.BeginChange(); try { // ITextRange range; string text; if (composition._ResultStart != null) { range = new TextRange(composition._ResultStart, composition._ResultEnd); text = composition.Text; } else { range = new TextRange(composition._CompositionStart, composition._CompositionEnd); text = composition.CompositionText; } _editor.SetText(range, text, InputLanguageManager.Current.CurrentInputLanguage); if (includeResultText) { start = range.Start; } else { start = range.Start.CreatePointer(resultLength, LogicalDirection.Forward); } end = range.End; ITextPointer caretPosition = _caretOffset >= 0 ? start.CreatePointer(_caretOffset, LogicalDirection.Forward) : end; _editor.Selection.Select(caretPosition, caretPosition); } finally { // We're about to raise the public event. // Set a flag so we can detect app changes. _compositionModifiedByApp = false; _editor.Selection.EndChange(); } undoCloseAction = UndoCloseAction.Commit; } finally { CloseCompositionUndoUnit(undoCloseAction, end); } } // Decorates the composition with IME specified underlining. private void SetCompositionAdorner(int[] clauseInfo, byte[] attributes) { if ((clauseInfo != null) && (attributes != null)) { for (int i = 0; i < clauseInfo.Length - 1; i++) { ITextPointer startClause = _startComposition.CreatePointer(clauseInfo[i], LogicalDirection.Backward); ITextPointer endClause = _startComposition.CreatePointer(clauseInfo[i + 1], LogicalDirection.Forward); if (_compositionAdorner == null) { _compositionAdorner = new CompositionAdorner(_editor.TextView); _compositionAdorner.Initialize(_editor.TextView); } // // UnsafeNativeMethods.TF_DISPLAYATTRIBUTE displayAttribute = new UnsafeNativeMethods.TF_DISPLAYATTRIBUTE(); displayAttribute.crLine.type = UnsafeNativeMethods.TF_DA_COLORTYPE.TF_CT_COLORREF; displayAttribute.crLine.indexOrColorRef = 0; displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_NONE; displayAttribute.fBoldLine = false; switch (attributes[clauseInfo[i]]) { case NativeMethods.ATTR_INPUT: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_DOT; break; case NativeMethods.ATTR_TARGET_CONVERTED: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID; displayAttribute.fBoldLine = true; break; case NativeMethods.ATTR_CONVERTED: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID; break; case NativeMethods.ATTR_TARGET_NOTCONVERTED: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID; break; case NativeMethods.ATTR_INPUT_ERROR: break; case NativeMethods.ATTR_FIXEDCONVERTED: break; } #if UNUSED_IME_HIGHLIGHT_LAYER // Demand create the highlight layer. if (_highlightLayer == null) { _highlightLayer = new DisplayAttributeHighlightLayer(); } // ToDo: Need to pass the foreground and background color of the composition _highlightLayer.Add(startClause, endClause, /*TextDecorationCollection:*/null); #endif TextServicesDisplayAttribute textServiceDisplayAttribute = new TextServicesDisplayAttribute(displayAttribute); // Add the attribute range into CompositionAdorner. _compositionAdorner.AddAttributeRange(startClause, endClause, textServiceDisplayAttribute); } #if UNUSED_IME_HIGHLIGHT_LAYER if (_highlightLayer != null) { _editor.TextContainer.Highlights.AddLayer(_highlightLayer); } #endif if (_compositionAdorner != null) { // Update the layout to get the acurated rectangle from calling GetRectangleFromTextPosition _editor.TextView.RenderScope.UpdateLayout(); // Invalidate the composition adorner to apply the composition attribute ranges. _compositionAdorner.InvalidateAdorner(); } } } // Start listening mouse event for MSIME mouse operation. private void RegisterMouseListeners() { UiScope.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonDown += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonUp += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseMove += new MouseEventHandler(OnMouseEvent); } // Stop listening mouse event for MSIME mouse operation. private void UnregisterMouseListeners() { if (this.UiScope != null) { UiScope.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseLeftButtonUp -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonDown -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonUp -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseMove -= new MouseEventHandler(OnMouseEvent); } } // // WM_IME_REQUEST handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private IntPtr OnWmImeRequest(IntPtr wParam, IntPtr lParam, ref bool handled) { IntPtr lret = IntPtr.Zero ; switch ( (int) wParam) { case NativeMethods.IMR_RECONVERTSTRING: lret = OnWmImeRequest_ReconvertString(lParam, ref handled, false); break; case NativeMethods.IMR_CONFIRMRECONVERTSTRING: lret = OnWmImeRequest_ConfirmReconvertString(lParam, ref handled); break; case NativeMethods.IMR_QUERYCHARPOSITION: break; case NativeMethods.IMR_DOCUMENTFEED: lret = OnWmImeRequest_ReconvertString(lParam, ref handled, true); break; } return lret; } // // WM_IME_REQUEST/IMR_RECONVERTSTRING handler // ////// Crtical: This code calls into PtrToStruct which is marked critical.It can also be used to spoof input /// [SecurityCritical] private IntPtr OnWmImeRequest_ReconvertString(IntPtr lParam, ref bool handled, bool fDocFeed) { if (!fDocFeed) { _isReconvReady = false; } if (!IsInKeyboardFocus) { return IntPtr.Zero ; } ITextRange range; // // If there is the composition string, we use it. Otherwise we use the current selection. // if (fDocFeed && (_startComposition != null) && (_endComposition != null)) { range = new TextRange(_startComposition, _endComposition); } else { range = _editor.Selection; } string target = range.Text; int requestSize = Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)) + (target.Length * Marshal.SizeOf(typeof(short))) + ((_maxSrounding + 1) * Marshal.SizeOf(typeof(short)) * 2); IntPtr lret = new IntPtr( requestSize); if (lParam != IntPtr.Zero) { int offsetStart; string surrounding = GetSurroundingText(range, out offsetStart); // Create RECONVERTSTRING structure from lParam. NativeMethods.RECONVERTSTRING reconv = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING)); reconv.dwSize = requestSize; reconv.dwVersion = 0; // must be 0 reconv.dwStrLen = surrounding.Length; // in char count reconv.dwStrOffset = Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)); // in byte count reconv.dwCompStrLen = target.Length; // in char count reconv.dwCompStrOffset = offsetStart * Marshal.SizeOf(typeof(short)); // in byte count reconv.dwTargetStrLen = target.Length; // in char count reconv.dwTargetStrOffset = offsetStart * Marshal.SizeOf(typeof(short)); // in byte count if (!fDocFeed) { // // If this is IMR_RECONVERTSTRING, we cache it. So we can refer it later when we get // IMR_CONFIRMRECONVERTSTRING message. // _reconv = reconv; _isReconvReady = true; } // Copy the strucuture back to lParam. Marshal.StructureToPtr(reconv, lParam, true); StoreSurroundingText(lParam, surrounding); } handled = true; return lret; } ////// Crtical: unsafe code to manipulate pointer. /// This should not be called unless we're sure the first param is the pointer to /// NativeMethods.RECONVERTSTRING. /// [SecurityCritical] private unsafe static void StoreSurroundingText(IntPtr reconv, string surrounding) { // Copy the string to the pointer right after the structure. byte *p = (byte *)reconv.ToPointer(); p += Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)); Marshal.Copy(surrounding.ToCharArray(), 0, new IntPtr((void *)p), surrounding.Length); } // // Get the surrounding text of the given range. // The offsetStart is out param to return the offset of the given range in the returned surrounding text. // private static string GetSurroundingText(ITextRange range, out int offsetStart) { ITextPointer navigator; bool done; string surrounding = ""; int bufLength; // // Get the previous text of the given range. // navigator = range.Start.CreatePointer(); done = false; bufLength = _maxSrounding; while (!done && (bufLength > 0)) { switch (navigator.GetPointerContext(LogicalDirection.Backward)) { case TextPointerContext.Text: char[] buffer = new char[bufLength]; int copied = ((TextPointer)navigator).GetTextInRun(LogicalDirection.Backward, buffer, 0, buffer.Length); Invariant.Assert(copied != 0); navigator.MoveByOffset(0 - copied); bufLength -= copied; surrounding = surrounding.Insert(0, new string(buffer, 0, copied)); break; case TextPointerContext.EmbeddedElement: done = true; break; case TextPointerContext.ElementStart: case TextPointerContext.ElementEnd: // ignore the inline element. if (!navigator.GetElementType(LogicalDirection.Backward).IsSubclassOf(typeof(Inline))) { done = true; } navigator.MoveToNextContextPosition(LogicalDirection.Backward); break; case TextPointerContext.None: done = true; break; default: navigator.MoveToNextContextPosition(LogicalDirection.Backward); break; } } // offsetStart is the amount of the current surroundingText. offsetStart = surrounding.Length; // // add the text in the given range. // surrounding += range.Text; // // Get the following text of the given range. // navigator = range.End.CreatePointer(); done = false; bufLength = _maxSrounding; while (!done && (bufLength > 0)) { switch (navigator.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.Text: char[] buffer = new char[bufLength]; int copied = ((TextPointer)navigator).GetTextInRun(LogicalDirection.Forward, buffer, 0, buffer.Length); navigator.MoveByOffset(copied); bufLength -= copied; surrounding += new string(buffer, 0, copied); break; case TextPointerContext.EmbeddedElement: done = true; break; case TextPointerContext.ElementStart: case TextPointerContext.ElementEnd: // ignore the inline element. if (!navigator.GetElementType(LogicalDirection.Forward).IsSubclassOf(typeof(Inline))) { done = true; } navigator.MoveToNextContextPosition(LogicalDirection.Forward); break; case TextPointerContext.None: done = true; break; default: navigator.MoveToNextContextPosition(LogicalDirection.Forward); break; } } return surrounding; } // // WM_IME_REQUEST/IMR_CONFIRMRECONVERTSTRING handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private IntPtr OnWmImeRequest_ConfirmReconvertString(IntPtr lParam, ref bool handled) { if (!IsInKeyboardFocus) { return IntPtr.Zero ; } if (!_isReconvReady) { return IntPtr.Zero ; } NativeMethods.RECONVERTSTRING reconv = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING)); // If the entire string in RECONVERTSTRING has been changed, we don't handle it. if (_reconv.dwStrLen != reconv.dwStrLen) { handled = true; return IntPtr.Zero ; } // // If the new CompStr was suggested by IME, we need to adjust the selection with it. // if ((_reconv.dwCompStrLen != reconv.dwCompStrLen) || (_reconv.dwCompStrOffset != reconv.dwCompStrOffset)) { ITextRange range = _editor.Selection; // // Create the start point from the selection // ITextPointer start = range.Start.CreatePointer(LogicalDirection.Backward); // Move the start point to new dwCompStrOffset. start = MoveToNextCharPos(start, (reconv.dwCompStrOffset - _reconv.dwCompStrOffset) / Marshal.SizeOf(typeof(short))); // Create the end position and move this as dwCompStrLen. ITextPointer end = start.CreatePointer(LogicalDirection.Forward); end = MoveToNextCharPos(end, reconv.dwCompStrLen); // Update the selection with new start and end. _editor.Selection.Select(start, end); } _isReconvReady = false; handled = true; return new IntPtr( 1 ) ; } // // Move the TextPointer by offset in char count. // private static ITextPointer MoveToNextCharPos(ITextPointer position, int offset) { bool done = false; if (offset < 0) { while ((offset < 0) && !done) { switch (position.GetPointerContext(LogicalDirection.Backward)) { case TextPointerContext.Text: offset++; break; case TextPointerContext.None: done = true; break; } position.MoveByOffset(-1); } } else if (offset > 0) { while ((offset > 0) && !done) { switch (position.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.Text: offset--; break; case TextPointerContext.None: done = true; break; } position.MoveByOffset(1); } } return position; } // // Move the TextPointer by offset in char count. // ////// Critical - calls ImmGetProperty that could expose the current ime's capability. /// TreatAsSafe - the return value says only if IME is near caret Chinese IME. /// exposing this information is safe. /// [SecurityCritical, SecurityTreatAsSafe] private bool IsReadingWindowIme() { int prop = UnsafeNativeMethods.ImmGetProperty(new HandleRef(this, SafeNativeMethods.GetKeyboardLayout(0)), NativeMethods.IGP_PROPERTY); return (((prop & NativeMethods.IME_PROP_AT_CARET) == 0) || ((prop & NativeMethods.IME_PROP_SPECIAL_UI) != 0)); } // // Mouse Button state was changed. // ////// Critical - calls critical code (InternalMouseEventHandler) /// TreatAsSafe - the mouse input here is ignored, but rather the mouse /// is directly queried, so there is no spoofing possibility /// [SecurityCritical, SecurityTreatAsSafe] private void OnMouseButtonEvent(object sender, MouseButtonEventArgs e) { e.Handled = InternalMouseEventHandler(); } // // Mouse was moved. // ////// Critical - calls critical code (InternalMouseEventHandler) /// TreatAsSafe - the mouse input here is ignored, but rather the mouse /// is directly queried, so there is no spoofing possibility /// [SecurityCritical, SecurityTreatAsSafe] private void OnMouseEvent(object sender, MouseEventArgs e) { e.Handled = InternalMouseEventHandler(); } // // The mouse event handler to generate MSIME message to IME listeners. // ////// Critical - calls unmanaged code, sends WM_* messages, simulates mouse /// messages to the IME. This could result in input spoofing. /// [SecurityCritical] private bool InternalMouseEventHandler() { int btnState = 0; if (Mouse.LeftButton == MouseButtonState.Pressed) { btnState = 1; // IMEMOUSE_LDOWN } if (Mouse.RightButton == MouseButtonState.Pressed) { btnState = 2; // IMEMOUSE_RDOWN } Point point = Mouse.GetPosition(RenderScope); ITextView view; ITextPointer positionCurrent; ITextPointer positionNext; Rect rectCurrent; Rect rectNext; view = TextEditor.GetTextView(RenderScope); // Validate layout information on TextView if (!view.Validate(point)) { return false; } // Do the hittest. positionCurrent = view.GetTextPositionFromPoint(point, false); if (positionCurrent == null) { return false; } rectCurrent = view.GetRectangleFromTextPosition(positionCurrent); positionNext = positionCurrent.CreatePointer(); if (positionNext == null) { return false; } if (point.X - rectCurrent.Left >= 0) { positionNext.MoveToNextInsertionPosition(LogicalDirection.Forward); } else { positionNext.MoveToNextInsertionPosition(LogicalDirection.Backward); } rectNext = view.GetRectangleFromTextPosition(positionNext); int edge; int quadrant; edge = _editor.TextContainer.Start.GetOffsetToPosition(positionCurrent); int startComposition = _editor.TextContainer.Start.GetOffsetToPosition(_startComposition); int endComposition = _editor.TextContainer.Start.GetOffsetToPosition(_endComposition); // // IMEs care about only the composition string range. // if (edge < startComposition) { return false; } if (edge > endComposition) { return false; } if (rectNext.Left == rectCurrent.Left) { // if rectNext.Left == rectCurrent.Left, the width of char is 0 and mouse click points there. // there is no quadrent. So we alwasys make it 0. quadrant = 0; } else { if (point.X - rectCurrent.Left >= 0) { if ((((point.X - rectCurrent.Left) * 4) / (rectNext.Left - rectCurrent.Left)) <= 1) quadrant = 2; else quadrant = 3; } else { if (((point.X - rectNext.Left) * 4) / (rectCurrent.Left - rectNext.Left) <= 3) quadrant = 0; else quadrant = 1; } } // // IMEs care about only the composition string range. // If the quadrant is outside of the range, we don't do SendMessage. // if ((edge == startComposition) && (quadrant <= 1)) { return false; } if ((edge == endComposition) && (quadrant >= 2)) { return false; } // // The edge must be relative to the composition string. // edge -= startComposition; int wParam = (edge << 16) + (quadrant << 8) + btnState; IntPtr hwnd = IntPtr.Zero; new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { hwnd = ((IWin32Window)_source).Handle; } finally { CodeAccessPermission.RevertAssert(); } IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); IntPtr lret = IntPtr.Zero; if (himc != IntPtr.Zero) { IntPtr hwndDefIme = IntPtr.Zero; new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();//Blessed Assert try { hwndDefIme = UnsafeNativeMethods.ImmGetDefaultIMEWnd(new HandleRef(this, hwnd)); lret = UnsafeNativeMethods.SendMessage(hwndDefIme, s_MsImeMouseMessage, new IntPtr(wParam), himc); } finally { SecurityPermission.RevertAssert(); } } // We eat this event if IME handled. return (lret != IntPtr.Zero) ? true : false; } // Opens a composition undo unit. Opens the compsed composition undo unit if it exist on the top // of the stack. Otherwise, create new composition undo unit and add it to the undo manager and // making it as the opened undo unit. private void OpenCompositionUndoUnit() { UndoManager undoManager; DependencyObject parent; parent = _editor.TextContainer.Parent; undoManager = UndoManager.GetUndoManager(parent); if (undoManager != null && undoManager.IsEnabled && undoManager.OpenedUnit == null) { if (_compositionUndoUnit != null && _compositionUndoUnit == undoManager.LastUnit && !_compositionUndoUnit.Locked) { // Opens a closed composition undo unit on the top of the stack. undoManager.Reopen(_compositionUndoUnit); } else { _compositionUndoUnit = new TextParentUndoUnit(_editor.Selection); // Add the given composition undo unit to the undo manager and making it // as the opened undo unit. undoManager.Open(_compositionUndoUnit); } } else { _compositionUndoUnit = null; } } // Closes an opened composition unit and adding it to the containing unit's undo stack. private void CloseCompositionUndoUnit(UndoCloseAction undoCloseAction, ITextPointer compositionEnd) { UndoManager undoManager; DependencyObject parent; parent = _editor.TextContainer.Parent; undoManager = UndoManager.GetUndoManager(parent); if (undoManager != null && undoManager.IsEnabled) { if (_compositionUndoUnit != null) { // Closes an opened composition unit and commit it to add the composition // undo unit into the containing unit's undo stack. if (undoCloseAction == UndoCloseAction.Commit) { _compositionUndoUnit.RecordRedoSelectionState(compositionEnd, compositionEnd); } undoManager.Close(_compositionUndoUnit, undoCloseAction); } } else { _compositionUndoUnit = null; } } // Converts a double into a 32 bit integer, truncating values that // exceed Int32.MinValue or Int32.MaxValue. private int ConvertToInt32(double value) { int i; if (Double.IsNaN(value)) { // (int)value is 0x80000000. So we should assign Int32.MinValue. i = Int32.MinValue; } else if (value < Int32.MinValue) { i = Int32.MinValue; } else if (value > Int32.MaxValue) { i = Int32.MaxValue; } else { i = Convert.ToInt32(value); } return i; } private void OnTextContainerChange(object sender, TextContainerChangeEventArgs args) { if (args.IMECharCount > 0 && (args.TextChange == TextChangeType.ContentAdded || args.TextChange == TextChangeType.ContentRemoved)) { _compositionModifiedByApp = true; } } //------------------------------------------------------ // // Private Properties // //----------------------------------------------------- private UIElement RenderScope { get { return _editor.TextView == null ? null : _editor.TextView.RenderScope; } } private FrameworkElement UiScope { get { return (_editor == null) ? null : _editor.UiScope; } } private bool IsReadOnly { get { return ((bool)UiScope.GetValue(TextEditor.IsReadOnlyProperty) || _editor.IsReadOnly); } } private bool IsInKeyboardFocus { get { if (_editor == null) { return false; } if (UiScope == null) { return false; } if (!UiScope.IsKeyboardFocused) { return false; } return true; } } //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ #region Private Fields // // HwndSource of this instance of ImmComposition. // [SecurityCritical] private HwndSource _source; // // TextEditor of the current focus element. // private TextEditor _editor; // // The current start position of the compositon string. // This is null if the composition string does not exist. // private ITextPointer _startComposition; // // The current end position of the compositon string. // This is null if the composition string does not exist. // private ITextPointer _endComposition; // // The offset in chars from the start of the composition to the IME caret. // private int _caretOffset; #if UNUSED_IME_HIGHLIGHT_LAYER // // Highlight layer forLevel3 composition drawing. // private DisplayAttributeHighlightLayer _highlightLayer; #endif // // CompositionAdorner for displaying the composition attributes. // private CompositionAdorner _compositionAdorner; // // List of ImmComposition instances. // private static Hashtable _list = new Hashtable(1); // // Dash length of the compositon string underline. // private const double _dashLength = 2.0; // // Max surrounding char count for RECONVERTSTRING/DOCFEED. // private const int _maxSrounding = 0x20; // // Cached RECONVERTSTRING structure for IMR_CONFIRMRECONVERTSTRING message handling. // private NativeMethods.RECONVERTSTRING _reconv; // // True if the cached RECONVERTSTRING structure is ready. // private bool _isReconvReady; // // MSIME mouse operation message. // ////// Critical: This code registers a custom message and calls into a critical method /// [SecurityCritical] private static int s_MsImeMouseMessage = UnsafeNativeMethods.RegisterWindowMessage("MSIMEMouseOperation"); // This is the composition undo unit. private TextParentUndoUnit _compositionUndoUnit; // Reentry flag, set true while handling WM_IME_UPDATE, WM_IME_CHAR. private bool _handlingImeMessage; // If this is true, call UpdateNearCaretCompositionWindow() at the next layout update. private bool _updateCompWndPosAtNextLayoutUpdate; // Set true if an application listener modified the document content // or selection inside a TextInput* event. private bool _compositionModifiedByApp; // Flag set true when TextInput events are handled by the default // TextEditor listener -- not intercepted by an application listener. private bool _handledByEditorListener; // Set true while completing a composition from OnLostFocus. private bool _losingFocus; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // // Description: // This class handles IMM32 IME's composition string and support level 3 input to TextBox and RichTextBox. // // History: // 11/09/2004 : yutakas - created // //--------------------------------------------------------------------------- using System; using System.Runtime.InteropServices; using System.Threading; using System.Collections; using System.Diagnostics; using System.Windows.Media; using System.Windows.Input; using System.Windows.Documents; using System.Windows.Interop; using System.Windows.Threading; using System.Security; using System.Security.Permissions; using System.Text; using MS.Win32; using MS.Internal.Documents; using MS.Internal.PresentationFramework; using MS.Internal; // Enable presharp pragma warning suppress directives. #pragma warning disable 1634, 1691 namespace System.Windows.Documents { //----------------------------------------------------- // // ImmComposition class // //----------------------------------------------------- // // This class handles IMM32 IME's composition string and // support level 3 input to TextBox and RichTextBox. // internal class ImmComposition { //------------------------------------------------------ // // Constructors // //----------------------------------------------------- #region Constructors // // Creates a new ImmComposition instance. // ////// Critical - This class exists purely to prevent the security exceptions from /// percolating /// TreatAsSafe: Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] static ImmComposition() { } // // Creates a new ImmComposition instance. // ////// Critical - calls critical code (UpdateSource) /// [SecurityCritical] internal ImmComposition(HwndSource source) { UpdateSource(null, source); } #endregion Constructors //------------------------------------------------------ // // Internal Method // //------------------------------------------------------ // // Create an instance of ImmComposition per source window. // ////// Critical - gets HwndSource (protected), then creates a new /// composition based on this. /// [SecurityCritical] internal static ImmComposition GetImmComposition(FrameworkElement scope) { HwndSource source = PresentationSource.CriticalFromVisual(scope) as HwndSource; ImmComposition immComposition = null; if (source != null) { lock (_list) { immComposition = (ImmComposition)_list[source]; if (immComposition == null) { immComposition = new ImmComposition(source); _list[source] = immComposition; } } } return immComposition; } // // This is called when TextEditor is detached. // We need to remove event handlers. // ////// Critical: This code removes the handler for OnSourceChanged which is critical /// TreatAsSafe:Removing the handler is a safe operation /// [SecurityCritical,SecurityTreatAsSafe] internal void OnDetach() { if (_editor != null) { PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); _editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange); } _editor = null; } // // Callback from TextEditor when it gets focus. // ////// Critical: This code calls into PresentationSource to remove and add source changed handlers /// TreatAsSafe: Calling this is safe. Since this does not expose the presentation source. Also the /// handler that is attached is private and critical /// [SecurityCritical,SecurityTreatAsSafe] internal void OnGotFocus(TextEditor editor) { if (editor == _editor) { // If an event listener does a reentrant SetFocus, we can get // here without a matching OnLostFocus. Early out so // that we don't attach too many handlers. return; } // remove source changed handler for previous editor. if (_editor != null) { PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); _editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange); } // Update the current focus TextEditor, RenderScope and UiScope. _editor = editor; // we need to track the source change. PresentationSource.AddSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); _editor.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange); // Update the current composition window position. UpdateNearCaretCompositionWindow(); } // // Callback from TextEditor when it lost focus. // internal void OnLostFocus() { if (_editor == null) return; _losingFocus = true; try { // complete the composition string when it lost focus. CompleteComposition(); } finally { _losingFocus = false; } } // // Callback from TextEditor when the layout is updated // internal void OnLayoutUpdated() { if (_updateCompWndPosAtNextLayoutUpdate && IsReadingWindowIme()) { UpdateNearCaretCompositionWindow(); } _updateCompWndPosAtNextLayoutUpdate = false; } // // complete the composition string by calling ImmNotifyIME. // ////// Critical - elevates to access protected resource (hwnd) /// TreatAsSafe - forces the composition to complete, which at worst /// causes someone's current input to commit. No additional /// spoofing or information disclosure could occur. /// [SecurityCritical, SecurityTreatAsSafe] internal void CompleteComposition() { UnregisterMouseListeners(); if (_source == null) { // Do nothing if HwndSource is already gone(disposed) or disconnected. return; } _compositionModifiedByApp = true; IntPtr hwnd = IntPtr.Zero; new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { hwnd = ((IWin32Window)_source).Handle; } finally { CodeAccessPermission.RevertAssert(); } IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { UnsafeNativeMethods.ImmNotifyIME(new HandleRef(this, himc), NativeMethods.NI_COMPOSITIONSTR, NativeMethods.CPS_COMPLETE, 0); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); } if (_compositionAdorner != null) { _compositionAdorner.Uninitialize(); _compositionAdorner = null; } _startComposition = null; _endComposition = null; } // Called as the selection changes. // We can't modify document state here in any way. internal void OnSelectionChange() { _compositionModifiedByApp = true; } // Callback for TextSelection.Changed event. internal void OnSelectionChanged() { if (!this.IsInKeyboardFocus) { return; } // Update the current composition window position. UpdateNearCaretCompositionWindow(); } //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ // // Returns true if we're in the middle of an ongoing composition. // internal bool IsComposition { get { return _startComposition != null; } } //----------------------------------------------------- // // Private Methods // //----------------------------------------------------- //----------------------------------------------------- // // SourceChanged callback // ////// Critical - calls critical code - NewSource, OldSource and UpdateSource. /// [SecurityCritical] private void OnSourceChanged(object sender, SourceChangedEventArgs e) { HwndSource newSource = null; HwndSource oldSource = null; new UIPermission(PermissionState.Unrestricted).Assert(); // BlessedAssert try { newSource = e.NewSource as HwndSource; oldSource = e.OldSource as HwndSource; } finally { UIPermission.RevertAssert(); } UpdateSource(oldSource, newSource); // Clean up the old source changed event handler that was connected with UiScope. if (oldSource != null && UiScope != null) { // Remove the source changed event handler here. // Ohterwise, we'll get the leak of the SourceChangedEventHandler. // New source changed event handler will be added by getting OnGotFocus on new UiScope. PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged)); } } // // Update _list and _source with new source. // ////// Critical - Calls critical code (add/remove hook) /// [SecurityCritical] private void UpdateSource(HwndSource oldSource, HwndSource newSource) { if (_source != null) { Debug.Assert((oldSource == null) || (oldSource == _source)); new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { _source.RemoveHook(new HwndSourceHook(ImmCompositionFilterMessage)); } finally { UIPermission.RevertAssert(); } _source.Disposed -= new EventHandler(OnHwndDisposed); // Remove HwndSource from the list. _list.Remove(_source); _source = null; } if (newSource != null) { _list[newSource] = this; _source = newSource; new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { _source.AddHook(new HwndSourceHook(ImmCompositionFilterMessage)); } finally { UIPermission.RevertAssert(); } _source.Disposed += new EventHandler(OnHwndDisposed); } // _source should always be a newSource. Debug.Assert(newSource == _source); } // // Window Hook to track WM_IME_ messages. // ////// Critical - access raw Win32 messages, raw input, etc. and can be used to spoof input /// [SecurityCritical] private IntPtr ImmCompositionFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { IntPtr lret = IntPtr.Zero ; switch (msg) { case NativeMethods.WM_IME_CHAR: OnWmImeChar(wParam, ref handled); break; case NativeMethods.WM_IME_NOTIFY: // we don't have to update handled. OnWmImeNotify(hwnd, wParam); break; case NativeMethods.WM_IME_STARTCOMPOSITION: case NativeMethods.WM_IME_ENDCOMPOSITION: if (IsInKeyboardFocus && !IsReadOnly) { // Do Level 2 for legacy Chinese IMM32 IMEs. if (!IsReadingWindowIme()) { handled = true; } } break; case NativeMethods.WM_IME_COMPOSITION: OnWmImeComposition(hwnd, lParam, ref handled); break; case NativeMethods.WM_IME_REQUEST: lret = OnWmImeRequest(wParam, lParam, ref handled); break; case NativeMethods.WM_INPUTLANGCHANGE: // Set the composition window position (reading window position) for // legacy Chinese IMM32 IMEs. if (IsReadingWindowIme()) { UpdateNearCaretCompositionWindow(); } break; } return lret; } // // WM_IME_COMPOSITION handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private void OnWmImeComposition(IntPtr hwnd, IntPtr lParam, ref bool handled) { IntPtr himc; int size; int cursorPos = 0; int deltaStart = 0; char[] result = null; char[] composition = null; int[] clauseInfo = null; byte[] attributes = null; if (IsReadingWindowIme()) { // Don't handle WM_IME_COMPOSITION for Chinese Legacy IMEs. return; } if (!IsInKeyboardFocus && !_losingFocus) { // Don't handle WM_IME_COMPOSITION if we don't have a focus. return; } if (IsReadOnly) { // Don't handle WM_IME_COMPOSITION if it is readonly. return; } himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc == IntPtr.Zero) { // we don't do anything with NULL-HIMC. return; } // // Get the result string from hIMC. // if (((int)lParam & NativeMethods.GCS_RESULTSTR) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_RESULTSTR, IntPtr.Zero, 0); if (size > 0) { result = new char[size / Marshal.SizeOf(typeof(short))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_RESULTSTR, result, size); } } // // Get the composition string from hIMC. // if (((int)lParam & NativeMethods.GCS_COMPSTR) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPSTR, IntPtr.Zero, 0); if (size > 0) { composition = new char[size / Marshal.SizeOf(typeof(short))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPSTR, composition, size); // // Get the caret position from hIMC. // if (((int)lParam & NativeMethods.GCS_CURSORPOS) != 0) { cursorPos = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_CURSORPOS, IntPtr.Zero, 0); } // // Get the delta start position from hIMC. // if (((int)lParam & NativeMethods.GCS_DELTASTART) != 0) { deltaStart = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_DELTASTART, IntPtr.Zero, 0); } // // Get the clause information from hIMC. // if (((int)lParam & NativeMethods.GCS_COMPCLAUSE) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPCLAUSE, IntPtr.Zero, 0); if (size > 0) { clauseInfo = new int[size / Marshal.SizeOf(typeof(int))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPCLAUSE, clauseInfo, size); } } // // Get the attribute information from hIMC. // if (((int)lParam & NativeMethods.GCS_COMPATTR) != 0) { size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPATTR, IntPtr.Zero, 0); if (size > 0) { attributes = new byte[size / Marshal.SizeOf(typeof(byte))]; // 3rd param is out and contains actual result of this call. // suppress Presharp 6031. #pragma warning suppress 6031 UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPATTR, attributes, size); } } } } UpdateCompositionString(result, composition, cursorPos, deltaStart, clauseInfo, attributes); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); handled = true; } // // WM_IME_CHAR handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private void OnWmImeChar(IntPtr wParam, ref bool handled) { if (!IsInKeyboardFocus && !_losingFocus) { // Don't handle WM_IME_CAHR if we don't have a focus. return; } if (IsReadOnly) { // Don't handle WM_IME_CAHR if it is readonly. return; } if (_handlingImeMessage) { // We will be called reentrantly while completing compositions // in response to application listeners. In that case, don't // propegate events to listeners. return; } _handlingImeMessage = true; try { int resultLength; string compositionString = BuildCompositionString(null, new char[] { (char)wParam }, out resultLength); if (compositionString == null) { CompleteComposition(); } else { FrameworkTextComposition composition = TextStore.CreateComposition(_editor, this); _compositionModifiedByApp = false; _caretOffset = 1; // // Raise TextInputStart. // bool handledbyApp = RaiseTextInputStartEvent(composition, resultLength, compositionString); if (handledbyApp) { CompleteComposition(); } else { // // Raise TextInput. // bool handledByApp = RaiseTextInputEvent(composition, compositionString); if (handledByApp) { CompleteComposition(); goto Exit; } } } } finally { _handlingImeMessage = false; } // the string has been finalized. Update the reading window position for // legacy Chinese IMEs. if (IsReadingWindowIme()) { UpdateNearCaretCompositionWindow(); } Exit: handled = true; } // // WM_IME_NOTIFY handler // ////// Critical - accepts raw Win32 messages, calls unmanaged code, deals /// with unmanaged data structures /// [SecurityCritical] private void OnWmImeNotify(IntPtr hwnd, IntPtr wParam) { int convmode; int sentence; IntPtr himc; // we don't have to do anything if _editor is null. if (!IsInKeyboardFocus) { return; } switch ( (int) wParam) { case NativeMethods.IMN_OPENCANDIDATE: himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { NativeMethods.CANDIDATEFORM candform = new NativeMethods.CANDIDATEFORM(); // // At IMN_OPENCANDIDATE, we need to set the candidate window location to hIMC. // if (IsReadingWindowIme()) { // Level 2 for Chinese legacy IMEs. // We have already set the composition form. The candidate window will follow it. candform.dwIndex = 0; candform.dwStyle = NativeMethods.CFS_DEFAULT; candform.rcArea.left = 0; candform.rcArea.right = 0; candform.rcArea.top = 0; candform.rcArea.bottom = 0; candform.ptCurrentPos = new NativeMethods.POINT(0, 0); } else { ITextView view; ITextPointer startNavigator; ITextPointer endNavigator; ITextPointer caretNavigator; GeneralTransform transform; Point milPointTopLeft; Point milPointBottomRight; Point milPointCaret; Rect rectStart; Rect rectEnd; Rect rectCaret; CompositionTarget compositionTarget; compositionTarget = _source.CompositionTarget; if (_startComposition != null) { startNavigator = _startComposition.CreatePointer(); } else { startNavigator = _editor.Selection.Start.CreatePointer(); } if (_endComposition != null) { endNavigator = _endComposition.CreatePointer(); } else { endNavigator = _editor.Selection.End.CreatePointer(); } if (_startComposition != null) { caretNavigator = _caretOffset > 0 ? _startComposition.CreatePointer(_caretOffset, LogicalDirection.Forward) : _endComposition; } else { caretNavigator = _editor.Selection.End.CreatePointer(); } ITextPointer startPosition = startNavigator.CreatePointer(LogicalDirection.Forward); ITextPointer endPosition = endNavigator.CreatePointer(LogicalDirection.Backward); ITextPointer caretPosition = caretNavigator.CreatePointer(LogicalDirection.Forward); // We need to update the layout before getting rect. It could be dirty. if (!startPosition.ValidateLayout() || !endPosition.ValidateLayout() || !caretPosition.ValidateLayout()) { return; } view = TextEditor.GetTextView(RenderScope); rectStart = view.GetRectangleFromTextPosition(startPosition); rectEnd = view.GetRectangleFromTextPosition(endPosition); rectCaret = view.GetRectangleFromTextPosition(caretPosition); // Take the "extended" union of the first and last char's bounding box. milPointTopLeft = new Point(Math.Min(rectStart.Left, rectEnd.Left), Math.Min(rectStart.Top, rectEnd.Top)); milPointBottomRight = new Point(Math.Max(rectStart.Left, rectEnd.Left), Math.Max(rectStart.Bottom, rectEnd.Bottom)); milPointCaret = new Point(rectCaret.Left, rectCaret.Bottom); // Transform to root visual coordinates. transform = RenderScope.TransformToAncestor(compositionTarget.RootVisual); transform.TryTransform(milPointTopLeft, out milPointTopLeft); transform.TryTransform(milPointBottomRight, out milPointBottomRight); transform.TryTransform(milPointCaret, out milPointCaret); // Transform to device units. milPointTopLeft = compositionTarget.TransformToDevice.Transform(milPointTopLeft); milPointBottomRight = compositionTarget.TransformToDevice.Transform(milPointBottomRight); milPointCaret = compositionTarget.TransformToDevice.Transform(milPointCaret); // Build CANDIDATEFORM. CANDIDATEFORM is window coodidate. candform.dwIndex = 0; candform.dwStyle = NativeMethods.CFS_EXCLUDE; candform.rcArea.left = ConvertToInt32(milPointTopLeft.X); candform.rcArea.right = ConvertToInt32(milPointBottomRight.X); candform.rcArea.top = ConvertToInt32(milPointTopLeft.Y); candform.rcArea.bottom = ConvertToInt32(milPointBottomRight.Y); candform.ptCurrentPos = new NativeMethods.POINT(ConvertToInt32(milPointCaret.X), ConvertToInt32(milPointCaret.Y)); } // Call IMM32 to set new candidate position to hIMC. // ImmSetCandidateWindow fails when // - candform.dwIndex is invalid (over 4). // - himc belongs to other threads. // - fail to lock IMC. // Those cases are ignorable for us. // In addition, it does not set win32 last error and we have no clue to handle error. #pragma warning suppress 6031 UnsafeNativeMethods.ImmSetCandidateWindow(new HandleRef(this, himc), ref candform); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); } // We want to pass this message to DefWindowProc. // We don't update "handled". break; case NativeMethods.IMN_SETCONVERSIONMODE: // The Conversion Mode has been changed by IMM32 API. We need to update InputMethod.ImeConversionMode property. ImeConversionModeValues imeConversionMode = 0; convmode = 0; sentence = 0; himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); // IME_CMODE_ALPHANUMERIC is 0. if ((convmode & (NativeMethods.IME_CMODE_NATIVE | NativeMethods.IME_CMODE_KATAKANA)) == 0) imeConversionMode |= ImeConversionModeValues.Alphanumeric; if ((convmode & NativeMethods.IME_CMODE_NATIVE) != 0) imeConversionMode |= ImeConversionModeValues.Native; if ((convmode & NativeMethods.IME_CMODE_KATAKANA) != 0) imeConversionMode |= ImeConversionModeValues.Katakana; if ((convmode & NativeMethods.IME_CMODE_FULLSHAPE) != 0) imeConversionMode |= ImeConversionModeValues.FullShape; if ((convmode & NativeMethods.IME_CMODE_ROMAN) != 0) imeConversionMode |= ImeConversionModeValues.Roman; if ((convmode & NativeMethods.IME_CMODE_CHARCODE) != 0) imeConversionMode |= ImeConversionModeValues.CharCode; if ((convmode & NativeMethods.IME_CMODE_NOCONVERSION) != 0) imeConversionMode |= ImeConversionModeValues.NoConversion; if ((convmode & NativeMethods.IME_CMODE_EUDC) != 0) imeConversionMode |= ImeConversionModeValues.Eudc; if ((convmode & NativeMethods.IME_CMODE_SYMBOL) != 0) imeConversionMode |= ImeConversionModeValues.Symbol; if ((convmode & NativeMethods.IME_CMODE_FIXED) != 0) imeConversionMode |= ImeConversionModeValues.Fixed; InputMethod.Current.ImeConversionMode = imeConversionMode; } break; case NativeMethods.IMN_SETSENTENCEMODE: // The Sentence Mode has been changed by IMM32 API. We need to update InputMethod.ImeSentenceMode property. ImeSentenceModeValues imeSentenceMode = 0; convmode = 0; sentence = 0; himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); // TF_SENTENCEMODE_ALPHANUMERIC is 0. if (sentence == NativeMethods.IME_SMODE_NONE) { imeSentenceMode = ImeSentenceModeValues.None; } else { if ((sentence & NativeMethods.IME_SMODE_PLAURALCLAUSE) != 0) imeSentenceMode |= ImeSentenceModeValues.PluralClause; if ((sentence & NativeMethods.IME_SMODE_SINGLECONVERT) != 0) imeSentenceMode |= ImeSentenceModeValues.SingleConversion; if ((sentence & NativeMethods.IME_SMODE_AUTOMATIC) != 0) imeSentenceMode |= ImeSentenceModeValues.Automatic; if ((sentence & NativeMethods.IME_SMODE_PHRASEPREDICT) != 0) imeSentenceMode |= ImeSentenceModeValues.PhrasePrediction; if ((sentence & NativeMethods.IME_SMODE_CONVERSATION) != 0) imeSentenceMode |= ImeSentenceModeValues.Conversation; } InputMethod.Current.ImeSentenceMode = imeSentenceMode; } break; case NativeMethods.IMN_SETOPENSTATUS: // The open status has been changed by IMM32 API. We need to update InputMethod.ImeState property. himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { bool fOpen = UnsafeNativeMethods.ImmGetOpenStatus(new HandleRef(this, himc)); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); InputMethod.Current.ImeState = fOpen ? InputMethodState.On : InputMethodState.Off; } break; } } // // Use Level 2 for Chinese IME // ////// Critical - elevates to access protected resources (hwnd) /// TreatAsSafe - positions the composition window, which is safe to do /// [SecurityCritical, SecurityTreatAsSafe] private void UpdateNearCaretCompositionWindow() { ITextView view; Rect rectUi; GeneralTransform transform; Point milPointTopLeft; Point milPointBottomRight; Point milPointCaret; Rect rectCaret; CompositionTarget compositionTarget; IntPtr hwnd; if (!IsInKeyboardFocus) { return; } if (_source == null) { return; } // get hwnd from _source. new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { hwnd = ((IWin32Window)_source).Handle; } finally { CodeAccessPermission.RevertAssert(); } rectUi = UiScope.VisualContentBounds; view = _editor.TextView; // // // During incremental layout update, the region of the view covered by // the selection may not be ready yet. if (!_editor.Selection.End.HasValidLayout) { _updateCompWndPosAtNextLayoutUpdate = true; return; } compositionTarget = _source.CompositionTarget; // HwndSource.CompositionTarget may return null if the target hwnd is being destroyed and disposed. if (compositionTarget == null) { return; } // If the mouse click happens before rendering, the seleciton move notification is generated. // However the visual tree is not completely connected yet. We need to check it. if (!compositionTarget.RootVisual.IsAncestorOf(RenderScope)) { return; } IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { rectCaret = view.GetRectangleFromTextPosition(_editor.Selection.End.CreatePointer(LogicalDirection.Backward)); // Take the points of the renderScope. milPointTopLeft = new Point(rectUi.Left, rectUi.Top); milPointBottomRight = new Point(rectUi.Right, rectUi.Bottom); // Take the "extended" union of the first and last char's bounding box. // milPointCaret = new Point(rectCaret.Left, rectCaret.Top); milPointCaret = new Point(rectCaret.Left, rectCaret.Bottom); // Transform to root visual coordinates. transform = RenderScope.TransformToAncestor(compositionTarget.RootVisual); transform.TryTransform(milPointTopLeft, out milPointTopLeft); transform.TryTransform(milPointBottomRight, out milPointBottomRight); transform.TryTransform(milPointCaret, out milPointCaret); // Transform to device units. milPointTopLeft = compositionTarget.TransformToDevice.Transform(milPointTopLeft); milPointBottomRight = compositionTarget.TransformToDevice.Transform(milPointBottomRight); milPointCaret = compositionTarget.TransformToDevice.Transform(milPointCaret); // Build COMPOSITIONFORM. COMPOSITIONFORM is window coodidate. NativeMethods.COMPOSITIONFORM compform = new NativeMethods.COMPOSITIONFORM(); compform.dwStyle = NativeMethods.CFS_RECT; compform.rcArea.left = ConvertToInt32(milPointTopLeft.X); compform.rcArea.right = ConvertToInt32(milPointBottomRight.X); compform.rcArea.top = ConvertToInt32(milPointTopLeft.Y); compform.rcArea.bottom = ConvertToInt32(milPointBottomRight.Y); compform.ptCurrentPos = new NativeMethods.POINT(ConvertToInt32(milPointCaret.X), ConvertToInt32(milPointCaret.Y)); // Call IMM32 to set new candidate position to hIMC. // ImmSetCompositionWindow fails when // - himc belongs to other threads. // - fail to lock IMC. // Those cases are ignorable for us. // In addition, it does not set win32 last error and we have no clue to handle error. UnsafeNativeMethods.ImmSetCompositionWindow(new HandleRef(this, himc), ref compform); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); } } // // Hwnd disposed callback. // ////// Critical - unparents the composition window visually /// TreatAsSafe - while a DOS, this doesn't present any specific security threat. /// [SecurityCritical, SecurityTreatAsSafe] private void OnHwndDisposed(object sender, EventArgs args) { UpdateSource(_source, null); } // // update the composition string on the scope // private void UpdateCompositionString(char[] resultChars, char[] compositionChars, int caretOffset, int deltaStart, int[] clauseInfo, byte[] attributes) { if (_handlingImeMessage) { // We will be called reentrantly while completing compositions // in response to application listeners. In that case, don't // propegate events to listeners. return; } _handlingImeMessage = true; try { // // Remove any existing composition adorner for display attribute. // if (_compositionAdorner != null) { _compositionAdorner.Uninitialize(); _compositionAdorner = null; } // // Build up an array of resultChars + compositionChars -- the complete span of changing text. // int resultLength; string compositionString = BuildCompositionString(resultChars, compositionChars, out resultLength); if (compositionString == null) { CompleteComposition(); return; } // // Remember where the IME placed the caret. // RecordCaretOffset(caretOffset, attributes, compositionString.Length); FrameworkTextComposition composition = TextStore.CreateComposition(_editor, this); _compositionModifiedByApp = false; if (_startComposition == null) { Invariant.Assert(_endComposition == null); // // Raise TextInputStart. // bool handledbyApp = RaiseTextInputStartEvent(composition, resultLength, compositionString); if (handledbyApp) { CompleteComposition(); return; } } else if (compositionChars != null) { // // Raise TextInputUpdate. // bool handledByApp = RaiseTextInputUpdateEvent(composition, resultLength, compositionString); if (handledByApp) { CompleteComposition(); return; } } if (compositionChars == null) { // // Raise TextInput. // bool handledByApp = RaiseTextInputEvent(composition, compositionString); if (handledByApp) { CompleteComposition(); return; } } if (_startComposition != null) { SetCompositionAdorner(clauseInfo, attributes); } } finally { _handlingImeMessage = false; } } // Attempts to build a string containing zero or more result chars // followed by zero or more composition chars. // // Will return null if an underlying TextBox has MaxLength property // that is exceeded by the new content. private string BuildCompositionString(char[] resultChars, char[] compositionChars, out int resultLength) { int compositionLength = compositionChars == null ? 0 : compositionChars.Length; resultLength = resultChars == null ? 0 : resultChars.Length; char[] compositionText; if (resultChars == null) { compositionText = compositionChars; } else if (compositionChars == null) { compositionText = resultChars; } else { compositionText = new char[resultLength + compositionLength]; Array.Copy(resultChars, 0, compositionText, 0, resultLength); Array.Copy(compositionChars, 0, compositionText, resultLength, compositionLength); } string compositionString = new string(compositionText); if (!_editor.AcceptsRichContent) { int charsToReplaceCount; if (_startComposition == null) { charsToReplaceCount = _editor.Selection.Start.GetOffsetToPosition(_editor.Selection.End); } else { charsToReplaceCount = _startComposition.GetOffsetToPosition(_endComposition); } compositionString = _editor._FilterText(compositionString, charsToReplaceCount); } int originalLength = (compositionText == null) ? 0 : compositionText.Length; return (compositionString.Length == originalLength) ? compositionString : null; } // Caches the IME specified caret offset. // Value is the offset in unicode code points from the composition start. private void RecordCaretOffset(int caretOffset, byte[] attributes, int compositionLength) { // Use the suggested value if it is on ATTR_INPUT, otherwise set the caret at the end of // composition string. So it always stays where the new char is inserted. if ((attributes != null) && // If the next char of the cursorPos is INPUTATTR. (((caretOffset >= 0) && (caretOffset < attributes.Length) && (attributes[caretOffset] == NativeMethods.ATTR_INPUT)) || // If the prev char os the cursorPos is INPUTATTR. ((caretOffset > 0) && ((caretOffset - 1) < attributes.Length) && (attributes[caretOffset - 1] == NativeMethods.ATTR_INPUT)))) { _caretOffset = caretOffset; } else { _caretOffset = -1; } } // Raises a public TextInputStart event. // Returns true if a listener handles the event or modifies document state. ////// Critical - calls critical (TextCompositionManager) code. /// TreatAsSafe - doesn't accept or return critical information. /// [SecurityCritical, SecurityTreatAsSafe] private bool RaiseTextInputStartEvent(FrameworkTextComposition composition, int resultLength, string compositionString) { composition.Stage = TextCompositionStage.None; composition.SetCompositionPositions(_editor.Selection.Start, _editor.Selection.End, compositionString); // PUBLIC event: bool handled = TextCompositionManager.StartComposition(composition); if (handled || composition.PendingComplete || _compositionModifiedByApp) { return true; } // UpdateCompositionText raises a PUBLIC EVENT.... UpdateCompositionText(composition, resultLength, true /* includeResultText */, out _startComposition, out _endComposition); if (_compositionModifiedByApp) { return true; } RegisterMouseListeners(); return false; } // Raises a public TextInputUpdate event. // Returns true if a listener handles the event or modifies document state. ////// Critical - calls critical (TextCompositionManager) code. /// TreatAsSafe - doesn't accept or return critical information. /// [SecurityCritical, SecurityTreatAsSafe] private bool RaiseTextInputUpdateEvent(FrameworkTextComposition composition, int resultLength, string compositionString) { composition.Stage = TextCompositionStage.Started; composition.SetCompositionPositions(_startComposition, _endComposition, compositionString); // PUBLIC event: bool handled = TextCompositionManager.UpdateComposition(composition); if (handled || composition.PendingComplete || _compositionModifiedByApp) { return true; } // UpdateCompositionText raises a PUBLIC EVENT.... UpdateCompositionText(composition, resultLength, false /* includeResultText */, out _startComposition, out _endComposition); if (_compositionModifiedByApp) { return true; } return false; } // Raises a public TextInput event. // Returns true if a listener handles the event or modifies document state. ////// Critical - calls critical (TextCompositionManager) code. /// TreatAsSafe - doesn't accept or return critical information. /// [SecurityCritical, SecurityTreatAsSafe] private bool RaiseTextInputEvent(FrameworkTextComposition composition, string compositionString) { composition.Stage = TextCompositionStage.Started; composition.SetResultPositions(_startComposition, _endComposition, compositionString); _startComposition = null; _endComposition = null; UnregisterMouseListeners(); _handledByEditorListener = false; // PUBLIC event: TextCompositionManager.CompleteComposition(composition); _compositionUndoUnit = null; return (!_handledByEditorListener || composition.PendingComplete || _compositionModifiedByApp); } // Inserts composition text into the document. // Raises public text, selection changed events. // Called by default editor TextInputEvent handler. internal void UpdateCompositionText(FrameworkTextComposition composition) { ITextPointer start; ITextPointer end; UpdateCompositionText(composition, 0, true /* includeResultText */ , out start, out end); } // Inserts composition text into the document. // Raises public text, selection changed events. // Returns the position of the inserted text. If includeResultText is // true, start/end will cover all the inserted text. Otherwise, text // from offset 0 to resultLength is omitted from start/end. internal void UpdateCompositionText(FrameworkTextComposition composition, int resultLength, bool includeResultText, out ITextPointer start, out ITextPointer end) { start = null; end = null; if (_compositionModifiedByApp) { // If the app has modified the document since this event was raised // (by hooking a TextInput event), then we don't know what to do, // so do nothing. return; } _handledByEditorListener = true; UndoCloseAction undoCloseAction = UndoCloseAction.Rollback; OpenCompositionUndoUnit(); try { _editor.Selection.BeginChange(); try { // ITextRange range; string text; if (composition._ResultStart != null) { range = new TextRange(composition._ResultStart, composition._ResultEnd); text = composition.Text; } else { range = new TextRange(composition._CompositionStart, composition._CompositionEnd); text = composition.CompositionText; } _editor.SetText(range, text, InputLanguageManager.Current.CurrentInputLanguage); if (includeResultText) { start = range.Start; } else { start = range.Start.CreatePointer(resultLength, LogicalDirection.Forward); } end = range.End; ITextPointer caretPosition = _caretOffset >= 0 ? start.CreatePointer(_caretOffset, LogicalDirection.Forward) : end; _editor.Selection.Select(caretPosition, caretPosition); } finally { // We're about to raise the public event. // Set a flag so we can detect app changes. _compositionModifiedByApp = false; _editor.Selection.EndChange(); } undoCloseAction = UndoCloseAction.Commit; } finally { CloseCompositionUndoUnit(undoCloseAction, end); } } // Decorates the composition with IME specified underlining. private void SetCompositionAdorner(int[] clauseInfo, byte[] attributes) { if ((clauseInfo != null) && (attributes != null)) { for (int i = 0; i < clauseInfo.Length - 1; i++) { ITextPointer startClause = _startComposition.CreatePointer(clauseInfo[i], LogicalDirection.Backward); ITextPointer endClause = _startComposition.CreatePointer(clauseInfo[i + 1], LogicalDirection.Forward); if (_compositionAdorner == null) { _compositionAdorner = new CompositionAdorner(_editor.TextView); _compositionAdorner.Initialize(_editor.TextView); } // // UnsafeNativeMethods.TF_DISPLAYATTRIBUTE displayAttribute = new UnsafeNativeMethods.TF_DISPLAYATTRIBUTE(); displayAttribute.crLine.type = UnsafeNativeMethods.TF_DA_COLORTYPE.TF_CT_COLORREF; displayAttribute.crLine.indexOrColorRef = 0; displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_NONE; displayAttribute.fBoldLine = false; switch (attributes[clauseInfo[i]]) { case NativeMethods.ATTR_INPUT: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_DOT; break; case NativeMethods.ATTR_TARGET_CONVERTED: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID; displayAttribute.fBoldLine = true; break; case NativeMethods.ATTR_CONVERTED: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID; break; case NativeMethods.ATTR_TARGET_NOTCONVERTED: displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID; break; case NativeMethods.ATTR_INPUT_ERROR: break; case NativeMethods.ATTR_FIXEDCONVERTED: break; } #if UNUSED_IME_HIGHLIGHT_LAYER // Demand create the highlight layer. if (_highlightLayer == null) { _highlightLayer = new DisplayAttributeHighlightLayer(); } // ToDo: Need to pass the foreground and background color of the composition _highlightLayer.Add(startClause, endClause, /*TextDecorationCollection:*/null); #endif TextServicesDisplayAttribute textServiceDisplayAttribute = new TextServicesDisplayAttribute(displayAttribute); // Add the attribute range into CompositionAdorner. _compositionAdorner.AddAttributeRange(startClause, endClause, textServiceDisplayAttribute); } #if UNUSED_IME_HIGHLIGHT_LAYER if (_highlightLayer != null) { _editor.TextContainer.Highlights.AddLayer(_highlightLayer); } #endif if (_compositionAdorner != null) { // Update the layout to get the acurated rectangle from calling GetRectangleFromTextPosition _editor.TextView.RenderScope.UpdateLayout(); // Invalidate the composition adorner to apply the composition attribute ranges. _compositionAdorner.InvalidateAdorner(); } } } // Start listening mouse event for MSIME mouse operation. private void RegisterMouseListeners() { UiScope.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonDown += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonUp += new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseMove += new MouseEventHandler(OnMouseEvent); } // Stop listening mouse event for MSIME mouse operation. private void UnregisterMouseListeners() { if (this.UiScope != null) { UiScope.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseLeftButtonUp -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonDown -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseRightButtonUp -= new MouseButtonEventHandler(OnMouseButtonEvent); UiScope.PreviewMouseMove -= new MouseEventHandler(OnMouseEvent); } } // // WM_IME_REQUEST handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private IntPtr OnWmImeRequest(IntPtr wParam, IntPtr lParam, ref bool handled) { IntPtr lret = IntPtr.Zero ; switch ( (int) wParam) { case NativeMethods.IMR_RECONVERTSTRING: lret = OnWmImeRequest_ReconvertString(lParam, ref handled, false); break; case NativeMethods.IMR_CONFIRMRECONVERTSTRING: lret = OnWmImeRequest_ConfirmReconvertString(lParam, ref handled); break; case NativeMethods.IMR_QUERYCHARPOSITION: break; case NativeMethods.IMR_DOCUMENTFEED: lret = OnWmImeRequest_ReconvertString(lParam, ref handled, true); break; } return lret; } // // WM_IME_REQUEST/IMR_RECONVERTSTRING handler // ////// Crtical: This code calls into PtrToStruct which is marked critical.It can also be used to spoof input /// [SecurityCritical] private IntPtr OnWmImeRequest_ReconvertString(IntPtr lParam, ref bool handled, bool fDocFeed) { if (!fDocFeed) { _isReconvReady = false; } if (!IsInKeyboardFocus) { return IntPtr.Zero ; } ITextRange range; // // If there is the composition string, we use it. Otherwise we use the current selection. // if (fDocFeed && (_startComposition != null) && (_endComposition != null)) { range = new TextRange(_startComposition, _endComposition); } else { range = _editor.Selection; } string target = range.Text; int requestSize = Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)) + (target.Length * Marshal.SizeOf(typeof(short))) + ((_maxSrounding + 1) * Marshal.SizeOf(typeof(short)) * 2); IntPtr lret = new IntPtr( requestSize); if (lParam != IntPtr.Zero) { int offsetStart; string surrounding = GetSurroundingText(range, out offsetStart); // Create RECONVERTSTRING structure from lParam. NativeMethods.RECONVERTSTRING reconv = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING)); reconv.dwSize = requestSize; reconv.dwVersion = 0; // must be 0 reconv.dwStrLen = surrounding.Length; // in char count reconv.dwStrOffset = Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)); // in byte count reconv.dwCompStrLen = target.Length; // in char count reconv.dwCompStrOffset = offsetStart * Marshal.SizeOf(typeof(short)); // in byte count reconv.dwTargetStrLen = target.Length; // in char count reconv.dwTargetStrOffset = offsetStart * Marshal.SizeOf(typeof(short)); // in byte count if (!fDocFeed) { // // If this is IMR_RECONVERTSTRING, we cache it. So we can refer it later when we get // IMR_CONFIRMRECONVERTSTRING message. // _reconv = reconv; _isReconvReady = true; } // Copy the strucuture back to lParam. Marshal.StructureToPtr(reconv, lParam, true); StoreSurroundingText(lParam, surrounding); } handled = true; return lret; } ////// Crtical: unsafe code to manipulate pointer. /// This should not be called unless we're sure the first param is the pointer to /// NativeMethods.RECONVERTSTRING. /// [SecurityCritical] private unsafe static void StoreSurroundingText(IntPtr reconv, string surrounding) { // Copy the string to the pointer right after the structure. byte *p = (byte *)reconv.ToPointer(); p += Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)); Marshal.Copy(surrounding.ToCharArray(), 0, new IntPtr((void *)p), surrounding.Length); } // // Get the surrounding text of the given range. // The offsetStart is out param to return the offset of the given range in the returned surrounding text. // private static string GetSurroundingText(ITextRange range, out int offsetStart) { ITextPointer navigator; bool done; string surrounding = ""; int bufLength; // // Get the previous text of the given range. // navigator = range.Start.CreatePointer(); done = false; bufLength = _maxSrounding; while (!done && (bufLength > 0)) { switch (navigator.GetPointerContext(LogicalDirection.Backward)) { case TextPointerContext.Text: char[] buffer = new char[bufLength]; int copied = ((TextPointer)navigator).GetTextInRun(LogicalDirection.Backward, buffer, 0, buffer.Length); Invariant.Assert(copied != 0); navigator.MoveByOffset(0 - copied); bufLength -= copied; surrounding = surrounding.Insert(0, new string(buffer, 0, copied)); break; case TextPointerContext.EmbeddedElement: done = true; break; case TextPointerContext.ElementStart: case TextPointerContext.ElementEnd: // ignore the inline element. if (!navigator.GetElementType(LogicalDirection.Backward).IsSubclassOf(typeof(Inline))) { done = true; } navigator.MoveToNextContextPosition(LogicalDirection.Backward); break; case TextPointerContext.None: done = true; break; default: navigator.MoveToNextContextPosition(LogicalDirection.Backward); break; } } // offsetStart is the amount of the current surroundingText. offsetStart = surrounding.Length; // // add the text in the given range. // surrounding += range.Text; // // Get the following text of the given range. // navigator = range.End.CreatePointer(); done = false; bufLength = _maxSrounding; while (!done && (bufLength > 0)) { switch (navigator.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.Text: char[] buffer = new char[bufLength]; int copied = ((TextPointer)navigator).GetTextInRun(LogicalDirection.Forward, buffer, 0, buffer.Length); navigator.MoveByOffset(copied); bufLength -= copied; surrounding += new string(buffer, 0, copied); break; case TextPointerContext.EmbeddedElement: done = true; break; case TextPointerContext.ElementStart: case TextPointerContext.ElementEnd: // ignore the inline element. if (!navigator.GetElementType(LogicalDirection.Forward).IsSubclassOf(typeof(Inline))) { done = true; } navigator.MoveToNextContextPosition(LogicalDirection.Forward); break; case TextPointerContext.None: done = true; break; default: navigator.MoveToNextContextPosition(LogicalDirection.Forward); break; } } return surrounding; } // // WM_IME_REQUEST/IMR_CONFIRMRECONVERTSTRING handler // ////// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources /// [SecurityCritical] private IntPtr OnWmImeRequest_ConfirmReconvertString(IntPtr lParam, ref bool handled) { if (!IsInKeyboardFocus) { return IntPtr.Zero ; } if (!_isReconvReady) { return IntPtr.Zero ; } NativeMethods.RECONVERTSTRING reconv = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING)); // If the entire string in RECONVERTSTRING has been changed, we don't handle it. if (_reconv.dwStrLen != reconv.dwStrLen) { handled = true; return IntPtr.Zero ; } // // If the new CompStr was suggested by IME, we need to adjust the selection with it. // if ((_reconv.dwCompStrLen != reconv.dwCompStrLen) || (_reconv.dwCompStrOffset != reconv.dwCompStrOffset)) { ITextRange range = _editor.Selection; // // Create the start point from the selection // ITextPointer start = range.Start.CreatePointer(LogicalDirection.Backward); // Move the start point to new dwCompStrOffset. start = MoveToNextCharPos(start, (reconv.dwCompStrOffset - _reconv.dwCompStrOffset) / Marshal.SizeOf(typeof(short))); // Create the end position and move this as dwCompStrLen. ITextPointer end = start.CreatePointer(LogicalDirection.Forward); end = MoveToNextCharPos(end, reconv.dwCompStrLen); // Update the selection with new start and end. _editor.Selection.Select(start, end); } _isReconvReady = false; handled = true; return new IntPtr( 1 ) ; } // // Move the TextPointer by offset in char count. // private static ITextPointer MoveToNextCharPos(ITextPointer position, int offset) { bool done = false; if (offset < 0) { while ((offset < 0) && !done) { switch (position.GetPointerContext(LogicalDirection.Backward)) { case TextPointerContext.Text: offset++; break; case TextPointerContext.None: done = true; break; } position.MoveByOffset(-1); } } else if (offset > 0) { while ((offset > 0) && !done) { switch (position.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.Text: offset--; break; case TextPointerContext.None: done = true; break; } position.MoveByOffset(1); } } return position; } // // Move the TextPointer by offset in char count. // ////// Critical - calls ImmGetProperty that could expose the current ime's capability. /// TreatAsSafe - the return value says only if IME is near caret Chinese IME. /// exposing this information is safe. /// [SecurityCritical, SecurityTreatAsSafe] private bool IsReadingWindowIme() { int prop = UnsafeNativeMethods.ImmGetProperty(new HandleRef(this, SafeNativeMethods.GetKeyboardLayout(0)), NativeMethods.IGP_PROPERTY); return (((prop & NativeMethods.IME_PROP_AT_CARET) == 0) || ((prop & NativeMethods.IME_PROP_SPECIAL_UI) != 0)); } // // Mouse Button state was changed. // ////// Critical - calls critical code (InternalMouseEventHandler) /// TreatAsSafe - the mouse input here is ignored, but rather the mouse /// is directly queried, so there is no spoofing possibility /// [SecurityCritical, SecurityTreatAsSafe] private void OnMouseButtonEvent(object sender, MouseButtonEventArgs e) { e.Handled = InternalMouseEventHandler(); } // // Mouse was moved. // ////// Critical - calls critical code (InternalMouseEventHandler) /// TreatAsSafe - the mouse input here is ignored, but rather the mouse /// is directly queried, so there is no spoofing possibility /// [SecurityCritical, SecurityTreatAsSafe] private void OnMouseEvent(object sender, MouseEventArgs e) { e.Handled = InternalMouseEventHandler(); } // // The mouse event handler to generate MSIME message to IME listeners. // ////// Critical - calls unmanaged code, sends WM_* messages, simulates mouse /// messages to the IME. This could result in input spoofing. /// [SecurityCritical] private bool InternalMouseEventHandler() { int btnState = 0; if (Mouse.LeftButton == MouseButtonState.Pressed) { btnState = 1; // IMEMOUSE_LDOWN } if (Mouse.RightButton == MouseButtonState.Pressed) { btnState = 2; // IMEMOUSE_RDOWN } Point point = Mouse.GetPosition(RenderScope); ITextView view; ITextPointer positionCurrent; ITextPointer positionNext; Rect rectCurrent; Rect rectNext; view = TextEditor.GetTextView(RenderScope); // Validate layout information on TextView if (!view.Validate(point)) { return false; } // Do the hittest. positionCurrent = view.GetTextPositionFromPoint(point, false); if (positionCurrent == null) { return false; } rectCurrent = view.GetRectangleFromTextPosition(positionCurrent); positionNext = positionCurrent.CreatePointer(); if (positionNext == null) { return false; } if (point.X - rectCurrent.Left >= 0) { positionNext.MoveToNextInsertionPosition(LogicalDirection.Forward); } else { positionNext.MoveToNextInsertionPosition(LogicalDirection.Backward); } rectNext = view.GetRectangleFromTextPosition(positionNext); int edge; int quadrant; edge = _editor.TextContainer.Start.GetOffsetToPosition(positionCurrent); int startComposition = _editor.TextContainer.Start.GetOffsetToPosition(_startComposition); int endComposition = _editor.TextContainer.Start.GetOffsetToPosition(_endComposition); // // IMEs care about only the composition string range. // if (edge < startComposition) { return false; } if (edge > endComposition) { return false; } if (rectNext.Left == rectCurrent.Left) { // if rectNext.Left == rectCurrent.Left, the width of char is 0 and mouse click points there. // there is no quadrent. So we alwasys make it 0. quadrant = 0; } else { if (point.X - rectCurrent.Left >= 0) { if ((((point.X - rectCurrent.Left) * 4) / (rectNext.Left - rectCurrent.Left)) <= 1) quadrant = 2; else quadrant = 3; } else { if (((point.X - rectNext.Left) * 4) / (rectCurrent.Left - rectNext.Left) <= 3) quadrant = 0; else quadrant = 1; } } // // IMEs care about only the composition string range. // If the quadrant is outside of the range, we don't do SendMessage. // if ((edge == startComposition) && (quadrant <= 1)) { return false; } if ((edge == endComposition) && (quadrant >= 2)) { return false; } // // The edge must be relative to the composition string. // edge -= startComposition; int wParam = (edge << 16) + (quadrant << 8) + btnState; IntPtr hwnd = IntPtr.Zero; new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert try { hwnd = ((IWin32Window)_source).Handle; } finally { CodeAccessPermission.RevertAssert(); } IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); IntPtr lret = IntPtr.Zero; if (himc != IntPtr.Zero) { IntPtr hwndDefIme = IntPtr.Zero; new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();//Blessed Assert try { hwndDefIme = UnsafeNativeMethods.ImmGetDefaultIMEWnd(new HandleRef(this, hwnd)); lret = UnsafeNativeMethods.SendMessage(hwndDefIme, s_MsImeMouseMessage, new IntPtr(wParam), himc); } finally { SecurityPermission.RevertAssert(); } } // We eat this event if IME handled. return (lret != IntPtr.Zero) ? true : false; } // Opens a composition undo unit. Opens the compsed composition undo unit if it exist on the top // of the stack. Otherwise, create new composition undo unit and add it to the undo manager and // making it as the opened undo unit. private void OpenCompositionUndoUnit() { UndoManager undoManager; DependencyObject parent; parent = _editor.TextContainer.Parent; undoManager = UndoManager.GetUndoManager(parent); if (undoManager != null && undoManager.IsEnabled && undoManager.OpenedUnit == null) { if (_compositionUndoUnit != null && _compositionUndoUnit == undoManager.LastUnit && !_compositionUndoUnit.Locked) { // Opens a closed composition undo unit on the top of the stack. undoManager.Reopen(_compositionUndoUnit); } else { _compositionUndoUnit = new TextParentUndoUnit(_editor.Selection); // Add the given composition undo unit to the undo manager and making it // as the opened undo unit. undoManager.Open(_compositionUndoUnit); } } else { _compositionUndoUnit = null; } } // Closes an opened composition unit and adding it to the containing unit's undo stack. private void CloseCompositionUndoUnit(UndoCloseAction undoCloseAction, ITextPointer compositionEnd) { UndoManager undoManager; DependencyObject parent; parent = _editor.TextContainer.Parent; undoManager = UndoManager.GetUndoManager(parent); if (undoManager != null && undoManager.IsEnabled) { if (_compositionUndoUnit != null) { // Closes an opened composition unit and commit it to add the composition // undo unit into the containing unit's undo stack. if (undoCloseAction == UndoCloseAction.Commit) { _compositionUndoUnit.RecordRedoSelectionState(compositionEnd, compositionEnd); } undoManager.Close(_compositionUndoUnit, undoCloseAction); } } else { _compositionUndoUnit = null; } } // Converts a double into a 32 bit integer, truncating values that // exceed Int32.MinValue or Int32.MaxValue. private int ConvertToInt32(double value) { int i; if (Double.IsNaN(value)) { // (int)value is 0x80000000. So we should assign Int32.MinValue. i = Int32.MinValue; } else if (value < Int32.MinValue) { i = Int32.MinValue; } else if (value > Int32.MaxValue) { i = Int32.MaxValue; } else { i = Convert.ToInt32(value); } return i; } private void OnTextContainerChange(object sender, TextContainerChangeEventArgs args) { if (args.IMECharCount > 0 && (args.TextChange == TextChangeType.ContentAdded || args.TextChange == TextChangeType.ContentRemoved)) { _compositionModifiedByApp = true; } } //------------------------------------------------------ // // Private Properties // //----------------------------------------------------- private UIElement RenderScope { get { return _editor.TextView == null ? null : _editor.TextView.RenderScope; } } private FrameworkElement UiScope { get { return (_editor == null) ? null : _editor.UiScope; } } private bool IsReadOnly { get { return ((bool)UiScope.GetValue(TextEditor.IsReadOnlyProperty) || _editor.IsReadOnly); } } private bool IsInKeyboardFocus { get { if (_editor == null) { return false; } if (UiScope == null) { return false; } if (!UiScope.IsKeyboardFocused) { return false; } return true; } } //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ #region Private Fields // // HwndSource of this instance of ImmComposition. // [SecurityCritical] private HwndSource _source; // // TextEditor of the current focus element. // private TextEditor _editor; // // The current start position of the compositon string. // This is null if the composition string does not exist. // private ITextPointer _startComposition; // // The current end position of the compositon string. // This is null if the composition string does not exist. // private ITextPointer _endComposition; // // The offset in chars from the start of the composition to the IME caret. // private int _caretOffset; #if UNUSED_IME_HIGHLIGHT_LAYER // // Highlight layer forLevel3 composition drawing. // private DisplayAttributeHighlightLayer _highlightLayer; #endif // // CompositionAdorner for displaying the composition attributes. // private CompositionAdorner _compositionAdorner; // // List of ImmComposition instances. // private static Hashtable _list = new Hashtable(1); // // Dash length of the compositon string underline. // private const double _dashLength = 2.0; // // Max surrounding char count for RECONVERTSTRING/DOCFEED. // private const int _maxSrounding = 0x20; // // Cached RECONVERTSTRING structure for IMR_CONFIRMRECONVERTSTRING message handling. // private NativeMethods.RECONVERTSTRING _reconv; // // True if the cached RECONVERTSTRING structure is ready. // private bool _isReconvReady; // // MSIME mouse operation message. // ////// Critical: This code registers a custom message and calls into a critical method /// [SecurityCritical] private static int s_MsImeMouseMessage = UnsafeNativeMethods.RegisterWindowMessage("MSIMEMouseOperation"); // This is the composition undo unit. private TextParentUndoUnit _compositionUndoUnit; // Reentry flag, set true while handling WM_IME_UPDATE, WM_IME_CHAR. private bool _handlingImeMessage; // If this is true, call UpdateNearCaretCompositionWindow() at the next layout update. private bool _updateCompWndPosAtNextLayoutUpdate; // Set true if an application listener modified the document content // or selection inside a TextInput* event. private bool _compositionModifiedByApp; // Flag set true when TextInput events are handled by the default // TextEditor listener -- not intercepted by an application listener. private bool _handledByEditorListener; // Set true while completing a composition from OnLostFocus. private bool _losingFocus; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ConnectionPoint.cs
- SqlParameterCollection.cs
- ThicknessKeyFrameCollection.cs
- PrefixQName.cs
- DataGridViewCellConverter.cs
- TraceSource.cs
- SortQuery.cs
- CreateBookmarkScope.cs
- WorkflowElementDialog.cs
- _PooledStream.cs
- SessionStateUtil.cs
- HintTextMaxWidthConverter.cs
- Scene3D.cs
- PartialCachingAttribute.cs
- ToolStripSplitStackLayout.cs
- ThousandthOfEmRealDoubles.cs
- _UncName.cs
- ReliabilityContractAttribute.cs
- Sentence.cs
- CaseInsensitiveOrdinalStringComparer.cs
- TextLineBreak.cs
- MarkedHighlightComponent.cs
- ThreadExceptionDialog.cs
- _IPv6Address.cs
- WebPartEditorOkVerb.cs
- wgx_render.cs
- SqlEnums.cs
- AssemblyLoader.cs
- ServiceHost.cs
- StrokeDescriptor.cs
- ChildrenQuery.cs
- DialogResultConverter.cs
- _NegoStream.cs
- FixedSOMPageConstructor.cs
- ContainerParaClient.cs
- ExclusiveTcpListener.cs
- HtmlListAdapter.cs
- AudioException.cs
- log.cs
- BitVector32.cs
- SpeakInfo.cs
- XmlSchemaAll.cs
- AggregateNode.cs
- DataGridViewColumnTypePicker.cs
- NotSupportedException.cs
- BehaviorDragDropEventArgs.cs
- OracleNumber.cs
- SystemPens.cs
- FormsAuthenticationUser.cs
- FontUnit.cs
- ProfileParameter.cs
- TrueReadOnlyCollection.cs
- NameHandler.cs
- ExternalFile.cs
- BamlLocalizerErrorNotifyEventArgs.cs
- HtmlImage.cs
- ComponentCollection.cs
- ProviderCollection.cs
- SQLDouble.cs
- ActivitySurrogate.cs
- SimpleWebHandlerParser.cs
- RemotingConfigParser.cs
- VisualBrush.cs
- HMAC.cs
- DataTable.cs
- ArgumentOutOfRangeException.cs
- CodeCommentStatementCollection.cs
- BackStopAuthenticationModule.cs
- TextProviderWrapper.cs
- Misc.cs
- webclient.cs
- InkCanvas.cs
- ColumnHeaderConverter.cs
- EFDataModelProvider.cs
- XmlStringTable.cs
- WinEventQueueItem.cs
- SchemaImporterExtension.cs
- WebServiceClientProxyGenerator.cs
- BlurBitmapEffect.cs
- Storyboard.cs
- NavigationHelper.cs
- ConnectionOrientedTransportChannelFactory.cs
- DataGridColumnDropSeparator.cs
- SpellerError.cs
- IndentTextWriter.cs
- DbParameterCollectionHelper.cs
- UInt64Storage.cs
- EFTableProvider.cs
- Unit.cs
- WorkflowRuntimeServiceElementCollection.cs
- MissingMemberException.cs
- IChannel.cs
- UserInitiatedNavigationPermission.cs
- MenuRenderer.cs
- ColorAnimationBase.cs
- OAVariantLib.cs
- DataGridViewLayoutData.cs
- ConfigurationStrings.cs
- FormViewDeleteEventArgs.cs
- Separator.cs