1 //
2 // Copyright RIME Developers
3 // Distributed under the BSD License
4 //
5 // 2011-08-09 GONG Chen <chen.sst@gmail.com>
6 //
7 #include <cstring>
8 #include <sstream>
9 #include <boost/format.hpp>
10 #include <rime/common.h>
11 #include <rime/composition.h>
12 #include <rime/config.h>
13 #include <rime/context.h>
14 #include <rime/deployer.h>
15 #include <rime/key_event.h>
16 #include <rime/menu.h>
17 #include <rime/module.h>
18 #include <rime/registry.h>
19 #include <rime/schema.h>
20 #include <rime/service.h>
21 #include <rime/setup.h>
22 #include <rime/signature.h>
23 #include <rime_api.h>
24 #include <rime_proto.capnp.h>
25 
26 using namespace rime;
27 using namespace std::placeholders;
28 
29 // assume member is a non-null pointer in struct *p.
30 #define PROVIDED(p, member) ((p) && RIME_STRUCT_HAS_MEMBER(*(p), (p)->member) && (p)->member)
31 
RimeSetupLogging(const char * app_name)32 RIME_API void RimeSetupLogging(const char* app_name) {
33   SetupLogging(app_name);
34 }
35 
36 #if RIME_BUILD_SHARED_LIBS
37 #define rime_declare_module_dependencies()
38 #else
39 extern void rime_require_module_core();
40 extern void rime_require_module_dict();
41 extern void rime_require_module_gears();
42 extern void rime_require_module_levers();
43 // link to default modules explicitly when building static library.
rime_declare_module_dependencies()44 static void rime_declare_module_dependencies() {
45   rime_require_module_core();
46   rime_require_module_dict();
47   rime_require_module_gears();
48   rime_require_module_levers();
49 }
50 #endif
51 
RimeSetup(RimeTraits * traits)52 RIME_API void RimeSetup(RimeTraits *traits) {
53   rime_declare_module_dependencies();
54 
55   SetupDeployer(traits);
56   if (PROVIDED(traits, app_name)) {
57     if (RIME_STRUCT_HAS_MEMBER(*traits, traits->min_log_level) &&
58         RIME_STRUCT_HAS_MEMBER(*traits, traits->log_dir)) {
59       SetupLogging(traits->app_name, traits->min_log_level, traits->log_dir);
60     } else {
61       SetupLogging(traits->app_name);
62     }
63   }
64 }
65 
RimeSetNotificationHandler(RimeNotificationHandler handler,void * context_object)66 RIME_API void RimeSetNotificationHandler(RimeNotificationHandler handler,
67                                          void* context_object) {
68   if (handler) {
69     Service::instance().SetNotificationHandler(
70         std::bind(handler, context_object, _1, _2, _3));
71   }
72   else {
73     Service::instance().ClearNotificationHandler();
74   }
75 }
76 
RimeInitialize(RimeTraits * traits)77 RIME_API void RimeInitialize(RimeTraits *traits) {
78   SetupDeployer(traits);
79   LoadModules(PROVIDED(traits, modules) ? traits->modules : kDefaultModules);
80   Service::instance().StartService();
81 }
82 
RimeFinalize()83 RIME_API void RimeFinalize() {
84   RimeJoinMaintenanceThread();
85   Service::instance().StopService();
86   Registry::instance().Clear();
87   ModuleManager::instance().UnloadModules();
88 }
89 
RimeStartMaintenance(Bool full_check)90 RIME_API Bool RimeStartMaintenance(Bool full_check) {
91   LoadModules(kDeployerModules);
92   Deployer &deployer(Service::instance().deployer());
93   deployer.RunTask("clean_old_log_files");
94   if (!deployer.RunTask("installation_update")) {
95     return False;
96   }
97   if (!full_check) {
98     TaskInitializer args{
99       vector<string>{
100         deployer.user_data_dir,
101         deployer.shared_data_dir,
102       },
103     };
104     if (!deployer.RunTask("detect_modifications", args)) {
105       return False;
106     }
107     LOG(INFO) << "changes detected; starting maintenance.";
108   }
109   deployer.ScheduleTask("workspace_update");
110   deployer.ScheduleTask("user_dict_upgrade");
111   deployer.ScheduleTask("cleanup_trash");
112   deployer.StartMaintenance();
113   return True;
114 }
115 
RimeStartMaintenanceOnWorkspaceChange()116 RIME_API Bool RimeStartMaintenanceOnWorkspaceChange() {
117   return RimeStartMaintenance(False);
118 }
119 
RimeIsMaintenancing()120 RIME_API Bool RimeIsMaintenancing() {
121   Deployer &deployer(Service::instance().deployer());
122   return Bool(deployer.IsMaintenanceMode());
123 }
124 
RimeJoinMaintenanceThread()125 RIME_API void RimeJoinMaintenanceThread() {
126   Deployer &deployer(Service::instance().deployer());
127   deployer.JoinMaintenanceThread();
128 }
129 
130 // deployment
131 
RimeDeployerInitialize(RimeTraits * traits)132 RIME_API void RimeDeployerInitialize(RimeTraits *traits) {
133   SetupDeployer(traits);
134   LoadModules(PROVIDED(traits, modules) ? traits->modules : kDeployerModules);
135 }
136 
RimePrebuildAllSchemas()137 RIME_API Bool RimePrebuildAllSchemas() {
138   Deployer &deployer(Service::instance().deployer());
139   return Bool(deployer.RunTask("prebuild_all_schemas"));
140 }
141 
RimeDeployWorkspace()142 RIME_API Bool RimeDeployWorkspace() {
143   Deployer &deployer(Service::instance().deployer());
144   return Bool(deployer.RunTask("installation_update") &&
145               deployer.RunTask("workspace_update") &&
146               deployer.RunTask("user_dict_upgrade") &&
147               deployer.RunTask("cleanup_trash"));
148 }
149 
RimeDeploySchema(const char * schema_file)150 RIME_API Bool RimeDeploySchema(const char *schema_file) {
151   Deployer &deployer(Service::instance().deployer());
152   return Bool(deployer.RunTask("schema_update", string(schema_file)));
153 }
154 
RimeDeployConfigFile(const char * file_name,const char * version_key)155 RIME_API Bool RimeDeployConfigFile(const char *file_name,
156                                    const char *version_key) {
157   Deployer& deployer(Service::instance().deployer());
158   TaskInitializer args(make_pair<string, string>(file_name, version_key));
159   return Bool(deployer.RunTask("config_file_update", args));
160 }
161 
RimeSyncUserData()162 RIME_API Bool RimeSyncUserData() {
163   RimeCleanupAllSessions();
164   Deployer& deployer(Service::instance().deployer());
165   deployer.ScheduleTask("installation_update");
166   deployer.ScheduleTask("backup_config_files");
167   deployer.ScheduleTask("user_dict_sync");
168   return Bool(deployer.StartMaintenance());
169 }
170 
171 // session management
172 
RimeCreateSession()173 RIME_API RimeSessionId RimeCreateSession() {
174   return Service::instance().CreateSession();
175 }
176 
RimeFindSession(RimeSessionId session_id)177 RIME_API Bool RimeFindSession(RimeSessionId session_id) {
178   return Bool(session_id && Service::instance().GetSession(session_id));
179 }
180 
RimeDestroySession(RimeSessionId session_id)181 RIME_API Bool RimeDestroySession(RimeSessionId session_id) {
182   return Bool(Service::instance().DestroySession(session_id));
183 }
184 
RimeCleanupStaleSessions()185 RIME_API void RimeCleanupStaleSessions() {
186   Service::instance().CleanupStaleSessions();
187 }
188 
RimeCleanupAllSessions()189 RIME_API void RimeCleanupAllSessions() {
190   Service::instance().CleanupAllSessions();
191 }
192 
193 // input
194 
RimeProcessKey(RimeSessionId session_id,int keycode,int mask)195 RIME_API Bool RimeProcessKey(RimeSessionId session_id, int keycode, int mask) {
196   an<Session> session(Service::instance().GetSession(session_id));
197   if (!session)
198     return False;
199   return Bool(session->ProcessKey(KeyEvent(keycode, mask)));
200 }
201 
RimeCommitComposition(RimeSessionId session_id)202 RIME_API Bool RimeCommitComposition(RimeSessionId session_id) {
203   an<Session> session(Service::instance().GetSession(session_id));
204   if (!session)
205     return False;
206   return Bool(session->CommitComposition());
207 }
208 
RimeClearComposition(RimeSessionId session_id)209 RIME_API void RimeClearComposition(RimeSessionId session_id) {
210   an<Session> session(Service::instance().GetSession(session_id));
211   if (!session)
212     return;
213   session->ClearComposition();
214 }
215 
216 // output
217 
rime_candidate_copy(RimeCandidate * dest,const an<Candidate> & src)218 static void rime_candidate_copy(RimeCandidate* dest, const an<Candidate>& src) {
219   dest->text = new char[src->text().length() + 1];
220   std::strcpy(dest->text, src->text().c_str());
221   string comment(src->comment());
222   if (!comment.empty()) {
223     dest->comment = new char[comment.length() + 1];
224     std::strcpy(dest->comment, comment.c_str());
225   }
226   else {
227     dest->comment = nullptr;
228   }
229   dest->reserved = nullptr;
230 }
231 
RimeGetContext(RimeSessionId session_id,RimeContext * context)232 RIME_API Bool RimeGetContext(RimeSessionId session_id, RimeContext* context) {
233   if (!context || context->data_size <= 0)
234     return False;
235   RIME_STRUCT_CLEAR(*context);
236   an<Session> session(Service::instance().GetSession(session_id));
237   if (!session)
238     return False;
239   Context *ctx = session->context();
240   if (!ctx)
241     return False;
242   if (ctx->IsComposing()) {
243     Preedit preedit = ctx->GetPreedit();
244     context->composition.length = preedit.text.length();
245     context->composition.preedit = new char[preedit.text.length() + 1];
246     std::strcpy(context->composition.preedit, preedit.text.c_str());
247     context->composition.cursor_pos = preedit.caret_pos;
248     context->composition.sel_start = preedit.sel_start;
249     context->composition.sel_end = preedit.sel_end;
250     if (RIME_STRUCT_HAS_MEMBER(*context, context->commit_text_preview)) {
251       string commit_text(ctx->GetCommitText());
252       if (!commit_text.empty()) {
253         context->commit_text_preview = new char[commit_text.length() + 1];
254         std::strcpy(context->commit_text_preview, commit_text.c_str());
255       }
256     }
257   }
258   if (ctx->HasMenu()) {
259     Segment &seg(ctx->composition().back());
260     int page_size = 5;
261     Schema *schema = session->schema();
262     if (schema)
263       page_size = schema->page_size();
264     int selected_index = seg.selected_index;
265     int page_no = selected_index / page_size;
266     the<Page> page(seg.menu->CreatePage(page_size, page_no));
267     if (page) {
268       context->menu.page_size = page_size;
269       context->menu.page_no = page_no;
270       context->menu.is_last_page = Bool(page->is_last_page);
271       context->menu.highlighted_candidate_index = selected_index % page_size;
272       int i = 0;
273       context->menu.num_candidates = page->candidates.size();
274       context->menu.candidates = new RimeCandidate[page->candidates.size()];
275       for (const an<Candidate> &cand : page->candidates) {
276         RimeCandidate* dest = &context->menu.candidates[i++];
277         rime_candidate_copy(dest, cand);
278       }
279       if (schema) {
280         const string& select_keys(schema->select_keys());
281         if (!select_keys.empty()) {
282           context->menu.select_keys = new char[select_keys.length() + 1];
283           std::strcpy(context->menu.select_keys, select_keys.c_str());
284         }
285         Config* config = schema->config();
286         an<ConfigList>  select_labels = config->GetList("menu/alternative_select_labels");
287         if (select_labels && (size_t)page_size <= select_labels->size()) {
288           context->select_labels = new char*[page_size];
289           for (size_t i = 0; i < (size_t)page_size; ++i) {
290             an<ConfigValue> value = select_labels->GetValueAt(i);
291             string label = value->str();
292             context->select_labels[i] = new char[label.length() + 1];
293             std::strcpy(context->select_labels[i], label.c_str());
294           }
295         }
296       }
297     }
298   }
299   return True;
300 }
301 
RimeFreeContext(RimeContext * context)302 RIME_API Bool RimeFreeContext(RimeContext* context) {
303   if (!context || context->data_size <= 0)
304     return False;
305   delete[] context->composition.preedit;
306   for (int i = 0; i < context->menu.num_candidates; ++i) {
307     delete[] context->menu.candidates[i].text;
308     delete[] context->menu.candidates[i].comment;
309   }
310   delete[] context->menu.candidates;
311   delete[] context->menu.select_keys;
312   if (RIME_STRUCT_HAS_MEMBER(*context, context->select_labels) && context->select_labels) {
313     for (int i = 0; i < context->menu.page_size; ++i) {
314       delete[] context->select_labels[i];
315     }
316     delete[] context->select_labels;
317   }
318   if (RIME_STRUCT_HAS_MEMBER(*context, context->commit_text_preview)) {
319     delete[] context->commit_text_preview;
320   }
321   RIME_STRUCT_CLEAR(*context);
322   return True;
323 }
324 
RimeContextProto(RimeSessionId session_id,RIME_PROTO_BUILDER * context_builder)325 void RimeContextProto(RimeSessionId session_id, RIME_PROTO_BUILDER* context_builder) {
326   an<Session> session = Service::instance().GetSession(session_id);
327   if (!session)
328     return;
329   Context *ctx = session->context();
330   if (!ctx)
331     return;
332   auto* context = (rime::proto::Context::Builder*)context_builder;
333   context->setInput(ctx->input());
334   context->setCaretPos(ctx->caret_pos());
335   if (ctx->IsComposing()) {
336     auto composition = context->getComposition();
337     Preedit preedit = ctx->GetPreedit();
338     composition.setLength(preedit.text.length());
339     composition.setPreedit(preedit.text);
340     composition.setCursorPos(preedit.caret_pos);
341     composition.setSelStart(preedit.sel_start);
342     composition.setSelEnd(preedit.sel_end);
343     string commit_text = ctx->GetCommitText();
344     if (!commit_text.empty()) {
345       composition.setCommitTextPreview(commit_text);
346     }
347   }
348   if (ctx->HasMenu()) {
349     auto menu = context->getMenu();
350     Segment &seg = ctx->composition().back();
351     Schema *schema = session->schema();
352     int page_size = schema ? schema->page_size() : 5;
353     int selected_index = seg.selected_index;
354     int page_number = selected_index / page_size;
355     the<Page> page(seg.menu->CreatePage(page_size, page_number));
356     if (page) {
357       menu.setPageSize(page_size);
358       menu.setPageNumber(page_number);
359       menu.setIsLastPage(page->is_last_page);
360       menu.setHighlightedCandidateIndex(selected_index % page_size);
361       vector<string> labels;
362       if (schema) {
363         const string& select_keys = schema->select_keys();
364         if (!select_keys.empty()) {
365           menu.setSelectKeys(select_keys);
366         }
367         Config* config = schema->config();
368         auto src_labels = config->GetList("menu/alternative_select_labels");
369         if (src_labels && (size_t)page_size <= src_labels->size()) {
370           auto dest_labels = menu.initSelectLabels(page_size);
371           for (size_t i = 0; i < (size_t)page_size; ++i) {
372             if (an<ConfigValue> value = src_labels->GetValueAt(i)) {
373               dest_labels.set(i, value->str());
374               labels.push_back(value->str());
375             }
376           }
377         } else if (!select_keys.empty()) {
378           for (const char key : select_keys) {
379             labels.push_back(string(1, key));
380             if (labels.size() >= page_size)
381               break;
382           }
383         }
384       }
385       int num_candidates = page->candidates.size();
386       auto dest_candidates = menu.initCandidates(num_candidates);
387       auto dest = dest_candidates.begin();
388       int index = 0;
389       for (const an<Candidate> &src : page->candidates) {
390         dest->setText(src->text());
391         string comment = src->comment();
392         if (!comment.empty()) {
393           dest->setComment(comment);
394         }
395         string label = index < labels.size()
396                        ? labels[index]
397                        : std::to_string(index + 1);
398         dest->setLabel(label);
399         ++dest;
400       }
401     }
402   }
403 }
404 
RimeGetCommit(RimeSessionId session_id,RimeCommit * commit)405 RIME_API Bool RimeGetCommit(RimeSessionId session_id, RimeCommit* commit) {
406   if (!commit)
407     return False;
408   RIME_STRUCT_CLEAR(*commit);
409   an<Session> session(Service::instance().GetSession(session_id));
410   if (!session)
411     return False;
412   const string& commit_text(session->commit_text());
413   if (!commit_text.empty()) {
414     commit->text = new char[commit_text.length() + 1];
415     std::strcpy(commit->text, commit_text.c_str());
416     session->ResetCommitText();
417     return True;
418   }
419   return False;
420 }
421 
RimeFreeCommit(RimeCommit * commit)422 RIME_API Bool RimeFreeCommit(RimeCommit* commit) {
423   if (!commit)
424     return False;
425   delete[] commit->text;
426   RIME_STRUCT_CLEAR(*commit);
427   return True;
428 }
429 
RimeCommitProto(RimeSessionId session_id,RIME_PROTO_BUILDER * commit_builder)430 void RimeCommitProto(RimeSessionId session_id, RIME_PROTO_BUILDER* commit_builder) {
431   an<Session> session(Service::instance().GetSession(session_id));
432   if (!session)
433     return;
434   const string& commit_text(session->commit_text());
435   if (!commit_text.empty()) {
436     auto* commit = (rime::proto::Commit::Builder*)commit_builder;
437     commit->setText(commit_text);
438     session->ResetCommitText();
439   }
440 }
441 
RimeGetStatus(RimeSessionId session_id,RimeStatus * status)442 RIME_API Bool RimeGetStatus(RimeSessionId session_id, RimeStatus* status) {
443   if (!status || status->data_size <= 0)
444     return False;
445   RIME_STRUCT_CLEAR(*status);
446   an<Session> session(Service::instance().GetSession(session_id));
447   if (!session)
448     return False;
449   Schema *schema = session->schema();
450   Context *ctx = session->context();
451   if (!schema || !ctx)
452     return False;
453   status->schema_id = new char[schema->schema_id().length() + 1];
454   std::strcpy(status->schema_id, schema->schema_id().c_str());
455   status->schema_name = new char[schema->schema_name().length() + 1];
456   std::strcpy(status->schema_name, schema->schema_name().c_str());
457   status->is_disabled = Service::instance().disabled();
458   status->is_composing = Bool(ctx->IsComposing());
459   status->is_ascii_mode = Bool(ctx->get_option("ascii_mode"));
460   status->is_full_shape = Bool(ctx->get_option("full_shape"));
461   status->is_simplified = Bool(ctx->get_option("simplification"));
462   status->is_traditional = Bool(ctx->get_option("traditional"));
463   status->is_ascii_punct = Bool(ctx->get_option("ascii_punct"));
464   return True;
465 }
466 
RimeFreeStatus(RimeStatus * status)467 RIME_API Bool RimeFreeStatus(RimeStatus* status) {
468   if (!status || status->data_size <= 0)
469     return False;
470   delete[] status->schema_id;
471   delete[] status->schema_name;
472   RIME_STRUCT_CLEAR(*status);
473   return True;
474 }
475 
RimeStatusProto(RimeSessionId session_id,RIME_PROTO_BUILDER * status_builder)476 void RimeStatusProto(RimeSessionId session_id, RIME_PROTO_BUILDER* status_builder) {
477   an<Session> session(Service::instance().GetSession(session_id));
478   if (!session)
479     return;
480   Schema *schema = session->schema();
481   Context *ctx = session->context();
482   if (!schema || !ctx)
483     return;
484   auto* status = (rime::proto::Status::Builder*)status_builder;
485   status->setSchemaId(schema->schema_id());
486   status->setSchemaName(schema->schema_name());
487   status->setIsDisabled(Service::instance().disabled());
488   status->setIsComposing(ctx->IsComposing());
489   status->setIsAsciiMode(ctx->get_option("ascii_mode"));
490   status->setIsFullShape(ctx->get_option("full_shape"));
491   status->setIsSimplified(ctx->get_option("simplification"));
492   status->setIsTraditional(ctx->get_option("traditional"));
493   status->setIsAsciiPunct(ctx->get_option("ascii_punct"));
494 }
495 
496 // Accessing candidate list
497 
RimeCandidateListFromIndex(RimeSessionId session_id,RimeCandidateListIterator * iterator,int index)498 RIME_API Bool RimeCandidateListFromIndex(RimeSessionId session_id,
499                                          RimeCandidateListIterator* iterator,
500                                          int index) {
501   if (!iterator)
502     return False;
503   an<Session> session(Service::instance().GetSession(session_id));
504   if (!session)
505     return False;
506   Context *ctx = session->context();
507   if (!ctx || !ctx->HasMenu())
508     return False;
509   memset(iterator, 0, sizeof(RimeCandidateListIterator));
510   iterator->ptr = ctx->composition().back().menu.get();
511   iterator->index = index - 1;
512   return True;
513 }
514 
RimeCandidateListBegin(RimeSessionId session_id,RimeCandidateListIterator * iterator)515 RIME_API Bool RimeCandidateListBegin(RimeSessionId session_id,
516                                      RimeCandidateListIterator* iterator) {
517   return RimeCandidateListFromIndex(session_id, iterator, 0);
518 }
519 
RimeCandidateListNext(RimeCandidateListIterator * iterator)520 RIME_API Bool RimeCandidateListNext(RimeCandidateListIterator* iterator) {
521   if (!iterator)
522     return False;
523   Menu *menu = reinterpret_cast<Menu*>(iterator->ptr);
524   if (!menu)
525     return False;
526   ++iterator->index;
527   if (auto cand = menu->GetCandidateAt((size_t)iterator->index)) {
528     delete[] iterator->candidate.text;
529     delete[] iterator->candidate.comment;
530     rime_candidate_copy(&iterator->candidate, cand);
531     return True;
532   }
533   return False;
534 }
535 
RimeCandidateListEnd(RimeCandidateListIterator * iterator)536 RIME_API void RimeCandidateListEnd(RimeCandidateListIterator* iterator) {
537   if (!iterator)
538     return;
539   delete[] iterator->candidate.text;
540   delete[] iterator->candidate.comment;
541   memset(iterator, 0, sizeof(RimeCandidateListIterator));
542 }
543 
544 // runtime options
545 
RimeSetOption(RimeSessionId session_id,const char * option,Bool value)546 RIME_API void RimeSetOption(RimeSessionId session_id, const char* option, Bool value) {
547   an<Session> session(Service::instance().GetSession(session_id));
548   if (!session)
549     return;
550   Context *ctx = session->context();
551   if (!ctx)
552     return;
553   ctx->set_option(option, !!value);
554 }
555 
RimeGetOption(RimeSessionId session_id,const char * option)556 RIME_API Bool RimeGetOption(RimeSessionId session_id, const char* option) {
557   an<Session> session(Service::instance().GetSession(session_id));
558   if (!session)
559     return False;
560   Context *ctx = session->context();
561   if (!ctx)
562     return False;
563   return Bool(ctx->get_option(option));
564 }
565 
RimeSetProperty(RimeSessionId session_id,const char * prop,const char * value)566 RIME_API void RimeSetProperty(RimeSessionId session_id, const char* prop, const char* value) {
567   an<Session> session(Service::instance().GetSession(session_id));
568   if (!session)
569     return;
570   Context *ctx = session->context();
571   if (!ctx)
572     return;
573   ctx->set_property(prop, value);
574 }
575 
RimeGetProperty(RimeSessionId session_id,const char * prop,char * value,size_t buffer_size)576 RIME_API Bool RimeGetProperty(RimeSessionId session_id, const char* prop,
577                               char* value, size_t buffer_size) {
578   an<Session> session(Service::instance().GetSession(session_id));
579   if (!session)
580     return False;
581   Context *ctx = session->context();
582   if (!ctx)
583     return False;
584   string str_value(ctx->get_property(prop));
585   if (str_value.empty())
586     return False;
587   strncpy(value, str_value.c_str(), buffer_size);
588   return True;
589 }
590 
RimeGetSchemaList(RimeSchemaList * output)591 RIME_API Bool RimeGetSchemaList(RimeSchemaList* output) {
592   if (!output) return False;
593   output->size = 0;
594   output->list = NULL;
595   Schema default_schema;
596   Config* config = default_schema.config();
597   if (!config) return False;
598   an<ConfigList> schema_list = config->GetList("schema_list");
599   if (!schema_list || schema_list->size() == 0)
600     return False;
601   output->list = new RimeSchemaListItem[schema_list->size()];
602   for (size_t i = 0; i < schema_list->size(); ++i) {
603     an<ConfigMap> item = As<ConfigMap>(schema_list->GetAt(i));
604     if (!item) continue;
605     an<ConfigValue> schema_property = item->GetValue("schema");
606     if (!schema_property) continue;
607     const string &schema_id(schema_property->str());
608     RimeSchemaListItem& x(output->list[output->size]);
609     x.schema_id = new char[schema_id.length() + 1];
610     strcpy(x.schema_id, schema_id.c_str());
611     Schema schema(schema_id);
612     x.name = new char[schema.schema_name().length() + 1];
613     strcpy(x.name, schema.schema_name().c_str());
614     x.reserved = NULL;
615     ++output->size;
616   }
617   if (output->size == 0) {
618     delete[] output->list;
619     output->list = NULL;
620     return False;
621   }
622   return True;
623 }
624 
RimeFreeSchemaList(RimeSchemaList * schema_list)625 RIME_API void RimeFreeSchemaList(RimeSchemaList* schema_list) {
626   if (!schema_list) return;
627   if (schema_list->list) {
628     for (size_t i = 0; i < schema_list->size; ++i) {
629       delete[] schema_list->list[i].schema_id;
630       delete[] schema_list->list[i].name;
631     }
632     delete[] schema_list->list;
633   }
634   schema_list->size = 0;
635   schema_list->list = NULL;
636 }
637 
RimeGetCurrentSchema(RimeSessionId session_id,char * schema_id,size_t buffer_size)638 RIME_API Bool RimeGetCurrentSchema(RimeSessionId session_id, char* schema_id, size_t buffer_size) {
639   an<Session> session(Service::instance().GetSession(session_id));
640   if (!session) return False;
641   Schema* schema = session->schema();
642   if (!schema) return False;
643   strncpy(schema_id, schema->schema_id().c_str(), buffer_size);
644   return True;
645 }
646 
RimeSelectSchema(RimeSessionId session_id,const char * schema_id)647 RIME_API Bool RimeSelectSchema(RimeSessionId session_id, const char* schema_id) {
648   if (!schema_id) return False;
649   an<Session> session(Service::instance().GetSession(session_id));
650   if (!session) return False;
651   session->ApplySchema(new Schema(schema_id));
652   return True;
653 }
654 
655 // config
656 
open_config_in_component(const char * config_component,const char * config_id,RimeConfig * config)657 static Bool open_config_in_component(const char* config_component,
658                                      const char* config_id,
659                                      RimeConfig* config) {
660   if (!config_id || !config) return False;
661   Config::Component* cc = Config::Require(config_component);
662   if (!cc) return False;
663   Config* c = cc->Create(config_id);
664   if (!c) return False;
665   config->ptr = (void*)c;
666   return True;
667 }
668 
RimeSchemaOpen(const char * schema_id,RimeConfig * config)669 RIME_API Bool RimeSchemaOpen(const char *schema_id, RimeConfig* config) {
670   return open_config_in_component("schema", schema_id, config);
671 }
672 
RimeConfigOpen(const char * config_id,RimeConfig * config)673 RIME_API Bool RimeConfigOpen(const char *config_id, RimeConfig* config) {
674   return open_config_in_component("config", config_id, config);
675 }
676 
RimeUserConfigOpen(const char * config_id,RimeConfig * config)677 RIME_API Bool RimeUserConfigOpen(const char* config_id, RimeConfig* config) {
678   return open_config_in_component("user_config", config_id, config);
679 }
680 
RimeConfigClose(RimeConfig * config)681 RIME_API Bool RimeConfigClose(RimeConfig *config) {
682   if (!config || !config->ptr) return False;
683   Config *c = reinterpret_cast<Config*>(config->ptr);
684   delete c;
685   config->ptr = NULL;
686   return True;
687 }
688 
RimeConfigGetBool(RimeConfig * config,const char * key,Bool * value)689 RIME_API Bool RimeConfigGetBool(RimeConfig *config, const char *key, Bool *value) {
690   if (!config || !key || !value) return False;
691   Config *c = reinterpret_cast<Config*>(config->ptr);
692   bool bool_value = false;
693   if (c->GetBool(key, &bool_value)) {
694     *value = Bool(bool_value);
695     return True;
696   }
697   return False;
698 }
699 
RimeConfigGetInt(RimeConfig * config,const char * key,int * value)700 RIME_API Bool RimeConfigGetInt(RimeConfig *config, const char *key, int *value) {
701   if (!config || !key || !value) return False;
702   Config *c = reinterpret_cast<Config*>(config->ptr);
703   return Bool(c->GetInt(key, value));
704 }
705 
RimeConfigGetDouble(RimeConfig * config,const char * key,double * value)706 RIME_API Bool RimeConfigGetDouble(RimeConfig *config, const char *key, double *value) {
707   if (!config || !key || !value) return False;
708   Config *c = reinterpret_cast<Config*>(config->ptr);
709   return Bool(c->GetDouble(key, value));
710 }
711 
RimeConfigGetString(RimeConfig * config,const char * key,char * value,size_t buffer_size)712 RIME_API Bool RimeConfigGetString(RimeConfig *config, const char *key,
713                                   char *value, size_t buffer_size) {
714   if (!config || !key || !value) return False;
715   Config *c = reinterpret_cast<Config*>(config->ptr);
716   if (!c) return False;
717   string str_value;
718   if (c->GetString(key, &str_value)) {
719     std::strncpy(value, str_value.c_str(), buffer_size);
720     return True;
721   }
722   return False;
723 }
724 
RimeConfigGetCString(RimeConfig * config,const char * key)725 RIME_API const char* RimeConfigGetCString(RimeConfig *config, const char *key) {
726   if (!config || !key) return NULL;
727   Config *c = reinterpret_cast<Config*>(config->ptr);
728   if (!c) return NULL;
729   if (an<ConfigValue> v = c->GetValue(key)) {
730     return v->str().c_str();
731   }
732   return NULL;
733 }
734 
RimeConfigUpdateSignature(RimeConfig * config,const char * signer)735 RIME_API Bool RimeConfigUpdateSignature(RimeConfig *config, const char* signer) {
736   if (!config || !signer) return False;
737   Config *c = reinterpret_cast<Config*>(config->ptr);
738   Deployer &deployer(Service::instance().deployer());
739   Signature sig(signer);
740   return Bool(sig.Sign(c, &deployer));
741 }
742 
743 template <class T>
744 struct RimeConfigIteratorImpl {
745   typename T::Iterator iter;
746   typename T::Iterator end;
747   string prefix;
748   string key;
749   string path;
750   RimeConfigIteratorImpl<T>(T& container, const string& root_path)
751       : iter(container.begin()),
752         end(container.end()) {
753     if (root_path.empty() || root_path == "/") {
754       // prefix is empty
755     }
756     else {
757       prefix = root_path + "/";
758     }
759   }
760 };
761 
RimeConfigBeginList(RimeConfigIterator * iterator,RimeConfig * config,const char * key)762 RIME_API Bool RimeConfigBeginList(RimeConfigIterator* iterator,
763                                   RimeConfig* config, const char* key) {
764   if (!iterator || !config || !key)
765     return False;
766   iterator->list = NULL;
767   iterator->map = NULL;
768   iterator->index = -1;
769   iterator->key = NULL;
770   iterator->path = NULL;
771   Config *c = reinterpret_cast<Config*>(config->ptr);
772   if (!c)
773     return False;
774   an<ConfigList> list = c->GetList(key);
775   if (!list)
776     return False;
777   iterator->list = new RimeConfigIteratorImpl<ConfigList>(*list, key);
778   return True;
779 }
780 
RimeConfigBeginMap(RimeConfigIterator * iterator,RimeConfig * config,const char * key)781 RIME_API Bool RimeConfigBeginMap(RimeConfigIterator* iterator,
782                                  RimeConfig* config, const char* key) {
783   if (!iterator || !config || !key)
784     return False;
785   iterator->list = NULL;
786   iterator->map = NULL;
787   iterator->index = -1;
788   iterator->key = NULL;
789   iterator->path = NULL;
790   Config *c = reinterpret_cast<Config*>(config->ptr);
791   if (!c) return False;
792   an<ConfigMap> m = c->GetMap(key);
793   if (!m) return False;
794   iterator->map = new RimeConfigIteratorImpl<ConfigMap>(*m, key);
795   return True;
796 }
797 
RimeConfigNext(RimeConfigIterator * iterator)798 RIME_API Bool RimeConfigNext(RimeConfigIterator* iterator) {
799   if (!iterator->list && !iterator->map)
800     return False;
801   if (iterator->list) {
802     RimeConfigIteratorImpl<ConfigList>* p =
803         reinterpret_cast<RimeConfigIteratorImpl<ConfigList>*>(iterator->list);
804     if (!p) return False;
805     if (++iterator->index > 0)
806       ++p->iter;
807     if (p->iter == p->end)
808       return False;
809     p->key = boost::str(boost::format("@%1%") % iterator->index);
810     p->path = p->prefix + p->key;
811     iterator->key = p->key.c_str();
812     iterator->path = p->path.c_str();
813     return True;
814   }
815   if (iterator->map) {
816     RimeConfigIteratorImpl<ConfigMap>* p =
817         reinterpret_cast<RimeConfigIteratorImpl<ConfigMap>*>(iterator->map);
818     if (!p) return False;
819     if (++iterator->index > 0)
820       ++p->iter;
821     if (p->iter == p->end)
822       return False;
823     p->key = p->iter->first;
824     p->path = p->prefix + p->key;
825     iterator->key = p->key.c_str();
826     iterator->path = p->path.c_str();
827     return True;
828   }
829   return False;
830 }
831 
RimeConfigEnd(RimeConfigIterator * iterator)832 RIME_API void RimeConfigEnd(RimeConfigIterator* iterator) {
833   if (!iterator) return;
834   if (iterator->list)
835     delete reinterpret_cast<RimeConfigIteratorImpl<ConfigList>*>(iterator->list);
836   if (iterator->map)
837     delete reinterpret_cast<RimeConfigIteratorImpl<ConfigMap>*>(iterator->map);
838   memset(iterator, 0, sizeof(RimeConfigIterator));
839 }
840 
841 
RimeSimulateKeySequence(RimeSessionId session_id,const char * key_sequence)842 RIME_API Bool RimeSimulateKeySequence(RimeSessionId session_id, const char *key_sequence) {
843   LOG(INFO) << "simulate key sequence: " << key_sequence;
844   an<Session> session(Service::instance().GetSession(session_id));
845   if (!session)
846     return False;
847   KeySequence keys;
848   if (!keys.Parse(key_sequence)) {
849     LOG(ERROR) << "error parsing input: '" << key_sequence << "'";
850     return False;
851   }
852   for (const KeyEvent& key : keys) {
853     session->ProcessKey(key);
854   }
855   return True;
856 }
857 
RimeRegisterModule(RimeModule * module)858 RIME_API Bool RimeRegisterModule(RimeModule* module) {
859   if (!module || !module->module_name)
860     return False;
861   ModuleManager::instance().Register(module->module_name, module);
862   return True;
863 }
864 
RimeFindModule(const char * module_name)865 RIME_API RimeModule* RimeFindModule(const char* module_name) {
866   return ModuleManager::instance().Find(module_name);
867 }
868 
RimeRunTask(const char * task_name)869 RIME_API Bool RimeRunTask(const char* task_name) {
870   if (!task_name)
871     return False;
872   Deployer &deployer(Service::instance().deployer());
873   return Bool(deployer.RunTask(task_name));
874 }
875 
RimeGetSharedDataDir()876 RIME_API const char* RimeGetSharedDataDir() {
877   Deployer &deployer(Service::instance().deployer());
878   return deployer.shared_data_dir.c_str();
879 }
880 
RimeGetUserDataDir()881 RIME_API const char* RimeGetUserDataDir() {
882   Deployer &deployer(Service::instance().deployer());
883   return deployer.user_data_dir.c_str();
884 }
885 
RimeGetPrebuiltDataDir()886 RIME_API const char* RimeGetPrebuiltDataDir() {
887   Deployer &deployer(Service::instance().deployer());
888   return deployer.prebuilt_data_dir.c_str();
889 }
890 
RimeGetStagingDir()891 RIME_API const char* RimeGetStagingDir() {
892   Deployer &deployer(Service::instance().deployer());
893   return deployer.staging_dir.c_str();
894 }
895 
RimeGetSyncDir()896 RIME_API const char* RimeGetSyncDir() {
897   Deployer &deployer(Service::instance().deployer());
898   return deployer.sync_dir.c_str();
899 }
900 
RimeGetUserId()901 RIME_API const char* RimeGetUserId() {
902   Deployer &deployer(Service::instance().deployer());
903   return deployer.user_id.c_str();
904 }
905 
RimeGetUserDataSyncDir(char * dir,size_t buffer_size)906 RIME_API void RimeGetUserDataSyncDir(char* dir, size_t buffer_size) {
907   Deployer &deployer(Service::instance().deployer());
908   strncpy(dir, deployer.user_data_sync_dir().c_str(), buffer_size);
909 }
910 
RimeConfigInit(RimeConfig * config)911 RIME_API Bool RimeConfigInit(RimeConfig* config) {
912   if (!config || config->ptr)
913     return False;
914   config->ptr = (void*)new Config;
915   return True;
916 }
917 
RimeConfigLoadString(RimeConfig * config,const char * yaml)918 RIME_API Bool RimeConfigLoadString(RimeConfig* config, const char* yaml) {
919   if (!config || !yaml) {
920     return False;
921   }
922   if (!config->ptr) {
923     RimeConfigInit(config);
924   }
925   Config* c = reinterpret_cast<Config*>(config->ptr);
926   std::istringstream iss(yaml);
927   return Bool(c->LoadFromStream(iss));
928 }
929 
RimeConfigGetItem(RimeConfig * config,const char * key,RimeConfig * value)930 RIME_API Bool RimeConfigGetItem(RimeConfig* config, const char* key, RimeConfig* value) {
931   if (!config || !key || !value)
932     return False;
933   Config* c = reinterpret_cast<Config*>(config->ptr);
934   if (!c)
935     return False;
936   if (!value->ptr) {
937     RimeConfigInit(value);
938   }
939   Config* v = reinterpret_cast<Config*>(value->ptr);
940   *v = c->GetItem(key);
941   return True;
942 }
943 
RimeConfigSetItem(RimeConfig * config,const char * key,RimeConfig * value)944 RIME_API Bool RimeConfigSetItem(RimeConfig* config, const char* key, RimeConfig* value) {
945   if (!config || !key)
946     return False;
947   Config* c = reinterpret_cast<Config*>(config->ptr);
948   if (!c)
949     return False;
950   an<ConfigItem> item;
951   if (value) {
952     if (Config* v = reinterpret_cast<Config*>(value->ptr)) {
953       item = v->GetItem("");
954     }
955   }
956   return Bool(c->SetItem(key, item));
957 }
958 
RimeConfigSetBool(RimeConfig * config,const char * key,Bool value)959 RIME_API Bool RimeConfigSetBool(RimeConfig* config, const char* key, Bool value) {
960   if (!config || !key)
961     return False;
962   Config* c = reinterpret_cast<Config*>(config->ptr);
963   if (!c)
964     return False;
965   return c->SetBool(key, value != False);
966 }
967 
RimeConfigSetInt(RimeConfig * config,const char * key,int value)968 RIME_API Bool RimeConfigSetInt(RimeConfig* config, const char* key, int value) {
969   if (!config || !key)
970     return False;
971   Config* c = reinterpret_cast<Config*>(config->ptr);
972   if (!c)
973     return False;
974   return Bool(c->SetInt(key, value));
975 }
976 
RimeConfigSetDouble(RimeConfig * config,const char * key,double value)977 RIME_API Bool RimeConfigSetDouble(RimeConfig* config, const char* key, double value) {
978   if (!config || !key)
979     return False;
980   Config* c = reinterpret_cast<Config*>(config->ptr);
981   if (!c)
982     return False;
983   return Bool(c->SetDouble(key, value));
984 }
985 
RimeConfigSetString(RimeConfig * config,const char * key,const char * value)986 RIME_API Bool RimeConfigSetString(RimeConfig* config, const char* key, const char* value) {
987   if (!config || !key || !value)
988     return False;
989   Config* c = reinterpret_cast<Config*>(config->ptr);
990   if (!c)
991     return False;
992   return Bool(c->SetString(key, value));
993 }
994 
RimeConfigClear(RimeConfig * config,const char * key)995 RIME_API Bool RimeConfigClear(RimeConfig* config, const char* key) {
996   if (!config || !key)
997     return False;
998   Config* c = reinterpret_cast<Config*>(config->ptr);
999   if (!c)
1000     return False;
1001   return Bool(c->SetItem(key, nullptr));
1002 }
1003 
RimeConfigCreateList(RimeConfig * config,const char * key)1004 RIME_API Bool RimeConfigCreateList(RimeConfig* config, const char* key) {
1005   if (!config || !key)
1006     return False;
1007   Config* c = reinterpret_cast<Config*>(config->ptr);
1008   if (!c)
1009     return False;
1010   return Bool(c->SetItem(key, New<ConfigList>()));
1011 }
1012 
RimeConfigCreateMap(RimeConfig * config,const char * key)1013 RIME_API Bool RimeConfigCreateMap(RimeConfig* config, const char* key) {
1014   if (!config || !key)
1015     return False;
1016   Config* c = reinterpret_cast<Config*>(config->ptr);
1017   if (!c)
1018     return False;
1019   return Bool(c->SetItem(key, New<ConfigMap>()));
1020 }
1021 
RimeConfigListSize(RimeConfig * config,const char * key)1022 RIME_API size_t RimeConfigListSize(RimeConfig* config, const char* key) {
1023   if (!config || !key)
1024     return 0;
1025   Config* c = reinterpret_cast<Config*>(config->ptr);
1026   if (!c)
1027     return 0;
1028   if (an<ConfigList> list = c->GetList(key)) {
1029     return list->size();
1030   }
1031   return 0;
1032 }
1033 
RimeGetInput(RimeSessionId session_id)1034 const char* RimeGetInput(RimeSessionId session_id) {
1035   an<Session> session(Service::instance().GetSession(session_id));
1036   if (!session)
1037     return NULL;
1038   Context *ctx = session->context();
1039   if (!ctx)
1040     return NULL;
1041   return ctx->input().c_str();
1042 }
1043 
RimeGetCaretPos(RimeSessionId session_id)1044 size_t RimeGetCaretPos(RimeSessionId session_id) {
1045   an<Session> session(Service::instance().GetSession(session_id));
1046   if (!session)
1047     return 0;
1048   Context *ctx = session->context();
1049   if (!ctx)
1050     return 0;
1051   return ctx->caret_pos();
1052 }
1053 
RimeSelectCandidate(RimeSessionId session_id,size_t index)1054 Bool RimeSelectCandidate(RimeSessionId session_id, size_t index) {
1055   an<Session> session(Service::instance().GetSession(session_id));
1056   if (!session)
1057     return False;
1058   Context *ctx = session->context();
1059   if (!ctx)
1060     return False;
1061   return Bool(ctx->Select(index));
1062 }
1063 
RimeSelectCandidateOnCurrentPage(RimeSessionId session_id,size_t index)1064 Bool RimeSelectCandidateOnCurrentPage(RimeSessionId session_id, size_t index) {
1065   an<Session> session(Service::instance().GetSession(session_id));
1066   if (!session)
1067     return False;
1068   Context *ctx = session->context();
1069   if (!ctx || !ctx->HasMenu())
1070     return False;
1071   Schema *schema = session->schema();
1072   if (!schema)
1073     return False;
1074   size_t page_size = (size_t)schema->page_size();
1075   if (index >= page_size)
1076     return False;
1077   const auto& seg(ctx->composition().back());
1078   size_t page_start = seg.selected_index / page_size * page_size;
1079   return Bool(ctx->Select(page_start + index));
1080 }
1081 
RimeGetVersion()1082 const char* RimeGetVersion() {
1083   return RIME_VERSION;
1084 }
1085 
RimeSetCaretPos(RimeSessionId session_id,size_t caret_pos)1086 void RimeSetCaretPos(RimeSessionId session_id, size_t caret_pos) {
1087   an<Session> session(Service::instance().GetSession(session_id));
1088   if (!session)
1089     return;
1090   Context *ctx = session->context();
1091   if (!ctx)
1092     return;
1093   return ctx->set_caret_pos(caret_pos);
1094 }
1095 
rime_get_api()1096 RIME_API RimeApi* rime_get_api() {
1097   static RimeApi s_api = {0};
1098   if (!s_api.data_size) {
1099     RIME_STRUCT_INIT(RimeApi, s_api);
1100     s_api.setup = &RimeSetup;
1101     s_api.set_notification_handler = &RimeSetNotificationHandler;
1102     s_api.initialize = &RimeInitialize;
1103     s_api.finalize = &RimeFinalize;
1104     s_api.start_maintenance = &RimeStartMaintenance;
1105     s_api.is_maintenance_mode = &RimeIsMaintenancing;
1106     s_api.join_maintenance_thread = &RimeJoinMaintenanceThread;
1107     s_api.deployer_initialize = &RimeDeployerInitialize;
1108     s_api.prebuild = &RimePrebuildAllSchemas;
1109     s_api.deploy = &RimeDeployWorkspace;
1110     s_api.deploy_schema = &RimeDeploySchema;
1111     s_api.deploy_config_file = &RimeDeployConfigFile;
1112     s_api.sync_user_data = &RimeSyncUserData;
1113     s_api.create_session = &RimeCreateSession;
1114     s_api.find_session = &RimeFindSession;
1115     s_api.destroy_session = &RimeDestroySession;
1116     s_api.cleanup_stale_sessions = &RimeCleanupStaleSessions;
1117     s_api.cleanup_all_sessions = &RimeCleanupAllSessions;
1118     s_api.process_key = &RimeProcessKey;
1119     s_api.commit_composition = &RimeCommitComposition;
1120     s_api.clear_composition = &RimeClearComposition;
1121     s_api.get_commit = &RimeGetCommit;
1122     s_api.free_commit = &RimeFreeCommit;
1123     s_api.get_context = &RimeGetContext;
1124     s_api.free_context = &RimeFreeContext;
1125     s_api.get_status =  &RimeGetStatus;
1126     s_api.free_status = &RimeFreeStatus;
1127     s_api.set_option = &RimeSetOption;
1128     s_api.get_option = &RimeGetOption;
1129     s_api.set_property = &RimeSetProperty;
1130     s_api.get_property = &RimeGetProperty;
1131     s_api.get_schema_list = &RimeGetSchemaList;
1132     s_api.free_schema_list = &RimeFreeSchemaList;
1133     s_api.get_current_schema = &RimeGetCurrentSchema;
1134     s_api.select_schema = &RimeSelectSchema;
1135     s_api.schema_open = &RimeSchemaOpen;
1136     s_api.config_open = &RimeConfigOpen;
1137     s_api.user_config_open = &RimeUserConfigOpen;
1138     s_api.config_close = &RimeConfigClose;
1139     s_api.config_get_bool = &RimeConfigGetBool;
1140     s_api.config_get_int = &RimeConfigGetInt;
1141     s_api.config_get_double = &RimeConfigGetDouble;
1142     s_api.config_get_string = &RimeConfigGetString;
1143     s_api.config_get_cstring = &RimeConfigGetCString;
1144     s_api.config_update_signature = &RimeConfigUpdateSignature;
1145     s_api.config_begin_map = &RimeConfigBeginMap;
1146     s_api.config_next = &RimeConfigNext;
1147     s_api.config_end = &RimeConfigEnd;
1148     s_api.simulate_key_sequence = &RimeSimulateKeySequence;
1149     s_api.register_module = &RimeRegisterModule;
1150     s_api.find_module = &RimeFindModule;
1151     s_api.run_task = &RimeRunTask;
1152     s_api.get_shared_data_dir = &RimeGetSharedDataDir;
1153     s_api.get_user_data_dir = &RimeGetUserDataDir;
1154     s_api.get_sync_dir = &RimeGetSyncDir;
1155     s_api.get_user_id = &RimeGetUserId;
1156     s_api.get_user_data_sync_dir = &RimeGetUserDataSyncDir;
1157     s_api.config_init = &RimeConfigInit;
1158     s_api.config_load_string = &RimeConfigLoadString;
1159     s_api.config_set_bool = &RimeConfigSetBool;
1160     s_api.config_set_int = &RimeConfigSetInt;
1161     s_api.config_set_double = &RimeConfigSetDouble;
1162     s_api.config_set_string = &RimeConfigSetString;
1163     s_api.config_get_item = &RimeConfigGetItem;
1164     s_api.config_set_item = &RimeConfigSetItem;
1165     s_api.config_clear = &RimeConfigClear;
1166     s_api.config_create_list = &RimeConfigCreateList;
1167     s_api.config_create_map = &RimeConfigCreateMap;
1168     s_api.config_list_size = &RimeConfigListSize;
1169     s_api.config_begin_list = &RimeConfigBeginList;
1170     s_api.get_input = &RimeGetInput;
1171     s_api.get_caret_pos = &RimeGetCaretPos;
1172     s_api.select_candidate = &RimeSelectCandidate;
1173     s_api.get_version = &RimeGetVersion;
1174     s_api.set_caret_pos = &RimeSetCaretPos;
1175     s_api.select_candidate_on_current_page = &RimeSelectCandidateOnCurrentPage;
1176     s_api.candidate_list_begin = &RimeCandidateListBegin;
1177     s_api.candidate_list_next = &RimeCandidateListNext;
1178     s_api.candidate_list_end = &RimeCandidateListEnd;
1179     s_api.candidate_list_from_index = &RimeCandidateListFromIndex;
1180     s_api.get_prebuilt_data_dir = &RimeGetPrebuiltDataDir;
1181     s_api.get_staging_dir = &RimeGetStagingDir;
1182     s_api.commit_proto = &RimeCommitProto;
1183     s_api.context_proto = &RimeContextProto;
1184     s_api.status_proto = &RimeStatusProto;
1185   }
1186   return &s_api;
1187 }
1188