// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved #include "Private.h" #include "Globals.h" #include "SampleIME.h" #include "CandidateListUIPresenter.h" #include "CompositionProcessorEngine.h" #include "KeyHandlerEditSession.h" #include "Compartment.h" // 0xF003, 0xF004 are the keys that the touch keyboard sends for next/previous #define THIRDPARTY_NEXTPAGE static_cast(0xF003) #define THIRDPARTY_PREVPAGE static_cast(0xF004) // Because the code mostly works with VKeys, here map a WCHAR back to a VKKey for certain // vkeys that the IME handles specially __inline UINT VKeyFromVKPacketAndWchar(UINT vk, WCHAR wch) { UINT vkRet = vk; if (LOWORD(vk) == VK_PACKET) { if (wch == L' ') { vkRet = VK_SPACE; } else if ((wch >= L'0') && (wch <= L'9')) { vkRet = static_cast(wch); } else if ((wch >= L'a') && (wch <= L'z')) { vkRet = (UINT)(L'A') + ((UINT)(L'z') - static_cast(wch)); } else if ((wch >= L'A') && (wch <= L'Z')) { vkRet = static_cast(wch); } else if (wch == THIRDPARTY_NEXTPAGE) { vkRet = VK_NEXT; } else if (wch == THIRDPARTY_PREVPAGE) { vkRet = VK_PRIOR; } } return vkRet; } //+--------------------------------------------------------------------------- // // _IsKeyEaten // //---------------------------------------------------------------------------- BOOL CSampleIME::_IsKeyEaten(_In_ ITfContext *pContext, UINT codeIn, _Out_ UINT *pCodeOut, _Out_writes_(1) WCHAR *pwch, _Out_opt_ _KEYSTROKE_STATE *pKeyState) { pContext; *pCodeOut = codeIn; BOOL isOpen = FALSE; CCompartment CompartmentKeyboardOpen(_pThreadMgr, _tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); CompartmentKeyboardOpen._GetCompartmentBOOL(isOpen); BOOL isDoubleSingleByte = FALSE; CCompartment CompartmentDoubleSingleByte(_pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); CompartmentDoubleSingleByte._GetCompartmentBOOL(isDoubleSingleByte); BOOL isPunctuation = FALSE; CCompartment CompartmentPunctuation(_pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentPunctuation); CompartmentPunctuation._GetCompartmentBOOL(isPunctuation); if (pKeyState) { pKeyState->Category = CATEGORY_NONE; pKeyState->Function = FUNCTION_NONE; } if (pwch) { *pwch = L'\0'; } // if the keyboard is disabled, we don't eat keys. if (_IsKeyboardDisabled()) { return FALSE; } // // Map virtual key to character code // BOOL isTouchKeyboardSpecialKeys = FALSE; WCHAR wch = ConvertVKey(codeIn); *pCodeOut = VKeyFromVKPacketAndWchar(codeIn, wch); if ((wch == THIRDPARTY_NEXTPAGE) || (wch == THIRDPARTY_PREVPAGE)) { // We always eat the above softkeyboard special keys isTouchKeyboardSpecialKeys = TRUE; if (pwch) { *pwch = wch; } } // if the keyboard is closed, we don't eat keys, with the exception of the touch keyboard specials keys if (!isOpen && !isDoubleSingleByte && !isPunctuation) { return isTouchKeyboardSpecialKeys; } if (pwch) { *pwch = wch; } // // Get composition engine // CCompositionProcessorEngine *pCompositionProcessorEngine; pCompositionProcessorEngine = _pCompositionProcessorEngine; if (isOpen) { // // The candidate or phrase list handles the keys through ITfKeyEventSink. // // eat only keys that CKeyHandlerEditSession can handles. // if (pCompositionProcessorEngine->IsVirtualKeyNeed(*pCodeOut, pwch, _IsComposing(), _candidateMode, _isCandidateWithWildcard, pKeyState)) { return TRUE; } } // // Punctuation // if (pCompositionProcessorEngine->IsPunctuation(wch)) { if ((_candidateMode == CANDIDATE_NONE) && isPunctuation) { if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_PUNCTUATION; } return TRUE; } } // // Double/Single byte // if (isDoubleSingleByte && pCompositionProcessorEngine->IsDoubleSingleByte(wch)) { if (_candidateMode == CANDIDATE_NONE) { if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_DOUBLE_SINGLE_BYTE; } return TRUE; } } return isTouchKeyboardSpecialKeys; } //+--------------------------------------------------------------------------- // // ConvertVKey // //---------------------------------------------------------------------------- WCHAR CSampleIME::ConvertVKey(UINT code) { // // Map virtual key to scan code // UINT scanCode = 0; scanCode = MapVirtualKey(code, 0); // // Keyboard state // BYTE abKbdState[256] = {'\0'}; if (!GetKeyboardState(abKbdState)) { return 0; } // // Map virtual key to character code // WCHAR wch = '\0'; if (ToUnicode(code, scanCode, abKbdState, &wch, 1, 0) == 1) { return wch; } return 0; } //+--------------------------------------------------------------------------- // // _IsKeyboardDisabled // //---------------------------------------------------------------------------- BOOL CSampleIME::_IsKeyboardDisabled() { ITfDocumentMgr* pDocMgrFocus = nullptr; ITfContext* pContext = nullptr; BOOL isDisabled = FALSE; if ((_pThreadMgr->GetFocus(&pDocMgrFocus) != S_OK) || (pDocMgrFocus == nullptr)) { // if there is no focus document manager object, the keyboard // is disabled. isDisabled = TRUE; } else if ((pDocMgrFocus->GetTop(&pContext) != S_OK) || (pContext == nullptr)) { // if there is no context object, the keyboard is disabled. isDisabled = TRUE; } else { CCompartment CompartmentKeyboardDisabled(_pThreadMgr, _tfClientId, GUID_COMPARTMENT_KEYBOARD_DISABLED); CompartmentKeyboardDisabled._GetCompartmentBOOL(isDisabled); CCompartment CompartmentEmptyContext(_pThreadMgr, _tfClientId, GUID_COMPARTMENT_EMPTYCONTEXT); CompartmentEmptyContext._GetCompartmentBOOL(isDisabled); } if (pContext) { pContext->Release(); } if (pDocMgrFocus) { pDocMgrFocus->Release(); } return isDisabled; } //+--------------------------------------------------------------------------- // // ITfKeyEventSink::OnSetFocus // // Called by the system whenever this service gets the keystroke device focus. //---------------------------------------------------------------------------- STDAPI CSampleIME::OnSetFocus(BOOL fForeground) { fForeground; return S_OK; } //+--------------------------------------------------------------------------- // // ITfKeyEventSink::OnTestKeyDown // // Called by the system to query this service wants a potential keystroke. //---------------------------------------------------------------------------- STDAPI CSampleIME::OnTestKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) { Global::UpdateModifiers(wParam, lParam); _KEYSTROKE_STATE KeystrokeState; WCHAR wch = '\0'; UINT code = 0; *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, &KeystrokeState); if (KeystrokeState.Category == CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION) { // // Invoke key handler edit session // KeystrokeState.Category = CATEGORY_COMPOSING; _InvokeKeyHandler(pContext, code, wch, (DWORD)lParam, KeystrokeState); } return S_OK; } //+--------------------------------------------------------------------------- // // ITfKeyEventSink::OnKeyDown // // Called by the system to offer this service a keystroke. If *pIsEaten == TRUE // on exit, the application will not handle the keystroke. //---------------------------------------------------------------------------- STDAPI CSampleIME::OnKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) { Global::UpdateModifiers(wParam, lParam); _KEYSTROKE_STATE KeystrokeState; WCHAR wch = '\0'; UINT code = 0; *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, &KeystrokeState); if (*pIsEaten) { bool needInvokeKeyHandler = true; // // Invoke key handler edit session // if (code == VK_ESCAPE) { KeystrokeState.Category = CATEGORY_COMPOSING; } // Always eat THIRDPARTY_NEXTPAGE and THIRDPARTY_PREVPAGE keys, but don't always process them. if ((wch == THIRDPARTY_NEXTPAGE) || (wch == THIRDPARTY_PREVPAGE)) { needInvokeKeyHandler = !((KeystrokeState.Category == CATEGORY_NONE) && (KeystrokeState.Function == FUNCTION_NONE)); } if (needInvokeKeyHandler) { _InvokeKeyHandler(pContext, code, wch, (DWORD)lParam, KeystrokeState); } } else if (KeystrokeState.Category == CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION) { // Invoke key handler edit session KeystrokeState.Category = CATEGORY_COMPOSING; _InvokeKeyHandler(pContext, code, wch, (DWORD)lParam, KeystrokeState); } return S_OK; } //+--------------------------------------------------------------------------- // // ITfKeyEventSink::OnTestKeyUp // // Called by the system to query this service wants a potential keystroke. //---------------------------------------------------------------------------- STDAPI CSampleIME::OnTestKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) { if (pIsEaten == nullptr) { return E_INVALIDARG; } Global::UpdateModifiers(wParam, lParam); WCHAR wch = '\0'; UINT code = 0; *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, NULL); return S_OK; } //+--------------------------------------------------------------------------- // // ITfKeyEventSink::OnKeyUp // // Called by the system to offer this service a keystroke. If *pIsEaten == TRUE // on exit, the application will not handle the keystroke. //---------------------------------------------------------------------------- STDAPI CSampleIME::OnKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) { Global::UpdateModifiers(wParam, lParam); WCHAR wch = '\0'; UINT code = 0; *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, NULL); return S_OK; } //+--------------------------------------------------------------------------- // // ITfKeyEventSink::OnPreservedKey // // Called when a hotkey (registered by us, or by the system) is typed. //---------------------------------------------------------------------------- STDAPI CSampleIME::OnPreservedKey(ITfContext *pContext, REFGUID rguid, BOOL *pIsEaten) { pContext; CCompositionProcessorEngine *pCompositionProcessorEngine; pCompositionProcessorEngine = _pCompositionProcessorEngine; pCompositionProcessorEngine->OnPreservedKey(rguid, pIsEaten, _GetThreadMgr(), _GetClientId()); return S_OK; } //+--------------------------------------------------------------------------- // // _InitKeyEventSink // // Advise a keystroke sink. //---------------------------------------------------------------------------- BOOL CSampleIME::_InitKeyEventSink() { ITfKeystrokeMgr* pKeystrokeMgr = nullptr; HRESULT hr = S_OK; if (FAILED(_pThreadMgr->QueryInterface(IID_ITfKeystrokeMgr, (void **)&pKeystrokeMgr))) { return FALSE; } hr = pKeystrokeMgr->AdviseKeyEventSink(_tfClientId, (ITfKeyEventSink *)this, TRUE); pKeystrokeMgr->Release(); return (hr == S_OK); } //+--------------------------------------------------------------------------- // // _UninitKeyEventSink // // Unadvise a keystroke sink. Assumes we have advised one already. //---------------------------------------------------------------------------- void CSampleIME::_UninitKeyEventSink() { ITfKeystrokeMgr* pKeystrokeMgr = nullptr; if (FAILED(_pThreadMgr->QueryInterface(IID_ITfKeystrokeMgr, (void **)&pKeystrokeMgr))) { return; } pKeystrokeMgr->UnadviseKeyEventSink(_tfClientId); pKeystrokeMgr->Release(); }