1 /***************************************************************************
2 * Copyright (C) 2011~2012 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 "config.h"
22
23 #include <errno.h>
24 #include <iconv.h>
25 #include <unistd.h>
26
27 #include <curl/curl.h>
28 #include <fcntl.h>
29
30 #include <fcitx/fcitx.h>
31 #include <fcitx/module.h>
32 #include <fcitx/hook.h>
33 #include <fcitx-utils/log.h>
34 #include <fcitx/candidate.h>
35 #include <fcitx-config/xdg.h>
36 #include <fcitx/module/pinyin/fcitx-pinyin.h>
37 #include <fcitx/module/freedesktop-notify/fcitx-freedesktop-notify.h>
38
39 #include "cloudpinyin.h"
40 #include "fetch.h"
41 #include "parse.h"
42
43 DEFINE_GET_ADDON("fcitx-sunpinyin", SunPinyin)
44 DEFINE_GET_ADDON("fcitx-libpinyin", LibPinyin)
45 DEFINE_GET_ADDON("fcitx-sogoupinyin", SogouPinyin)
46 DEFINE_GET_AND_INVOKE_FUNC(SunPinyin, GetFullPinyin, 0)
47 DEFINE_GET_AND_INVOKE_FUNC(SunPinyin, AddWord, 1)
48
49 // Maybe not the right name, but doesn't matter....
50 DEFINE_GET_AND_INVOKE_FUNC(LibPinyin, AddWord, 0)
51 DEFINE_GET_AND_INVOKE_FUNC(SogouPinyin, AddWord, 0)
52
53 #define CHECK_VALID_IM (im && \
54 strcmp(im->langCode, "zh_CN") == 0 && \
55 (strcmp(im->uniqueName, "pinyin") == 0 || \
56 strcmp(im->uniqueName, "pinyin-libpinyin") == 0 || \
57 strcmp(im->uniqueName, "shuangpin-libpinyin") == 0 || \
58 strcmp(im->uniqueName, "googlepinyin") == 0 || \
59 strcmp(im->uniqueName, "sunpinyin") == 0 || \
60 strcmp(im->uniqueName, "shuangpin") == 0 || \
61 strcmp(im->uniqueName, "sogou-pinyin") == 0))
62
63 #define CLOUDPINYIN_CHECK_PAGE_NUMBER 3
64
65 typedef struct _CloudCandWord {
66 boolean filled;
67 uint64_t timestamp;
68 } CloudCandWord;
69
70 typedef struct _CloudPinyinEngine {
71 const char* RequestKey;
72 const char* RequestPinyin;
73 void (*ParseKey)(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue);
74 char* (*ParsePinyin)(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue);
75 boolean supportSeparator;
76 } CloudPinyinEngine;
77
78 static void* CloudPinyinCreate(FcitxInstance* instance);
79 static void CloudPinyinSetFD(void* arg);
80 static void CloudPinyinProcessEvent(void* arg);
81 static void CloudPinyinDestroy(void* arg);
82 static void CloudPinyinReloadConfig(void* arg);
83 static void CloudPinyinAddCandidateWord(void* arg);
84 static void CloudPinyinRequestKey(FcitxCloudPinyin* cloudpinyin);
85 static void CloudPinyinAddInputRequest(FcitxCloudPinyin* cloudpinyin, const char* strPinyin);
86 static void CloudPinyinHandleRequest(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue);
87 static size_t CloudPinyinWriteFunction(char *ptr, size_t size, size_t nmemb, void *userdata);
88 static CloudPinyinCache* CloudPinyinCacheLookup(FcitxCloudPinyin* cloudpinyin, const char* pinyin);
89 static CloudPinyinCache* CloudPinyinAddToCache(FcitxCloudPinyin* cloudpinyin, const char* pinyin, char* string);
90 static INPUT_RETURN_VALUE CloudPinyinGetCandWord(void* arg, FcitxCandidateWord* candWord);
91 static void _CloudPinyinAddCandidateWord(FcitxCloudPinyin* cloudpinyin, const char* pinyin);
92 static void CloudPinyinFillCandidateWord(FcitxCloudPinyin* cloudpinyin, const char* pinyin);
93 static boolean CloudPinyinConfigLoad(FcitxCloudPinyinConfig* fs);
94 static void CloudPinyinConfigSave(FcitxCloudPinyinConfig* fs);
95 static char *GetCurrentString(FcitxCloudPinyin* cloudpinyin,
96 char **ascii_part);
97 static void CloudPinyinHookForNewRequest(void* arg);
98 static CURL* CloudPinyinGetFreeCurlHandle(FcitxCloudPinyin* cloudpinyin);
99 static void CloudPinyinReleaseCurlHandle(FcitxCloudPinyin* cloudpinyin,
100 CURL* curl);
101 static INPUT_RETURN_VALUE CloudPinyinToggle(void* arg);
102
103 CloudPinyinEngine engine[] =
104 {
105 #if 0
106 {
107 "http://web.pinyin.sogou.com/web_ime/patch.php",
108 "http://web.pinyin.sogou.com/api/py?key=%s&query=%s",
109 SogouParseKey,
110 SogouParsePinyin
111 },
112 {
113 "http://ime.qq.com/fcgi-bin/getkey",
114 "http://ime.qq.com/fcgi-bin/getword?key=%s&q=%s",
115 QQParseKey,
116 QQParsePinyin
117 },
118 #endif
119 {
120 NULL,
121 "https://www.google.com/inputtools/request?ime=pinyin&text=%s",
122 NULL,
123 GoogleParsePinyin,
124 true
125 },
126 {
127 NULL,
128 "https://olime.baidu.com/py?py=%s&rn=0&pn=1&ol=1",
129 NULL,
130 BaiduParsePinyin,
131 true
132 }
133 };
134
135
136 CONFIG_DESC_DEFINE(GetCloudPinyinConfigDesc, "fcitx-cloudpinyin.desc")
137
138 FCITX_DEFINE_PLUGIN(fcitx_cloudpinyin, module, FcitxModule) = {
139 .Create = CloudPinyinCreate,
140 .Destroy = CloudPinyinDestroy,
141 .SetFD = CloudPinyinSetFD,
142 .ProcessEvent = CloudPinyinProcessEvent,
143 .ReloadConfig = CloudPinyinReloadConfig
144 };
145
146 static uint64_t
CloudGetTimeStamp()147 CloudGetTimeStamp()
148 {
149 struct timeval current_time;
150 gettimeofday(¤t_time, NULL);
151 return (((uint64_t)current_time.tv_sec * 1000)
152 + (current_time.tv_usec / 1000));
153 }
154
155 static void
CloudSetClientPreedit(FcitxCloudPinyin * cloudpinyin,const char * str)156 CloudSetClientPreedit(FcitxCloudPinyin *cloudpinyin, const char *str)
157 {
158 FcitxInputState *input = FcitxInstanceGetInputState(cloudpinyin->owner);
159 FcitxMessages *message = FcitxInputStateGetClientPreedit(input);
160 char *py;
161 char *string = GetCurrentString(cloudpinyin, &py);
162 FcitxMessagesSetMessageCount(message, 0);
163 if (py) {
164 *py = '\0';
165 FcitxMessagesAddMessageAtLast(message, MSG_INPUT, "%s%s", string, str);
166 } else {
167 FcitxMessagesAddMessageAtLast(message, MSG_INPUT, "%s", str);
168 }
169 fcitx_utils_free(string);
170 FcitxInstanceUpdateClientSideUI(
171 cloudpinyin->owner, FcitxInstanceGetCurrentIC(cloudpinyin->owner));
172 }
173
CloudPinyinCreate(FcitxInstance * instance)174 void* CloudPinyinCreate(FcitxInstance* instance)
175 {
176 FcitxCloudPinyin *cloudpinyin = fcitx_utils_new(FcitxCloudPinyin);
177 bindtextdomain("fcitx-cloudpinyin", LOCALEDIR);
178 bind_textdomain_codeset("fcitx-cloudpinyin", "UTF-8");
179 cloudpinyin->owner = instance;
180 int pipe1[2];
181 int pipe2[2];
182
183 if (!CloudPinyinConfigLoad(&cloudpinyin->config))
184 {
185 free(cloudpinyin);
186 return NULL;
187 }
188
189 if (pipe(pipe1) < 0)
190 {
191 free(cloudpinyin);
192 return NULL;
193 }
194
195 if (pipe(pipe2) < 0) {
196 close(pipe1[0]);
197 close(pipe1[1]);
198 free(cloudpinyin);
199 return NULL;
200 }
201
202 cloudpinyin->pipeRecv = pipe1[0];
203 cloudpinyin->pipeNotify = pipe2[1];
204
205 fcntl(pipe1[0], F_SETFL, O_NONBLOCK);
206 fcntl(pipe1[1], F_SETFL, O_NONBLOCK);
207 fcntl(pipe2[0], F_SETFL, O_NONBLOCK);
208 fcntl(pipe2[1], F_SETFL, O_NONBLOCK);
209
210 cloudpinyin->pendingQueue = fcitx_utils_malloc0(sizeof(CurlQueue));
211 cloudpinyin->finishQueue = fcitx_utils_malloc0(sizeof(CurlQueue));
212 pthread_mutex_init(&cloudpinyin->pendingQueueLock, NULL);
213 pthread_mutex_init(&cloudpinyin->finishQueueLock, NULL);
214
215 FcitxFetchThread* fetch = fcitx_utils_malloc0(sizeof(FcitxFetchThread));
216 cloudpinyin->fetch = fetch;
217 fetch->owner = cloudpinyin;
218 fetch->pipeRecv = pipe2[0];
219 fetch->pipeNotify = pipe1[1];
220 fetch->pendingQueueLock = &cloudpinyin->pendingQueueLock;
221 fetch->finishQueueLock = &cloudpinyin->finishQueueLock;
222 fetch->queue = fcitx_utils_malloc0(sizeof(CurlQueue));
223
224 FcitxIMEventHook hook;
225 hook.arg = cloudpinyin;
226 hook.func = CloudPinyinAddCandidateWord;
227
228 FcitxInstanceRegisterUpdateCandidateWordHook(instance, hook);
229
230 hook.arg = cloudpinyin;
231 hook.func = CloudPinyinHookForNewRequest;
232
233 FcitxInstanceRegisterResetInputHook(instance, hook);
234 FcitxInstanceRegisterInputFocusHook(instance, hook);
235 FcitxInstanceRegisterInputUnFocusHook(instance, hook);
236 FcitxInstanceRegisterTriggerOnHook(instance, hook);
237
238 FcitxHotkeyHook hkhook;
239 hkhook.arg = cloudpinyin;
240 hkhook.hotkey = cloudpinyin->config.hkToggle.hotkey;
241 hkhook.hotkeyhandle = CloudPinyinToggle;
242
243 FcitxInstanceRegisterHotkeyFilter(instance, hkhook);
244
245 pthread_create(&cloudpinyin->pid, NULL, FetchThread, fetch);
246
247 CloudPinyinRequestKey(cloudpinyin);
248
249 return cloudpinyin;
250 }
251
CloudPinyinGetFreeCurlHandle(FcitxCloudPinyin * cloudpinyin)252 CURL* CloudPinyinGetFreeCurlHandle(FcitxCloudPinyin* cloudpinyin)
253 {
254 int i = 0;
255 for (i = 0; i < MAX_HANDLE; i ++) {
256 if (!cloudpinyin->freeList[i].used) {
257 cloudpinyin->freeList[i].used = true;
258 if (cloudpinyin->freeList[i].curl == NULL) {
259 cloudpinyin->freeList[i].curl = curl_easy_init();
260 }
261 return cloudpinyin->freeList[i].curl;
262 }
263 }
264 return NULL;
265 }
266
CloudPinyinReleaseCurlHandle(FcitxCloudPinyin * cloudpinyin,CURL * curl)267 void CloudPinyinReleaseCurlHandle(FcitxCloudPinyin* cloudpinyin, CURL* curl)
268 {
269 if (curl == NULL)
270 return;
271 int i = 0;
272 for (i = 0; i < MAX_HANDLE; i ++) {
273 if (cloudpinyin->freeList[i].curl == curl) {
274 cloudpinyin->freeList[i].used = false;
275 return;
276 }
277 }
278 /* if handle is stalled, free it */
279 curl_easy_cleanup(curl);
280 }
281
282
CloudPinyinAddCandidateWord(void * arg)283 void CloudPinyinAddCandidateWord(void* arg)
284 {
285 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
286 FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
287 FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
288
289 if (!cloudpinyin->initialized || !cloudpinyin->config.bEnabled)
290 return;
291
292 /* check whether the current im is pinyin */
293 if (CHECK_VALID_IM)
294 {
295 /* there is something pending input */
296 if (FcitxInputStateGetRawInputBufferSize(input) >= cloudpinyin->config.iMinimumPinyinLength)
297 {
298 char* strToFree = NULL, *inputString;
299 strToFree = GetCurrentString(cloudpinyin, &inputString);
300
301 if (inputString) {
302 CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, inputString);
303 FcitxLog(DEBUG, "%s", inputString);
304 if (cacheEntry == NULL)
305 CloudPinyinAddInputRequest(cloudpinyin, inputString);
306 _CloudPinyinAddCandidateWord(cloudpinyin, inputString);
307 }
308 if (strToFree)
309 free(strToFree);
310 }
311 }
312
313 return;
314 }
315
CloudPinyinRequestKey(FcitxCloudPinyin * cloudpinyin)316 void CloudPinyinRequestKey(FcitxCloudPinyin* cloudpinyin)
317 {
318 if (cloudpinyin->isrequestkey)
319 return;
320
321 cloudpinyin->isrequestkey = true;
322 if (engine[cloudpinyin->config.source].RequestKey == NULL)
323 {
324 cloudpinyin->initialized = true;
325 cloudpinyin->key[0] = '\0';
326 cloudpinyin->isrequestkey = false;
327 return;
328 }
329
330 CURL* curl = CloudPinyinGetFreeCurlHandle(cloudpinyin);
331 if (!curl)
332 return;
333 CurlQueue* queue = fcitx_utils_malloc0(sizeof(CurlQueue)), *head = cloudpinyin->pendingQueue;
334 queue->curl = curl;
335 queue->next = NULL;
336 queue->type = RequestKey;
337 queue->source = cloudpinyin->config.source;
338
339 curl_easy_setopt(curl, CURLOPT_URL, engine[cloudpinyin->config.source].RequestKey);
340 curl_easy_setopt(curl, CURLOPT_WRITEDATA, queue);
341 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CloudPinyinWriteFunction);
342 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10l);
343 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1l);
344
345 /* push into pending queue */
346 pthread_mutex_lock(&cloudpinyin->pendingQueueLock);
347 while (head->next != NULL)
348 head = head->next;
349 head->next = queue;
350 pthread_mutex_unlock(&cloudpinyin->pendingQueueLock);
351
352 char c = 0;
353 write(cloudpinyin->pipeNotify, &c, sizeof(char));
354 }
355
356
357
CloudPinyinSetFD(void * arg)358 void CloudPinyinSetFD(void* arg)
359 {
360 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
361 FcitxInstance* instance = cloudpinyin->owner;
362 int maxfd = cloudpinyin->pipeRecv;
363 FD_SET(maxfd, FcitxInstanceGetReadFDSet(instance));
364 if (maxfd > FcitxInstanceGetMaxFD(instance))
365 FcitxInstanceSetMaxFD(instance, maxfd);
366 }
367
CloudPinyinProcessEvent(void * arg)368 void CloudPinyinProcessEvent(void* arg)
369 {
370 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
371 FcitxInstance* instance = cloudpinyin->owner;
372 if (!FD_ISSET(cloudpinyin->pipeRecv, FcitxInstanceGetReadFDSet(instance)))
373 return;
374
375 char c;
376 while (read(cloudpinyin->pipeRecv, &c, sizeof(char)) > 0);
377 pthread_mutex_lock(&cloudpinyin->finishQueueLock);
378 CurlQueue* queue;
379 queue = cloudpinyin->finishQueue;
380 /* this queue header is empty, so the check condition is "next" not null */
381 while (queue->next != NULL)
382 {
383 /* remove pivot from queue, thus pivot need to be free'd in HandleRequest */
384 CurlQueue* pivot = queue->next;
385 queue->next = queue->next->next;
386 CloudPinyinHandleRequest(cloudpinyin, pivot);
387 }
388 pthread_mutex_unlock(&cloudpinyin->finishQueueLock);
389 }
390
CloudPinyinDestroy(void * arg)391 void CloudPinyinDestroy(void* arg)
392 {
393 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
394 char c = 1;
395 write(cloudpinyin->pipeNotify, &c, sizeof(char));
396 pthread_join(cloudpinyin->pid, NULL);
397 pthread_mutex_destroy(&cloudpinyin->pendingQueueLock);
398 pthread_mutex_destroy(&cloudpinyin->finishQueueLock);
399 while (cloudpinyin->cache)
400 {
401 CloudPinyinCache* head = cloudpinyin->cache;
402 HASH_DEL(cloudpinyin->cache, cloudpinyin->cache);
403 free(head->pinyin);
404 free(head->str);
405 free(head);
406 }
407
408 close(cloudpinyin->pipeRecv);
409 close(cloudpinyin->pipeNotify);
410
411 close(cloudpinyin->fetch->pipeRecv);
412 close(cloudpinyin->fetch->pipeNotify);
413 int i = 0;
414 for (i = 0; i < MAX_HANDLE; i ++) {
415 if (cloudpinyin->freeList[i].curl) {
416 curl_easy_cleanup(cloudpinyin->freeList[i].curl);
417 }
418 }
419
420 curl_multi_cleanup(cloudpinyin->fetch->curlm);
421 #define _FREE_QUEUE(NAME) \
422 while(NAME) { \
423 CurlQueue* queue = NAME; \
424 NAME = NAME->next; \
425 fcitx_utils_free(queue->str); \
426 fcitx_utils_free(queue->pinyin); \
427 free(queue); \
428 }
429 _FREE_QUEUE(cloudpinyin->pendingQueue)
430 _FREE_QUEUE(cloudpinyin->finishQueue)
431 _FREE_QUEUE(cloudpinyin->fetch->queue)
432 FcitxConfigFree(&cloudpinyin->config.config);
433 free(cloudpinyin->fetch);
434 free(cloudpinyin);
435 }
436
CloudPinyinReloadConfig(void * arg)437 void CloudPinyinReloadConfig(void* arg)
438 {
439 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
440 CloudPinyinSource previousSource = cloudpinyin->config.source;
441 CloudPinyinConfigLoad(&cloudpinyin->config);
442 if (previousSource != cloudpinyin->config.source)
443 {
444 cloudpinyin->initialized = false;
445 cloudpinyin->key[0] = '\0';
446 }
447 }
448
CloudPinyinAddInputRequest(FcitxCloudPinyin * cloudpinyin,const char * strPinyin)449 void CloudPinyinAddInputRequest(FcitxCloudPinyin* cloudpinyin, const char* strPinyin)
450 {
451 CURL* curl = CloudPinyinGetFreeCurlHandle(cloudpinyin);
452 if (!curl)
453 return;
454 CurlQueue* queue = fcitx_utils_malloc0(sizeof(CurlQueue)), *head = cloudpinyin->pendingQueue;
455 queue->curl = curl;
456 queue->next = NULL;
457 queue->type = RequestPinyin;
458 queue->pinyin = strdup(strPinyin);
459 queue->source = cloudpinyin->config.source;
460 char* urlstring = curl_escape(strPinyin, strlen(strPinyin));
461 char *url = NULL;
462 if (engine[cloudpinyin->config.source].RequestKey)
463 asprintf(&url, engine[cloudpinyin->config.source].RequestPinyin, cloudpinyin->key, urlstring);
464 else
465 asprintf(&url, engine[cloudpinyin->config.source].RequestPinyin, urlstring);
466 curl_free(urlstring);
467
468 curl_easy_setopt(curl, CURLOPT_URL, url);
469 curl_easy_setopt(curl, CURLOPT_WRITEDATA, queue);
470 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CloudPinyinWriteFunction);
471 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10l);
472 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1l);
473
474 free(url);
475
476 /* push into pending queue */
477 pthread_mutex_lock(&cloudpinyin->pendingQueueLock);
478 while (head->next != NULL)
479 head = head->next;
480 head->next = queue;
481 pthread_mutex_unlock(&cloudpinyin->pendingQueueLock);
482
483 char c = 0;
484 write(cloudpinyin->pipeNotify, &c, sizeof(char));
485 }
486
CloudPinyinHandleRequest(FcitxCloudPinyin * cloudpinyin,CurlQueue * queue)487 void CloudPinyinHandleRequest(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
488 {
489 if (queue->type == RequestKey)
490 {
491 cloudpinyin->isrequestkey = false;
492 if (queue->source != cloudpinyin->config.source)
493 return;
494
495 if (queue->http_code == 200)
496 {
497 if (engine[cloudpinyin->config.source].ParseKey)
498 engine[cloudpinyin->config.source].ParseKey(cloudpinyin, queue);
499 }
500 }
501 else if (queue->type == RequestPinyin)
502 {
503 if (queue->http_code == 200 && cloudpinyin->config.source == queue->source)
504 {
505 char *realstring = engine[cloudpinyin->config.source].ParsePinyin(cloudpinyin, queue);
506 if (realstring)
507 {
508 CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, queue->pinyin);
509 if (cacheEntry == NULL)
510 cacheEntry = CloudPinyinAddToCache(cloudpinyin, queue->pinyin, realstring);
511
512 FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
513
514 char* strToFree = NULL, *inputString;
515 strToFree = GetCurrentString(cloudpinyin, &inputString);
516
517 if (inputString) {
518 FcitxLog(DEBUG, "fill: %s %s", inputString, queue->pinyin);
519 if (strcmp(inputString, queue->pinyin) == 0)
520 {
521 if (CHECK_VALID_IM)
522 {
523 CloudPinyinFillCandidateWord(cloudpinyin, inputString);
524 }
525 }
526 }
527 if (strToFree)
528 free(strToFree);
529 free(realstring);
530 }
531 }
532
533 if (queue->http_code != 200)
534 {
535 cloudpinyin->errorcount ++;
536 if (cloudpinyin->errorcount > MAX_ERROR)
537 {
538 cloudpinyin->initialized = false;
539 cloudpinyin->key[0] = '\0';
540 cloudpinyin->errorcount = 0;
541 }
542 }
543 }
544 CloudPinyinReleaseCurlHandle(cloudpinyin, queue->curl);
545 fcitx_utils_free(queue->str);
546 fcitx_utils_free(queue->pinyin);
547 free(queue);
548 }
549
CloudPinyinWriteFunction(char * ptr,size_t size,size_t nmemb,void * userdata)550 size_t CloudPinyinWriteFunction(char *ptr, size_t size, size_t nmemb, void *userdata)
551 {
552 CurlQueue* queue = (CurlQueue*) userdata;
553
554 size_t realsize = size * nmemb;
555 /*
556 * We know that it isn't possible to overflow during multiplication if
557 * neither operand uses any of the most significant half of the bits in
558 * a size_t.
559 */
560
561 if ((unsigned long long)((nmemb | size) &
562 ((unsigned long long)SIZE_MAX << (sizeof(size_t) << 2))) &&
563 (realsize / size != nmemb))
564 return 0;
565
566 if (SIZE_MAX - queue->size - 1 < realsize)
567 realsize = SIZE_MAX - queue->size - 1;
568
569 if (queue->str != NULL)
570 queue->str = realloc(queue->str, queue->size + realsize + 1);
571 else
572 queue->str = fcitx_utils_malloc0(realsize + 1);
573
574 if (queue->str != NULL) {
575 memcpy(&(queue->str[queue->size]), ptr, realsize);
576 queue->size += realsize;
577 queue->str[queue->size] = '\0';
578 }
579 return realsize;
580 }
581
CloudPinyinCacheLookup(FcitxCloudPinyin * cloudpinyin,const char * pinyin)582 CloudPinyinCache* CloudPinyinCacheLookup(FcitxCloudPinyin* cloudpinyin, const char* pinyin)
583 {
584 CloudPinyinCache* cacheEntry = NULL;
585 HASH_FIND_STR(cloudpinyin->cache, pinyin, cacheEntry);
586 return cacheEntry;
587 }
588
CloudPinyinAddToCache(FcitxCloudPinyin * cloudpinyin,const char * pinyin,char * string)589 CloudPinyinCache* CloudPinyinAddToCache(FcitxCloudPinyin* cloudpinyin, const char* pinyin, char* string)
590 {
591 CloudPinyinCache* cacheEntry = fcitx_utils_malloc0(sizeof(CloudPinyinCache));
592 cacheEntry->pinyin = strdup(pinyin);
593 cacheEntry->str = strdup(string);
594 HASH_ADD_KEYPTR(hh, cloudpinyin->cache, cacheEntry->pinyin, strlen(cacheEntry->pinyin), cacheEntry);
595
596 /* if there is too much cached, remove the first one, though LRU might be a better algorithm */
597 if (HASH_COUNT(cloudpinyin->cache) > MAX_CACHE_ENTRY)
598 {
599 CloudPinyinCache* head = cloudpinyin->cache;
600 HASH_DEL(cloudpinyin->cache, cloudpinyin->cache);
601 free(head->pinyin);
602 free(head->str);
603 free(head);
604 }
605 return cacheEntry;
606 }
607
CloudPinyinToggle(void * arg)608 INPUT_RETURN_VALUE CloudPinyinToggle(void* arg)
609 {
610 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
611 FcitxInstance* instance = cloudpinyin->owner;
612 FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
613
614 if (CHECK_VALID_IM) {
615 cloudpinyin->config.bEnabled = !cloudpinyin->config.bEnabled;
616
617 FcitxFreeDesktopNotifyShowAddonTip(
618 instance, "fcitx-cloudpinyin-toggle",
619 "fcitx",
620 _("Cloud Pinyin"),
621 cloudpinyin->config.bEnabled ? _("Cloud Pinyin is Enabled.") :
622 _("Cloud Pinyin is Disabled."));
623 CloudPinyinConfigSave(&cloudpinyin->config);
624 // TODO: add a notification here
625
626 return IRV_DO_NOTHING;
627 }
628 return IRV_TO_PROCESS;
629 }
630
_CloudPinyinAddCandidateWord(FcitxCloudPinyin * cloudpinyin,const char * pinyin)631 void _CloudPinyinAddCandidateWord(FcitxCloudPinyin* cloudpinyin, const char* pinyin)
632 {
633 CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, pinyin);
634 FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
635 FcitxCandidateWordList* candList = FcitxInputStateGetCandidateList(input);
636
637 int order = (cloudpinyin->config.iCandidateOrder <= 2) ?
638 1 : (cloudpinyin->config.iCandidateOrder - 1);
639
640 if (cacheEntry) {
641 FcitxCandidateWord* cand;
642 /* only check the first three page */
643 int pagesize = FcitxCandidateWordGetPageSize(candList);
644 int size = pagesize * CLOUDPINYIN_CHECK_PAGE_NUMBER;
645 int i;
646 if (cloudpinyin->config.iCandidateOrder <= 1) {
647 order = 0;
648 }
649 for (i = 0;i < size &&
650 (cand = FcitxCandidateWordGetByTotalIndex(candList, i));i++) {
651 if (strcmp(cand->strWord, cacheEntry->str) == 0) {
652 if (i > order && i >= pagesize) {
653 FcitxCandidateWordMoveByWord(candList, cand, order);
654 if (order == 0) {
655 CloudSetClientPreedit(cloudpinyin, cacheEntry->str);
656 }
657 }
658 return;
659 }
660 }
661 if (order == 0) {
662 CloudSetClientPreedit(cloudpinyin, cacheEntry->str);
663 }
664 }
665
666 FcitxCandidateWord candWord;
667 CloudCandWord* cloudCand = fcitx_utils_malloc0(sizeof(CloudCandWord));
668 if (cacheEntry) {
669 cloudCand->filled = true;
670 cloudCand->timestamp = 0;
671 candWord.strWord = strdup(cacheEntry->str);
672 } else {
673 cloudCand->filled = false;
674 cloudCand->timestamp = CloudGetTimeStamp();
675 candWord.strWord = strdup("..");
676 }
677
678 candWord.callback = CloudPinyinGetCandWord;
679 candWord.owner = cloudpinyin;
680 candWord.priv = cloudCand;
681 candWord.wordType = MSG_TIPS;
682 if (cloudpinyin->config.bDontShowSource)
683 candWord.strExtra = NULL;
684 else {
685 candWord.strExtra = strdup(_(" (via cloud)"));
686 candWord.extraType = MSG_TIPS;
687 }
688
689 FcitxCandidateWordInsert(candList, &candWord, order);
690 }
691
692 #define LOADING_TIME_QUICK_THRESHOLD 300
693 #define DUP_PLACE_HOLDER "\xe2\x98\xba"
694
CloudPinyinFillCandidateWord(FcitxCloudPinyin * cloudpinyin,const char * pinyin)695 void CloudPinyinFillCandidateWord(FcitxCloudPinyin* cloudpinyin,
696 const char* pinyin)
697 {
698 CloudPinyinCache* cacheEntry = CloudPinyinCacheLookup(cloudpinyin, pinyin);
699 FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
700 FcitxCandidateWordList* candList = FcitxInputStateGetCandidateList(input);
701 if (cacheEntry) {
702 int cloudidx;
703 FcitxCandidateWord *candWord;
704 for (cloudidx = 0;
705 (candWord = FcitxCandidateWordGetByTotalIndex(candList, cloudidx));
706 cloudidx++) {
707 if (candWord->owner == cloudpinyin)
708 break;
709 }
710
711 if (candWord == NULL)
712 return;
713
714 CloudCandWord* cloudCand = candWord->priv;
715 if (cloudCand->filled)
716 return;
717
718 FcitxCandidateWord *cand;
719 int i;
720 int pagesize = FcitxCandidateWordGetPageSize(candList);
721 int size = pagesize * CLOUDPINYIN_CHECK_PAGE_NUMBER;
722 for (i = 0;i < size &&
723 (cand = FcitxCandidateWordGetByTotalIndex(candList, i));i++) {
724 if (strcmp(cand->strWord, cacheEntry->str) == 0) {
725 uint64_t ts = cloudCand->timestamp;
726 uint64_t curTs = CloudGetTimeStamp();
727 FcitxCandidateWordRemove(candList, candWord);
728 /* if cloud word is not on the first page.. impossible */
729 if (cloudidx < pagesize) {
730 /* if the duplication before cloud word */
731 if (i < cloudidx) {
732 if (curTs - ts
733 > LOADING_TIME_QUICK_THRESHOLD) {
734 FcitxCandidateWordInsertPlaceHolder(candList, cloudidx);
735 FcitxCandidateWord* placeHolder = FcitxCandidateWordGetByTotalIndex(candList, cloudidx);
736 if (placeHolder && placeHolder->strWord == NULL)
737 placeHolder->strWord = strdup(DUP_PLACE_HOLDER);
738 }
739 } else {
740 if (i >= pagesize) {
741 FcitxCandidateWordMove(candList, i - 1, cloudidx);
742 } else {
743 if (curTs - ts > LOADING_TIME_QUICK_THRESHOLD) {
744 FcitxCandidateWordInsertPlaceHolder(candList, cloudidx);
745 FcitxCandidateWord* placeHolder = FcitxCandidateWordGetByTotalIndex(candList, cloudidx);
746 if (placeHolder && placeHolder->strWord == NULL)
747 placeHolder->strWord = strdup(DUP_PLACE_HOLDER);
748 }
749 }
750 }
751 }
752 FcitxUIUpdateInputWindow(cloudpinyin->owner);
753 candWord = NULL;
754 break;
755 }
756 }
757
758 if (candWord) {
759 if (cloudCand->filled == false) {
760 cloudCand->filled = true;
761 free(candWord->strWord);
762 candWord->strWord = strdup(cacheEntry->str);
763 if (cloudpinyin->config.iCandidateOrder <= 1 &&
764 (CloudGetTimeStamp() - cloudCand->timestamp
765 <= LOADING_TIME_QUICK_THRESHOLD)) {
766 FcitxCandidateWordMoveByWord(candList, candWord, 0);
767 CloudSetClientPreedit(cloudpinyin, cacheEntry->str);
768 }
769 FcitxUIUpdateInputWindow(cloudpinyin->owner);
770 }
771 }
772 }
773 }
774
CloudPinyinGetCandWord(void * arg,FcitxCandidateWord * candWord)775 INPUT_RETURN_VALUE CloudPinyinGetCandWord(void* arg, FcitxCandidateWord* candWord)
776 {
777 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
778 CloudCandWord* cloudCand = candWord->priv;
779 FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
780 if (cloudCand->filled)
781 {
782 char *py;
783 char *string = GetCurrentString(cloudpinyin, &py);
784 if (py) {
785 *py = 0;
786
787 snprintf(FcitxInputStateGetOutputString(input),
788 MAX_USER_INPUT, "%s%s", string, candWord->strWord);
789
790 FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
791 if (im) {
792 char *output_string = FcitxInputStateGetOutputString(input);
793 FCITX_DEF_MODULE_ARGS(args, output_string);
794 if (strcmp(im->uniqueName, "sunpinyin") == 0) {
795 FcitxSunPinyinInvokeAddWord(cloudpinyin->owner, args);
796 } else if (strcmp(im->uniqueName, "shuangpin") == 0 ||
797 strcmp(im->uniqueName, "pinyin") == 0) {
798 FcitxPinyinInvokeAddUserPhrase(cloudpinyin->owner, args);
799 } else if (strcmp(im->uniqueName, "pinyin-libpinyin") == 0 ||
800 strcmp(im->uniqueName, "shuangpin-libpinyin") == 0) {
801 FcitxLibPinyinInvokeAddWord(cloudpinyin->owner, args);
802 }
803 else if (strcmp(im->uniqueName, "sogou-pinyin") == 0)
804 {
805 FcitxSogouPinyinInvokeAddWord(cloudpinyin->owner, args);
806 }
807 }
808 }
809 if (string)
810 free(string);
811 return IRV_COMMIT_STRING;
812 } else {
813 return IRV_DO_NOTHING;
814 }
815 }
816
817
818 /**
819 * @brief Load the config file for fcitx-cloudpinyin
820 *
821 * @param Bool is reload or not
822 **/
CloudPinyinConfigLoad(FcitxCloudPinyinConfig * fs)823 boolean CloudPinyinConfigLoad(FcitxCloudPinyinConfig* fs)
824 {
825 FcitxConfigFileDesc *configDesc = GetCloudPinyinConfigDesc();
826 if (configDesc == NULL)
827 return false;
828
829 FILE *fp = FcitxXDGGetFileUserWithPrefix("conf", "fcitx-cloudpinyin.config", "r", NULL);
830
831 if (!fp)
832 {
833 if (errno == ENOENT)
834 CloudPinyinConfigSave(fs);
835 }
836 FcitxConfigFile *cfile = FcitxConfigParseConfigFileFp(fp, configDesc);
837 FcitxCloudPinyinConfigConfigBind(fs, cfile, configDesc);
838 FcitxConfigBindSync(&fs->config);
839
840 if (fp)
841 fclose(fp);
842
843 return true;
844 }
845
846 /**
847 * @brief Save the config
848 *
849 * @return void
850 **/
CloudPinyinConfigSave(FcitxCloudPinyinConfig * fs)851 void CloudPinyinConfigSave(FcitxCloudPinyinConfig* fs)
852 {
853 FcitxConfigFileDesc *configDesc = GetCloudPinyinConfigDesc();
854 FILE *fp = FcitxXDGGetFileUserWithPrefix("conf", "fcitx-cloudpinyin.config", "w", NULL);
855 FcitxConfigSaveConfigFileFp(fp, &fs->config, configDesc);
856 if (fp)
857 fclose(fp);
858 }
859
GetCurrentString(FcitxCloudPinyin * cloudpinyin,char ** ascii_part)860 char *GetCurrentString(FcitxCloudPinyin* cloudpinyin, char **ascii_part)
861 {
862 FcitxIM* im = FcitxInstanceGetCurrentIM(cloudpinyin->owner);
863 if (!im) {
864 *ascii_part = NULL;
865 return NULL;
866 }
867 FcitxInputState* input = FcitxInstanceGetInputState(cloudpinyin->owner);
868 char* string = FcitxUIMessagesToCString(FcitxInputStateGetPreedit(input));
869 char p[MAX_USER_INPUT + 1], *pinyin, *lastpos;
870 pinyin = fcitx_utils_get_ascii_part(string);
871 lastpos = pinyin;
872 boolean endflag;
873 int hzlength = pinyin - string;
874 size_t plength = hzlength;
875 strncpy(p, string, hzlength);
876 p[hzlength] = '\0';
877 // lastpos points to the start of a pinyin
878 // pinyin points to the end of current pinyin
879 // for example
880 // xi'an
881 // | |
882 // l p
883 // and we check the separator by supportSeparator for each engine.
884 // shuangpin-libpinyin returns full pinyin in preedit, so we also treat space as separator.
885 do {
886 endflag = (*pinyin != '\0');
887 if (*pinyin == ' ' || *pinyin == '\'' || *pinyin == '\0') {
888 boolean isSeparator = false;
889
890 // skip all continous separator
891 while (*pinyin == ' ' || *pinyin == '\'') {
892 isSeparator = isSeparator || (*pinyin) == '\'' || (strcmp(im->uniqueName, "shuangpin-libpinyin") == 0 && (*pinyin) == ' ');
893 *pinyin = 0;
894 pinyin++;
895 }
896
897 if (*lastpos != '\0') {
898 char* result = NULL;
899 boolean isshuangpin = false;
900 if (strcmp(im->uniqueName, "sunpinyin") == 0) {
901 FCITX_DEF_MODULE_ARGS(args, lastpos, &isshuangpin);
902 result = FcitxSunPinyinInvokeGetFullPinyin(cloudpinyin->owner, args);
903 } else if (strcmp(im->uniqueName, "shuangpin") == 0) {
904 isshuangpin = true;
905 result = FcitxPinyinSP2QP(cloudpinyin->owner, lastpos);
906 }
907 if (isshuangpin) {
908 if (result) {
909 if (plength + strlen(result) + (engine[cloudpinyin->config.source].supportSeparator ? 1 : 0) < MAX_USER_INPUT) {
910 strcat(p + plength, result);
911 plength += strlen(result);
912 if (engine[cloudpinyin->config.source].supportSeparator) {
913 strcat(p + plength, "'");
914 plength += 1;
915 }
916 free(result);
917 } else {
918 p[hzlength] = '\0';
919 break;
920 }
921 }
922 } else {
923 #define PINYIN_USE_SEPARATOR_CASE (isSeparator && engine[cloudpinyin->config.source].supportSeparator)
924 if (plength + strlen(lastpos) + (PINYIN_USE_SEPARATOR_CASE ? 1 : 0) < MAX_USER_INPUT) {
925 strcat(p + plength, lastpos);
926 plength += strlen(lastpos);
927 if (PINYIN_USE_SEPARATOR_CASE) {
928 strcat(p + plength, "'");
929 plength += 1;
930 }
931 } else {
932 p[hzlength] = '\0';
933 break;
934 }
935 }
936
937 isSeparator = false;
938 }
939 lastpos = pinyin;
940 } else {
941 pinyin ++;
942 }
943 } while(endflag);
944 free(string);
945 /* no pinyin append, return NULL for off it */
946 if (p[hzlength] == '\0') {
947 *ascii_part = NULL;
948 return NULL;
949 } else {
950 if (plength >= 1 && p[plength - 1] == '\'') {
951 p[plength - 1] = '\0';
952 }
953 char *res = strdup(p);
954 *ascii_part = res + hzlength;
955 return res;
956 }
957 }
958
CloudPinyinHookForNewRequest(void * arg)959 void CloudPinyinHookForNewRequest(void* arg)
960 {
961 FcitxCloudPinyin* cloudpinyin = (FcitxCloudPinyin*) arg;
962 if (!cloudpinyin->initialized && !cloudpinyin->isrequestkey) {
963 CloudPinyinRequestKey(cloudpinyin);
964 }
965 }
966
967 // kate: indent-mode cstyle; space-indent on; indent-width 0;
968