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