1 #include "rime_config.h"
2 #include <string.h>
3 #include <rime_api.h>
4 #include "rime_engine.h"
5 #include "rime_settings.h"
6
7 // TODO:
8 #define _(x) (x)
9
10 extern RimeApi *rime_api;
11
12 typedef struct _IBusRimeEngine IBusRimeEngine;
13 typedef struct _IBusRimeEngineClass IBusRimeEngineClass;
14
15 struct _IBusRimeEngine {
16 IBusEngine parent;
17
18 /* members */
19 RimeSessionId session_id;
20 RimeStatus status;
21 IBusLookupTable* table;
22 IBusPropList* props;
23 };
24
25 struct _IBusRimeEngineClass {
26 IBusEngineClass parent;
27 };
28
29 /* functions prototype */
30 static void ibus_rime_engine_class_init (IBusRimeEngineClass *klass);
31 static void ibus_rime_engine_init (IBusRimeEngine *rime_engine);
32 static void ibus_rime_engine_destroy (IBusRimeEngine *rime_engine);
33 static gboolean ibus_rime_engine_process_key_event (IBusEngine *engine,
34 guint keyval,
35 guint keycode,
36 guint modifiers);
37 static void ibus_rime_engine_focus_in (IBusEngine *engine);
38 static void ibus_rime_engine_focus_out (IBusEngine *engine);
39 static void ibus_rime_engine_reset (IBusEngine *engine);
40 static void ibus_rime_engine_enable (IBusEngine *engine);
41 static void ibus_rime_engine_disable (IBusEngine *engine);
42 static void ibus_engine_set_cursor_location (IBusEngine *engine,
43 gint x,
44 gint y,
45 gint w,
46 gint h);
47 static void ibus_rime_engine_set_capabilities (IBusEngine *engine,
48 guint caps);
49 static void ibus_rime_engine_page_up (IBusEngine *engine);
50 static void ibus_rime_engine_page_down (IBusEngine *engine);
51 static void ibus_rime_engine_cursor_up (IBusEngine *engine);
52 static void ibus_rime_engine_cursor_down (IBusEngine *engine);
53 static void ibus_rime_engine_candidate_clicked (IBusEngine *engine,
54 guint index,
55 guint button,
56 guint state);
57 static void ibus_rime_engine_property_activate (IBusEngine *engine,
58 const gchar *prop_name,
59 guint prop_state);
60 static void ibus_rime_engine_property_show (IBusEngine *engine,
61 const gchar *prop_name);
62 static void ibus_rime_engine_property_hide (IBusEngine *engine,
63 const gchar *prop_name);
64
65 static void ibus_rime_engine_update (IBusRimeEngine *rime_engine);
66
G_DEFINE_TYPE(IBusRimeEngine,ibus_rime_engine,IBUS_TYPE_ENGINE)67 G_DEFINE_TYPE (IBusRimeEngine, ibus_rime_engine, IBUS_TYPE_ENGINE)
68
69 static void
70 ibus_rime_engine_class_init (IBusRimeEngineClass *klass)
71 {
72 IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass);
73 IBusEngineClass *engine_class = IBUS_ENGINE_CLASS (klass);
74
75 ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_rime_engine_destroy;
76
77 engine_class->process_key_event = ibus_rime_engine_process_key_event;
78 engine_class->focus_in = ibus_rime_engine_focus_in;
79 engine_class->focus_out = ibus_rime_engine_focus_out;
80 engine_class->reset = ibus_rime_engine_reset;
81 engine_class->enable = ibus_rime_engine_enable;
82 engine_class->disable = ibus_rime_engine_disable;
83 engine_class->property_activate = ibus_rime_engine_property_activate;
84 engine_class->candidate_clicked = ibus_rime_engine_candidate_clicked;
85 engine_class->page_up = ibus_rime_engine_page_up;
86 engine_class->page_down = ibus_rime_engine_page_down;
87 }
88
89 static void
ibus_rime_create_session(IBusRimeEngine * rime_engine)90 ibus_rime_create_session (IBusRimeEngine *rime_engine)
91 {
92 rime_engine->session_id = rime_api->create_session();
93 Bool inline_caret =
94 g_ibus_rime_settings.embed_preedit_text &&
95 g_ibus_rime_settings.preedit_style == PREEDIT_STYLE_COMPOSITION &&
96 g_ibus_rime_settings.cursor_type == CURSOR_TYPE_INSERT;
97 rime_api->set_option(rime_engine->session_id, "soft_cursor", !inline_caret);
98 }
99
100 static void
ibus_rime_engine_init(IBusRimeEngine * rime_engine)101 ibus_rime_engine_init (IBusRimeEngine *rime_engine)
102 {
103 ibus_rime_create_session(rime_engine);
104
105 RIME_STRUCT_INIT(RimeStatus, rime_engine->status);
106 RIME_STRUCT_CLEAR(rime_engine->status);
107
108 rime_engine->table = ibus_lookup_table_new(9, 0, TRUE, FALSE);
109 g_object_ref_sink(rime_engine->table);
110
111 rime_engine->props = ibus_prop_list_new();
112 g_object_ref_sink(rime_engine->props);
113
114 IBusProperty* prop;
115 IBusText* label;
116 IBusText* tips;
117 label = ibus_text_new_from_static_string("中文");
118 tips = ibus_text_new_from_static_string("中 ↔ A");
119 prop = ibus_property_new("InputMode",
120 PROP_TYPE_NORMAL,
121 label,
122 IBUS_RIME_ICONS_DIR "/zh.png",
123 tips,
124 TRUE,
125 TRUE,
126 PROP_STATE_UNCHECKED,
127 NULL);
128 ibus_prop_list_append(rime_engine->props, prop);
129 label = ibus_text_new_from_static_string("部署");
130 tips = ibus_text_new_from_static_string(_("Deploy"));
131 prop = ibus_property_new("deploy",
132 PROP_TYPE_NORMAL,
133 label,
134 IBUS_RIME_ICONS_DIR "/reload.png",
135 tips,
136 TRUE,
137 TRUE,
138 PROP_STATE_UNCHECKED,
139 NULL);
140 ibus_prop_list_append(rime_engine->props, prop);
141 label = ibus_text_new_from_static_string("同步");
142 tips = ibus_text_new_from_static_string(_("Sync data"));
143 prop = ibus_property_new("sync",
144 PROP_TYPE_NORMAL,
145 label,
146 IBUS_RIME_ICONS_DIR "/sync.png",
147 tips,
148 TRUE,
149 TRUE,
150 PROP_STATE_UNCHECKED,
151 NULL);
152 ibus_prop_list_append(rime_engine->props, prop);
153 }
154
155 static void
ibus_rime_engine_destroy(IBusRimeEngine * rime_engine)156 ibus_rime_engine_destroy (IBusRimeEngine *rime_engine)
157 {
158 if (rime_engine->session_id) {
159 rime_api->destroy_session(rime_engine->session_id);
160 rime_engine->session_id = 0;
161 }
162
163 if (rime_engine->status.schema_id) {
164 g_free(rime_engine->status.schema_id);
165 }
166 if (rime_engine->status.schema_name) {
167 g_free(rime_engine->status.schema_name);
168 }
169 RIME_STRUCT_CLEAR(rime_engine->status);
170
171 if (rime_engine->table) {
172 g_object_unref(rime_engine->table);
173 rime_engine->table = NULL;
174 }
175
176 if (rime_engine->props) {
177 g_object_unref(rime_engine->props);
178 rime_engine->props = NULL;
179 }
180
181 ((IBusObjectClass *) ibus_rime_engine_parent_class)->destroy(
182 (IBusObject *)rime_engine);
183 }
184
185 static void
ibus_rime_engine_focus_in(IBusEngine * engine)186 ibus_rime_engine_focus_in (IBusEngine *engine)
187 {
188 IBusRimeEngine *rime_engine = (IBusRimeEngine *)engine;
189 ibus_engine_register_properties(engine, rime_engine->props);
190 if (!rime_engine->session_id) {
191 ibus_rime_create_session(rime_engine);
192 }
193 ibus_rime_engine_update(rime_engine);
194 }
195
196 static void
ibus_rime_engine_focus_out(IBusEngine * engine)197 ibus_rime_engine_focus_out (IBusEngine *engine)
198 {
199 }
200
201 static void
ibus_rime_engine_reset(IBusEngine * engine)202 ibus_rime_engine_reset (IBusEngine *engine)
203 {
204 }
205
206 static void
ibus_rime_engine_enable(IBusEngine * engine)207 ibus_rime_engine_enable (IBusEngine *engine)
208 {
209 }
210
211 static void
ibus_rime_engine_disable(IBusEngine * engine)212 ibus_rime_engine_disable (IBusEngine *engine)
213 {
214 IBusRimeEngine *rime_engine = (IBusRimeEngine *)engine;
215 if (rime_engine->session_id) {
216 rime_api->destroy_session(rime_engine->session_id);
217 rime_engine->session_id = 0;
218 }
219 }
220
ibus_rime_update_status(IBusRimeEngine * rime_engine,RimeStatus * status)221 static void ibus_rime_update_status(IBusRimeEngine *rime_engine,
222 RimeStatus *status)
223 {
224 if (status &&
225 rime_engine->status.is_disabled == status->is_disabled &&
226 rime_engine->status.is_ascii_mode == status->is_ascii_mode &&
227 rime_engine->status.schema_id && status->schema_id &&
228 !strcmp(rime_engine->status.schema_id, status->schema_id)) {
229 // no updates
230 return;
231 }
232
233 rime_engine->status.is_disabled = status ? status->is_disabled : False;
234 rime_engine->status.is_ascii_mode = status ? status->is_ascii_mode : False;
235 if (rime_engine->status.schema_id) {
236 g_free(rime_engine->status.schema_id);
237 }
238 rime_engine->status.schema_id =
239 status && status->schema_id ? g_strdup(status->schema_id) : NULL;
240
241 IBusProperty* prop = ibus_prop_list_get(rime_engine->props, 0);
242 const gchar* icon;
243 IBusText* label;
244 if (prop) {
245 if (!status || status->is_disabled) {
246 icon = IBUS_RIME_ICONS_DIR "/disabled.png";
247 label = ibus_text_new_from_static_string("維護");
248 }
249 else if (status->is_ascii_mode) {
250 icon = IBUS_RIME_ICONS_DIR "/abc.png";
251 label = ibus_text_new_from_static_string("Abc");
252 }
253 else {
254 icon = IBUS_RIME_ICONS_DIR "/zh.png";
255 /* schema_name is ".default" in switcher */
256 if (status->schema_name && status->schema_name[0] != '.') {
257 label = ibus_text_new_from_string(status->schema_name);
258 }
259 else {
260 label = ibus_text_new_from_static_string("中文");
261 }
262 }
263 if (status && !status->is_disabled && ibus_text_get_length(label) > 0) {
264 gunichar c = g_utf8_get_char(ibus_text_get_text(label));
265 IBusText* symbol = ibus_text_new_from_unichar(c);
266 ibus_property_set_symbol(prop, symbol);
267 }
268 ibus_property_set_icon(prop, icon);
269 ibus_property_set_label(prop, label);
270 ibus_engine_update_property((IBusEngine *)rime_engine, prop);
271 }
272 }
273
ibus_rime_engine_update(IBusRimeEngine * rime_engine)274 static void ibus_rime_engine_update(IBusRimeEngine *rime_engine)
275 {
276 // update properties
277 RIME_STRUCT(RimeStatus, status);
278 if (rime_api->get_status(rime_engine->session_id, &status)) {
279 ibus_rime_update_status(rime_engine, &status);
280 rime_api->free_status(&status);
281 }
282 else {
283 ibus_rime_update_status(rime_engine, NULL);
284 }
285
286 // commit text
287 RIME_STRUCT(RimeCommit, commit);
288 if (rime_api->get_commit(rime_engine->session_id, &commit)) {
289 IBusText *text;
290 text = ibus_text_new_from_string(commit.text);
291 // the text object will be released by ibus
292 ibus_engine_commit_text((IBusEngine *)rime_engine, text);
293 rime_api->free_commit(&commit);
294 }
295
296 // begin updating UI
297
298 RIME_STRUCT(RimeContext, context);
299 if (!rime_api->get_context(rime_engine->session_id, &context) ||
300 context.composition.length == 0) {
301 ibus_engine_hide_preedit_text((IBusEngine *)rime_engine);
302 ibus_engine_hide_auxiliary_text((IBusEngine *)rime_engine);
303 ibus_engine_hide_lookup_table((IBusEngine *)rime_engine);
304 rime_api->free_context(&context);
305 return;
306 }
307
308 IBusText* inline_text = NULL;
309 IBusText* auxiliary_text = NULL;
310 guint inline_cursor_pos = 0;
311 int preedit_offset = 0;
312
313 const gboolean has_highlighted_span =
314 (context.composition.sel_start < context.composition.sel_end);
315
316 // display preview text inline, if the commit_text_preview API is supported.
317 if (g_ibus_rime_settings.embed_preedit_text &&
318 g_ibus_rime_settings.preedit_style == PREEDIT_STYLE_PREVIEW &&
319 RIME_STRUCT_HAS_MEMBER(context, context.commit_text_preview) &&
320 context.commit_text_preview) {
321 inline_text = ibus_text_new_from_string(context.commit_text_preview);
322 const guint inline_text_len = ibus_text_get_length(inline_text);
323 inline_cursor_pos =
324 g_ibus_rime_settings.cursor_type == CURSOR_TYPE_SELECT ?
325 g_utf8_strlen(context.composition.preedit,
326 context.composition.sel_start) :
327 inline_text_len;
328 inline_text->attrs = ibus_attr_list_new();
329 ibus_attr_list_append(
330 inline_text->attrs,
331 ibus_attr_underline_new(
332 IBUS_ATTR_UNDERLINE_SINGLE, 0, inline_text_len));
333 // show the unconverted range of preedit text as auxiliary text
334 if (has_highlighted_span) {
335 preedit_offset = context.composition.sel_start;
336 if (g_ibus_rime_settings.color_scheme) {
337 const guint start = g_utf8_strlen(
338 context.composition.preedit, context.composition.sel_start);
339 const guint end = inline_text_len;
340 ibus_attr_list_append(
341 inline_text->attrs,
342 ibus_attr_foreground_new(
343 g_ibus_rime_settings.color_scheme->text_color, start, end));
344 ibus_attr_list_append(
345 inline_text->attrs,
346 ibus_attr_background_new(
347 g_ibus_rime_settings.color_scheme->back_color, start, end));
348 }
349 } else {
350 // hide auxiliary text
351 preedit_offset = context.composition.length;
352 }
353 }
354 // display preedit text inline
355 else if (g_ibus_rime_settings.embed_preedit_text &&
356 g_ibus_rime_settings.preedit_style == PREEDIT_STYLE_COMPOSITION) {
357 inline_text = ibus_text_new_from_string(context.composition.preedit);
358 const guint inline_text_len = ibus_text_get_length(inline_text);
359 inline_cursor_pos =
360 g_ibus_rime_settings.cursor_type == CURSOR_TYPE_SELECT ?
361 g_utf8_strlen(context.composition.preedit,
362 context.composition.sel_start) :
363 g_utf8_strlen(context.composition.preedit,
364 context.composition.cursor_pos);
365 inline_text->attrs = ibus_attr_list_new();
366 ibus_attr_list_append(
367 inline_text->attrs,
368 ibus_attr_underline_new(
369 IBUS_ATTR_UNDERLINE_SINGLE, 0, inline_text_len));
370 if (has_highlighted_span && g_ibus_rime_settings.color_scheme) {
371 const guint start = g_utf8_strlen(
372 context.composition.preedit, context.composition.sel_start);
373 const glong end = g_utf8_strlen(
374 context.composition.preedit, context.composition.sel_end);
375 ibus_attr_list_append(
376 inline_text->attrs,
377 ibus_attr_foreground_new(
378 g_ibus_rime_settings.color_scheme->text_color, start, end));
379 ibus_attr_list_append(
380 inline_text->attrs,
381 ibus_attr_background_new(
382 g_ibus_rime_settings.color_scheme->back_color, start, end));
383 }
384 preedit_offset = context.composition.length;
385 }
386
387 // calculate auxiliary text
388 if (preedit_offset < context.composition.length) {
389 const char* preedit = context.composition.preedit + preedit_offset;
390 auxiliary_text = ibus_text_new_from_string(preedit);
391 // glong preedit_len = g_utf8_strlen(preedit, -1);
392 // glong cursor_pos = g_utf8_strlen(
393 // preedit, context.composition.cursor_pos - preedit_offset);
394 if (has_highlighted_span) {
395 auxiliary_text->attrs = ibus_attr_list_new();
396 const glong start = g_utf8_strlen(
397 preedit, context.composition.sel_start - preedit_offset);
398 const glong end = g_utf8_strlen(
399 preedit, context.composition.sel_end - preedit_offset);
400 ibus_attr_list_append(
401 auxiliary_text->attrs,
402 ibus_attr_foreground_new(RIME_COLOR_BLACK, start, end));
403 ibus_attr_list_append(
404 auxiliary_text->attrs,
405 ibus_attr_background_new(RIME_COLOR_LIGHT, start, end));
406 }
407 }
408
409 if (inline_text) {
410 ibus_engine_update_preedit_text(
411 (IBusEngine *)rime_engine, inline_text, inline_cursor_pos, TRUE);
412 } else {
413 ibus_engine_hide_preedit_text((IBusEngine *)rime_engine);
414 }
415 if (auxiliary_text) {
416 ibus_engine_update_auxiliary_text(
417 (IBusEngine *)rime_engine, auxiliary_text, TRUE);
418 } else {
419 ibus_engine_hide_auxiliary_text((IBusEngine *)rime_engine);
420 }
421
422 ibus_lookup_table_clear(rime_engine->table);
423 if (context.menu.num_candidates) {
424 int i;
425 int num_select_keys =
426 context.menu.select_keys ? strlen(context.menu.select_keys) : 0;
427 gboolean has_labels =
428 RIME_STRUCT_HAS_MEMBER(context, context.select_labels) &&
429 context.select_labels;
430 gboolean has_page_down = !context.menu.is_last_page;
431 gboolean has_page_up =
432 context.menu.is_last_page && context.menu.page_no > 0;
433 ibus_lookup_table_set_round(
434 rime_engine->table,
435 !(context.menu.is_last_page || context.menu.page_no == 0));
436 ibus_lookup_table_set_page_size(rime_engine->table, context.menu.page_size);
437 if (has_page_up) { //show page up for last page
438 for (i = 0; i < context.menu.page_size; ++i) {
439 ibus_lookup_table_append_candidate(
440 rime_engine->table, ibus_text_new_from_string(""));
441 }
442 }
443 for (i = 0; i < context.menu.num_candidates; ++i) {
444 gchar* text = context.menu.candidates[i].text;
445 gchar* comment = context.menu.candidates[i].comment;
446 IBusText *cand_text = NULL;
447 if (comment) {
448 gchar* temp = g_strconcat(text, " ", comment, NULL);
449 cand_text = ibus_text_new_from_string(temp);
450 g_free(temp);
451 int text_len = g_utf8_strlen(text, -1);
452 int end_index = ibus_text_get_length(cand_text);
453 ibus_text_append_attribute(cand_text,
454 IBUS_ATTR_TYPE_FOREGROUND,
455 RIME_COLOR_DARK,
456 text_len, end_index);
457 }
458 else {
459 cand_text = ibus_text_new_from_string(text);
460 }
461 ibus_lookup_table_append_candidate(rime_engine->table, cand_text);
462 IBusText *label = NULL;
463 if (i < context.menu.page_size && has_labels) {
464 label = ibus_text_new_from_string(context.select_labels[i]);
465 }
466 else if (i < num_select_keys) {
467 label = ibus_text_new_from_unichar(context.menu.select_keys[i]);
468 }
469 else {
470 label = ibus_text_new_from_printf("%d", (i + 1) % 10);
471 }
472 ibus_lookup_table_set_label(rime_engine->table, i, label);
473 }
474 if (has_page_down) { //show page down except last page
475 ibus_lookup_table_append_candidate(
476 rime_engine->table, ibus_text_new_from_string(""));
477 }
478 if (has_page_up) { //show page up for last page
479 ibus_lookup_table_set_cursor_pos(
480 rime_engine->table,
481 context.menu.page_size + context.menu.highlighted_candidate_index);
482 }
483 else {
484 ibus_lookup_table_set_cursor_pos(
485 rime_engine->table, context.menu.highlighted_candidate_index);
486 }
487 ibus_lookup_table_set_orientation(
488 rime_engine->table, g_ibus_rime_settings.lookup_table_orientation);
489 ibus_engine_update_lookup_table(
490 (IBusEngine *)rime_engine, rime_engine->table, TRUE);
491 }
492 else {
493 ibus_engine_hide_lookup_table((IBusEngine *)rime_engine);
494 }
495
496 // end updating UI
497 rime_api->free_context(&context);
498 }
499
500 static gboolean
ibus_rime_engine_process_key_event(IBusEngine * engine,guint keyval,guint keycode,guint modifiers)501 ibus_rime_engine_process_key_event (IBusEngine *engine,
502 guint keyval,
503 guint keycode,
504 guint modifiers)
505 {
506 // ignore super key
507 if (modifiers & IBUS_SUPER_MASK) {
508 return FALSE;
509 }
510
511 IBusRimeEngine *rime_engine = (IBusRimeEngine *)engine;
512
513 modifiers &= (IBUS_RELEASE_MASK | IBUS_LOCK_MASK | IBUS_SHIFT_MASK |
514 IBUS_CONTROL_MASK | IBUS_MOD1_MASK);
515
516 if (!rime_api->find_session(rime_engine->session_id)) {
517 ibus_rime_create_session(rime_engine);
518 }
519 if (!rime_engine->session_id) { // service disabled
520 ibus_rime_engine_update(rime_engine);
521 return FALSE;
522 }
523 gboolean result =
524 rime_api->process_key(rime_engine->session_id, keyval, modifiers);
525 ibus_rime_engine_update(rime_engine);
526 return result;
527 }
528
ibus_rime_engine_property_activate(IBusEngine * engine,const gchar * prop_name,guint prop_state)529 static void ibus_rime_engine_property_activate (IBusEngine *engine,
530 const gchar *prop_name,
531 guint prop_state)
532 {
533 extern void ibus_rime_start(gboolean full_check);
534 extern void ibus_rime_stop();
535 IBusRimeEngine *rime_engine = (IBusRimeEngine *)engine;
536 if (!strcmp("deploy", prop_name)) {
537 ibus_rime_stop();
538 ibus_rime_start(TRUE);
539 ibus_rime_engine_update(rime_engine);
540 }
541 else if (!strcmp("sync", prop_name)) {
542 // in the case a maintenance thread has already been started
543 // by start_maintenance(); the following call to sync_user_data()
544 // will queue data synching tasks for execution in the working
545 // maintenance thread, and will return False.
546 // however, there is still chance that the working maintenance thread
547 // happens to be quitting when new tasks are added, thus leaving newly
548 // added tasks undone...
549 rime_api->sync_user_data();
550 ibus_rime_engine_update(rime_engine);
551 }
552 else if (!strcmp("InputMode", prop_name)) {
553 rime_api->set_option(
554 rime_engine->session_id, "ascii_mode",
555 !rime_api->get_option(rime_engine->session_id, "ascii_mode"));
556 ibus_rime_engine_update(rime_engine);
557 }
558 }
559
ibus_rime_engine_candidate_clicked(IBusEngine * engine,guint index,guint button,guint state)560 static void ibus_rime_engine_candidate_clicked (IBusEngine *engine,
561 guint index,
562 guint button,
563 guint state)
564 {
565 IBusRimeEngine *rime_engine = (IBusRimeEngine *)engine;
566 if (RIME_API_AVAILABLE(rime_api, select_candidate)) {
567 RIME_STRUCT(RimeContext, context);
568 if (!rime_api->get_context(rime_engine->session_id, &context) ||
569 context.composition.length == 0) {
570 rime_api->free_context(&context);
571 return;
572 }
573 rime_api->select_candidate(
574 rime_engine->session_id,
575 context.menu.page_no * context.menu.page_size + index);
576 rime_api->free_context(&context);
577 ibus_rime_engine_update(rime_engine);
578 }
579 }
580
ibus_rime_engine_page_up(IBusEngine * engine)581 static void ibus_rime_engine_page_up (IBusEngine *engine)
582 {
583 IBusRimeEngine *rime_engine = (IBusRimeEngine *)engine;
584 rime_api->process_key(rime_engine->session_id, IBUS_KEY_Page_Up, 0);
585 ibus_rime_engine_update(rime_engine);
586 }
587
ibus_rime_engine_page_down(IBusEngine * engine)588 static void ibus_rime_engine_page_down (IBusEngine *engine)
589 {
590 IBusRimeEngine *rime_engine = (IBusRimeEngine *)engine;
591 rime_api->process_key(rime_engine->session_id, IBUS_KEY_Page_Down, 0);
592 ibus_rime_engine_update(rime_engine);
593 }
594