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