msctf/tests: Test ITfContext Push, Pop, GetTop and GetBase.
[wine] / dlls / msctf / tests / inputprocessor.c
1 /*
2  * Unit tests for ITfInputProcessor
3  *
4  * Copyright 2009 Aric Stewart, CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdio.h>
22
23 #define COBJMACROS
24 #include "wine/test.h"
25 #include "winuser.h"
26 #include "initguid.h"
27 #include "shlwapi.h"
28 #include "shlguid.h"
29 #include "comcat.h"
30 #include "msctf.h"
31
32 static ITfInputProcessorProfiles* g_ipp;
33 static LANGID gLangid;
34 static ITfCategoryMgr * g_cm = NULL;
35 static ITfThreadMgr* g_tm = NULL;
36 static ITfDocumentMgr *g_dm = NULL;
37 static TfClientId cid = 0;
38 static TfClientId tid = 0;
39
40 #define SINK_UNEXPECTED 0
41 #define SINK_EXPECTED 1
42 #define SINK_FIRED 2
43
44 static BOOL test_ShouldActivate = FALSE;
45 static BOOL test_ShouldDeactivate = FALSE;
46
47 static DWORD tmSinkCookie;
48 static DWORD tmSinkRefCount;
49 static ITfDocumentMgr *test_CurrentFocus = NULL;
50 static ITfDocumentMgr *test_PrevFocus = NULL;
51 static INT  test_OnSetFocus = SINK_UNEXPECTED;
52 static INT  test_OnInitDocumentMgr = SINK_UNEXPECTED;
53 static INT  test_OnPushContext = SINK_UNEXPECTED;
54 static INT  test_OnPopContext = SINK_UNEXPECTED;
55
56 HRESULT RegisterTextService(REFCLSID rclsid);
57 HRESULT UnregisterTextService();
58 HRESULT ThreadMgrEventSink_Constructor(IUnknown **ppOut);
59
60 DEFINE_GUID(CLSID_FakeService, 0xEDE1A7AD,0x66DE,0x47E0,0xB6,0x20,0x3E,0x92,0xF8,0x24,0x6B,0xF3);
61 DEFINE_GUID(CLSID_TF_InputProcessorProfiles, 0x33c53a50,0xf456,0x4884,0xb0,0x49,0x85,0xfd,0x64,0x3e,0xcf,0xed);
62 DEFINE_GUID(CLSID_TF_CategoryMgr,         0xA4B544A1,0x438D,0x4B41,0x93,0x25,0x86,0x95,0x23,0xE2,0xD6,0xC7);
63 DEFINE_GUID(GUID_TFCAT_TIP_KEYBOARD,     0x34745c63,0xb2f0,0x4784,0x8b,0x67,0x5e,0x12,0xc8,0x70,0x1a,0x31);
64 DEFINE_GUID(GUID_TFCAT_TIP_SPEECH,       0xB5A73CD1,0x8355,0x426B,0xA1,0x61,0x25,0x98,0x08,0xF2,0x6B,0x14);
65 DEFINE_GUID(GUID_TFCAT_TIP_HANDWRITING,  0x246ecb87,0xc2f2,0x4abe,0x90,0x5b,0xc8,0xb3,0x8a,0xdd,0x2c,0x43);
66 DEFINE_GUID (GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER,  0x046B8C80,0x1647,0x40F7,0x9B,0x21,0xB9,0x3B,0x81,0xAA,0xBC,0x1B);
67 DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0);
68 DEFINE_GUID(CLSID_TF_ThreadMgr,           0x529a9e6b,0x6587,0x4f23,0xab,0x9e,0x9c,0x7d,0x68,0x3e,0x3c,0x50);
69
70
71 static HRESULT initialize(void)
72 {
73     HRESULT hr;
74     CoInitialize(NULL);
75     hr = CoCreateInstance (&CLSID_TF_InputProcessorProfiles, NULL,
76           CLSCTX_INPROC_SERVER, &IID_ITfInputProcessorProfiles, (void**)&g_ipp);
77     if (SUCCEEDED(hr))
78         hr = CoCreateInstance (&CLSID_TF_CategoryMgr, NULL,
79           CLSCTX_INPROC_SERVER, &IID_ITfCategoryMgr, (void**)&g_cm);
80     if (SUCCEEDED(hr))
81         hr = CoCreateInstance (&CLSID_TF_ThreadMgr, NULL,
82           CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, (void**)&g_tm);
83     return hr;
84 }
85
86 static void cleanup(void)
87 {
88     if (g_ipp)
89         ITfInputProcessorProfiles_Release(g_ipp);
90     if (g_cm)
91         ITfCategoryMgr_Release(g_cm);
92     if (g_tm)
93         ITfThreadMgr_Release(g_tm);
94     CoUninitialize();
95 }
96
97 static void test_Register(void)
98 {
99     HRESULT hr;
100
101     static const WCHAR szDesc[] = {'F','a','k','e',' ','W','i','n','e',' ','S','e','r','v','i','c','e',0};
102     static const WCHAR szFile[] = {'F','a','k','e',' ','W','i','n','e',' ','S','e','r','v','i','c','e',' ','F','i','l','e',0};
103
104     hr = RegisterTextService(&CLSID_FakeService);
105     ok(SUCCEEDED(hr),"Unable to register COM for TextService\n");
106     hr = ITfInputProcessorProfiles_Register(g_ipp, &CLSID_FakeService);
107     ok(SUCCEEDED(hr),"Unable to register text service(%x)\n",hr);
108     hr = ITfInputProcessorProfiles_AddLanguageProfile(g_ipp, &CLSID_FakeService, gLangid, &CLSID_FakeService, szDesc, sizeof(szDesc)/sizeof(WCHAR), szFile, sizeof(szFile)/sizeof(WCHAR), 1);
109     ok(SUCCEEDED(hr),"Unable to add Language Profile (%x)\n",hr);
110 }
111
112 static void test_Unregister(void)
113 {
114     HRESULT hr;
115     hr = ITfInputProcessorProfiles_Unregister(g_ipp, &CLSID_FakeService);
116     ok(SUCCEEDED(hr),"Unable to unregister text service(%x)\n",hr);
117     UnregisterTextService();
118 }
119
120 static void test_EnumInputProcessorInfo(void)
121 {
122     IEnumGUID *ppEnum;
123     BOOL found = FALSE;
124
125     if (SUCCEEDED(ITfInputProcessorProfiles_EnumInputProcessorInfo(g_ipp, &ppEnum)))
126     {
127         ULONG fetched;
128         GUID g;
129         while (IEnumGUID_Next(ppEnum, 1, &g, &fetched) == S_OK)
130         {
131             if(IsEqualGUID(&g,&CLSID_FakeService))
132                 found = TRUE;
133         }
134     }
135     ok(found,"Did not find registered text service\n");
136 }
137
138 static void test_EnumLanguageProfiles(void)
139 {
140     BOOL found = FALSE;
141     IEnumTfLanguageProfiles *ppEnum;
142     if (SUCCEEDED(ITfInputProcessorProfiles_EnumLanguageProfiles(g_ipp,gLangid,&ppEnum)))
143     {
144         TF_LANGUAGEPROFILE profile;
145         while (IEnumTfLanguageProfiles_Next(ppEnum,1,&profile,NULL)==S_OK)
146         {
147             if (IsEqualGUID(&profile.clsid,&CLSID_FakeService))
148             {
149                 found = TRUE;
150                 ok(profile.langid == gLangid, "LangId Incorrect\n");
151                 ok(IsEqualGUID(&profile.catid,&GUID_TFCAT_TIP_KEYBOARD), "CatId Incorrect\n");
152                 ok(IsEqualGUID(&profile.guidProfile,&CLSID_FakeService), "guidProfile Incorrect\n");
153             }
154         }
155     }
156     ok(found,"Registered text service not found\n");
157 }
158
159 static void test_RegisterCategory(void)
160 {
161     HRESULT hr;
162     hr = ITfCategoryMgr_RegisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_TIP_KEYBOARD, &CLSID_FakeService);
163     ok(SUCCEEDED(hr),"ITfCategoryMgr_RegisterCategory failed\n");
164     hr = ITfCategoryMgr_RegisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER, &CLSID_FakeService);
165     ok(SUCCEEDED(hr),"ITfCategoryMgr_RegisterCategory failed\n");
166 }
167
168 static void test_UnregisterCategory(void)
169 {
170     HRESULT hr;
171     hr = ITfCategoryMgr_UnregisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_TIP_KEYBOARD, &CLSID_FakeService);
172     todo_wine ok(SUCCEEDED(hr),"ITfCategoryMgr_UnregisterCategory failed\n");
173     hr = ITfCategoryMgr_UnregisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER, &CLSID_FakeService);
174     todo_wine ok(SUCCEEDED(hr),"ITfCategoryMgr_UnregisterCategory failed\n");
175 }
176
177 static void test_FindClosestCategory(void)
178 {
179     GUID output;
180     HRESULT hr;
181     const GUID *list[3] = {&GUID_TFCAT_TIP_SPEECH, &GUID_TFCAT_TIP_KEYBOARD, &GUID_TFCAT_TIP_HANDWRITING};
182
183     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, NULL, 0);
184     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
185     ok(IsEqualGUID(&output,&GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER),"Wrong GUID\n");
186
187     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, list, 1);
188     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
189     ok(IsEqualGUID(&output,&GUID_NULL),"Wrong GUID\n");
190
191     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, list, 3);
192     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
193     ok(IsEqualGUID(&output,&GUID_TFCAT_TIP_KEYBOARD),"Wrong GUID\n");
194 }
195
196 static void test_Enable(void)
197 {
198     HRESULT hr;
199     BOOL enabled = FALSE;
200
201     hr = ITfInputProcessorProfiles_EnableLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, TRUE);
202     ok(SUCCEEDED(hr),"Failed to enable text service\n");
203     hr = ITfInputProcessorProfiles_IsEnabledLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, &enabled);
204     ok(SUCCEEDED(hr),"Failed to get enabled state\n");
205     ok(enabled == TRUE,"enabled state incorrect\n");
206 }
207
208 static void test_Disable(void)
209 {
210     HRESULT hr;
211
212     trace("Disabling\n");
213     hr = ITfInputProcessorProfiles_EnableLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, FALSE);
214     ok(SUCCEEDED(hr),"Failed to disable text service\n");
215 }
216
217 static void test_ThreadMgrAdviseSinks(void)
218 {
219     ITfSource *source = NULL;
220     HRESULT hr;
221     IUnknown *sink;
222
223     hr = ITfThreadMgr_QueryInterface(g_tm, &IID_ITfSource, (LPVOID*)&source);
224     ok(SUCCEEDED(hr),"Failed to get IID_ITfSource for ThreadMgr\n");
225     if (!source)
226         return;
227
228     ThreadMgrEventSink_Constructor(&sink);
229
230     tmSinkRefCount = 1;
231     tmSinkCookie = 0;
232     hr = ITfSource_AdviseSink(source,&IID_ITfThreadMgrEventSink, sink, &tmSinkCookie);
233     ok(SUCCEEDED(hr),"Failed to Advise Sink\n");
234     ok(tmSinkCookie!=0,"Failed to get sink cookie\n");
235
236     /* Advising the sink adds a ref, Relesing here lets the object be deleted
237        when unadvised */
238     tmSinkRefCount = 2;
239     IUnknown_Release(sink);
240     ITfSource_Release(source);
241 }
242
243 static void test_ThreadMgrUnadviseSinks(void)
244 {
245     ITfSource *source = NULL;
246     HRESULT hr;
247
248     hr = ITfThreadMgr_QueryInterface(g_tm, &IID_ITfSource, (LPVOID*)&source);
249     ok(SUCCEEDED(hr),"Failed to get IID_ITfSource for ThreadMgr\n");
250     if (!source)
251         return;
252
253     tmSinkRefCount = 1;
254     hr = ITfSource_UnadviseSink(source, tmSinkCookie);
255     ok(SUCCEEDED(hr),"Failed to unadvise Sink\n");
256     ITfSource_Release(source);
257 }
258
259 static void test_Activate(void)
260 {
261     HRESULT hr;
262
263     hr = ITfInputProcessorProfiles_ActivateLanguageProfile(g_ipp,&CLSID_FakeService,gLangid,&CLSID_FakeService);
264     todo_wine ok(SUCCEEDED(hr),"Failed to Activate text service\n");
265 }
266
267 static int inline check_context_refcount(ITfContext *iface)
268 {
269     IUnknown_AddRef(iface);
270     return IUnknown_Release(iface);
271 }
272
273 static void test_startSession(void)
274 {
275     HRESULT hr;
276     DWORD cnt;
277     DWORD editCookie;
278     ITfDocumentMgr *dmtest;
279     ITfContext *cxt,*cxt2,*cxt3,*cxtTest;
280
281     test_ShouldActivate = TRUE;
282     ITfThreadMgr_Activate(g_tm,&cid);
283     todo_wine ok(cid != tid,"TextService id mistakenly matches Client id\n");
284
285     hr = ITfThreadMgr_CreateDocumentMgr(g_tm,&g_dm);
286     ok(SUCCEEDED(hr),"CreateDocumentMgr failed\n");
287
288     hr = ITfThreadMgr_GetFocus(g_tm,&dmtest);
289     ok(SUCCEEDED(hr),"GetFocus Failed\n");
290     ok(dmtest == NULL,"Initial focus not null\n");
291
292     test_CurrentFocus = g_dm;
293     test_PrevFocus = NULL;
294     test_OnSetFocus  = SINK_EXPECTED;
295     hr = ITfThreadMgr_SetFocus(g_tm,g_dm);
296     ok(SUCCEEDED(hr),"SetFocus Failed\n");
297     ok(test_OnSetFocus == SINK_FIRED, "OnSetFocus sink not called\n");
298     test_OnSetFocus  = SINK_UNEXPECTED;
299
300     hr = ITfThreadMgr_GetFocus(g_tm,&dmtest);
301     ok(SUCCEEDED(hr),"GetFocus Failed\n");
302     ok(g_dm == dmtest,"Expected DocumentMgr not focused\n");
303
304     cnt = ITfDocumentMgr_Release(g_dm);
305     ok(cnt == 2,"DocumentMgr refcount not expected (2 vs %i)\n",cnt);
306
307     hr = ITfThreadMgr_GetFocus(g_tm,&dmtest);
308     ok(SUCCEEDED(hr),"GetFocus Failed\n");
309     ok(g_dm == dmtest,"Expected DocumentMgr not focused\n");
310
311     hr = ITfDocumentMgr_CreateContext(g_dm, cid, 0, NULL, &cxt, &editCookie);
312     ok(SUCCEEDED(hr),"CreateContext Failed\n");
313
314     hr = ITfDocumentMgr_CreateContext(g_dm, cid, 0, NULL, &cxt2, &editCookie);
315     ok(SUCCEEDED(hr),"CreateContext Failed\n");
316
317     hr = ITfDocumentMgr_CreateContext(g_dm, cid, 0, NULL, &cxt3, &editCookie);
318     ok(SUCCEEDED(hr),"CreateContext Failed\n");
319
320     cnt = check_context_refcount(cxt);
321     test_OnPushContext = SINK_EXPECTED;
322     test_OnInitDocumentMgr = SINK_EXPECTED;
323     hr = ITfDocumentMgr_Push(g_dm, cxt);
324     ok(SUCCEEDED(hr),"Push Failed\n");
325     ok(check_context_refcount(cxt) > cnt, "Ref count did not increase\n");
326     ok(test_OnPushContext == SINK_FIRED, "OnPushContext sink not fired\n");
327     ok(test_OnInitDocumentMgr == SINK_FIRED, "OnInitDocumentMgr sink not fired\n");
328
329     hr = ITfDocumentMgr_GetTop(g_dm, &cxtTest);
330     ok(SUCCEEDED(hr),"GetTop Failed\n");
331     ok(cxtTest == cxt, "Wrong context on top\n");
332     ok(check_context_refcount(cxt) > cnt, "Ref count did not increase\n");
333     cnt = ITfContext_Release(cxtTest);
334
335     hr = ITfDocumentMgr_GetBase(g_dm, &cxtTest);
336     ok(SUCCEEDED(hr),"GetBase Failed\n");
337     ok(cxtTest == cxt, "Wrong context on Base\n");
338     ok(check_context_refcount(cxt) > cnt, "Ref count did not increase\n");
339     ITfContext_Release(cxtTest);
340
341     check_context_refcount(cxt2);
342     test_OnPushContext = SINK_EXPECTED;
343     hr = ITfDocumentMgr_Push(g_dm, cxt2);
344     ok(SUCCEEDED(hr),"Push Failed\n");
345     ok(test_OnPushContext == SINK_FIRED, "OnPushContext sink not fired\n");
346
347     cnt = check_context_refcount(cxt2);
348     hr = ITfDocumentMgr_GetTop(g_dm, &cxtTest);
349     ok(SUCCEEDED(hr),"GetTop Failed\n");
350     ok(cxtTest == cxt2, "Wrong context on top\n");
351     ok(check_context_refcount(cxt2) > cnt, "Ref count did not increase\n");
352     ITfContext_Release(cxtTest);
353
354     cnt = check_context_refcount(cxt);
355     hr = ITfDocumentMgr_GetBase(g_dm, &cxtTest);
356     ok(SUCCEEDED(hr),"GetBase Failed\n");
357     ok(cxtTest == cxt, "Wrong context on Base\n");
358     ok(check_context_refcount(cxt) > cnt, "Ref count did not increase\n");
359     ITfContext_Release(cxtTest);
360
361     cnt = check_context_refcount(cxt3);
362     hr = ITfDocumentMgr_Push(g_dm, cxt3);
363     ok(!SUCCEEDED(hr),"Push Succeeded\n");
364     ok(check_context_refcount(cxt3) == cnt, "Ref changed\n");
365
366     cnt = check_context_refcount(cxt2);
367     hr = ITfDocumentMgr_GetTop(g_dm, &cxtTest);
368     ok(SUCCEEDED(hr),"GetTop Failed\n");
369     ok(cxtTest == cxt2, "Wrong context on top\n");
370     ok(check_context_refcount(cxt2) > cnt, "Ref count did not increase\n");
371     ITfContext_Release(cxtTest);
372
373     cnt = check_context_refcount(cxt);
374     hr = ITfDocumentMgr_GetBase(g_dm, &cxtTest);
375     ok(SUCCEEDED(hr),"GetBase Failed\n");
376     ok(cxtTest == cxt, "Wrong context on Base\n");
377     ok(check_context_refcount(cxt) > cnt, "Ref count did not increase\n");
378     ITfContext_Release(cxtTest);
379
380     cnt = check_context_refcount(cxt2);
381     test_OnPopContext = SINK_EXPECTED;
382     hr = ITfDocumentMgr_Pop(g_dm, 0);
383     ok(SUCCEEDED(hr),"Pop Failed\n");
384     ok(check_context_refcount(cxt2) < cnt, "Ref count did not decrease\n");
385     ok(test_OnPopContext == SINK_FIRED, "OnPopContext sink not fired\n");
386
387     hr = ITfDocumentMgr_GetTop(g_dm, &cxtTest);
388     ok(SUCCEEDED(hr),"GetTop Failed\n");
389     ok(cxtTest == cxt, "Wrong context on top\n");
390     ITfContext_Release(cxtTest);
391
392     hr = ITfDocumentMgr_GetBase(g_dm, &cxtTest);
393     ok(SUCCEEDED(hr),"GetBase Failed\n");
394     ok(cxtTest == cxt, "Wrong context on base\n");
395     ITfContext_Release(cxtTest);
396
397     hr = ITfDocumentMgr_Pop(g_dm, 0);
398     ok(!SUCCEEDED(hr),"Pop Succeeded\n");
399
400     hr = ITfDocumentMgr_GetTop(g_dm, &cxtTest);
401     ok(SUCCEEDED(hr),"GetTop Failed\n");
402     ok(cxtTest == cxt, "Wrong context on top\n");
403     ITfContext_Release(cxtTest);
404
405     hr = ITfDocumentMgr_GetBase(g_dm, &cxtTest);
406     ok(SUCCEEDED(hr),"GetBase Failed\n");
407     ok(cxtTest == cxt, "Wrong context on base\n");
408     ITfContext_Release(cxtTest);
409
410     ITfContext_Release(cxt);
411     ITfContext_Release(cxt2);
412     ITfContext_Release(cxt3);
413 }
414
415 static void test_endSession(void)
416 {
417     test_ShouldDeactivate = TRUE;
418     test_CurrentFocus = NULL;
419     test_PrevFocus = g_dm;
420     test_OnSetFocus  = SINK_EXPECTED;
421     ITfThreadMgr_Deactivate(g_tm);
422     ok(test_OnSetFocus == SINK_FIRED, "OnSetFocus sink not called\n");
423     test_OnSetFocus  = SINK_UNEXPECTED;
424 }
425
426 START_TEST(inputprocessor)
427 {
428     if (SUCCEEDED(initialize()))
429     {
430         gLangid = GetUserDefaultLCID();
431         test_Register();
432         test_RegisterCategory();
433         test_EnumInputProcessorInfo();
434         test_Enable();
435         test_ThreadMgrAdviseSinks();
436         test_Activate();
437         test_startSession();
438         test_endSession();
439         test_EnumLanguageProfiles();
440         test_FindClosestCategory();
441         test_Disable();
442         test_ThreadMgrUnadviseSinks();
443         test_UnregisterCategory();
444         test_Unregister();
445     }
446     else
447         skip("Unable to create InputProcessor\n");
448     cleanup();
449 }
450
451 /**********************************************************************
452  * ITfThreadMgrEventSink
453  **********************************************************************/
454 typedef struct tagThreadMgrEventSink
455 {
456     const ITfThreadMgrEventSinkVtbl *ThreadMgrEventSinkVtbl;
457     LONG refCount;
458 } ThreadMgrEventSink;
459
460 static void ThreadMgrEventSink_Destructor(ThreadMgrEventSink *This)
461 {
462     HeapFree(GetProcessHeap(),0,This);
463 }
464
465 static HRESULT WINAPI ThreadMgrEventSink_QueryInterface(ITfThreadMgrEventSink *iface, REFIID iid, LPVOID *ppvOut)
466 {
467     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
468     *ppvOut = NULL;
469
470     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ITfThreadMgrEventSink))
471     {
472         *ppvOut = This;
473     }
474
475     if (*ppvOut)
476     {
477         IUnknown_AddRef(iface);
478         return S_OK;
479     }
480
481     return E_NOINTERFACE;
482 }
483
484 static ULONG WINAPI ThreadMgrEventSink_AddRef(ITfThreadMgrEventSink *iface)
485 {
486     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
487     ok (tmSinkRefCount == This->refCount,"ThreadMgrEventSink refcount off %i vs %i\n",This->refCount,tmSinkRefCount);
488     return InterlockedIncrement(&This->refCount);
489 }
490
491 static ULONG WINAPI ThreadMgrEventSink_Release(ITfThreadMgrEventSink *iface)
492 {
493     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
494     ULONG ret;
495
496     ok (tmSinkRefCount == This->refCount,"ThreadMgrEventSink refcount off %i vs %i\n",This->refCount,tmSinkRefCount);
497     ret = InterlockedDecrement(&This->refCount);
498     if (ret == 0)
499         ThreadMgrEventSink_Destructor(This);
500     return ret;
501 }
502
503 static HRESULT WINAPI ThreadMgrEventSink_OnInitDocumentMgr(ITfThreadMgrEventSink *iface,
504 ITfDocumentMgr *pdim)
505 {
506     ok(test_OnInitDocumentMgr == SINK_EXPECTED, "Unexpected OnInitDocumentMgr sink\n");
507     test_OnInitDocumentMgr = SINK_FIRED;
508     return S_OK;
509 }
510
511 static HRESULT WINAPI ThreadMgrEventSink_OnUninitDocumentMgr(ITfThreadMgrEventSink *iface,
512 ITfDocumentMgr *pdim)
513 {
514     trace("\n");
515     return S_OK;
516 }
517
518 static HRESULT WINAPI ThreadMgrEventSink_OnSetFocus(ITfThreadMgrEventSink *iface,
519 ITfDocumentMgr *pdimFocus, ITfDocumentMgr *pdimPrevFocus)
520 {
521     ok(test_OnSetFocus == SINK_EXPECTED, "Unexpected OnSetFocus sink\n");
522     ok(pdimFocus == test_CurrentFocus,"Sink reports wrong focus\n");
523     ok(pdimPrevFocus == test_PrevFocus,"Sink reports wrong previous focus\n");
524     test_OnSetFocus = SINK_FIRED;
525     return S_OK;
526 }
527
528 static HRESULT WINAPI ThreadMgrEventSink_OnPushContext(ITfThreadMgrEventSink *iface,
529 ITfContext *pic)
530 {
531     ok(test_OnPushContext == SINK_EXPECTED, "Unexpected OnPushContext sink\n");
532     test_OnPushContext = SINK_FIRED;
533     return S_OK;
534 }
535
536 static HRESULT WINAPI ThreadMgrEventSink_OnPopContext(ITfThreadMgrEventSink *iface,
537 ITfContext *pic)
538 {
539     ok(test_OnPopContext == SINK_EXPECTED, "Unexpected OnPopContext sink\n");
540     test_OnPopContext = SINK_FIRED;
541     return S_OK;
542 }
543
544 static const ITfThreadMgrEventSinkVtbl ThreadMgrEventSink_ThreadMgrEventSinkVtbl =
545 {
546     ThreadMgrEventSink_QueryInterface,
547     ThreadMgrEventSink_AddRef,
548     ThreadMgrEventSink_Release,
549
550     ThreadMgrEventSink_OnInitDocumentMgr,
551     ThreadMgrEventSink_OnUninitDocumentMgr,
552     ThreadMgrEventSink_OnSetFocus,
553     ThreadMgrEventSink_OnPushContext,
554     ThreadMgrEventSink_OnPopContext
555 };
556
557 HRESULT ThreadMgrEventSink_Constructor(IUnknown **ppOut)
558 {
559     ThreadMgrEventSink *This;
560
561     This = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(ThreadMgrEventSink));
562     if (This == NULL)
563         return E_OUTOFMEMORY;
564
565     This->ThreadMgrEventSinkVtbl = &ThreadMgrEventSink_ThreadMgrEventSinkVtbl;
566     This->refCount = 1;
567
568     *ppOut = (IUnknown *)This;
569     return S_OK;
570 }
571
572
573 /********************************************************************************************
574  * Stub text service for testing
575  ********************************************************************************************/
576
577 static LONG TS_refCount;
578 static IClassFactory *cf;
579 static DWORD regid;
580
581 typedef HRESULT (*LPFNCONSTRUCTOR)(IUnknown *pUnkOuter, IUnknown **ppvOut);
582
583 typedef struct tagClassFactory
584 {
585     const IClassFactoryVtbl *vtbl;
586     LONG   ref;
587     LPFNCONSTRUCTOR ctor;
588 } ClassFactory;
589
590 typedef struct tagTextService
591 {
592     const ITfTextInputProcessorVtbl *TextInputProcessorVtbl;
593     LONG refCount;
594 } TextService;
595
596 static void ClassFactory_Destructor(ClassFactory *This)
597 {
598     HeapFree(GetProcessHeap(),0,This);
599     TS_refCount--;
600 }
601
602 static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, LPVOID *ppvOut)
603 {
604     *ppvOut = NULL;
605     if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown))
606     {
607         IClassFactory_AddRef(iface);
608         *ppvOut = iface;
609         return S_OK;
610     }
611
612     return E_NOINTERFACE;
613 }
614
615 static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface)
616 {
617     ClassFactory *This = (ClassFactory *)iface;
618     return InterlockedIncrement(&This->ref);
619 }
620
621 static ULONG WINAPI ClassFactory_Release(IClassFactory *iface)
622 {
623     ClassFactory *This = (ClassFactory *)iface;
624     ULONG ret = InterlockedDecrement(&This->ref);
625
626     if (ret == 0)
627         ClassFactory_Destructor(This);
628     return ret;
629 }
630
631 static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, IUnknown *punkOuter, REFIID iid, LPVOID *ppvOut)
632 {
633     ClassFactory *This = (ClassFactory *)iface;
634     HRESULT ret;
635     IUnknown *obj;
636
637     ret = This->ctor(punkOuter, &obj);
638     if (FAILED(ret))
639         return ret;
640     ret = IUnknown_QueryInterface(obj, iid, ppvOut);
641     IUnknown_Release(obj);
642     return ret;
643 }
644
645 static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock)
646 {
647     if(fLock)
648         InterlockedIncrement(&TS_refCount);
649     else
650         InterlockedDecrement(&TS_refCount);
651
652     return S_OK;
653 }
654
655 static const IClassFactoryVtbl ClassFactoryVtbl = {
656     /* IUnknown */
657     ClassFactory_QueryInterface,
658     ClassFactory_AddRef,
659     ClassFactory_Release,
660
661     /* IClassFactory*/
662     ClassFactory_CreateInstance,
663     ClassFactory_LockServer
664 };
665
666 static HRESULT ClassFactory_Constructor(LPFNCONSTRUCTOR ctor, LPVOID *ppvOut)
667 {
668     ClassFactory *This = HeapAlloc(GetProcessHeap(),0,sizeof(ClassFactory));
669     This->vtbl = &ClassFactoryVtbl;
670     This->ref = 1;
671     This->ctor = ctor;
672     *ppvOut = (LPVOID)This;
673     TS_refCount++;
674     return S_OK;
675 }
676
677 static void TextService_Destructor(TextService *This)
678 {
679     HeapFree(GetProcessHeap(),0,This);
680 }
681
682 static HRESULT WINAPI TextService_QueryInterface(ITfTextInputProcessor *iface, REFIID iid, LPVOID *ppvOut)
683 {
684     TextService *This = (TextService *)iface;
685     *ppvOut = NULL;
686
687     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ITfTextInputProcessor))
688     {
689         *ppvOut = This;
690     }
691
692     if (*ppvOut)
693     {
694         IUnknown_AddRef(iface);
695         return S_OK;
696     }
697
698     return E_NOINTERFACE;
699 }
700
701 static ULONG WINAPI TextService_AddRef(ITfTextInputProcessor *iface)
702 {
703     TextService *This = (TextService *)iface;
704     return InterlockedIncrement(&This->refCount);
705 }
706
707 static ULONG WINAPI TextService_Release(ITfTextInputProcessor *iface)
708 {
709     TextService *This = (TextService *)iface;
710     ULONG ret;
711
712     ret = InterlockedDecrement(&This->refCount);
713     if (ret == 0)
714         TextService_Destructor(This);
715     return ret;
716 }
717
718 static HRESULT WINAPI TextService_Activate(ITfTextInputProcessor *iface,
719         ITfThreadMgr *ptim, TfClientId id)
720 {
721     trace("TextService_Activate\n");
722     ok(test_ShouldActivate,"Activation came unexpectedly\n");
723     tid = id;
724     return S_OK;
725 }
726
727 static HRESULT WINAPI TextService_Deactivate(ITfTextInputProcessor *iface)
728 {
729     trace("TextService_Deactivate\n");
730     ok(test_ShouldDeactivate,"Deactivation came unexpectedly\n");
731     return S_OK;
732 }
733
734 static const ITfTextInputProcessorVtbl TextService_TextInputProcessorVtbl=
735 {
736     TextService_QueryInterface,
737     TextService_AddRef,
738     TextService_Release,
739
740     TextService_Activate,
741     TextService_Deactivate
742 };
743
744 HRESULT TextService_Constructor(IUnknown *pUnkOuter, IUnknown **ppOut)
745 {
746     TextService *This;
747     if (pUnkOuter)
748         return CLASS_E_NOAGGREGATION;
749
750     This = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(TextService));
751     if (This == NULL)
752         return E_OUTOFMEMORY;
753
754     This->TextInputProcessorVtbl= &TextService_TextInputProcessorVtbl;
755     This->refCount = 1;
756
757     *ppOut = (IUnknown *)This;
758     return S_OK;
759 }
760
761 HRESULT RegisterTextService(REFCLSID rclsid)
762 {
763     ClassFactory_Constructor( TextService_Constructor ,(LPVOID*)&cf);
764     return CoRegisterClassObject(rclsid, (IUnknown*) cf, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &regid);
765 }
766
767 HRESULT UnregisterTextService()
768 {
769     return CoRevokeClassObject(regid);
770 }