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