msctf/tests: Test DocumentMgr creation and Focus.
[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
53 HRESULT RegisterTextService(REFCLSID rclsid);
54 HRESULT UnregisterTextService();
55 HRESULT ThreadMgrEventSink_Constructor(IUnknown **ppOut);
56
57 DEFINE_GUID(CLSID_FakeService, 0xEDE1A7AD,0x66DE,0x47E0,0xB6,0x20,0x3E,0x92,0xF8,0x24,0x6B,0xF3);
58 DEFINE_GUID(CLSID_TF_InputProcessorProfiles, 0x33c53a50,0xf456,0x4884,0xb0,0x49,0x85,0xfd,0x64,0x3e,0xcf,0xed);
59 DEFINE_GUID(CLSID_TF_CategoryMgr,         0xA4B544A1,0x438D,0x4B41,0x93,0x25,0x86,0x95,0x23,0xE2,0xD6,0xC7);
60 DEFINE_GUID(GUID_TFCAT_TIP_KEYBOARD,     0x34745c63,0xb2f0,0x4784,0x8b,0x67,0x5e,0x12,0xc8,0x70,0x1a,0x31);
61 DEFINE_GUID(GUID_TFCAT_TIP_SPEECH,       0xB5A73CD1,0x8355,0x426B,0xA1,0x61,0x25,0x98,0x08,0xF2,0x6B,0x14);
62 DEFINE_GUID(GUID_TFCAT_TIP_HANDWRITING,  0x246ecb87,0xc2f2,0x4abe,0x90,0x5b,0xc8,0xb3,0x8a,0xdd,0x2c,0x43);
63 DEFINE_GUID (GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER,  0x046B8C80,0x1647,0x40F7,0x9B,0x21,0xB9,0x3B,0x81,0xAA,0xBC,0x1B);
64 DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0);
65 DEFINE_GUID(CLSID_TF_ThreadMgr,           0x529a9e6b,0x6587,0x4f23,0xab,0x9e,0x9c,0x7d,0x68,0x3e,0x3c,0x50);
66
67
68 static HRESULT initialize(void)
69 {
70     HRESULT hr;
71     CoInitialize(NULL);
72     hr = CoCreateInstance (&CLSID_TF_InputProcessorProfiles, NULL,
73           CLSCTX_INPROC_SERVER, &IID_ITfInputProcessorProfiles, (void**)&g_ipp);
74     if (SUCCEEDED(hr))
75         hr = CoCreateInstance (&CLSID_TF_CategoryMgr, NULL,
76           CLSCTX_INPROC_SERVER, &IID_ITfCategoryMgr, (void**)&g_cm);
77     if (SUCCEEDED(hr))
78         hr = CoCreateInstance (&CLSID_TF_ThreadMgr, NULL,
79           CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, (void**)&g_tm);
80     return hr;
81 }
82
83 static void cleanup(void)
84 {
85     if (g_ipp)
86         ITfInputProcessorProfiles_Release(g_ipp);
87     if (g_cm)
88         ITfCategoryMgr_Release(g_cm);
89     if (g_tm)
90         ITfThreadMgr_Release(g_tm);
91     CoUninitialize();
92 }
93
94 static void test_Register(void)
95 {
96     HRESULT hr;
97
98     static const WCHAR szDesc[] = {'F','a','k','e',' ','W','i','n','e',' ','S','e','r','v','i','c','e',0};
99     static const WCHAR szFile[] = {'F','a','k','e',' ','W','i','n','e',' ','S','e','r','v','i','c','e',' ','F','i','l','e',0};
100
101     hr = RegisterTextService(&CLSID_FakeService);
102     ok(SUCCEEDED(hr),"Unable to register COM for TextService\n");
103     hr = ITfInputProcessorProfiles_Register(g_ipp, &CLSID_FakeService);
104     ok(SUCCEEDED(hr),"Unable to register text service(%x)\n",hr);
105     hr = ITfInputProcessorProfiles_AddLanguageProfile(g_ipp, &CLSID_FakeService, gLangid, &CLSID_FakeService, szDesc, sizeof(szDesc)/sizeof(WCHAR), szFile, sizeof(szFile)/sizeof(WCHAR), 1);
106     ok(SUCCEEDED(hr),"Unable to add Language Profile (%x)\n",hr);
107 }
108
109 static void test_Unregister(void)
110 {
111     HRESULT hr;
112     hr = ITfInputProcessorProfiles_Unregister(g_ipp, &CLSID_FakeService);
113     ok(SUCCEEDED(hr),"Unable to unregister text service(%x)\n",hr);
114     UnregisterTextService();
115 }
116
117 static void test_EnumInputProcessorInfo(void)
118 {
119     IEnumGUID *ppEnum;
120     BOOL found = FALSE;
121
122     if (SUCCEEDED(ITfInputProcessorProfiles_EnumInputProcessorInfo(g_ipp, &ppEnum)))
123     {
124         ULONG fetched;
125         GUID g;
126         while (IEnumGUID_Next(ppEnum, 1, &g, &fetched) == S_OK)
127         {
128             if(IsEqualGUID(&g,&CLSID_FakeService))
129                 found = TRUE;
130         }
131     }
132     ok(found,"Did not find registered text service\n");
133 }
134
135 static void test_EnumLanguageProfiles(void)
136 {
137     BOOL found = FALSE;
138     IEnumTfLanguageProfiles *ppEnum;
139     if (SUCCEEDED(ITfInputProcessorProfiles_EnumLanguageProfiles(g_ipp,gLangid,&ppEnum)))
140     {
141         TF_LANGUAGEPROFILE profile;
142         while (IEnumTfLanguageProfiles_Next(ppEnum,1,&profile,NULL)==S_OK)
143         {
144             if (IsEqualGUID(&profile.clsid,&CLSID_FakeService))
145             {
146                 found = TRUE;
147                 ok(profile.langid == gLangid, "LangId Incorrect\n");
148                 ok(IsEqualGUID(&profile.catid,&GUID_TFCAT_TIP_KEYBOARD), "CatId Incorrect\n");
149                 ok(IsEqualGUID(&profile.guidProfile,&CLSID_FakeService), "guidProfile Incorrect\n");
150             }
151         }
152     }
153     ok(found,"Registered text service not found\n");
154 }
155
156 static void test_RegisterCategory(void)
157 {
158     HRESULT hr;
159     hr = ITfCategoryMgr_RegisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_TIP_KEYBOARD, &CLSID_FakeService);
160     ok(SUCCEEDED(hr),"ITfCategoryMgr_RegisterCategory failed\n");
161     hr = ITfCategoryMgr_RegisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER, &CLSID_FakeService);
162     ok(SUCCEEDED(hr),"ITfCategoryMgr_RegisterCategory failed\n");
163 }
164
165 static void test_UnregisterCategory(void)
166 {
167     HRESULT hr;
168     hr = ITfCategoryMgr_UnregisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_TIP_KEYBOARD, &CLSID_FakeService);
169     todo_wine ok(SUCCEEDED(hr),"ITfCategoryMgr_UnregisterCategory failed\n");
170     hr = ITfCategoryMgr_UnregisterCategory(g_cm, &CLSID_FakeService, &GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER, &CLSID_FakeService);
171     todo_wine ok(SUCCEEDED(hr),"ITfCategoryMgr_UnregisterCategory failed\n");
172 }
173
174 static void test_FindClosestCategory(void)
175 {
176     GUID output;
177     HRESULT hr;
178     const GUID *list[3] = {&GUID_TFCAT_TIP_SPEECH, &GUID_TFCAT_TIP_KEYBOARD, &GUID_TFCAT_TIP_HANDWRITING};
179
180     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, NULL, 0);
181     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
182     ok(IsEqualGUID(&output,&GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER),"Wrong GUID\n");
183
184     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, list, 1);
185     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
186     ok(IsEqualGUID(&output,&GUID_NULL),"Wrong GUID\n");
187
188     hr = ITfCategoryMgr_FindClosestCategory(g_cm, &CLSID_FakeService, &output, list, 3);
189     ok(SUCCEEDED(hr),"ITfCategoryMgr_FindClosestCategory failed (%x)\n",hr);
190     ok(IsEqualGUID(&output,&GUID_TFCAT_TIP_KEYBOARD),"Wrong GUID\n");
191 }
192
193 static void test_Enable(void)
194 {
195     HRESULT hr;
196     BOOL enabled = FALSE;
197
198     hr = ITfInputProcessorProfiles_EnableLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, TRUE);
199     ok(SUCCEEDED(hr),"Failed to enable text service\n");
200     hr = ITfInputProcessorProfiles_IsEnabledLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, &enabled);
201     ok(SUCCEEDED(hr),"Failed to get enabled state\n");
202     ok(enabled == TRUE,"enabled state incorrect\n");
203 }
204
205 static void test_Disable(void)
206 {
207     HRESULT hr;
208
209     trace("Disabling\n");
210     hr = ITfInputProcessorProfiles_EnableLanguageProfile(g_ipp,&CLSID_FakeService, gLangid, &CLSID_FakeService, FALSE);
211     ok(SUCCEEDED(hr),"Failed to disable text service\n");
212 }
213
214 static void test_ThreadMgrAdviseSinks(void)
215 {
216     ITfSource *source = NULL;
217     HRESULT hr;
218     IUnknown *sink;
219
220     hr = ITfThreadMgr_QueryInterface(g_tm, &IID_ITfSource, (LPVOID*)&source);
221     ok(SUCCEEDED(hr),"Failed to get IID_ITfSource for ThreadMgr\n");
222     if (!source)
223         return;
224
225     ThreadMgrEventSink_Constructor(&sink);
226
227     tmSinkRefCount = 1;
228     tmSinkCookie = 0;
229     hr = ITfSource_AdviseSink(source,&IID_ITfThreadMgrEventSink, sink, &tmSinkCookie);
230     ok(SUCCEEDED(hr),"Failed to Advise Sink\n");
231     ok(tmSinkCookie!=0,"Failed to get sink cookie\n");
232
233     /* Advising the sink adds a ref, Relesing here lets the object be deleted
234        when unadvised */
235     tmSinkRefCount = 2;
236     IUnknown_Release(sink);
237     ITfSource_Release(source);
238 }
239
240 static void test_ThreadMgrUnadviseSinks(void)
241 {
242     ITfSource *source = NULL;
243     HRESULT hr;
244
245     hr = ITfThreadMgr_QueryInterface(g_tm, &IID_ITfSource, (LPVOID*)&source);
246     ok(SUCCEEDED(hr),"Failed to get IID_ITfSource for ThreadMgr\n");
247     if (!source)
248         return;
249
250     tmSinkRefCount = 1;
251     hr = ITfSource_UnadviseSink(source, tmSinkCookie);
252     ok(SUCCEEDED(hr),"Failed to unadvise Sink\n");
253     ITfSource_Release(source);
254 }
255
256 static void test_Activate(void)
257 {
258     HRESULT hr;
259
260     hr = ITfInputProcessorProfiles_ActivateLanguageProfile(g_ipp,&CLSID_FakeService,gLangid,&CLSID_FakeService);
261     todo_wine ok(SUCCEEDED(hr),"Failed to Activate text service\n");
262 }
263
264 static void test_startSession(void)
265 {
266     HRESULT hr;
267     DWORD cnt;
268     ITfDocumentMgr *dmtest;
269
270     test_ShouldActivate = TRUE;
271     ITfThreadMgr_Activate(g_tm,&cid);
272     todo_wine ok(cid != tid,"TextService id mistakenly matches Client id\n");
273
274     hr = ITfThreadMgr_CreateDocumentMgr(g_tm,&g_dm);
275     ok(SUCCEEDED(hr),"CreateDocumentMgr failed\n");
276
277     hr = ITfThreadMgr_GetFocus(g_tm,&dmtest);
278     ok(SUCCEEDED(hr),"GetFocus Failed\n");
279     ok(dmtest == NULL,"Initial focus not null\n");
280
281     test_CurrentFocus = g_dm;
282     test_PrevFocus = NULL;
283     test_OnSetFocus  = SINK_EXPECTED;
284     hr = ITfThreadMgr_SetFocus(g_tm,g_dm);
285     ok(SUCCEEDED(hr),"SetFocus Failed\n");
286     ok(test_OnSetFocus == SINK_FIRED, "OnSetFocus sink not called\n");
287     test_OnSetFocus  = SINK_UNEXPECTED;
288
289     hr = ITfThreadMgr_GetFocus(g_tm,&dmtest);
290     ok(SUCCEEDED(hr),"GetFocus Failed\n");
291     ok(g_dm == dmtest,"Expected DocumentMgr not focused\n");
292
293     cnt = ITfDocumentMgr_Release(g_dm);
294     ok(cnt == 2,"DocumentMgr refcount not expected (2 vs %i)\n",cnt);
295
296     hr = ITfThreadMgr_GetFocus(g_tm,&dmtest);
297     ok(SUCCEEDED(hr),"GetFocus Failed\n");
298     ok(g_dm == dmtest,"Expected DocumentMgr not focused\n");
299 }
300
301 static void test_endSession(void)
302 {
303     test_ShouldDeactivate = TRUE;
304     test_CurrentFocus = NULL;
305     test_PrevFocus = g_dm;
306     test_OnSetFocus  = SINK_EXPECTED;
307     ITfThreadMgr_Deactivate(g_tm);
308     ok(test_OnSetFocus == SINK_FIRED, "OnSetFocus sink not called\n");
309     test_OnSetFocus  = SINK_UNEXPECTED;
310 }
311
312 START_TEST(inputprocessor)
313 {
314     if (SUCCEEDED(initialize()))
315     {
316         gLangid = GetUserDefaultLCID();
317         test_Register();
318         test_RegisterCategory();
319         test_EnumInputProcessorInfo();
320         test_Enable();
321         test_ThreadMgrAdviseSinks();
322         test_Activate();
323         test_startSession();
324         test_endSession();
325         test_EnumLanguageProfiles();
326         test_FindClosestCategory();
327         test_Disable();
328         test_ThreadMgrUnadviseSinks();
329         test_UnregisterCategory();
330         test_Unregister();
331     }
332     else
333         skip("Unable to create InputProcessor\n");
334     cleanup();
335 }
336
337 /**********************************************************************
338  * ITfThreadMgrEventSink
339  **********************************************************************/
340 typedef struct tagThreadMgrEventSink
341 {
342     const ITfThreadMgrEventSinkVtbl *ThreadMgrEventSinkVtbl;
343     LONG refCount;
344 } ThreadMgrEventSink;
345
346 static void ThreadMgrEventSink_Destructor(ThreadMgrEventSink *This)
347 {
348     HeapFree(GetProcessHeap(),0,This);
349 }
350
351 static HRESULT WINAPI ThreadMgrEventSink_QueryInterface(ITfThreadMgrEventSink *iface, REFIID iid, LPVOID *ppvOut)
352 {
353     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
354     *ppvOut = NULL;
355
356     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ITfThreadMgrEventSink))
357     {
358         *ppvOut = This;
359     }
360
361     if (*ppvOut)
362     {
363         IUnknown_AddRef(iface);
364         return S_OK;
365     }
366
367     return E_NOINTERFACE;
368 }
369
370 static ULONG WINAPI ThreadMgrEventSink_AddRef(ITfThreadMgrEventSink *iface)
371 {
372     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
373     ok (tmSinkRefCount == This->refCount,"ThreadMgrEventSink refcount off %i vs %i\n",This->refCount,tmSinkRefCount);
374     return InterlockedIncrement(&This->refCount);
375 }
376
377 static ULONG WINAPI ThreadMgrEventSink_Release(ITfThreadMgrEventSink *iface)
378 {
379     ThreadMgrEventSink *This = (ThreadMgrEventSink *)iface;
380     ULONG ret;
381
382     ok (tmSinkRefCount == This->refCount,"ThreadMgrEventSink refcount off %i vs %i\n",This->refCount,tmSinkRefCount);
383     ret = InterlockedDecrement(&This->refCount);
384     if (ret == 0)
385         ThreadMgrEventSink_Destructor(This);
386     return ret;
387 }
388
389 static HRESULT WINAPI ThreadMgrEventSink_OnInitDocumentMgr(ITfThreadMgrEventSink *iface,
390 ITfDocumentMgr *pdim)
391 {
392     trace("\n");
393     return S_OK;
394 }
395
396 static HRESULT WINAPI ThreadMgrEventSink_OnUninitDocumentMgr(ITfThreadMgrEventSink *iface,
397 ITfDocumentMgr *pdim)
398 {
399     trace("\n");
400     return S_OK;
401 }
402
403 static HRESULT WINAPI ThreadMgrEventSink_OnSetFocus(ITfThreadMgrEventSink *iface,
404 ITfDocumentMgr *pdimFocus, ITfDocumentMgr *pdimPrevFocus)
405 {
406     ok(test_OnSetFocus == SINK_EXPECTED, "Unexpected OnSetFocus sink\n");
407     ok(pdimFocus == test_CurrentFocus,"Sink reports wrong focus\n");
408     ok(pdimPrevFocus == test_PrevFocus,"Sink reports wrong previous focus\n");
409     test_OnSetFocus = SINK_FIRED;
410     return S_OK;
411 }
412
413 static HRESULT WINAPI ThreadMgrEventSink_OnPushContext(ITfThreadMgrEventSink *iface,
414 ITfContext *pic)
415 {
416     trace("\n");
417     return S_OK;
418 }
419
420 static HRESULT WINAPI ThreadMgrEventSink_OnPopContext(ITfThreadMgrEventSink *iface,
421 ITfContext *pic)
422 {
423     trace("\n");
424     return S_OK;
425 }
426
427 static const ITfThreadMgrEventSinkVtbl ThreadMgrEventSink_ThreadMgrEventSinkVtbl =
428 {
429     ThreadMgrEventSink_QueryInterface,
430     ThreadMgrEventSink_AddRef,
431     ThreadMgrEventSink_Release,
432
433     ThreadMgrEventSink_OnInitDocumentMgr,
434     ThreadMgrEventSink_OnUninitDocumentMgr,
435     ThreadMgrEventSink_OnSetFocus,
436     ThreadMgrEventSink_OnPushContext,
437     ThreadMgrEventSink_OnPopContext
438 };
439
440 HRESULT ThreadMgrEventSink_Constructor(IUnknown **ppOut)
441 {
442     ThreadMgrEventSink *This;
443
444     This = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(ThreadMgrEventSink));
445     if (This == NULL)
446         return E_OUTOFMEMORY;
447
448     This->ThreadMgrEventSinkVtbl = &ThreadMgrEventSink_ThreadMgrEventSinkVtbl;
449     This->refCount = 1;
450
451     *ppOut = (IUnknown *)This;
452     return S_OK;
453 }
454
455
456 /********************************************************************************************
457  * Stub text service for testing
458  ********************************************************************************************/
459
460 static LONG TS_refCount;
461 static IClassFactory *cf;
462 static DWORD regid;
463
464 typedef HRESULT (*LPFNCONSTRUCTOR)(IUnknown *pUnkOuter, IUnknown **ppvOut);
465
466 typedef struct tagClassFactory
467 {
468     const IClassFactoryVtbl *vtbl;
469     LONG   ref;
470     LPFNCONSTRUCTOR ctor;
471 } ClassFactory;
472
473 typedef struct tagTextService
474 {
475     const ITfTextInputProcessorVtbl *TextInputProcessorVtbl;
476     LONG refCount;
477 } TextService;
478
479 static void ClassFactory_Destructor(ClassFactory *This)
480 {
481     HeapFree(GetProcessHeap(),0,This);
482     TS_refCount--;
483 }
484
485 static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, LPVOID *ppvOut)
486 {
487     *ppvOut = NULL;
488     if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown))
489     {
490         IClassFactory_AddRef(iface);
491         *ppvOut = iface;
492         return S_OK;
493     }
494
495     return E_NOINTERFACE;
496 }
497
498 static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface)
499 {
500     ClassFactory *This = (ClassFactory *)iface;
501     return InterlockedIncrement(&This->ref);
502 }
503
504 static ULONG WINAPI ClassFactory_Release(IClassFactory *iface)
505 {
506     ClassFactory *This = (ClassFactory *)iface;
507     ULONG ret = InterlockedDecrement(&This->ref);
508
509     if (ret == 0)
510         ClassFactory_Destructor(This);
511     return ret;
512 }
513
514 static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, IUnknown *punkOuter, REFIID iid, LPVOID *ppvOut)
515 {
516     ClassFactory *This = (ClassFactory *)iface;
517     HRESULT ret;
518     IUnknown *obj;
519
520     ret = This->ctor(punkOuter, &obj);
521     if (FAILED(ret))
522         return ret;
523     ret = IUnknown_QueryInterface(obj, iid, ppvOut);
524     IUnknown_Release(obj);
525     return ret;
526 }
527
528 static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock)
529 {
530     if(fLock)
531         InterlockedIncrement(&TS_refCount);
532     else
533         InterlockedDecrement(&TS_refCount);
534
535     return S_OK;
536 }
537
538 static const IClassFactoryVtbl ClassFactoryVtbl = {
539     /* IUnknown */
540     ClassFactory_QueryInterface,
541     ClassFactory_AddRef,
542     ClassFactory_Release,
543
544     /* IClassFactory*/
545     ClassFactory_CreateInstance,
546     ClassFactory_LockServer
547 };
548
549 static HRESULT ClassFactory_Constructor(LPFNCONSTRUCTOR ctor, LPVOID *ppvOut)
550 {
551     ClassFactory *This = HeapAlloc(GetProcessHeap(),0,sizeof(ClassFactory));
552     This->vtbl = &ClassFactoryVtbl;
553     This->ref = 1;
554     This->ctor = ctor;
555     *ppvOut = (LPVOID)This;
556     TS_refCount++;
557     return S_OK;
558 }
559
560 static void TextService_Destructor(TextService *This)
561 {
562     HeapFree(GetProcessHeap(),0,This);
563 }
564
565 static HRESULT WINAPI TextService_QueryInterface(ITfTextInputProcessor *iface, REFIID iid, LPVOID *ppvOut)
566 {
567     TextService *This = (TextService *)iface;
568     *ppvOut = NULL;
569
570     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_ITfTextInputProcessor))
571     {
572         *ppvOut = This;
573     }
574
575     if (*ppvOut)
576     {
577         IUnknown_AddRef(iface);
578         return S_OK;
579     }
580
581     return E_NOINTERFACE;
582 }
583
584 static ULONG WINAPI TextService_AddRef(ITfTextInputProcessor *iface)
585 {
586     TextService *This = (TextService *)iface;
587     return InterlockedIncrement(&This->refCount);
588 }
589
590 static ULONG WINAPI TextService_Release(ITfTextInputProcessor *iface)
591 {
592     TextService *This = (TextService *)iface;
593     ULONG ret;
594
595     ret = InterlockedDecrement(&This->refCount);
596     if (ret == 0)
597         TextService_Destructor(This);
598     return ret;
599 }
600
601 static HRESULT WINAPI TextService_Activate(ITfTextInputProcessor *iface,
602         ITfThreadMgr *ptim, TfClientId id)
603 {
604     trace("TextService_Activate\n");
605     ok(test_ShouldActivate,"Activation came unexpectedly\n");
606     tid = id;
607     return S_OK;
608 }
609
610 static HRESULT WINAPI TextService_Deactivate(ITfTextInputProcessor *iface)
611 {
612     trace("TextService_Deactivate\n");
613     ok(test_ShouldDeactivate,"Deactivation came unexpectedly\n");
614     return S_OK;
615 }
616
617 static const ITfTextInputProcessorVtbl TextService_TextInputProcessorVtbl=
618 {
619     TextService_QueryInterface,
620     TextService_AddRef,
621     TextService_Release,
622
623     TextService_Activate,
624     TextService_Deactivate
625 };
626
627 HRESULT TextService_Constructor(IUnknown *pUnkOuter, IUnknown **ppOut)
628 {
629     TextService *This;
630     if (pUnkOuter)
631         return CLASS_E_NOAGGREGATION;
632
633     This = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(TextService));
634     if (This == NULL)
635         return E_OUTOFMEMORY;
636
637     This->TextInputProcessorVtbl= &TextService_TextInputProcessorVtbl;
638     This->refCount = 1;
639
640     *ppOut = (IUnknown *)This;
641     return S_OK;
642 }
643
644 HRESULT RegisterTextService(REFCLSID rclsid)
645 {
646     ClassFactory_Constructor( TextService_Constructor ,(LPVOID*)&cf);
647     return CoRegisterClassObject(rclsid, (IUnknown*) cf, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &regid);
648 }
649
650 HRESULT UnregisterTextService()
651 {
652     return CoRevokeClassObject(regid);
653 }