锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 开源技术 / Windows输入法技术TSF理论摘抄和源代码剖析摘抄
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883

Windows输入法技术TSF理论摘抄和源代码剖析摘抄

自己线索总结

CCompositionProcessorEngine和码表有关,拼音转换结果有关
_pTableDictionaryEngine码表相关
KeyHandler.cpp是处理加入的键
CCandidateWindow候选窗口
以前的状态栏变成什么了?好像和语言栏有关系
InitializeSampleIMECompartment,初始化com组件
SetupConfiguration();
TF_PRESERVEDKEY预留键
pKS = pKeystroke->Append();
添加一个并返回添加的指针

数字签名

Requirements for Windows 8 IMEs

A third-party IME must meet these requirements:

  • Must be digitally signed.
  • Must be Text Services Framework (TSF) aware, and proper IME flags must be set to run properly in Windows 8.
  • Must follow UX guidelines for Metro style apps and be compatible with Metro style apps.

A third-party IME that doesn't meet these requirements is blocked from running in the Metro environment, but it can still run on the desktop.
Also, Windows Defender removes malicious IMEs from the system. Because of this, it's important that you familiarize yourself with the IME coding requirements for Windows 8. For more info, see Guidelines and checklist for IME development.
---------------------------------------------------------------
是不是一个TextService.dll没有数字签名时,metro下就不允许调用该输入法呢?还有,个人数字证书(非受信任的)对其进行签名有用吗?
你的所有以来的可执行代码需要签名。
你无法确保所有的用户都会手动将你的证书加为受信用户,所以要使用受信机构颁发的证书。

架构理解

不过根据我的了解,TFS分为两部分,一部分是接受文字输入的地方,另一部分就是接受文字输出的地方。一般认为如果你非要这么做的话,你可能需要伪造一个看不见的输入框,然后强制打开某个输入法来输入文字,然后你再设置sink监听回来。

通用元素

接口或界面

问题总结

正常情况下,使用ITfKeystrokeMgr::PreserveKey(...)注册的组合键事件,应该在ITfKeyEventSink::OnPreservedKey()中获得响应。
但在某些程序中,虽然ITfKeystrokeMgr::PreserveKey(...)成功,但是当按下组合键时,ITfKeyEventSink::OnPreservedKey()却无法响应。

已经发现的程序有:
1)IE在打开google.com.hk之后,页面内的搜索框内无法响应
2)QQ聊天窗口中,无法响应
附:在windows 8.1其它所有程序中可正常使用的输入法程序,下载页面http://chinput.com/thread-1768-1-1.html,可在上述程序及其它程序中尝试使用SHIFT+SPACE切换半角/全角,即可发现问题。

win7下的微软拼音,以及win8下的搜狗或QQ输入法等,都不再使用旧的IMM了,我用
https://social.technet.microsoft.com/Forums/office/zh-CN/002efcfc-8d21-4674-b93b-53c8424d448e/vista-api-immgetdescription?forum=2087
这个帖子里的代码来获取当前激活的TSF输入法,以及强行激活为别的输入法,都可以成功,但仅限于当前程序自己创建的窗口,我现在想用自己的后台程序,获取和设置任意前台窗口的TSF输入法,总是无效(函数返回都是S_OK)
我试过用ITfThreadMgr的AssociateFocus(GetForegroundWindow(), pDocMgr, &pPrevDocMgr)
还有AttachThreadInput(GetWindowThreadProcessId(GetForegroundWindow(), NULL), GetCurrentThreadId(), TRUE); 依然没用,用IMM相关函数都可以设置的。。。请问下大神我这个需求在TSF上可以实现吗?

Win8支持

的确是这样,输入法直接在Win8style程序是无法使用的。需要进行声明:
To declare an IME as compatible with Metro style apps, set the dwCaps field withTF_IPP_CAPS_IMMERSIVESUPPORT.

怎么安装

微软网站上也没说编程安装的方法,不过说了用installshield可以安装

例子

例程
链接:http://pan.baidu.com/s/1kTxmnnp 密码:mxjb
求帮忙调试一下Tibetan_input下的那个源码,第二个可以作为参考。(其实两个都有很多问题)

安装

CTF框架

CTF框架下,一个输入法为一个TIP(Text Input Processor),其首先必须注册为一个COM组件。通过ITfInputProcessorProfileMgr::RegisterProfile()接口注册TIP的CLSID和ProfileID。这等价于下面写注册表的方式:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\TIP\{CLSID}\LanguageProfile\[langid]\{guidProfile}
Description=SZ:
IconFile=SZ:
IconIndex=DWORD:
Enable=DWORD:[0|1]
SubstituteLayout=SZ:
CLSID 代表TIP,同时指容纳TIP的COM的GUID,ProfileID是指具体某个输入法的ID,一个COM可以包含多个输入法ProfileID。譬如,微软拼音2010就在一个COM中实现了两个输入法:新体验和简捷,以满足不同用户需求。
或者使用老接口来注册
1) 通过ITfInputProcessorProfiles::Register()注册CLSID
2) 通过ITfInputProcessorProfiles::AddLanguageProfile()添加language profile
-可以添加不同语言的多种的profile
这等价于:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\TIP\{CLSID}\LanguageProfile\[langid]\{guidProfile}
Description=SZ:
IconFile=SZ:
IconIndex=DWORD:
3) 通过ITfInputProcesorProfiles::EnableLanguageProfileByDefault()来缺省Enable或disable 某profile.
- 这个设置是系统级别,即应用于不同系统中的不同用户.
- 如果没有调用此接口,默认是enable
- 可以在HKCU中覆盖此设置
这等价于:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\TIP\{CLSID}\LanguageProfile\[langid]\{guidProfile}
Enable=DWORD:[0|1]
4) 设置profile的名字:调用ITfInputProcessorProfilesEx::SetLanguageProfileDisplayName().
- 可选步骤. 注意设置不同语言的名字。
这等价于:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\TIP\{CLSID}\LanguageProfile\[langid]\{guidProfile}
Display Description=SZ:
5) 设置可替换的keyboard layout (仅使用键盘TIP)
- ITfInputProcessorProfiles::SubstituteKeyboardLayout() 为profile设置可替换的hkl。
当焦点从Cicero aware 的控件切换到non-Cicero aware的控件上时,这个hkl会被用到。这等价于:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\TIP\{CLSID}\LanguageProfile\[langid]\{guidProfile}
SubstituteLayout=SZ:
可选 – 在控制面板输入法对话框中隐藏profile
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\TIP\{CLSID}\LanguageProfile\[langid]\{guidProfile}
HiddenInSettingUI=DWORD:[0|1]
如果此键值不存在,则默认为0,即此profile显示在控制面板输入法对话框中
顺便提一下,在当前用户下设置默认输入法:
ITfInputProcessorProfile::SetDefaultLanguageProfile()
这只影响到新创建线程,而不会对已经运行的线程产生影响。当然,重启后,在所有线程都会生效。次接口只会影响当前用户,对系统中其他用户无影响
这等价于:
HKEY_CURRENT_USER\SOFTWARE\Microsoft\CTF\Assembly\[langid]\{TIP’sCategory}
Default=SZ:TIP’sCLSID
KayboardLayout=DWORD:
Profile=SZ:TIP’s guidProfile
从上面可以看到,无论哪种框架,都需要向注册表HKEY_LOCAL_MACHINE路径写入输入法信息,另外不同输入法也可能注册自己的组件到操作系统中,所以安装时:
第一,需要administrator权限;要求所用户必须属于administrators组的成员;
第二,如果系统中安装了某些安全软件,其可能阻挡写入注册表系统路径(如HKEY_LOCAL_MACHINE),这时候安装就不能成功。要么暂时关闭其功能,要么在其提示是选择允许写入,要么卸载它后再安装。

重要引导

在了解了TSF的强大之后,很容易产生一个疑问,TSF是如何将应用程序和 Text Service 隔离开的呢?这里简单介绍下TSF 的工作原理。
首先需要知道,基于TSF 框架的输入法 实际上是一个COM程序。也就是说,微软为我们提供了很多的虚基类,然后我们需要实现一个COM 程序。
(1)首先要确认,在应用程序和 Text Service 之间进行传递的是一个 text stream(文本流),既然是 text stream, 肯定要有text(可以理解成 text stream 的载体),比如说notepad,word,各种输入框,都可以理解成是一个text。TSF 的处理是首先由应用程序创建一个 Thread Manager,创建方法是通过 CoCreateInstance 创建一个组件对象,对应的,微软提供的接口是 ITfThreadMgr。
(2)创建好Thread Manager 之后,用 Thread Manager 来创建一个 Document Manager(文档管理器),方法是 ITfThreadMgr::CreateDocumentMgr。应用程序会为每一个不同的Document 创建一个 Document Manager
(3)创建 Document Manager 后,用 ITfDocumentMgr 来创建一个 edit context,方法是 ITfDocumentMgr::CreateContext。
实际上,Thread Manager 为每个 Document Manager 都维护了一个 context stack,新创建的context 被压入到了栈中。
那么,Text Service 是如何往context 中写入text stream 的呢?对于这个问题,首先Text Service 要获得一个context。
当 Text Service 获取一个context 时,很容易想到,此时可能有很多个Document Manager,可能有更多的context,到底获取哪一个呢?
(1)首先获取当前处于焦点的Document Manager,采用的方法是ITfThreadMgr::GetFocus,得到一个Document Manager 对象
(2)获取之前得到的 Document Manager 的 context stack 中的栈顶 context,方法是 ITfDocumentMgr::GetTop
至此,应用程序和 Text Service 通过 Thread Manager 创建的某个 context 产生了连接。
那么,TSF 是如何进行 Text Stores(文本存储)呢?这个问题,微软也为我们提供了相应的接口。
比如说,我们可以实现一个TTsfTextStore,可以继承 ITextStoreAcp,这个接口中有一些函数,可以在TTsfTextStore 中实现这些函数,这些函数中就有传递 text stream 的实现。给出一个ITextStoreAcp 中的函数列表:
/* ITextStoreACP Interfaces */
HRESULT       STDMETHODCALLTYPE     AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask);
HRESULT    STDMETHODCALLTYPE      UnadviseSink(IUnknown* punk);
HRESULT    STDMETHODCALLTYPE      RequestLock(DWORD dwLockFlags, HRESULT* phrSession);
HRESULT    STDMETHODCALLTYPE      GetStatus(TS_STATUS* pdcs);
HRESULT    STDMETHODCALLTYPE      QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch, LONG* pacpResultStart, LONG* pacpResultEnd);
HRESULT    STDMETHODCALLTYPE      GetSelection(ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP* pSelection, ULONG* pcFetched);
HRESULT    STDMETHODCALLTYPE      SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection);
HRESULT    STDMETHODCALLTYPE      GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain, ULONG cchPlainReq, ULONG* pcchPlainOut, TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq, ULONG* pulRunInfoOut, LONG* pacpNext);
HRESULT    STDMETHODCALLTYPE      SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd, const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange);
HRESULT    STDMETHODCALLTYPE      GetFormattedText(LONG acpStart, LONG acpEnd, IDataObject* *ppDataObject);
HRESULT    STDMETHODCALLTYPE      GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid, IUnknown* *ppunk);
HRESULT    STDMETHODCALLTYPE      QueryInsertEmbedded(const GUID* pguidService, const FORMATETC* pFormatEtc, BOOL* pfInsertable);
HRESULT    STDMETHODCALLTYPE      InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd, IDataObject* pDataObject, TS_TEXTCHANGE* pChange);
HRESULT    STDMETHODCALLTYPE      RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID* paFilterAttrs);
HRESULT    STDMETHODCALLTYPE      RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID* paFilterAttrs, DWORD dwFlags);
HRESULT    STDMETHODCALLTYPE         RequestAttrsTransitioningAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID* paFilterAttrs, DWORD dwFlags);
HRESULT    STDMETHODCALLTYPE      FindNextAttrTransition(LONG acpStart, LONG acpHalt, ULONG cFilterAttrs, const TS_ATTRID* paFilterAttrs, DWORD dwFlags, LONG* pacpNext, BOOL* pfFound, LONG* plFoundOffset);
HRESULT    STDMETHODCALLTYPE      RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals, ULONG* pcFetched);
HRESULT    STDMETHODCALLTYPE      GetEndACP(LONG* pacp);
HRESULT    STDMETHODCALLTYPE      GetActiveView(TsViewCookie* pvcView);
HRESULT    STDMETHODCALLTYPE      GetACPFromPoint(TsViewCookie vcView, const POINT* pt, DWORD dwFlags, LONG* pacp);
HRESULT    STDMETHODCALLTYPE      GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT* prc, BOOL* pfClipped);
HRESULT    STDMETHODCALLTYPE      GetScreenExt(TsViewCookie vcView, RECT* prc);
HRESULT    STDMETHODCALLTYPE      GetWnd(TsViewCookie vcView, HWND* phwnd);
HRESULT    STDMETHODCALLTYPE      InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText, ULONG cch, LONG* pacpStart, LONG* pacpEnd, TS_TEXTCHANGE* pChange);
HRESULT    STDMETHODCALLTYPE      InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject, LONG* pacpStart, LONG* pacpEnd, TS_TEXTCHANGE* pChange);
当然,通常情况下,我们不需要实现全部的函数,只需要根据自己的需求,实现部分函数即可。

状态栏

有个问题困惑我好几天了,不知大家可否帮我解惑,以google输入法为例

把输入光标放到一个文本文件中,并选择google输入法后,会出现一个状态栏。如果把光标移到别的程序中,google输入法被切换掉后,该状态栏消失

现在问题是,我根据tsf写的输入法,用什么事件接收器能感知到当前context失去输入焦点了?
我现在是在()中把状态栏显示出来,然后在ITfTextInputProcessor:: Deactive()中销毁,现在对现象是打开文本选择输入法,状态栏出现,直到文本关闭后状态栏才能销毁。找了好久也没找到正确对方法,还望大家不吝赐教!

UINT WMAPP_FOCUS = RegisterWindowMessage(L"TargetAppFocus");

STDAPI CTextService::OnSetFocus(ITfDocumentMgr *pDocMgrFocus, ITfDocumentMgr *pDocMgrPrevFocus)
{
PostMessage(_hStatusWnd, WMAPP_FOCUS, NULL, 0);

_InitTextEditSink(pDocMgrFocus);

return S_OK;
}


LRESULT WINAPI StatusWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//----处理 目标窗口 焦点 切换----//
if(message==WMAPP_FOCUS)
{
if(GetFocus()==NULL)
ShowWindow(hWnd, SW_HIDE);
else
ShowWindow(hWnd, SW_SHOWNOACTIVATE);
return 0L;
}

问题flip selection,和flip doc不起作用。

输入法注册:
编译生成tsfcase.dll文件到指定路径,如:x:\tsfcase.dll,然后用Regsvr32.exe x:\tsfcase.dll注册输入法(vista下需要写成bat文件,然后以管理员身份执行)。然后,打开一个文本文件选择英文输入法,再选择该输入法,它的名字是Case Text Service,该输入法会在语言栏上额外加一个图标,点击它弹出操作菜单。包括show snoop wnd(显示监视窗口),hello world(插入字符串hello world!),flip selcetion(转换选中字符串的大小写),flip doc(转换整个文档的大小写),flip keystrokes(转换键盘输入大小写)

调试:
与一般dll调试类似,在调试命令中加入x:\windows\nodepad.exe
然后调试启动nodepad,选择该输入法,即可进入断点。

问题:
现在问题是flip selection,和flip doc不起作用。
跟踪至函数ToggleCase发现,下面这句有问题:
if (pRange->GetText(ec, dwFlags, achText, ARRAYSIZE(achText), &cch) != S_OK)
cch始终是0,致使该循环break跳出。
该函数如下:
void ToggleCase(TfEditCookie ec, ITfRange *pRange, BOOL fIgnoreRangeEnd)
{
ITfRange *pRangeToggle;
ULONG cch;
ULONG i;
DWORD dwFlags;
WCHAR achText[64];

// backup the current range
if (pRange->Clone(&pRangeToggle) != S_OK)
return;

dwFlags = TF_TF_MOVESTART | (fIgnoreRangeEnd ? TF_TF_IGNOREEND : 0);

while (TRUE)
{
// grab the next block of chars
if (pRange->GetText(ec, dwFlags, achText, ARRAYSIZE(achText), &cch) != S_OK)
break;

// out of text?
if (cch == 0)
{
break;
}

// toggle the case
for (i=0; i<cch; i++)
{
achText[i] = ToggleChar(achText[i]);
}

// shift pRangeToggle so it covers just the text we read
if (pRangeToggle->ShiftEndToRange(ec, pRange, TF_ANCHOR_START) != S_OK)
break;

// replace the text
pRangeToggle->SetText(ec, 0, achText, cch);

// prepare for next iteration
pRangeToggle->Collapse(ec, TF_ANCHOR_END);
}

pRangeToggle->Release();
}

是因为 notepad 的 tsf 支持是通过 CUAS实现的. 所以並不完全支持 TSF 的 text store界面来对文字内容存取.

友情链接
版权所有 Copyright(c)2004-2021 锐英源软件
公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768
地址:郑州大学北校区院(文化路97号院)内