]> icculus.org git repositories - divverent/darkplaces.git/blob - uim.c
Merge branch 'ft2' into ft2uim
[divverent/darkplaces.git] / uim.c
1 /* UIM input support for
2  * DarkPlaces
3  */
4 #include "quakedef.h"
5
6 #include "keys.h"
7 #include "uim_dp.h"
8
9 //#include <locale.h>
10
11 /*
12 ================================================================================
13 CVars introduced with the UIM extension
14 ================================================================================
15 */
16
17 cvar_t im_enabled = {CVAR_SAVE, "im_enabled", "1", "use UIM input"};
18 cvar_t im_engine = {CVAR_SAVE, "im_engine", "anthy", "which input method to use if uim is supported and active"};
19 cvar_t im_language = {CVAR_SAVE, "im_language", "ja", "which language should be used for the input editor (en for english, ja for japanese, etc.)"};
20
21 cvar_t im_cursor = {CVAR_SAVE, "im_cursor", "", "the cursor to append to what you type when not selecting candidates - you don't want this for the chatbox"};
22 cvar_t im_cursor_start = {CVAR_SAVE, "im_cursor_start", "^3[^x888", "how to mark the beginning of the input cursor"};
23 cvar_t im_cursor_end = {CVAR_SAVE, "im_cursor_end", "^3]", "how to mark the end of the input cursor"};
24 cvar_t im_selection_start = {CVAR_SAVE, "im_selection_start", "^xf80", "how to mark the beginning of the current selection (as in, what UIM would display underlined)"};
25 cvar_t im_selection_end = {CVAR_SAVE, "im_selection_end", "", "how to mark the end of the current selection (as in, what UIM would display underlined)"};
26 cvar_t im_separator = {CVAR_SAVE, "im_separator", "|", "separator to use..."};
27
28 /*
29 ================================================================================
30 Library imports. Taken from the uim headers.
31 ================================================================================
32 */
33 struct uim_code_converter **quim_iconv;
34
35 int           (*quim_init)(void);
36 void          (*quim_quit)(void);
37 uim_candidate (*quim_get_candidate)(uim_context, int index, int accel_enum_hint);
38 void          (*quim_candidate_free)(uim_candidate);
39 int           (*quim_get_candidate_index)(uim_context);
40 const char*   (*quim_candidate_get_cand_str)(uim_candidate);
41 const char*   (*quim_get_default_im_name)(const char *localename);
42 uim_context   (*quim_create_context)(void *cookie,
43                                      const char *encoding,
44                                      const char *lang,
45                                      const char *engine,
46                                      struct uim_code_converter *conv,
47                                      void (*commit_cb)(void *ptr, const char *str));
48 void          (*quim_reset_context)(uim_context);
49 void          (*quim_release_context)(uim_context);
50 int           (*quim_helper_init_client_fd)(void (*disconnect_cb)(void));
51 void          (*quim_helper_close_client_fd)(int);
52 void          (*quim_helper_send_message)(int, const char*);
53 void          (*quim_set_preedit_cb)(uim_context,
54                                      void (*clear_cb)(void*),
55                                      void (*pushback_cb)(void*, int attr, const char *str),
56                                      void (*update_cb)(void*));
57 void          (*quim_set_candidate_selector_cb)(uim_context,
58                                                 void (*activate_cb)(void*, int nr, int display_limit),
59                                                 void (*select_cb)(void*, int index),
60                                                 void (*shift_page_cb)(void*, int direction),
61                                                 void (*deactivate_cb)(void*));
62 void          (*quim_prop_list_update)(uim_context);
63 int           (*quim_press_key)(uim_context, int key, int mods);
64 int           (*quim_release_key)(uim_context, int key, int mods);
65 void          (*quim_set_prop_list_update_cb)(uim_context, void (*update_cb)(void*, const char *str));
66 void          (*quim_set_configuration_changed_cb)(uim_context, void (*changed_cb)(void*));
67
68 /*
69 ================================================================================
70 Support for dynamically loading the UIM library
71 ================================================================================
72 */
73
74 static dllfunction_t uimfuncs[] =
75 {
76         {"uim_init",                    (void **) &quim_init},
77         {"uim_quit",                    (void **) &quim_quit},
78         {"uim_get_candidate",           (void **) &quim_get_candidate},
79         {"uim_candidate_free",          (void **) &quim_candidate_free},
80         {"uim_get_candidate_index",     (void **) &quim_get_candidate_index},
81         {"uim_candidate_get_cand_str",  (void **) &quim_candidate_get_cand_str},
82         {"uim_get_default_im_name",     (void **) &quim_get_default_im_name},
83         {"uim_create_context",          (void **) &quim_create_context},
84         {"uim_reset_context",           (void **) &quim_reset_context},
85         {"uim_release_context",         (void **) &quim_release_context},
86         {"uim_helper_init_client_fd",   (void **) &quim_helper_init_client_fd},
87         {"uim_helper_close_client_fd",  (void **) &quim_helper_close_client_fd},
88         {"uim_helper_send_message",     (void **) &quim_helper_send_message},
89         {"uim_set_preedit_cb",          (void **) &quim_set_preedit_cb},
90         {"uim_set_candidate_selector_cb",               (void **) &quim_set_candidate_selector_cb},
91         {"uim_prop_list_update",        (void **) &quim_prop_list_update},
92         {"uim_press_key",               (void **) &quim_press_key},
93         {"uim_release_key",             (void **) &quim_release_key},
94         {"uim_iconv",                   (void **) &quim_iconv},
95         {"uim_set_prop_list_update_cb", (void **) &quim_set_prop_list_update_cb},
96         {"uim_set_configuration_changed_cb", (void **) &quim_set_configuration_changed_cb},
97
98         {NULL, NULL}
99 };
100
101 /// Handle for UIM dll
102 static dllhandle_t uim_dll = NULL;
103
104 /*
105 ====================
106 UIM_CloseLibrary
107
108 Unload the UIM DLL
109 ====================
110 */
111 void UIM_CloseLibrary (void)
112 {
113         Sys_UnloadLibrary (&uim_dll);
114 }
115
116 /*
117 ====================
118 UIM_OpenLibrary
119
120 Try to load the UIM DLL
121 ====================
122 */
123 qboolean UIM_OpenLibrary (void)
124 {
125         const char* dllnames [] =
126         {
127 #if defined(WIN64)
128                 #error path for freetype 2 dll
129 #elif defined(WIN32)
130                 #error path for freetype 2 dll
131 #elif defined(MACOSX)
132                 "libuim.dylib",
133 #else
134                 "libuim.so.6",
135                 "libuim.so",
136 #endif
137                 NULL
138         };
139
140         // Already loaded?
141         if (uim_dll)
142                 return true;
143
144         // Load the DLL
145         if (!Sys_LoadLibrary (dllnames, &uim_dll, uimfuncs))
146                 return false;
147         return true;
148 }
149
150 /*
151 ================================================================================
152 UIM input method implementation.
153 ================================================================================
154 */
155
156 typedef struct
157 {
158         uim_context    ctx;
159         int            fd;
160         //mempool_t     *mempool;
161
162         char          *buffer;
163         size_t         buffer_pos;
164         size_t         buffer_size;
165         size_t         length;
166         size_t         edit_start;
167         size_t         edit_pos;
168         size_t         edit_length;
169         size_t         tail_length;
170         size_t         cursor_pos;
171         size_t         cursor_length;
172         size_t         cursor_inpos;
173         size_t         cursor_inlength;
174
175         // where can we find the previous color code?
176         size_t         pushcount;
177         const char    *pc;
178         size_t         pc_len;
179
180         qboolean       active;
181         int            active_count;
182         int            active_limit;
183
184         /*
185         char          *copybuffer;
186         size_t         copylen;
187         size_t         copypos;
188         */
189
190         qUIM_SetCursor setcursor;
191
192         // this is the only way K_RETURN actually sends a chat message
193         // when using stuff like anthy
194         int            actions;
195         int            pushed;
196 } quim_state;
197
198 static quim_state quim;
199
200 static void UIM_PropListUpdate(void*, const char *str);
201 static void UIM_Commit(void*, const char *str);
202 static void UIM_HelperDisconnected(void);
203 static void UIM_Clear(void*);
204 static void UIM_Push(void*, int attr, const char *str);
205 static void UIM_Update(void*);
206 static void UIM_Activate(void*, int nr, int display_limit);
207 static void UIM_Select(void*, int index);
208 static void UIM_Shift(void*, int dir);
209 static void UIM_Deactivate(void*);
210 static void UIM_ConfigChanged(void*);
211 static void UIM_Restart_f(void);
212
213 /*
214 ====================
215 UIM_Init
216
217 Load UIM support and register commands / cvars
218 ====================
219 */
220 void UIM_Start(void);
221 static qboolean UIM_InitConverter(void);
222 void UIM_Init(void)
223 {
224         // register the cvars in any case so they're at least saved,
225         // after all, they're for the user
226
227         Cvar_RegisterVariable(&im_engine);
228         Cvar_RegisterVariable(&im_language);
229         Cvar_RegisterVariable(&im_enabled);
230         Cvar_RegisterVariable(&im_cursor);
231         Cvar_RegisterVariable(&im_cursor_start);
232         Cvar_RegisterVariable(&im_cursor_end);
233         Cvar_RegisterVariable(&im_selection_start);
234         Cvar_RegisterVariable(&im_selection_end);
235         Cvar_RegisterVariable(&im_separator);
236         Cmd_AddCommand ("im_restart", UIM_Restart_f, "restart the input editor");
237
238         //quim.mempool = Mem_AllocPool("UIM", 0, NULL);
239         //UIM_Start();
240 }
241
242 static void UIM_Restart_f(void)
243 {
244         UIM_Shutdown();
245         UIM_Start();
246 }
247
248 static struct uim_code_converter *dp_converter;
249
250 static qboolean UIM_InitConverter(void)
251 {
252         /*
253         dp_converter = &dp_uim_conv;
254         dp_uim_conv.is_convertible = &conv_IsConvertible;
255         dp_uim_conv.create = &conv_Create;
256         dp_uim_conv.convert = &conv_Convert;
257         dp_uim_conv.release = &conv_Release;
258         */
259         dp_converter = *quim_iconv;
260         return true;
261 }
262
263 /*
264 ====================
265 UIM_Shutdown
266
267 Unload all UIM resources
268 ====================
269 */
270 void UIM_Shutdown(void)
271 {
272         if (UIM_Available())
273         {
274                 int fd = quim.fd;
275                 quim.fd = 0;
276                 UIM_CancelBuffer();
277                 quim_release_context(quim.ctx);
278                 quim_helper_close_client_fd(fd);
279                 quim.ctx = NULL;
280         }
281         //if (quim.mempool)
282         //      Mem_FreePool(&quim.mempool);
283 }
284
285 /*
286 ====================
287 UIM_Start
288
289 Try starting up UIM, this should be made available as console command maybe?
290 ====================
291 */
292 void UIM_Start(void)
293 {
294         if (!UIM_OpenLibrary())
295                 return;
296
297         if (!UIM_InitConverter())
298                 return;
299
300         //setlocale(LC_CTYPE, NULL);
301         if (quim_init() != 0)
302         {
303                 Con_Print("Failed to initialize UIM support\n");
304                 UIM_Shutdown();
305                 return;
306         }
307
308         quim.ctx = quim_create_context(NULL, "UTF-8", im_language.string, im_engine.string, dp_converter, &UIM_Commit);
309         if (quim.ctx == NULL)
310         {
311                 Con_Print("Failed to create UIM context\n");
312                 UIM_Shutdown();
313                 return;
314         }
315
316         quim.fd = quim_helper_init_client_fd(&UIM_HelperDisconnected);
317         if (quim.fd < 2) // well, in and out aren't exactly ... good
318         {
319                 Con_Print("Failed to initialize UIM helper client fd\n");
320                 UIM_Shutdown();
321                 return;
322         }
323         Con_Print("UIM Helper connected\n");
324
325         quim_set_preedit_cb(quim.ctx, &UIM_Clear, &UIM_Push, &UIM_Update);
326         quim_set_candidate_selector_cb(quim.ctx, &UIM_Activate, &UIM_Select, &UIM_Shift, &UIM_Deactivate);
327         quim_set_prop_list_update_cb(quim.ctx, &UIM_PropListUpdate);
328         quim_set_configuration_changed_cb(quim.ctx, &UIM_ConfigChanged);
329         quim_prop_list_update(quim.ctx);
330 }
331
332 // api entry, must check for UIM availability *ahem*
333 qboolean UIM_Available(void)
334 {
335         if (!im_enabled.integer)
336                 return false;
337         return (!!uim_dll && quim.ctx);
338 }
339
340 // api entry, must check for UIM availability
341 qboolean UIM_Direct(void)
342 {
343         if (!uim_dll || !quim.buffer || !quim.ctx || quim.fd <= 0 || !quim.buffer_size)
344                 return true;
345         // FIXME: direct!
346         return false;
347 }
348
349 static int UIM_GetKeyMod(void)
350 {
351         int mod = 0;
352         if (Key_IsPressed(K_SHIFT)) mod |= UMod_Shift;
353         if (Key_IsPressed(K_ALT))   mod |= UMod_Alt;
354         if (Key_IsPressed(K_CTRL))  mod |= UMod_Control;
355         if (Key_IsPressed(K_SUPER)) mod |= UMod_Super;
356         return mod;
357 }
358
359 static int UIM_KeyToUKey(int key, Uchar unicode)
360 {
361         int ofs = 0;
362         switch (key)
363         {
364         case K_TAB:
365                 return UKey_Tab;
366         case K_ENTER:
367                 return UKey_Return;
368         case K_ESCAPE:
369                 return UKey_Escape;
370         case K_SPACE:
371                 return ' ';
372
373         case K_BACKSPACE:
374                 return UKey_Backspace;
375         case K_UPARROW:
376                 return UKey_Up;
377         case K_DOWNARROW:
378                 return UKey_Down;
379         case K_LEFTARROW:
380                 return UKey_Left;
381         case K_RIGHTARROW:
382                 return UKey_Right;
383
384         case K_ALT:
385                 return UKey_Alt;
386         case K_CTRL:
387                 return UKey_Control;
388         case K_SHIFT:
389                 return UKey_Shift;
390
391         case K_F1: ++ofs;
392         case K_F2: ++ofs;
393         case K_F3: ++ofs;
394         case K_F4: ++ofs;
395         case K_F5: ++ofs;
396         case K_F6: ++ofs;
397         case K_F7: ++ofs;
398         case K_F8: ++ofs;
399         case K_F9: ++ofs;
400         case K_F10: ++ofs;
401         case K_F11: ++ofs;
402         case K_F12: ++ofs;
403                 return UKey_F1 + ofs;
404
405         case K_INS:
406                 return UKey_Insert;
407         case K_DEL:
408         case K_KP_DEL:
409                 return UKey_Delete;
410         case K_PGUP:
411                 //case K_KP_PGUP:
412                 return UKey_Next;
413         case K_PGDN:
414                 //case K_KP_PGDN:
415                 return UKey_Prior;
416         case K_HOME:
417                 return UKey_Home;
418         case K_END:
419                 return UKey_End;
420
421         case K_NUMLOCK:
422                 return UKey_Num_Lock;
423         case K_CAPSLOCK:
424                 return UKey_Caps_Lock;
425         case K_SCROLLOCK:
426                 return UKey_Scroll_Lock;
427
428         case K_KP_0:
429                 return '0';
430         case K_KP_1:
431                 return '1';
432         case K_KP_2:
433                 return '2';
434         case K_KP_3:
435                 return '3';
436         case K_KP_4:
437                 return '4';
438         case K_KP_5:
439                 return '5';
440         case K_KP_6:
441                 return '6';
442         case K_KP_7:
443                 return '7';
444         case K_KP_8:
445                 return '8';
446         case K_KP_9:
447                 return '9';
448
449         default:
450                 if (unicode < 0x7E)
451                         return unicode;
452                 return 0;
453         }
454 }
455
456 // api entry, must check for UIM availability
457 qboolean UIM_KeyUp(int key, Uchar unicode)
458 {
459         if (!UIM_Available())
460                 return false;
461         quim.actions = 0;
462         return (quim_release_key(quim.ctx, UIM_KeyToUKey(key, unicode), UIM_GetKeyMod()) == 0) && !!quim.actions;
463 }
464
465 // api entry, must check for UIM availability
466 qboolean UIM_KeyDown(int key, Uchar unicode)
467 {
468         if (!UIM_Available())
469                 return false;
470         quim.actions = 0;
471         return (quim_press_key(quim.ctx, UIM_KeyToUKey(key, unicode), UIM_GetKeyMod()) == 0) && !!quim.actions;
472 }
473
474 // api entry, must check for UIM availability
475 qboolean UIM_Key(int key, Uchar unicode)
476 {
477         qboolean handled = false;
478         int mod, ukey;
479         if (!UIM_Available())
480                 return false;
481         quim.actions = 0;
482         mod = UIM_GetKeyMod();
483         ukey = UIM_KeyToUKey(key, unicode);
484         //Con_Printf("uim handling key: %i (mod: %i) char: %c\n", ukey, mod, (ukey >= 32 && ukey < 0x7F) ? ukey : '.');
485         if (quim_press_key(quim.ctx, ukey, mod) == 0)
486                 handled = true;
487         if (quim_release_key(quim.ctx, ukey, mod) == 0)
488                 handled = true;
489         return handled && !!quim.actions;
490 }
491
492 // api entry, must check for UIM availability
493 static char defcolor[3];
494 qboolean UIM_EnterBuffer(char *buffer, size_t bufsize, size_t pos, qUIM_SetCursor setcursor_cb)
495 {
496         if (!UIM_Available())
497                 return false;
498         if (quim.buffer)
499                 UIM_CancelBuffer();
500         if (!buffer)
501                 return false;
502
503         quim.actions = 0;
504         quim.pushcount = 0;
505         quim.active = false;
506         quim.buffer = buffer;
507         quim.buffer_size = bufsize;
508         quim.buffer_pos = pos;
509         quim.edit_start = pos;
510         quim.edit_pos = pos;
511         quim.edit_length = 0;
512         quim.length = strlen(buffer);
513         quim.tail_length = quim.length - quim.edit_start;
514         
515         quim.setcursor = setcursor_cb;
516
517         defcolor[0] = STRING_COLOR_TAG;
518         defcolor[1] = '7';
519         defcolor[2] = 0;
520         quim.pc = defcolor;
521         quim.pc_len = 2;
522
523         // search for the previous color code
524         if (pos >= 2)
525         {
526                 do
527                 {
528                         --pos;
529                         if (pos >= 1 && buffer[pos-1] == STRING_COLOR_TAG && isdigit(buffer[pos]))
530                         {
531                                 quim.pc = &buffer[pos-1];
532                                 quim.pc_len = 2;
533                                 break;
534                         }
535                         else if (pos >= 4 &&
536                                  buffer[pos-4] == STRING_COLOR_TAG &&
537                                  buffer[pos-3] == STRING_COLOR_RGB_TAG_CHAR &&
538                                  isxdigit(buffer[pos-2]) &&
539                                  isxdigit(buffer[pos-1]) &&
540                                  isxdigit(buffer[pos]))
541                         {
542                                 quim.pc = &buffer[pos-4];
543                                 quim.pc_len = 5;
544                                 break;
545                         }
546                 } while(pos);
547         }
548         return true;
549 }
550
551 // api entry, must check for UIM availability
552 void UIM_CancelBuffer(void)
553 {
554         if (!UIM_Available())
555                 return;
556         quim_reset_context(quim.ctx);
557         quim.buffer = NULL;
558         quim.setcursor = NULL;
559         quim.pc = defcolor;
560         quim.pc_len = 2;
561 }
562
563 // api entry, must check for UIM availability
564 void UIM_SetCursor(int pos)
565 {
566         UIM_EnterBuffer(quim.buffer, quim.buffer_size, pos, quim.setcursor);
567 }
568
569 static qboolean UIM_Insert2(const char *str, size_t _slen)
570 {
571         size_t slen;
572         if (!*str)
573                 return true;
574         if (_slen)
575                 slen = _slen;
576         else
577                 slen = strlen(str);
578         if (quim.edit_pos + slen + quim.tail_length >= quim.buffer_size - 1) {
579                 Con_Print("UIM: Insertion failed: not enough space!\n");
580                 return false;
581         }
582         //Con_Printf("Inserting [%s]\n", str);
583         //Con_Printf("Insertion point: %lu\n", (unsigned long)quim.edit_pos);
584         memmove(quim.buffer + quim.edit_pos + slen,
585                 quim.buffer + quim.edit_pos,
586                 quim.buffer_size - quim.edit_pos - slen);
587         memcpy(quim.buffer + quim.edit_pos, str, slen);
588         if (quim.edit_pos >= quim.length)
589                 quim.buffer[quim.edit_pos + slen] = 0;
590         quim.edit_pos += slen;
591         quim.edit_length += slen;
592         return true;
593 }
594
595 static inline qboolean UIM_Insert(const char *str)
596 {
597         return UIM_Insert2(str, 0);
598 }
599
600 static void UIM_Commit(void *cookie, const char *str)
601 {
602         ++quim.actions;
603         quim.pushed = 0;
604         //Con_Printf("UIM_Commit: %s\n", str);
605         UIM_Clear(cookie);
606         if (!UIM_Insert(str))
607         {
608                 Con_Print("UIM: Failed to commit buffer\n");
609                 return;
610         }
611         quim.buffer_pos = quim.edit_pos;
612         quim.edit_start = quim.edit_pos;
613         quim.edit_length = 0;
614         quim.pushcount = 0;
615         if (quim.setcursor)
616                 quim.setcursor(quim.buffer_pos);
617 }
618
619 static void UIM_HelperDisconnected(void)
620 {
621         ++quim.actions;
622         //Con_Print("UIM Helper disconnected\n");
623         if (quim.fd > 0)
624                 UIM_Shutdown();
625 }
626
627 static void UIM_Clear(void *cookie)
628 {
629         ++quim.actions;
630         quim.pushed = 0;
631         //Con_Print("UIM_Clear\n");
632         memmove(quim.buffer + quim.buffer_pos,
633                 quim.buffer + quim.edit_pos,
634                 quim.buffer_size - quim.edit_pos);
635         quim.edit_pos = quim.buffer_pos;
636         quim.edit_length = 0;
637         quim.cursor_pos = quim.edit_pos;
638         quim.cursor_length = 0;
639         quim.cursor_inpos = quim.edit_pos;
640         quim.cursor_inlength = 0;
641         quim.pushcount = 0;
642         if (quim.setcursor)
643                 quim.setcursor(quim.edit_pos);
644 }
645
646 // we have BUFSTART and QUIM_CURSOR
647 static void UIM_Push(void *cookie, int attr, const char *str)
648 {
649         ++quim.actions;
650         if (!str[0])
651                 return;
652         ++quim.pushed;
653         //Con_Printf("UIM_Push: (%i) [%s] <%lu>\n", attr, str, strlen(str));
654         if ((attr & (UPreeditAttr_Cursor | UPreeditAttr_Reverse)) == (UPreeditAttr_Cursor | UPreeditAttr_Reverse))
655         {
656                 quim.cursor_pos = quim.edit_pos;
657                 quim.cursor_length = 0;
658                 quim.cursor_inpos = quim.edit_pos;
659                 quim.cursor_inlength = 0;
660                 if (!UIM_Insert(im_cursor_start.string))
661                         return;
662                 quim.cursor_inpos = quim.edit_pos;
663                 quim.cursor_length = strlen(im_cursor_start.string);
664         }
665         if (attr & UPreeditAttr_UnderLine)
666         {
667                 if (!UIM_Insert(im_selection_start.string))
668                         return;
669         }
670         if (str[0])
671         {
672                 if (!UIM_Insert(str))
673                         return;
674         }
675         if (attr & UPreeditAttr_UnderLine)
676         {
677                 if (!UIM_Insert(im_selection_end.string))
678                         return;
679         }
680         if (attr & UPreeditAttr_Cursor)
681         {
682                 if (attr & UPreeditAttr_Reverse)
683                 {
684                         size_t slen = strlen(str);
685                         size_t cl = quim.cursor_length;
686                         quim.cursor_length += slen;
687                         quim.cursor_inlength += slen;
688                         if (!UIM_Insert(im_cursor_end.string))
689                                 return;
690                         quim.cursor_length += cl;
691                 }
692                 else
693                 {
694                         /*
695                         size_t epos = quim.edit_pos;
696                         size_t elen = quim.edit_length;
697                         */
698                         if (!UIM_Insert(im_cursor.string))
699                                 return;
700                         /*
701                         quim.edit_pos = epos;
702                         quim.edit_length = elen;
703                         */
704                 }
705         }
706 }
707
708 static void UIM_Update(void *cookie)
709 {
710         ++quim.actions;
711         if (quim.pushed) // do not append color to commits, only to pushed objects
712                 UIM_Insert2(quim.pc, quim.pc_len);
713         if (quim.setcursor)
714                 quim.setcursor(quim.edit_pos);
715         //Con_Print("UIM_Update\n");
716         // well, of course
717         // we could allocate a buffer in which we work
718         // and only update on "update"
719         // but who cares, seriously?
720 }
721
722 static void UIM_Activate(void *cookie, int nr, int display_limit)
723 {
724         ++quim.actions;
725         //Con_Print("UIM_Activate\n");
726         quim.active = true;
727         quim.active_count = nr;
728         quim.active_limit = display_limit;
729 }
730
731 static void UIM_Select(void *cookie, int index)
732 {
733         uim_candidate cd;
734         const char *str;
735         size_t slen;
736         ++quim.actions;
737
738         //Con_Print("UIM_Select\n");
739         cd = quim_get_candidate(quim.ctx, index, quim.active_count);
740         str = quim_candidate_get_cand_str(cd);
741         if (str)
742                 slen = strlen(str);
743
744         // check if we even fit into that buffer
745         if (!str ||
746             quim.edit_pos + quim.tail_length + slen - quim.cursor_inlength >= quim.buffer_size - 1)
747         {
748                 // erase the part in the cursor:
749                 memmove(quim.buffer + quim.cursor_inpos,
750                         quim.buffer + quim.cursor_inpos + quim.cursor_inlength,
751                         quim.buffer_size - quim.cursor_inpos - quim.cursor_inlength);
752                 quim.cursor_length -= quim.cursor_inlength;
753                 quim.cursor_inpos -= quim.cursor_inlength;
754                 quim.edit_length -= quim.cursor_inlength;
755                 quim.cursor_inlength = 0;
756                 if (str)
757                         Con_Print("Failed to select entry: buffer too short\n");
758                 return;
759         }
760
761         // we can insert this stuff
762         // move the cursor-end + tail
763         memmove(quim.buffer + quim.cursor_inpos + quim.cursor_inlength,
764                 quim.buffer + quim.cursor_pos + quim.cursor_length,
765                 quim.buffer_size - quim.cursor_pos - quim.cursor_length);
766         
767         memcpy(quim.buffer + quim.cursor_inpos, str, slen);
768
769         quim.edit_length = quim.edit_length + slen - quim.cursor_inlength;
770         quim.cursor_length = quim.cursor_length + slen - quim.cursor_inlength;
771         quim.cursor_inlength = slen;
772
773         quim_candidate_free(cd);
774 }
775
776 static void UIM_Shift(void *cookie, int dir)
777 {
778         ++quim.actions;
779         Con_Print("^1ERROR: ^7UIM_Shift: Not implemented\n");
780 }
781
782 static void UIM_Deactivate(void *cookie)
783 {
784         ++quim.actions;
785         quim.active = false;
786         //Con_Print("^3UIM: make UIM_Deactivate move the cursor...\n");
787 }
788
789 static void UIM_ConfigChanged(void *cookie)
790 {
791         ++quim.actions;
792         //Con_Print("UIM_ConfigChanged\n");
793 }
794
795 static void UIM_PropListUpdate(void *cookie, const char *str)
796 {
797         ++quim.actions;
798         if (quim.fd > 0)
799         {
800                 char buffer[1024<<2];
801                 //Con_Printf("UIM_PropListUpdate\n%s\n", str);
802                 dpsnprintf(buffer, sizeof(buffer), "prop_list_update\ncharset=UTF-8\n%s", str);
803                 quim_helper_send_message(quim.fd, buffer);
804         }
805 }