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 // XIM Server supporting CJK languages
34 // initialize many modules
35
36 #ifdef HAVE_CONFIG_H
37 # include <config.h>
38 #endif
39
40 #include <clocale>
41 #include <csignal>
42 #include <cstdio>
43 #include <cstdlib>
44 #include <cstring>
45 #include <sys/select.h>
46 #include <time.h>
47
48 #include "xim.h"
49 #include "xdispatch.h"
50 #include "ximserver.h"
51 #include "canddisp.h"
52 #include "connection.h"
53 #include "util.h"
54 #include "helper.h"
55
56 #include "uim/uim-util.h"
57 #include "uim/uim-im-switcher.h"
58 #include "uim/uim-scm.h"
59
60 Display *XimServer::gDpy;
61 std::map<Window, XimServer *> XimServer::gServerMap;
62
63 // Configuration
64 int g_option_mask;
65 int scr_width, scr_height;
66 int host_byte_order;
67
68 #define VERSION_NAME "uim-xim under the way! Version " PACKAGE_VERSION "\n"
69 const char *version_name=VERSION_NAME;
70 const char *usage=
71 "--help , --version :Show usage or version\n"
72 "--list :Show available backend conversion engines\n"
73 "--engine=ENGINE :Use ENGINE as a backend conversion engine at startup\n"
74 "--async :Use on-demand-synchronous method of XIM event flow\n"
75 " (using this option is not safe for Tcl/Tk GUI toolkit)\n"
76 "--trace :trace-connection\n"
77 "--trace-xim :trace-xim-message\n";
78 const char *default_engine;
79
80
81 static Atom atom_locales;
82 static Atom atom_transport;
83 Atom xim_servers;
84
85 struct fd_watch_struct {
86 int mask;
87 void (*fn)(int, int);
88 };
89 static std::map<int, fd_watch_struct> fd_watch_stat;
90 static std::map<unsigned int, WindowIf *> window_watch_stat;
91
92 static char *supported_locales;
93 std::list<UIMInfo> uim_info;
94 static void check_pending_xevent(void);
95
96 #if UIM_XIM_USE_DELAY
97 static void timer_check(void);
98 static void *timer_ptr;
99 static void (*timer_cb)(void *ptr);
100 static time_t timer_time;
101 #endif
102
103 bool
pretrans_register()104 pretrans_register()
105 {
106 xim_servers = XInternAtom(XimServer::gDpy, "XIM_SERVERS", 0);
107 atom_locales = XInternAtom(XimServer::gDpy, "LOCALES", 0);
108 atom_transport = XInternAtom(XimServer::gDpy, "TRANSPORT", 0);
109 XFlush(XimServer::gDpy);
110 scr_width = DisplayWidth(XimServer::gDpy, 0);
111 scr_height = DisplayHeight(XimServer::gDpy, 0);
112 return true;
113 }
114
~WindowIf()115 WindowIf::~WindowIf()
116 {
117 }
118
119 void
resize(Window,int,int)120 WindowIf::resize(Window, int, int)
121 {
122 // do nothing
123 }
124
remove_current_fd_watch(int fd)125 void remove_current_fd_watch(int fd)
126 {
127 std::map<int, fd_watch_struct>::iterator i;
128 i = fd_watch_stat.find(fd);
129 if (i == fd_watch_stat.end())
130 return;
131
132 fd_watch_stat.erase(i);
133 }
134
add_fd_watch(int fd,int mask,void (* fn)(int,int))135 void add_fd_watch(int fd, int mask, void (*fn)(int, int))
136 {
137 remove_current_fd_watch(fd);
138
139 fd_watch_struct s;
140 s.mask = mask;
141 s.fn = fn;
142 std::pair<int, fd_watch_struct> p(fd, s);
143 fd_watch_stat.insert(p);
144 }
145
main_loop()146 static void main_loop()
147 {
148 fd_set rfds, wfds;
149 struct timeval tv;
150
151 while (1) {
152 FD_ZERO(&rfds);
153 FD_ZERO(&wfds);
154 #if UIM_XIM_USE_DELAY
155 tv.tv_sec = 1;
156 #else
157 tv.tv_sec = 2;
158 #endif
159 tv.tv_usec = 0;
160
161 std::map<int, fd_watch_struct>::iterator it;
162 int fd_max = 0;
163 for (it = fd_watch_stat.begin(); it != fd_watch_stat.end(); ++it) {
164 int fd = it->first;
165 if (it->second.mask & READ_OK)
166 FD_SET(fd, &rfds);
167 if (it->second.mask & WRITE_OK)
168 FD_SET(fd, &wfds);
169 if (fd_max < fd)
170 fd_max = fd;
171 }
172 if ((select(fd_max + 1, &rfds, &wfds, NULL, &tv)) == 0) {
173 check_pending_xevent();
174 #if UIM_XIM_USE_DELAY
175 timer_check();
176 #endif
177 continue;
178 }
179
180 it = fd_watch_stat.begin();
181 while (it != fd_watch_stat.end()) {
182 int fd = it->first;
183 if (FD_ISSET(fd, &rfds))
184 it->second.fn(fd, READ_OK);
185 if (FD_ISSET(fd, &wfds))
186 it->second.fn(fd, WRITE_OK);
187 // fd_watch_stat may be modified by above functions at
188 // this point. Since the behavior with incrementing
189 // invalidated iterator is compiler dependent, use safer
190 // way.
191 it = fd_watch_stat.find(fd);
192 if (it == fd_watch_stat.end()) // shouldn't happen
193 break;
194 ++it;
195 }
196 #if UIM_XIM_USE_DELAY
197 timer_check();
198 #endif
199 }
200 }
201
202 void
add_window_watch(Window id,WindowIf * w,int mask)203 add_window_watch(Window id, WindowIf *w, int mask)
204 {
205 std::pair<unsigned int, WindowIf *> p(static_cast<unsigned int>(id), w);
206 window_watch_stat.insert(p);
207
208 // Event mask is the same value defined in X,
209 // but do not depend on.
210 int emask = 0;
211 if (mask & EXPOSE_MASK)
212 emask |= ExposureMask;
213 if (mask & STRUCTURE_NOTIFY_MASK)
214 emask |= StructureNotifyMask;
215
216 XSelectInput(XimServer::gDpy, id, emask);
217 }
218
219 void
remove_window_watch(Window id)220 remove_window_watch(Window id)
221 {
222 std::map<unsigned int, WindowIf *>::iterator i;
223 i = window_watch_stat.find(static_cast<unsigned int>(id));
224 if (i != window_watch_stat.end())
225 window_watch_stat.erase(i);
226 }
227
228 WindowIf *
findWindowIf(Window w)229 findWindowIf(Window w)
230 {
231 std::map<unsigned int, WindowIf *>::iterator i;
232 i = window_watch_stat.find(static_cast<unsigned int>(w));
233 if (i == window_watch_stat.end())
234 return NULL;
235
236 return (*i).second;
237 }
238
239 static int
X_ErrorHandler(Display * d,XErrorEvent * e)240 X_ErrorHandler(Display *d, XErrorEvent *e)
241 {
242 if (g_option_mask & OPT_TRACE) {
243 if (e->error_code) {
244 char buf[64];
245 XGetErrorText(d, e->error_code, buf, 63);
246 printf("X error occurred. %s\n", buf);
247 }
248 }
249
250 return 0;
251 }
252
253 static int
X_IOErrorHandler(Display * d)254 X_IOErrorHandler(Display *d)
255 {
256 fprintf(stderr, "%s: X IO error.\n", DisplayString(d));
257 return 0;
258 }
259
260
261 static void
sendSelectionNotify(XEvent * ev,const char * buf,int len)262 sendSelectionNotify(XEvent *ev, const char *buf, int len)
263 {
264 XEvent e;
265 e.type = SelectionNotify;
266 e.xselection.requestor = ev->xselectionrequest.requestor;
267 e.xselection.selection = ev->xselectionrequest.selection;
268 e.xselection.target = ev->xselectionrequest.target;
269 e.xselection.time = ev->xselectionrequest.time;
270 e.xselection.property = ev->xselectionrequest.property;
271 XChangeProperty(XimServer::gDpy, e.xselection.requestor,
272 e.xselection.property,
273 e.xselection.target,
274 8, PropModeReplace,
275 (unsigned char *)buf, len);
276 XSendEvent(XimServer::gDpy, e.xselection.requestor, 0, 0, &e);
277 XFlush(XimServer::gDpy);
278 }
279
280 void
notifyLocale(XEvent * ev)281 notifyLocale(XEvent *ev)
282 {
283 sendSelectionNotify(ev, supported_locales,
284 static_cast<int>(strlen(supported_locales)) + 1);
285 if (g_option_mask & OPT_TRACE)
286 printf("selection notify request for locale.\n");
287 }
288
289 void
notifyTransport(XEvent * ev)290 notifyTransport(XEvent *ev)
291 {
292 sendSelectionNotify(ev, "@transport=X/", 13 + 1);
293 if (g_option_mask & OPT_TRACE)
294 printf("selection notify request for transport.\n");
295 }
296
297 void
ProcXEvent(XEvent * e)298 ProcXEvent(XEvent *e)
299 {
300 Atom p;
301 WindowIf *i;
302 switch (e->type) {
303 case SelectionRequest:
304 p = e->xselectionrequest.property;
305 if (p == atom_locales)
306 notifyLocale(e);
307 else if (p == atom_transport)
308 notifyTransport(e);
309 else
310 printf("property %s?\n",
311 XGetAtomName(XimServer::gDpy, e->xselection.property));
312 break;
313 case Expose:
314 if (e->xexpose.count == 0) {
315 i = findWindowIf(e->xexpose.window);
316 if (i)
317 i->expose(e->xexpose.window);
318 }
319 break;
320 case ConfigureNotify:
321 i = findWindowIf(e->xconfigure.window);
322 if (i)
323 i->resize(e->xconfigure.window, e->xconfigure.x, e->xconfigure.y);
324 break;
325 case DestroyNotify:
326 i = findWindowIf(e->xdestroywindow.window);
327 if (i)
328 i->destroy(e->xdestroywindow.window);
329 remove_window_watch(e->xdestroywindow.window);
330 break;
331 case ClientMessage:
332 procXClientMessage(&e->xclient);
333 break;
334 case MappingNotify:
335 XRefreshKeyboardMapping((XMappingEvent *)e);
336 init_modifier_keys();
337 break;
338 default:;
339 //printf("unknown type of X event. %d\n", e->type);
340 }
341 }
342
343 static void
check_pending_xevent(void)344 check_pending_xevent(void)
345 {
346 XEvent e;
347 while (XPending(XimServer::gDpy)) {
348 XNextEvent(XimServer::gDpy, &e);
349 ProcXEvent(&e);
350 }
351 }
352
353 static void
xEventRead(int,int)354 xEventRead(int /* fd */, int /* ev */)
355 {
356 check_pending_xevent();
357 }
358
359 #if UIM_XIM_USE_DELAY
360 static void
timer_check(void)361 timer_check(void)
362 {
363 if (timer_time > 0 && time(NULL) >= timer_time) {
364 timer_time = 0;
365 timer_cb(timer_ptr);
366 }
367 }
368
369 void
timer_set(int seconds,void (* timeout_cb)(void * ptr),void * ptr)370 timer_set(int seconds, void (*timeout_cb)(void *ptr), void *ptr)
371 {
372 timer_time = time(NULL) + seconds;
373 timer_cb = timeout_cb;
374 timer_ptr = ptr;
375 }
376
377 void
timer_cancel()378 timer_cancel()
379 {
380 timer_time = 0;
381 }
382 #endif
383
384 static void
error_handler_setup()385 error_handler_setup()
386 {
387 XSetErrorHandler(X_ErrorHandler);
388 XSetIOErrorHandler(X_IOErrorHandler);
389 }
390
391 static int
pretrans_setup()392 pretrans_setup()
393 {
394 int fd = XConnectionNumber(XimServer::gDpy);
395
396 add_fd_watch(fd, READ_OK, xEventRead);
397 return fd;
398 }
399
400 static void
print_version()401 print_version()
402 {
403 printf("%s", version_name);
404 }
405
406 static void
print_usage()407 print_usage()
408 {
409 print_version();
410 printf("%s", usage);
411 exit(0);
412 }
413
414 static void
get_uim_info()415 get_uim_info()
416 {
417 int res;
418
419 res = uim_init();
420 if (res) {
421 printf("Failed to init uim\n");
422 exit(1);
423 }
424 uim_context uc = uim_create_context(NULL, "UTF-8", NULL,
425 NULL, uim_iconv, NULL);
426
427 struct UIMInfo ui;
428
429 int nr = uim_get_nr_im(uc);
430 for (int i = 0; i < nr; i++) {
431 ui.name = strdup(uim_get_im_name(uc, i));
432 ui.lang = strdup(uim_get_im_language(uc, i));
433 ui.desc = strdup(uim_get_im_short_desc(uc, i));
434 uim_info.push_back(ui);
435 }
436 uim_release_context(uc);
437 }
438
439 static void
print_uim_info()440 print_uim_info()
441 {
442 std::list<UIMInfo>::iterator it;
443
444 printf("Supported conversion engines:\n");
445 if (uim_info.empty())
446 printf(" None.\n");
447 else
448 for (it = uim_info.begin(); it != uim_info.end(); ++it)
449 printf(" %s (%s)\n", it->name, it->lang);
450
451 }
452
453 static void
clear_uim_info()454 clear_uim_info()
455 {
456 std::list<UIMInfo>::iterator it;
457 for (it = uim_info.begin(); it != uim_info.end(); ++it) {
458 free(it->name);
459 free(it->lang);
460 free(it->desc);
461 }
462 uim_info.clear();
463 }
464
465 static void
init_supported_locales()466 init_supported_locales()
467 {
468 std::list<char *> locale_list;
469 char *locales;
470 const char *s;
471 int len;
472
473 if (asprintf(&supported_locales, "@locale=") == -1) {
474 free(supported_locales);
475 return;
476 }
477 len = static_cast<int>(strlen(supported_locales));
478
479 // get all locales
480 s = compose_localenames_from_im_lang("*");
481 if (s)
482 locales = strdup(s);
483 else
484 locales = strdup("");
485 // replace ':' with ','
486 char *sep;
487 char *tmp = locales;
488 while ((sep = strchr(tmp, ':')) != NULL) {
489 *sep = ',';
490 tmp = sep;
491 }
492
493 len += static_cast<int>(strlen(locales));
494 supported_locales = (char *)realloc(supported_locales,
495 sizeof(char) * len + 1);
496 if (!supported_locales) {
497 fprintf(stderr, "Error: failed to register supported_locales. Aborting....");
498 exit(1);
499 }
500
501 strcat(supported_locales, locales);
502 free(locales);
503 }
504
505 static void
parse_args(int argc,char ** argv)506 parse_args(int argc, char **argv)
507 {
508 int i;
509 for (i = 1; i < argc; i++) {
510 if (!strncmp(argv[i], "--", 2)) {
511 char *opt;
512 opt = &argv[i][2];
513 if (!strcmp(opt, "version")) {
514 print_version();
515 exit(0);
516 } else if (!strcmp(opt, "help")) {
517 print_usage();
518 } else if (!strcmp(opt, "list")) {
519 get_uim_info();
520 print_uim_info();
521 exit(0);
522 } else if (!strcmp(opt, "trace")) {
523 g_option_mask |= OPT_TRACE;
524 } else if (!strcmp(opt, "trace-xim")) {
525 g_option_mask |= OPT_TRACE_XIM;
526 } else if (!strncmp(opt, "engine=", 7)) {
527 default_engine = strdup(&argv[i][9]);
528 } else if (!strcmp(opt, "async")) {
529 g_option_mask |= OPT_ON_DEMAND_SYNC;
530 }
531 }
532 }
533 }
534
check_default_engine(const char * locale)535 static void check_default_engine(const char *locale)
536 {
537 bool found = false;
538 if (default_engine) {
539 std::list<UIMInfo>::iterator it;
540 for (it = uim_info.begin(); it != uim_info.end(); ++it) {
541 if (!strcmp(it->name, default_engine)) {
542 found = true;
543 break;
544 }
545 }
546 }
547
548 if (found == false)
549 default_engine = uim_get_default_im_name(locale);
550 }
551
552 static void
get_runtime_env()553 get_runtime_env()
554 {
555 int i = 1;
556 char *v = (char *)&i;
557 if (*v == 1)
558 host_byte_order = LSB_FIRST;
559 else
560 host_byte_order = MSB_FIRST;
561 }
562
563 static void
terminate_x_connection()564 terminate_x_connection()
565 {
566 int fd = XConnectionNumber(XimServer::gDpy);
567
568 remove_current_fd_watch(fd);
569 }
570
571 void
reload_uim(int reload_libuim)572 reload_uim(int reload_libuim)
573 {
574 if (reload_libuim) {
575 fprintf(stderr, "\nReloading uim...\n\n");
576
577 terminate_canddisp_connection();
578 helper_disconnect_cb();
579 terminate_x_connection();
580
581 std::map<Window, XimServer *>::iterator it;
582 std::list<InputContext *>::iterator it_c;
583
584 for (it = XimServer::gServerMap.begin(); it != XimServer::gServerMap.end(); ++it) {
585 XimServer *xs = it->second;
586 for (it_c = xs->ic_list.begin(); it_c != xs->ic_list.end(); ++it_c)
587 (*it_c)->clear();
588 }
589 uim_quit();
590 }
591
592 clear_uim_info();
593 get_uim_info();
594 //print_uim_info();
595
596 if (reload_libuim) {
597 std::map<Window, XimServer *>::iterator it;
598 std::list<InputContext *>::iterator it_c;
599
600 for (it = XimServer::gServerMap.begin(); it != XimServer::gServerMap.end(); ++it) {
601 XimServer *xs = it->second;
602 for (it_c = xs->ic_list.begin(); it_c != xs->ic_list.end(); ++it_c) {
603 const char *engine = (*it_c)->get_engine_name();
604 (*it_c)->createUimContext(engine);
605 }
606 }
607
608 // make sure to use appropriate locale for the focused context
609 InputContext *focusedContext = InputContext::focusedContext();
610 if (focusedContext)
611 focusedContext->focusIn();
612
613 pretrans_setup();
614 }
615 }
616
617 int
main(int argc,char ** argv)618 main(int argc, char **argv)
619 {
620 const char *locale;
621
622 printf("uim <-> XIM bridge. Supporting multiple locales.\n");
623
624 get_runtime_env();
625
626 parse_args(argc, argv);
627
628 if (g_option_mask & OPT_ON_DEMAND_SYNC)
629 printf("Using on-demand-synchronous XIM event flow (not safe for Tcl/TK)\n");
630 else
631 printf("Using full-synchronous XIM event flow\n");
632
633 signal(SIGPIPE, SIG_IGN);
634 signal(SIGUSR1, reload_uim);
635
636 check_helper_connection();
637
638 XimServer::gDpy = XOpenDisplay(NULL);
639 if (!XimServer::gDpy) {
640 printf("failed to open display!\n");
641 return 1;
642 }
643 if (!pretrans_register()) {
644 printf("pretrans_register failed\n");
645 return 1;
646 }
647
648 get_uim_info();
649 print_uim_info();
650
651 locale = setlocale(LC_CTYPE, "");
652 if (!locale)
653 locale = setlocale(LC_CTYPE, "C");
654
655 check_default_engine(locale);
656 init_supported_locales();
657 init_modifier_keys();
658
659 std::list<UIMInfo>::iterator it;
660 bool res = false;
661
662 // First, setup conversion engine selected by cmdline option or
663 // "default-im-name" on ~/.uim.
664 for (it = uim_info.begin(); it != uim_info.end(); ++it) {
665 if (strcmp(it->name, default_engine) == 0) {
666 XimServer *xs = new XimServer(it->name, it->lang);
667 res = xs->setupConnection(true);
668 if (res)
669 printf("XMODIFIERS=@im=uim registered, selecting %s (%s) as default conversion engine\n", it->name, it->lang);
670 else
671 delete xs;
672 break;
673 }
674 }
675
676 if (!res) {
677 printf("aborting...\n");
678 return 1;
679 }
680
681 connection_setup();
682 error_handler_setup();
683 if (pretrans_setup() == -1)
684 return 0;
685
686 #if HAVE_XFT_UTF8_STRING
687 if (uim_scm_symbol_value_bool("uim-xim-use-xft-font?"))
688 init_default_xftfont(); // setup Xft fonts for Ov/Rw preedit
689 #endif
690 check_candwin_style();
691 check_candwin_pos_type();
692
693 // Handle pending events to prevent hang just after startup
694 check_pending_xevent();
695
696 main_loop();
697 return 0;
698 }
699
700 /*
701 * Local variables:
702 * c-indent-level: 4
703 * c-basic-offset: 4
704 * End:
705 */
706