如何让全屏游戏的文本编辑框支持IME和TSF输入法

如何让全屏游戏的文本编辑框支持IME和TSF输入法

本文主要解决游戏在全屏状态下文本编辑框无法显示组词窗口和候选词窗口的问题
文章首发:http://blog.csdn.net/goodboychina/article/details/52067561

导致这个问题的原因是TSF与IME不兼容,且从Win7开始TSF(高级文字服务)默认是开启状态,并且无法关闭。这个情况在Win8下更严重。
要解决这个问题要从TSF框架的引入导致的兼容性问题入手。首先先了解下文本编辑框用到的IME相关的API和相关概念。

  • HKL:键盘布局,最初的含义就是单纯的键盘布局,能够将键盘的扫描码转换成设备无关的虚键码。现在HKL的含义更广泛,表示本地化标识符。

  • HKL名字:键盘布局编号,设备码和语言代码组成。前四位是设备码,后四位是语言码。注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts中列出了所有键盘布局。项名就是HKL名称。

    • “00000804” 表示 中文(简体) - 默认键盘
    • “E0220804” 表示 中文(简体) - 搜狗输入法
    • “E0230804” 表示 中文(简体) – 必应Bing输入法
  • API与消息

    • BOOL WINAPI GetKeyboardLayoutName(_Out_ LPTSTR pwszKLID); 可以获得当前键盘布局或输入法的HKL名字
    • WM_INPUTLANGCHANGE 消息可以获知输入法切换情况
  • 组词窗口 组词窗口显示了当前已经键入的字符序列

    • WM_IME_STARTCOMPOSITION:输入法进入组词模式,程序会收到这个消息。
    • WM_IME_ENDCOMPOSITION:输入法结束组词模式,程序会收到这个消息。
    • WM_IME_COMPOSITION:组词发生改变,程序会收到这个消息
  • 候选词窗口 候选词窗口列出可以选择的字或词

    • WM_IME_NOTIFY wParam==IMN_CHANGECANDIDATE:候选词发生变动
    • WM_IME_NOTIFY wParam==IMN_CLOSECANDIDATE:关闭候选词窗口
    • WM_IME_NOTIFY wParam==IMN_OPENCANDIDATE:打开候选词窗口

TSF对输入框架重新进行了设计,基于COM组件实现,部分原有的消息与API已不能正常工作

  • ITfTextInputProcessor:TIP 文字输入处理器
  • ITfInputProcessorProfiles:管理系统范围内的TIP
  • ITfInputProcessorProfileMgr:管理程序当前的TIP
  • ITfUIElementSink:接口类,实现此接口可以监听组词窗口和候选词窗口相关事件
  • ITfInputProcessorProfileActivationSink:接口类,实现此接口可以得知TIP的切换

程序需要作调整,能够同时支持TIP和IME

获取输入法名字示例代码

/* 获取HKL的显示名字
 */
bool GetLayoutName(const wchar_t* kl, wchar_t* nm )
{
    long lRet;
    HKEY hKey;
    static wchar_t tchData[64];
    DWORD dwSize;
    wchar_t keypath[200];

    wsprintfW(keypath, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", kl);

    lRet = RegOpenKeyExW(
        HKEY_LOCAL_MACHINE,
        keypath,
        0,
        KEY_QUERY_VALUE,
        &hKey
        );

    if (lRet == ERROR_SUCCESS)
    {
        dwSize = sizeof(tchData);
        lRet = RegQueryValueExW(
            hKey,
            L"Layout Text",
            NULL,
            NULL,
            (LPBYTE)tchData,
            &dwSize
            );
    }

    RegCloseKey(hKey);

    if (lRet == ERROR_SUCCESS && wcslen(nm) < 64)
    {
        wcscpy(nm, tchData);
        return true;
    }

    return false;
}

/* 获取当前输入法名字
 */
wchar_t* TsfApp::GetCurrentIMEName()
{
    static wchar_t _LastTipName[64];

    ZeroMemory(_LastTipName, sizeof(_LastTipName));

    TF_INPUTPROCESSORPROFILE tip;
    _pProfileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &tip);

    if (tip.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR)
    {
        BSTR bstrImeName = NULL;
        m_pProfiles->GetLanguageProfileDescription(tip.clsid, tip.langid, tip.guidProfile, &bstrImeName);
        if (wcslen(bstrImeName) < 64)
            wcscpy(_LastTipName, bstrImeName);

        SysFreeString(bstrImeName);
    }
    else if (tip.dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT)
    {
        static wchar_t klnm[KL_NAMELENGTH];
        if (GetKeyboardLayoutNameW(klnm))
        {
            GetLayoutName(klnm, _LastTipName);
        }
    }

    return _LastTipName;
}

TIP相关消息处理

struct TsfApp : public ITfUIElementSink, public ITfInputProcessorProfileActivationSink
{
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // ITfUIElementSink
    //   WM_IME_COMPOSITION、WM_IME_NOTIFY消息的替代者
    STDMETHODIMP BeginUIElement(DWORD dwUIElementId, BOOL *pbShow);
    STDMETHODIMP UpdateUIElement(DWORD dwUIElementId);
    STDMETHODIMP EndUIElement(DWORD dwUIElementId);

    // ITfInputProcessorProfileActivationSink
    //   WM_INPUTLANGUAGECHANGED消息的替代者
    STDMETHODIMP OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
        REFGUID guidProfile, HKL hkl, DWORD dwFlags);

    LONG _cRef;

    DWORD m_dwUIElementSinkCookie;
    DWORD m_dwAlpnSinkCookie;

    ITfThreadMgrEx              *m_pThreadMgrEx;
    ITfInputProcessorProfiles   *m_pProfiles;
    ITfInputProcessorProfileMgr *_pProfileMgr;

    BOOL SetupSinks();
    void ReleaseSinks();        
};

BOOL TsfApp::SetupSinks()
{
    CoInitialize(NULL);
    HRESULT hr;

    hr = CoCreateInstance(CLSID_TF_ThreadMgr,
        NULL,
        CLSCTX_INPROC_SERVER,
        __uuidof(ITfThreadMgrEx),
        (void**)&m_pThreadMgrEx);

    if (FAILED(hr))
    {
        return FALSE;
    }

    TfClientId cid;
    if (FAILED(m_pThreadMgrEx->ActivateEx(&cid, TF_TMAE_UIELEMENTENABLEDONLY)))
    {
        return FALSE;
    }

    ITfSource *srcTm;
    if (SUCCEEDED(hr = m_pThreadMgrEx->QueryInterface(__uuidof(ITfSource), (void **)&srcTm)))
    {
        srcTm->AdviseSink(__uuidof(ITfUIElementSink), (ITfUIElementSink*)this, &m_dwUIElementSinkCookie);
        srcTm->AdviseSink(__uuidof(ITfInputProcessorProfileActivationSink), (ITfInputProcessorProfileActivationSink*)this, &m_dwAlpnSinkCookie);
        srcTm->Release();
    }

    hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (LPVOID*)&m_pProfiles);

    if (FAILED(hr))
        return FALSE;

    m_pProfiles->QueryInterface(IID_ITfInputProcessorProfileMgr, (void **)&_pProfileMgr);
    return S_OK;
}

STDAPI TsfApp::QueryInterface(REFIID riid, void **ppvObj)
{
    if (ppvObj == NULL)
        return E_INVALIDARG;

    *ppvObj = NULL;
    if (IsEqualIID(riid, IID_IUnknown))
        *ppvObj = reinterpret_cast<IUnknown *>(this);
    else if (IsEqualIID(riid, __uuidof(ITfUIElementSink)))
        *ppvObj = (ITfUIElementSink *)this;
    else if (IsEqualIID(riid, __uuidof(ITfInputProcessorProfileActivationSink)))
        *ppvObj = (ITfInputProcessorProfileActivationSink*)this;
    else if (IsEqualIID(riid, __uuidof(ITfLanguageProfileNotifySink)))
        *ppvObj = (ITfLanguageProfileNotifySink*)this;

    if (*ppvObj)
    {
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDAPI TsfApp::BeginUIElement(DWORD dwUIElementId, BOOL *pbShow)
{
    /* 设置为FALSE表示隐藏UI */
    *pbShow = FALSE;
    return S_OK;
}

STDAPI TsfApp::UpdateUIElement(DWORD dwUIElementId)
{
    ITfUIElementMgr *lpMgr = NULL;
    ITfCandidateListUIElement *lpCandUI = NULL;
    ITfReadingInformationUIElement *lpReading = NULL;
    ITfUIElement *pElement = NULL;

    /* 获取候选词或组合词 */
    if(SUCCEEDED(m_pThreadMgrEx->QueryInterface(IID_ITfUIElementMgr, (void**)&lpMgr)))
    {
        if(SUCCEEDED(lpMgr->GetUIElement(dwUIElementId, &pElement)))
        {
            if (SUCCEEDED(pElement->QueryInterface(IID_ITfCandidateListUIElement, (void**)&lpCandUI)))
            {
                BSTR _sss;
                lpCandUI->GetString(0, &_sss);
                SysFreeString(_sss);
                SAFE_RELEASE(lpCandUI);
            }

            if (SUCCEEDED(pElement->QueryInterface(IID_ITfReadingInformationUIElement, (void**)&lpReading)))
            {
                BSTR _sss;
                lpReading->GetString(&_sss);
                SysFreeString(_sss);

                SAFE_RELEASE(lpReading);
            }

            SAFE_RELEASE(pElement);
        }
        SAFE_RELEASE(lpMgr);
    }

    return S_OK;
}

STDAPI TsfApp::EndUIElement(DWORD dwUIElementId)
{
    ITfDocumentMgr *pDocMgr=NULL;
    ITfContext *pContex = NULL;
    ITfContextView *pContexView = NULL;

    HWND hActiveHwnd = NULL;

    if (SUCCEEDED(m_pThreadMgrEx->GetFocus(&pDocMgr)))
    {
        if (SUCCEEDED(pDocMgr->GetTop(&pContex)))
        {
            if (SUCCEEDED(pContex->GetActiveView(&pContexView)))
            {
                pContexView->GetWnd(&hActiveHwnd);
                SAFE_RELEASE(pContexView);
            }
            SAFE_RELEASE(pContex);
        }
        SAFE_RELEASE(pDocMgr);
    }

    if (NULL != hActiveHwnd)
    {
        SendMessageW(hActiveHwnd, WM_IME_NOTIFY, IMN_CLOSECANDIDATE, 0);
    }

    return S_OK;
}

STDAPI TsfApp::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
    REFGUID guidProfile, HKL hkl, DWORD dwFlags)
{
    bool bActive = (dwFlags & TF_IPSINK_FLAG_ACTIVE);
    if (!bActive)
        return S_OK;

    if (dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR)
    {
        /* 输入法是 TIP */

        printf("TIP输入法 [%08X]\n", (unsigned int)hkl);
    }
    else if (dwProfileType & TF_PROFILETYPE_KEYBOARDLAYOUT)
    {
        /* 当前输入法是键盘布局或IME */

        printf("HKL/IME %08X\n", (unsigned int)hkl);
    }
    else
    {
        /* 不可能走到这里 */
        printf("dwProfileType unknown!!!\n");
    }

    return S_OK;
}

你可能感兴趣的:(默认分类)