1 /* Copyright (C) 2007-2008 The Android Open Source Project
2 **
3 ** This software is licensed under the terms of the GNU General Public
4 ** License version 2, as published by the Free Software Foundation, and
5 ** may be copied, distributed, and modified under those terms.
6 **
7 ** This program is distributed in the hope that it will be useful,
8 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
9 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 ** GNU General Public License for more details.
11 */
12 #include "android/skin/keyboard.h"
13 #include "android/utils/debug.h"
14 #include "android/utils/bufprint.h"
15 #include "android/utils/system.h"
16 #include "android/android.h"
17
18 #define DEBUG 1
19
20 #if DEBUG
21 # define D(...) VERBOSE_PRINT(keys,__VA_ARGS__)
22 #else
23 # define D(...) ((void)0)
24 #endif
25
26
27 /** LAST PRESSED KEYS
28 ** a small buffer of last pressed keys, this is used to properly
29 ** implement the Unicode keyboard mode (SDL key up event always have
30 ** their .unicode field set to 0
31 **/
32 typedef struct {
33 int unicode; /* Unicode of last pressed key */
34 int sym; /* SDL key symbol value (e.g. SDLK_a) */
35 int mod; /* SDL key modifier value */
36 } LastKey;
37
38 #define MAX_LAST_KEYS 16
39 #define MAX_KEYCODES 256*2
40
41 struct SkinKeyboard {
42 const AKeyCharmap* charmap;
43 SkinKeyset* kset;
44 char enabled;
45 char raw_keys;
46 char last_count;
47 int keycode_count;
48
49 SkinRotation rotation;
50
51 SkinKeyCommandFunc command_func;
52 void* command_opaque;
53 SkinKeyEventFunc press_func;
54 void* press_opaque;
55
56 LastKey last_keys[ MAX_LAST_KEYS ];
57 int keycodes[ MAX_KEYCODES ];
58 };
59
60
61 void
skin_keyboard_set_keyset(SkinKeyboard * keyboard,SkinKeyset * kset)62 skin_keyboard_set_keyset( SkinKeyboard* keyboard, SkinKeyset* kset )
63 {
64 if (kset == NULL)
65 return;
66 if (keyboard->kset && keyboard->kset != android_keyset) {
67 skin_keyset_free(keyboard->kset);
68 }
69 keyboard->kset = kset;
70 }
71
72
73 const char*
skin_keyboard_charmap_name(SkinKeyboard * keyboard)74 skin_keyboard_charmap_name( SkinKeyboard* keyboard )
75 {
76 if (keyboard && keyboard->charmap)
77 return keyboard->charmap->name;
78
79 return "qwerty";
80 }
81
82 void
skin_keyboard_set_rotation(SkinKeyboard * keyboard,SkinRotation rotation)83 skin_keyboard_set_rotation( SkinKeyboard* keyboard,
84 SkinRotation rotation )
85 {
86 keyboard->rotation = (rotation & 3);
87 }
88
89 void
skin_keyboard_on_command(SkinKeyboard * keyboard,SkinKeyCommandFunc cmd_func,void * cmd_opaque)90 skin_keyboard_on_command( SkinKeyboard* keyboard, SkinKeyCommandFunc cmd_func, void* cmd_opaque )
91 {
92 keyboard->command_func = cmd_func;
93 keyboard->command_opaque = cmd_opaque;
94 }
95
96 void
skin_keyboard_on_key_press(SkinKeyboard * keyboard,SkinKeyEventFunc press_func,void * press_opaque)97 skin_keyboard_on_key_press( SkinKeyboard* keyboard, SkinKeyEventFunc press_func, void* press_opaque )
98 {
99 keyboard->press_func = press_func;
100 keyboard->press_opaque = press_opaque;
101 }
102
103 void
skin_keyboard_add_key_event(SkinKeyboard * kb,unsigned code,unsigned down)104 skin_keyboard_add_key_event( SkinKeyboard* kb,
105 unsigned code,
106 unsigned down )
107 {
108 if (code != 0 && kb->keycode_count < MAX_KEYCODES) {
109 //dprint("add keycode %d, down %d\n", code % 0x1ff, down );
110 kb->keycodes[(int)kb->keycode_count++] = ( (code & 0x1ff) | (down ? 0x200 : 0) );
111 }
112 }
113
114
115 void
skin_keyboard_flush(SkinKeyboard * kb)116 skin_keyboard_flush( SkinKeyboard* kb )
117 {
118 if (kb->keycode_count > 0) {
119 if (VERBOSE_CHECK(keys)) {
120 int nn;
121 printf(">> KEY" );
122 for (nn = 0; nn < kb->keycode_count; nn++) {
123 int code = kb->keycodes[nn];
124 printf(" [0x%03x,%s]", (code & 0x1ff), (code & 0x200) ? "down" : " up " );
125 }
126 printf( "\n" );
127 }
128 kbd_put_keycodes(kb->keycodes, kb->keycode_count);
129 kb->keycode_count = 0;
130 }
131 }
132
133
134 static void
skin_keyboard_cmd(SkinKeyboard * keyboard,SkinKeyCommand command,int param)135 skin_keyboard_cmd( SkinKeyboard* keyboard,
136 SkinKeyCommand command,
137 int param )
138 {
139 if (keyboard->command_func) {
140 keyboard->command_func( keyboard->command_opaque, command, param );
141 }
142 }
143
144
145 static LastKey*
skin_keyboard_find_last(SkinKeyboard * keyboard,int sym)146 skin_keyboard_find_last( SkinKeyboard* keyboard,
147 int sym )
148 {
149 LastKey* k = keyboard->last_keys;
150 LastKey* end = k + keyboard->last_count;
151
152 for ( ; k < end; k++ ) {
153 if (k->sym == sym)
154 return k;
155 }
156 return NULL;
157 }
158
159 static void
skin_keyboard_add_last(SkinKeyboard * keyboard,int sym,int mod,int unicode)160 skin_keyboard_add_last( SkinKeyboard* keyboard,
161 int sym,
162 int mod,
163 int unicode )
164 {
165 LastKey* k = keyboard->last_keys + keyboard->last_count;
166
167 if (keyboard->last_count < MAX_LAST_KEYS) {
168 k->sym = sym;
169 k->mod = mod;
170 k->unicode = unicode;
171
172 keyboard->last_count += 1;
173 }
174 }
175
176 static void
skin_keyboard_remove_last(SkinKeyboard * keyboard,int sym)177 skin_keyboard_remove_last( SkinKeyboard* keyboard,
178 int sym )
179 {
180 LastKey* k = keyboard->last_keys;
181 LastKey* end = k + keyboard->last_count;
182
183 for ( ; k < end; k++ ) {
184 if (k->sym == sym) {
185 /* we don't need a sorted array, so place the last
186 * element in place at the position of the removed
187 * one... */
188 k[0] = end[-1];
189 keyboard->last_count -= 1;
190 break;
191 }
192 }
193 }
194
195 static void
skin_keyboard_clear_last(SkinKeyboard * keyboard)196 skin_keyboard_clear_last( SkinKeyboard* keyboard )
197 {
198 keyboard->last_count = 0;
199 }
200
201 static int
skin_keyboard_rotate_sym(SkinKeyboard * keyboard,int sym)202 skin_keyboard_rotate_sym( SkinKeyboard* keyboard,
203 int sym )
204 {
205 switch (keyboard->rotation) {
206 case SKIN_ROTATION_90:
207 switch (sym) {
208 case SDLK_LEFT: sym = SDLK_DOWN; break;
209 case SDLK_RIGHT: sym = SDLK_UP; break;
210 case SDLK_UP: sym = SDLK_LEFT; break;
211 case SDLK_DOWN: sym = SDLK_RIGHT; break;
212 }
213 break;
214
215 case SKIN_ROTATION_180:
216 switch (sym) {
217 case SDLK_LEFT: sym = SDLK_RIGHT; break;
218 case SDLK_RIGHT: sym = SDLK_LEFT; break;
219 case SDLK_UP: sym = SDLK_DOWN; break;
220 case SDLK_DOWN: sym = SDLK_UP; break;
221 }
222 break;
223
224 case SKIN_ROTATION_270:
225 switch (sym) {
226 case SDLK_LEFT: sym = SDLK_UP; break;
227 case SDLK_RIGHT: sym = SDLK_DOWN; break;
228 case SDLK_UP: sym = SDLK_RIGHT; break;
229 case SDLK_DOWN: sym = SDLK_LEFT; break;
230 }
231 break;
232
233 default: ;
234 }
235 return sym;
236 }
237
238 static AndroidKeyCode
skin_keyboard_key_to_code(SkinKeyboard * keyboard,unsigned sym,int mod,int down)239 skin_keyboard_key_to_code( SkinKeyboard* keyboard,
240 unsigned sym,
241 int mod,
242 int down )
243 {
244 AndroidKeyCode code = 0;
245 int mod0 = mod;
246 SkinKeyCommand command;
247
248 /* first, handle the arrow keys directly */
249 /* rotate them if necessary */
250 sym = skin_keyboard_rotate_sym(keyboard, sym);
251 mod &= (KMOD_CTRL | KMOD_ALT | KMOD_SHIFT);
252
253 switch (sym) {
254 case SDLK_LEFT: code = kKeyCodeDpadLeft; break;
255 case SDLK_RIGHT: code = kKeyCodeDpadRight; break;
256 case SDLK_UP: code = kKeyCodeDpadUp; break;
257 case SDLK_DOWN: code = kKeyCodeDpadDown; break;
258 default: ;
259 }
260
261 if (code != 0) {
262 D("handling arrow (sym=%d mod=%d)", sym, mod);
263 if (!keyboard->raw_keys) {
264 int doCapL, doCapR, doAltL, doAltR;
265
266 if (!down) {
267 LastKey* k = skin_keyboard_find_last(keyboard, sym);
268 if (k != NULL) {
269 mod = k->mod;
270 skin_keyboard_remove_last( keyboard, sym );
271 }
272 } else {
273 skin_keyboard_add_last( keyboard, sym, mod, 0);
274 }
275
276 doCapL = (mod & 0x7ff) & KMOD_LSHIFT;
277 doCapR = (mod & 0x7ff) & KMOD_RSHIFT;
278 doAltL = (mod & 0x7ff) & KMOD_LALT;
279 doAltR = (mod & 0x7ff) & KMOD_RALT;
280
281 if (down) {
282 if (doAltL) skin_keyboard_add_key_event( keyboard, kKeyCodeAltLeft, 1 );
283 if (doAltR) skin_keyboard_add_key_event( keyboard, kKeyCodeAltRight, 1 );
284 if (doCapL) skin_keyboard_add_key_event( keyboard, kKeyCodeCapLeft, 1 );
285 if (doCapR) skin_keyboard_add_key_event( keyboard, kKeyCodeCapRight, 1 );
286 }
287 skin_keyboard_add_key_event(keyboard, code, down);
288
289 if (!down) {
290 if (doCapR) skin_keyboard_add_key_event( keyboard, kKeyCodeCapRight, 0 );
291 if (doCapL) skin_keyboard_add_key_event( keyboard, kKeyCodeCapLeft, 0 );
292 if (doAltR) skin_keyboard_add_key_event( keyboard, kKeyCodeAltRight, 0 );
293 if (doAltL) skin_keyboard_add_key_event( keyboard, kKeyCodeAltLeft, 0 );
294 }
295 code = 0;
296 }
297 return code;
298 }
299
300 /* special case for keypad keys, ignore them here if numlock is on */
301 if ((mod0 & KMOD_NUM) != 0) {
302 switch (sym) {
303 case SDLK_KP0:
304 case SDLK_KP1:
305 case SDLK_KP2:
306 case SDLK_KP3:
307 case SDLK_KP4:
308 case SDLK_KP5:
309 case SDLK_KP6:
310 case SDLK_KP7:
311 case SDLK_KP8:
312 case SDLK_KP9:
313 case SDLK_KP_PLUS:
314 case SDLK_KP_MINUS:
315 case SDLK_KP_MULTIPLY:
316 case SDLK_KP_DIVIDE:
317 case SDLK_KP_EQUALS:
318 case SDLK_KP_PERIOD:
319 case SDLK_KP_ENTER:
320 return 0;
321 }
322 }
323
324 /* now try all keyset combos */
325 command = skin_keyset_get_command( keyboard->kset, sym, mod );
326 if (command != SKIN_KEY_COMMAND_NONE) {
327 D("handling command %s from (sym=%d, mod=%d, str=%s)",
328 skin_key_command_to_str(command), sym, mod, skin_key_symmod_to_str(sym,mod));
329 skin_keyboard_cmd( keyboard, command, down );
330 return 0;
331 }
332 D("could not handle (sym=%d, mod=%d, str=%s)", sym, mod,
333 skin_key_symmod_to_str(sym,mod));
334 return -1;
335 }
336
337 /* this gets called only if the reverse unicode mapping didn't work
338 * or wasn't used (when in raw keys mode)
339 */
340 static AndroidKeyCode
skin_keyboard_raw_key_to_code(SkinKeyboard * kb,unsigned sym,int down)341 skin_keyboard_raw_key_to_code(SkinKeyboard* kb, unsigned sym, int down)
342 {
343 switch(sym){
344 case SDLK_1: return kKeyCode1;
345 case SDLK_2: return kKeyCode2;
346 case SDLK_3: return kKeyCode3;
347 case SDLK_4: return kKeyCode4;
348 case SDLK_5: return kKeyCode5;
349 case SDLK_6: return kKeyCode6;
350 case SDLK_7: return kKeyCode7;
351 case SDLK_8: return kKeyCode8;
352 case SDLK_9: return kKeyCode9;
353 case SDLK_0: return kKeyCode0;
354
355 case SDLK_q: return kKeyCodeQ;
356 case SDLK_w: return kKeyCodeW;
357 case SDLK_e: return kKeyCodeE;
358 case SDLK_r: return kKeyCodeR;
359 case SDLK_t: return kKeyCodeT;
360 case SDLK_y: return kKeyCodeY;
361 case SDLK_u: return kKeyCodeU;
362 case SDLK_i: return kKeyCodeI;
363 case SDLK_o: return kKeyCodeO;
364 case SDLK_p: return kKeyCodeP;
365 case SDLK_a: return kKeyCodeA;
366 case SDLK_s: return kKeyCodeS;
367 case SDLK_d: return kKeyCodeD;
368 case SDLK_f: return kKeyCodeF;
369 case SDLK_g: return kKeyCodeG;
370 case SDLK_h: return kKeyCodeH;
371 case SDLK_j: return kKeyCodeJ;
372 case SDLK_k: return kKeyCodeK;
373 case SDLK_l: return kKeyCodeL;
374 case SDLK_z: return kKeyCodeZ;
375 case SDLK_x: return kKeyCodeX;
376 case SDLK_c: return kKeyCodeC;
377 case SDLK_v: return kKeyCodeV;
378 case SDLK_b: return kKeyCodeB;
379 case SDLK_n: return kKeyCodeN;
380 case SDLK_m: return kKeyCodeM;
381 case SDLK_COMMA: return kKeyCodeComma;
382 case SDLK_PERIOD: return kKeyCodePeriod;
383 case SDLK_SPACE: return kKeyCodeSpace;
384 case SDLK_SLASH: return kKeyCodeSlash;
385 case SDLK_RETURN: return kKeyCodeNewline;
386 case SDLK_BACKSPACE: return kKeyCodeDel;
387
388 /* these are qwerty keys not on a device keyboard */
389 case SDLK_TAB: return kKeyCodeTab;
390 case SDLK_BACKQUOTE: return kKeyCodeGrave;
391 case SDLK_MINUS: return kKeyCodeMinus;
392 case SDLK_EQUALS: return kKeyCodeEquals;
393 case SDLK_LEFTBRACKET: return kKeyCodeLeftBracket;
394 case SDLK_RIGHTBRACKET: return kKeyCodeRightBracket;
395 case SDLK_BACKSLASH: return kKeyCodeBackslash;
396 case SDLK_SEMICOLON: return kKeyCodeSemicolon;
397 case SDLK_QUOTE: return kKeyCodeApostrophe;
398
399 case SDLK_RSHIFT: return kKeyCodeCapRight;
400 case SDLK_LSHIFT: return kKeyCodeCapLeft;
401 case SDLK_RMETA: return kKeyCodeSym;
402 case SDLK_LMETA: return kKeyCodeSym;
403 case SDLK_RALT: return kKeyCodeAltRight;
404 case SDLK_LALT: return kKeyCodeAltLeft;
405 case SDLK_RCTRL: return kKeyCodeSym;
406 case SDLK_LCTRL: return kKeyCodeSym;
407
408 default:
409 /* fprintf(stderr,"* unknown sdl keysym %d *\n", sym); */
410 return -1;
411 }
412 }
413
414
415 static void
skin_keyboard_do_key_event(SkinKeyboard * kb,AndroidKeyCode code,int down)416 skin_keyboard_do_key_event( SkinKeyboard* kb,
417 AndroidKeyCode code,
418 int down )
419 {
420 if (kb->press_func) {
421 kb->press_func( kb->press_opaque, code, down );
422 }
423 skin_keyboard_add_key_event(kb, code, down);
424 }
425
426
427 int
skin_keyboard_process_unicode_event(SkinKeyboard * kb,unsigned int unicode,int down)428 skin_keyboard_process_unicode_event( SkinKeyboard* kb, unsigned int unicode, int down )
429 {
430 const AKeyCharmap* cmap = kb->charmap;
431 int n;
432
433 if (unicode == 0)
434 return 0;
435
436 /* check base keys */
437 for (n = 0; n < cmap->num_entries; n++) {
438 if (cmap->entries[n].base == unicode) {
439 skin_keyboard_add_key_event(kb, cmap->entries[n].code, down);
440 return 1;
441 }
442 }
443
444 /* check caps + keys */
445 for (n = 0; n < cmap->num_entries; n++) {
446 if (cmap->entries[n].caps == unicode) {
447 if (down)
448 skin_keyboard_add_key_event(kb, kKeyCodeCapLeft, down);
449 skin_keyboard_add_key_event(kb, cmap->entries[n].code, down);
450 if (!down)
451 skin_keyboard_add_key_event(kb, kKeyCodeCapLeft, down);
452 return 2;
453 }
454 }
455
456 /* check fn + keys */
457 for (n = 0; n < cmap->num_entries; n++) {
458 if (cmap->entries[n].fn == unicode) {
459 if (down)
460 skin_keyboard_add_key_event(kb, kKeyCodeAltLeft, down);
461 skin_keyboard_add_key_event(kb, cmap->entries[n].code, down);
462 if (!down)
463 skin_keyboard_add_key_event(kb, kKeyCodeAltLeft, down);
464 return 2;
465 }
466 }
467
468 /* check caps + fn + keys */
469 for (n = 0; n < cmap->num_entries; n++) {
470 if (cmap->entries[n].caps_fn == unicode) {
471 if (down) {
472 skin_keyboard_add_key_event(kb, kKeyCodeAltLeft, down);
473 skin_keyboard_add_key_event(kb, kKeyCodeCapLeft, down);
474 }
475 skin_keyboard_add_key_event(kb, cmap->entries[n].code, down);
476 if (!down) {
477 skin_keyboard_add_key_event(kb, kKeyCodeCapLeft, down);
478 skin_keyboard_add_key_event(kb, kKeyCodeAltLeft, down);
479 }
480 return 3;
481 }
482 }
483
484 /* no match */
485 return 0;
486 }
487
488
489 void
skin_keyboard_enable(SkinKeyboard * keyboard,int enabled)490 skin_keyboard_enable( SkinKeyboard* keyboard,
491 int enabled )
492 {
493 keyboard->enabled = enabled;
494 if (enabled) {
495 SDL_EnableUNICODE(!keyboard->raw_keys);
496 SDL_EnableKeyRepeat(0,0);
497 }
498 }
499
500 void
skin_keyboard_process_event(SkinKeyboard * kb,SDL_Event * ev,int down)501 skin_keyboard_process_event( SkinKeyboard* kb, SDL_Event* ev, int down )
502 {
503 unsigned code;
504 int unicode = ev->key.keysym.unicode;
505 int sym = ev->key.keysym.sym;
506 int mod = ev->key.keysym.mod;
507
508 /* ignore key events if we're not enabled */
509 if (!kb->enabled) {
510 printf( "ignoring key event sym=%d mod=0x%x unicode=%d\n",
511 sym, mod, unicode );
512 return;
513 }
514
515 /* first, try the keyboard-mode-independent keys */
516 code = skin_keyboard_key_to_code( kb, sym, mod, down );
517 if (code == 0)
518 return;
519
520 if ((int)code > 0) {
521 skin_keyboard_do_key_event(kb, code, down);
522 skin_keyboard_flush(kb);
523 return;
524 }
525
526 /* Ctrl-K is used to switch between 'unicode' and 'raw' modes */
527 if (sym == SDLK_k)
528 {
529 int mod2 = mod & 0x7ff;
530
531 if ( mod2 == KMOD_LCTRL || mod2 == KMOD_RCTRL ) {
532 if (down) {
533 skin_keyboard_clear_last(kb);
534 kb->raw_keys = !kb->raw_keys;
535 SDL_EnableUNICODE(!kb->raw_keys);
536 D( "switching keyboard to %s mode", kb->raw_keys ? "raw" : "unicode" );
537 }
538 return;
539 }
540 }
541
542 if (!kb->raw_keys) {
543 /* ev->key.keysym.unicode is only valid on keydown events, and will be 0
544 * on the corresponding keyup ones, so remember the set of last pressed key
545 * syms to "undo" the job
546 */
547 if ( !down && unicode == 0 ) {
548 LastKey* k = skin_keyboard_find_last(kb, sym);
549 if (k != NULL) {
550 unicode = k->unicode;
551 skin_keyboard_remove_last(kb, sym);
552 }
553 }
554 }
555 if (!kb->raw_keys &&
556 skin_keyboard_process_unicode_event( kb, unicode, down ) > 0)
557 {
558 if (down)
559 skin_keyboard_add_last( kb, sym, mod, unicode );
560
561 skin_keyboard_flush( kb );
562 return;
563 }
564
565 code = skin_keyboard_raw_key_to_code( kb, sym, down );
566
567 if ( !kb->raw_keys &&
568 (code == kKeyCodeAltLeft || code == kKeyCodeAltRight ||
569 code == kKeyCodeCapLeft || code == kKeyCodeCapRight ||
570 code == kKeyCodeSym) )
571 return;
572
573 if (code == -1) {
574 D("ignoring keysym %d", sym );
575 } else if (code > 0) {
576 skin_keyboard_do_key_event(kb, code, down);
577 skin_keyboard_flush(kb);
578 }
579 }
580
581 static SkinKeyboard*
skin_keyboard_create_from_charmap_name(const char * charmap_name,int use_raw_keys)582 skin_keyboard_create_from_charmap_name(const char* charmap_name,
583 int use_raw_keys)
584 {
585 SkinKeyboard* kb;
586 int nn;
587
588 ANEW0(kb);
589
590 // Find charmap by its name in the array of available charmaps.
591 for (nn = 0; nn < android_charmap_count; nn++) {
592 if (!strcmp(android_charmaps[nn]->name, charmap_name)) {
593 kb->charmap = android_charmaps[nn];
594 break;
595 }
596 }
597
598 if (!kb->charmap) {
599 // Charmap name was not found. Default to the first charmap in the array.
600 fprintf(stderr, "### warning, skin requires unknown '%s' charmap, reverting to '%s'\n",
601 charmap_name, android_charmaps[0]->name );
602 kb->charmap = android_charmaps[0];
603 }
604 kb->raw_keys = use_raw_keys;
605 kb->enabled = 0;
606
607 /* add default keyset */
608 if (android_keyset)
609 kb->kset = android_keyset;
610 else
611 kb->kset = skin_keyset_new_from_text( skin_keyset_get_default() );
612
613 return kb;
614 }
615
616 SkinKeyboard*
skin_keyboard_create_from_aconfig(AConfig * aconfig,int use_raw_keys)617 skin_keyboard_create_from_aconfig( AConfig* aconfig, int use_raw_keys )
618 {
619 const char* charmap_name = "qwerty";
620 AConfig* node = aconfig_find( aconfig, "keyboard" );
621 if (node != NULL) {
622 charmap_name = aconfig_str(node, "charmap", charmap_name);
623 }
624 return skin_keyboard_create_from_charmap_name(charmap_name, use_raw_keys);
625 }
626
627 SkinKeyboard*
skin_keyboard_create_from_kcm(const char * kcm_file_path,int use_raw_keys)628 skin_keyboard_create_from_kcm( const char* kcm_file_path, int use_raw_keys )
629 {
630 char charmap_name[AKEYCHARMAP_NAME_SIZE];
631 kcm_extract_charmap_name(kcm_file_path, charmap_name,
632 sizeof(charmap_name));
633 return skin_keyboard_create_from_charmap_name(charmap_name, use_raw_keys);
634 }
635
636 void
skin_keyboard_free(SkinKeyboard * keyboard)637 skin_keyboard_free( SkinKeyboard* keyboard )
638 {
639 if (keyboard) {
640 AFREE(keyboard);
641 }
642 }
643