1 /** @file scim_kmfl_imengine.cpp
2  * implementation of class KmflInstance.
3  */
4 
5 /*
6  * KMFL Input Method for SCIM (Smart Common Input Method)
7  *
8  * Copyright (C) 2005 SIL International
9  * based on source from SCIM Copyright (c) 2004 James Su <suzhe@tsinghua.org.cn>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24  *
25  */
26 
27 #include <stdarg.h>
28 #include <dirent.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <X11/X.h>
33 #include <X11/Xlib.h>
34 #include <X11/keysym.h>
35 #include <kmfl/kmfl.h>
36 #include <kmfl/libkmfl.h>
37 #include "xkbmap.h"
38 
39 #define Uses_SCIM_IMENGINE
40 #define Uses_SCIM_ICONV
41 #define Uses_SCIM_CONFIG_BASE
42 #define Uses_SCIM_CONFIG_PATH
43 #include "scim_kmfl_imengine_private.h"
44 
45 #include <scim.h>
46 #include <queue>
47 #include "scim_kmfl_imengine.h"
48 
49 #define scim_module_init kmfl_LTX_scim_module_init
50 #define scim_module_exit kmfl_LTX_scim_module_exit
51 #define scim_imengine_module_init kmfl_LTX_scim_imengine_module_init
52 #define scim_imengine_module_create_factory kmfl_LTX_scim_imengine_module_create_factory
53 
54 #ifndef SCIM_KMFL_IMENGINE_MODULE_DATADIR
55 #define SCIM_KMFL_IMENGINE_MODULE_DATADIR "/usr/share/scim/kmfl"
56 #endif
57 #define SCIM_KMFL_MAX_KEYBOARD_NUMBER  64
58 
59 #define KEY_AltRMask 0x10;
60 
61 #define COMMIT_KEYCODE 0xFFFE
62 #define COMMIT_KEYMASK 0x0F
63 
64 using namespace scim;
65 static unsigned int _scim_number_of_keyboards = 0;
66 
67 static Pointer < KmflFactory >
68     _scim_kmfl_imengine_factories[SCIM_KMFL_MAX_KEYBOARD_NUMBER];
69 
70 static std::vector < String > _scim_system_keyboard_list;
71 
72 static std::vector < String > _scim_user_keyboard_list;
73 
74 static ConfigPointer _scim_config;
75 
76 static Xkbmap xkbmap;
77 
78 static const char *_DEFAULT_LOCALES = N_("en_US.UTF-8,"
79                                          "en_AU.UTF-8,"
80                                          "en_CA.UTF-8,"
81                                          "en_GB.UTF-8,"
82                                          "my_MM.UTF-8,"
83                                          "zh_CN.UTF-8,zh_CN.GB18030,zh_CN.GBK,zh_CN.GB2312,zh_CN,"
84                                          "zh_TW.UTF-8,zh_TW.Big5,zh_TW,"
85                                          "zh_HK.UTF-8,zh_HK,"
86                                          "ja_JP.UTF-8,ja_JP.eucJP,ja_JP.ujis,ja_JP,ja,"
87                                          "ko_KR.UTF-8,ko_KR.eucKR,ko_KR");
88 
get_dirname(const String & path)89 static String get_dirname(const String & path)
90 {
91     size_t dirend = path.find_last_of(SCIM_PATH_DELIM_STRING);
92 
93     if (dirend > 0) {
94         return path.substr(0, dirend);
95     } else {
96         return String("");
97     }
98 }
99 
100 static void
_get_keyboard_list(std::vector<String> & keyboard_list,const String & path)101 _get_keyboard_list(std::vector < String > &keyboard_list,
102                    const String & path)
103 {
104     keyboard_list.clear();
105     DIR *dir = opendir(path.c_str());
106 
107     if (dir != NULL) {
108         struct dirent *file = readdir(dir);
109         while (file != NULL) {
110             struct stat filestat;
111             String absfn = path + SCIM_PATH_DELIM_STRING + file->d_name;
112             stat(absfn.c_str(), &filestat);
113 
114 
115             // Only .kmfl and .kmn extensions are valid keyboard files
116             if (S_ISREG(filestat.st_mode)
117                 && ((absfn.substr(absfn.length() - 5, 5) == ".kmfl"
118 				     && kmfl_check_keyboard(absfn.c_str()) == 0)
119 					|| absfn.substr(absfn.length() - 4, 4) == ".kmn")) {
120                 DBGMSG(1, "DAR: kmfl - found keyboard: %s\n",
121                        absfn.c_str());
122 
123                 keyboard_list.push_back(absfn);
124             }
125 
126             file = readdir(dir);
127         }
128         closedir(dir);
129     }
130 }
131 
132 extern "C" {
scim_module_init(void)133     void scim_module_init(void)
134     {
135 #ifdef DEBUGGING
136         kmfl_debug = 1;
137 #endif
138         DBGMSG(1, "DAR/JD: kmfl - Kmfl Module init!!!\n");
139     }
140 
scim_module_exit(void)141     void scim_module_exit(void)
142     {
143         DBGMSG(1, "DAR: kmfl - Kmfl Module exit\n");
144         for (UINT i = 0; i < _scim_number_of_keyboards; ++i) {
145             _scim_kmfl_imengine_factories[i].reset();
146         }
147 
148         _scim_config.reset();
149     }
150 
scim_imengine_module_init(const ConfigPointer & config)151     unsigned int scim_imengine_module_init(const ConfigPointer & config)
152     {
153         DBGMSG(1, "DAR: kmfl - Kmfl IMEngine Module init\n");
154 
155         _scim_config = config;
156         _get_keyboard_list(_scim_system_keyboard_list,
157                            SCIM_KMFL_IMENGINE_MODULE_DATADIR);
158         _get_keyboard_list(_scim_user_keyboard_list,
159                            scim_get_home_dir() + SCIM_PATH_DELIM_STRING +
160                            ".kmfl");
161 
162         _scim_number_of_keyboards =
163             _scim_system_keyboard_list.size() +
164             _scim_user_keyboard_list.size();
165         if (_scim_number_of_keyboards == 0) {
166             DBGMSG(1, "DAR: kmfl - No valid keyboards found\n");
167         }
168 
169         return _scim_number_of_keyboards;        // actually the number of files, may not all be valid
170     }
171 
scim_imengine_module_create_factory(unsigned int imengine)172     IMEngineFactoryPointer scim_imengine_module_create_factory(unsigned int imengine)
173     {
174         DBGMSG(1, "DAR: kmfl - Kmfl IMEngine Module Create Factory %d\n",
175                imengine);
176 
177         if (imengine >= _scim_number_of_keyboards) {
178             return 0;
179         }
180 
181         if (_scim_kmfl_imengine_factories[imengine].null()) {
182             _scim_kmfl_imengine_factories[imengine] = new KmflFactory();
183 
184             if (imengine < _scim_system_keyboard_list.size()) {
185                 if (!_scim_kmfl_imengine_factories[imengine]->
186                     load_keyboard(_scim_system_keyboard_list[imengine],
187                                   false))
188 					return 0;
189             } else {
190                 if (!_scim_kmfl_imengine_factories[imengine]->
191                     load_keyboard(_scim_user_keyboard_list
192                                   [imengine -
193                                    _scim_system_keyboard_list.size()],
194                                   true))
195 					return 0;
196             }
197 
198             if (!_scim_kmfl_imengine_factories[imengine]->valid()) {
199                 _scim_kmfl_imengine_factories[imengine].reset();
200             }
201 
202             char buf[2];
203             sprintf(buf, "%c", 21 + imengine);
204             _scim_kmfl_imengine_factories[imengine]->
205                 set_uuid(String("d1534208-27e5-8ec4-b2cd-df0fb0d2275") +
206                          String(buf));
207         }
208         return _scim_kmfl_imengine_factories[imengine];
209     }
210 }
211 
212 // Implementation of Kmfl
KmflFactory()213 KmflFactory::KmflFactory()
214 {
215     String current_locale = String (setlocale (LC_CTYPE, 0));
216 
217     if (current_locale.length() > 0) {
218         set_locales(String(_(_DEFAULT_LOCALES)) + String(",") +
219                 current_locale);
220     } else {
221         set_locales(String(_(_DEFAULT_LOCALES)));
222     }
223 }
224 
KmflFactory(const WideString & name,const String & locales)225 KmflFactory::KmflFactory(const WideString & name,
226                                      const String & locales)
227 {
228     if (locales == String("default")) {
229         String current_locale = String (setlocale (LC_CTYPE, 0));
230 
231         if (current_locale.length() > 0) {
232             set_locales(String(_(_DEFAULT_LOCALES)) + String(",") +
233                         current_locale);
234         } else {
235             set_locales(String(_(_DEFAULT_LOCALES)));
236         }
237     } else {
238         set_locales(locales);
239     }
240 }
241 
~KmflFactory()242 KmflFactory::~KmflFactory()
243 {
244     kmfl_unload_keyboard(m_keyboard_number);
245 }
246 
247 
load_keyboard(const String & keyboard_file,bool user_keyboard)248 bool KmflFactory::load_keyboard(const String & keyboard_file,
249                                       bool user_keyboard)
250 {
251     char buf[256];
252     KMSI * p_kmsi;
253     m_keyboard_file = keyboard_file;
254     DBGMSG(1, "DAR/jd: kmfl loading %s\n", keyboard_file.c_str());
255     if (keyboard_file.length()) {
256         m_keyboard_number =
257             kmfl_load_keyboard((char *) keyboard_file.c_str());
258         if (m_keyboard_number >= 0) {
259             m_name = WideString(utf8_mbstowcs(kmfl_keyboard_name(m_keyboard_number)));
260             DBGMSG(1, "DAR/jd: kmfl - Keyboard %s loaded\n",
261                    kmfl_keyboard_name(m_keyboard_number));
262 
263     		p_kmsi = kmfl_make_keyboard_instance(NULL);
264 
265 		    if (p_kmsi) {
266         		kmfl_attach_keyboard(p_kmsi, m_keyboard_number);
267 		        *buf='\0';
268 		        kmfl_get_header(p_kmsi,SS_AUTHOR,buf,sizeof(buf) - 1);
269 		    	m_Author=String(buf);
270 		        *buf='\0';
271 		        kmfl_get_header(p_kmsi,SS_COPYRIGHT,buf,sizeof(buf) - 1);
272 		    	m_Copyright=String(buf);
273 		        *buf='\0';
274 		        kmfl_get_header(p_kmsi,SS_LANGUAGE,buf,sizeof(buf) - 1);
275 		    	m_Language=String(buf);
276 		        kmfl_detach_keyboard(p_kmsi);
277 		        kmfl_delete_keyboard_instance(p_kmsi);
278 
279 		    }
280 		    if (m_Language.length() != 0)
281 	            set_languages(m_Language);
282             return valid();
283         }
284         return false;
285     }
286     return false;
287 }
288 
get_name() const289 WideString KmflFactory::get_name() const
290 {
291     return m_name;
292 }
293 
get_authors() const294 WideString KmflFactory::get_authors() const
295 {
296     return utf8_mbstowcs(m_Author);
297 }
298 
get_credits() const299 WideString KmflFactory::get_credits() const
300 {
301     return utf8_mbstowcs(m_Copyright);
302 }
303 
get_language() const304 String KmflFactory::get_language () const
305 {
306     return scim_validate_language(m_Language);
307 }
308 
get_help() const309 WideString KmflFactory::get_help() const
310 {
311     return utf8_mbstowcs(String(_("Hot Keys:\n\n"
312                                   "  Esc:\n"
313                                   "  reset the input method.\n")));
314 }
315 
set_uuid(const String & suuid)316 void KmflFactory::set_uuid(const String & suuid)
317 {
318     uuid = suuid;
319 }
320 
get_uuid() const321 String KmflFactory::get_uuid() const
322 {
323     return uuid;
324 }
325 
get_icon_file() const326 String KmflFactory::get_icon_file() const
327 {
328     String icon_file = kmfl_icon_file(m_keyboard_number);
329     String valid_extensions[3]= {"", ".bmp", ".png"};
330     String test_path;
331     if (icon_file.length() == 0) {
332         return String(SCIM_KMFL_IMENGINE_MODULE_DATADIR
333                       SCIM_PATH_DELIM_STRING "icons" SCIM_PATH_DELIM_STRING
334                       "default.png");
335     } else {
336         String full_path_to_icon_file =
337             get_dirname(m_keyboard_file) +
338             SCIM_PATH_DELIM_STRING "icons" SCIM_PATH_DELIM_STRING +
339             icon_file;
340         struct stat filestat;
341 
342         for (int i=0; i < 3; i++)
343         {
344             test_path=full_path_to_icon_file+valid_extensions[i];
345             stat(test_path.c_str(), &filestat);
346 
347             if (S_ISREG(filestat.st_mode))
348                 return test_path;
349         }
350 
351         return String("");
352     }
353 }
354 
355 IMEngineInstancePointer
create_instance(const String & encoding,int id)356     KmflFactory::create_instance(const String & encoding,
357                                               int id)
358 {
359     return new KmflInstance(this, encoding, id);
360 }
361 
362 // Implementation of KmflInstance
KmflInstance(KmflFactory * factory,const String & encoding,int id)363 KmflInstance::KmflInstance(KmflFactory * factory,
364                            const String & encoding, int id)
365 : IMEngineInstanceBase(factory, encoding, id), m_factory(factory),
366   m_forward(false), m_focused(false), m_unicode(false),
367   m_changelayout(false), m_iconv(encoding), p_kmsi(NULL), m_currentsymbols(""), m_keyboardlayout(""), m_keyboardlayoutactive(false)
368 {
369     m_display = XOpenDisplay(NULL);
370 
371     if (factory) {
372         p_kmsi = kmfl_make_keyboard_instance(this);
373 
374         if (p_kmsi) {
375             char buf[256];
376             int keyboard_number = factory->get_keyboard_number();
377             DBGMSG(1, "DAR: Loading keyboard %d\n", keyboard_number);
378 
379             kmfl_attach_keyboard(p_kmsi, keyboard_number);
380             *buf='\0';
381             if (kmfl_get_header(p_kmsi, SS_LAYOUT, buf, sizeof(buf) - 1)== 0) {
382                 m_keyboardlayout= buf;
383                 if (m_keyboardlayout.length() > 0) {
384                     *buf='\0';
385                     if (kmfl_get_header(p_kmsi,SS_MNEMONIC,buf,sizeof(buf) - 1) == 0) {
386                         if (*buf != '1' && *buf != '2') {
387                             m_changelayout= true;
388                         }
389                     } else {
390                         m_changelayout= true;
391                     }
392                 }
393             }
394         }
395     }
396     if (m_changelayout) {
397             DBGMSG(1, "DAR: change layout is set, layout is %s\n", m_keyboardlayout.c_str());
398     } else {
399             DBGMSG(1, "DAR: change layout is not set\n");
400     }
401 
402 }
403 
~KmflInstance()404 KmflInstance::~KmflInstance()
405 {
406     restore_system_layout();
407     if (p_kmsi) {
408         kmfl_detach_keyboard(p_kmsi);
409         kmfl_delete_keyboard_instance(p_kmsi);
410     }
411     p_kmsi = NULL;
412     XCloseDisplay(m_display);
413 }
activate_keyboard_layout(void)414 void KmflInstance::activate_keyboard_layout(void)
415 {
416     if (!m_keyboardlayoutactive) {
417         m_currentsymbols=xkbmap.getCurrentSymbols();
418         DBGMSG(1, "DAR: changing layout from %s to %s\n", m_currentsymbols.c_str(), m_keyboardlayout.c_str());
419         xkbmap.setLayout(m_keyboardlayout);
420         m_keyboardlayoutactive= true;
421     }
422 }
423 
restore_system_layout(void)424 void KmflInstance::restore_system_layout(void)
425 {
426     if (m_keyboardlayoutactive)        {
427         DBGMSG(1, "DAR: changing layout from %s to %s\n", m_keyboardlayout.c_str(), m_currentsymbols.c_str());
428         xkbmap.setSymbols(m_currentsymbols);
429         m_keyboardlayoutactive=false;
430     }
431 }
432 
is_key_pressed(char * key_vec,KeySym keysym)433 int KmflInstance::is_key_pressed(char *key_vec, KeySym keysym)
434 {
435     unsigned char keycode;
436     keycode = XKeysymToKeycode(m_display, keysym);
437     return key_vec[keycode >> 3] & (1 << (keycode & 7));
438 }
439 
process_key_event(const KeyEvent & key)440 bool KmflInstance::process_key_event(const KeyEvent & key)
441 {
442     int mask;
443     WideString context;
444     int cursor;
445 
446     if (!m_focused) {
447         return false;
448     }
449 
450     DBGMSG(1, "DAR: kmfl - Keyevent, code: %x, mask: %x\n", key.code,
451            key.mask);
452 
453     // Ignore key releases
454     if (key.is_key_release()) {
455         return true;
456     }
457 
458     if (key.code == SCIM_KEY_Sys_Req && (key.mask & SCIM_KEY_ControlMask) && (key.mask & SCIM_KEY_AltMask)){
459         DBGMSG(1, "DAR: kmfl -Reloading all keyboards\n");
460         kmfl_reload_all_keyboards();
461         return true;
462     }
463 
464     if (key.code == SCIM_KEY_Print && (key.mask & SCIM_KEY_ControlMask)) {
465         DBGMSG(1, "DAR: kmfl -Reloading keyboard %s\n", p_kmsi->kbd_name);
466         kmfl_reload_keyboard(p_kmsi->keyboard_number);
467         return true;
468     }
469 
470     if (!m_forward) {
471         // If a modifier key is pressed, check to see if it is a right modifier key
472         // This is rather expensive so only do it if a shift state is active
473         int right_modifier_mask = 0;
474         if (key.mask & (SCIM_KEY_ShiftMask | SCIM_KEY_ControlMask | SCIM_KEY_Mod1Mask)) {
475             char key_vec[32];
476             XQueryKeymap(m_display, key_vec);
477 
478             if ((key.mask & SCIM_KEY_Mod1Mask) && is_key_pressed(key_vec, SCIM_KEY_Alt_R)) {
479                 right_modifier_mask |= (SCIM_KEY_Mod1Mask << 8);
480             }
481 
482             if ((key.mask & SCIM_KEY_ControlMask) && is_key_pressed(key_vec, SCIM_KEY_Control_R)) {
483                 right_modifier_mask |= (SCIM_KEY_ControlMask << 8);
484             }
485 
486             if ((key.mask & SCIM_KEY_ShiftMask) && is_key_pressed(key_vec, SCIM_KEY_Shift_R)) {
487                 right_modifier_mask |= (SCIM_KEY_ShiftMask << 8);
488             }
489         }
490 
491         mask = key.mask | right_modifier_mask;
492 
493         DBGMSG(1, "DAR: kmfl - keymask %x\n", mask);
494 
495         // Reset key
496         if (key.code == SCIM_KEY_Pause) {
497             reset();
498             return true;
499         }
500 
501         DBGMSG(1, "DAR: kmfl - Checking sequences for %d\n", key.code);
502 
503     	if (!deadkey_in_history(p_kmsi)) {
504             if (get_surrounding_text (context, cursor, MAX_HISTORY, 0)) {
505                 UINT nItems= context.size ();
506                 ITEM items[MAX_HISTORY];
507 
508                 DBGMSG(1, "DAR: kmfl -  get_surround_text: cursor at %d, length = %d, string %s\n", cursor, nItems, utf8_wcstombs(context).c_str());
509                 for (unsigned int i=0; i< nItems; ++i) {
510                     items[nItems - i - 1] =  MAKE_ITEM(ITEM_CHAR,context [i]);
511                 }
512                 set_history(p_kmsi, items, nItems);
513             }
514         }
515 
516         if (kmfl_interpret(p_kmsi, key.code, mask) == 1) {
517             return true;
518             // Not a modifier key, ie shift, ctrl, alt, etc
519         } else if (!(key.code >= XK_Shift_L && key.code <= XK_Hyper_R)) {
520             DBGMSG(1, "DAR: kmfl - key.code causing reset %x\n", key.code);
521             reset();
522         }
523     }
524 
525     return false;
526 }
527 
reset()528 void KmflInstance::reset()
529 {
530 
531     DBGMSG(1, "DAR: kmfl - Reset called\n");
532 
533     // Clear the history for this instance (reset the context)
534     clear_history(p_kmsi);
535 
536     m_iconv.set_encoding(get_encoding());
537 }
538 
focus_in()539 void KmflInstance::focus_in()
540 {
541     if (m_changelayout && !m_forward) {
542         activate_keyboard_layout();
543     }
544     m_focused = true;
545     refresh_status_property();
546 
547     initialize_properties ();
548 }
549 
focus_out()550 void KmflInstance::focus_out()
551 {
552     if (m_changelayout) {
553         restore_system_layout();
554     }
555 
556     m_focused = false;
557 }
558 
toggle_input_status()559 void KmflInstance::toggle_input_status()
560 {
561     DBGMSG(1, "DAR: kmfl - toggle_input_status\n");
562 }
563 
trigger_property(const String & property)564 void KmflInstance::trigger_property(const String &property)
565 {
566     DBGMSG(1, "DAR: kmfl - trigger_property\n");
567 }
568 
initialize_properties()569 void KmflInstance::initialize_properties ()
570 {
571     PropertyList proplist;
572 
573     proplist.push_back (m_factory->m_status_property);
574 
575     register_properties (proplist);
576 
577     refresh_status_property ();
578 }
579 
refresh_status_property()580 void KmflInstance::refresh_status_property()
581 {
582     if (m_focused) {
583         if (m_forward) {
584             m_factory->m_status_property.set_label(_("En"));
585         } else if (m_unicode) {
586             m_factory->m_status_property.set_label(_("Unicode"));
587         } else {
588             m_factory->m_status_property.set_label(get_encoding());
589         }
590         update_property (m_factory->m_status_property);
591     }
592 
593 }
594 
forward_keyevent(unsigned int key,unsigned int state)595 void KmflInstance::forward_keyevent(unsigned int key, unsigned int state)
596 {
597     KeyEvent fkey(key, state);
598 
599     DBGMSG(1, "DAR: kmfl - forward key event key=%x, state=%x\n", key,state);
600 
601     forward_key_event(fkey);
602 }
603 
erase_char()604 void KmflInstance::erase_char()
605 {
606     KeyEvent backspacekey(SCIM_KEY_BackSpace, 0);
607 
608     WideString text;
609     int cursor;
610 
611     DBGMSG(1, "DAR: kmfl - backspace\n");
612 
613     if (get_surrounding_text (text, cursor, 1, 0)) {
614         if (!delete_surrounding_text(-1, 1)) {
615             DBGMSG(1, "DAR: delete_surrounding_text failed...forwarding key event\n");
616 
617             forward_key_event(backspacekey);
618             DBGMSG(1, "DAR: kmfl -  key event forwarded\n");
619         }
620     } else {
621         forward_key_event(backspacekey);
622         DBGMSG(1, "DAR: kmfl -  key event forwarded\n");
623     }
624 }
625 
output_string(const String & str)626 void KmflInstance::output_string(const String & str)
627 {
628     if (str.length() > 0) {
629         DBGMSG(1, "DAR: kmfl - committing string %s\n", str.c_str());
630 
631         commit_string(utf8_mbstowcs(str));
632     }
633 }
634 
output_beep()635 void KmflInstance::output_beep()
636 {
637 	beep();
638 }
639 
640 extern "C" {
641 
output_string(void * contrack,char * ptr)642     void output_string(void *contrack, char *ptr) {
643         if (ptr) {
644             ((KmflInstance *) contrack)->output_string(ptr);
645         }
646     }
647 
erase_char(void * contrack)648     void erase_char(void *contrack) {
649         ((KmflInstance *) contrack)->erase_char();
650     }
651 
output_char(void * contrack,unsigned char byte)652     void output_char(void *contrack, unsigned char byte) {
653         if (byte == 8) {
654             erase_char(contrack);
655         } else {
656             char s[2];
657             s[0] = byte;
658             s[1] = '\0';
659             output_string(contrack, s);
660         }
661     }
662 
forward_keyevent(void * contrack,unsigned int key,unsigned int state)663     void forward_keyevent(void *contrack, unsigned int key, unsigned int state)
664     {
665         ((KmflInstance *) contrack)->forward_keyevent(key, state);
666     }
667 
output_beep(void * contrack)668     void output_beep(void *contrack) {
669         DBGMSG(1, "DAR: kmfl - beep\n");
670         ((KmflInstance *) contrack)->output_beep();
671 
672     }
673 }                                /* extern "c" */
674 
675 /*
676 vi:ts=4:nowrap:ai:expandtab
677 */
678