void FWindowsTextInputMethodSystem::OnIMEActivationStateChanged(const bool bIsEnabled) { if(bIsEnabled) { // It seems that switching away from an IMM based IME doesn't generate a deactivation notification //check(CurrentAPI == EAPI::Unknown); const HKL KeyboardLayout = ::GetKeyboardLayout(0); TF_INPUTPROCESSORPROFILE TSFProfile; if(SUCCEEDED(TSFInputProcessorProfileManager->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &TSFProfile)) && TSFProfile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR) { CurrentAPI = EAPI::TSF; } else if(::ImmGetIMEFileName(KeyboardLayout, nullptr, 0) > 0) { CurrentAPI = EAPI::IMM; UpdateIMMProperty(KeyboardLayout); } else { CurrentAPI = EAPI::Unknown; } } else { // It seems that switching away from an IMM based IME doesn't generate a deactivation notification //check(CurrentAPI != EAPI::Unknown); CurrentAPI = EAPI::Unknown; } LogActiveIMEInfo(); }
void FWindowsTextInputMethodSystem::OnIMEActivationStateChanged(const bool bIsEnabled) { if(bIsEnabled) { // It seems that switching away from an IMM based IME doesn't generate a deactivation notification //check(CurrentAPI == EAPI::Unknown); FString APIString; const HKL KeyboardLayout = ::GetKeyboardLayout(0); if(::ImmGetIMEFileName(KeyboardLayout, nullptr, 0) > 0) { CurrentAPI = EAPI::IMM; UpdateIMMProperty(KeyboardLayout); APIString = TEXT("IMM"); } else { CurrentAPI = EAPI::TSF; APIString = TEXT("TSF"); } UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("IME system now activated using %s."), *APIString); } else { // It seems that switching away from an IMM based IME doesn't generate a deactivation notification //check(CurrentAPI != EAPI::Unknown); CurrentAPI = EAPI::Unknown; UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("IME system now deactivated.")); } }
bool FWindowsTextInputMethodSystem::InitializeIMM() { UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("Initializing IMM...")); IMMContextId = ::ImmCreateContext(); UpdateIMMProperty(::GetKeyboardLayout(0)); UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("Initialized IMM!")); return true; }
int32 FWindowsTextInputMethodSystem::ProcessMessage(HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam) { if(CurrentAPI != EAPI::IMM) { return DefWindowProc(hwnd, msg, wParam, lParam); } switch(msg) { case WM_INPUTLANGCHANGEREQUEST: case WM_INPUTLANGCHANGE: { HKL KeyboardLayoutHandle = reinterpret_cast<HKL>(lParam); UpdateIMMProperty(KeyboardLayoutHandle); return DefWindowProc(hwnd, msg, wParam, lParam); } break; case WM_IME_SETCONTEXT: { if(ActiveContext.IsValid()) { // Disable showing an IME-implemented composition window if we're going to draw the composition string ourselves. if(wParam && ShouldDrawIMMCompositionString()) { lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; } UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("Setting IMM context.")); } return DefWindowProc(hwnd, msg, wParam, lParam); } break; case WM_IME_NOTIFY: { return DefWindowProc(hwnd, msg, wParam, lParam); } break; case WM_IME_REQUEST: { return DefWindowProc(hwnd, msg, wParam, lParam); } break; case WM_IME_STARTCOMPOSITION: { if(ActiveContext.IsValid()) { BeginIMMComposition(); UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("Beginning IMM composition.")); } return DefWindowProc(hwnd, msg, wParam, lParam); } break; case WM_IME_COMPOSITION: { if(ActiveContext.IsValid()) { FInternalContext& InternalContext = ContextToInternalContextMap[ActiveContext]; check(InternalContext.IMMContext.IsComposing); HIMC IMMContext = ::ImmGetContext(hwnd); UpdateIMMWindowPositions(IMMContext); const bool bHasBeenCanceled = !lParam; const bool bHasCompositionStringFlag = !!(lParam & GCS_COMPSTR); const bool bHasResultStringFlag = !!(lParam & GCS_RESULTSTR); const bool bHasNoMoveCaretFlag = !!(lParam & CS_NOMOVECARET); const bool bHasCursorPosFlag = !!(lParam & GCS_CURSORPOS); // Canceled, so remove the compositing string if(bHasBeenCanceled) { CancelIMMComposition(); } // Check Result if(bHasResultStringFlag) { // If we're being deactivated, so we need to take the current selection so we can restore it properly // otherwise calling SetTextInRange can cause the cursor/selection to jump around uint32 SelectionBeginIndex = 0; uint32 SelectionLength = 0; ITextInputMethodContext::ECaretPosition SelectionCaretPosition = ITextInputMethodContext::ECaretPosition::Ending; ActiveContext->GetSelectionRange(SelectionBeginIndex, SelectionLength, SelectionCaretPosition); const FString ResultString = GetIMMStringAsFString(IMMContext, GCS_RESULTSTR); UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("WM_IME_COMPOSITION Result String: %s"), *ResultString); // Update Result ActiveContext->SetTextInRange(InternalContext.IMMContext.CompositionBeginIndex, InternalContext.IMMContext.CompositionLength, ResultString); if(InternalContext.IMMContext.IsDeactivating) { // Restore any previous selection ActiveContext->SetSelectionRange(SelectionBeginIndex, SelectionLength, SelectionCaretPosition); } else { // Once we get a result, we're done; set the caret to the end of the result and end the current composition ActiveContext->SetSelectionRange(InternalContext.IMMContext.CompositionBeginIndex + ResultString.Len(), 0, ITextInputMethodContext::ECaretPosition::Ending); } EndIMMComposition(); } // Check Composition if(bHasCompositionStringFlag) { const FString CompositionString = GetIMMStringAsFString(IMMContext, GCS_COMPSTR); UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("WM_IME_COMPOSITION Composition String: %s"), *CompositionString); // Not all IMEs send a cancel request when you press escape, but instead just set the string to empty // We need to cancel out the composition string here to avoid weirdness when you start typing again // Don't do this if we have a result string, as we'll have already called EndIMMComposition to finish the composition if(CompositionString.Len() == 0 && !bHasResultStringFlag) { CancelIMMComposition(); } // We've typed a character, so we need to clear out any currently selected text to mimic what happens when you normally type into a text input uint32 SelectionBeginIndex = 0; uint32 SelectionLength = 0; ITextInputMethodContext::ECaretPosition SelectionCaretPosition = ITextInputMethodContext::ECaretPosition::Ending; ActiveContext->GetSelectionRange(SelectionBeginIndex, SelectionLength, SelectionCaretPosition); if(SelectionLength) { ActiveContext->SetTextInRange(SelectionBeginIndex, SelectionLength, TEXT("")); } // If we received a result (handled above) then the previous composition will have been ended, so we need to start a new one now // This ensures that each composition ends up as its own distinct undo if(!InternalContext.IMMContext.IsComposing) { BeginIMMComposition(); } const int32 CurrentCompositionBeginIndex = InternalContext.IMMContext.CompositionBeginIndex; const uint32 CurrentCompositionLength = InternalContext.IMMContext.CompositionLength; // Update Composition Range InternalContext.IMMContext.CompositionLength = CompositionString.Len(); ActiveContext->UpdateCompositionRange(InternalContext.IMMContext.CompositionBeginIndex, InternalContext.IMMContext.CompositionLength); // Update Composition ActiveContext->SetTextInRange(CurrentCompositionBeginIndex, CurrentCompositionLength, CompositionString); } // Check Cursor if(!bHasNoMoveCaretFlag && bHasCursorPosFlag) { const LONG CursorPositionResult = ::ImmGetCompositionString(IMMContext, GCS_CURSORPOS, nullptr, 0); const int16 CursorPosition = CursorPositionResult & 0xFFFF; // Update Cursor UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("WM_IME_COMPOSITION Cursor Position: %d"), CursorPosition); ActiveContext->SetSelectionRange(InternalContext.IMMContext.CompositionBeginIndex + CursorPosition, 0, ITextInputMethodContext::ECaretPosition::Ending); } ::ImmReleaseContext(hwnd, IMMContext); UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("Updating IMM composition.")); } return DefWindowProc(hwnd, msg, wParam, lParam); } break; case WM_IME_ENDCOMPOSITION: { // On composition end, notify context of the end. if(ActiveContext.IsValid()) { EndIMMComposition(); UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("Ending IMM composition.")); } return DefWindowProc(hwnd, msg, wParam, lParam); } break; case WM_IME_CHAR: { // Suppress sending a WM_CHAR for this WM_IME_CHAR - the composition windows messages will have handled this. UE_LOG(LogWindowsTextInputMethodSystem, Verbose, TEXT("Ignoring WM_IME_CHAR message.")); return 0; } break; default: { UE_LOG(LogWindowsTextInputMethodSystem, Warning, TEXT("Unexpected windows message received for processing.")); return DefWindowProc(hwnd, msg, wParam, lParam); } break; } }