zoukankan      html  css  js  c++  java
  • VC++在Win7和Win8系统下获得百度输入法的名字

    最近又开始研究输入法的bug了。。。真倒霉。。。这次是为了解决在微软拼音和谷歌拼音输入法开启的时候,Dynamic input输入的第一个数字(比如圆的直径)会丢失,这肯定是来自中国客户的抱怨,而且貌似是个大客户,上头催得紧,得罪不起,咋办,只能研究了呀!


    我们之前的逻辑是这样的:

    1. 在View里面监视KeyDown事件,假如收到的Char是VK_PROCESSKEY,那么就认为这是一个输入法字符,会触发输入法的Composition,处理的方法就是:

    a)把事件标记成已处理,这样就屏蔽了WM_CHAR消息;

    b)显示一个空的输入框~(对于用户来说,看到的是一个空的输入框,外加一个输入法自己显示的候选词窗口)。 对于普通的中文输入法来说(搜狗,QQ),这个是完美的,但是微软和谷歌则不一样,在输入法开启后,就算你输入的是数字!他发送的也是VK_PROCESSKEY消息,而且,不会出现候选词窗口,那么用户看到的就是一个空的输入框了(因为WM_CHAR被屏蔽了)。。。



    2. 那么解决方案是什么呢?。。。最简单的话,就是判断,这是不是一个真正的IME Char,或者说这个字符到底会不会触发IME Composition窗口!假如不会,那么就继续发送WM_CHAR消息,于是就有了这么如下代码:

    bool
    CAcDynInput::onExternalKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags, bool& bWantOnChar)
    {
        // IME
        if (nChar == VK_PROCESSKEY) {
    
            bool bImeEnterCompositionMode = true;
    
            // DID#1488000. The origin logic is that if nChar == VK_PROCESSKEY,
            // we assume it will turn IME into Composition Mode and show the candidate
            // words in IME's composition floating window. So we suppress the
            // WM_CHAR message and show an empty Dynamic Input Box and user 
            // will see the IME compostion window near by. But actually, for Microsoft 
            // Pinyin IME and Google Pinyin IME, if you type number '1', though the nChar
            // is still VK_PROCESSKEY, but it won't enter IME composition mode. As a result, 
            // the char is lost (bcz we suppress WM_CHAR message) and user only sees an
            // empty Dynamic Input Box.
            //
            // To Fix this issue, we need check whether the charactor the user inputs
            // will trun the IME into composition mode.
            //
            AcApView* pView = curView();
            if (pView != NULL) {
                bImeEnterCompositionMode = pView->isEnterImeCompositionMode();
                if (!bImeEnterCompositionMode) {
                    // To fix DID#1484305 (crash due to focus switching when IME is in
                    // composition mode), we eat(cancel) the first IME char and re-send
                    // it after focus is switched. For now, since we changed the logic 
                    // that we didn't suppress WM_CHAR message in this case, then we should 
                    // not re-send it, or we will get double char. So here we suppress
                    // re-send the eaten char once.
                    pView->suppressResendEatenImeChar();
                }
            }
            if (bImeEnterCompositionMode) {
                // Suppress WM_CHAR and show empty Dynamic Input Box with IME composition
                // window near by.
                resetKeyDownHandled();
                return onExternalChar(nChar, nRepCnt, nFlags);
            }
        }
    
        。。。。。。
    }
    

    上面有一个pView->suppressResendEatenImeChar()别去管他,将来我写另外一篇博文的时候会用到,主要是为了修另外一个bug,做的特殊处理,防止一个输入法字符发送2次。
    注意到,我上面用了一个函数pView->isEnterImeCompositionMode(),这个函数的代码:

    bool CAcDwgView::isEnterImeCompositionMode() const
    {
        bool bEnterCompositionMode = true;
        HWND hwnd = this->GetSafeHwnd();
        HIMC hIMC = ImmGetContext(hwnd);
        if (hIMC != NULL) 
        {
            if (!ImmGetCompositionString(hIMC, GCS_COMPSTR, NULL, 0))
            {
                bEnterCompositionMode = false;
    
                // Fix for Baidu IME, Baidu IME said that they are using "async" to 
                // handle ImmGetCompositionString call, so if we call the function in 
                // WM_KEYDOWN message, it always return false. So for now, there is no
                // easy way to tell whether the Baidu IME is really entering composition
                // mode.
                if (isCurrentImeBaiduPinyinIme())
                    bEnterCompositionMode = true;
            }
            ImmReleaseContext(hwnd, hIMC);
            hIMC = NULL;
        }
    
        return bEnterCompositionMode;
    }

    我用的是ImmGetCompositionString()函数来判断,当前输入法是不是进入了Composition模式,其实他的返回值应该能反应真实的情况,但是有一个万恶的输入法,对于你输入的第一个字符(就是你输入的触发输入法进入Composition模式的第一个字符),其实他已经进入了组词模式了,但是他返回给你的是Fasle。为了解决这个问题,我特地在他们官方论坛提交了bug:http://pcbbs.baidu.com/thread-334055-1-2.html,他们回复告知,他们就是这么设计的,使用了异步查询的方法(我一头雾水。。。)。既然他们无法修改设计,那么只能我做特殊处理了。isCurrentImeBaiduPinyinIme()函数就应运而生!代码如下:

    bool CAcDwgView::isCurrentImeBaiduPinyinIme() const
    {
        // Check if the current OS is Win8
        // Note, since GetVersionInfoEx will be deprecated in Win8, we can replace the
        // following code snippets with IsWindows8OrGreater() API (in versionhelpers.h) 
        // once we move to Visual Studio 2013.
        bool isWindows8OrGreater = false;
        OSVERSIONINFOEX osvi;
        ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
        osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
        if (VERIFY(GetVersionEx((OSVERSIONINFO*)&osvi)))
            isWindows8OrGreater = (osvi.dwMajorVersion >= 6 && osvi.dwMinorVersion > 1);
    
        DWORD dwThreadId = GetWindowThreadProcessId(this->GetSafeHwnd(), NULL); 
        HKL hkl = GetKeyboardLayout(dwThreadId);
    
        // Get IME name
        CString szImeName;
        if (!isWindows8OrGreater)
        {
            ImmGetDescription(hkl,szImeName.GetBuffer(MAX_PATH), MAX_PATH); 
            szImeName.ReleaseBuffer();
        }
        else
        {
            // Baidu IME in Win8 uses TSF(Text service framework) which no longer 
            // supports ImmGetDescription() API
    
            HRESULT hr = S_OK;
            CComPtr<ITfInputProcessorProfiles> pProfiles;
            LANGID langid;
            BSTR bstrImeName = NULL;
    
            hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (LPVOID*)&pProfiles);
            if(!VERIFY(SUCCEEDED(hr)))
                return false;
    
            hr = pProfiles->GetCurrentLanguage(&langid);
            if(!VERIFY(SUCCEEDED(hr)))
                return false;
    
            CLSID textSrvId, profileId;
            hr = pProfiles->GetDefaultLanguageProfile(langid, GUID_TFCAT_TIP_KEYBOARD, &textSrvId, &profileId);
            if(!VERIFY(SUCCEEDED(hr)))
                return false;
    
            hr = pProfiles->GetActiveLanguageProfile(textSrvId, &langid, &profileId);
            if(!VERIFY(SUCCEEDED(hr)))
                return false;
    
            hr = pProfiles->GetLanguageProfileDescription(textSrvId, langid, profileId, &bstrImeName);
            if(!VERIFY(SUCCEEDED(hr)))
                return false;
    
            szImeName = bstrImeName;
            SysFreeString(bstrImeName);
        }
    
        // Encode Chinese words "BaiDu" since our source code file is in ASCII encoding
        static const TCHAR szBaidu[] = _T("u767eu5ea6");
        return (szImeName.Find(szBaidu) != -1);
    }
    

    这个逻辑很简单,就是判断用户使用的是不是百度输入法。这个函数代码量还是有一些的,但是原理很简单也很丑陋,获取输入法名字,搜索中文“百度”一词。。。这个和百度输入法开发人员确认过了,他们保证,他们输入法名字里面肯定有百度二字!这个函数的具体分析先放一放,先理一遍思路:

    1. 截获了WM_KEYDOWN,假如是一个VK_PROCESSKEY,那么就再判断 --> 是不是输入法进入了组词模式?是的话,那么就是真实的IME,就吞了WM_CHAR,显示空的输入框和IME窗口;假如不是,那么就发送WM_CHAR,让输入框正常显示出来,那个按键也自动会发送的输入框。

    2. 对于百度输入法,首先WM_KEYDOWN里面如果用户的输入不会触发组词模式,那么收到的就不会是VK_PROCESSKEY,那就正常发送WM_CHAR消息;假如是VK_PROCESSKEY,那么就认为他肯定进入了组词模式,从而显示空的输入框和候选词窗口,没问题~


    好,现在就可以仔细看看isCurrentImeBaiduPinyinIme()这个函数了,他里面取得了操作系统的版本号:

    a)假如是win7,那么直接调用ImmGetDescription()。

    b)如果是win8,Imm*** 的API很多就不能用了,因为Win8开始就使用了TSF(Text Service Framework),TSF非常复杂,使用了一系列的COM接口来操作IME,但是官方的文档都偏重于讲如果去开发一个输入法,很少提及,外部程序如果使用这些COM接口操作输入法。但是这篇博客很好的介绍了TSF的架构,这是由微软Bing输入法团队写的一篇博文:http://blog.csdn.net/mspinyin/article/details/6137709。我也会专门写一篇博文,讲述TSF(在这里:http://blog.csdn.net/puncha/article/details/13293665)。

    看了微软团队的博文,只是有了个大概,具体怎么使用那些COM API呢?费劲千辛万苦,终于找到了这个帖子:http://social.technet.microsoft.com/Forums/office/zh-CN/002efcfc-8d21-4674-b93b-53c8424d448e/vista-api-immgetdescription?forum=2087,在最后一个评论里面,有人贴了一段代码!那段代码就是用来获取输入法名字的!而,我使用的代码也是基于他写的,逻辑是:

    a)构造ITfInputProcessorProfiles接口。

    b)根据当前的langid调用pProfiles->DefaultLanguageProfile(),这是为了获取Text Service ID。

    c)有了上面取得的Text Service ID,就可以获取Profile ID了(上面也获取到了一个,但是没用,是defaut的,不是active的,真累。。)

    d)有了Text Service ID、Profile ID调用pProfiles->GetLanguageProfileDescription()就能取得输入法名字了!

    要注意,对于百度输入法,Win8的分支代码在Win7下不能用,会崩溃。同样,在Win7的分支代码在Win8下也无效。为什么呢?我个人理解,Windows有2个输入法框架,IMM和TSF,Win7操作系统上,输入法基本上只实现了IMM框架,所以Imm*** API都正常。而Win8下,那些输入法都只实现了TSF框架,导致了Imm*** API不能用。但是Bing输入法是例外, 也正如他们团队那篇博文所述“按照微软的说法,TSF会最终取代IMM框架。而微软拼音基于兼容,功能和性能方面的原因,将这两个框架都实现了。所以Bing输入法在Win8还是能通过ImmGetDescription()来获取输入法名字。






  • 相关阅读:
    mysql GRANT ALL PRIVILEGES 限制某个或所有客户端都可以连接至mysql
    MySql开启远程用户登录GRANTALLPRIVILEGESON*.*TO'root'@'%'I MySql开启远程用户登录GRANTALLPRIVILEGESON*.*TO'root'@'%'I
    php中 -> 和 => 和 :: 的用法 以及 self 和 $this 的用法
    mysql case when then else end 的用法
    C/C++ 程序的build过程
    Git 笔记
    English Snippets
    Ubuntu 使用笔记
    在CentOS上安装Sublime Text
    hihoCoder #1379 Emulator
  • 原文地址:https://www.cnblogs.com/puncha/p/3876869.html
Copyright © 2011-2022 走看看