2 * Functions for further XIM control
4 * Copyright 2003 CodeWeavers, Aric Stewart
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.
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.
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
31 #define WIN32_NO_STATUS
38 #include "wine/debug.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(xim
);
42 #ifndef HAVE_XICCALLBACK_CALLBACK
43 #define XICCallback XIMCallback
44 #define XICProc XIMProc
57 static pthread_mutex_t ime_mutex
= PTHREAD_MUTEX_INITIALIZER
;
58 static struct list ime_updates
= LIST_INIT(ime_updates
);
59 static DWORD ime_update_count
;
60 static WCHAR
*ime_comp_buf
;
62 static XIMStyle input_style
= 0;
63 static XIMStyle input_style_req
= XIMPreeditCallbacks
| XIMStatusCallbacks
;
65 static const char *debugstr_xim_style( XIMStyle style
)
67 char buffer
[1024], *buf
= buffer
;
69 buf
+= sprintf( buf
, "preedit" );
70 if (style
& XIMPreeditArea
) buf
+= sprintf( buf
, " area" );
71 if (style
& XIMPreeditCallbacks
) buf
+= sprintf( buf
, " callbacks" );
72 if (style
& XIMPreeditPosition
) buf
+= sprintf( buf
, " position" );
73 if (style
& XIMPreeditNothing
) buf
+= sprintf( buf
, " nothing" );
74 if (style
& XIMPreeditNone
) buf
+= sprintf( buf
, " none" );
76 buf
+= sprintf( buf
, ", status" );
77 if (style
& XIMStatusArea
) buf
+= sprintf( buf
, " area" );
78 if (style
& XIMStatusCallbacks
) buf
+= sprintf( buf
, " callbacks" );
79 if (style
& XIMStatusNothing
) buf
+= sprintf( buf
, " nothing" );
80 if (style
& XIMStatusNone
) buf
+= sprintf( buf
, " none" );
82 return wine_dbg_sprintf( "%s", buffer
);
85 BOOL
xim_in_compose_mode(void)
87 return !!ime_comp_buf
;
90 static void post_ime_update( HWND hwnd
, UINT cursor_pos
, WCHAR
*comp_str
, WCHAR
*result_str
)
92 UINT id
, comp_len
, result_len
;
93 struct ime_update
*update
;
95 comp_len
= comp_str
? wcslen( comp_str
) + 1 : 0;
96 result_len
= result_str
? wcslen( result_str
) + 1 : 0;
98 if (!(update
= malloc( offsetof(struct ime_update
, buffer
[comp_len
+ result_len
]) ))) return;
99 update
->cursor_pos
= cursor_pos
;
100 update
->comp_str
= comp_str
? memcpy( update
->buffer
, comp_str
, comp_len
* sizeof(WCHAR
) ) : NULL
;
101 update
->result_str
= result_str
? memcpy( update
->buffer
+ comp_len
, result_str
, result_len
* sizeof(WCHAR
) ) : NULL
;
103 pthread_mutex_lock( &ime_mutex
);
104 id
= update
->id
= ++ime_update_count
;
105 list_add_tail( &ime_updates
, &update
->entry
);
106 pthread_mutex_unlock( &ime_mutex
);
108 NtUserPostMessage( hwnd
, WM_IME_NOTIFY
, IMN_WINE_SET_COMP_STRING
, id
);
111 static void xim_update_comp_string( UINT offset
, UINT old_len
, const WCHAR
*text
, UINT new_len
)
113 UINT len
= ime_comp_buf
? wcslen( ime_comp_buf
) : 0;
114 int diff
= new_len
- old_len
;
117 TRACE( "offset %u, old_len %u, text %s\n", offset
, old_len
, debugstr_wn(text
, new_len
) );
119 if (!(ptr
= realloc( ime_comp_buf
, (len
+ max(0, diff
) + 1) * sizeof(WCHAR
) )))
121 ERR( "Failed to reallocate composition string buffer\n" );
126 ptr
= ime_comp_buf
+ offset
;
127 memmove( ptr
+ new_len
, ptr
+ old_len
, (len
- offset
- old_len
) * sizeof(WCHAR
) );
128 if (text
) memcpy( ptr
, text
, new_len
* sizeof(WCHAR
) );
129 ime_comp_buf
[len
+ diff
] = 0;
132 void xim_set_result_string( HWND hwnd
, const char *str
, UINT count
)
137 TRACE( "hwnd %p, string %s\n", hwnd
, debugstr_an(str
, count
) );
139 if (!(output
= malloc( (count
+ 1) * sizeof(WCHAR
) ))) return;
140 len
= ntdll_umbstowcs( str
, count
, output
, count
);
143 post_ime_update( hwnd
, 0, NULL
, output
);
148 static BOOL
xic_preedit_state_notify( XIC xic
, XPointer user
, XPointer arg
)
150 XIMPreeditStateNotifyCallbackStruct
*params
= (void *)arg
;
151 const XIMPreeditState state
= params
->state
;
152 HWND hwnd
= (HWND
)user
;
154 TRACE( "xic %p, hwnd %p, state %lu\n", xic
, hwnd
, state
);
158 case XIMPreeditEnable
:
159 NtUserPostMessage( hwnd
, WM_IME_NOTIFY
, IMN_WINE_SET_OPEN_STATUS
, TRUE
);
161 case XIMPreeditDisable
:
162 NtUserPostMessage( hwnd
, WM_IME_NOTIFY
, IMN_WINE_SET_OPEN_STATUS
, FALSE
);
169 static int xic_preedit_start( XIC xic
, XPointer user
, XPointer arg
)
171 HWND hwnd
= (HWND
)user
;
173 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
175 if ((ime_comp_buf
= realloc( ime_comp_buf
, sizeof(WCHAR
) ))) *ime_comp_buf
= 0;
176 else ERR( "Failed to allocate preedit buffer\n" );
178 NtUserPostMessage( hwnd
, WM_IME_NOTIFY
, IMN_WINE_SET_OPEN_STATUS
, TRUE
);
179 post_ime_update( hwnd
, 0, ime_comp_buf
, NULL
);
184 static int xic_preedit_done( XIC xic
, XPointer user
, XPointer arg
)
186 HWND hwnd
= (HWND
)user
;
188 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
190 free( ime_comp_buf
);
193 post_ime_update( hwnd
, 0, NULL
, NULL
);
194 NtUserPostMessage( hwnd
, WM_IME_NOTIFY
, IMN_WINE_SET_OPEN_STATUS
, FALSE
);
199 static int xic_preedit_draw( XIC xic
, XPointer user
, XPointer arg
)
201 XIMPreeditDrawCallbackStruct
*params
= (void *)arg
;
202 HWND hwnd
= (HWND
)user
;
209 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
211 if (!params
) return 0;
213 if (!(text
= params
->text
)) str
= NULL
;
214 else if (!text
->encoding_is_wchar
) str
= text
->string
.multi_byte
;
215 else if ((len
= wcstombs( NULL
, text
->string
.wide_char
, text
->length
)) < 0) str
= NULL
;
216 else if ((str
= malloc( len
+ 1 )))
218 wcstombs( str
, text
->string
.wide_char
, len
);
222 if (!str
|| !(text_len
= strlen( str
)) || !(output
= malloc( text_len
* sizeof(WCHAR
) )))
223 xim_update_comp_string( params
->chg_first
, params
->chg_length
, NULL
, 0 );
226 text_len
= ntdll_umbstowcs( str
, text_len
, output
, text_len
);
227 xim_update_comp_string( params
->chg_first
, params
->chg_length
, output
, text_len
);
231 if (text
&& str
!= text
->string
.multi_byte
) free( str
);
233 post_ime_update( hwnd
, params
->caret
, ime_comp_buf
, NULL
);
238 static int xic_preedit_caret( XIC xic
, XPointer user
, XPointer arg
)
240 static int xim_caret_pos
;
241 XIMPreeditCaretCallbackStruct
*params
= (void *)arg
;
242 HWND hwnd
= (HWND
)user
;
245 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
247 if (!params
) return 0;
250 switch (params
->direction
)
256 case XIMBackwardChar
:
257 case XIMBackwardWord
:
263 case XIMAbsolutePosition
:
264 pos
= params
->position
;
267 params
->position
= pos
;
271 case XIMPreviousLine
:
274 FIXME( "Not implemented\n" );
277 params
->position
= xim_caret_pos
= pos
;
279 post_ime_update( hwnd
, pos
, ime_comp_buf
, NULL
);
284 static int xic_status_start( XIC xic
, XPointer user
, XPointer arg
)
286 HWND hwnd
= (HWND
)user
;
287 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
291 static int xic_status_done( XIC xic
, XPointer user
, XPointer arg
)
293 HWND hwnd
= (HWND
)user
;
294 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
298 static int xic_status_draw( XIC xic
, XPointer user
, XPointer arg
)
300 HWND hwnd
= (HWND
)user
;
301 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
305 /***********************************************************************
306 * NotifyIMEStatus (X11DRV.@)
308 void X11DRV_NotifyIMEStatus( HWND hwnd
, UINT status
)
310 XIMPreeditState state
= status
? XIMPreeditEnable
: XIMPreeditDisable
;
314 TRACE( "hwnd %p, status %#x\n", hwnd
, status
);
316 if (!(xic
= X11DRV_get_ic( hwnd
))) return;
318 if ((attr
= XVaCreateNestedList( 0, XNPreeditState
, state
, NULL
)))
320 XSetICValues( xic
, XNPreeditAttributes
, attr
, NULL
);
324 if (!status
) XFree( XmbResetIC( xic
) );
327 /***********************************************************************
330 BOOL
xim_init( const WCHAR
*input_style
)
332 static const WCHAR offthespotW
[] = {'o','f','f','t','h','e','s','p','o','t',0};
333 static const WCHAR overthespotW
[] = {'o','v','e','r','t','h','e','s','p','o','t',0};
334 static const WCHAR rootW
[] = {'r','o','o','t',0};
336 if (!XSupportsLocale())
338 WARN("X does not support locale.\n");
341 if (XSetLocaleModifiers("") == NULL
)
343 WARN("Could not set locale modifiers.\n");
347 if (!wcsicmp( input_style
, offthespotW
))
348 input_style_req
= XIMPreeditArea
| XIMStatusArea
;
349 else if (!wcsicmp( input_style
, overthespotW
))
350 input_style_req
= XIMPreeditPosition
| XIMStatusNothing
;
351 else if (!wcsicmp( input_style
, rootW
))
352 input_style_req
= XIMPreeditNothing
| XIMStatusNothing
;
354 TRACE( "requesting %s style %#lx %s\n", debugstr_w(input_style
), input_style_req
,
355 debugstr_xim_style( input_style_req
) );
360 static void xim_open( Display
*display
, XPointer user
, XPointer arg
);
361 static void xim_destroy( XIM xim
, XPointer user
, XPointer arg
);
363 static XIM
xim_create( struct x11drv_thread_data
*data
)
365 XIMCallback destroy
= {.callback
= xim_destroy
, .client_data
= (XPointer
)data
};
366 XIMStyle input_style_fallback
= XIMPreeditNone
| XIMStatusNone
;
367 XIMStyles
*styles
= NULL
;
371 if (!(xim
= XOpenIM( data
->display
, NULL
, NULL
, NULL
)))
373 WARN("Could not open input method.\n");
377 if (XSetIMValues( xim
, XNDestroyCallback
, &destroy
, NULL
))
378 WARN( "Could not set destroy callback.\n" );
380 TRACE( "xim %p, XDisplayOfIM %p, XLocaleOfIM %s\n", xim
, XDisplayOfIM( xim
),
381 debugstr_a(XLocaleOfIM( xim
)) );
383 XGetIMValues( xim
, XNQueryInputStyle
, &styles
, NULL
);
386 WARN( "Could not find supported input style.\n" );
391 TRACE( "input styles count %u\n", styles
->count_styles
);
392 for (i
= 0, input_style
= 0; i
< styles
->count_styles
; ++i
)
394 XIMStyle style
= styles
->supported_styles
[i
];
395 TRACE( " %u: %#lx %s\n", i
, style
, debugstr_xim_style( style
) );
397 if (style
== input_style_req
) input_style
= style
;
398 if (!input_style
&& (style
& input_style_req
)) input_style
= style
;
399 if (input_style_fallback
> style
) input_style_fallback
= style
;
403 if (!input_style
) input_style
= input_style_fallback
;
404 TRACE( "selected style %#lx %s\n", input_style
, debugstr_xim_style( input_style
) );
409 static void xim_open( Display
*display
, XPointer user
, XPointer arg
)
411 struct x11drv_thread_data
*data
= (void *)user
;
412 TRACE( "display %p, data %p, arg %p\n", display
, user
, arg
);
413 if (!(data
->xim
= xim_create( data
))) return;
414 XUnregisterIMInstantiateCallback( display
, NULL
, NULL
, NULL
, xim_open
, user
);
417 static void xim_destroy( XIM xim
, XPointer user
, XPointer arg
)
419 struct x11drv_thread_data
*data
= x11drv_thread_data();
420 TRACE( "xim %p, user %p, arg %p\n", xim
, user
, arg
);
421 if (data
->xim
!= xim
) return;
423 XRegisterIMInstantiateCallback( data
->display
, NULL
, NULL
, NULL
, xim_open
, user
);
426 void xim_thread_attach( struct x11drv_thread_data
*data
)
428 Display
*display
= data
->display
;
432 data
->font_set
= XCreateFontSet( display
, "fixed", &list
, &count
, NULL
);
433 TRACE( "created XFontSet %p, list %p, count %d\n", data
->font_set
, list
, count
);
434 for (i
= 0; list
&& i
< count
; ++i
) TRACE( " %d: %s\n", i
, list
[i
] );
435 if (list
) XFreeStringList( list
);
437 if ((data
->xim
= xim_create( data
))) return;
438 XRegisterIMInstantiateCallback( display
, NULL
, NULL
, NULL
, xim_open
, (XPointer
)data
);
441 static BOOL
xic_destroy( XIC xic
, XPointer user
, XPointer arg
)
443 struct x11drv_win_data
*data
;
444 HWND hwnd
= (HWND
)user
;
446 TRACE( "xic %p, hwnd %p, arg %p\n", xic
, hwnd
, arg
);
448 if ((data
= get_win_data( hwnd
)))
450 if (data
->xic
== xic
) data
->xic
= NULL
;
451 release_win_data( data
);
457 static XIC
xic_create( XIM xim
, HWND hwnd
, Window win
)
459 XICCallback destroy
= {.callback
= xic_destroy
, .client_data
= (XPointer
)hwnd
};
460 XICCallback preedit_caret
= {.callback
= xic_preedit_caret
, .client_data
= (XPointer
)hwnd
};
461 XICCallback preedit_done
= {.callback
= xic_preedit_done
, .client_data
= (XPointer
)hwnd
};
462 XICCallback preedit_draw
= {.callback
= xic_preedit_draw
, .client_data
= (XPointer
)hwnd
};
463 XICCallback preedit_start
= {.callback
= xic_preedit_start
, .client_data
= (XPointer
)hwnd
};
464 XICCallback preedit_state_notify
= {.callback
= xic_preedit_state_notify
, .client_data
= (XPointer
)hwnd
};
465 XICCallback status_done
= {.callback
= xic_status_done
, .client_data
= (XPointer
)hwnd
};
466 XICCallback status_draw
= {.callback
= xic_status_draw
, .client_data
= (XPointer
)hwnd
};
467 XICCallback status_start
= {.callback
= xic_status_start
, .client_data
= (XPointer
)hwnd
};
469 XVaNestedList preedit
, status
;
471 XFontSet fontSet
= x11drv_thread_data()->font_set
;
473 TRACE( "xim %p, hwnd %p/%lx\n", xim
, hwnd
, win
);
475 preedit
= XVaCreateNestedList( 0, XNFontSet
, fontSet
,
476 XNPreeditCaretCallback
, &preedit_caret
,
477 XNPreeditDoneCallback
, &preedit_done
,
478 XNPreeditDrawCallback
, &preedit_draw
,
479 XNPreeditStartCallback
, &preedit_start
,
480 XNPreeditStateNotifyCallback
, &preedit_state_notify
,
481 XNSpotLocation
, &spot
, NULL
);
482 status
= XVaCreateNestedList( 0, XNFontSet
, fontSet
,
483 XNStatusStartCallback
, &status_start
,
484 XNStatusDoneCallback
, &status_done
,
485 XNStatusDrawCallback
, &status_draw
,
487 xic
= XCreateIC( xim
, XNInputStyle
, input_style
, XNPreeditAttributes
, preedit
, XNStatusAttributes
, status
,
488 XNClientWindow
, win
, XNFocusWindow
, win
, XNDestroyCallback
, &destroy
, NULL
);
489 TRACE( "created XIC %p\n", xic
);
497 XIC
X11DRV_get_ic( HWND hwnd
)
499 struct x11drv_win_data
*data
;
503 if (!(data
= get_win_data( hwnd
))) return 0;
504 x11drv_thread_data()->last_xic_hwnd
= hwnd
;
505 if (!(ret
= data
->xic
) && (xim
= x11drv_thread_data()->xim
))
506 ret
= data
->xic
= xic_create( xim
, hwnd
, data
->whole_window
);
507 release_win_data( data
);
512 void xim_set_focus( HWND hwnd
, BOOL focus
)
514 struct list updates
= LIST_INIT(updates
);
515 struct ime_update
*update
, *next
;
518 if (!(xic
= X11DRV_get_ic( hwnd
))) return;
520 if (focus
) XSetICFocus( xic
);
521 else XUnsetICFocus( xic
);
523 pthread_mutex_lock( &ime_mutex
);
524 list_move_tail( &updates
, &ime_updates
);
525 pthread_mutex_unlock( &ime_mutex
);
527 LIST_FOR_EACH_ENTRY_SAFE( update
, next
, &updates
, struct ime_update
, entry
) free( update
);
530 static struct ime_update
*find_ime_update( UINT id
)
532 struct ime_update
*update
;
533 LIST_FOR_EACH_ENTRY( update
, &ime_updates
, struct ime_update
, entry
)
534 if (update
->id
== id
) return update
;
538 /***********************************************************************
539 * ImeToAsciiEx (X11DRV.@)
541 * As XIM filters key events upfront, we don't use ImeProcessKey and ImeToAsciiEx is instead called
542 * back from the IME UI window procedure when WM_IME_NOTIFY / IMN_WINE_SET_COMP_STRING messages are
543 * sent to it, to retrieve composition string updates and generate WM_IME messages.
545 UINT
X11DRV_ImeToAsciiEx( UINT vkey
, UINT lparam
, const BYTE
*state
, COMPOSITIONSTRING
*compstr
, HIMC himc
)
547 UINT needed
= sizeof(COMPOSITIONSTRING
), comp_len
, result_len
;
548 struct ime_update
*update
;
551 TRACE( "vkey %#x, lparam %#x, state %p, compstr %p, himc %p\n", vkey
, lparam
, state
, compstr
, himc
);
553 pthread_mutex_lock( &ime_mutex
);
555 if (!(update
= find_ime_update( lparam
)))
557 pthread_mutex_unlock( &ime_mutex
);
561 if (!update
->comp_str
) comp_len
= 0;
564 comp_len
= wcslen( update
->comp_str
);
565 needed
+= comp_len
* sizeof(WCHAR
); /* GCS_COMPSTR */
566 needed
+= comp_len
; /* GCS_COMPATTR */
567 needed
+= 2 * sizeof(DWORD
); /* GCS_COMPCLAUSE */
570 if (!update
->result_str
) result_len
= 0;
573 result_len
= wcslen( update
->result_str
);
574 needed
+= result_len
* sizeof(WCHAR
); /* GCS_RESULTSTR */
575 needed
+= 2 * sizeof(DWORD
); /* GCS_RESULTCLAUSE */
578 if (compstr
->dwSize
< needed
)
580 compstr
->dwSize
= needed
;
581 pthread_mutex_unlock( &ime_mutex
);
582 return STATUS_BUFFER_TOO_SMALL
;
585 list_remove( &update
->entry
);
586 pthread_mutex_unlock( &ime_mutex
);
588 memset( compstr
, 0, sizeof(*compstr
) );
589 compstr
->dwSize
= sizeof(*compstr
);
591 if (update
->comp_str
)
593 compstr
->dwCursorPos
= update
->cursor_pos
;
595 compstr
->dwCompStrLen
= comp_len
;
596 compstr
->dwCompStrOffset
= compstr
->dwSize
;
597 dst
= (BYTE
*)compstr
+ compstr
->dwCompStrOffset
;
598 memcpy( dst
, update
->comp_str
, compstr
->dwCompStrLen
* sizeof(WCHAR
) );
599 compstr
->dwSize
+= compstr
->dwCompStrLen
* sizeof(WCHAR
);
601 compstr
->dwCompClauseLen
= 2 * sizeof(DWORD
);
602 compstr
->dwCompClauseOffset
= compstr
->dwSize
;
603 dst
= (BYTE
*)compstr
+ compstr
->dwCompClauseOffset
;
604 *((DWORD
*)dst
+ 0) = 0;
605 *((DWORD
*)dst
+ 1) = compstr
->dwCompStrLen
;
606 compstr
->dwSize
+= compstr
->dwCompClauseLen
;
608 compstr
->dwCompAttrLen
= compstr
->dwCompStrLen
;
609 compstr
->dwCompAttrOffset
= compstr
->dwSize
;
610 dst
= (BYTE
*)compstr
+ compstr
->dwCompAttrOffset
;
611 memset( dst
, ATTR_INPUT
, compstr
->dwCompAttrLen
);
612 compstr
->dwSize
+= compstr
->dwCompAttrLen
;
615 if (update
->result_str
)
617 compstr
->dwResultStrLen
= result_len
;
618 compstr
->dwResultStrOffset
= compstr
->dwSize
;
619 dst
= (BYTE
*)compstr
+ compstr
->dwResultStrOffset
;
620 memcpy( dst
, update
->result_str
, compstr
->dwResultStrLen
* sizeof(WCHAR
) );
621 compstr
->dwSize
+= compstr
->dwResultStrLen
* sizeof(WCHAR
);
623 compstr
->dwResultClauseLen
= 2 * sizeof(DWORD
);
624 compstr
->dwResultClauseOffset
= compstr
->dwSize
;
625 dst
= (BYTE
*)compstr
+ compstr
->dwResultClauseOffset
;
626 *((DWORD
*)dst
+ 0) = 0;
627 *((DWORD
*)dst
+ 1) = compstr
->dwResultStrLen
;
628 compstr
->dwSize
+= compstr
->dwResultClauseLen
;