2025-11-28 00:35:46 +09:00

458 lines
13 KiB
C++

// 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<WORD>(0xF003)
#define THIRDPARTY_PREVPAGE static_cast<WORD>(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<UINT>(wch);
}
else if ((wch >= L'a') && (wch <= L'z'))
{
vkRet = (UINT)(L'A') + ((UINT)(L'z') - static_cast<UINT>(wch));
}
else if ((wch >= L'A') && (wch <= L'Z'))
{
vkRet = static_cast<UINT>(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();
}