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