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