]> icculus.org git repositories - divverent/darkplaces.git/blob - uim.c
Merge branch 'master' 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         switch (key)
362         {
363         case K_TAB:
364                 return UKey_Tab;
365         case K_ENTER:
366                 return UKey_Return;
367         case K_ESCAPE:
368                 return UKey_Escape;
369         case K_SPACE:
370                 return ' ';
371
372         case K_BACKSPACE:
373                 return UKey_Backspace;
374         case K_UPARROW:
375                 return UKey_Up;
376         case K_DOWNARROW:
377                 return UKey_Down;
378         case K_LEFTARROW:
379                 return UKey_Left;
380         case K_RIGHTARROW:
381                 return UKey_Right;
382
383         case K_ALT:
384                 return UKey_Alt;
385         case K_CTRL:
386                 return UKey_Control;
387         case K_SHIFT:
388                 return UKey_Shift;
389
390         case K_F1:
391                 return UKey_F1;
392         case K_F2:
393                 return UKey_F2;
394         case K_F3:
395                 return UKey_F3;
396         case K_F4:
397                 return UKey_F4;
398         case K_F5:
399                 return UKey_F5;
400         case K_F6:
401                 return UKey_F6;
402         case K_F7:
403                 return UKey_F7;
404         case K_F8:
405                 return UKey_F8;
406         case K_F9:
407                 return UKey_F9;
408         case K_F10:
409                 return UKey_F10;
410         case K_F11:
411                 return UKey_F11;
412         case K_F12:
413                 return UKey_F12;
414
415         case K_INS:
416                 return UKey_Insert;
417         case K_DEL:
418         case K_KP_DEL:
419                 return UKey_Delete;
420         case K_PGUP:
421                 //case K_KP_PGUP:
422                 return UKey_Next;
423         case K_PGDN:
424                 //case K_KP_PGDN:
425                 return UKey_Prior;
426         case K_HOME:
427                 return UKey_Home;
428         case K_END:
429                 return UKey_End;
430
431         case K_NUMLOCK:
432                 return UKey_Num_Lock;
433         case K_CAPSLOCK:
434                 return UKey_Caps_Lock;
435         case K_SCROLLOCK:
436                 return UKey_Scroll_Lock;
437
438         case K_KP_0:
439                 return '0';
440         case K_KP_1:
441                 return '1';
442         case K_KP_2:
443                 return '2';
444         case K_KP_3:
445                 return '3';
446         case K_KP_4:
447                 return '4';
448         case K_KP_5:
449                 return '5';
450         case K_KP_6:
451                 return '6';
452         case K_KP_7:
453                 return '7';
454         case K_KP_8:
455                 return '8';
456         case K_KP_9:
457                 return '9';
458
459         default:
460                 if (unicode < 0x7E)
461                         return unicode;
462                 return 0;
463         }
464 }
465
466 // api entry, must check for UIM availability
467 qboolean UIM_KeyUp(int key, Uchar unicode)
468 {
469         if (!UIM_Available())
470                 return false;
471         quim.actions = 0;
472         return (quim_release_key(quim.ctx, UIM_KeyToUKey(key, unicode), UIM_GetKeyMod()) == 0) && !!quim.actions;
473 }
474
475 // api entry, must check for UIM availability
476 qboolean UIM_KeyDown(int key, Uchar unicode)
477 {
478         if (!UIM_Available())
479                 return false;
480         quim.actions = 0;
481         return (quim_press_key(quim.ctx, UIM_KeyToUKey(key, unicode), UIM_GetKeyMod()) == 0) && !!quim.actions;
482 }
483
484 // api entry, must check for UIM availability
485 qboolean UIM_Key(int key, Uchar unicode)
486 {
487         qboolean handled = false;
488         int mod, ukey;
489         if (!UIM_Available())
490                 return false;
491         quim.actions = 0;
492         mod = UIM_GetKeyMod();
493         ukey = UIM_KeyToUKey(key, unicode);
494         //Con_Printf("uim handling key: %i (mod: %i) char: %c\n", ukey, mod, (ukey >= 32 && ukey < 0x7F) ? ukey : '.');
495         if (quim_press_key(quim.ctx, ukey, mod) == 0)
496                 handled = true;
497         if (quim_release_key(quim.ctx, ukey, mod) == 0)
498                 handled = true;
499         return handled && !!quim.actions;
500 }
501
502 // api entry, must check for UIM availability
503 static char defcolor[3];
504 qboolean UIM_EnterBuffer(char *buffer, size_t bufsize, size_t pos, qUIM_SetCursor setcursor_cb)
505 {
506         if (!UIM_Available())
507                 return false;
508         if (quim.buffer)
509                 UIM_CancelBuffer();
510         if (!buffer)
511                 return false;
512
513         quim.actions = 0;
514         quim.pushcount = 0;
515         quim.active = false;
516         quim.buffer = buffer;
517         quim.buffer_size = bufsize;
518         quim.buffer_pos = pos;
519         quim.edit_start = pos;
520         quim.edit_pos = pos;
521         quim.edit_length = 0;
522         quim.length = strlen(buffer);
523         quim.tail_length = quim.length - quim.edit_start;
524         
525         quim.setcursor = setcursor_cb;
526
527         defcolor[0] = STRING_COLOR_TAG;
528         defcolor[1] = '7';
529         defcolor[2] = 0;
530         quim.pc = defcolor;
531         quim.pc_len = 2;
532
533         // search for the previous color code
534         if (pos >= 2)
535         {
536                 do
537                 {
538                         --pos;
539                         if (pos >= 1 && buffer[pos-1] == STRING_COLOR_TAG && isdigit(buffer[pos]))
540                         {
541                                 quim.pc = &buffer[pos-1];
542                                 quim.pc_len = 2;
543                                 break;
544                         }
545                         else if (pos >= 4 &&
546                                  buffer[pos-4] == STRING_COLOR_TAG &&
547                                  buffer[pos-3] == STRING_COLOR_RGB_TAG_CHAR &&
548                                  isxdigit(buffer[pos-2]) &&
549                                  isxdigit(buffer[pos-1]) &&
550                                  isxdigit(buffer[pos]))
551                         {
552                                 quim.pc = &buffer[pos-4];
553                                 quim.pc_len = 5;
554                                 break;
555                         }
556                 } while(pos);
557         }
558         return true;
559 }
560
561 // api entry, must check for UIM availability
562 void UIM_CancelBuffer(void)
563 {
564         if (!UIM_Available())
565                 return;
566         quim_reset_context(quim.ctx);
567         quim.buffer = NULL;
568         quim.setcursor = NULL;
569         quim.pc = defcolor;
570         quim.pc_len = 2;
571 }
572
573 // api entry, must check for UIM availability
574 void UIM_SetCursor(int pos)
575 {
576         UIM_EnterBuffer(quim.buffer, quim.buffer_size, pos, quim.setcursor);
577 }
578
579 static qboolean UIM_Insert2(const char *str, size_t _slen)
580 {
581         size_t slen;
582         if (!*str)
583                 return true;
584         if (_slen)
585                 slen = _slen;
586         else
587                 slen = strlen(str);
588         if (quim.edit_pos + slen + quim.tail_length >= quim.buffer_size - 1) {
589                 Con_Print("UIM: Insertion failed: not enough space!\n");
590                 return false;
591         }
592         //Con_Printf("Inserting [%s]\n", str);
593         //Con_Printf("Insertion point: %lu\n", (unsigned long)quim.edit_pos);
594         memmove(quim.buffer + quim.edit_pos + slen,
595                 quim.buffer + quim.edit_pos,
596                 quim.buffer_size - quim.edit_pos - slen);
597         memcpy(quim.buffer + quim.edit_pos, str, slen);
598         if (quim.edit_pos >= quim.length)
599                 quim.buffer[quim.edit_pos + slen] = 0;
600         quim.edit_pos += slen;
601         quim.edit_length += slen;
602         return true;
603 }
604
605 static inline qboolean UIM_Insert(const char *str)
606 {
607         return UIM_Insert2(str, 0);
608 }
609
610 static void UIM_Commit(void *cookie, const char *str)
611 {
612         ++quim.actions;
613         quim.pushed = 0;
614         //Con_Printf("UIM_Commit: %s\n", str);
615         UIM_Clear(cookie);
616         if (!UIM_Insert(str))
617         {
618                 Con_Print("UIM: Failed to commit buffer\n");
619                 return;
620         }
621         quim.buffer_pos = quim.edit_pos;
622         quim.edit_start = quim.edit_pos;
623         quim.edit_length = 0;
624         quim.pushcount = 0;
625         if (quim.setcursor)
626                 quim.setcursor(quim.buffer_pos);
627 }
628
629 static void UIM_HelperDisconnected(void)
630 {
631         ++quim.actions;
632         //Con_Print("UIM Helper disconnected\n");
633         if (quim.fd > 0)
634                 UIM_Shutdown();
635 }
636
637 static void UIM_Clear(void *cookie)
638 {
639         ++quim.actions;
640         quim.pushed = 0;
641         //Con_Print("UIM_Clear\n");
642         memmove(quim.buffer + quim.buffer_pos,
643                 quim.buffer + quim.edit_pos,
644                 quim.buffer_size - quim.edit_pos);
645         quim.edit_pos = quim.buffer_pos;
646         quim.edit_length = 0;
647         quim.cursor_pos = quim.edit_pos;
648         quim.cursor_length = 0;
649         quim.cursor_inpos = quim.edit_pos;
650         quim.cursor_inlength = 0;
651         quim.pushcount = 0;
652         if (quim.setcursor)
653                 quim.setcursor(quim.edit_pos);
654 }
655
656 // we have BUFSTART and QUIM_CURSOR
657 static void UIM_Push(void *cookie, int attr, const char *str)
658 {
659         ++quim.actions;
660         if (!str[0])
661                 return;
662         ++quim.pushed;
663         //Con_Printf("UIM_Push: (%i) [%s] <%lu>\n", attr, str, strlen(str));
664         if ((attr & (UPreeditAttr_Cursor | UPreeditAttr_Reverse)) == (UPreeditAttr_Cursor | UPreeditAttr_Reverse))
665         {
666                 quim.cursor_pos = quim.edit_pos;
667                 quim.cursor_length = 0;
668                 quim.cursor_inpos = quim.edit_pos;
669                 quim.cursor_inlength = 0;
670                 if (!UIM_Insert(im_cursor_start.string))
671                         return;
672                 quim.cursor_inpos = quim.edit_pos;
673                 quim.cursor_length = strlen(im_cursor_start.string);
674         }
675         if (attr & UPreeditAttr_UnderLine)
676         {
677                 if (!UIM_Insert(im_selection_start.string))
678                         return;
679         }
680         if (str[0])
681         {
682                 if (!UIM_Insert(str))
683                         return;
684         }
685         if (attr & UPreeditAttr_UnderLine)
686         {
687                 if (!UIM_Insert(im_selection_end.string))
688                         return;
689         }
690         if (attr & UPreeditAttr_Cursor)
691         {
692                 if (attr & UPreeditAttr_Reverse)
693                 {
694                         size_t slen = strlen(str);
695                         size_t cl = quim.cursor_length;
696                         quim.cursor_length += slen;
697                         quim.cursor_inlength += slen;
698                         if (!UIM_Insert(im_cursor_end.string))
699                                 return;
700                         quim.cursor_length += cl;
701                 }
702                 else
703                 {
704                         /*
705                         size_t epos = quim.edit_pos;
706                         size_t elen = quim.edit_length;
707                         */
708                         if (!UIM_Insert(im_cursor.string))
709                                 return;
710                         /*
711                         quim.edit_pos = epos;
712                         quim.edit_length = elen;
713                         */
714                 }
715         }
716 }
717
718 static void UIM_Update(void *cookie)
719 {
720         ++quim.actions;
721         if (quim.pushed) // do not append color to commits, only to pushed objects
722                 UIM_Insert2(quim.pc, quim.pc_len);
723         if (quim.setcursor)
724                 quim.setcursor(quim.edit_pos);
725         //Con_Print("UIM_Update\n");
726         // well, of course
727         // we could allocate a buffer in which we work
728         // and only update on "update"
729         // but who cares, seriously?
730 }
731
732 static void UIM_Activate(void *cookie, int nr, int display_limit)
733 {
734         ++quim.actions;
735         //Con_Print("UIM_Activate\n");
736         quim.active = true;
737         quim.active_count = nr;
738         quim.active_limit = display_limit;
739 }
740
741 static void UIM_Select(void *cookie, int index)
742 {
743         uim_candidate cd;
744         const char *str;
745         size_t slen;
746         ++quim.actions;
747
748         //Con_Print("UIM_Select\n");
749         cd = quim_get_candidate(quim.ctx, index, quim.active_count);
750         str = quim_candidate_get_cand_str(cd);
751         if (str)
752                 slen = strlen(str);
753
754         // check if we even fit into that buffer
755         if (!str ||
756             quim.edit_pos + quim.tail_length + slen - quim.cursor_inlength >= quim.buffer_size - 1)
757         {
758                 // erase the part in the cursor:
759                 memmove(quim.buffer + quim.cursor_inpos,
760                         quim.buffer + quim.cursor_inpos + quim.cursor_inlength,
761                         quim.buffer_size - quim.cursor_inpos - quim.cursor_inlength);
762                 quim.cursor_length -= quim.cursor_inlength;
763                 quim.cursor_inpos -= quim.cursor_inlength;
764                 quim.edit_length -= quim.cursor_inlength;
765                 quim.cursor_inlength = 0;
766                 if (str)
767                         Con_Print("Failed to select entry: buffer too short\n");
768                 return;
769         }
770
771         // we can insert this stuff
772         // move the cursor-end + tail
773         memmove(quim.buffer + quim.cursor_inpos + quim.cursor_inlength,
774                 quim.buffer + quim.cursor_pos + quim.cursor_length,
775                 quim.buffer_size - quim.cursor_pos - quim.cursor_length);
776         
777         memcpy(quim.buffer + quim.cursor_inpos, str, slen);
778
779         quim.edit_length = quim.edit_length + slen - quim.cursor_inlength;
780         quim.cursor_length = quim.cursor_length + slen - quim.cursor_inlength;
781         quim.cursor_inlength = slen;
782
783         quim_candidate_free(cd);
784 }
785
786 static void UIM_Shift(void *cookie, int dir)
787 {
788         ++quim.actions;
789         Con_Print("^1ERROR: ^7UIM_Shift: Not implemented\n");
790 }
791
792 static void UIM_Deactivate(void *cookie)
793 {
794         ++quim.actions;
795         quim.active = false;
796         //Con_Print("^3UIM: make UIM_Deactivate move the cursor...\n");
797 }
798
799 static void UIM_ConfigChanged(void *cookie)
800 {
801         ++quim.actions;
802         //Con_Print("UIM_ConfigChanged\n");
803 }
804
805 static void UIM_PropListUpdate(void *cookie, const char *str)
806 {
807         ++quim.actions;
808         if (quim.fd > 0)
809         {
810                 char buffer[1024<<2];
811                 //Con_Printf("UIM_PropListUpdate\n%s\n", str);
812                 dpsnprintf(buffer, sizeof(buffer), "prop_list_update\ncharset=UTF-8\n%s", str);
813                 quim_helper_send_message(quim.fd, buffer);
814         }
815 }