winex11.drv: Only initialize static variables once per process.
[wine] / dlls / winex11.drv / xim.c
1 /*
2  * Functions for further XIM control
3  *
4  * Copyright 2003 CodeWeavers, Aric Stewart
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 "config.h"
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winuser.h"
29 #include "wingdi.h"
30 #include "winnls.h"
31 #include "x11drv.h"
32 #include "imm.h"
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(x11drv);
36
37 /* this must match with imm32/imm.c */
38 #define FROM_IME 0xcafe1337
39
40 BOOL ximInComposeMode=FALSE;
41
42 static HIMC root_context;
43 static XIMStyle ximStyle = 0;
44 static XIMStyle ximStyleRoot = 0;
45
46 /* moved here from imm32 for dll separation */
47 static DWORD dwCompStringLength = 0;
48 static LPBYTE CompositionString = NULL;
49 static DWORD dwCompStringSize = 0;
50 static LPBYTE ResultString = NULL;
51 static DWORD dwResultStringSize = 0;
52
53 static HMODULE hImmDll = NULL;
54 static HIMC (WINAPI *pImmAssociateContext)(HWND,HIMC);
55 static HIMC (WINAPI *pImmCreateContext)(void);
56 static VOID (WINAPI *pImmSetOpenStatus)(HIMC,BOOL);
57 static BOOL (WINAPI *pImmSetCompositionString)(HIMC, DWORD, LPWSTR,
58                                                DWORD, LPWSTR, DWORD);
59 static VOID (WINAPI *pImmNotifyIME)(HIMC, DWORD, DWORD, DWORD);
60
61 /* WINE specific messages from the xim in x11drv level */
62
63 #define STYLE_OFFTHESPOT (XIMPreeditArea | XIMStatusArea)
64 #define STYLE_OVERTHESPOT (XIMPreeditPosition | XIMStatusNothing)
65 #define STYLE_ROOT (XIMPreeditNothing | XIMStatusNothing)
66 /* this uses all the callbacks to utilize full IME support */
67 #define STYLE_CALLBACK (XIMPreeditCallbacks | XIMStatusNothing)
68 /* inorder to enable deadkey support */
69 #define STYLE_NONE (XIMPreeditNothing | XIMStatusNothing)
70
71 /*
72  * here are the functions that sort of marshall calls into IMM32.DLL
73  */
74 static void LoadImmDll(void)
75 {
76     hImmDll = LoadLibraryA("imm32.dll");
77
78     pImmAssociateContext = (void *)GetProcAddress(hImmDll, "ImmAssociateContext");
79     if (!pImmAssociateContext)
80         WARN("IMM: pImmAssociateContext not found in DLL\n");
81
82     pImmCreateContext = (void *)GetProcAddress(hImmDll, "ImmCreateContext");
83     if (!pImmCreateContext)
84         WARN("IMM: pImmCreateContext not found in DLL\n");
85
86     pImmSetOpenStatus = (void *)GetProcAddress( hImmDll, "ImmSetOpenStatus");
87     if (!pImmSetOpenStatus)
88         WARN("IMM: pImmSetOpenStatus not found in DLL\n");
89
90     pImmSetCompositionString =(void *)GetProcAddress(hImmDll, "ImmSetCompositionStringW");
91
92     if (!pImmSetCompositionString)
93         WARN("IMM: pImmSetCompositionStringW not found in DLL\n");
94
95     pImmNotifyIME = (void *)GetProcAddress( hImmDll, "ImmNotifyIME");
96
97     if (!pImmNotifyIME)
98         WARN("IMM: pImmNotifyIME not found in DLL\n");
99 }
100
101 static BOOL X11DRV_ImmSetInternalString(DWORD dwIndex, DWORD dwOffset,
102                                         DWORD selLength, LPWSTR lpComp, DWORD dwCompLen)
103 {
104     /* Composition strings are edited in chunks */
105     unsigned int byte_length = dwCompLen * sizeof(WCHAR);
106     unsigned int byte_offset = dwOffset * sizeof(WCHAR);
107     unsigned int byte_selection = selLength * sizeof(WCHAR);
108     BOOL rc = FALSE;
109
110     TRACE("( %i, %i, %d, %p, %d):\n", dwOffset, selLength, dwIndex, lpComp, dwCompLen );
111
112     if (dwIndex == GCS_COMPSTR)
113     {
114         unsigned int i,j;
115         LPBYTE ptr_new;
116         LPBYTE ptr_old;
117
118         if ((dwCompLen == 0) && (selLength == 0))
119         {
120             /* DO Nothing */
121         }
122         /* deletion occurred */
123         else if ((dwCompLen== 0) && (selLength != 0))
124         {
125             if (dwCompStringLength)
126             {
127                 for (i = 0; i < byte_selection; i++)
128                 {
129                     if (byte_offset+byte_selection+i <
130                         dwCompStringLength)
131                     {
132                         CompositionString[byte_offset + i] =
133                         CompositionString[byte_offset + byte_selection + i];
134                     }
135                     else
136                         CompositionString[byte_offset + i] = 0;
137                 }
138                 /* clean up the end */
139                 dwCompStringLength -= byte_selection;
140
141                 i = dwCompStringLength;
142                 while (i < dwCompStringSize)
143                 {
144                     CompositionString[i++] = 0;
145                 }
146             }
147         }
148         else
149         {
150             int byte_expansion = byte_length - byte_selection;
151
152             if (byte_expansion + dwCompStringLength >= dwCompStringSize)
153             {
154                 if (CompositionString)
155                     CompositionString =
156                         HeapReAlloc(GetProcessHeap(), 0,
157                                     CompositionString,
158                                     dwCompStringSize +
159                                     byte_expansion);
160                 else
161                      CompositionString =
162                         HeapAlloc(GetProcessHeap(), 0, dwCompStringSize +
163                                     byte_expansion);
164
165                 memset(&(CompositionString[dwCompStringSize]), 0,
166                         byte_expansion);
167
168                 dwCompStringSize += byte_expansion;
169             }
170
171             ptr_new =  ((LPBYTE)lpComp);
172             ptr_old = CompositionString + byte_offset + byte_selection;
173
174             dwCompStringLength += byte_expansion;
175
176             for (j=0,i = byte_offset; i < dwCompStringSize; i++)
177             {
178                 if (j < byte_length)
179                 {
180                     CompositionString[i] = ptr_new[j++];
181                 }
182                 else
183                 {
184                     if (ptr_old < CompositionString + dwCompStringSize)
185                     {
186                         CompositionString[i] = *ptr_old;
187                         ptr_old++;
188                             }
189                     else
190                         CompositionString[i] = 0;
191                 }
192             }
193         }
194
195         if (pImmSetCompositionString)
196             rc = pImmSetCompositionString((HIMC)FROM_IME, SCS_SETSTR,
197                                  (LPWSTR)CompositionString, dwCompStringLength,
198                                   NULL, 0);
199     }
200     else if ((dwIndex == GCS_RESULTSTR) && (lpComp) && (dwCompLen))
201     {
202         if (dwResultStringSize)
203             HeapFree(GetProcessHeap(),0,ResultString);
204         dwResultStringSize= byte_length;
205         ResultString= HeapAlloc(GetProcessHeap(),0,byte_length);
206         memcpy(ResultString,lpComp,byte_length);
207
208         if (pImmSetCompositionString)
209             rc = pImmSetCompositionString((HIMC)FROM_IME, SCS_SETSTR,
210                                  (LPWSTR)ResultString, dwResultStringSize,
211                                   NULL, 0);
212
213         if (pImmNotifyIME)
214             pImmNotifyIME((HIMC)FROM_IME, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
215     }
216
217     return rc;
218 }
219
220 void X11DRV_XIMLookupChars( const char *str, DWORD count )
221 {
222     DWORD dwOutput;
223     WCHAR wcOutput[64];
224     HWND focus;
225
226     dwOutput = MultiByteToWideChar(CP_UNIXCP, 0, str, count, wcOutput, sizeof(wcOutput)/sizeof(WCHAR));
227
228     if (pImmAssociateContext && (focus = GetFocus()))
229         pImmAssociateContext(focus,root_context);
230
231     X11DRV_ImmSetInternalString(GCS_RESULTSTR,0,0,wcOutput,dwOutput);
232 }
233
234 static void X11DRV_ImmSetOpenStatus(BOOL fOpen)
235 {
236     if (fOpen == FALSE)
237     {
238         if (dwCompStringSize)
239             HeapFree(GetProcessHeap(),0,CompositionString);
240
241         dwCompStringSize = 0;
242         dwCompStringLength = 0;
243         CompositionString = NULL;
244
245         if (dwResultStringSize)
246             HeapFree(GetProcessHeap(),0,ResultString);
247
248         dwResultStringSize = 0;
249         ResultString = NULL;
250     }
251
252     if (pImmSetOpenStatus)
253         pImmSetOpenStatus((HIMC)FROM_IME,fOpen);
254 }
255
256 static int XIMPreEditStartCallback(XIC ic, XPointer client_data, XPointer call_data)
257 {
258     TRACE("PreEditStartCallback %p\n",ic);
259     X11DRV_ImmSetOpenStatus(TRUE);
260     ximInComposeMode = TRUE;
261     return -1;
262 }
263
264 static void XIMPreEditDoneCallback(XIC ic, XPointer client_data, XPointer call_data)
265 {
266     TRACE("PreeditDoneCallback %p\n",ic);
267     ximInComposeMode = FALSE;
268     X11DRV_ImmSetOpenStatus(FALSE);
269 }
270
271 static void XIMPreEditDrawCallback(XIM ic, XPointer client_data,
272                                    XIMPreeditDrawCallbackStruct *P_DR)
273 {
274     DWORD dwOutput;
275     WCHAR wcOutput[64];
276
277     TRACE("PreEditDrawCallback %p\n",ic);
278
279     if (P_DR)
280     {
281         int sel = P_DR->chg_first;
282         int len = P_DR->chg_length;
283         if (P_DR->text)
284         {
285             if (! P_DR->text->encoding_is_wchar)
286             {
287                 TRACE("multibyte\n");
288                 dwOutput = MultiByteToWideChar(CP_UNIXCP, 0,
289                            P_DR->text->string.multi_byte, -1,
290                            wcOutput, 64);
291
292                 /* ignore null */
293                 dwOutput --;
294                 X11DRV_ImmSetInternalString (GCS_COMPSTR, sel, len, wcOutput, dwOutput);
295             }
296             else
297             {
298                 FIXME("wchar PROBIBILY WRONG\n");
299                 X11DRV_ImmSetInternalString (GCS_COMPSTR, sel, len,
300                                              (LPWSTR)P_DR->text->string.wide_char,
301                                              P_DR->text->length);
302             }
303         }
304         else
305             X11DRV_ImmSetInternalString (GCS_COMPSTR, sel, len, NULL, 0);
306     }
307     TRACE("Finished\n");
308 }
309
310 static void XIMPreEditCaretCallback(XIC ic, XPointer client_data,
311                                     XIMPreeditCaretCallbackStruct *P_C)
312 {
313     FIXME("PreeditCaretCalback %p\n",ic);
314 }
315
316 void X11DRV_ForceXIMReset(HWND hwnd)
317 {
318     XIC ic = X11DRV_get_ic(hwnd);
319     if (ic)
320     {
321         char* leftover;
322         TRACE("Forcing Reset %p\n",ic);
323         wine_tsx11_lock();
324         leftover = XmbResetIC(ic);
325         XFree(leftover);
326         wine_tsx11_unlock();
327     }
328 }
329
330 /***********************************************************************
331 *           X11DRV Ime creation
332 */
333 XIM X11DRV_SetupXIM(Display *display, const char *input_style)
334 {
335     XIMStyle ximStyleRequest, ximStyleCallback, ximStyleNone;
336     XIMStyles *ximStyles = NULL;
337     INT i;
338     XIM xim;
339
340     ximStyleRequest = STYLE_CALLBACK;
341
342     if (!strcasecmp(input_style, "offthespot"))
343         ximStyleRequest = STYLE_OFFTHESPOT;
344     else if (!strcasecmp(input_style, "overthespot"))
345         ximStyleRequest = STYLE_OVERTHESPOT;
346     else if (!strcasecmp(input_style, "root"))
347         ximStyleRequest = STYLE_ROOT;
348
349     wine_tsx11_lock();
350
351     if(!XSupportsLocale())
352     {
353         WARN("X does not support locale.\n");
354         goto err;
355     }
356     if(XSetLocaleModifiers("") == NULL)
357     {
358         WARN("Could not set locale modifiers.\n");
359         goto err;
360     }
361
362     xim = XOpenIM(display, NULL, NULL, NULL);
363     if (xim == NULL)
364     {
365         WARN("Could not open input method.\n");
366         goto err;
367     }
368
369     TRACE("X display of IM = %p\n", XDisplayOfIM(xim));
370     TRACE("Using %s locale of Input Method\n", XLocaleOfIM(xim));
371
372     XGetIMValues(xim, XNQueryInputStyle, &ximStyles, NULL);
373     if (ximStyles == 0)
374     {
375         WARN("Could not find supported input style.\n");
376     }
377     else
378     {
379         TRACE("ximStyles->count_styles = %d\n", ximStyles->count_styles);
380
381         ximStyleRoot = 0;
382         ximStyleNone = 0;
383         ximStyleCallback = 0;
384
385         for (i = 0; i < ximStyles->count_styles; ++i)
386         {
387             int style = ximStyles->supported_styles[i];
388             TRACE("ximStyles[%d] = %s%s%s%s%s\n", i,
389                         (style&XIMPreeditArea)?"XIMPreeditArea ":"",
390                         (style&XIMPreeditCallbacks)?"XIMPreeditCallbacks ":"",
391                         (style&XIMPreeditPosition)?"XIMPreeditPosition ":"",
392                         (style&XIMPreeditNothing)?"XIMPreeditNothing ":"",
393                         (style&XIMPreeditNone)?"XIMPreeditNone ":"");
394             if (!ximStyle && (ximStyles->supported_styles[i] ==
395                                 ximStyleRequest))
396             {
397                 ximStyle = ximStyleRequest;
398                 TRACE("Setting Style: ximStyle = ximStyleRequest\n");
399             }
400             else if (!ximStyleRoot &&(ximStyles->supported_styles[i] ==
401                      STYLE_ROOT))
402             {
403                 ximStyleRoot = STYLE_ROOT;
404                 TRACE("Setting Style: ximStyleRoot = STYLE_ROOT\n");
405             }
406             else if (!ximStyleCallback &&(ximStyles->supported_styles[i] ==
407                      STYLE_CALLBACK))
408             {
409                 ximStyleCallback = STYLE_CALLBACK;
410                 TRACE("Setting Style: ximStyleCallback = STYLE_CALLBACK\n");
411             }
412             else if (!ximStyleNone && (ximStyles->supported_styles[i] ==
413                      STYLE_NONE))
414             {
415                 TRACE("Setting Style: ximStyleNone = STYLE_NONE\n");
416                 ximStyleNone = STYLE_NONE;
417             }
418         }
419         XFree(ximStyles);
420
421         if (ximStyle == 0)
422             ximStyle = ximStyleRoot;
423
424         if (ximStyle == 0)
425             ximStyle = ximStyleNone;
426
427         if (ximStyleCallback == 0)
428         {
429             TRACE("No callback style avalable\n");
430             ximStyleCallback = ximStyle;
431         }
432
433     }
434
435     wine_tsx11_unlock();
436
437     if(!hImmDll)
438     {
439         LoadImmDll();
440
441         if (pImmCreateContext)
442         {
443             root_context = pImmCreateContext();
444             if (pImmAssociateContext)
445                 pImmAssociateContext(0,root_context);
446         }
447     }
448
449     return xim;
450
451 err:
452     wine_tsx11_unlock();
453     return NULL;
454 }
455
456
457 XIC X11DRV_CreateIC(XIM xim, Display *display, Window win)
458 {
459     XFontSet fontSet;
460     char **list;
461     int count;
462     XPoint spot = {0};
463     XVaNestedList preedit = NULL;
464     XVaNestedList status = NULL;
465     XIC xic;
466     XIMCallback P_StartCB;
467     XIMCallback P_DoneCB;
468     XIMCallback P_DrawCB;
469     XIMCallback P_CaretCB;
470     LANGID langid = PRIMARYLANGID(LANGIDFROMLCID(GetThreadLocale()));
471
472     wine_tsx11_lock();
473
474     /* use complex and slow XIC initialization method only for CJK */
475     if (langid != LANG_CHINESE &&
476         langid != LANG_JAPANESE &&
477         langid != LANG_KOREAN)
478     {
479         xic = XCreateIC(xim,
480                         XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
481                         XNClientWindow, win,
482                         XNFocusWindow, win,
483                         NULL);
484         wine_tsx11_unlock();
485         return xic;
486     }
487
488     fontSet = XCreateFontSet(display,
489                       "*", /*FIXME*/
490                       &list, &count, NULL);
491
492     TRACE("ximFontSet = %p\n", fontSet);
493     TRACE("list = %p, count = %d\n", list, count);
494
495     if (list != NULL)
496     {
497         int i;
498
499         for (i = 0; i < count; ++i)
500         {
501             TRACE("list[%d] = %s\n", i, list[i]);
502         }
503         XFreeStringList(list);
504     }
505
506     /* create callbacks */
507     P_StartCB.client_data = NULL;
508     P_StartCB.callback = (XIMProc)XIMPreEditStartCallback;
509     P_DoneCB.client_data = NULL;
510     P_DoneCB.callback = (XIMProc)XIMPreEditDoneCallback;
511     P_DrawCB.client_data = NULL;
512     P_DrawCB.callback = (XIMProc)XIMPreEditDrawCallback;
513     P_CaretCB.client_data = NULL;
514     P_CaretCB.callback = (XIMProc)XIMPreEditCaretCallback;
515
516     if ((ximStyle & (XIMPreeditNothing | XIMPreeditNone)) == 0)
517     {
518         preedit = XVaCreateNestedList(0,
519                         XNFontSet, fontSet,
520                         XNSpotLocation, &spot,
521                         XNPreeditStartCallback, &P_StartCB,
522                         XNPreeditDoneCallback, &P_DoneCB,
523                         XNPreeditDrawCallback, &P_DrawCB,
524                         XNPreeditCaretCallback, &P_CaretCB,
525                         NULL);
526         TRACE("preedit = %p\n", preedit);
527     }
528     else
529     {
530         preedit = XVaCreateNestedList(0,
531                         XNPreeditStartCallback, &P_StartCB,
532                         XNPreeditDoneCallback, &P_DoneCB,
533                         XNPreeditDrawCallback, &P_DrawCB,
534                         XNPreeditCaretCallback, &P_CaretCB,
535                         NULL);
536
537         TRACE("preedit = %p\n", preedit);
538     }
539
540     if ((ximStyle & (XIMStatusNothing | XIMStatusNone)) == 0)
541     {
542         status = XVaCreateNestedList(0,
543             XNFontSet, fontSet,
544             NULL);
545         TRACE("status = %p\n", status);
546      }
547
548     if (preedit != NULL && status != NULL)
549     {
550         xic = XCreateIC(xim,
551               XNInputStyle, ximStyle,
552               XNPreeditAttributes, preedit,
553               XNStatusAttributes, status,
554               XNClientWindow, win,
555               XNFocusWindow, win,
556               NULL);
557      }
558     else if (preedit != NULL)
559     {
560         xic = XCreateIC(xim,
561               XNInputStyle, ximStyle,
562               XNPreeditAttributes, preedit,
563               XNClientWindow, win,
564               XNFocusWindow, win,
565               NULL);
566     }
567     else if (status != NULL)
568     {
569         xic = XCreateIC(xim,
570               XNInputStyle, ximStyle,
571               XNStatusAttributes, status,
572               XNClientWindow, win,
573               XNFocusWindow, win,
574               NULL);
575     }
576     else
577     {
578         xic = XCreateIC(xim,
579               XNInputStyle, ximStyle,
580               XNClientWindow, win,
581               XNFocusWindow, win,
582               NULL);
583     }
584
585     TRACE("xic = %p\n", xic);
586
587     if (preedit != NULL)
588         XFree(preedit);
589     if (status != NULL)
590         XFree(status);
591
592     wine_tsx11_unlock();
593
594     return xic;
595 }