1 /***************************************************************************
2 * Copyright (C) 2010~2010 by CSSlayer *
3 * wengxt@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include <X11/Xmd.h>
22 #include <X11/Xlib.h>
23 #include <X11/Xutil.h>
24 #include <libintl.h>
25 #include <ctype.h>
26 #include <errno.h>
27
28 #include "fcitx/fcitx.h"
29 #include "fcitx/addon.h"
30 #include "fcitx/frontend.h"
31 #include "fcitx/module.h"
32 #include "fcitx/configfile.h"
33 #include "fcitx/instance.h"
34 #include "fcitx-utils/utils.h"
35 #include "fcitx-utils/log.h"
36 #include "fcitx-config/fcitx-config.h"
37 #include "IMdkit.h"
38 #include "Xi18n.h"
39 #include "IC.h"
40 #include "xim.h"
41 #include "ximhandler.h"
42 #include "ximqueue.h"
43 #include "module/x11/fcitx-x11.h"
44 #include "fcitx-config/xdg.h"
45 #include "fcitx/hook.h"
46
47 static void* XimCreate(FcitxInstance* instance, int frontendid);
48 static boolean XimDestroy(void* arg);
49 static void XimEnableIM(void* arg, FcitxInputContext* ic);
50 static void XimCloseIM(void* arg, FcitxInputContext* ic);
51 static void XimCommitString(void* arg, FcitxInputContext* ic, const char* str);
52 static void XimForwardKey(void* arg, FcitxInputContext* ic, FcitxKeyEventType event, FcitxKeySym sym, unsigned int state);
53 static void XimSetWindowOffset(void* arg, FcitxInputContext* ic, int x, int y);
54 static void XimGetWindowRect(void* arg, FcitxInputContext* ic, int* x, int* y, int* w, int* h);
55 static void XimUpdatePreedit(void* arg, FcitxInputContext* ic);
56 // static pid_t XimGetPid(void* arg, FcitxInputContext* ic);
57 // static pid_t XimFindApplicationPid(FcitxXimFrontend* xim, Window w);
58
59 static Bool XimProtocolHandler(XIMS _ims, IMProtocol * call_data);
60 DECLARE_ADDFUNCTIONS(Xim)
61
62 static XIMStyle OverTheSpot_Styles[] = {
63 XIMPreeditPosition | XIMStatusArea, //OverTheSpot
64 XIMPreeditPosition | XIMStatusNothing, //OverTheSpot
65 XIMPreeditPosition | XIMStatusNone, //OverTheSpot
66 XIMPreeditNothing | XIMStatusNothing, //Root
67 XIMPreeditNothing | XIMStatusNone, //Root
68 /*
69 XIMPreeditArea | XIMStatusArea, //OffTheSpot
70 XIMPreeditArea | XIMStatusNothing, //OffTheSpot
71 XIMPreeditArea | XIMStatusNone, //OffTheSpot */
72 0
73 };
74
75 static XIMStyle OnTheSpot_Styles [] = {
76 XIMPreeditPosition | XIMStatusNothing,
77 XIMPreeditCallbacks | XIMStatusNothing,
78 XIMPreeditNothing | XIMStatusNothing,
79 XIMPreeditPosition | XIMStatusCallbacks,
80 XIMPreeditCallbacks | XIMStatusCallbacks,
81 XIMPreeditNothing | XIMStatusCallbacks,
82 0
83 };
84
85 FCITX_DEFINE_PLUGIN(fcitx_xim, frontend, FcitxFrontend) = {
86 XimCreate,
87 XimDestroy,
88 XimCreateIC,
89 XimCheckIC,
90 XimDestroyIC,
91 XimEnableIM,
92 XimCloseIM,
93 XimCommitString,
94 XimForwardKey,
95 XimSetWindowOffset,
96 XimGetWindowRect,
97 XimUpdatePreedit,
98 NULL,
99 NULL,
100 XimCheckICFromSameApplication,
101 NULL,
102 NULL,
103 NULL
104 };
105
106 FcitxXimFrontend *ximfrontend;
107
108 CONFIG_DESC_DEFINE(GetXimConfigDesc, "fcitx-xim.desc")
109
110 /* Supported Chinese Encodings */
111 static XIMEncoding zhEncodings[] = {
112 "COMPOUND_TEXT",
113 NULL
114 };
115
116 static char strLocale[LOCALES_BUFSIZE + 1] = LOCALES_STRING;
117
118 static void*
XimCreate(FcitxInstance * instance,int frontendid)119 XimCreate(FcitxInstance* instance, int frontendid)
120 {
121 if (ximfrontend != NULL)
122 return NULL;
123 FcitxXimFrontend *xim = fcitx_utils_new(FcitxXimFrontend);
124 if (xim == NULL)
125 return NULL;
126
127 ximfrontend = xim;
128
129 char *imname = NULL;
130 char *p;
131
132 xim->display = FcitxX11GetDisplay(instance);
133 if (xim->display == NULL) {
134 FcitxLog(FATAL, _("X11 not initialized"));
135 free(xim);
136 return NULL;
137 }
138
139 xim->iScreen = DefaultScreen(xim->display);
140 xim->owner = instance;
141 xim->frontendid = frontendid;
142 xim->xim_window = XCreateWindow(xim->display, DefaultRootWindow(xim->display),
143 0, 0, 1, 1, 0, 0, InputOnly,
144 CopyFromParent, 0, NULL);
145 if (!xim->xim_window) {
146 FcitxLog(FATAL, _("Can't Create imWindow"));
147 free(xim);
148 return NULL;
149 }
150
151 if (!imname) {
152 imname = getenv("XMODIFIERS");
153 if (imname) {
154 if (!strncmp(imname, "@im=", strlen("@im="))) {
155 imname += 4;
156 } else {
157 FcitxLog(WARNING, _("XMODIFIERS Error."));
158 imname = DEFAULT_IMNAME;
159 }
160 } else {
161 FcitxLog(WARNING, _("Please set XMODIFIERS."));
162 imname = DEFAULT_IMNAME;
163 }
164 }
165 XimQueueInit(xim);
166
167 if (GetXimConfigDesc() == NULL)
168 xim->bUseOnTheSpotStyle = false;
169 else {
170 FcitxConfigFileDesc* configDesc = GetXimConfigDesc();
171
172 FILE *fp;
173 fp = FcitxXDGGetFileUserWithPrefix("conf", "fcitx-xim.config", "r", NULL);
174 if (!fp) {
175 if (errno == ENOENT) {
176 FILE *fp2 = FcitxXDGGetFileUserWithPrefix("conf",
177 "fcitx-xim.config",
178 "w", NULL);
179 FcitxConfigSaveConfigFileFp(fp2, &xim->gconfig, configDesc);
180 if (fp2) {
181 fclose(fp2);
182 }
183 }
184 }
185
186 FcitxConfigFile *cfile = FcitxConfigParseConfigFileFp(fp, configDesc);
187
188 FcitxXimFrontendConfigBind(xim, cfile, configDesc);
189 FcitxConfigBindSync((FcitxGenericConfig*)xim);
190
191 if (fp) {
192 fclose(fp);
193 }
194 }
195
196 XIMStyles input_styles;
197 if (xim->bUseOnTheSpotStyle) {
198 input_styles.count_styles =
199 sizeof(OnTheSpot_Styles) / sizeof(XIMStyle) - 1;
200 input_styles.supported_styles = OnTheSpot_Styles;
201 } else {
202 input_styles.count_styles =
203 sizeof(OverTheSpot_Styles) / sizeof(XIMStyle) - 1;
204 input_styles.supported_styles = OverTheSpot_Styles;
205 }
206
207 XIMEncodings encodings = {
208 .count_encodings = sizeof(zhEncodings) / sizeof(XIMEncoding) - 1,
209 .supported_encodings = zhEncodings
210 };
211
212 p = getenv("LC_CTYPE");
213 if (!p) {
214 p = getenv("LC_ALL");
215 if (!p)
216 p = getenv("LANG");
217 }
218 if (p) {
219 int p_l = strlen(p);
220 if (strlen(LOCALES_STRING) + p_l + 1 < LOCALES_BUFSIZE) {
221 strLocale[strlen(LOCALES_STRING)] = ',';
222 memcpy(strLocale + strlen(LOCALES_STRING) + 1, p, p_l + 1);
223 }
224 }
225
226 xim->ims = IMOpenIM(xim->display,
227 IMModifiers, "Xi18n",
228 IMServerWindow, xim->xim_window,
229 IMServerName, imname,
230 IMLocale, strLocale,
231 IMServerTransport, "X/",
232 IMInputStyles, &input_styles,
233 IMEncodingList, &encodings,
234 IMProtocolHandler, XimProtocolHandler,
235 IMFilterEventMask, KeyPressMask | KeyReleaseMask,
236 NULL);
237
238 if (xim->ims == (XIMS) NULL) {
239 FcitxLog(ERROR, _("Start XIM error. Another XIM daemon named %s "
240 "is running?"), imname);
241 XimDestroy(xim);
242 FcitxInstanceEnd(instance);
243 return NULL;
244 }
245 FcitxXimAddFunctions(instance);
246 return xim;
247 }
248
XimProtocolHandler(XIMS _ims,IMProtocol * call_data)249 Bool XimProtocolHandler(XIMS _ims, IMProtocol * call_data)
250 {
251 FCITX_UNUSED(_ims);
252 switch (call_data->major_code) {
253 case XIM_OPEN:
254 FcitxLog(DEBUG, "XIM_OPEN:\t\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
255 ((IMForwardEventStruct *) call_data)->connect_id);
256 break;
257 case XIM_CLOSE:
258 FcitxLog(DEBUG, "XIM_CLOSE:\t\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
259 ((IMForwardEventStruct *) call_data)->connect_id);
260 break;
261 case XIM_CREATE_IC:
262 FcitxLog(DEBUG, "XIM_CREATE_IC:\t\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
263 ((IMForwardEventStruct *) call_data)->connect_id);
264 break;
265 case XIM_DESTROY_IC:
266 FcitxLog(DEBUG, "XIM_DESTROY_IC:\t\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
267 ((IMForwardEventStruct *) call_data)->connect_id);
268 break;
269 case XIM_SET_IC_VALUES:
270 FcitxLog(DEBUG, "XIM_SET_IC_VALUES:\t\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
271 ((IMForwardEventStruct *) call_data)->connect_id);
272 break;
273 case XIM_GET_IC_VALUES:
274 FcitxLog(DEBUG, "XIM_GET_IC_VALUES:\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
275 ((IMForwardEventStruct *) call_data)->connect_id);
276 break;
277 case XIM_FORWARD_EVENT:
278 FcitxLog(DEBUG, "XIM_FORWARD_EVENT:\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
279 ((IMForwardEventStruct *) call_data)->connect_id);
280 break;
281 case XIM_SET_IC_FOCUS:
282 FcitxLog(DEBUG, "XIM_SET_IC_FOCUS:\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
283 ((IMForwardEventStruct *) call_data)->connect_id);
284 break;
285 case XIM_UNSET_IC_FOCUS:
286 FcitxLog(DEBUG, "XIM_UNSET_IC_FOCUS:\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
287 ((IMForwardEventStruct *) call_data)->connect_id);
288 break;
289 case XIM_RESET_IC:
290 FcitxLog(DEBUG, "XIM_RESET_IC:\t\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
291 ((IMForwardEventStruct *) call_data)->connect_id);
292 break;
293 case XIM_TRIGGER_NOTIFY:
294 FcitxLog(DEBUG, "XIM_TRIGGER_NOTIFY:\t\ticid=%d\tconnect_id=%d", ((IMForwardEventStruct *) call_data)->icid,
295 ((IMForwardEventStruct *) call_data)->connect_id);
296 break;
297 default:
298 FcitxLog(DEBUG, "XIM_DEFAULT:\t\ticid=%d\tconnect_id=%d\t%d", ((IMForwardEventStruct *) call_data)->icid,
299 ((IMForwardEventStruct *) call_data)->connect_id, call_data->major_code);
300 break;
301 }
302
303 switch (call_data->major_code) {
304 case XIM_OPEN:
305 return XIMOpenHandler(ximfrontend, (IMOpenStruct *) call_data);
306 case XIM_CLOSE:
307 return XIMCloseHandler(ximfrontend, (IMOpenStruct *) call_data);
308 case XIM_CREATE_IC:
309 return XIMCreateICHandler(ximfrontend, (IMChangeICStruct *) call_data);
310 case XIM_DESTROY_IC:
311 return XIMDestroyICHandler(ximfrontend, (IMChangeICStruct *) call_data);
312 case XIM_SET_IC_VALUES:
313 return XIMSetICValuesHandler(ximfrontend, (IMChangeICStruct *) call_data);
314 case XIM_GET_IC_VALUES:
315 return XIMGetICValuesHandler(ximfrontend, (IMChangeICStruct *) call_data);
316 case XIM_FORWARD_EVENT:
317 XIMProcessKey(ximfrontend, (IMForwardEventStruct *) call_data);
318 return True;
319 case XIM_SET_IC_FOCUS:
320 return XIMSetFocusHandler(ximfrontend, (IMChangeFocusStruct *) call_data);
321 case XIM_UNSET_IC_FOCUS:
322 return XIMUnsetFocusHandler(ximfrontend, (IMChangeICStruct *) call_data);
323 case XIM_RESET_IC:
324 return XIMResetICHandler(ximfrontend, (IMResetICStruct *) call_data);
325 case XIM_PREEDIT_START_REPLY:
326 return 0;
327 case XIM_PREEDIT_CARET_REPLY:
328 return 0;
329 case XIM_SYNC_REPLY:
330 return 0;
331 default:
332 return True;
333 }
334 }
335
XimDestroy(void * arg)336 boolean XimDestroy(void* arg)
337 {
338 FcitxXimFrontend* xim = (FcitxXimFrontend*) arg;
339
340 FcitxConfigFree(&xim->gconfig);
341
342 /**
343 * Destroy the window BEFORE(!!!!!) CloseIM!!!
344 * Work around for a bug in libX11. See wengxt's commit log:
345 * f773dd4f7152a4b4b7f406fe01bff466e0de3dc2
346 * [xim, x11] correctly shutdown xim, destroy x11 with error handling
347 **/
348 if (xim->xim_window) {
349 XDestroyWindow(xim->display, xim->xim_window);
350 }
351 if (xim->ims) {
352 IMCloseIM(xim->ims);
353 xim->ims = NULL;
354 }
355 XimQueueDestroy(xim);
356 free(xim);
357 return true;
358 }
359
XimEnableIM(void * arg,FcitxInputContext * ic)360 void XimEnableIM(void* arg, FcitxInputContext* ic)
361 {
362 FcitxXimFrontend* xim = (FcitxXimFrontend*) arg;
363 FcitxXimIC* ximic = (FcitxXimIC*) ic->privateic;
364 IMChangeFocusStruct* call_data = fcitx_utils_new(IMChangeFocusStruct);
365 call_data->connect_id = ximic->connect_id;
366 call_data->icid = ximic->id;
367 XimPendingCall(xim, XCT_PREEDIT_START, (XPointer) call_data);
368 }
369
XimCloseIM(void * arg,FcitxInputContext * ic)370 void XimCloseIM(void* arg, FcitxInputContext* ic)
371 {
372 FcitxXimFrontend* xim = (FcitxXimFrontend*) arg;
373 IMChangeFocusStruct* call_data = fcitx_utils_new(IMChangeFocusStruct);
374 FcitxXimIC* ximic = (FcitxXimIC*) ic->privateic;
375 call_data->connect_id = ximic->connect_id;
376 call_data->icid = ximic->id;
377 XimPendingCall(xim, XCT_PREEDIT_END, (XPointer) call_data);
378 }
379
XimCommitString(void * arg,FcitxInputContext * ic,const char * str)380 void XimCommitString(void* arg, FcitxInputContext* ic, const char* str)
381 {
382 FcitxXimFrontend* xim = (FcitxXimFrontend*) arg;
383 XTextProperty tp;
384 FcitxXimIC* ximic = (FcitxXimIC*) ic->privateic;
385
386 /* avoid Seg fault */
387 if (!ic)
388 return;
389
390
391 Xutf8TextListToTextProperty(xim->display, (char **) &str, 1, XCompoundTextStyle, &tp);
392 IMCommitStruct* cms = fcitx_utils_new(IMCommitStruct);
393
394 cms->major_code = XIM_COMMIT;
395 cms->icid = ximic->id;
396 cms->connect_id = ximic->connect_id;
397 cms->flag = XimLookupChars;
398 cms->commit_string = (char *) tp.value;
399 XimPendingCall(xim, XCT_COMMIT, (XPointer) cms);
400 }
401
XimForwardKey(void * arg,FcitxInputContext * ic,FcitxKeyEventType event,FcitxKeySym sym,unsigned int state)402 void XimForwardKey(void *arg, FcitxInputContext* ic, FcitxKeyEventType event, FcitxKeySym sym, unsigned int state)
403 {
404 FcitxXimFrontend* xim = (FcitxXimFrontend*) arg;
405 Window win;
406 if (!(win = GetXimIC(ic)->focus_win))
407 win = GetXimIC(ic)->client_win;
408 XEvent xEvent;
409 memset(&xEvent, 0, sizeof(xEvent));
410 xEvent.xkey.type = (event == FCITX_PRESS_KEY) ? KeyPress : KeyRelease;
411 xEvent.xkey.serial = xim->currentSerialNumberKey;
412 xEvent.xkey.send_event = False;
413 xEvent.xkey.display = xim->display;
414 xEvent.xkey.window = win;
415 xEvent.xkey.root = DefaultRootWindow(xim->display);
416 xEvent.xkey.subwindow = None;
417 xEvent.xkey.time = CurrentTime;
418 xEvent.xkey.state = state;
419 xEvent.xkey.keycode = XKeysymToKeycode(xim->display, sym);
420 xEvent.xkey.same_screen = False;
421
422 XimForwardKeyInternal(xim, GetXimIC(ic), &xEvent);
423 }
424
XimSetWindowOffset(void * arg,FcitxInputContext * ic,int x,int y)425 void XimSetWindowOffset(void* arg, FcitxInputContext* ic, int x, int y)
426 {
427 FCITX_UNUSED(arg);
428
429 ic->offset_x = x;
430 ic->offset_y = y;
431 }
432
XimGetWindowRect(void * arg,FcitxInputContext * ic,int * x,int * y,int * w,int * h)433 void XimGetWindowRect(void* arg, FcitxInputContext* ic, int* x, int* y, int* w, int* h)
434 {
435 FCITX_UNUSED(arg);
436 *x = ic->offset_x;
437 *y = ic->offset_y;
438 *w = 0;
439 *h = 0;
440 }
441
XimUpdatePreedit(void * arg,FcitxInputContext * ic)442 void XimUpdatePreedit(void* arg, FcitxInputContext* ic)
443 {
444 FcitxXimFrontend* xim = (FcitxXimFrontend*) arg;
445 FcitxInputState* input = FcitxInstanceGetInputState(xim->owner);
446 char* strPreedit = FcitxUIMessagesToCString(FcitxInputStateGetClientPreedit(input));
447 char* str = FcitxInstanceProcessOutputFilter(xim->owner, strPreedit);
448 if (str) {
449 free(strPreedit);
450 strPreedit = str;
451 }
452
453 if (strlen(strPreedit) == 0 && GetXimIC(ic)->bPreeditStarted == true) {
454 XimPreeditCallbackDraw(xim, GetXimIC(ic), strPreedit, 0);
455 XimPreeditCallbackDone(xim, GetXimIC(ic));
456 GetXimIC(ic)->bPreeditStarted = false;
457 }
458
459 if (strlen(strPreedit) != 0 && GetXimIC(ic)->bPreeditStarted == false) {
460 XimPreeditCallbackStart(xim, GetXimIC(ic));
461 GetXimIC(ic)->bPreeditStarted = true;
462 }
463 if (strlen(strPreedit) != 0) {
464 XimPreeditCallbackDraw(xim, GetXimIC(ic), strPreedit, FcitxInputStateGetClientCursorPos(input));
465 }
466
467 free(strPreedit);
468 }
469
470 #if 0
471 pid_t XimGetPid(void* arg, FcitxInputContext* ic)
472 {
473 FcitxXimFrontend* xim = (FcitxXimFrontend*) arg;
474 Window w;
475 FcitxXimIC* ximic = GetXimIC(ic);
476 if (!(w = ximic->focus_win))
477 w = ximic->client_win;
478
479 return XimFindApplicationPid(xim, w);
480 }
481
482 pid_t XimFindApplicationPid(FcitxXimFrontend* xim, Window w) {
483 if (w == DefaultRootWindow(xim->display))
484 return 0;
485
486 Atom actual_type;
487 int actual_format;
488 unsigned long nitems;
489 unsigned long bytes;
490 unsigned char* prop;
491 int status = XGetWindowProperty(
492 xim->display, w, XInternAtom(xim->display, "_NET_WM_PID", True), 0,
493 1024L, False, AnyPropertyType, &actual_type, &actual_format, &nitems,
494 &bytes, (unsigned char**) &prop);
495 if (status != 0) {
496 if (status == BadRequest)
497 return 0;
498 return 0;
499 }
500 if (!prop) {
501 Window parent;
502 Window root;
503 Window* children = NULL;
504 unsigned int sz = 0;
505 status = XQueryTree(xim->display, w, &root, &parent, &children, &sz);
506 if (status != 0) {
507 if (status == BadRequest)
508 return 0;
509 return 0;
510 }
511 if (children)
512 XFree(children);
513 return XimFindApplicationPid(xim, parent);
514 } else {
515 // TODO: is this portable?
516 return prop[1] * 256 + prop[0];
517 }
518 }
519 #endif
520
521 #include "fcitx-xim-addfunctions.h"
522