1 /*
2
3 Copyright (c) 2003-2013 uim Project https://github.com/uim/uim
4
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10
11 1. Redistributions of source code must retain the above copyright
12 notice, this list of conditions and the following disclaimer.
13 2. Redistributions in binary form must reproduce the above copyright
14 notice, this list of conditions and the following disclaimer in the
15 documentation and/or other materials provided with the distribution.
16 3. Neither the name of authors nor the names of its contributors
17 may be used to endorse or promote products derived from this software
18 without specific prior written permission.
19
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
21 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 SUCH DAMAGE.
31 */
32
33 // Handle IC (Input Context) part of XIM protocol.
34 // Intermediate key handling between backend IM engine.
35
36 #ifdef HAVE_CONFIG_H
37 # include <config.h>
38 #endif
39
40 #include <cctype>
41 #include <cstdio>
42 #include <cstdlib>
43 #include <cstring>
44
45 #include "xim.h"
46 #include "convdisp.h"
47 #include "ximserver.h"
48 #include "connection.h"
49 #include "util.h"
50
51 #include "uim/uim-scm.h"
52
53 #ifdef HAVE_ALLOCA_H
54 # include <alloca.h>
55 #endif
56
57 char invalid_style_msg[]=
58 "Client requested unsupported input style";
59
60 XimIC *XimIC::current_ic;
61 int XimIC::nrActiveIC;
62
63 struct FSCache {
64 int refc;
65 XFontSet fs;
66 char *name;
67 char *locale;
68 };
69
70 static std::list<FSCache> fs_cache;
71
72 XFontSet
get_font_set(const char * name,const char * locale)73 get_font_set(const char *name, const char *locale)
74 {
75 std::list<FSCache>::iterator it;
76 for (it = fs_cache.begin(); it != fs_cache.end(); ++it) {
77 if (!strcmp(it->name, name) && !strcmp(it->locale, locale)) {
78 it->refc++;
79 return it->fs;
80 }
81 }
82 struct FSCache fc;
83 char **missing, *def;
84 int nr_missing;
85 fc.fs = XCreateFontSet(XimServer::gDpy, name,
86 &missing, &nr_missing, &def);
87 if (missing)
88 XFreeStringList(missing);
89
90 if (fc.fs == NULL) {
91 fprintf(stderr, "Critical: XCreateFontSet failed!\n");
92 return NULL;
93 }
94
95 fc.refc = 1;
96 fc.name = strdup(name);
97 fc.locale = strdup(locale);
98 fs_cache.push_back(fc);
99 return fc.fs;
100 }
101
102 static void
release_font_set(const char * name,const char * locale)103 release_font_set(const char *name, const char *locale)
104 {
105 std::list<FSCache>::iterator it;
106 for (it = fs_cache.begin(); it != fs_cache.end(); ++it) {
107 if (!strcmp(it->name, name) && !strcmp(it->locale, locale)) {
108 it->refc--;
109 if (!it->refc) {
110 XFreeFontSet(XimServer::gDpy, it->fs);
111 free(it->name);
112 free(it->locale);
113 fs_cache.erase(it);
114 }
115 return;
116 }
117 }
118 }
119
pe_stat(InputContext * c)120 pe_stat::pe_stat(InputContext *c)
121 {
122 cont = c;
123 clear();
124 }
125
clear()126 void pe_stat::clear()
127 {
128 ustrings.erase(ustrings.begin(), ustrings.end());
129 caret_pos = 0;
130 }
131
new_segment(int s)132 void pe_stat::new_segment(int s)
133 {
134 pe_ustring p;
135 p.stat = s;
136 ustrings.push_back(p);
137 }
138
push_uchar(uchar c)139 void pe_stat::push_uchar(uchar c)
140 {
141 std::list<pe_ustring>::reverse_iterator i = ustrings.rbegin();
142 (*i).s.push_back(c);
143 }
144
get_char_count()145 int pe_stat::get_char_count()
146 {
147 std::list<pe_ustring>::iterator i;
148 uString::iterator j;
149 int k = 0;
150 for (i = ustrings.begin(); i != ustrings.end(); ++i) {
151 for (j = (*i).s.begin(); j != (*i).s.end(); ++j) {
152 k++;
153 }
154 }
155 return k;
156 }
157
icxatr()158 icxatr::icxatr()
159 {
160 atr_mask = 0;
161 change_mask = 0;
162 font_set_name = NULL;
163 font_set = NULL;
164 m_locale = NULL;
165 foreground_pixel = static_cast<C32>(BlackPixel(XimServer::gDpy,
166 DefaultScreen(XimServer::gDpy)));
167 background_pixel = static_cast<C32>(WhitePixel(XimServer::gDpy,
168 DefaultScreen(XimServer::gDpy)));
169 line_space = 0;
170 #if HAVE_XFT_UTF8_STRING
171 m_use_xft = uim_scm_symbol_value_bool("uim-xim-use-xft-font?");
172 #else
173 m_use_xft = false;
174 #endif
175 }
176
~icxatr()177 icxatr::~icxatr()
178 {
179 if (font_set_name) {
180 if (use_xft() == false)
181 release_font_set(font_set_name, m_locale);
182 free((void *)font_set_name);
183 }
184 free(m_locale);
185 }
186
has_atr(C16 id)187 bool icxatr::has_atr(C16 id)
188 {
189 return atr_mask & (1 << id);
190 }
191
set_atr(C16 id,C8 * val,int o)192 void icxatr::set_atr(C16 id, C8 *val, int o)
193 {
194 switch (id) {
195 case ICA_InputStyle:
196 input_style = readC32(val, o);
197 break;
198 case ICA_ClientWindow:
199 client_window = readC32(val, o);
200 break;
201 case ICA_FocusWindow:
202 focus_window = readC32(val, o);
203 break;
204 case ICA_Foreground:
205 foreground_pixel = readC32(val, o);
206 break;
207 case ICA_Background:
208 background_pixel = readC32(val, o);
209 break;
210 case ICA_SpotLocation:
211 spot_location.x = readC16(val, o);
212 spot_location.y = readC16(&val[2], o);
213 break;
214 case ICA_FontSet:
215 {
216 int len = readC16(val, o);
217 char *new_fsn = (char *)alloca(len + 1);
218 new_fsn[len] = '\0';
219 memcpy(new_fsn, &val[2], len);
220 if (font_set_name && !strcmp(font_set_name, new_fsn))
221 break;
222
223 free(font_set_name);
224 font_set_name = strdup(new_fsn);
225 if (use_xft() == false) {
226 font_set = get_font_set(font_set_name, m_locale);
227 if (!font_set) {
228 free(font_set_name);
229 font_set_name = NULL;
230 return; // Don't set atr mask
231 }
232 }
233 }
234 break;
235 case ICA_Area:
236 {
237 area.x = readC16(&val[0], o);
238 area.y = readC16(&val[2], o);
239 area.width = readC16(&val[4], o);
240 area.height = readC16(&val[6], o);
241 }
242 break;
243 case ICA_LineSpace:
244 line_space = readC16(val, o);
245 break;
246 default:
247 // unknown attribute
248 printf("try to set unknown ic attribute %d.\n", id);
249 return;
250 }
251 atr_mask |= (1 << id);
252 change_mask |= (1 << id);
253 }
254
is_changed(C16 id)255 bool icxatr::is_changed(C16 id)
256 {
257 if (change_mask & (1 << id))
258 return true;
259
260 return false;
261 }
262
unset_change_mask(C16 id)263 void icxatr::unset_change_mask(C16 id)
264 {
265 change_mask &= (~(1 << id));
266 }
267
print()268 void icxatr::print()
269 {
270 if (has_atr(ICA_InputStyle))
271 printf("input-style %ld.\n", input_style);
272 else
273 printf("input-style undefined.\n");
274
275 if (has_atr(ICA_ClientWindow))
276 printf("client-window id %ld.\n", client_window);
277 else
278 printf("client-window id undefined.\n");
279
280 if (has_atr(ICA_FocusWindow))
281 printf("focus-window id %ld.\n", focus_window);
282 else
283 printf("focus-window id undefined.\n");
284
285 if (has_atr(ICA_Foreground))
286 printf("foreground-pixel %d.\n", foreground_pixel);
287 else
288 printf("foreground-pixel undefined.\n");
289
290 if (has_atr(ICA_Background))
291 printf("background-pixel %d.\n", background_pixel);
292 else
293 printf("background-pixel undefined.\n");
294
295 if (has_atr(ICA_SpotLocation))
296 printf("spot location x=%d,y=%d.\n",
297 spot_location.x, spot_location.y);
298 else
299 printf("spot location undefined.\n");
300
301 if (has_atr(ICA_FontSet))
302 printf("font-set-name %s\n", font_set_name);
303 else
304 printf("font-set-name undefined.\n");
305
306 if (has_atr(ICA_Area))
307 printf("area = x=%d y=%d width=%d height=%d.\n",
308 area.x, area.y, area.width, area.height);
309 else
310 printf("area undefined.\n");
311
312 if (has_atr(ICA_LineSpace))
313 printf("line-space %d.\n", line_space);
314 else
315 printf("line-space undefined.\n");
316
317 }
318
getSize(C16 id)319 C16 icxatr::getSize(C16 id)
320 {
321 switch (id) {
322 case ICA_FocusWindow:
323 return 4;
324 case ICA_FilterEvents:
325 return 4;
326 case ICA_InputStyle:
327 return 4;
328 }
329 return 0;
330 }
331
set_locale_name(const char * locale)332 void icxatr::set_locale_name(const char *locale)
333 {
334 free(m_locale);
335 m_locale = strdup(locale);
336 }
337
use_xft()338 bool icxatr::use_xft() {
339 return m_use_xft;
340 }
341
XimIC(Connection * c,C16 imid,C16 icid,const char * engine)342 XimIC::XimIC(Connection *c, C16 imid, C16 icid, const char *engine)
343 {
344 mConn = c;
345 mIMid = imid;
346 mICid = icid;
347 mIsActive = false;
348
349 XimServer *svr = mConn->getXimServer();
350 m_kkContext = svr->createContext(this, engine);
351
352 const char *locale = m_kkContext->get_locale_name();
353 m_xatr.set_locale_name(locale);
354
355 mConvdisp = NULL;
356 m_keyState = new keyState(this);
357 if (g_option_mask & OPT_TRACE)
358 printf("imid=%d, icid=%d ic created.\n", mIMid, mICid);
359 }
360
~XimIC()361 XimIC::~XimIC()
362 {
363 if (g_option_mask & OPT_TRACE)
364 printf("imid=%d, icid=%d ic deleted.\n", mIMid , mICid);
365
366 unsetFocus();
367 if (current_ic == this)
368 current_ic = 0;
369
370 // The sequence is important.
371 delete m_kkContext;
372 if (mConvdisp)
373 delete mConvdisp;
374 delete m_keyState;
375 }
376
isActive()377 bool XimIC::isActive()
378 {
379 return mIsActive;
380 }
381
get_icid()382 C16 XimIC::get_icid()
383 {
384 return mICid;
385 }
386
get_imid()387 C16 XimIC::get_imid()
388 {
389 return mIMid;
390 }
391
setFocus()392 void XimIC::setFocus()
393 {
394 if (!mIsActive)
395 nrActiveIC++;
396
397 current_ic = this;
398 mIsActive = true;
399 m_kkContext->focusIn();
400 }
401
402 // Note that the sequence of XIM_SET_IC_FOCUS and XIM_UNSET_FOCUS
403 // events is not consistent. Be careful about hiding caret state and
404 // candidate window.
unsetFocus()405 void XimIC::unsetFocus()
406 {
407 if (!mIsActive)
408 return;
409
410 mIsActive = false;
411 nrActiveIC--;
412 m_kkContext->focusOut();
413 }
414
OnKeyEvent(keyEventX e)415 void XimIC::OnKeyEvent(keyEventX e)
416 {
417 int s;
418 m_keyState->check_key(&e);
419
420 s = m_kkContext->pushKey(m_keyState);
421 if (s & COMMIT_RAW)
422 send_key_event(&e.ev.xkey);
423
424 if (s & UPDATE_MODE)
425 onSendPacket(); // send XIM_COMMIT
426 }
427
changeContext(const char * engine)428 void XimIC::changeContext(const char *engine)
429 {
430 m_kkContext->changeContext(engine);
431 }
432
send_key_event(XKeyEvent * e)433 void XimIC::send_key_event(XKeyEvent *e)
434 {
435 TxPacket *t;
436 t = createTxPacket(XIM_FORWARD_EVENT, 0);
437 t->pushC16(mIMid);
438 t->pushC16(mICid);
439 t->pushC16(1); // flag, synchronous
440 t->pushC16((C16)((e->serial >> 16) & 0xffff));
441
442 t->pushC8((C8)e->type);
443 t->pushC8((C8)e->keycode);
444 t->pushC16((C16)e->serial & 0xffff);
445 t->pushC32(static_cast<C32>(e->time));
446 t->pushC32(static_cast<C32>(e->root));
447 t->pushC32(static_cast<C32>(e->window));
448 t->pushC32(static_cast<C32>(e->subwindow));
449 t->pushC16((C16)e->x_root);
450 t->pushC16((C16)e->y_root);
451 t->pushC16((C16)e->x);
452 t->pushC16((C16)e->y);
453 t->pushC16((C16)e->state);
454 t->pushC8((C8)e->same_screen);
455 t->pushC8(0);
456 mConn->push_packet(t);
457 }
458
commit_string(const char * str)459 void XimIC::commit_string(const char *str)
460 {
461 uString us;
462 mConn->getXimServer()->strToUstring(&us, str);
463 append_ustring(&mPending, &us);
464 }
465
extra_input(char * s)466 void XimIC::extra_input(char *s)
467 {
468 if (s == NULL)
469 return;
470
471 commit_string(s);
472 onSendPacket();
473 send_sync();
474 force_send_packet();
475 }
476
send_sync()477 void XimIC::send_sync() {
478
479 XimIM *im = get_im_by_id(mIMid);
480 im->send_sync(mICid);
481 }
482
force_send_packet(void)483 void XimIC::force_send_packet(void) {
484 (dynamic_cast<XConnection *>(mConn))->writeProc();
485 }
486
setICAttrs(void * val,int len)487 void XimIC::setICAttrs(void *val, int len)
488 {
489 unsigned char *p = (unsigned char *)val;
490 int byte_order = mConn->byte_order();
491 int i;
492 for (i = 0; i < len;) {
493 C16 atr_id, atr_len;
494 atr_id = readC16(&p[i], byte_order);
495 i += 2;
496
497 atr_len = readC16(&p[i], byte_order);
498 i += 2;
499
500 unsigned char *q;
501 q = (unsigned char *)alloca(atr_len + pad4(atr_len));
502
503 int j;
504 for (j = 0; j < atr_len + pad4(atr_len); j++, i++) {
505 q[j] = p[i];
506 }
507
508 set_ic_attr(atr_id, (C8 *)q, atr_len);
509 }
510 }
511
get_ic_atr(C16 id,TxPacket * t)512 C16 XimIC::get_ic_atr(C16 id, TxPacket *t)
513 {
514 C16 l = m_xatr.getSize(id);
515 if (!t)
516 return l;
517
518 switch (id) {
519 case ICA_FocusWindow:
520 t->pushC32(static_cast<C32>(m_xatr.focus_window));
521 break;
522 case ICA_FilterEvents:
523 if (g_option_mask & OPT_ON_DEMAND_SYNC)
524 t->pushC32(KeyPressMask|KeyReleaseMask);
525 else // Filtering KeyRelease event with full-synchronous method
526 // causes problem with mozilla (1.7.3) gtk2 on navigation
527 // toolbar's auto text completion...
528 t->pushC32(KeyPressMask);
529 break;
530 case ICA_InputStyle:
531 t->pushC32(static_cast<C32>(m_xatr.input_style));
532 break;
533 default:
534 printf("try to get unknown ic attribute %d.\n", id);
535 break;
536 }
537 return l;
538 }
539
lookup_style(unsigned long s)540 int XimIC::lookup_style(unsigned long s)
541 {
542 int i;
543 struct input_style *is = get_im_by_id(mIMid)->getInputStyles();
544 for (i = 0; is[i].x_style; i++) {
545 if (is[i].x_style == (int)s)
546 return is[i].style;
547 }
548 return IS_INVALID;
549 }
550
set_ic_attr(C16 id,C8 * val,int len)551 void XimIC::set_ic_attr(C16 id, C8 *val, int len)
552 {
553 if (id == ICA_PreeditAttribute || id == ICA_StatusAttributes)
554 setICAttrs(val, len); // list of attribute
555 else
556 m_xatr.set_atr(id, val, mConn->byte_order());
557
558 if (mConvdisp)
559 mConvdisp->update_icxatr();
560 else {
561 if (m_xatr.has_atr(ICA_InputStyle)) {
562 mConvdisp = create_convdisp(lookup_style(m_xatr.input_style),
563 m_kkContext, &m_xatr, mConn);
564 m_kkContext->setConvdisp(mConvdisp);
565
566 if (mConvdisp)
567 mConvdisp->update_icxatr();
568 }
569 }
570 }
571
reset_ic()572 void XimIC::reset_ic()
573 {
574 TxPacket *t;
575 t = createTxPacket(XIM_RESET_IC_REPLY, 0);
576 t->pushC16(mIMid);
577 t->pushC16(mICid);
578
579 uString s;
580
581 // m_kkContext->get_preedit_string() returns uncommitted preedit
582 // strings, which will be committed in client applications.
583 s = m_kkContext->get_preedit_string();
584 if (!s.empty()) {
585 char *p;
586 C16 i;
587 int len = 0;
588 p = get_im_by_id(mIMid)->uStringToCtext(&s);
589 if (p) {
590 len = static_cast<int>(strlen(p));
591 t->pushC16((C16)len); // length of committed strings
592 for (i = 0; i < len; i++) {
593 t->pushC8(p[i]); // put string here
594 }
595 len = pad4(len + 2);
596 for (i = 0; i < len; i++) {
597 t->pushC8(0); // padding (len + 2bytes)
598 }
599 free(p);
600 } else {
601 t->pushC16(0);
602 t->pushC16(0);
603 }
604 } else {
605 t->pushC16(0);
606 t->pushC16(0);
607 }
608
609 mConn->push_packet(t);
610
611 // After sending RESET_IC_REPLY, uim-xim needs to clear and reset
612 // the input context.
613 m_kkContext->clear();
614
615 // Also reset key state
616 m_keyState->reset();
617 }
618
get_convdisp()619 Convdisp *XimIC::get_convdisp()
620 {
621 return mConvdisp;
622 }
623
onSendPacket()624 void XimIC::onSendPacket()
625 {
626 if (mPending.empty())
627 return;
628
629 char *p;
630 p = get_im_by_id(mIMid)->uStringToCtext(&mPending);
631
632 if (!p) {
633 erase_ustring(&mPending);
634 return;
635 }
636
637 TxPacket *t;
638 t = createTxPacket(XIM_COMMIT, 0);
639 t->pushC16(mIMid);
640 t->pushC16(mICid);
641
642 t->pushC16(3); // XLookupChars|synchronous
643
644 int i, len;
645 len = static_cast<int>(strlen(p));
646
647 t->pushC16((C16)len);
648 for (i = 0; i < len; i++) {
649 t->pushC8(p[i]);
650 }
651 len = pad4(len);
652 for (i = 0; i < len; i++) {
653 t->pushC8(0);
654 }
655 free(p);
656
657 mConn->push_passive_packet(t);
658
659 erase_ustring(&mPending);
660 }
661
get_current_ic()662 XimIC *XimIC::get_current_ic()
663 {
664 return current_ic;
665 }
666
isAnyActive()667 bool XimIC::isAnyActive()
668 {
669 if (nrActiveIC)
670 return true;
671
672 return false;
673 }
674
get_encoding()675 const char *XimIC::get_encoding()
676 {
677 XimIM *im = get_im_by_id(mIMid);
678 return im->get_encoding();
679 }
680
get_lang_region()681 const char *XimIC::get_lang_region()
682 {
683 XimIM *im = get_im_by_id(mIMid);
684 return im->get_lang_region();
685 }
686
create_ic(Connection * c,RxPacket * p,C16 imid,C16 icid,const char * engine)687 XimIC *create_ic(Connection *c, RxPacket *p, C16 imid, C16 icid, const char *engine)
688 {
689 XimIC *ic;
690 ic = new XimIC(c, imid, icid, engine);
691 p->rewind();
692 p->getC16(); // discard
693 int atr_len = p->getC16();
694 unsigned char *v;
695 v = (unsigned char *)alloca(atr_len);
696 int i;
697 for (i = 0; i < atr_len; i++) {
698 v[i] = p->getC8();
699 }
700 ic->setICAttrs((void *)v, atr_len);
701 if (!ic->get_convdisp()) {
702 delete ic;
703 return 0;
704 }
705 return ic;
706 }
707 /*
708 * Local variables:
709 * c-indent-level: 4
710 * c-basic-offset: 4
711 * End:
712 */
713