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