1
- use std:: {
2
- os:: raw:: { c_short, c_void} ,
3
- ptr,
4
- sync:: Arc ,
5
- } ;
1
+ use std:: mem:: transmute;
2
+ use std:: os:: raw:: c_short;
3
+ use std:: ptr;
4
+ use std:: sync:: Arc ;
6
5
7
6
use super :: { ffi, util, XConnection , XError } ;
7
+ use crate :: platform_impl:: platform:: x11:: ime:: { ImeEvent , ImeEventSender } ;
8
+ use std:: ffi:: CStr ;
9
+ use x11_dl:: xlib:: { XIMCallback , XIMPreeditCaretCallbackStruct , XIMPreeditDrawCallbackStruct } ;
8
10
11
+ /// IME creation error.
9
12
#[ derive( Debug ) ]
10
13
pub enum ImeContextCreationError {
14
+ /// Got the error from Xlib.
11
15
XError ( XError ) ,
16
+
17
+ /// Got null pointer from Xlib but without exact reason.
12
18
Null ,
13
19
}
14
20
15
- unsafe fn create_pre_edit_attr < ' a > (
16
- xconn : & ' a Arc < XConnection > ,
17
- ic_spot : & ' a ffi:: XPoint ,
18
- ) -> util:: XSmartPointer < ' a , c_void > {
19
- util:: XSmartPointer :: new (
20
- xconn,
21
- ( xconn. xlib . XVaCreateNestedList ) (
22
- 0 ,
23
- ffi:: XNSpotLocation_0 . as_ptr ( ) as * const _ ,
24
- ic_spot,
25
- ptr:: null_mut :: < ( ) > ( ) ,
26
- ) ,
27
- )
28
- . expect ( "XVaCreateNestedList returned NULL" )
21
+ /// The callback used by XIM preedit functions.
22
+ type XIMProcNonnull = unsafe extern "C" fn ( ffi:: XIM , ffi:: XPointer , ffi:: XPointer ) ;
23
+
24
+ /// Wrapper for creating XIM callbacks.
25
+ #[ inline]
26
+ fn create_xim_callback ( client_data : ffi:: XPointer , callback : XIMProcNonnull ) -> ffi:: XIMCallback {
27
+ XIMCallback {
28
+ client_data,
29
+ callback : Some ( callback) ,
30
+ }
31
+ }
32
+
33
+ /// The server started preedit.
34
+ extern "C" fn preedit_start_callback (
35
+ _xim : ffi:: XIM ,
36
+ client_data : ffi:: XPointer ,
37
+ _call_data : ffi:: XPointer ,
38
+ ) -> i32 {
39
+ let client_data = unsafe { & mut * ( client_data as * mut ImeContextClientData ) } ;
40
+
41
+ client_data. text . clear ( ) ;
42
+ client_data. cursor_pos = 0 ;
43
+ client_data
44
+ . event_sender
45
+ . send ( ( client_data. window , ImeEvent :: Start ) )
46
+ . expect ( "failed to send preedit start event" ) ;
47
+ -1
48
+ }
49
+
50
+ /// Done callback is called when the user finished preedit and the text is about to be inserted into
51
+ /// underlying widget.
52
+ extern "C" fn preedit_done_callback (
53
+ _xim : ffi:: XIM ,
54
+ client_data : ffi:: XPointer ,
55
+ _call_data : ffi:: XPointer ,
56
+ ) {
57
+ let client_data = unsafe { & mut * ( client_data as * mut ImeContextClientData ) } ;
58
+
59
+ client_data
60
+ . event_sender
61
+ . send ( ( client_data. window , ImeEvent :: End ) )
62
+ . expect ( "failed to send preedit end event" ) ;
63
+ }
64
+
65
+ fn calc_byte_position ( text : & Vec < char > , pos : usize ) -> usize {
66
+ let mut byte_pos = 0 ;
67
+ for i in 0 ..pos {
68
+ byte_pos += text[ i] . len_utf8 ( ) ;
69
+ }
70
+ byte_pos
71
+ }
72
+
73
+ /// Preedit text information to be drawn inline by the client.
74
+ extern "C" fn preedit_draw_callback (
75
+ _xim : ffi:: XIM ,
76
+ client_data : ffi:: XPointer ,
77
+ call_data : ffi:: XPointer ,
78
+ ) {
79
+ let client_data = unsafe { & mut * ( client_data as * mut ImeContextClientData ) } ;
80
+ let call_data = unsafe { & mut * ( call_data as * mut XIMPreeditDrawCallbackStruct ) } ;
81
+ client_data. cursor_pos = call_data. caret as usize ;
82
+
83
+ let chg_range =
84
+ call_data. chg_first as usize ..( call_data. chg_first + call_data. chg_length ) as usize ;
85
+ if chg_range. start > client_data. text . len ( ) || chg_range. end > client_data. text . len ( ) {
86
+ warn ! (
87
+ "invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}" ,
88
+ client_data. text. len( ) ,
89
+ call_data. chg_first,
90
+ call_data. chg_length
91
+ ) ;
92
+ return ;
93
+ }
94
+
95
+ // NULL indicate text deletion
96
+ let mut new_chars = if call_data. text . is_null ( ) {
97
+ Vec :: new ( )
98
+ } else {
99
+ let xim_text = unsafe { & mut * ( call_data. text ) } ;
100
+ if xim_text. encoding_is_wchar > 0 {
101
+ return ;
102
+ }
103
+ let new_text = unsafe { CStr :: from_ptr ( xim_text. string . multi_byte ) } ;
104
+
105
+ String :: from ( new_text. to_str ( ) . expect ( "Invalid UTF-8 String from IME" ) )
106
+ . chars ( )
107
+ . collect ( )
108
+ } ;
109
+ let mut old_text_tail = client_data. text . split_off ( chg_range. end ) ;
110
+ client_data. text . truncate ( chg_range. start ) ;
111
+ client_data. text . append ( & mut new_chars) ;
112
+ client_data. text . append ( & mut old_text_tail) ;
113
+ let cursor_byte_pos = calc_byte_position ( & client_data. text , client_data. cursor_pos ) ;
114
+
115
+ client_data
116
+ . event_sender
117
+ . send ( (
118
+ client_data. window ,
119
+ ImeEvent :: Update ( client_data. text . iter ( ) . collect ( ) , cursor_byte_pos) ,
120
+ ) )
121
+ . expect ( "failed to send preedit update event" ) ;
122
+ }
123
+
124
+ /// Handling of cursor movements in preedit text.
125
+ extern "C" fn preedit_caret_callback (
126
+ _xim : ffi:: XIM ,
127
+ client_data : ffi:: XPointer ,
128
+ call_data : ffi:: XPointer ,
129
+ ) {
130
+ let client_data = unsafe { & mut * ( client_data as * mut ImeContextClientData ) } ;
131
+ let call_data = unsafe { & mut * ( call_data as * mut XIMPreeditCaretCallbackStruct ) } ;
132
+ client_data. cursor_pos = call_data. position as usize ;
133
+ let cursor_byte_pos = calc_byte_position ( & client_data. text , client_data. cursor_pos ) ;
134
+
135
+ client_data
136
+ . event_sender
137
+ . send ( (
138
+ client_data. window ,
139
+ ImeEvent :: Update ( client_data. text . iter ( ) . collect ( ) , cursor_byte_pos) ,
140
+ ) )
141
+ . expect ( "failed to send preedit update event" ) ;
142
+ }
143
+
144
+ /// Struct to simplify callback creation and latter passing into Xlib XIM.
145
+ struct PreeditCallbacks {
146
+ start_callback : ffi:: XIMCallback ,
147
+ done_callback : ffi:: XIMCallback ,
148
+ draw_callback : ffi:: XIMCallback ,
149
+ caret_callback : ffi:: XIMCallback ,
150
+ }
151
+
152
+ impl PreeditCallbacks {
153
+ pub fn new ( client_data : ffi:: XPointer ) -> PreeditCallbacks {
154
+ let start_callback = create_xim_callback ( client_data, unsafe {
155
+ transmute ( preedit_start_callback as usize )
156
+ } ) ;
157
+ let done_callback = create_xim_callback ( client_data, preedit_done_callback) ;
158
+ let caret_callback = create_xim_callback ( client_data, preedit_caret_callback) ;
159
+ let draw_callback = create_xim_callback ( client_data, preedit_draw_callback) ;
160
+
161
+ PreeditCallbacks {
162
+ start_callback,
163
+ done_callback,
164
+ caret_callback,
165
+ draw_callback,
166
+ }
167
+ }
29
168
}
30
169
31
- // WARNING: this struct doesn't destroy its XIC resource when dropped.
170
+ struct ImeContextClientData {
171
+ window : ffi:: Window ,
172
+ event_sender : ImeEventSender ,
173
+ text : Vec < char > ,
174
+ cursor_pos : usize ,
175
+ }
176
+
177
+ // XXX: this struct doesn't destroy its XIC resource when dropped.
32
178
// This is intentional, as it doesn't have enough information to know whether or not the context
33
179
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
34
180
// through `ImeInner`.
35
- #[ derive( Debug ) ]
36
181
pub struct ImeContext {
37
- pub ic : ffi:: XIC ,
38
- pub ic_spot : ffi:: XPoint ,
182
+ pub ( super ) ic : ffi:: XIC ,
183
+ pub ( super ) ic_spot : ffi:: XPoint ,
184
+ pub ( super ) is_allowed : bool ,
185
+ // Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
186
+ // there we keep the pointer to automatically deallocate it.
187
+ _client_data : Box < ImeContextClientData > ,
39
188
}
40
189
41
190
impl ImeContext {
@@ -44,66 +193,111 @@ impl ImeContext {
44
193
im : ffi:: XIM ,
45
194
window : ffi:: Window ,
46
195
ic_spot : Option < ffi:: XPoint > ,
196
+ is_allowed : bool ,
197
+ event_sender : ImeEventSender ,
47
198
) -> Result < Self , ImeContextCreationError > {
48
- let ic = if let Some ( ic_spot) = ic_spot {
49
- ImeContext :: create_ic_with_spot ( xconn, im, window, ic_spot)
199
+ let client_data = Box :: into_raw ( Box :: new ( ImeContextClientData {
200
+ window,
201
+ event_sender,
202
+ text : Vec :: new ( ) ,
203
+ cursor_pos : 0 ,
204
+ } ) ) ;
205
+
206
+ let ic = if is_allowed {
207
+ ImeContext :: create_ic ( xconn, im, window, client_data as ffi:: XPointer )
208
+ . ok_or ( ImeContextCreationError :: Null ) ?
50
209
} else {
51
- ImeContext :: create_ic ( xconn, im, window)
210
+ ImeContext :: create_none_ic ( xconn, im, window) . ok_or ( ImeContextCreationError :: Null ) ?
52
211
} ;
53
212
54
- let ic = ic. ok_or ( ImeContextCreationError :: Null ) ?;
55
213
xconn
56
214
. check_errors ( )
57
215
. map_err ( ImeContextCreationError :: XError ) ?;
58
216
59
- Ok ( ImeContext {
217
+ let mut context = ImeContext {
60
218
ic,
61
- ic_spot : ic_spot. unwrap_or ( ffi:: XPoint { x : 0 , y : 0 } ) ,
62
- } )
219
+ ic_spot : ffi:: XPoint { x : 0 , y : 0 } ,
220
+ is_allowed,
221
+ _client_data : Box :: from_raw ( client_data) ,
222
+ } ;
223
+
224
+ // Set the spot location, if it's present.
225
+ if let Some ( ic_spot) = ic_spot {
226
+ context. set_spot ( xconn, ic_spot. x , ic_spot. y )
227
+ }
228
+
229
+ Ok ( context)
63
230
}
64
231
65
- unsafe fn create_ic (
232
+ unsafe fn create_none_ic (
66
233
xconn : & Arc < XConnection > ,
67
234
im : ffi:: XIM ,
68
235
window : ffi:: Window ,
69
236
) -> Option < ffi:: XIC > {
70
237
let ic = ( xconn. xlib . XCreateIC ) (
71
238
im,
72
239
ffi:: XNInputStyle_0 . as_ptr ( ) as * const _ ,
73
- ffi:: XIMPreeditNothing | ffi:: XIMStatusNothing ,
240
+ ffi:: XIMPreeditNone | ffi:: XIMStatusNone ,
74
241
ffi:: XNClientWindow_0 . as_ptr ( ) as * const _ ,
75
242
window,
76
243
ptr:: null_mut :: < ( ) > ( ) ,
77
244
) ;
78
- if ic. is_null ( ) {
79
- None
80
- } else {
81
- Some ( ic)
82
- }
245
+
246
+ ( !ic. is_null ( ) ) . then ( || ic)
83
247
}
84
248
85
- unsafe fn create_ic_with_spot (
249
+ unsafe fn create_ic (
86
250
xconn : & Arc < XConnection > ,
87
251
im : ffi:: XIM ,
88
252
window : ffi:: Window ,
89
- ic_spot : ffi:: XPoint ,
253
+ client_data : ffi:: XPointer ,
90
254
) -> Option < ffi:: XIC > {
91
- let pre_edit_attr = create_pre_edit_attr ( xconn, & ic_spot) ;
92
- let ic = ( xconn. xlib . XCreateIC ) (
93
- im,
94
- ffi:: XNInputStyle_0 . as_ptr ( ) as * const _ ,
95
- ffi:: XIMPreeditNothing | ffi:: XIMStatusNothing ,
96
- ffi:: XNClientWindow_0 . as_ptr ( ) as * const _ ,
97
- window,
98
- ffi:: XNPreeditAttributes_0 . as_ptr ( ) as * const _ ,
99
- pre_edit_attr. ptr ,
100
- ptr:: null_mut :: < ( ) > ( ) ,
101
- ) ;
102
- if ic. is_null ( ) {
103
- None
104
- } else {
105
- Some ( ic)
106
- }
255
+ let preedit_callbacks = PreeditCallbacks :: new ( client_data) ;
256
+ let preedit_attr = util:: XSmartPointer :: new (
257
+ xconn,
258
+ ( xconn. xlib . XVaCreateNestedList ) (
259
+ 0 ,
260
+ ffi:: XNPreeditStartCallback_0 . as_ptr ( ) as * const _ ,
261
+ & ( preedit_callbacks. start_callback ) as * const _ ,
262
+ ffi:: XNPreeditDoneCallback_0 . as_ptr ( ) as * const _ ,
263
+ & ( preedit_callbacks. done_callback ) as * const _ ,
264
+ ffi:: XNPreeditCaretCallback_0 . as_ptr ( ) as * const _ ,
265
+ & ( preedit_callbacks. caret_callback ) as * const _ ,
266
+ ffi:: XNPreeditDrawCallback_0 . as_ptr ( ) as * const _ ,
267
+ & ( preedit_callbacks. draw_callback ) as * const _ ,
268
+ ptr:: null_mut :: < ( ) > ( ) ,
269
+ ) ,
270
+ )
271
+ . expect ( "XVaCreateNestedList returned NULL" ) ;
272
+
273
+ let ic = {
274
+ let ic = ( xconn. xlib . XCreateIC ) (
275
+ im,
276
+ ffi:: XNInputStyle_0 . as_ptr ( ) as * const _ ,
277
+ ffi:: XIMPreeditCallbacks | ffi:: XIMStatusNothing ,
278
+ ffi:: XNClientWindow_0 . as_ptr ( ) as * const _ ,
279
+ window,
280
+ ffi:: XNPreeditAttributes_0 . as_ptr ( ) as * const _ ,
281
+ preedit_attr. ptr ,
282
+ ptr:: null_mut :: < ( ) > ( ) ,
283
+ ) ;
284
+
285
+ // If we've failed to create IC with preedit callbacks fallback to normal one.
286
+ if ic. is_null ( ) {
287
+ ( xconn. xlib . XCreateIC ) (
288
+ im,
289
+ ffi:: XNInputStyle_0 . as_ptr ( ) as * const _ ,
290
+ ffi:: XIMPreeditNothing | ffi:: XIMStatusNothing ,
291
+ ffi:: XNClientWindow_0 . as_ptr ( ) as * const _ ,
292
+ window,
293
+ ptr:: null_mut :: < ( ) > ( ) ,
294
+ )
295
+ } else {
296
+ ic
297
+ }
298
+ } ;
299
+
300
+ ( !ic. is_null ( ) ) . then ( || ic)
107
301
}
108
302
109
303
pub fn focus ( & self , xconn : & Arc < XConnection > ) -> Result < ( ) , XError > {
@@ -120,18 +314,34 @@ impl ImeContext {
120
314
xconn. check_errors ( )
121
315
}
122
316
317
+ // Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
318
+ // are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
319
+ // window and couldn't be changed.
320
+ //
321
+ // For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
123
322
pub fn set_spot ( & mut self , xconn : & Arc < XConnection > , x : c_short , y : c_short ) {
124
- if self . ic_spot . x == x && self . ic_spot . y == y {
323
+ if ! self . is_allowed || self . ic_spot . x == x && self . ic_spot . y == y {
125
324
return ;
126
325
}
326
+
127
327
self . ic_spot = ffi:: XPoint { x, y } ;
128
328
129
329
unsafe {
130
- let pre_edit_attr = create_pre_edit_attr ( xconn, & self . ic_spot ) ;
330
+ let preedit_attr = util:: XSmartPointer :: new (
331
+ xconn,
332
+ ( xconn. xlib . XVaCreateNestedList ) (
333
+ 0 ,
334
+ ffi:: XNSpotLocation_0 . as_ptr ( ) ,
335
+ & self . ic_spot ,
336
+ ptr:: null_mut :: < ( ) > ( ) ,
337
+ ) ,
338
+ )
339
+ . expect ( "XVaCreateNestedList returned NULL" ) ;
340
+
131
341
( xconn. xlib . XSetICValues ) (
132
342
self . ic ,
133
343
ffi:: XNPreeditAttributes_0 . as_ptr ( ) as * const _ ,
134
- pre_edit_attr . ptr ,
344
+ preedit_attr . ptr ,
135
345
ptr:: null_mut :: < ( ) > ( ) ,
136
346
) ;
137
347
}
0 commit comments