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